Documentation¶
Getting started¶
Installation¶
Yacas is available for a variety of platforms. See http://www.yacas.org/getting_started/downloads/ for binary packages and installation instructions.
Installation from sources¶
Getting sources¶
Version 1.5.0 can be downloaded from https://github.com/grzegorzmazur/yacas/archive/v1.5.0.zip or https://github.com/grzegorzmazur/yacas/archive/v1.5.0.tar.gz, while the current development version is accessible from https://github.com/grzegorzmazur/yacas/archive/develop.zip.
Compilation¶
MacOS X¶
Open
Terminal
windowChange directory to the yacas source directory
Execute
mkdir build cd build cmake -G Xcode ..
Open generated project in
Xcode
Microsoft Windows¶
Open
Command Prompt
windowChange directory to the yacas source directory
Execute
mkdir build cd build cmake -G "Visual Studio 14 2015 Win64" ..
Open generated project in
Visual Studio
Linux¶
Open
Terminal
windowChange directory to the yacas source directory
Execute
mkdir build cd build cmake -DCMAKE_BUILD_TYPE=Release -DENABLE_DOCS=On .. make
To install newly built binaries execute
make install
Java¶
- Open
Terminal
orCommand Prompt
window - Change directory to the yacas source directory
- Execute
ant jar
Tutorial¶
Yacas syntax¶
Expressions in Yacas are generally built up of words. We will not bore
you with the exact definitions of such words, but roughly speaking
they are either sequences of alphabetic letters, or a number, or a
bracket, or space to separate words, or a word built up from symbols
like +
, -
, *
, <
, etc.. If you want, you can mix
these different types of characters, by surrounding them with
quotes. Thus, "This text"
is what is called one token, surrounded
by quotes.
The usual notation people use when writing down a calculation is
called the infix notation, and you can readily recognize it, as for
example 2+3
and 3*4
. Prefix operators also exist. These
operators come before an expression, like for example the unary minus
sign (called unary because it accepts one argument), -(3*4)
. In
addition to prefix operators there are also postfix operators, like
the exclamation mark to calculate the factorial of a number, 10!
.
Yacas understands standard simple arithmetic expressions. Some examples:
2+3
(addition)2*3
(multiplication)2-3
(subtraction)2^3
(raising powers)2+3*4
(2+3)*4
6/3
(division)1/3
Divisions are not reduced to real numbers, but kept as a rational for
as long as possible, since the rational is an exact correct expression
(and any real number would just be an approximation). Yacas is able to
change a rational in to a number with the function N
, for example
N(1/3)
.
Operators have precedence, meaning that certain operations are done
first before others are done. For example, in 2+3*4
the
multiplication is performed before the addition. The usual way to
change the order of a calculation is with round brackets. The round
brackets in the expression (2+3)*4
will force Yacas to first add 2
and 3, and then multiply the result.
Simple function calls have their arguments between round brackets,
separated by commas. Examples are Sin(Pi)
(which indicates that
you are interested in the value of the trigonometric function
\(\sin\) applied to the constant \(\pi\)), and
Min(5,1,3,-5,10)
(which should return the lowest of its arguments,
-5
in this case). Functions usually have the form f()
,
f(x)
or f(x,y,z,...)
depending on how many arguments the
function accepts. Functions always return a result. For example,
Cos(0)
should return 1
. Evaluating functions can be thought of
as simplifying an expression as much as possible. Sometimes further
simplification is not possible and a function returns itself
unsimplified, like taking the square root of an integer Sqrt(2)
. A
reduction to a number would be an approximation. We explain elsewhere
how to get Yacas to simplify an expression to a number.
Yacas allows for use of the infix notation, but with some
additions. Functions can be bodied, meaning that the last argument
is written past the close bracket. An example is ForEach
, where we
write ForEach(item, 1 .. 10) Echo(item);
. Echo(item)
is the
last argument to the function ForEach
. <br /> A list is enclosed
with curly braces, and is written out with commas between the
elements, like for example {1,2,3}
. items in lists (and things
that can be made to look like lists, like arrays and strings), can
then be accessed by indicating the index between square brackets after
the object. {a,b,c}[2]
should return b
, as b
is the second
element in the list (Yacas starts counting from 1 when accessing
elements). The same can be done with strings: "abc"[2]
. <br />
And finally, function calls can be grouped together, where they get
executed one at a time, and the result of executing the last
expression is returned. This is done through square brackets, as [
Echo("Hello"); Echo("World"); True; ];
, which first writes Hello
to screen, then World
on the next line, and then returns True
.
When you type in an expression, you have to take in to account the
fact that Yacas is case-sensitive. This means that a function sin
(with all lowercase) is a different function from Sin
(which
starts with a capital S), and the variable v
is a different one
from V
.
Using Yacas from the calculation center¶
As mentioned earlier, you can type in commands on the command line in
the calculation center. Typically, you would enter one statement per
line, for example, click on Sin(Pi/2);
. The has a memory, and
remembers results from calculations performed before. For example, if
you define a function on a line (or set a variable to a value), the
defined function (or variable) are available to be used in following
lines. A session can be restarted (forgetting all previous definitions
and results) by typing restart
. All memory is erased in that
case.
Statements should end with a semicolon ;
although this is not
required in interactive sessions (Yacas will append a semicolon at end
of line to finish the statement).
The command line has a history list, so it should be easy to browse through the expressions you entered previously using the up and down arrow keys.
When a few characters have been typed, the command line will use the characters before the cursor as a filter into the history, and allow you to browse through all the commands in the history that start with these characters quickly, instead of browsing through the entire history. If the system recognized the first few characters, it will also show the commands that start with the sequence entered. You can use the arrow keys to browse through this list, and then select the intended function to be inserted by pressing enter.
Commands spanning multiple lines can (and actually have to) be entered
by using a trailing backslash at end of each continued line. For
example, clicking on 2+3+
will result in an
error, but entering the same with a backslash at the end and then
entering another expression will concatenate the two lines and
evaluate the concatenated input.
Incidentally, any text Yacas prints without a prompt is either
a message printed by a function as a side-effect, or an error
message. Resulting values of expressions are always printed after an
Out>
prompt.
Yacas as a symbolic calculator¶
We are ready to try some calculations. Yacas uses a C-like
infix syntax and is case-sensitive. Here are some exact manipulations
with fractions for a start: 1/14+5/21*(30-(1+1/2)*5^2);
The standard scripts already contain a simple math library for
symbolic simplification of basic algebraic functions. Any names such
as x
are treated as independent, symbolic variables and are not
evaluated by default. Some examples to try:
0+x
x+1*y
Sin(ArcSin(alpha))+Tan(ArcTan(beta))
Note that the answers are not just simple numbers here, but actual expressions. This is where Yacas shines. It was built specifically to do calculations that have expressions as answers.
In Yacas after a calculation is done, you can refer to the previous
result with %
. For example, we could first type (x+1)*(x-1)
,
and then decide we would like to see a simpler version of that
expression, and thus type Simplify(%)
,
which should result in x^2-1
.
The special operator %
automatically recalls the result from the
previous line. The function Simplify
attempts to reduce an
expression to a simpler form. Note that standard function names in
Yacas are typically capitalized. Multiple capitalization such as
ArcSin
is sometimes used. The underscore character _
is a
reserved operator symbol and cannot be part of variable or function
names.
Yacas offers some more powerful symbolic manipulation operations. A few will be shown here to wetten the appetite.
Some simple equation solving algorithms are in place:
Solve(x/(1+x) == a, x);
Solve(x^2+x == 0, x);
Solve(a+x*y==z,x);
(Note the use of the ==
operator, which does not evaluate to
anything, to denote an “equation” object.)
Taylor series are supported, for example: Taylor(x,0,3) Exp(x)
is
a bodied operator that expands Exp(x)
for x
around x=0
, up
to order 3.
Symbolic manipulation is the main application of Yacas. This is a small tour of the capabilities Yacas currently offers. Note that this list of examples is far from complete. Yacas contains a few hundred commands, of which only a few are shown here.
Expand((1+x)^5);
(expand the expression into a polynomial)Limit(x,0) Sin(x)/x;
(calculate the limit ofSin(x)/x
asx
approaches zero)Newton(Sin(x),x,3,0.0001);
(use Newton’s method to find the value ofx
near3
whereSin(x)
equals zero numerically and stop if the result is closer than0.0001
to the real result)DiagonalMatrix({a,b,c});
(create a matrix with the elements specified in the vector on the diagonal)Integrate(x,a,b) x*Sin(x);
(integrate a function over variablex
, froma
tob
)Factor(x^2-1);
(factorize a polynomial)Apart(1/(x^2-1),x);
(create a partial fraction expansion of a polynomial)Simplify((x^2-1)/(x-1));
(simplification of expressions)CanProve( (a And b) Or (a And Not b) );
(special-purpose simplifier that tries to simplify boolean expressions as much as possible)TrigSimpCombine(Cos(a)*Sin(b));
(special-purpose simplifier that tries to transform trigonometric expressions into a form where there are only additions of trigonometric functions involved and no multiplications)
Arbitrary precision numbers¶
Yacas can deal with arbitrary precision numbers. It can work with
large integers, like 20!
(The ! means factorial, thus
1*2*3*...*20
).
As we saw before, rational numbers will stay rational as long as the
numerator and denominator are integers, so 55/10
will evaluate to
11/2
. You can override this behavior by using the numerical
evaluation function N()
. For example, N(55/10)
will evaluate
to 5.5
. This behavior holds for most math functions. Yacas will
try to maintain an exact answer (in terms of integers or fractions)
instead of using floating point numbers, unless N()
is used. Where
the value for the constant pi is needed, use the built-in variable
Pi
. It will be replaced by the (approximate) numerical value when
N(Pi)
is called. Yacas knows some simplification rules using
Pi
(especially with trigonometric functions).
The function N
takes either one or two arguments. It evaluates its
first argument and tries to reduce it as much as possible to a
real-valued approximation of the expression. If the second argument is
present, it states the number of digits precision required. Thus
N(1/234)
returns a number with the current default precision
(which starts at 20 digits), but you can request as many digits as you
like by passing a second argument, as in N(1/234, 10)
, N(1/234,
20)
, N(1/234, 30)
, etcetera.
Note that we need to enter N()
to force the approximate
calculation, otherwise the fraction would have been left unevaluated.
Revisiting Pi
, we can get as many digits of Pi
as we like, by
providing the precision required as argument to N
. So to get 50
digits precision, we can evaluate N(Pi,50)
.
Taking a derivative of a function was amongst the very first of
symbolic calculations to be performed by a computer, as the operation
lends itself surprisingly well to being performed
automatically. Naturally, it is also implemented in Yacas, through the
function D
. D
is a <i>bodied</i> function, meaning that its
last argument is past the closing brackets. Where normal functions are
called with syntax similar to f(x,y,z)
, a bodied function would be
called with a syntax f(x,y)z
. Here are two examples of taking a
derivative: <ul> <li> D(x) Sin(x);
(taking a derivative)</li>
<li>``D(x) D(x) Sin(x);`` (taking a derivative twice)</li> </ul> The
{D} function also accepts an argument specifying how often the
derivative has to be taken. In that case, the above expressions can
also be written as: <ul> <li> D(x,1) Sin(x);
(taking a
derivative)</li> <li>``D(x,2) Sin(x);`` (taking a derivative
twice)</li> </ul>
Analytic functions¶
Many of the usual analytic functions have been defined in the Yacas
library. Examples are Exp(1)
, Sin(2)
, ArcSin(1/2)
,
Sqrt(2)
. These will not evaluate to a numeric result in general,
unless the result is an integer, like Sqrt(4)
. If asked to reduce
the result to a numeric approximation with the function N
, then
<i>Yacas will do so</i>, as for example in N(Sqrt(2),50)
.
Variables¶
Yacas supports variables. You can set the value of a variable with the
:=
infix operator, as in a:=1;
. The variable can then be used
in expressions, and everywhere where it is referred to, it will be
replaced by its value.
To clear a variable binding, execute Clear(a);
. A variable will
evaluate to itself after a call to clear it (so after the call to
clear a
above, calling <span class=”commandlink”>a`` should now
return a
). This is one of the properties of the evaluation scheme
of Yacas; when some object can not be evaluated or transformed any
further, it is returned as the final result.
Functions¶
The :=
operator can also be used to define simple functions:
f(x):=2*x*x
. will define a new function, f
, that accepts one
argument and returns twice the square of that argument. This function
can now be called, f(a)
. You can change the definition of a
function by defining it again.
One and the same function name such as f
may define different
functions if they take different numbers of arguments. One can define
a function f
which takes one argument, as for example
f(x):=x^2;
, or two arguments, f(x,y):=x*y;
. If you clicked on
both links, both functions should now be defined, and f(a)
calls
the one function whereas f(a,b)
calls the other.
Yacas is very flexible when it comes to types of mathematical objects. Functions can in general accept or return any type of argument.
Boolean expressions and predicates¶
Yacas predefines True
and False
as boolean values. Functions
returning boolean values are called <i>predicates</i>. For example,
IsNumber()
and`` IsInteger()`` are predicates defined in the Yacas
environment. For example, try IsNumber(2+x);
, or
IsInteger(15/5);
.
There are also comparison operators. Typing 2 > 1
would return
True
. You can also use the infix operators And
and Or
, and
the prefix operator Not
, to make more complex boolean
expressions. For example, try True And False
, True Or False
,
True And Not(False)
.
Strings and lists¶
In addition to numbers and variables, Yacas supports strings and
lists. Strings are simply sequences of characters enclosed by double
quotes, for example: "this is a string with \"quotes\" in it"
.
Lists are ordered groups of items, as usual. Yacas represents lists by
putting the objects between braces and separating them with
commas. The list consisting of objects a, b, and c could be entered by
typing {a,b,c}
. In Yacas, vectors are represented as lists and
matrices as lists of lists.
Items in a list can be accessed through the [ ]
operator. The
first element has index one. Examples: when you enter
uu:={a,b,c,d,e,f};
then uu[2];
evaluates to b
, and
uu[2 .. 4];
evaluates to {b,c,d}
. The “range” expression
2 .. 4
evaluates to {2,3,4}
. Note that spaces around the
..
operator are necessary, or else the parser will not be able to
distinguish it from a part of a number.
Lists evaluate their arguments, and return a list with results of
evaluating each element. So, typing {1+2,3};
would evaluate to {3,3}
.
The idea of using lists to represent expressions dates back to the language LISP developed in the 1970’s. From a small set of operations on lists, very powerful symbolic manipulation algorithms can be built. Lists can also be used as function arguments when a variable number of arguments are necessary.
Let’s try some list operations now. First click on m:={a,b,c};
to
set up an initial list to work on. Then click on links below: <ul>
<li>``Length(m);`` (return the length of a list)</li>
<li>``Reverse(m);`` (return the string reversed)</li>
<li>``Concat(m,m);`` (concatenate two strings)</li> <li>``m[1]:=d;``
(setting the first element of the list to a new value, d, as can be
verified by evaluating m
)</li> </ul> Many more list operations are
described in the reference manual.
Writing simplification rules¶
Mathematical calculations require versatile transformations on symbolic quantities. Instead of trying to define all possible transformations, Yacas provides a simple and easy to use pattern matching scheme for manipulating expressions according to user-defined <i>rules</i>. Yacas itself is designed as a small core engine executing a large library of rules to match and replace patterns.
One simple application of pattern-matching rules is to define new
functions. (This is actually the only way Yacas can learn about new
functions.) As an example, let’s define a function f
that will
evaluate factorials of non-negative integers. We will define a
predicate to check whether our argument is indeed a non-negative
integer, and we will use this predicate and the obvious recursion
f(n)=n*f(n-1) if n>0 and 1 if n=0
to evaluate the factorial.
We start with the simple termination condition, which is that f(n)
should return one if n
is zero: <ul> <li>``10 # f(0) <–
1;``</li> </ul> You can verify that this already works for input value
zero, with f(0)
.
Now we come to the more complex line, <ul> <li>``20 #
f(n_IsIntegerGreaterThanZero) <– n*f(n-1);``</li> </ul> Now we
realize we need a function IsGreaterThanZero
, so we define this
function, with <ul> <li>``IsIntegerGreaterThanZero(_n) <–
(IsInteger(n) And n>0);``</li> </ul> You can verify that it works
by trying f(5)
, which should return the same value as 5!
.
In the above example we have first defined two “simplification rules”
for a new function f()
. Then we realized that we need to define a
predicate IsIntegerGreaterThanZero()
. A predicate equivalent to
IsIntegerGreaterThanZero()
is actually already defined in the
standard library and it’s called IsPositiveInteger
, so it was not
necessary, strictly speaking, to define our own predicate to do the
same thing. We did it here just for illustration purposes.
The first two lines recursively define a factorial function
f(n)=n*(n-1)*...*1
. The rules are given precedence values 10 and
20, so the first rule will be applied first. Incidentally, the
factorial is also defined in the standard library as a postfix
operator ! and it is bound to an internal routine much faster than the
recursion in our example. The example does show how to create your own
routine with a few lines of code. One of the design goals of Yacas was
to allow precisely that, definition of a new function with very little
effort.
The operator <--
defines a rule to be applied to a specific
function. (The <--
operation cannot be applied to an atom.)
The _n
in the rule for IsIntegerGreaterThanZero()
specifies
that any object which happens to be the argument of that predicate is
matched and assigned to the local variable n
. The expression to
the right of <--
can use n (without the underscore) as a
variable.
Now we consider the rules for the function f
. The first rule just
specifies that f(0)
should be replaced by 1 in any expression. The
second rule is a little more involved. n_IsIntegerGreaterThanZero
is a match for the argument of f
, with the proviso that the
predicate IsIntegerGreaterThanZero(n)
should return True
,
otherwise the pattern is not matched. The underscore operator is to be
used only on the left hand side of the rule definition operator
<--
.
There is another, slightly longer but equivalent way of writing the
second rule: <ul> <li>``20 # f(_n)_(IsIntegerGreaterThanZero(n))
<– n*f(n-1);``</li> </ul> The underscore after the function object
denotes a “postpredicate” that should return True
or else there is
no match. This predicate may be a complicated expression involving
several logical operations, unlike the simple checking of just one
predicate in the n_IsIntegerGreaterThanZero
construct. The
postpredicate can also use the variable n
(without the
underscore).
Precedence values for rules are given by a number followed by the
#
infix operator (and the transformation rule after it). This
number determines the ordering of precedence for the pattern matching
rules, with 0 the lowest allowed precedence value, i.e. rules with
precedence 0 will be tried first. Multiple rules can have the same
number: this just means that it doesn’t matter what order these
patterns are tried in. If no number is supplied, 0 is assumed. In our
example, the rule f(0) <-- 1
must be applied earlier than the
recursive rule, or else the recursion will never terminate. But as
long as there are no other rules concerning the function f
, the
assignment of numbers 10 and 20 is arbitrary, and they could have been
500 and 501 just as well. It is usually a good idea however to keep
some space between these numbers, so you have room to insert new
transformation rules later on.
Predicates can be combined: for example, {IsIntegerGreaterThanZero()}
could also have been defined as: <ul> <li>``10 #
IsIntegerGreaterThanZero(n_IsInteger)_(n>0) <– True;``</li>
<li>``20 # IsIntegerGreaterThanZero(_n) <– False;``</li> </ul> The
first rule specifies that if n is an integer, and is greater than
zero, the result is True
, and the second rule states that
otherwise (when the rule with precedence 10 did not apply) the
predicate returns False
.
In the above example, the expression n > 0
is added after the
pattern and allows the pattern to match only if this predicate return
True
. This is a useful syntax for defining rules with complicated
predicates. There is no difference between the rules``
F(n_IsPositiveInteger) <–...`` and F(_n)_(IsPositiveInteger(n))
<-- ...
except that the first syntax is a little more concise.
The left hand side of a rule expression has the following form:
<i>precedence</i> # <i>pattern</i> _ <i>postpredicate</i> <-- <i>replacement</i> ;
The optional precedence must be a positive integer.
Some more examples of rules (not made clickable because their equivalents are already in the basic Yacas library): <ul> <li>``10 # _x + 0 <– x;``</li> <li>``20 # _x - _x <– 0;``</li> <li>``ArcSin(Sin(_x)) <– x;``</li> </ul> The last rule has no explicit precedence specified in it (the precedence zero will be assigned automatically by the system).
Yacas will first try to match the pattern as a template. Names
preceded or followed by an underscore can match any one object: a
number, a function, a list, etc. Yacas will assign the relevant
variables as local variables within the rule, and try the predicates
as stated in the pattern. The post-predicate (defined after the
pattern) is tried after all these matched. As an example, the
simplification rule _x - _x <--0
specifies that the two objects
at left and at right of the minus sign should be the same for this
transformation rule to apply.
Local simplification rules¶
Sometimes you have an expression, and you want to use specific
simplification rules on it that should not be universally applied.
This can be done with the /:
and the /::
operators. Suppose
we have the expression containing things such as Ln(a*b)
, and we
want to change these into Ln(a)+Ln(b)
. The easiest way to do this
is using the /:
operator as follows:
Sin(x)*Ln(a*b)
(example expression without simplification)Sin(x)*Ln(a*b) /: {Ln(_x*_y) <- Ln(x)+Ln(y) }
(with instruction to simplify the expression)
A whole list of simplification rules can be built up in the list, and
they will be applied to the expression on the left hand side of
/:
.
Note that for these local rules, <-
should be used instead of
<--
. Using latter would result in a global definition of a new
transformation rule on evaluation, which is not the intention.
The /:
operator traverses an expression from the top down, trying
to apply the rules from the beginning of the list of rules to the end
of the list of rules. If no rules can be applied to the whole
expression, it will try the sub-expressions of the expression being
analyzed.
It might be sometimes necessary to use the /::
operator, which
repeatedly applies the /:
operator until the result does not
change any more. Caution is required, since rules can contradict each
other, and that could result in an infinite loop. To detect this
situation, just use /:
repeatedly on the expression. The
repetitive nature should become apparent.
Programming essentials¶
An important feature of Yacas is its programming language which allows you to create your own programs for doing calculations. This section describes some constructs and functions for control flow.
Looping can be done with the function ForEach
. There are more
options, but ForEach is the simplest to use for now and will suffice
for this turorial. The statement form ForEach(x, list) body
executes its body for each element of the list and assigns the
variable x to that element each time. The statement form
While(predicate) body
repeats execution of the expression
represented by body
until evaluation of the expression represented
by predicate
returns False
.
This example loops over the integers from one to three, and writes out
a line for each, multiplying the integer by 3 and displaying the
result with the function Echo
: ForEach(x,1 .. 5) Echo(x," times
3 equals ",3*x);
Compound statements¶
Multiple statements can be grouped together using the [
and ]
brackets. The compound [a; Echo("In the middle"); 1+2;];
evaluates
a
, then the Echo
command, and finally evaluates 1+2
, and
returns the result of evaluating the last statement 1+2
.
A variable can be declared local to a compound statement block by the
function Local(var1, var2, ...)
. For example, if you execute
[Local(v);v:=1+2;v;];
the result will be 3
. The program body
created a variable called v
, assigned the value of evaluating
1+2
to it, and made sure the contents of the variable v
were
returned. If you now evaluate v
afterwards you will notice that
the variable v
is not bound to a value any more. The variable
v
was defined locally in the program body between the two square
brackets [
and ]
.
Conditional execution is implemented by the If(predicate, body1,
body2)
function call. If the expression predicate
evaluates to
True
, the expression represented by body1
is evaluated,
otherwise body2
is evaluated, and the corresponding value is
returned. For example, the absolute value of a number can be computed
with: f(x) := If(x < 0,-x,x);
(note that there already is a
standard library function that calculates the absolute value of a
number).
Variables can also be made to be local to a small set of functions,
with LocalSymbols(variables) body
. For example, the following code
snippet: LocalSymbols(a,b) [a:=0;b:=0;
inc():=[a:=a+1;b:=b-1;show();]; show():=Echo("a = ",a," b = ",b); ];
defines two functions, inc
and show
. Calling inc()
repeatedly increments a
and decrements b
, and calling
show()
then shows the result (the function “inc” also calls the
function “show”, but the purpose of this example is to show how two
functions can share the same variable while the outside world cannot
get at that variable). The variables are local to these two functions,
as you can see by evaluating a
and b
outside the scope of
these two functions. This feature is very important when writing a
larger body of code, where you want to be able to guarantee that there
are no unintended side-effects due to two bits of code defined in
different files accidentally using the same global variable.
To illustrate these features, let us create a list of all even integers from 2 to 20 and compute the product of all those integers except those divisible by 3
[
Local(L,i,answer);
L:={}; i:=2;
/*Make a list of all even integers from 2 to 20 */
While (i <= 20) [ L := Append(L, i); i := i + 2; ];
/* Now calculate the product of all of these numbers that are not divisible by 3 */
answer := 1;
ForEach(i,L) If (Mod(i, 3) != 0, answer := answer * i);
/* And return the answer */
answer;
];
(Note that it is not necessarily the most economical way to do it in Yacas.)
We used a shorter form of If(predicate, body)
with only one body
which is executed when the condition holds. If the condition does not
hold, this function call returns False
. We also introduced
comments, which can be placed between /*
and */
. Yacas will
ignore anything between those two. When putting a program in a file
you can also use //
. Everything after //
up until the end of
the line will be a comment. Also shown is the use of the While
function. Its form is While (predicate) body
. While the
expression represented by predicate
evaluates to True
, the
expression represented by body
will keep on being evaluated.
The above example is not the shortest possible way to write out the algorithm. It is written out in a procedural way, where the program explains step by step what the computer should do. There is nothing fundamentally wrong with the approach of writing down a program in a procedural way, but the symbolic nature of Yacas also allows you to write it in a more concise, elegant, compact way, by combining function calls.
There is nothing wrong with procedural style, but there is amore ‘functional’ approach to the same problem would go as follows below. The advantage of the functional approach is that it is shorter and more concise (the difference is cosmetic mostly).
Before we show how to do the same calculation in a functional style,
we need to explain what a pure function is, as you will need it a
lot when programming in a functional style. We will jump in with an
example that should be self-explanatory. Consider the expression
Lambda({x,y},x+y)
. This has two arguments, the first listing x
and y, and the second an expression. We can use this construct with
the function Apply as follows: Apply(Lambda({x,y},x+y),{2,3})
. The
result should be 5
, the result of adding 2
and 3
. The
expression starting with Lambda
is essentially a prescription for
a specific operation, where it is stated that it accepts 2 arguments,
and returns the arguments added together. In this case, since the
operation was so simple, we could also have used the name of a
function to apply the arguments to, the addition operator in this case
Apply("+",{2,3})
. When the operations become more complex however,
the Lambda construct becomes more useful.
Now we are ready to do the same example using a functional
approach. First, let us construct a list with all even numbers from 2
to 20. For this we use the ..
operator to set up all numbers from
one to ten, and then multiply that with two: 2 * (1 .. 10)
.
Now we want an expression that returns all the even numbers up to 20
which are not divisible by 3. For this we can use Select
, which
takes as first argument a predicate that should return True
if the
list item is to be accepted, and False
otherwise, and as second
argument the list in question:
Select(Lambda({n},Mod(n,3)!=0),2*(1 .. 10))
. The numbers 6, 12
and 18 have been correctly filtered out. Here you see one example of a
pure function where the operation is a little bit more complex.
All that remains is to factor the items in this list. For this we can
use UnFlatten
. Two examples of the use of UnFlatten
are
UnFlatten({a,b,c},"*",1)
UnFlatten({a,b,c},"+",0)
The 0 and 1 are a base element to start with when grouping the arguments in to an expression (they should be the respective identity elements, hence it is zero for addition and 1 for multiplication).
Now we have all the ingredients to finally do the same calculation we did above in a procedural way, but this time we can do it in a functional style, and thus captured in one concise single line:
UnFlatten(Select(Lambda({n},Mod(n,3)!=0),2*(1 .. 10)),"*",1)
As was mentioned before, the choice between the two is mostly a matter of style.
Macros¶
One of the powerful constructs in Yacas is the construct of a
macro. In its essence, a macro is a prescription to create another
program before executing the program. An example perhaps explains it
best. Evaluate the following expression Macro(for,{st,pr,in,bd})
[(@st);While(@pr)[(@bd);(@in);];];
. This expression defines a macro
that allows for looping. Yacas has a For
function already, but
this is how it could be defined in one line (In Yacas the For
function is bodied, we left that out here for clarity, as the example
is about macros).
To see it work just type for(i:=0,i<3,i:=i+1,Echo(i))
. You will see
the count from one to three.
The construct works as follows; The expression defining the macro sets
up a macro named for
with four
arguments. On the right is the body of the macro. This body contains
expressions of the form @var
. These are replaced by the values
passed in on calling the macro. After all the variables have been
replaced, the resulting expression is evaluated. In effect a new
program has been created. Such macro constructs come from LISP, and
are famous for allowing you to almost design your own programming
language constructs just for your own problem at hand. When used
right, macros can greatly simplify the task of writing a program.
You can also use the back-quote `
to expand a macro in-place. It
takes on the form `(expression)
, where the expression can again
contain sub-expressions of the form @variable
. These instances
will be replaced with the values of these variables.
The practice of programming in Yacas¶
When you become more proficient in working with Yacas you will be
doing more and more sophisticated calculations. For such calculations
it is generally necessary to write little programs. In real life you
will usually write these programs in a text editor, and then start
Yacas, load the text file you just wrote, and try out the
calculation. Generally this is an iterative process, where you go back
to the text editor to modify something, and then go back to Yacas,
type restart
and then reload the file.
On this site you can run Yacas in a little window called a Yacas calculation center (the same as the one below this tutorial). On page there is tab that contains a Yacas calculation center. If you click on that tab you will be directed to a larger calculation center than the one below this tutorial. In this page you can easily switch between doing a calculation and editing a program to load at startup. We tried to make the experience match the general use of Yacas on a desktop as much as possible. The Yacas journal (which you see when you go to the Yacas web site) contains examples of calculations done before by others.
Defining your own operators¶
Large part of the Yacas system is defined in the scripting language itself. This includes the definitions of the operators it accepts, and their precedences. This means that you too can define your own operators. This section shows you how to do that.
Suppose we wanted to define a function F(x,y)=x/y+y/x
. We could
use the standard syntax F(a,b) := a/b + b/a;
. F(1,2);
. For
the purpose of this demonstration, lets assume that we want to define
an infix operator xx
for this operation. We can teach Yacas about
this infix operator with Infix("xx", OpPrecedence("/"));
. Here we
told Yacas that the operator xx
is to have the same precedence as
the division operator. We can now proceed to tell Yacas how to
evaluate expressions involving the operator xx
by defining it as
we would with a function, a xx b := a/b + b/a;
.
You can verify for yourself 3 xx 2 + 1;
and 1 + 3 xx 2;
return
the same value, and that they follow the precedence rules (eg. xx
binds stronger than +
).
We have chosen the name xx
just to show that we don’t need to use
the special characters in the infix operator’s name. However we must
define this operator as infix before using it in expressions,
otherwise Yacas will raise a syntax error.
Finally, we might decide to be completely flexible with this important
function and also define it as a mathematical operator ##
. First
we define ##
as a <i>bodied</i> function and then proceed as
before. First we can tell Yacas that ##
is a bodied operator with
Bodied("##", OpPrecedence("/"));
. Then we define the function
itself: ##(a) b := a xx b;
. And now we can use the function,
##(1) 3 + 2;
.
We have used the name ##
but we could have used any other name
such as xx
or F
or even _-+@+-_
. Apart from possibly
confusing yourself, it doesn’t matter what you call the functions you
define.
There is currently one limitation in Yacas: once a function name is
declared as infix (prefix, postfix) or bodied, it will always be
interpreted that way. If we declare a function f
to be bodied, we
may later define different functions named f
with different
numbers of arguments, however all of these functions must be bodied.
When you use infix operators and either a prefix of postfix operator next to it you can run in to a situation where Yacas can not quite figure out what you typed. This happens when the operators are right next to each other and all consist of symbols (and could thus in principle form a single operator). Yacas will raise an error in that case. This can be avoided by inserting spaces.
Some assorted programming topics¶
One use of lists is the associative list, sometimes called a
dictionary in other programming languages, which is implemented in
Yacas simply as a list of key-value pairs. Keys must be strings and
values may be any objects. Associative lists can also work as
mini-databases, where a name is associated to an object. As an
example, first enter record:={};
to set up
an empty record. After that, we can fill arbitrary fields in this
record:
record["name"]:="Isaia";
record["occupation"]:="prophet";
record["is alive"]:=False;
Now, evaluating record["name"]
should result in the answer
"Isaia"
. The record is now a list that contains three sublists, as
you can see by evaluating record
.
Assignment of multiple variables is also possible using lists. For
instance, evaluating {x,y}:={2!,3!}
will result in 2 being
assigned to x
and 6 to y
.
When assigning variables, the right hand side is evaluated before it
is assigned. Thus a:=2*2
will set a to 4. This is however
<i>not</i> the case for functions. When entering f(x):=x+x
the
right hand side, x+x
, is not evaluated before being assigned. This
can be forced by using Eval()
. Defining f(x)
with
f(x):=Eval(x+x)
will tell the system to first evaluate x+x
(which results in 2*x
) before assigning it to the user function
f
. This specific example is not a very useful one but it will come
in handy when the operation being performed on the right hand side is
expensive. For example, if we evaluate a Taylor series expansion
before assigning it to the user-defined function, the engine doesn’t
need to create the Taylor series expansion each time that user-defined
function is called.
The imaginary unit i is denoted I
and complex numbers can be
entered as either expressions involving I
, as for example
1+I*2
, or explicitly as Complex(a,b)
for a+ib. The form
Complex(re,im)
is the way Yacas deals with complex numbers
internally.
Linear Algebra¶
Vectors of fixed dimension are represented as lists of their
components. The list {1, 2+x, 3*Sin(p)}
would be a
three-dimensional vector with components 1
, 2+x
and
3*Sin(p)
. Matrices are represented as a lists of lists.
Vector components can be assigned values just like list items, since
they are in fact list items. If we first set up a variable called
“vector” to contain a three-dimensional vector with the command
vector:=ZeroVector(3);
(you can verify that it is indeed a vector
with all components set to zero by evaluating vector
), you can
change elements of the vector just like you would the elements of a
list (seeing as it is represented as a list). For example, to set the
second element to two, just evaluate vector[2] := 2;
. This results
in a new value for vector
.
Yacas can perform multiplication of matrices, vectors and numbers as
usual in linear algebra. The standard Yacas script library also
includes taking the determinant and inverse of a matrix, finding
eigenvectors and eigenvalues (in simple cases) and solving linear sets
of equations, such as A * x = b where A is a matrix, and x and b are
vectors. As a little example to wetten your appetite, we define a
Hilbert matrix: hilbert:=HilbertMatrix(3)
. We can then calculate
the determinant with Determinant(hilbert)
, or the inverse with
Inverse(hilbert)
. There are several more matrix operations
supported. See the reference manual for more details.
“Threading” of functions¶
Some functions in Yacas can be “threaded”. This means that calling the
function with a list as argument will result in a list with that
function being called on each item in the list. E.g. Sin({a,b,c});
will result in {Sin(a),Sin(b),Sin(c)}
. This functionality is
implemented for most normal analytic functions and arithmetic
operators.
Functions as lists¶
For some work it pays to understand how things work under the
hood. Internally, Yacas represents all atomic expressions (numbers and
variables) as strings and all compound expressions as lists, like
Lisp. Try FullForm(a+b*c);
and you will see the text (+ a (* b c
))
appear on the screen. This function is occasionally useful, for
example when trying to figure out why a specific transformation rule
does not work on a specific expression.
If you try FullForm(1+2)
you will see that the result is not quite
what we intended. The system first adds up one and two, and then shows
the tree structure of the end result, which is a simple number
3
. To stop Yacas from evaluating something, you can use the
function Hold
, as FullForm(Hold(1+2))
. The function Eval
is the opposite, it instructs Yacas to re-evaluate its argument
(effectively evaluating it twice). This undoes the effect of Hold
,
as for example Eval(Hold(1+2))
.
Also, any expression can be converted to a list by the function
Listify
or back to an expression by the function UnList
:
Listify(a+b*(c+d));
UnList({Atom("+"),x,1});
Note that the first element of the list is the name of the function
+
which is equivalently represented as Atom("+")
and that the
subexpression b*(c+d)
was not converted to list form. Listify just
took the top node of the expression.
Reference Manual¶
{Yacas} (Yet Another Computer Algebra System) is a small and highly flexible general-purpose computer algebra system and programming language. The language has a familiar, C-like infix-operator syntax. The distribution contains a small library of mathematical functions, but its real strength is in the language in which you can easily write your own symbolic manipulation algorithms. The core engine supports arbitrary precision arithmetic, and is able to execute symbolic manipulations on various mathematical objects by following user-defined rules.
This document describes the functions that are useful in the context of using {Yacas} as an end user. It is recommended to first read the online interactive tutorial to get acquainted with the basic language constructs first. This document expands on the tutorial by explaining the usage of the functions that are useful when doing calculations.
Arithmetic and other operations on numbers¶
-
x
+
y¶ addition
Addition can work on integers, rational numbers, complex numbers, vectors, matrices and lists.
Hint
Addition is implemented in the standard math library (as opposed to being built-in). This means that it can be extended by the user.
Example: In> 2+3 Out> 5
-
-
x¶ negation
Negation can work on integers, rational numbers, complex numbers, vectors, matrices and lists.
Hint
Negation is implemented in the standard math library (as opposed to being built-in). This means that it can be extended by the user.
Example: In> - 3 Out> -3
-
x
-
y subtraction
Subtraction can work on integers, rational numbers, complex numbers, vectors, matrices and lists.
Hint
Subtraction is implemented in the standard math library (as opposed to being built-in). This means that it can be extended by the user.
Example: In> 2-3 Out> -1
-
x
*
y¶ multiplication
Multiplication can work on integers, rational numbers, complex numbers, vectors, matrices and lists.
Note
In the case of matrices, multiplication is defined in terms of standard matrix product.
Hint
Multiplication is implemented in the standard math library (as opposed to being built-in). This means that it can be extended by the user.
Example: In> 2*3 Out> 6
-
x
/
y¶ division
Division can work on integers, rational numbers, complex numbers, vectors, matrices and lists.
Note
For matrices division is element-wise.
Hint
Division is implemented in the standard math library (as opposed to being built-in). This means that it can be extended by the user.
Example: In> 6/2 Out> 3
-
x
^
y¶ exponentiation
Exponentiation can work on integers, rational numbers, complex numbers, vectors, matrices and lists.
Note
In the case of matrices, exponentiation is defined in terms of standard matrix product.
Hint
Exponentiation is implemented in the standard math library (as opposed to being built-in). This means that it can be extended by the user.
Example: In> 2^3 Out> 8
-
Div
(x, y)¶ - determine divisor
Div()
performs integer division. IfDiv(x,y)
returnsa
andMod(x,y)
equalsb
, then these numbers satisfy \(x =ay + b\) and \(0 \leq b < y\).Example: In> Div(5,3) Out> 1
-
Mod
(x, y)¶ - determine remainder
Mod()
returns the division remainder. IfDiv(x,y)
returnsa
andMod(x,y)
equalsb
, then these numbers satisfy \(x =ay + b\) and \(0 \leq b < y\).Example: In> Div(5,3) Out> 1 In> Mod(5,3) Out> 2
-
Gcd
(n, m)¶ -
Gcd
(list) greatest common divisor
This function returns the greatest common divisor of
n
andm
or of all elements oflist
.See also
-
Lcm
(n, m)¶ -
Lcm
(list) least common multiple
This command returns the least common multiple of
n
andm
or of all elements oflist
.Example: In> Lcm(60,24) Out> 120 In> Lcm({3,5,7,9}) Out> 315
See also
-
n
<<
m¶ -
n
>>
m¶ binary shift operators
These operators shift integers to the left or to the right. They are similar to the C shift operators. These are sign-extended shifts, so they act as multiplication or division by powers of 2.
Example: In> 1 << 10 Out> 1024 In> -1024 >> 10 Out> -1
-
FromBase
(base, "string")¶ conversion of a number from non-decimal base to decimal base
Parameters: - base – integer, base to convert to/from
- number – integer, number to write out in a different base
- "string" – string representing a number in a different base
In Yacas, all numbers are written in decimal notation (base 10). The two functions {FromBase}, {ToBase} convert numbers between base 10 and a different base. Numbers in non-decimal notation are represented by strings. {FromBase} converts an integer, written as a string in base {base}, to base 10. {ToBase} converts {number}, written in base 10, to base {base}.
-
N
(expression)¶ try determine numerical approximation of expression
Parameters: - expression – expression to evaluate
- precision – integer, precision to use
The function
N()
instructs yacas to try to coerce an expression in to a numerical approximation to the expressionexpr
, usingprec
digits precision if the second calling sequence is used, and the default precision otherwise. This overrides the normal behaviour, in which expressions are kept in symbolic form (eg.Sqrt(2)
instead of1.41421
). Application of theN()
operator will make yacas calculate floating point representations of functions whenever possible. In addition, the variablePi
is bound to the value of \(\pi\) calculated at the current precision.Note
N()
is a macro. Its argumentexpr
will only be evaluated after switching to numeric mode.Example: In> 1/2 Out> 1/2; In> N(1/2) Out> 0.5; In> Sin(1) Out> Sin(1); In> N(Sin(1),10) Out> 0.8414709848; In> Pi Out> Pi; In> N(Pi,20) Out> 3.14159265358979323846;
See also
-
Rationalize
(expr)¶ convert floating point numbers to fractions
Parameters: expr – an expression containing real numbers This command converts every real number in the expression “expr” into a rational number. This is useful when a calculation needs to be done on floating point numbers and the algorithm is unstable. Converting the floating point numbers to rational numbers will force calculations to be done with infinite precision (by using rational numbers as representations). It does this by finding the smallest integer $n$ such that multiplying the number with $10^n$ is an integer. Then it divides by $10^n$ again, depending on the internal gcd calculation to reduce the resulting division of integers.
Example: In> {1.2,3.123,4.5} Out> {1.2,3.123,4.5}; In> Rationalize(%) Out> {6/5,3123/1000,9/2};
See also
-
ContFrac
(x[, depth=6])¶ continued fraction expansion
Parameters: - x – number or polynomial to expand in continued fractions
- depth – positive integer, maximum required depth
This command returns the continued fraction expansion of
x
, which should be either a floating point number or a polynomial. The remainder is denoted by {rest}. This is especially useful for polynomials, since series expansions that converge slowly will typically converge a lot faster if calculated using a continued fraction expansion.Example: In> PrettyForm(ContFrac(N(Pi))) 1 --------------------------- + 3 1 ----------------------- + 7 1 ------------------ + 15 1 -------------- + 1 1 -------- + 292 rest + 1 Out> True; In> PrettyForm(ContFrac(x^2+x+1, 3)) x ---------------- + 1 x 1 - ------------ x -------- + 1 rest + 1 Out> True;
See also
-
Decimal
(frac)¶ decimal representation of a rational
Parameters: frac – a rational number This function returns the infinite decimal representation of a rational number {frac}. It returns a list, with the first element being the number before the decimal point and the last element the sequence of digits that will repeat forever. All the intermediate list elements are the initial digits before the period sets in.
Example: In> Decimal(1/22) Out> {0,0,{4,5}}; In> N(1/22,30) Out> 0.045454545454545454545454545454;
See also
-
Floor
(x)¶ round a number downwards
Parameters: x – a number This function returns \(\left \lfloor{x}\right \rfloor\), the largest integer smaller than or equal to
x
.Example: In> Floor(1.1) Out> 1; In> Floor(-1.1) Out> -2;
-
Ceil
(x)¶ round a number upwards
Parameters: x – a number This function returns \(\left \lceil{x}\right \rceil\), the smallest integer larger than or equal to
x
.Example: In> Ceil(1.1) Out> 2; In> Ceil(-1.1) Out> -1;
-
Round
(x)¶ round a number to the nearest integer
Parameters: x – a number This function returns the integer closest to $x$. Half-integers (i.e. numbers of the form $n + 0.5$, with $n$ an integer) are rounded upwards.
Example: In> Round(1.49) Out> 1; In> Round(1.51) Out> 2; In> Round(-1.49) Out> -1; In> Round(-1.51) Out> -2;
-
Min
(x, y)¶ minimum of a number of values
Parameters: - {y (x},) – pair of values to determine the minimum of
- list – list of values from which the minimum is sought
This function returns the minimum value of its argument(s). If the first calling sequence is used, the smaller of “x” and “y” is returned. If one uses the second form, the smallest of the entries in “list” is returned. In both cases, this function can only be used with numerical values and not with symbolic arguments.
Example: In> Min(2,3); Out> 2; In> Min({5,8,4}); Out> 4;
-
Max
(x, y)¶ maximum of a number of values
Parameters: - {y (x},) – pair of values to determine the maximum of
- list – list of values from which the maximum is sought
This function returns the maximum value of its argument(s). If the first calling sequence is used, the larger of “x” and “y” is returned. If one uses the second form, the largest of the entries in “list” is returned. In both cases, this function can only be used with numerical values and not with symbolic arguments.
Example: In> Max(2,3); Out> 3; In> Max({5,8,4}); Out> 8;
-
Numer
(expr)¶ numerator of an expression
Parameters: expr – expression to determine numerator of This function determines the numerator of the rational expression
expr
and returns it. As a special case, if its argument is numeric but not rational, it returns this number. Ifexpr
is neither rational nor numeric, the function returns unevaluated.Example: In> Numer(2/7) Out> 2; In> Numer(a / x^2) Out> a; In> Numer(5) Out> 5;
See also
-
Denom
(expr)¶ denominator of an expression
Parameters: expr – expression to determine denominator of This function determines the denominator of the rational expression
expr
and returns it. As a special case, if its argument is numeric but not rational, it returns1
. Ifexpr
is neither rational nor numeric, the function returns unevaluated.Example: In> Denom(2/7) Out> 7; In> Denom(a / x^2) Out> x^2; In> Denom(5) Out> 1;
See also
-
Pslq
(xlist, precision)¶ search for integer relations between reals
Parameters: - xlist – list of numbers
- precision – required number of digits precision of calculation
This function is an integer relation detection algorithm. This means that, given the numbers \(x_i\) in the list
xlist
, it tries to find integer coefficients \(a_i\) such that \(a_1*x_`+\ldots+a_n*x_n = 0\). The list of integer coefficients is returned. The numbers in “xlist” must evaluate to floating point numbers when theN()
operator is applied to them.
-
e1
<
e2¶ test for “less than”
Parameters: - e1 – expression to be compared
- e2 – expression to be compared
The two expression are evaluated. If both results are numeric, they are compared. If the first expression is smaller than the second one, the result is
True
and it isFalse
otherwise. If either of the expression is not numeric, after evaluation, the expression is returned with evaluated arguments. The word “numeric” in the previous paragraph has the following meaning. An expression is numeric if it is either a number (i.e. {IsNumber} returnsTrue
), or the quotient of two numbers, or an infinity (i.e. {IsInfinity} returnsTrue
). Yacas will try to coerce the arguments passed to this comparison operator to a real value before making the comparison.Example: In> 2 < 5; Out> True; In> Cos(1) < 5; Out> True;
See also
-
e1
>
e2¶ test for “greater than”
Parameters: - e1 – expression to be compared
- e2 – expression to be compared
The two expression are evaluated. If both results are numeric, they are compared. If the first expression is larger than the second one, the result is
True
and it isFalse
otherwise. If either of the expression is not numeric, after evaluation, the expression is returned with evaluated arguments. The word “numeric” in the previous paragraph has the following meaning. An expression is numeric if it is either a number (i.e. {IsNumber} returnsTrue
), or the quotient of two numbers, or an infinity (i.e. {IsInfinity} returnsTrue
). Yacas will try to coerce the arguments passed to this comparison operator to a real value before making the comparison.Example: In> 2 > 5; Out> False; In> Cos(1) > 5; Out> False
See also
-
e1
<=
e2¶ test for “less or equal”
Parameters: - e1 – expression to be compared
- e2 – expression to be compared
The two expression are evaluated. If both results are numeric, they are compared. If the first expression is smaller than or equals the second one, the result is
True
and it isFalse
otherwise. If either of the expression is not numeric, after evaluation, the expression is returned with evaluated arguments. The word “numeric” in the previous paragraph has the following meaning. An expression is numeric if it is either a number (i.e. {IsNumber} returnsTrue
), or the quotient of two numbers, or an infinity (i.e. {IsInfinity} returnsTrue
). Yacas will try to coerce the arguments passed to this comparison operator to a real value before making the comparison.Example: In> 2 <= 5; Out> True; In> Cos(1) <= 5; Out> True
See also
-
e1
>=
e2¶ test for “greater or equal”
Parameters: - e1 – expression to be compared
- e2 – expression to be compared
The two expression are evaluated. If both results are numeric, they are compared. If the first expression is larger than or equals the second one, the result is
True
and it isFalse
otherwise. If either of the expression is not numeric, after evaluation, the expression is returned with evaluated arguments. The word “numeric” in the previous paragraph has the following meaning. An expression is numeric if it is either a number (i.e. {IsNumber} returnsTrue
), or the quotient of two numbers, or an infinity (i.e. {IsInfinity} returnsTrue
). Yacas will try to coerce the arguments passed to this comparison operator to a real value before making the comparison.Example: In> 2 >= 5; Out> False; In> Cos(1) >= 5; Out> False
See also
-
IsZero
(n)¶ test whether argument is zero
Parameters: n – number to test IsZero(n)
evaluates toTrue
ifn
is zero. In casen
is not a number, the function returnsFalse
.Example: In> IsZero(3.25) Out> False; In> IsZero(0) Out> True; In> IsZero(x) Out> False;
See also
-
IsRational
(expr)¶ test whether argument is a rational
Parameters: expr – expression to test This commands tests whether the expression “expr” is a rational number, i.e. an integer or a fraction of integers.
Example: In> IsRational(5) Out> False; In> IsRational(2/7) Out> True; In> IsRational(0.5) Out> False; In> IsRational(a/b) Out> False; In> IsRational(x + 1/x) Out> False;
Calculus and elementary functions¶
In this chapter, some facilities for doing calculus are described. These include functions implementing differentiation, integration, standard mathematical functions, and solving of equations.
-
Sin
(x)¶ trigonometric sine function
Parameters: x – argument to the function, in radians This function represents the trigonometric function sine. Yacas leaves expressions alone even if x is a number, trying to keep the result as exact as possible. The floating point approximations of these functions can be forced by using the {N} function. Yacas knows some trigonometric identities, so it can simplify to exact results even if {N} is not used. This is the case, for instance, when the argument is a multiple of $Pi$/6 or $Pi$/4.
Example: In> Sin(1) Out> Sin(1); In> N(Sin(1),20) Out> 0.84147098480789650665; In> Sin(Pi/4) Out> Sqrt(2)/2;
-
Cos
(x)¶ trigonometric cosine function
Parameters: x – argument to the function, in radians This function represents the trigonometric function cosine. Yacas leaves expressions alone even if x is a number, trying to keep the result as exact as possible. The floating point approximations of these functions can be forced by using the {N} function. Yacas knows some trigonometric identities, so it can simplify to exact results even if {N} is not used. This is the case, for instance, when the argument is a multiple of $Pi$/6 or $Pi$/4. These functions are threaded, meaning that if the argument {x} is a list, the function is applied to all entries in the list.
Example: In> Cos(1) Out> Cos(1); In> N(Cos(1),20) Out> 0.5403023058681397174; In> Cos(Pi/4) Out> Sqrt(1/2);
-
Tan
(x)¶ trigonometric tangent function
Parameters: x – argument to the function, in radians This function represents the trigonometric function tangent. Yacas leaves expressions alone even if x is a number, trying to keep the result as exact as possible. The floating point approximations of these functions can be forced by using the {N} function. Yacas knows some trigonometric identities, so it can simplify to exact results even if {N} is not used. This is the case, for instance, when the argument is a multiple of $Pi$/6 or $Pi$/4. These functions are threaded, meaning that if the argument {x} is a list, the function is applied to all entries in the list.
Example: In> Tan(1) Out> Tan(1); In> N(Tan(1),20) Out> 1.5574077246549022305; In> Tan(Pi/4) Out> 1;
-
ArcSin
(x)¶ inverse trigonometric function arc-sine
Parameters: x – argument to the function This function represents the inverse trigonometric function arcsine. For instance, the value of $ArcSin(x)$ is a number $y$ such that $Sin(y)$ equals $x$. Note that the number $y$ is not unique. For instance, $Sin(0)$ and $Sin(Pi)$ both equal 0, so what should $ArcSin(0)$ be? In Yacas, it is agreed that the value of $ArcSin(x)$ should be in the interval [-$Pi$/2,$Pi$/2]. Usually, Yacas leaves this function alone unless it is forced to do a numerical evaluation by the {N} function. If the argument is -1, 0, or 1 however, Yacas will simplify the expression. If the argument is complex, the expression will be rewritten using the {Ln} function. This function is threaded, meaning that if the argument {x} is a list, the function is applied to all entries in the list.
Example: In> ArcSin(1) Out> Pi/2; In> ArcSin(1/3) Out> ArcSin(1/3); In> Sin(ArcSin(1/3)) Out> 1/3; In> x:=N(ArcSin(0.75)) Out> 0.848062; In> N(Sin(x)) Out> 0.7499999477;
-
ArcCos
(x)¶ inverse trigonometric function arc-cosine
Parameters: x – argument to the function This function represents the inverse trigonometric function arc-cosine. For instance, the value of $ArcCos(x)$ is a number $y$ such that $Cos(y)$ equals $x$. Note that the number $y$ is not unique. For instance, $Cos(Pi/2)$ and $Cos(3*Pi/2)$ both equal 0, so what should $ArcCos(0)$ be? In Yacas, it is agreed that the value of $ArcCos(x)$ should be in the interval [0,$Pi$] . Usually, Yacas leaves this function alone unless it is forced to do a numerical evaluation by the {N} function. If the argument is -1, 0, or 1 however, Yacas will simplify the expression. If the argument is complex, the expression will be rewritten using the {Ln} function. This function is threaded, meaning that if the argument {x} is a list, the function is applied to all entries in the list.
Example: In> ArcCos(0) Out> Pi/2 In> ArcCos(1/3) Out> ArcCos(1/3) In> Cos(ArcCos(1/3)) Out> 1/3 In> x:=N(ArcCos(0.75)) Out> 0.7227342478 In> N(Cos(x)) Out> 0.75
-
ArcTan
(x)¶ inverse trigonometric function arc-tangent
Parameters: x – argument to the function This function represents the inverse trigonometric function arctangent. For instance, the value of $ArcTan(x)$ is a number $y$ such that $Tan(y)$ equals $x$. Note that the number $y$ is not unique. For instance, $Tan(0)$ and $Tan(2*Pi)$ both equal 0, so what should $ArcTan(0)$ be? In Yacas, it is agreed that the value of $ArcTan(x)$ should be in the interval [-$Pi$/2,$Pi$/2]. Usually, Yacas leaves this function alone unless it is forced to do a numerical evaluation by the {N} function. Yacas will try to simplify as much as possible while keeping the result exact. If the argument is complex, the expression will be rewritten using the {Ln} function. This function is threaded, meaning that if the argument {x} is a list, the function is applied to all entries in the list.
Example: In> ArcTan(1) Out> Pi/4 In> ArcTan(1/3) Out> ArcTan(1/3) In> Tan(ArcTan(1/3)) Out> 1/3 In> x:=N(ArcTan(0.75)) Out> 0.643501108793285592213351264945231378078460693359375 In> N(Tan(x)) Out> 0.75
-
Exp
(x)¶ exponential function
Parameters: x – argument to the function This function calculates \(e^x\) where \(e\) is the mathematic constant 2.71828... One can use
Exp(1)
to represent \(e\). This function is threaded function, meaning that if the argumentx
is a list, the function is applied to all entries in the list.Example: In> Exp(0) Out> 1; In> Exp(I*Pi) Out> -1; In> N(Exp(1)) Out> 2.7182818284;
-
Ln
(x)¶ natural logarithm
Parameters: x – argument to the function This function calculates the natural logarithm of “x”. This is the inverse function of the exponential function, {Exp}, i.e. $Ln(x) = y$ implies that $Exp(y) = x$. For complex arguments, the imaginary part of the logarithm is in the interval (-$Pi$,$Pi$]. This is compatible with the branch cut of {Arg}. This function is threaded, meaning that if the argument {x} is a list, the function is applied to all entries in the list.
Example: In> Ln(1) Out> 0; In> Ln(Exp(x)) Out> x; In> D(x) Ln(x) Out> 1/x;
See also
Exp()
,Arg()
-
Sqrt
(x)¶ square root
Parameters: x – argument to the function This function calculates the square root of “x”. If the result is not rational, the call is returned unevaluated unless a numerical approximation is forced with the {N} function. This function can also handle negative and complex arguments. This function is threaded, meaning that if the argument {x} is a list, the function is applied to all entries in the list.
Example: In> Sqrt(16) Out> 4; In> Sqrt(15) Out> Sqrt(15); In> N(Sqrt(15)) Out> 3.8729833462; In> Sqrt(4/9) Out> 2/3; In> Sqrt(-1) Out> Complex(0,1);
-
Abs
(x)¶ absolute value or modulus of complex number
Parameters: x – argument to the function This function returns the absolute value (also called the modulus) of “x”. If “x” is positive, the absolute value is “x” itself; if “x” is negative, the absolute value is “-x”. For complex “x”, the modulus is the “r” in the polar decomposition \(x = re^{\imath\phi}\). This function is connected to the {Sign} function by the identity
Abs(x) * Sign(x) = x
for real “x”. This function is threaded, meaning that if the argument {x} is a list, the function is applied to all entries in the list.Example: In> Abs(2); Out> 2; In> Abs(-1/2); Out> 1/2; In> Abs(3+4*I); Out> 5;
See also
Sign()
,Arg()
-
Sign
(x)¶ sign of a number
Parameters: x – argument to the function This function returns the sign of the real number $x$. It is “1” for positive numbers and “-1” for negative numbers. Somewhat arbitrarily, {Sign(0)} is defined to be 1. This function is connected to the {Abs} function by the identity $Abs(x) * Sign(x) = x$ for real $x$. This function is threaded, meaning that if the argument {x} is a list, the function is applied to all entries in the list.
Example: In> Sign(2) Out> 1; In> Sign(-3) Out> -1; In> Sign(0) Out> 1; In> Sign(-3) * Abs(-3) Out> -3;
See also
Arg()
,Abs()
-
D
(variable[, n=1]) expression¶ derivative
Parameters: - variable – variable
- expression – expression to take derivatives of
- n – order
Returns: n
-th derivative ofexpression
with respect tovariable
-
D
(variable) expression derivative
Parameters: - variable – variable
- list – a list of variables
- expression – expression to take derivatives of
- n – order of derivative
Returns: derivative of
expression
with respect tovariable
This function calculates the derivative of the expression {expr} with respect to the variable {var} and returns it. If the third calling format is used, the {n}-th derivative is determined. Yacas knows how to differentiate standard functions such as {Ln} and {Sin}. The {D} operator is threaded in both {var} and {expr}. This means that if either of them is a list, the function is applied to each entry in the list. The results are collected in another list which is returned. If both {var} and {expr} are a list, their lengths should be equal. In this case, the first entry in the list {expr} is differentiated with respect to the first entry in the list {var}, the second entry in {expr} is differentiated with respect to the second entry in {var}, and so on. The {D} operator returns the original function if $n=0$, a common mathematical idiom that simplifies many formulae.
Example: In> D(x)Sin(x*y) Out> y*Cos(x*y); In> D({x,y,z})Sin(x*y) Out> {y*Cos(x*y),x*Cos(x*y),0}; In> D(x,2)Sin(x*y) Out> -Sin(x*y)*y^2; In> D(x){Sin(x),Cos(x)} Out> {Cos(x),-Sin(x)};
See also
Integrate()
,Taylor()
,Diverge()
,Curl()
-
Curl
(vector, basis)¶ curl of a vector field
Parameters: - vector – vector field to take the curl of
- basis – list of variables forming the basis
This function takes the curl of the vector field “vector” with respect to the variables “basis”. The curl is defined in the usual way, Curl(f,x) = { D(x[2]) f[3] - D(x[3]) f[2], D(x[3]) f[1] - D(x[1]) f[3], D(x[1]) f[2] - D(x[2]) f[1] } Both “vector” and “basis” should be lists of length 3.
-
Diverge
(vector, basis)¶ divergence of a vector field
Parameters: - vector – vector field to calculate the divergence of
- basis – list of variables forming the basis
This function calculates the divergence of the vector field “vector” with respect to the variables “basis”. The divergence is defined as Diverge(f,x) = D(x[1]) f[1] + ... + D(x[n]) f[n], where {n} is the length of the lists “vector” and “basis”. These lists should have equal length.
-
Integrate
(var) expr¶ -
Integrate
(var, x1, x2) expr integral
Parameters: - expr – expression to integrate
- var – atom, variable to integrate over
- x1 – first point of definite integration
- x2 – second point of definite integration
This function integrates the expression {expr} with respect to the variable {var}. In the case of definite integral, the integration is carried out from $var=x1$ to $var=x2$”. Some simple integration rules have currently been implemented. Polynomials, some quotients of polynomials, trigonometric functions and their inverses, hyperbolic functions and their inverses, {Exp}, and {Ln}, and products of these functions with polynomials can be integrated.
Example: In> Integrate(x,a,b) Cos(x) Out> Sin(b)-Sin(a); In> Integrate(x) Cos(x) Out> Sin(x);
See also
-
Limit
(var, val) expr¶ limit of an expression
Parameters: - var – variable
- val – number or
Infinity
- dir – direction (
Left
orRight
) - expr – an expression
This command tries to determine the value that the expression “expr” converges to when the variable “var” approaches “val”. One may use {Infinity} or {-Infinity} for “val”. The result of {Limit} may be one of the symbols {Undefined} (meaning that the limit does not exist), {Infinity}, or {-Infinity}. The second calling sequence is used for unidirectional limits. If one gives “dir” the value {Left}, the limit is taken as “var” approaches “val” from the positive infinity; and {Right} will take the limit from the negative infinity.
Example: In> Limit(x,0) Sin(x)/x Out> 1; In> Limit(x,0) (Sin(x)-Tan(x))/(x^3) Out> -1/2; In> Limit(x,0) 1/x Out> Undefined; In> Limit(x,0,Left) 1/x Out> -Infinity; In> Limit(x,0,Right) 1/x Out> Infinity; Random numbers
-
Random
()¶ (pseudo-) random number generator
Parameters: - init – integer, initial seed value
- option – atom, option name
- value – atom, option value
- r – a list, RNG object
These commands are an object-oriented interface to (pseudo-)random number generators (RNGs). {RngCreate} returns a list which is a well-formed RNG object. Its value should be saved in a variable and used to call {Rng} and {RngSeed}. {Rng(r)} returns a floating-point random number between 0 and 1 and updates the RNG object {r}. (Currently, the Gaussian option makes a RNG return a <i>complex</i> random number instead of a real random number.) {RngSeed(r,init)} re-initializes the RNG object {r} with the seed value {init}. The seed value should be a positive integer. The {RngCreate} function accepts several options as arguments. Currently the following options are available:
-
RandomIntegerMatrix
(rows, cols, from, to)¶ generate a matrix of random integers
Parameters: - rows – number of rows in matrix
- cols – number of cols in matrix
- from – lower bound
- to – upper bound
This function generates a {rows x cols} matrix of random integers. All entries lie between “from” and “to”, including the boundaries, and are uniformly distributed in this interval.
Example: In> PrettyForm( RandomIntegerMatrix(5,5,-2^10,2^10) ) / \ | ( -506 ) ( 749 ) ( -574 ) ( -674 ) ( -106 ) | | | | ( 301 ) ( 151 ) ( -326 ) ( -56 ) ( -277 ) | | | | ( 777 ) ( -761 ) ( -161 ) ( -918 ) ( -417 ) | | | | ( -518 ) ( 127 ) ( 136 ) ( 797 ) ( -406 ) | | | | ( 679 ) ( 854 ) ( -78 ) ( 503 ) ( 772 ) | \ /
See also
-
RandomIntegerVector
(nr, from, to)¶ generate a vector of random integers
Parameters: - nr – number of integers to generate
- from – lower bound
- to – upper bound
This function generates a list with “nr” random integers. All entries lie between “from” and “to”, including the boundaries, and are uniformly distributed in this interval.
Example: In> RandomIntegerVector(4,-3,3) Out> {0,3,2,-2};
See also
-
RandomPoly
(var, deg, coefmin, coefmax)¶ construct a random polynomial
Parameters: - var – free variable for resulting univariate polynomial
- deg – degree of resulting univariate polynomial
- coefmin – minimum value for coefficients
- coefmax – maximum value for coefficients
RandomPoly generates a random polynomial in variable “var”, of degree “deg”, with integer coefficients ranging from “coefmin” to “coefmax” (inclusive). The coefficients are uniformly distributed in this interval, and are independent of each other.
Example: In> RandomPoly(x,3,-10,10) Out> 3*x^3+10*x^2-4*x-6; In> RandomPoly(x,3,-10,10) Out> -2*x^3-8*x^2+8;
See also
-
Add
(val1, val2, ...)¶ find sum of a list of values
Parameters: - {val2 (val1},) – expressions
- {list} – list of expressions to add
This function adds all its arguments and returns their sum. It accepts any number of arguments. The arguments can be also passed as a list.
Example: In> Add(1,4,9); Out> 14; In> Add(1 .. 10); Out> 55;
-
Sum
(var, from, to, body)¶ find sum of a sequence
Parameters: - var – variable to iterate over
- from – integer value to iterate from
- to – integer value to iterate up to
- body – expression to evaluate for each iteration
The command finds the sum of the sequence generated by an iterative formula. The expression “body” is evaluated while the variable “var” ranges over all integers from “from” up to “to”, and the sum of all the results is returned. Obviously, “to” should be greater than or equal to “from”. Warning: {Sum} does not evaluate its arguments {var} and {body} until the actual loop is run.
Example: In> Sum(i, 1, 3, i^2); Out> 14;
See also
-
Factorize
(list)¶ product of a list of values
Parameters: - list – list of values to multiply
- var – variable to iterate over
- from – integer value to iterate from
- to – integer value to iterate up to
- body – expression to evaluate for each iteration
The first form of the {Factorize} command simply multiplies all the entries in “list” and returns their product. If the second calling sequence is used, the expression “body” is evaluated while the variable “var” ranges over all integers from “from” up to “to”, and the product of all the results is returned. Obviously, “to” should be greater than or equal to “from”.
Example: In> Factorize({1,2,3,4}); Out> 24; In> Factorize(i, 1, 4, i); Out> 24;
-
Taylor(var, at, order) expr
univariate Taylor series expansion
Parameters: - var – variable
- at – point to get Taylor series around
- order – order of approximation
- expr – expression to get Taylor series for
This function returns the Taylor series expansion of the expression “expr” with respect to the variable “var” around “at” up to order “order”. This is a polynomial which agrees with “expr” at the point “var = at”, and furthermore the first “order” derivatives of the polynomial at this point agree with “expr”. Taylor expansions around removable singularities are correctly handled by taking the limit as “var” approaches “at”.
Example: In> PrettyForm(Taylor(x,0,9) Sin(x)) 3 5 7 9 x x x x x - -- + --- - ---- + ------ 6 120 5040 362880 Out> True;
See also
D()
,InverseTaylor()
,ReversePoly()
,BigOh()
-
InverseTaylor(var, at, order) expr
Taylor expansion of inverse
Parameters: - var – variable
- at – point to get inverse Taylor series around
- order – order of approximation
- expr – expression to get inverse Taylor series for
This function builds the Taylor series expansion of the inverse of the expression “expr” with respect to the variable “var” around “at” up to order “order”. It uses the function {ReversePoly} to perform the task.
Example: In> PrettyPrinter'Set("PrettyForm") True In> exp1 := Taylor(x,0,7) Sin(x) 3 5 7 x x x x - -- + --- - ---- 6 120 5040 In> exp2 := InverseTaylor(x,0,7) ArcSin(x) 5 7 3 x x x --- - ---- - -- + x 120 5040 6 In> Simplify(exp1-exp2) 0
See also
ReversePoly()
,Taylor()
,BigOh()
-
ReversePoly
(f, g, var, newvar, degree)¶ solve $h(f(x)) = g(x) + O(x^n)$ for $h$
Parameters: - f – function of
var
- g – function of
var
- var – a variable
- newvar – a new variable to express the result in
- degree – the degree of the required solution
This function returns a polynomial in “newvar”, say “h(newvar)”, with the property that “h(f(var))” equals “g(var)” up to order “degree”. The degree of the result will be at most “degree-1”. The only requirement is that the first derivative of “f” should not be zero. This function is used to determine the Taylor series expansion of the inverse of a function “f”: if we take “g(var)=var”, then “h(f(var))=var” (up to order “degree”), so “h” will be the inverse of “f”.
Example: In> f(x):=Eval(Expand((1+x)^4)) Out> True; In> g(x) := x^2 Out> True; In> h(y):=Eval(ReversePoly(f(x),g(x),x,y,8)) Out> True; In> BigOh(h(f(x)),x,8) Out> x^2; In> h(x) Out> (-2695*(x-1)^7)/131072+(791*(x-1)^6)/32768 +(-119*(x-1)^5)/4096+(37*(x-1)^4)/1024+(-3*(x-1)^3)/64+(x-1)^2/16;
See also
InverseTaylor()
,Taylor()
,BigOh()
- f – function of
-
BigOh
(poly, var, degree)¶ drop all terms of a certain order in a polynomial
Parameters: - poly – a univariate polynomial
- var – a free variable
- degree – positive integer
This function drops all terms of order “degree” or higher in “poly”, which is a polynomial in the variable “var”.
Example: In> BigOh(1+x+x^2+x^3,x,2) Out> x+1;
See also
Taylor()
,InverseTaylor()
-
LagrangeInterpolant
(xlist, ylist, var)¶ polynomial interpolation
Parameters: - xlist – list of argument values
- ylist – list of function values
- var – free variable for resulting polynomial
This function returns a polynomial in the variable “var” which interpolates the points “(xlist, ylist)”. Specifically, the value of the resulting polynomial at “xlist[1]” is “ylist[1]”, the value at “xlist[2]” is “ylist[2]”, etc. The degree of the polynomial is not greater than the length of “xlist”. The lists “xlist” and “ylist” should be of equal length. Furthermore, the entries of “xlist” should be all distinct to ensure that there is one and only one solution. This routine uses the Lagrange interpolant formula to build up the polynomial.
Example: In> f := LagrangeInterpolant({0,1,2}, \ {0,1,1}, x); Out> (x*(x-1))/2-x*(x-2); In> Eval(Subst(x,0) f); Out> 0; In> Eval(Subst(x,1) f); Out> 1; In> Eval(Subst(x,2) f); Out> 1; In> PrettyPrinter'Set("PrettyForm"); True In> LagrangeInterpolant({x1,x2,x3}, {y1,y2,y3}, x) y1 * ( x - x2 ) * ( x - x3 ) ---------------------------- ( x1 - x2 ) * ( x1 - x3 ) y2 * ( x - x1 ) * ( x - x3 ) + ---------------------------- ( x2 - x1 ) * ( x2 - x3 ) y3 * ( x - x1 ) * ( x - x2 ) + ---------------------------- ( x3 - x1 ) * ( x3 - x2 )
See also
-
n
!
¶ factorial
Parameters: - m – integer
- n – integer, half-integer, or list
- {b (a},) – numbers
The factorial function {n!} calculates the factorial of integer or half-integer numbers. For nonnegative integers, $n! := n*(n-1)*(n-2)*...*1$. The factorial of half-integers is defined via Euler’s Gamma function, $z! := Gamma(z+1)$. If $n=0$ the function returns $1$. The “double factorial” function {n!!} calculates $n*(n-2)*(n-4)*...$. This product terminates either with $1$ or with $2$ depending on whether $n$ is odd or even. If $n=0$ the function returns $1$. The “partial factorial” function {a * b} calculates the product $a*(a+1)*...$ which is terminated at the least integer not greater than $b$. The arguments $a$ and $b$ do not have to be integers; for integer arguments, {a * b} = $b! / (a-1)!$. This function is sometimes a lot faster than evaluating the two factorials, especially if $a$ and $b$ are close together. If $a>b$ the function returns $1$. The {Subfactorial} function can be interpreted as the number of permutations of {m} objects in which no object appears in its natural place, also called “derangements.” The factorial functions are threaded, meaning that if the argument {n} is a list, the function will be applied to each element of the list. Note: For reasons of Yacas syntax, the factorial sign {!} cannot precede other non-letter symbols such as {+} or {*}. Therefore, you should enter a space after {!} in expressions such as {x! +1}. The factorial functions terminate and print an error message if the arguments are too large (currently the limit is $n < 65535$) because exact factorials of such large numbers are computationally expensive and most probably not useful. One can call {Internal’LnGammaNum()} to evaluate logarithms of such factorials to desired precision.
Example: In> 5! Out> 120; In> 1 * 2 * 3 * 4 * 5 Out> 120; In> (1/2)! Out> Sqrt(Pi)/2; In> 7!!; Out> 105; In> 1/3 *** 10; Out> 17041024000/59049; In> Subfactorial(10) Out> 1334961;
See also
Bin()
,Factorize()
,Gamma()
,!()
,***()
,Subfactorial()
-
n
!!
¶ double factorial
-
x
***
y¶ whatever
-
Bin
(n, m)¶ binomial coefficients
Parameters: {m (n},) – integers This function calculates the binomial coefficient “n” above “m”, which equals $$n! / (m! * (n-m)!)$$ This is equal to the number of ways to choose “m” objects out of a total of “n” objects if order is not taken into account. The binomial coefficient is defined to be zero if “m” is negative or greater than “n”; {Bin(0,0)}=1.
Example: In> Bin(10, 4) Out> 210; In> 10! / (4! * 6!) Out> 210;
See also
()
,Eulerian()
-
Eulerian
(n, m)¶ Eulerian numbers
Parameters: {m (n},) – integers The Eulerian numbers can be viewed as a generalization of the binomial coefficients, and are given explicitly by $$ Sum(j,0,k+1,(-1)^j*Bin(n+1,j)*(k-j+1)^n) $$ .
Example: In> Eulerian(6,2) Out> 302; In> Eulerian(10,9) Out> 1;
See also
-
LeviCivita
(list)¶ totally anti-symmetric Levi-Civita symbol
Parameters: list – a list of integers 1 .. n in some order {LeviCivita} implements the Levi-Civita symbol. This is generally useful for tensor calculus. {list} should be a list of integers, and this function returns 1 if the integers are in successive order, eg. {LeviCivita( {1,2,3,...} )} would return 1. Swapping two elements of this list would return -1. So, {LeviCivita( {2,1,3} )} would evaluate to -1.
Example: In> LeviCivita({1,2,3}) Out> 1; In> LeviCivita({2,1,3}) Out> -1; In> LeviCivita({2,2,3}) Out> 0;
See also
-
Permutations
(list)¶ get all permutations of a list
Parameters: list – a list of elements Permutations returns a list with all the permutations of the original list.
Example: In> Permutations({a,b,c}) Out> {{a,b,c},{a,c,b},{c,a,b},{b,a,c}, {b,c,a},{c,b,a}};
See also
-
Gamma
(x)¶ Euler’s Gamma function
Parameters: - x – expression
- number – expression that can be evaluated to a number
{Gamma(x)} is an interface to Euler’s Gamma function $Gamma(x)$. It returns exact values on integer and half-integer arguments. {N(Gamma(x)} takes a numeric parameter and always returns a floating-point number in the current precision. Note that Euler’s constant $gamma<=>0.57722$ is the lowercase {gamma} in Yacas.
Example: In> Gamma(1.3) Out> Gamma(1.3); In> N(Gamma(1.3),30) Out> 0.897470696306277188493754954771; In> Gamma(1.5) Out> Sqrt(Pi)/2; In> N(Gamma(1.5),30); Out> 0.88622692545275801364908374167;
-
Zeta
(x)¶ Riemann’s Zeta function
Parameters: - x – expression
- number – expression that can be evaluated to a number
{Zeta(x)} is an interface to Riemann’s Zeta function $zeta(s)$. It returns exact values on integer and half-integer arguments. {N(Zeta(x)} takes a numeric parameter and always returns a floating-point number in the current precision.
Example: In> Precision(30) Out> True; In> Zeta(1) Out> Infinity; In> Zeta(1.3) Out> Zeta(1.3); In> N(Zeta(1.3)) Out> 3.93194921180954422697490751058798; In> Zeta(2) Out> Pi^2/6; In> N(Zeta(2)); Out> 1.64493406684822643647241516664602;
See also
()
,N()
-
Bernoulli
(index)¶ Bernoulli numbers and polynomials
Parameters: - x – expression that will be the variable in the polynomial
- index – expression that can be evaluated to an integer
{Bernoulli(n)} evaluates the $n$-th Bernoulli number. {Bernoulli(n, x)} returns the $n$-th Bernoulli polynomial in the variable $x$. The polynomial is returned in the Horner form.
-
Euler
(index)¶ Euler numbers and polynomials
Parameters: - x – expression that will be the variable in the polynomial
- index – expression that can be evaluated to an integer
{Euler(n)} evaluates the $n$-th Euler number. {Euler(n,x)} returns the $n$-th Euler polynomial in the variable $x$.
Example: In> Euler(6) Out> -61; In> A:=Euler(5,x) Out> (x-1/2)^5+(-10*(x-1/2)^3)/4+(25*(x-1/2))/16; In> Simplify(A) Out> (2*x^5-5*x^4+5*x^2-1)/2;
See also
-
LambertW
(x)¶ Lambert’s \(W\) function
Parameters: x – expression, argument of the function Lambert’s \(W\) function is (a multiple-valued, complex function) defined for any (complex) \(z\) by
\[W(z)e^{W(z)}=z\]The \(W\) function is sometimes useful to represent solutions of transcendental equations. For example, the equation $Ln(x)=3*x$ can be “solved” by writing $x= -3*W(-1/3)$. It is also possible to take a derivative or integrate this function “explicitly”. For real arguments $x$, $W(x)$ is real if $x>= -Exp(-1)$. To compute the numeric value of the principal branch of Lambert’s $W$ function for real arguments $x>= -Exp(-1)$ to current precision, one can call {N(LambertW(x))} (where the function {N} tries to approximate its argument with a real value).
Example: In> LambertW(0) Out> 0; In> N(LambertW(-0.24/Sqrt(3*Pi))) Out> -0.0851224014;
See also
Simplification of expressions¶
Simplification of expression is a big and non-trivial subject. Simplification implies that there is a preferred form. In practice the preferred form depends on the calculation at hand. This chapter describes the functions offered that allow simplification of expressions.
-
Simplify
(expr)¶ try to simplify an expression
Parameters: expr – expression to simplify This function tries to simplify the expression {expr} as much as possible. It does this by grouping powers within terms, and then grouping similar terms.
Example: In> a*b*a^2/b-a^3 Out> (b*a^3)/b-a^3; In> Simplify(a*b*a^2/b-a^3) Out> 0;
See also
-
RadSimp
(expr)¶ simplify expression with nested radicals
Parameters: expr – an expression containing nested radicals This function tries to write the expression “expr” as a sum of roots of integers: $Sqrt(e1) + Sqrt(e2) + ...$, where $e1$, $e2$ and so on are natural numbers. The expression “expr” may not contain free variables. It does this by trying all possible combinations for $e1$, $e2$, ... Every possibility is numerically evaluated using {N} and compared with the numerical evaluation of “expr”. If the approximations are equal (up to a certain margin), this possibility is returned. Otherwise, the expression is returned unevaluated. Note that due to the use of numerical approximations, there is a small chance that the expression returned by {RadSimp} is close but not equal to {expr}. The last example underneath illustrates this problem. Furthermore, if the numerical value of {expr} is large, the number of possibilities becomes exorbitantly big so the evaluation may take very long.
Example: In> RadSimp(Sqrt(9+4*Sqrt(2))) Out> Sqrt(8)+1; In> RadSimp(Sqrt(5+2*Sqrt(6)) \ +Sqrt(5-2*Sqrt(6))) Out> Sqrt(12); In> RadSimp(Sqrt(14+3*Sqrt(3+2 *Sqrt(5-12*Sqrt(3-2*Sqrt(2)))))) Out> Sqrt(2)+3; But this command may yield incorrect results: In> RadSimp(Sqrt(1+10^(-6))) Out> 1;
See also
-
FactorialSimplify
(expression)¶ Simplify hypergeometric expressions containing factorials
Parameters: expression – expression to simplify {FactorialSimplify} takes an expression that may contain factorials, and tries to simplify it. An expression like $ (n+1)! / n! $ would simplify to $(n+1)$. The following steps are taken to simplify:
-
LnExpand
(expr)¶ expand a logarithmic expression using standard logarithm rules
Parameters: expr – the logarithm of an expression {LnExpand} takes an expression of the form $Ln(expr)$, and applies logarithm rules to expand this into multiple {Ln} expressions where possible. An expression like $Ln(a*b^n)$ would be expanded to $Ln(a)+n*Ln(b)$. If the logarithm of an integer is discovered, it is factorised using {Factors} and expanded as though {LnExpand} had been given the factorised form. So $Ln(18)$ goes to $Ln(x)+2*Ln(3)$.
-
LnCombine
(expr)¶ combine logarithmic expressions using standard logarithm rules
Parameters: expr – an expression possibly containing multiple {Ln} terms to be combined {LnCombine} finds {Ln} terms in the expression it is given, and combines them using logarithm rules. It is intended to be the exact converse of {LnExpand}.
-
TrigSimpCombine
(expr)¶ combine products of trigonometric functions
Parameters: expr – expression to simplify This function applies the product rules of trigonometry, e.g. $Cos(u)*Sin(v) = (1/2)*(Sin(v-u) + Sin(v+u))$. As a result, all products of the trigonometric functions {Cos} and {Sin} disappear. The function also tries to simplify the resulting expression as much as possible by combining all similar terms. This function is used in for instance {Integrate}, to bring down the expression into a simpler form that hopefully can be integrated easily.
Example: In> PrettyPrinter'Set("PrettyForm"); True In> TrigSimpCombine(Cos(a)^2+Sin(a)^2) 1 In> TrigSimpCombine(Cos(a)^2-Sin(a)^2) Cos( -2 * a ) Out> In> TrigSimpCombine(Cos(a)^2*Sin(b)) Sin( b ) Sin( -2 * a + b ) -------- + ----------------- 2 4 Sin( -2 * a - b ) - ----------------- 4
See also
Solvers¶
By solving one tries to find a mathematical object that meets certain criteria. This chapter documents the functions that are available to help find solutions to specific types of problems.
Symbolic Solvers¶
-
Solve
(eq, var)¶ solve an equation
Parameters: - eq – equation to solve
- var – variable to solve for
This command tries to solve an equation. If {eq} does not contain the {==} operator, it is assumed that the user wants to solve $eq == 0$. The result is a list of equations of the form {var == value}, each representing a solution of the given equation. The {Where} operator can be used to substitute this solution in another expression. If the given equation {eq} does not have any solutions, or if {Solve} is unable to find any, then an empty list is returned. The current implementation is far from perfect. In particular, the user should keep the following points in mind:
-
OldSolve
(eq, var)¶ old version of {Solve}
Parameters: - eq – single identity equation
- var – single variable
- eqlist – list of identity equations
- varlist – list of variables
This is an older version of {Solve}. It is retained for two reasons. The first one is philosophical: it is good to have multiple algorithms available. The second reason is more practical: the newer version cannot handle systems of equations, but {OldSolve} can. This command tries to solve one or more equations. Use the first form to solve a single equation and the second one for systems of equations. The first calling sequence solves the equation “eq” for the variable “var”. Use the {==} operator to form the equation. The value of “var” which satisfies the equation, is returned. Note that only one solution is found and returned. To solve a system of equations, the second form should be used. It solves the system of equations contained in the list “eqlist” for the variables appearing in the list “varlist”. A list of results is returned, and each result is a list containing the values of the variables in “varlist”. Again, at most a single solution is returned. The task of solving a single equation is simply delegated to {SuchThat}. Multiple equations are solved recursively: firstly, an equation is sought in which one of the variables occurs exactly once; then this equation is solved with {SuchThat}; and finally the solution is substituted in the other equations by {Eliminate} decreasing the number of equations by one. This suffices for all linear equations and a large group of simple nonlinear equations.
Example: In> OldSolve(a+x*y==z,x) Out> (z-a)/y; In> OldSolve({a*x+y==0,x+z==0},{x,y}) Out> {{-z,z*a}}; This means that "x = (z-a)/y" is a solution of the first equation and that "x = -z", "y = z*a" is a solution of the systems of equations in the second command. An example which {OldSolve} cannot solve: In> OldSolve({x^2-x == y^2-y,x^2-x == y^3+y},{x,y}); Out> {};
See also
Solve()
,SuchThat()
,Eliminate()
,PSolve()
,==()
-
SuchThat
(expr, var)¶ special purpose solver
Parameters: - expr – expression to make zero
- var – variable (or subexpression) to solve for
This functions tries to find a value of the variable “var” which makes the expression “expr” zero. It is also possible to pass a subexpression as “var”, in which case {SuchThat} will try to solve for that subexpression. Basically, only expressions in which “var” occurs only once are handled; in fact, {SuchThat} may even give wrong results if the variables occurs more than once. This is a consequence of the implementation, which repeatedly applies the inverse of the top function until the variable “var” is reached.
Example: In> SuchThat(a+b*x, x) Out> (-a)/b; In> SuchThat(Cos(a)+Cos(b)^2, Cos(b)) Out> Cos(a)^(1/2); In> A:=Expand(a*x+b*x+c, x) Out> (a+b)*x+c; In> SuchThat(A, x) Out> (-c)/(a+b);
See also
-
Eliminate
(var, value, expr)¶ substitute and simplify
Parameters: - var – variable (or subexpression) to substitute
- value – new value of “var”
- expr – expression in which the substitution should take place
This function uses {Subst} to replace all instances of the variable (or subexpression) “var” in the expression “expr” with “value”, calls {Simplify} to simplify the resulting expression, and returns the result.
Example: In> Subst(Cos(b), c) (Sin(a)+Cos(b)^2/c) Out> Sin(a)+c^2/c; In> Eliminate(Cos(b), c, Sin(a)+Cos(b)^2/c) Out> Sin(a)+c;
See also
-
PSolve
(poly, var)¶ solve a polynomial equation
Parameters: - poly – a polynomial in “var”
- var – a variable
This commands returns a list containing the roots of “poly”, considered as a polynomial in the variable “var”. If there is only one root, it is not returned as a one-entry list but just by itself. A double root occurs twice in the result, and similarly for roots of higher multiplicity. All polynomials of degree up to 4 are handled.
Example: In> PSolve(b*x+a,x) Out> -a/b; In> PSolve(c*x^2+b*x+a,x) Out> {(Sqrt(b^2-4*c*a)-b)/(2*c),(-(b+ Sqrt(b^2-4*c*a)))/(2*c)};
-
MatrixSolve
(A, b)¶ solve a system of equations
Parameters: - A – coefficient matrix
- b – row vector
{MatrixSolve} solves the matrix equations {A*x = b} using Gaussian Elimination with Backward substitution. If your matrix is triangular or diagonal, it will be recognized as such and a faster algorithm will be used.
Example: In> A:={{2,4,-2,-2},{1,2,4,-3},{-3,-3,8,-2},{-1,1,6,-3}}; Out> {{2,4,-2,-2},{1,2,4,-3},{-3,-3,8,-2},{-1,1,6,-3}}; In> b:={-4,5,7,7}; Out> {-4,5,7,7}; In> MatrixSolve(A,b); Out> {1,2,3,4}; Numeric solvers
Numeric Solvers¶
-
Newton
(expr, var, initial, accuracy)¶ solve an equation numerically with Newton’s method
Parameters: - expr – an expression to find a zero for
- var – free variable to adjust to find a zero
- initial – initial value for “var” to use in the search
- accuracy – minimum required accuracy of the result
- min – minimum value for “var” to use in the search
- max – maximum value for “var” to use in the search
This function tries to numerically find a zero of the expression {expr}, which should depend only on the variable {var}. It uses the value {initial} as an initial guess. The function will iterate using Newton’s method until it estimates that it has come within a distance {accuracy} of the correct solution, and then it will return its best guess. In particular, it may loop forever if the algorithm does not converge. When {min} and {max} are supplied, the Newton iteration takes them into account by returning {Fail} if it failed to find a root in the given range. Note this doesn’t mean there isn’t a root, just that this algorithm failed to find it due to the trial values going outside of the bounds.
Example: In> Newton(Sin(x),x,3,0.0001) Out> 3.1415926535; In> Newton(x^2-1,x,2,0.0001,-5,5) Out> 1; In> Newton(x^2+1,x,2,0.0001,-5,5) Out> Fail;
See also
-
FindRealRoots
(p)¶ find the real roots of a polynomial
Parameters: p – a polynomial in {x} Return a list with the real roots of $ p $. It tries to find the real-valued roots, and thus requires numeric floating point calculations. The precision of the result can be improved by increasing the calculation precision.
Example: In> p:=Expand((x+3.1)^5*(x-6.23)) Out> x^6+9.27*x^5-0.465*x^4-300.793*x^3- 1394.2188*x^2-2590.476405*x-1783.5961073; In> FindRealRoots(p) Out> {-3.1,6.23};
See also
SquareFree()
,NumRealRoots()
,MinimumBound()
,MaximumBound()
,Factor()
-
NumRealRoots
(p)¶ return the number of real roots of a polynomial
Parameters: p – a polynomial in {x} Returns the number of real roots of a polynomial $ p $. The polynomial must use the variable {x} and no other variables.
Example: In> NumRealRoots(x^2-1) Out> 2; In> NumRealRoots(x^2+1) Out> 0;
See also
FindRealRoots()
,SquareFree()
,MinimumBound()
,MaximumBound()
,Factor()
-
MinimumBound
(p)¶ return lower bounds on the absolute values of real roots of a polynomial
Parameters: p – a polynomial in $x$ Return minimum and maximum bounds for the absolute values of the real roots of a polynomial {p}. The polynomial has to be converted to one with rational coefficients first, and be made square-free. The polynomial must use the variable {x}.
Example: In> p:=SquareFree(Rationalize((x-3.1)*(x+6.23))) Out> (-40000*x^2-125200*x+772520)/870489; In> MinimumBound(p) Out> 5000000000/2275491039; In> N(%) Out> 2.1973279236; In> MaximumBound(p) Out> 10986639613/1250000000; In> N(%) Out> 8.7893116904;
See also
Differential Equations¶
In this chapter, some facilities for solving differential equations are described. Currently only simple equations without auxiliary conditions are supported.
-
OdeSolve
(expr1==expr2)¶ general ODE solver
Parameters: expr1,expr2 – expressions containing a function to solve for This function currently can solve second order homogeneous linear real constant coefficient equations. The solution is returned with unique constants generated by {UniqueConstant}. The roots of the auxiliary equation are used as the arguments of exponentials. If the roots are complex conjugate pairs, then the solution returned is in the form of exponentials, sines and cosines. First and second derivatives are entered as {y’,y’‘}. Higher order derivatives may be entered as {y(n)}, where {n} is any integer.
Example: In> OdeSolve( y'' + y == 0 ) Out> C42*Sin(x)+C43*Cos(x); In> OdeSolve( 2*y'' + 3*y' + 5*y == 0 ) Out> Exp(((-3)*x)/4)*(C78*Sin(Sqrt(31/16)*x)+C79*Cos(Sqrt(31/16)*x)); In> OdeSolve( y'' - 4*y == 0 ) Out> C132*Exp((-2)*x)+C136*Exp(2*x); In> OdeSolve( y'' +2*y' + y == 0 ) Out> (C183+C184*x)*Exp(-x);
See also
Solve()
,RootsWithMultiples()
-
OdeTest
(eqn, testsol)¶ test the solution of an ODE
Parameters: - eqn – equation to test
- testsol – test solution
This function automates the verification of the solution of an ODE. It can also be used to quickly see how a particular equation operates on a function.
Example: In> OdeTest(y''+y,Sin(x)+Cos(x)) Out> 0; In> OdeTest(y''+2*y,Sin(x)+Cos(x)) Out> Sin(x)+Cos(x);
See also
-
OdeOrder
(eqn)¶ return order of an ODE
Parameters: eqn – equation This function returns the order of the differential equation, which is order of the highest derivative. If no derivatives appear, zero is returned.
Example: In> OdeOrder(y'' + 2*y' == 0) Out> 2; In> OdeOrder(Sin(x)*y(5) + 2*y' == 0) Out> 5; In> OdeOrder(2*y + Sin(y) == 0) Out> 0;
See also
Propositional logic theorem prover¶
-
CanProve
(proposition)¶ try to prove statement
Parameters: proposition – an expression with logical operations Yacas has a small built-in propositional logic theorem prover. It can be invoked with a call to {CanProve}. An example of a proposition is: “if a implies b and b implies c then a implies c”. Yacas supports the following logical operations: {Not} : negation, read as “not” {And} : conjunction, read as “and” {Or} : disjunction, read as “or” {=>} : implication, read as “implies” The abovementioned proposition would be represented by the following expression, ( (a=>b) And (b=>c) ) => (a=>c) Yacas can prove that is correct by applying {CanProve} to it: In> CanProve(( (a=>b) And (b=>c) ) => (a=>c)) Out> True; It does this in the following way: in order to prove a proposition $p$, it suffices to prove that $Not p$ is false. It continues to simplify $Not p$ using the rules: Not ( Not x) –> x (eliminate double negation), x=>y –> Not x Or y (eliminate implication), Not (x And y) –> Not x Or Not y (De Morgan’s law), Not (x Or y) –> Not x And Not y (De Morgan’s law), (x And y) Or z –> (x Or z) And (y Or z) (distribution), x Or (y And z) –> (x Or y) And (x Or z) (distribution), and the obvious other rules, such as, True Or x –> True etc. The above rules will translate a proposition into a form (p1 Or p2 Or ...) And (q1 Or q2 Or ...) And ... If any of the clauses is false, the entire expression will be false. In the next step, clauses are scanned for situations of the form: (p Or Y) And ( Not p Or Z) –> (Y Or Z) If this combination {(Y Or Z)} is empty, it is false, and thus the entire proposition is false. As a last step, the algorithm negates the result again. This has the added advantage of simplifying the expression further.
Example: In> CanProve(a Or Not a) Out> True; In> CanProve(True Or a) Out> True; In> CanProve(False Or a) Out> a; In> CanProve(a And Not a) Out> False; In> CanProve(a Or b Or (a And b)) Out> a Or b;
Linear Algebra¶
This chapter describes the commands for doing linear algebra. They can be used to manipulate vectors, represented as lists, and matrices, represented as lists of lists.
-
Dot
(t1, t2)¶ get dot product of tensors
Parameters: t1,t2 – tensor lists (currently only vectors and matrices are supported) {Dot} returns the dot (aka inner) product of two tensors t1 and t2. The last index of t1 and the first index of t2 are contracted. Currently {Dot} works only for vectors and matrices. {Dot}-multiplication of two vectors, a matrix with a vector (and vice versa) or two matrices yields either a scalar, a vector or a matrix.
Example: In> Dot({1,2},{3,4}) Out> 11; In> Dot({{1,2},{3,4}},{5,6}) Out> {17,39}; In> Dot({5,6},{{1,2},{3,4}}) Out> {23,34}; In> Dot({{1,2},{3,4}},{{5,6},{7,8}}) Out> {{19,22},{43,50}}; Or, using the "."-Operator: In> {1,2} . {3,4} Out> 11; In> {{1,2},{3,4}} . {5,6} Out> {17,39}; In> {5,6} . {{1,2},{3,4}} Out> {23,34}; In> {{1,2},{3,4}} . {{5,6},{7,8}} Out> {{19,22},{43,50}};
See also
Outer()
,Cross()
,IsScalar()
,IsVector()
,IsMatrix()
-
InProduct
(a, b)¶ inner product of vectors (deprecated)
Parameters: {b (a},) – vectors of equal length The inner product of the two vectors “a” and “b” is returned. The vectors need to have the same size. This function is superceded by the {.} operator.
Example: In> {a,b,c} . {d,e,f}; Out> a*d+b*e+c*f;
See also
-
CrossProduct
(a, b)¶ outer product of vectors
Parameters: {b (a},) – three-dimensional vectors The cross product of the vectors “a” and “b” is returned. The result is perpendicular to both “a” and “b” and its length is the product of the lengths of the vectors. Both “a” and “b” have to be three-dimensional.
Example: In> {a,b,c} X {d,e,f}; Out> {b*f-c*e,c*d-a*f,a*e-b*d};
See also
-
Outer
(t1, t2)¶ get outer tensor product
Parameters: t1,t2 – tensor lists (currently only vectors are supported) {Outer} returns the outer product of two tensors t1 and t2. Currently {Outer} work works only for vectors, i.e. tensors of rank 1. The outer product of two vectors yields a matrix.
Example: In> Outer({1,2},{3,4,5}) Out> {{3,4,5},{6,8,10}}; In> Outer({a,b},{c,d}) Out> {{a*c,a*d},{b*c,b*d}}; Or, using the "o"-Operator: In> {1,2} o {3,4,5} Out> {{3,4,5},{6,8,10}}; In> {a,b} o {c,d} Out> {{a*c,a*d},{b*c,b*d}};
See also
Dot()
,Cross()
-
ZeroVector
(n)¶ create a vector with all zeroes
Parameters: n – length of the vector to return This command returns a vector of length “n”, filled with zeroes.
Example: In> ZeroVector(4) Out> {0,0,0,0};
See also
-
BaseVector
(k, n)¶ base vector
Parameters: - k – index of the base vector to construct
- n – dimension of the vector
This command returns the “k”-th base vector of dimension “n”. This is a vector of length “n” with all zeroes except for the “k”-th entry, which contains a 1.
Example: In> BaseVector(2,4) Out> {0,1,0,0};
See also
-
Identity
(n)¶ make identity matrix
Parameters: n – size of the matrix This commands returns the identity matrix of size “n” by “n”. This matrix has ones on the diagonal while the other entries are zero.
Example: In> Identity(3) Out> {{1,0,0},{0,1,0},{0,0,1}};
See also
-
ZeroMatrix
(n)¶ make a zero matrix
Parameters: - n – number of rows
- m – number of columns
This command returns a matrix with {n} rows and {m} columns, completely filled with zeroes. If only given one parameter, it returns the square {n} by {n} zero matrix.
Example: In> ZeroMatrix(3,4) Out> {{0,0,0,0},{0,0,0,0},{0,0,0,0}}; In> ZeroMatrix(3) Out> {{0,0,0},{0,0,0},{0,0,0}};
See also
-
Diagonal
(A)¶ extract the diagonal from a matrix
Parameters: A – matrix This command returns a vector of the diagonal components of the matrix {A}.
Example: In> Diagonal(5*Identity(4)) Out> {5,5,5,5}; In> Diagonal(HilbertMatrix(3)) Out> {1,1/3,1/5};
See also
-
DiagonalMatrix
(d)¶ construct a diagonal matrix
Parameters: d – list of values to put on the diagonal This command constructs a diagonal matrix, that is a square matrix whose off-diagonal entries are all zero. The elements of the vector “d” are put on the diagonal.
Example: In> DiagonalMatrix(1 .. 4) Out> {{1,0,0,0},{0,2,0,0},{0,0,3,0},{0,0,0,4}};
See also
-
OrthogonalBasis
(W)¶ create an orthogonal basis
Parameters: W – A linearly independent set of row vectors (aka a matrix) Given a linearly independent set {W} (constructed of rows vectors), this command returns an orthogonal basis {V} for {W}, which means that span(V) = span(W) and {InProduct(V[i],V[j]) = 0} when {i != j}. This function uses the Gram-Schmidt orthogonalization process.
Example: In> OrthogonalBasis({{1,1,0},{2,0,1},{2,2,1}}) Out> {{1,1,0},{1,-1,1},{-1/3,1/3,2/3}};
See also
-
OrthonormalBasis
(W)¶ create an orthonormal basis
Parameters: W – A linearly independent set of row vectors (aka a matrix) Given a linearly independent set {W} (constructed of rows vectors), this command returns an orthonormal basis {V} for {W}. This is done by first using {OrthogonalBasis(W)}, then dividing each vector by its magnitude, so as the give them unit length.
Example: In> OrthonormalBasis({{1,1,0},{2,0,1},{2,2,1}}) Out> {{Sqrt(1/2),Sqrt(1/2),0},{Sqrt(1/3),-Sqrt(1/3),Sqrt(1/3)}, {-Sqrt(1/6),Sqrt(1/6),Sqrt(2/3)}};
See also
-
Normalize
(v)¶ normalize a vector
Parameters: v – a vector Return the normalized (unit) vector parallel to {v}: a vector having the same direction but with length 1.
Example: In> v:=Normalize({3,4}) Out> {3/5,4/5}; In> v . v Out> 1;
See also
-
Transpose
(M)¶ get transpose of a matrix
Parameters: M – a matrix {Transpose} returns the transpose of a matrix $M$. Because matrices are just lists of lists, this is a useful operation too for lists.
Example: In> Transpose({{a,b}}) Out> {{a},{b}};
-
Determinant
(M)¶ determinant of a matrix
Parameters: M – a matrix Returns the determinant of a matrix M.
Example: In> A:=DiagonalMatrix(1 .. 4) Out> {{1,0,0,0},{0,2,0,0},{0,0,3,0},{0,0,0,4}}; In> Determinant(A) Out> 24;
-
Trace
(M)¶ trace of a matrix
Parameters: M – a matrix {Trace} returns the trace of a matrix $M$ (defined as the sum of the elements on the diagonal of the matrix).
Example: In> A:=DiagonalMatrix(1 .. 4) Out> {{1,0,0,0},{0,2,0,0},{0,0,3,0},{0,0,0,4}}; In> Trace(A) Out> 10;
-
Inverse
(M)¶ get inverse of a matrix
Parameters: M – a matrix Inverse returns the inverse of matrix $M$. The determinant of $M$ should be non-zero. Because this function uses {Determinant} for calculating the inverse of a matrix, you can supply matrices with non-numeric (symbolic) matrix elements.
Example: In> A:=DiagonalMatrix({a,b,c}) Out> {{a,0,0},{0,b,0},{0,0,c}}; In> B:=Inverse(A) Out> {{(b*c)/(a*b*c),0,0},{0,(a*c)/(a*b*c),0}, {0,0,(a*b)/(a*b*c)}}; In> Simplify(B) Out> {{1/a,0,0},{0,1/b,0},{0,0,1/c}};
See also
-
Minor
(M, i, j)¶ get principal minor of a matrix
Parameters: - M – a matrix
- {j (i},) – positive integers
Minor returns the minor of a matrix around the element ($i$, $j$). The minor is the determinant of the matrix obtained from $M$ by deleting the $i$-th row and the $j$-th column.
Example: In> A := {{1,2,3}, {4,5,6}, {7,8,9}}; Out> {{1,2,3},{4,5,6},{7,8,9}}; In> PrettyForm(A); / \ | ( 1 ) ( 2 ) ( 3 ) | | | | ( 4 ) ( 5 ) ( 6 ) | | | | ( 7 ) ( 8 ) ( 9 ) | \ / Out> True; In> Minor(A,1,2); Out> -6; In> Determinant({{2,3}, {8,9}}); Out> -6;
See also
-
CoFactor
(M, i, j)¶ cofactor of a matrix
Parameters: - M – a matrix
- {j (i},) – positive integers
{CoFactor} returns the cofactor of a matrix around the element ($i$, $j$). The cofactor is the minor times $(-1)^(i+j)$.
Example: In> A := {{1,2,3}, {4,5,6}, {7,8,9}}; Out> {{1,2,3},{4,5,6},{7,8,9}}; In> PrettyForm(A); / \ | ( 1 ) ( 2 ) ( 3 ) | | | | ( 4 ) ( 5 ) ( 6 ) | | | | ( 7 ) ( 8 ) ( 9 ) | \ / Out> True; In> CoFactor(A,1,2); Out> 6; In> Minor(A,1,2); Out> -6; In> Minor(A,1,2) * (-1)^(1+2); Out> 6;
See also
-
MatrixPower
(mat, n)¶ get nth power of a square matrix
Parameters: - mat – a square matrix
- n – an integer
{MatrixPower(mat,n)} returns the {n}th power of a square matrix {mat}. For positive {n} it evaluates dot products of {mat} with itself. For negative {n} the nth power of the inverse of {mat} is returned. For {n}=0 the identity matrix is returned.
-
SolveMatrix
(M, v)¶ solve a linear system
Parameters: - M – a matrix
- v – a vector
{SolveMatrix} returns the vector $x$ that satisfies the equation $M*x = v$. The determinant of $M$ should be non-zero.
Example: In> A := {{1,2}, {3,4}}; Out> {{1,2},{3,4}}; In> v := {5,6}; Out> {5,6}; In> x := SolveMatrix(A, v); Out> {-4,9/2}; In> A * x; Out> {5,6};
See also
-
CharacteristicEquation
(matrix, var)¶ get characteristic polynomial of a matrix
Parameters: - matrix – a matrix
- var – a free variable
CharacteristicEquation returns the characteristic equation of “matrix”, using “var”. The zeros of this equation are the eigenvalues of the matrix, Det(matrix-I*var);
Example: In> A:=DiagonalMatrix({a,b,c}) Out> {{a,0,0},{0,b,0},{0,0,c}}; In> B:=CharacteristicEquation(A,x) Out> (a-x)*(b-x)*(c-x); In> Expand(B,x) Out> (b+a+c)*x^2-x^3-((b+a)*c+a*b)*x+a*b*c;
See also
-
EigenValues
(matrix)¶ get eigenvalues of a matrix
Parameters: matrix – a square matrix EigenValues returns the eigenvalues of a matrix. The eigenvalues x of a matrix M are the numbers such that $M*v=x*v$ for some vector. It first determines the characteristic equation, and then factorizes this equation, returning the roots of the characteristic equation Det(matrix-x*identity).
Example: In> M:={{1,2},{2,1}} Out> {{1,2},{2,1}}; In> EigenValues(M) Out> {3,-1};
See also
-
EigenVectors
(A, eigenvalues)¶ get eigenvectors of a matrix
Parameters: - matrix – a square matrix
- eigenvalues – list of eigenvalues as returned by {EigenValues}
{EigenVectors} returns a list of the eigenvectors of a matrix. It uses the eigenvalues and the matrix to set up n equations with n unknowns for each eigenvalue, and then calls {Solve} to determine the values of each vector.
Example: In> M:={{1,2},{2,1}} Out> {{1,2},{2,1}}; In> e:=EigenValues(M) Out> {3,-1}; In> EigenVectors(M,e) Out> {{-ki2/ -1,ki2},{-ki2,ki2}};
See also
-
Sparsity
(matrix)¶ get the sparsity of a matrix
Parameters: matrix – a matrix The function {Sparsity} returns a number between {0} and {1} which represents the percentage of zero entries in the matrix. Although there is no definite critical value, a sparsity of {0.75} or more is almost universally considered a “sparse” matrix. These type of matrices can be handled in a different manner than “full” matrices which speedup many calculations by orders of magnitude.
Example: In> Sparsity(Identity(2)) Out> 0.5; In> Sparsity(Identity(10)) Out> 0.9; In> Sparsity(HankelMatrix(10)) Out> 0.45; In> Sparsity(HankelMatrix(100)) Out> 0.495; In> Sparsity(HilbertMatrix(10)) Out> 0; In> Sparsity(ZeroMatrix(10,10)) Out> 1;
-
Cholesky
(A)¶ find the Cholesky Decomposition
Parameters: A – a square positive definite matrix {Cholesky} returns a upper triangular matrix {R} such that {Transpose(R)*R = A}. The matrix {A} must be positive definite, {Cholesky} will notify the user if the matrix is not. Some families of positive definite matrices are all symmetric matrices, diagonal matrices with positive elements and Hilbert matrices.
Example: In> A:={{4,-2,4,2},{-2,10,-2,-7},{4,-2,8,4},{2,-7,4,7}} Out> {{4,-2,4,2},{-2,10,-2,-7},{4,-2,8,4},{2,-7,4,7}}; In> R:=Cholesky(A); Out> {{2,-1,2,1},{0,3,0,-2},{0,0,2,1},{0,0,0,1}}; In> Transpose(R)*R = A Out> True; In> Cholesky(4*Identity(5)) Out> {{2,0,0,0,0},{0,2,0,0,0},{0,0,2,0,0},{0,0,0,2,0},{0,0,0,0,2}}; In> Cholesky(HilbertMatrix(3)) Out> {{1,1/2,1/3},{0,Sqrt(1/12),Sqrt(1/12)},{0,0,Sqrt(1/180)}}; In> Cholesky(ToeplitzMatrix({1,2,3})) In function "Check" : CommandLine(1) : "Cholesky: Matrix is not positive definite"
See also
-
IsScalar
(expr)¶ test for a scalar
Parameters: expr – a mathematical object {IsScalar} returns
True
if {expr} is a scalar,False
otherwise. Something is considered to be a scalar if it’s not a list.Example: In> IsScalar(7) Out> True; In> IsScalar(Sin(x)+x) Out> True; In> IsScalar({x,y}) Out> False;
See also
-
IsVector
([pred, ]expr)¶ test for a vector
Parameters: - expr – expression to test
- pred – predicate test (e.g. IsNumber, IsInteger, ...)
{IsVector(expr)} returns
True
if {expr} is a vector,False
otherwise. Something is considered to be a vector if it’s a list of scalars. {IsVector(pred,expr)} returnsTrue
if {expr} is a vector and if the predicate test {pred} returnsTrue
when applied to every element of the vector {expr},False
otherwise.Example: In> IsVector({a,b,c}) Out> True; In> IsVector({a,{b},c}) Out> False; In> IsVector(IsInteger,{1,2,3}) Out> True; In> IsVector(IsInteger,{1,2.5,3}) Out> False;
See also
-
IsMatrix
([pred, ]expr)¶ test for a matrix
Parameters: - expr – expression to test
- pred – predicate test (e.g. IsNumber, IsInteger, ...)
{IsMatrix(expr)} returns
True
if {expr} is a matrix,False
otherwise. Something is considered to be a matrix if it’s a list of vectors of equal length. {IsMatrix(pred,expr)} returnsTrue
if {expr} is a matrix and if the predicate test {pred} returnsTrue
when applied to every element of the matrix {expr},False
otherwise.Example: In> IsMatrix(1) Out> False; In> IsMatrix({1,2}) Out> False; In> IsMatrix({{1,2},{3,4}}) Out> True; In> IsMatrix(IsRational,{{1,2},{3,4}}) Out> False; In> IsMatrix(IsRational,{{1/2,2/3},{3/4,4/5}}) Out> True;
See also
-
IsSquareMatrix
([pred, ]expr)¶ test for a square matrix
Parameters: - expr – expression to test
- pred – predicate test (e.g. IsNumber, IsInteger, ...)
{IsSquareMatrix(expr)} returns
True
if {expr} is a square matrix,False
otherwise. Something is considered to be a square matrix if it’s a matrix having the same number of rows and columns. {IsMatrix(pred,expr)} returnsTrue
if {expr} is a square matrix and if the predicate test {pred} returnsTrue
when applied to every element of the matrix {expr},False
otherwise.Example: In> IsSquareMatrix({{1,2},{3,4}}); Out> True; In> IsSquareMatrix({{1,2,3},{4,5,6}}); Out> False; In> IsSquareMatrix(IsBoolean,{{1,2},{3,4}}); Out> False; In> IsSquareMatrix(IsBoolean,{{True,False},{False,True}}); Out> True;
See also
-
IsHermitian
(A)¶ test for a Hermitian matrix
Parameters: A – a square matrix IsHermitian(A) returns
True
if {A} is Hermitian andFalse
otherwise. $A$ is a Hermitian matrix iff Conjugate( Transpose $A$ )=$A$. If $A$ is a real matrix, it must be symmetric to be Hermitian.Example: In> IsHermitian({{0,I},{-I,0}}) Out> True; In> IsHermitian({{0,I},{2,0}}) Out> False;
See also
-
IsOrthogonal
(A)¶ test for an orthogonal matrix
Parameters: A – square matrix {IsOrthogonal(A)} returns
True
if {A} is orthogonal andFalse
otherwise. $A$ is orthogonal iff $A$*Transpose($A$) = Identity, or equivalently Inverse($A$) = Transpose($A$).Example: In> A := {{1,2,2},{2,1,-2},{-2,2,-1}}; Out> {{1,2,2},{2,1,-2},{-2,2,-1}}; In> PrettyForm(A/3) / \ | / 1 \ / 2 \ / 2 \ | | | - | | - | | - | | | \ 3 / \ 3 / \ 3 / | | | | / 2 \ / 1 \ / -2 \ | | | - | | - | | -- | | | \ 3 / \ 3 / \ 3 / | | | | / -2 \ / 2 \ / -1 \ | | | -- | | - | | -- | | | \ 3 / \ 3 / \ 3 / | \ / Out> True; In> IsOrthogonal(A/3) Out> True;
-
IsDiagonal
(A)¶ test for a diagonal matrix
Parameters: A – a matrix {IsDiagonal(A)} returns
True
if {A} is a diagonal square matrix andFalse
otherwise.Example: In> IsDiagonal(Identity(5)) Out> True; In> IsDiagonal(HilbertMatrix(5)) Out> False;
-
IsLowerTriangular
(A)¶ test for a lower triangular matrix
Parameters: A – a matrix A lower/upper triangular matrix is a square matrix which has all zero entries above/below the diagonal. {IsLowerTriangular(A)} returns
True
if {A} is a lower triangular matrix andFalse
otherwise. {IsUpperTriangular(A)} returnsTrue
if {A} is an upper triangular matrix andFalse
otherwise.Example: In> IsUpperTriangular(Identity(5)) Out> True; In> IsLowerTriangular(Identity(5)) Out> True; In> IsLowerTriangular({{1,2},{0,1}}) Out> False; In> IsUpperTriangular({{1,2},{0,1}}) Out> True; A non-square matrix cannot be triangular: In> IsUpperTriangular({{1,2,3},{0,1,2}}) Out> False;
See also
-
IsSymmetric
(A)¶ test for a symmetric matrix
Parameters: A – a matrix {IsSymmetric(A)} returns
True
if {A} is symmetric andFalse
otherwise. $A$ is symmetric iff Transpose ($A$) =$A$.Example: In> A := {{1,0,0,0,1},{0,2,0,0,0},{0,0,3,0,0}, {0,0,0,4,0},{1,0,0,0,5}}; In> PrettyForm(A) / \ | ( 1 ) ( 0 ) ( 0 ) ( 0 ) ( 1 ) | | | | ( 0 ) ( 2 ) ( 0 ) ( 0 ) ( 0 ) | | | | ( 0 ) ( 0 ) ( 3 ) ( 0 ) ( 0 ) | | | | ( 0 ) ( 0 ) ( 0 ) ( 4 ) ( 0 ) | | | | ( 1 ) ( 0 ) ( 0 ) ( 0 ) ( 5 ) | \ / Out> True; In> IsSymmetric(A) Out> True;
See also
-
IsSkewSymmetric
(A)¶ test for a skew-symmetric matrix
Parameters: A – a square matrix {IsSkewSymmetric(A)} returns
True
if {A} is skew symmetric andFalse
otherwise. $A$ is skew symmetric iff $Transpose(A)$ =$-A$.Example: In> A := {{0,-1},{1,0}} Out> {{0,-1},{1,0}}; In> PrettyForm(%) / \ | ( 0 ) ( -1 ) | | | | ( 1 ) ( 0 ) | \ / Out> True; In> IsSkewSymmetric(A); Out> True;
See also
-
IsUnitary
(A)¶ test for a unitary matrix
Parameters: A – a square matrix This function tries to find out if A is unitary. A matrix $A$ is orthogonal iff $A^(-1)$ = Transpose( Conjugate($A$) ). This is equivalent to the fact that the columns of $A$ build an orthonormal system (with respect to the scalar product defined by {InProduct}).
Example: In> IsUnitary({{0,I},{-I,0}}) Out> True; In> IsUnitary({{0,I},{2,0}}) Out> False;
See also
-
IsIdempotent
(A)¶ test for an idempotent matrix
Parameters: A – a square matrix {IsIdempotent(A)} returns
True
if {A} is idempotent andFalse
otherwise. $A$ is idempotent iff $A^2=A$. Note that this also implies that $A$ raised to any power is also equal to $A$.Example: In> IsIdempotent(ZeroMatrix(10,10)); Out> True; In> IsIdempotent(Identity(20)) Out> True; Special matrices
-
JacobianMatrix
(functions, variables)¶ calculate the Jacobian matrix of $n$ functions in $n$ variables
Parameters: - functions – an $n$-dimensional vector of functions
- variables – an $n$-dimensional vector of variables
The function {JacobianMatrix} calculates the Jacobian matrix of n functions in n variables. The ($i$,$j$)-th element of the Jacobian matrix is defined as the derivative of $i$-th function with respect to the $j$-th variable.
Example: In> JacobianMatrix( {Sin(x),Cos(y)}, {x,y} ); Out> {{Cos(x),0},{0,-Sin(y)}}; In> PrettyForm(%) / \ | ( Cos( x ) ) ( 0 ) | | | | ( 0 ) ( -( Sin( y ) ) ) | \ /
-
VandermondeMatrix
(vector)¶ create the Vandermonde matrix
Parameters: vector – an $n$-dimensional vector The function {VandermondeMatrix} calculates the Vandermonde matrix of a vector. The ($i$,$j$)-th element of the Vandermonde matrix is defined as $i^(j-1)$.
Example: In> VandermondeMatrix({1,2,3,4}) Out> {{1,1,1,1},{1,2,3,4},{1,4,9,16},{1,8,27,64}}; In>PrettyForm(%) / \ | ( 1 ) ( 1 ) ( 1 ) ( 1 ) | | | | ( 1 ) ( 2 ) ( 3 ) ( 4 ) | | | | ( 1 ) ( 4 ) ( 9 ) ( 16 ) | | | | ( 1 ) ( 8 ) ( 27 ) ( 64 ) | \ /
-
HessianMatrix
(function, var)¶ create the Hessian matrix
Parameters: - function – a function in $n$ variables
- var – an $n$-dimensional vector of variables
The function {HessianMatrix} calculates the Hessian matrix of a vector. If $f(x)$ is a function of an $n$-dimensional vector $x$, then the ($i$,$j$)-th element of the Hessian matrix of the function $f(x)$ is defined as $ Deriv(x[i]) Deriv(x[j]) f(x) $. If the third order mixed partials are continuous, then the Hessian matrix is symmetric (a standard theorem of calculus). The Hessian matrix is used in the second derivative test to discern if a critical point is a local maximum, a local minimum or a saddle point.
Example: In> HessianMatrix(3*x^2-2*x*y+y^2-8*y, {x,y} ) Out> {{6,-2},{-2,2}}; In> PrettyForm(%) / \ | ( 6 ) ( -2 ) | | | | ( -2 ) ( 2 ) | \ /
-
HilbertMatrix
(n)¶ create a Hilbert matrix
Parameters: n,m – positive integers The function {HilbertMatrix} returns the {n} by {m} Hilbert matrix if given two arguments, and the square {n} by {n} Hilbert matrix if given only one. The Hilbert matrix is defined as {A(i,j) = 1/(i+j-1)}. The Hilbert matrix is extremely sensitive to manipulate and invert numerically.
Example: In> PrettyForm(HilbertMatrix(4)) / \ | ( 1 ) / 1 \ / 1 \ / 1 \ | | | - | | - | | - | | | \ 2 / \ 3 / \ 4 / | | | | / 1 \ / 1 \ / 1 \ / 1 \ | | | - | | - | | - | | - | | | \ 2 / \ 3 / \ 4 / \ 5 / | | | | / 1 \ / 1 \ / 1 \ / 1 \ | | | - | | - | | - | | - | | | \ 3 / \ 4 / \ 5 / \ 6 / | | | | / 1 \ / 1 \ / 1 \ / 1 \ | | | - | | - | | - | | - | | | \ 4 / \ 5 / \ 6 / \ 7 / | \ /
See also
-
HilbertInverseMatrix
(n)¶ create a Hilbert inverse matrix
Parameters: n – positive integer The function {HilbertInverseMatrix} returns the {n} by {n} inverse of the corresponding Hilbert matrix. All Hilbert inverse matrices have integer entries that grow in magnitude rapidly.
Example: In> PrettyForm(HilbertInverseMatrix(4)) / \ | ( 16 ) ( -120 ) ( 240 ) ( -140 ) | | | | ( -120 ) ( 1200 ) ( -2700 ) ( 1680 ) | | | | ( 240 ) ( -2700 ) ( 6480 ) ( -4200 ) | | | | ( -140 ) ( 1680 ) ( -4200 ) ( 2800 ) | \ /
See also
-
ToeplitzMatrix
(N)¶ create a Toeplitz matrix
Parameters: N – an $n$-dimensional row vector The function {ToeplitzMatrix} calculates the Toeplitz matrix given an $n$-dimensional row vector. This matrix has the same entries in all diagonal columns, from upper left to lower right.
Example: In> PrettyForm(ToeplitzMatrix({1,2,3,4,5})) / \ | ( 1 ) ( 2 ) ( 3 ) ( 4 ) ( 5 ) | | | | ( 2 ) ( 1 ) ( 2 ) ( 3 ) ( 4 ) | | | | ( 3 ) ( 2 ) ( 1 ) ( 2 ) ( 3 ) | | | | ( 4 ) ( 3 ) ( 2 ) ( 1 ) ( 2 ) | | | | ( 5 ) ( 4 ) ( 3 ) ( 2 ) ( 1 ) | \ /
-
WronskianMatrix
(func, var)¶ create the Wronskian matrix
Parameters: - func – an $n$-dimensional vector of functions
- var – a variable to differentiate with respect to
The function {WronskianMatrix} calculates the Wronskian matrix of $n$ functions. The Wronskian matrix is created by putting each function as the first element of each column, and filling in the rest of each column by the ($i-1$)-th derivative, where $i$ is the current row. The Wronskian matrix is used to verify that the $n$ functions are linearly independent, usually solutions to a differential equation. If the determinant of the Wronskian matrix is zero, then the functions are dependent, otherwise they are independent.
Example: In> WronskianMatrix({Sin(x),Cos(x),x^4},x); Out> {{Sin(x),Cos(x),x^4},{Cos(x),-Sin(x),4*x^3}, {-Sin(x),-Cos(x),12*x^2}}; In> PrettyForm(%) / \ | ( Sin( x ) ) ( Cos( x ) ) / 4 \ | | \ x / | | | | ( Cos( x ) ) ( -( Sin( x ) ) ) / 3 \ | | \ 4 * x / | | | | ( -( Sin( x ) ) ) ( -( Cos( x ) ) ) / 2 \ | | \ 12 * x / | \ / The last element is a linear combination of the first two, so the determinant is zero: In> A:=Determinant( WronskianMatrix( {x^4,x^3,2*x^4 + 3*x^3},x ) ) Out> x^4*3*x^2*(24*x^2+18*x)-x^4*(8*x^3+9*x^2)*6*x +(2*x^4+3*x^3)*4*x^3*6*x-4*x^6*(24*x^2+18*x)+x^3 *(8*x^3+9*x^2)*12*x^2-(2*x^4+3*x^3)*3*x^2*12*x^2; In> Simplify(A) Out> 0;
-
SylvesterMatrix
(poly1, poly2, variable)¶ calculate the Sylvester matrix of two polynomials
Parameters: - poly1 – polynomial
- poly2 – polynomial
- variable – variable to express the matrix for
The function {SylvesterMatrix} calculates the Sylvester matrix for a pair of polynomials. The Sylvester matrix is closely related to the resultant, which is defined as the determinant of the Sylvester matrix. Two polynomials share common roots only if the resultant is zero.
Example: In> ex1:= x^2+2*x-a Out> x^2+2*x-a; In> ex2:= x^2+a*x-4 Out> x^2+a*x-4; In> A:=SylvesterMatrix(ex1,ex2,x) Out> {{1,2,-a,0},{0,1,2,-a}, {1,a,-4,0},{0,1,a,-4}}; In> B:=Determinant(A) Out> 16-a^2*a- -8*a-4*a+a^2- -2*a^2-16-4*a; In> Simplify(B) Out> 3*a^2-a^3; The above example shows that the two polynomials have common zeros if $ a = 3 $.
See also
Operations on polynomials¶
This chapter contains commands to manipulate polynomials. This includes functions for constructing and evaluating orthogonal polynomials.
-
Expand
(expr)¶ -
Expand
(expr, var) -
Expand
(expr, varlist) transform a polynomial to an expanded form
Parameters: - expr – a polynomial expression
- var – a variable
- varlist – a list of variables
This command brings a polynomial in expanded form, in which polynomials are represented in the form \(c_0 + c_1x + c_2x^2 + ... + c_nx^n\). In this form, it is easier to test whether a polynomial is zero, namely by testing whether all coefficients are zero. If the polynomial {expr} contains only one variable, the first calling sequence can be used. Otherwise, the second form should be used which explicitly mentions that {expr} should be considered as a polynomial in the variable {var}. The third calling form can be used for multivariate polynomials. Firstly, the polynomial {expr} is expanded with respect to the first variable in {varlist}. Then the coefficients are all expanded with respect to the second variable, and so on.
Example: In> Expand((1+x)^5) Out> x^5+5*x^4+10*x^3+10*x^2+5*x+1 In> Expand((1+x-y)^2, x); Out> x^2+2*(1-y)*x+(1-y)^2 In> Expand((1+x-y)^2, {x,y}) Out> x^2+((-2)*y+2)*x+y^2-2*y+1
See also
-
Degree
(expr[, var])¶ degree of a polynomial
Parameters: - expr – a polynomial
- var – a variable occurring in {expr}
This command returns the degree of the polynomial
expr
with respect to the variablevar
. If only one variable occurs inexpr
, the first calling sequence can be used. Otherwise the user should use the second form in which the variable is explicitly mentioned.Example: In> Degree(x^5+x-1); Out> 5; In> Degree(a+b*x^3, a); Out> 1; In> Degree(a+b*x^3, x); Out> 3;
-
Coef
(expr, var, order)¶ coefficient of a polynomial
Parameters: - expr – a polynomial
- var – a variable occurring in {expr}
- order – integer or list of integers
This command returns the coefficient of {var} to the power {order} in the polynomial {expr}. The parameter {order} can also be a list of integers, in which case this function returns a list of coefficients.
Example: In> e := Expand((a+x)^4,x) Out> x^4+4*a*x^3+(a^2+(2*a)^2+a^2)*x^2+ (a^2*2*a+2*a^3)*x+a^4; In> Coef(e,a,2) Out> 6*x^2; In> Coef(e,a,0 .. 4) Out> {x^4,4*x^3,6*x^2,4*x,1};
See also
-
Content
(expr)¶ content of a univariate polynomial
Parameters: expr – univariate polynomial This command determines the content of a univariate polynomial.
Example: In> poly := 2*x^2 + 4*x; Out> 2*x^2+4*x; In> c := Content(poly); Out> 2*x; In> pp := PrimitivePart(poly); Out> x+2; In> Expand(pp*c); Out> 2*x^2+4*x;
See also
-
PrimitivePart
(expr)¶ primitive part of a univariate polynomial
Parameters: expr – univariate polynomial This command determines the primitive part of a univariate polynomial. The primitive part is what remains after the content is divided out. So the product of the content and the primitive part equals the original polynomial.
Example: In> poly := 2*x^2 + 4*x; Out> 2*x^2+4*x; In> c := Content(poly); Out> 2*x; In> pp := PrimitivePart(poly); Out> x+2; In> Expand(pp*c); Out> 2*x^2+4*x;
See also
-
LeadingCoef
(poly)¶ leading coefficient of a polynomial
Parameters: - poly – a polynomial
- var – a variable
This function returns the leading coefficient of {poly}, regarded as a polynomial in the variable {var}. The leading coefficient is the coefficient of the term of highest degree. If only one variable appears in the expression {poly}, it is obvious that it should be regarded as a polynomial in this variable and the first calling sequence may be used.
Example: In> poly := 2*x^2 + 4*x; Out> 2*x^2+4*x; In> lc := LeadingCoef(poly); Out> 2; In> m := Monic(poly); Out> x^2+2*x; In> Expand(lc*m); Out> 2*x^2+4*x; In> LeadingCoef(2*a^2 + 3*a*b^2 + 5, a); Out> 2; In> LeadingCoef(2*a^2 + 3*a*b^2 + 5, b); Out> 3*a;
-
Monic
(poly)¶ monic part of a polynomial
Parameters: - poly – a polynomial
- var – a variable
This function returns the monic part of {poly}, regarded as a polynomial in the variable {var}. The monic part of a polynomial is the quotient of this polynomial by its leading coefficient. So the leading coefficient of the monic part is always one. If only one variable appears in the expression {poly}, it is obvious that it should be regarded as a polynomial in this variable and the first calling sequence may be used.
Example: In> poly := 2*x^2 + 4*x; Out> 2*x^2+4*x; In> lc := LeadingCoef(poly); Out> 2; In> m := Monic(poly); Out> x^2+2*x; In> Expand(lc*m); Out> 2*x^2+4*x; In> Monic(2*a^2 + 3*a*b^2 + 5, a); Out> a^2+(a*3*b^2)/2+5/2; In> Monic(2*a^2 + 3*a*b^2 + 5, b); Out> b^2+(2*a^2+5)/(3*a);
See also
-
SquareFree
(p)¶ return the square-free part of polynomial
Parameters: p – a polynomial in {x} Given a polynomial $$ p = p[1]^n[1]* ... * p[m]^n[m] $$ with irreducible polynomials $ p[i] $, return the square-free version part (with all the factors having multiplicity 1): $$ p[1]* ... * p[m] $$
Example: In> Expand((x+1)^5) Out> x^5+5*x^4+10*x^3+10*x^2+5*x+1; In> SquareFree(%) Out> (x+1)/5; In> Monic(%) Out> x+1;
See also
FindRealRoots()
,NumRealRoots()
,MinimumBound()
,MaximumBound()
,Factor()
-
SquareFreeFactorize
(p, x)¶ return square-free decomposition of polynomial
Parameters: p – a polynomial in {x} Given a polynomial $p$ having square-free decomposition $$ p = p[1]^n[1] * ... * p[m]^n[m] $$ where $p[i]$ are square-free and $n[i+1]>n[i]$, return the list of pairs ($p[i]$, $n[i]$)
Example: In> Expand((x+1)^5) Out> x^5+5*x^4+10*x^3+10*x^2+5*x+1 In> SquareFreeFactorize(%,x) Out> {{x+1,5}}
See also
-
Horner
(expr, var)¶ convert a polynomial into the Horner form
Parameters: - expr – a polynomial in {var}
- var – a variable
This command turns the polynomial {expr}, considered as a univariate polynomial in {var}, into Horner form. A polynomial in normal form is an expression such as $$c[0] + c[1]*x + ... + c[n]*x^n$$. If one converts this polynomial into Horner form, one gets the equivalent expression $$(...( c[n] * x + c[n-1] ) * x + ... + c[1] ) * x + c[0]$$. Both expression are equal, but the latter form gives a more efficient way to evaluate the polynomial as the powers have disappeared.
Example: In> expr1:=Expand((1+x)^4) Out> x^4+4*x^3+6*x^2+4*x+1; In> Horner(expr1,x) Out> (((x+4)*x+6)*x+4)*x+1;
See also
-
ExpandBrackets
(expr)¶ expand all brackets
Parameters: expr – an expression This command tries to expand all the brackets by repeatedly using the distributive laws $a * (b+c) = a*b + a*c$ and $(a+b) * c = a*c + b*c$. It goes further than {Expand}, in that it expands all brackets.
Example: In> Expand((a-x)*(b-x),x) Out> x^2-(b+a)*x+a*b; In> Expand((a-x)*(b-x),{x,a,b}) Out> x^2-(b+a)*x+b*a; In> ExpandBrackets((a-x)*(b-x)) Out> a*b-x*b+x^2-a*x;
See also
-
EvaluateHornerScheme
(coeffs, x)¶ fast evaluation of polynomials
Parameters: - coeffs – a list of coefficients
- x – expression
This function evaluates a polynomial given as a list of its coefficients, using the Horner scheme. The list of coefficients starts with the $0$-th power.
-
OrthoP(n, x);
Legendre and Jacobi orthogonal polynomials
Parameters: - n – degree of polynomial
- x – point to evaluate polynomial at
- {b (a},) – parameters for Jacobi polynomial
The first calling format with two arguments evaluates the Legendre polynomial of degree {n} at the point {x}. The second form does the same for the Jacobi polynomial with parameters {a} and {b}, which should be both greater than -1. The Jacobi polynomials are orthogonal with respect to the weight function $(1-x)^a (1+x)^b$ on the interval [-1,1]. They satisfy the recurrence relation $$P(n,a,b,x) = (2*n+a+b-1)/(2*n+a+b-2) $$ $$ ((a^2-b^2+x*(2*n+a+b-2)*(n+a+b))/(2*n*(n+a+b))) * P(n-1,a,b,x)$$ $$ - ((n+a-1)*(n+b-1)*(2*n+a+b))/(n*(n+a+b)*(2*n+a+b-2))*P(n-2,a,b,x)$$ for $n > 1$, with $P(0,a,b,x) = 1$, $$P(1,a,b,x) = (a-b)/2+x*(1+(a+b)/2)$$.
-
OrthoH(n, x);
Hermite orthogonal polynomials
Parameters: - n – degree of polynomial
- x – point to evaluate polynomial at
This function evaluates the Hermite polynomial of degree {n} at the point {x}. The Hermite polynomials are orthogonal with respect to the weight function $Exp(-x^2/2)$ on the entire real axis. They satisfy the recurrence relation $$ H(n,x) = 2*x*H(n-1,x) - 2*(n-1)*H(n-2,x) $$ for $n > 1$, with $H(0,x) = 1$, $H(1,x) = 2*x$. Most of the work is performed by the internal function {OrthoPoly}.
Example: In> OrthoH(3, x); Out> x*(8*x^2-12); In> OrthoH(6, 0.5); Out> 31;
See also
OrthoHSum()
,OrthoPoly()
-
OrthoG(n, a, x);
Gegenbauer orthogonal polynomials
Parameters: - n – degree of polynomial
- a – parameter
- x – point to evaluate polynomial at
This function evaluates the Gegenbauer (or ultraspherical) polynomial with parameter {a} and degree {n} at the point {x}. The parameter {a} should be greater than -1/2. The Gegenbauer polynomials are orthogonal with respect to the weight function $(1-x^2)^(a-1/2)$ on the interval [-1,1]. Hence they are connected to the Jacobi polynomials via $$ G(n, a, x) = P(n, a-1/2, a-1/2, x) $$. They satisfy the recurrence relation $$ G(n,a,x) = 2*(1+(a-1)/n)*x*G(n-1,a,x) $$ $$ -(1+2*(a-2)/n)*G(n-2,a,x) $$ for $n>1$, with $G(0,a,x) = 1$, $G(1,a,x) = 2*x$.
-
OrthoL(n, a, x);
Laguerre orthogonal polynomials
Parameters: - n – degree of polynomial
- a – parameter
- x – point to evaluate polynomial at
This function evaluates the Laguerre polynomial with parameter {a} and degree {n} at the point {x}. The parameter {a} should be greater than -1. The Laguerre polynomials are orthogonal with respect to the weight function $x^a * Exp(-x)$ on the positive real axis. They satisfy the recurrence relation $$ L(n,a,x) = (2+(a-1-x)/n)* L(n-1,a,x) $$ $$ -(1-(a-1)/n)*L(n-2,a,x) $$ for $n>1$, with $L(0,a,x) = 1$, $L(1,a,x) = a + 1 - x$.
-
OrthoT(n, x);
Chebyshev polynomials
Parameters: - n – degree of polynomial
- x – point to evaluate polynomial at
These functions evaluate the Chebyshev polynomials of the first kind $T(n,x)$ and of the second kind $U(n,x)$, of degree {n} at the point {x}. (The name of this Russian mathematician is also sometimes spelled {Tschebyscheff}.) The Chebyshev polynomials are orthogonal with respect to the weight function $(1-x^2)^(-1/2)$. Hence they are a special case of the Gegenbauer polynomials $G(n,a,x)$, with $a=0$. They satisfy the recurrence relations $$ T(n,x) = 2* x* T(n-1,x) - T(n-2,x) $$, $$ U(n,x) = 2* x* U(n-1,x) - U(n-2,x) $$ for $n > 1$, with $T(0,x) = 1$, $T(1,x) = x$, $U(0,x) = 1$, $U(1,x) = 2*x$.
Example: In> OrthoT(3, x); Out> 2*x*(2*x^2-1)-x; In> OrthoT(10, 0.9); Out> -0.2007474688; In> OrthoU(3, x); Out> 4*x*(2*x^2-1); In> OrthoU(10, 0.9); Out> -2.2234571776;
See also
OrthoG()
,OrthoTSum()
,OrthoUSum()
,OrthoPoly()
-
OrthoPSum(c, x);
sums of series of orthogonal polynomials
Parameters: - c – list of coefficients
- {b (a},) – parameters of specific polynomials
- x – point to evaluate polynomial at
These functions evaluate the sum of series of orthogonal polynomials at the point {x}, with given list of coefficients {c} of the series and fixed polynomial parameters {a}, {b} (if applicable). The list of coefficients starts with the lowest order, so that for example OrthoLSum(c, a, x) = c[1] L[0](a,x) + c[2] L[1](a,x) + ... + c[N] L[N-1](a,x). See pages for specific orthogonal polynomials for more details on the parameters of the polynomials. Most of the work is performed by the internal function {OrthoPolySum}. The individual polynomials entering the series are not computed, only the sum of the series.
Example: In> Expand(OrthoPSum({1,0,0,1/7,1/8}, 3/2, \ 2/3, x)); Out> (7068985*x^4)/3981312+(1648577*x^3)/995328+ (-3502049*x^2)/4644864+(-4372969*x)/6967296 +28292143/27869184;
See also
OrthoP()
,OrthoG()
,OrthoH()
,OrthoL()
,OrthoT()
,OrthoU()
,OrthoPolySum()
-
OrthoPoly
(name, n, par, x)¶ internal function for constructing orthogonal polynomials
Parameters: - name – string containing name of orthogonal family
- n – degree of the polynomial
- par – list of values for the parameters
- x – point to evaluate at
This function is used internally to construct orthogonal polynomials. It returns the {n}-th polynomial from the family {name} with parameters {par} at the point {x}. All known families are stored in the association list returned by the function {KnownOrthoPoly()}. The name serves as key. At the moment the following names are known to Yacas: {“Jacobi”}, {“Gegenbauer”}, {“Laguerre”}, {“Hermite”}, {“Tscheb1”}, and {“Tscheb2”}. The value associated to the key is a pure function that takes two arguments: the order {n} and the extra parameters {p}, and returns a list of two lists: the first list contains the coefficients {A,B} of the n=1 polynomial, i.e. $A+B*x$; the second list contains the coefficients {A,B,C} in the recurrence relation, i.e. $P[n] = (A+B*x)*P[n-1]+C*P[n-2]$. (There are only 3 coefficients in the second list, because none of the polynomials use $C+D*x$ instead of $C$ in the recurrence relation. This is assumed in the implementation!) If the argument {x} is numerical, the function {OrthoPolyNumeric} is called. Otherwise, the function {OrthoPolyCoeffs} computes a list of coefficients, and {EvaluateHornerScheme} converts this list into a polynomial expression.
See also
OrthoP()
,OrthoG()
,OrthoH()
,OrthoL()
,OrthoT()
,OrthoU()
,OrthoPolySum()
-
OrthoPolySum
(name, c, par, x)¶ internal function for computing series of orthogonal polynomials
Parameters: - name – string containing name of orthogonal family
- c – list of coefficients
- par – list of values for the parameters
- x – point to evaluate at
This function is used internally to compute series of orthogonal polynomials. It is similar to the function {OrthoPoly} and returns the result of the summation of series of polynomials from the family {name} with parameters {par} at the point {x}, where {c} is the list of coefficients of the series. The algorithm used to compute the series without first computing the individual polynomials is the Clenshaw-Smith recurrence scheme. (See the algorithms book for explanations.) If the argument {x} is numerical, the function {OrthoPolySumNumeric} is called. Otherwise, the function {OrthoPolySumCoeffs} computes the list of coefficients of the resulting polynomial, and {EvaluateHornerScheme} converts this list into a polynomial expression.
See also
OrthoPSum()
,OrthoGSum()
,OrthoHSum()
,OrthoLSum()
,OrthoTSum()
,OrthoUSum()
,OrthoPoly()
List operations¶
Most objects that can be of variable size are represented as lists (linked lists internally). Yacas does implement arrays, which are faster when the number of elements in a collection of objects doesn’t change. Operations on lists have better support in the current system.
-
Head
(list)¶ Returns the first element of a list
Parameters: list – a list This function returns the first element of a list. If it is applied to a general expression, it returns the first operand. An error is returned if
list
is an atom.Example: In> Head({a,b,c}) Out> a; In> Head(f(a,b,c)); Out> a;
-
Tail
(list)¶ Returns a list without its first element
Parameters: list – a list This function returns
list
without its first element.Example: In> Tail({a,b,c}) Out> {b,c};
-
Length
(object)¶ The length of a list or string
Parameters: object – a list or string Length returns the length of a list or string.
Example: In> Length({a,b,c}) Out> 3; In> Length("abcdef"); Out> 6;
-
Map
(fn, list)¶ apply an n-ary function to all entries in a list
Parameters: - fn – to apply
- list – list of lists of arguments
This function applies
fn
to every list of arguments to be found inlist
. So the first entry oflist
should be a list containing the first, second, third, ... argument tofn
, and the same goes for the other entries oflist
. The function can either be given as a string or as a pure function (seeApply()
for more information on pure functions).Example: In> MapSingle("Sin",{a,b,c}); Out> {Sin(a),Sin(b),Sin(c)}; In> Map("+",{{a,b},{c,d}}); Out> {a+c,b+d};
See also
-
MapSingle
(fn, list)¶ apply a unary function to all entries in a list
Parameters: - fn – function to apply
- list – list of arguments
The function
fn
is successively applied to all entries inlist
, and a list containing the respective results is returned. The function can be given either as a string or as a pure function (seeApply()
for more information on pure functions).The
/@
operator provides a shorthand forMapSingle()
.Example: In> MapSingle("Sin",{a,b,c}); Out> {Sin(a),Sin(b),Sin(c)}; In> MapSingle({{x},x^2}, {a,2,c}); Out> {a^2,4,c^2};
-
MakeVector
(var, n)¶ vector of uniquely numbered variable names
Parameters: - var – free variable
- n – length of the vector
A list of length
n
is generated. The first entry contains the identifiervar
with the number 1 appended to it, the second entry containsvar
with the suffix 2, and so on until the last entry which containsvar
with the numbern
appended to it.Example: In> MakeVector(a,3) Out> {a1,a2,a3};
See also
-
Select
(pred, list)¶ select entries satisfying some predicate
Parameters: - pred – a predicate
- list – a list of elements to select from
Select
returns a sublist oflist
which contains all the entries for which the predicatepred
returnsTrue
when applied to this entry.Example: In> Select("IsInteger",{a,b,2,c,3,d,4,e,f}) Out> {2,3,4};
-
Nth
(list, n)¶ return the
n
-th element of a listParameters: - list – list to choose from
- n – index of the entry to pick
The entry with index
n
fromlist
is returned. The first entry has index 1. It is possible to pick several entries of the list by takingn
to be a list of indices.More generally,
Nth
returns then
-th operand of the expression passed as first argument.An alternative but equivalent form of
Nth(list, n)
islist[n]
.Example: In> lst := {a,b,c,13,19}; Out> {a,b,c,13,19}; In> Nth(lst, 3); Out> c; In> lst[3]; Out> c; In> Nth(lst, {3,4,1}); Out> {c,13,a}; In> Nth(b*(a+c), 2); Out> a+c;
-
Reverse
(list)¶ return the reversed list (without touching the original)
Parameters: list – list to reverse This function returns a list reversed, without changing the original list. It is similar to
DestructiveReverse()
, but safer and slower.Example: In> lst:={a,b,c,13,19} Out> {a,b,c,13,19}; In> revlst:=Reverse(lst) Out> {19,13,c,b,a}; In> lst Out> {a,b,c,13,19};
See also
-
List
(expr1, expr2, ...)¶ construct a list
Parameters: - expr1 –
- expr2 –
- ... – expressions making up the list
A list is constructed whose first entry is
expr1
, the second entry isexpr2
, and so on. This command is equivalent to the expression{expr1, expr2, ...}
.Example: In> List(); Out> {}; In> List(a,b); Out> {a,b}; In> List(a,{1,2},d); Out> {a,{1,2},d};
-
UnList
(list)¶ convert a list to a function application
Parameters: list – list to be converted This command converts a list to a function application. The first entry of
list
is treated as a function atom, and the following entries are the arguments to this function. So the function referred to in the first element oflist
is applied to the other elements.Note that
list
is evaluated before the function application is formed, but the resulting expression is left unevaluated. The functions {UnList()} and {Hold()} both stop the process of evaluation.Example: In> UnList({Cos, x}); Out> Cos(x); In> UnList({f}); Out> f(); In> UnList({Taylor,x,0,5,Cos(x)}); Out> Taylor(x,0,5)Cos(x); In> Eval(%); Out> 1-x^2/2+x^4/24;
-
Listify
(expr)¶ convert a function application to a list
Parameters: expr – expression to be converted The parameter
expr
is expected to be a compound object, i.e. not an atom. It is evaluated and then converted to a list. The first entry in the list is the top-level operator in the evaluated expression and the other entries are the arguments to this operator. Finally, the list is returned.Example: In> Listify(Cos(x)); Out> {Cos,x}; In> Listify(3*a); Out> {*,3,a};
-
Concat
(list1, list2, ...)¶ concatenate lists
Parameters: - list1 –
- list2 –
- ... – lists to concatenate
The lists
list1
,list2
, ... are evaluated and concatenated. The resulting big list is returned.Example: In> Concat({a,b}, {c,d}); Out> {a,b,c,d}; In> Concat({5}, {a,b,c}, {{f(x)}}); Out> {5,a,b,c,{f(x)}};
See also
-
Delete
(list, n)¶ delete an element from a list
Parameters: - list – list from which an element should be removed
- n – index of the element to remove
This command deletes the n-th element from “list”. The first parameter should be a list, while “n” should be a positive integer less than or equal to the length of “list”. The entry with index “n” is removed (the first entry has index 1), and the resulting list is returned.
Example: In> Delete({a,b,c,d,e,f}, 4); Out> {a,b,c,e,f};
See also
-
Insert
(list, n, expr)¶ insert an element into a list
Parameters: - list – list in which
expr
should be inserted - n – index at which to insert
- expr – expression to insert in
list
The expression “expr” is inserted just before the n-th entry in “list”. The first parameter “list” should be a list, while “n” should be a positive integer less than or equal to the length of “list” plus one. The expression “expr” is placed between the entries in “list” with entries “n-1” and “n”. There are two border line cases: if “n” is 1, the expression “expr” is placed in front of the list (just as by the {:} operator); if “n” equals the length of “list” plus one, the expression “expr” is placed at the end of the list (just as by {Append}). In any case, the resulting list is returned.
Example: In> Insert({a,b,c,d}, 4, x); Out> {a,b,c,x,d}; In> Insert({a,b,c,d}, 5, x); Out> {a,b,c,d,x}; In> Insert({a,b,c,d}, 1, x); Out> {x,a,b,c,d};
See also
- list – list in which
-
Replace
(list, n, expr)¶ replace an entry in a list
Parameters: - list – list of which an entry should be replaced
- n – index of entry to replace
- expr – expression to replace the
n
-th entry with
The
n
-th entry oflist
is replaced by the expressionexpr
. This is equivalent to callingDelete()
andInsert()
in sequence. To be precise, the expressionReplace(list, n, expr)
has the same result as the expressionInsert(Delete(list, n), n, expr)
.Example: In> Replace({a,b,c,d,e,f}, 4, x); Out> {a,b,c,x,e,f};
See also
-
FlatCopy
(list)¶ copy the top level of a list
Parameters: list – list to be copied A copy of
list
is made and returned. The list is not recursed into, only the first level is copied. This is useful in combination with the destructive commands that actually modify lists in place (for efficiency).The following shows a possible way to define a command that reverses a list nondestructively.
Example: In> reverse(l_IsList) <-- DestructiveReverse \ (FlatCopy(l)); Out> True; In> lst := {a,b,c,d,e}; Out> {a,b,c,d,e}; In> reverse(lst); Out> {e,d,c,b,a}; In> lst; Out> {a,b,c,d,e};
-
Contains
(list, expr)¶ test whether a list contains a certain element
Parameters: - list – list to examine
- expr – expression to look for in
list
This command tests whether
list
contains the expressionexpr
as an entry. It returnsTrue
if it does andFalse
otherwise. Only the top level oflist
is examined. The parameterlist
may also be a general expression, in that case the top-level operands are tested for the occurrence ofexpr
.Example: In> Contains({a,b,c,d}, b); Out> True; In> Contains({a,b,c,d}, x); Out> False; In> Contains({a,{1,2,3},z}, 1); Out> False; In> Contains(a*b, b); Out> True;
-
Find
(list, expr)¶ get the index at which a certain element occurs
Parameters: - list – the list to examine
- expr – expression to look for in
list
This commands returns the index at which the expression
expr
occurs inlist
. Ifexpr
occurs more than once, the lowest index is returned. Ifexpr
does not occur at all, {-1} is returned.Example: In> Find({a,b,c,d,e,f}, d); Out> 4; In> Find({1,2,3,2,1}, 2); Out> 2; In> Find({1,2,3,2,1}, 4); Out> -1;
See also
-
Append
(list, expr)¶ append an entry at the end of a list
Parameters: - list – list to append
expr
to - expr – expression to append to the list
The expression
expr
is appended at the end oflist
and the resulting list is returned.Note that due to the underlying data structure, the time it takes to append an entry at the end of a list grows linearly with the length of the list, while the time for prepending an entry at the beginning is constant.
Example: In> Append({a,b,c,d}, 1); Out> {a,b,c,d,1};
See also
- list – list to append
-
RemoveDuplicates
(list)¶ remove any duplicates from a list
Parameters: list – list to act on This command removes all duplicate elements from a given list and returns the resulting list. To be precise, the second occurrence of any entry is deleted, as are the third, the fourth, etc.
Example: In> RemoveDuplicates({1,2,3,2,1}); Out> {1,2,3}; In> RemoveDuplicates({a,1,b,1,c,1}); Out> {a,1,b,c};
-
Swap
(list, i1, i2)¶ swap two elements in a list
Parameters: - list – the list in which a pair of entries should be swapped
- i2 (i1,) – indices of the entries in
list
to swap
This command swaps the pair of entries with entries
i1
andi2
inlist
. So the element at indexi1
ends up at indexi2
and the entry ati2
is put at indexi1
. Both indices should be valid to address elements in the list. Then the updated list is returned. {Swap()} works also on generic arrays.Example: In> lst := {a,b,c,d,e,f}; Out> {a,b,c,d,e,f}; In> Swap(lst, 2, 4); Out> {a,d,c,b,e,f};
See also
-
Count
(list, expr)¶ count the number of occurrences of an expression
Parameters: - list – the list to examine
- expr – expression to look for in
list
This command counts the number of times that the expression
expr
occurs inlist
and returns this number.Example: In> lst := {a,b,c,b,a}; Out> {a,b,c,b,a}; In> Count(lst, a); Out> 2; In> Count(lst, c); Out> 1; In> Count(lst, x); Out> 0;
See also
-
FillList
(expr, n)¶ fill a list with a certain expression
Parameters: - expr – expression to fill the list with
- n – the length of the list to construct
This command creates a list of length
n
in which all slots contain the expressionexpr
and returns this list.Example: In> FillList(x, 5); Out> {x,x,x,x,x};
See also
-
Drop
(list, n)¶ -
Drop
(list, -n) -
Drop
(list, {m, n}) drop a range of elements from a list
Parameters: - list – list to act on
- m (n,) – indices
This command removes a sublist of
list
and returns a list containing the remaining entries. The first calling sequence drops the firstn
entries inlist
. The second form drops the lastn
entries. The last invocation drops the elements with indicesm
throughn
.Example: In> lst := {a,b,c,d,e,f,g}; Out> {a,b,c,d,e,f,g}; In> Drop(lst, 2); Out> {c,d,e,f,g}; In> Drop(lst, -3); Out> {a,b,c,d}; In> Drop(lst, {2,4}); Out> {a,e,f,g};
-
Take
(list, n)¶ -
Take
(list, -n) -
Take
(list, {m, n}) take a sublist from a list, dropping the rest
Parameters: - list – list to act on
- m (n,) – indices
This command takes a sublist of
list
, drops the rest, and returns the selected sublist. The first calling sequence selects the firstn
entries inlist
. The second form takes the lastn
entries. The last invocation selects the sublist beginning with entry numberm
and ending with then
-th entry.Example: In> lst := {a,b,c,d,e,f,g}; Out> {a,b,c,d,e,f,g}; In> Take(lst, 2); Out> {a,b}; In> Take(lst, -3); Out> {e,f,g}; In> Take(lst, {2,4}); Out> {b,c,d};
-
Partition
(list, n)¶ partition a list in sublists of equal length
Parameters: - list – list to partition
- n – length of partitions
This command partitions
list
into non-overlapping sublists of lengthn
and returns a list of these sublists. The firstn
entries inlist
form the first partition, the entries from positionn+1
up to2n
form the second partition, and so on. Ifn
does not divide the length oflist
, the remaining entries will be thrown away. Ifn
equals zero, an empty list is returned.Example: In> Partition({a,b,c,d,e,f,}, 2); Out> {{a,b},{c,d},{e,f}}; In> Partition(1 .. 11, 3); Out> {{1,2,3},{4,5,6},{7,8,9}};
See also
-
Flatten
(expression, operator)¶ flatten expression w.r.t. some operator
Parameters: - expression – an expression
- operator – string with the contents of an infix operator.
Flatten flattens an expression with respect to a specific operator, converting the result into a list. This is useful for unnesting an expression. Flatten is typically used in simple simplification schemes.
Example: In> Flatten(a+b*c+d, "+"); Out> {a,b*c,d}; In> Flatten({a,{b,c},d}, "List"); Out> {a,b,c,d};
See also
-
UnFlatten
(list, operator, identity)¶ inverse operation of Flatten
Parameters: - list – list of objects the operator is to work on
- operator – infix operator
- identity – identity of the operator
UnFlatten is the inverse operation of Flatten. Given a list, it can be turned into an expression representing for instance the addition of these elements by calling UnFlatten with
+
as argument to operator, and 0 as argument to identity (0 is the identity for addition, since a+0=a). For multiplication the identity element would be 1.Example: In> UnFlatten({a,b,c},"+",0) Out> a+b+c; In> UnFlatten({a,b,c},"*",1) Out> a*b*c;
See also
-
Type
(expr)¶ return the type of an expression
Parameters: expr – expression to examine The type of the expression
expr
is represented as a string and returned. So, ifexpr
is a list, the string"List"
is returned. In general, the top-level operator ofexpr
is returned. If the argumentexpr
is an atom, the result is the empty string""
.Example: In> Type({a,b,c}); Out> "List"; In> Type(a*(b+c)); Out> "*"; In> Type(123); Out> "";
-
NrArgs
(expr)¶ return number of top-level arguments
Parameters: expr – expression to examine This function evaluates to the number of top-level arguments of the expression
expr
. The argumentexpr
may not be an atom, since that would lead to an error.Example: In> NrArgs(f(a,b,c)) Out> 3; In> NrArgs(Sin(x)); Out> 1; In> NrArgs(a*(b+c)); Out> 2;
-
VarList
(expr)¶ -
VarListArith
(expr)¶ -
VarListSome
(expr, list)¶ list of variables appearing in an expression
Parameters: - expr – an expression
- list – a list of function atoms
The command {VarList(expr)} returns a list of all variables that appear in the expression {expr}. The expression is traversed recursively.
The command {VarListSome} looks only at arguments of functions in the {list}. All other functions are considered
opaque
(as if they do not contain any variables) and their arguments are not checked. For example, {VarListSome(a + Sin(b-c))} will return {{a, b, c}}, but {VarListSome(a*Sin(b-c), {*})
} will not look at arguments of {Sin()} and will return {{a,Sin(b-c)}}. Here {Sin(b-c)} is considered avariable
because the function {Sin} does not belong to {list}.The command {VarListArith} returns a list of all variables that appear arithmetically in the expression {expr}. This is implemented through {VarListSome} by restricting to the arithmetic functions {+}, {-}, {*}, {/}. Arguments of other functions are not checked.
Note that since the operators
{+}
and{-}
are prefix as well as infix operators, it is currently required to use {Atom(+
)} to obtain the unevaluated atom{+}
.Example: In> VarList(Sin(x)) Out> {x}; In> VarList(x+a*y) Out> {x,a,y}; In> VarListSome(x+a*y, {Atom(``+``)}) Out> {x,a*y}; In> VarListArith(x+y*Cos(Ln(x)/x)) Out> {x,y,Cos(Ln(x)/x)} In> VarListArith(x+a*y^2-1) Out> {x,a,y^2};
See also
IsFreeOf()
,IsVariable()
,FuncList()
,HasExpr()
,HasFunc()
-
FuncList
(expr)¶ list of functions used in an expression
Parameters: expr – an expression The command {FuncList(expr)} returns a list of all function atoms that appear in the expression {expr}. The expression is recursively traversed.
Example: In> FuncList(x+y*Cos(Ln(x)/x)) Out> {+,*,Cos,/,Ln};
-
FuncListArith
(expr)¶ list of functions used in an expression
Parameters: expr – an expression FuncListArith
is defined throughFuncListSome()
to look only at arithmetic operations {+}, {-}, {*}, {/}.Example: In> FuncListArith(x+y*Cos(Ln(x)/x)) Out> {+,*,Cos};
-
FuncListSome
(expr, list)¶ list of functions used in an expression
Parameters: - expr – an expression
- list – list of function atoms to be considered transparent
The command {FuncListSome(expr, list)} does the same, except it only looks at arguments of a given {list} of functions. All other functions become
opaque
(as if they do not contain any other functions). For example, {FuncListSome(a + Sin(b-c))} will see that the expression has a{-}
operation and return {{+,Sin,-}}, but {FuncListSome(a + Sin(b-c), {+})} will not look at arguments of {Sin()} and will return {{+,Sin}}.Note that since the operators
{+}
and{-}
are prefix as well as infix operators, it is currently required to use {Atom(+
)} to obtain the unevaluated atom{+}
.Example: In> FuncListSome({a+b*2,c/d},{List}) Out> {List,+,/};
-
PrintList
(list[, padding])¶ print list with padding
Parameters: - list – a list to be printed
- padding – (optional) a string
Prints
list
and inserts thepadding
string between each pair of items of the list. Items of the list which are strings are printed without quotes, unlikeWrite()
. Items of the list which are themselves lists are printed inside braces{}
. If padding is not specified, standard one is used (comma, space).Example: In> PrintList({a,b,{c, d}}, `` .. ``) Out> `` a .. b .. { c .. d}``;
See also
-
Table
(body, var, from, to, step)¶ evaluate while some variable ranges over interval
Parameters: - body – expression to evaluate multiple times
- var – variable to use as loop variable
- from – initial value for
var
- to – final value for
var
- step – step size with which
var
is incremented
This command generates a list of values from
body
, by assigning variablevar
values fromfrom
up toto
, incrementingstep
each time. So, the variablevar
first gets the valuefrom
, and the expressionbody
is evaluated. Then the valuefrom``+``step
is assigned tovar
and the expressionbody
is again evaluated. This continues, incrementingvar
withstep
on every iteration, untilvar
exceedsto
. At that moment, all the results are assembled in a list and this list is returned.Example: In> Table(i!, i, 1, 9, 1); Out> {1,2,6,24,120,720,5040,40320,362880}; In> Table(i, i, 3, 16, 4); Out> {3,7,11,15}; In> Table(i^2, i, 10, 1, -1); Out> {100,81,64,49,36,25,16,9,4,1};
See also
For()
,MapSingle()
, ..:,TableForm()
-
TableForm
(list)¶ print each entry in a list on a line
Parameters: list – list to print This functions writes out the list {list} in a better readable form, by printing every element in the list on a separate line.
Example: In> TableForm(Table(i!, i, 1, 10, 1)); 1 2 6 24 120 720 5040 40320 362880 3628800 Out> True;
See also
Destructive operations¶
-
DestructiveAppend
(list, expr)¶ destructively append an entry to a list
Parameters: - list – list to append
expr
to - expr – expression to append to the list
This is the destructive counterpart of {Append}. This command yields the same result as the corresponding call to {Append}, but the original list is modified. So if a variable is bound to
list
, it will now be bound to the list with the expressionexpr
inserted.Destructive commands run faster than their nondestructive counterparts because the latter copy the list before they alter it.
Example: In> lst := {a,b,c,d}; Out> {a,b,c,d}; In> Append(lst, 1); Out> {a,b,c,d,1}; In> lst Out> {a,b,c,d}; In> DestructiveAppend(lst, 1); Out> {a,b,c,d,1}; In> lst; Out> {a,b,c,d,1};
- list – list to append
-
DestructiveDelete
(list, n)¶ delete an element destructively from a list
Parameters: - list – list from which an element should be removed
- n – index of the element to remove
This is the destructive counterpart of {Delete}. This command yields the same result as the corresponding call to {Delete}, but the original list is modified. So if a variable is bound to “list”, it will now be bound to the list with the n-th entry removed.
Destructive commands run faster than their nondestructive counterparts because the latter copy the list before they alter it.
Example: In> lst := {a,b,c,d,e,f}; Out> {a,b,c,d,e,f}; In> Delete(lst, 4); Out> {a,b,c,e,f}; In> lst; Out> {a,b,c,d,e,f}; In> DestructiveDelete(lst, 4); Out> {a,b,c,e,f}; In> lst; Out> {a,b,c,e,f};
See also
-
DestructiveInsert
(list, n, expr)¶ insert an element destructively into a list
Parameters: - list – list in which
expr
should be inserted - n – index at which to insert
- expr – expression to insert in
list
This is the destructive counterpart of
Insert()
. This command yields the same result as the corresponding call toInsert()
, but the original list is modified. So if a variable is bound tolist
, it will now be bound to the list with the expressionexpr
inserted.Destructive commands run faster than their nondestructive counterparts because the latter copy the list before they alter it.
Example: In> lst := {a,b,c,d}; Out> {a,b,c,d}; In> Insert(lst, 2, x); Out> {a,x,b,c,d}; In> lst; Out> {a,b,c,d}; In> DestructiveInsert(lst, 2, x); Out> {a,x,b,c,d}; In> lst; Out> {a,x,b,c,d};
See also
- list – list in which
-
DestructiveReplace
(list, n, expr)¶ replace an entry destructively in a list
Parameters: - list – list of which an entry should be replaced
- n – index of entry to replace
- expr – expression to replace the
n
-th entry with
This is the destructive counterpart of
Replace()
. This command yields the same result as the corresponding call toReplace()
, but the original list is modified. So if a variable is bound tolist
, it will now be bound to the list with the expressionexpr
inserted.Destructive commands run faster than their nondestructive counterparts because the latter copy the list before they alter it.
Example: In> lst := {a,b,c,d,e,f}; Out> {a,b,c,d,e,f}; In> Replace(lst, 4, x); Out> {a,b,c,x,e,f}; In> lst; Out> {a,b,c,d,e,f}; In> DestructiveReplace(lst, 4, x); Out> {a,b,c,x,e,f}; In> lst; Out> {a,b,c,x,e,f};
See also
-
DestructiveReverse
(list)¶ reverse a list destructively
Parameters: list – list to reverse This command reverses
list
in place, so that the original is destroyed. This means that any variable bound tolist
will now have an undefined content, and should not be used any more. The reversed list is returned.Destructive commands are faster than their nondestructive counterparts. Reverse is the non-destructive version of this function.
Example: In> lst := {a,b,c,13,19}; Out> {a,b,c,13,19}; In> revlst := DestructiveReverse(lst); Out> {19,13,c,b,a}; In> lst; Out> {a};
See also
Set operations¶
-
Intersection
(l1, l2)¶ return the intersection of two lists
Parameters: l2 (l1,) – two lists The intersection of the lists
l1
andl2
is determined and returned. The intersection contains all elements that occur in both lists. The entries in the result are listed in the same order as inl1
. If an expression occurs multiple times in bothl1
andl2
, then it will occur the same number of times in the result.Example: In> Intersection({a,b,c}, {b,c,d}); Out> {b,c}; In> Intersection({a,e,i,o,u}, {f,o,u,r,t,e,e,n}); Out> {e,o,u}; In> Intersection({1,2,2,3,3,3}, {1,1,2,2,3,3}); Out> {1,2,2,3,3};
See also
-
Union
(l1, l2)¶ return the union of two lists
Parameters: l2 (l1,) – two lists The union of the lists
l1
andl2
is determined and returned. The union contains all elements that occur in one or both of the lists. In the resulting list, any element will occur only once.Example: In> Union({a,b,c}, {b,c,d}); Out> {a,b,c,d}; In> Union({a,e,i,o,u}, {f,o,u,r,t,e,e,n}); Out> {a,e,i,o,u,f,r,t,n}; In> Union({1,2,2,3,3,3}, {2,2,3,3,4,4}); Out> {1,2,3,4};
See also
-
Difference
(l1, l2)¶ return the difference of two lists
Parameters: {l2 (l1},) – two lists The difference of the lists
l1
andl2
is determined and returned. The difference contains all elements that occur inl1
but not inl2
. The order of elements inl1
is preserved. If a certain expression occursn1
times in the first list andn2
times in the second list, it will occurn1-n2
times in the result ifn1
is greater thann2
and not at all otherwise.Example: In> Difference({a,b,c}, {b,c,d}); Out> {a}; In> Difference({a,e,i,o,u}, {f,o,u,r,t,e,e,n}); Out> {a,i}; In> Difference({1,2,2,3,3,3}, {2,2,3,4,4}); Out> {1,3,3};
See also
Associative map¶
-
Assoc
(key, alist)¶ return element stored in association list
Parameters: - key – string, key under which element is stored
- alist – association list to examine
The association list
alist
is searched for an entry stored with indexkey
. If such an entry is found, it is returned. Otherwise the atom {Empty} is returned.Association lists are represented as a list of two-entry lists. The first element in the two-entry list is the key, the second element is the value stored under this key.
The call {Assoc(key, alist)} can (probably more intuitively) be accessed as {alist[key]}.
Example: In> writer := {}; Out> {}; In> writer[``Iliad``] := ``Homer``; Out> True; In> writer[``Henry IV``] := ``Shakespeare``; Out> True; In> writer[``Ulysses``] := ``James Joyce``; Out> True; In> Assoc(``Henry IV``, writer); Out> {``Henry IV``,``Shakespeare``}; In> Assoc(``War and Peace``, writer); Out> Empty;
See also
AssocIndices()
,[]()
,:=()
,AssocDelete()
-
AssocIndices
(alist)¶ return the keys in an association list
Parameters: alist – association list to examine All the keys in the association list
alist
are assembled in a list and this list is returned.Example: In> writer := {}; Out> {}; In> writer[``Iliad``] := ``Homer``; Out> True; In> writer[``Henry IV``] := ``Shakespeare``; Out> True; In> writer[``Ulysses``] := ``James Joyce``; Out> True; In> AssocIndices(writer); Out> {``Iliad``,``Henry IV``,``Ulysses``};
See also
-
AssocDelete
()¶ - delete an entry in an association list
- AssocDelete(alist,
key
) AssocDelete(alist, {key, value})
Parameters: - alist – association list
- key – string, association key
- value – value of the key to be deleted
The key {
key
} in the association list {alist} is deleted. (The list itself is modified.) If the key was found and successfully deleted, returnsTrue
, otherwise if the given key was not found, the function returnsFalse
.The second, longer form of the function deletes the entry that has both the specified key and the specified value. It can be used for two purposes:
- to make sure that we are deleting the right value;
- if several values are stored on the same key, to delete the specified entry (see the last example).
At most one entry is deleted.
Example: In> writer := {}; Out> {}; In> writer[``Iliad``] := ``Homer``; Out> True; In> writer[``Henry IV``] := ``Shakespeare``; Out> True; In> writer[``Ulysses``] := ``James Joyce``; Out> True; In> AssocDelete(writer, ``Henry IV``) Out> True; In> AssocDelete(writer, ``Henry XII``) Out> False; In> writer Out> {{``Ulysses``,``James Joyce``}, {``Iliad``,``Homer``}}; In> DestructiveAppend(writer, {``Ulysses``, ``Dublin``}); Out> {{``Iliad``,``Homer``},{``Ulysses``,``James Joyce``}, {``Ulysses``,``Dublin``}}; In> writer[``Ulysses``]; Out> ``James Joyce``; In> AssocDelete(writer,{``Ulysses``,``James Joyce``}); Out> True; In> writer Out> {{``Iliad``,``Homer``},{``Ulysses``,``Dublin``}};
See also
Sorting¶
-
BubbleSort
(list, compare)¶ sort a list
Parameters: - list – list to sort
- compare – function used to compare elements of {list}
This command returns {list} after it is sorted using {compare} to compare elements. The function {compare} should accept two arguments, which will be elements of {list}, and compare them. It should return
True
if in the sorted list the second argument should come after the first one, andFalse
otherwise.The function {BubbleSort} uses the so-called
bubble sort
algorithm to do the sorting by swapping elements that are out of order. This algorithm is easy to implement, though it is not particularly fast. The sorting time is proportional to \(n^2\) where \(n\) is the length of the list.Example: In> BubbleSort({4,7,23,53,-2,1}, "<"); Out> {-2,1,4,7,23,53};
See also
-
HeapSort
(list, compare)¶ sort a list
Parameters: - list – list to sort
- compare – function used to compare elements of {list}
This command returns {list} after it is sorted using {compare} to compare elements. The function {compare} should accept two arguments, which will be elements of {list}, and compare them. It should return
True
if in the sorted list the second argument should come after the first one, andFalse
otherwise.The function {HeapSort} uses the
heapsort algorithm
and is much faster for large lists. The sorting time is proportional to \(n*\ln(n)\) where \(n\) is the length of the list.Example: In> HeapSort({4,7,23,53,-2,1}, ``>``); Out> {53,23,7,4,1,-2};
See also
Stack and queue operations¶
-
Push
(stack, expr)¶ add an element on top of a stack
Parameters: - stack – a list (which serves as the stack container)
- expr – expression to push on
stack
This is part of a simple implementation of a stack, internally represented as a list. This command pushes the expression
expr
on top of the stack, and returns the stack afterwards.Example: In> stack := {}; Out> {}; In> Push(stack, x); Out> {x}; In> Push(stack, x2); Out> {x2,x}; In> PopFront(stack); Out> x2;
See also
-
Pop
(stack, n)¶ remove an element from a stack
Parameters: - stack – a list (which serves as the stack container)
- n – index of the element to remove
This is part of a simple implementation of a stack, internally represented as a list. This command removes the element with index
n
from the stack and returns this element. The top of the stack is represented by the index 1. Invalid indices, for example indices greater than the number of element on the stack, lead to an error.Example: In> stack := {}; Out> {}; In> Push(stack, x); Out> {x}; In> Push(stack, x2); Out> {x2,x}; In> Push(stack, x3); Out> {x3,x2,x}; In> Pop(stack, 2); Out> x2; In> stack; Out> {x3,x};
See also
-
PopFront
(stack)¶ remove an element from the top of a stack
Parameters: stack – a list (which serves as the stack container) This is part of a simple implementation of a stack, internally represented as a list. This command removes the element on the top of the stack and returns it. This is the last element that is pushed onto the stack.
Example: In> stack := {}; Out> {}; In> Push(stack, x); Out> {x}; In> Push(stack, x2); Out> {x2,x}; In> Push(stack, x3); Out> {x3,x2,x}; In> PopFront(stack); Out> x3; In> stack; Out> {x2,x};
-
PopBack
(stack)¶ remove an element from the bottom of a stack
Parameters: stack – a list (which serves as the stack container) This is part of a simple implementation of a stack, internally represented as a list. This command removes the element at the bottom of the stack and returns this element. Of course, the stack should not be empty.
Example: In> stack := {}; Out> {}; In> Push(stack, x); Out> {x}; In> Push(stack, x2); Out> {x2,x}; In> Push(stack, x3); Out> {x3,x2,x}; In> PopBack(stack); Out> x; In> stack; Out> {x3,x2};
See also
Global stack¶
-
GlobalPop
()¶ restore variables using a global stack
-
GlobalPush
()¶ - save variables using a global stack
- GlobalPop(var) GlobalPop() GlobalPush(expr)
Parameters: - var – atom, name of variable to restore from the stack
- expr – expression, value to save on the stack
These functions operate with a global stack, currently implemented as a list that is not accessible externally (it is protected through {LocalSymbols}).
{GlobalPush} stores a value on the stack. {GlobalPop} removes the last pushed value from the stack. If a variable name is given, the variable is assigned, otherwise the popped value is returned.
If the global stack is empty, an error message is printed.
Example: In> GlobalPush(3) Out> 3; In> GlobalPush(Sin(x)) Out> Sin(x); In> GlobalPop(x) Out> Sin(x); In> GlobalPop(x) Out> 3; In> x Out> 3;
See also
Graphs¶
-
Graph
(edges)¶ -
Graph
(vertices, edges) construct a graph
-
Vertices
(g)¶ return list of graph vertices
-
Edges
(g)¶ return list of graph edges
See also
-
AdjacencyList
(g)¶ adjacency list
Parameters: g – graph Return adjacency list of graph
g
.See also
-
AdjacencyMatrix
(g)¶ adjacency matrix
Parameters: g – graph Return adjacency matrix of graph
g
.See also
-
BFS
(g, f)¶ -
BFS
(g, v, f) traverse graph in breadth-first order
Traverse graph
g
in breadth-first order, starting fromv
if provided, or from the first vertex.f
is called for every visited vertex.
-
DFS
(g, f)¶ -
DFS
(g, v, f) traverse graph in depth-first order
Traverse graph
g
in depth-first order, starting fromv
if provided, or from the first vertex.f
is called for every visited vertex.
Functional operators¶
These operators can help the user to program in the style of functional programming languages such as Miranda or Haskell.
-
item
:
list¶ prepend item to list, or concatenate strings
Parameters: - item – an item to be prepended to a list
- list – a list
- string1 – a string
- string2 – a string
The first form prepends “item” as the first entry to the list “list”. The second form concatenates the strings “string1” and “string2”.
Example: In> a:b:c:{} Out> {a,b,c}; In> "This":"Is":"A":"String" Out> "ThisIsAString";
See also
-
fn
@
arglist¶ apply a function
Parameters: - fn – function to apply
- arglist – single argument, or a list of arguments
This function is a shorthand for
Apply()
. It applies the function “fn” to the argument(s) in “arglist” and returns the result. The first parameter “fn” can either be a string containing the name of a function or a pure function.Example: In> "Sin" @ a Out> Sin(a); In> {{a},Sin(a)} @ a Out> Sin(a); In> "f" @ {a,b} Out> f(a,b);
See also
-
fn
/@
list¶ apply a function to all entries in a list
Parameters: - fn – function to apply
- list – list of arguments
This function is a shorthand for {MapSingle}. It successively applies the function “fn” to all the entries in “list” and returns a list contains the results. The parameter “fn” can either be a string containing the name of a function or a pure function.
Example: In> "Sin" /@ {a,b} Out> {Sin(a),Sin(b)}; In> {{a},Sin(a)*a} /@ {a,b} Out> {Sin(a)*a,Sin(b)*b};
See also
-
n
..
m¶ construct a list of consecutive integers
Parameters: - n – integer. the first entry in the list
- m – integer, the last entry in the list
This command returns the list {{n, n+1, n+2, ..., m}}. If {m} is smaller than {n}, the empty list is returned. Note that the {..} operator should be surrounded by spaces to keep the parser happy, if “n” is a number. So one should write “{1 .. 4}” instead of “{1..4}”.
-
NFunction
("newname", "funcname", {arglist})¶ make wrapper for numeric functions
Parameters: - "newname" – name of new function
- "funcname" – name of an existing function
- arglist – symbolic list of arguments
This function will define a function named “newname” with the same arguments as an existing function named “funcname”. The new function will evaluate and return the expression “funcname(arglist)” only when all items in the argument list {arglist} are numbers, and return unevaluated otherwise. This can be useful when plotting functions defined through other Yacas routines that cannot return unevaluated. If the numerical calculation does not return a number (for example, it might return the atom {nan}, “not a number”, for some arguments), then the new function will return {Undefined}.
Example: In> f(x) := N(Sin(x)); Out> True; In> NFunction("f1", "f", {x}); Out> True; In> f1(a); Out> f1(a); In> f1(0); Out> 0;
Suppose we need to define a complicated function {t(x)} which cannot be evaluated unless {x} is a number:
In> t(x) := If(x<=0.5, 2*x, 2*(1-x)); Out> True; In> t(0.2); Out> 0.4; In> t(x); In function "If" : bad argument number 1 (counting from 1) CommandLine(1) : Invalid argument
Then, we can use {NFunction()} to define a wrapper {t1(x)} around {t(x)} which will not try to evaluate {t(x)} unless {x} is a number:
In> NFunction("t1", "t", {x}) Out> True; In> t1(x); Out> t1(x); In> t1(0.2); Out> 0.4;
Now we can plot the function.
In> Plot2D(t1(x), -0.1: 1.1) Out> True;See also
-
expr
Where
x==v¶ substitute result into expression
Parameters: - expr – expression to evaluate
- x – variable to set
- v – value to substitute for variable
The operator {Where} fills in values for variables, in its simplest form. It accepts sets of variable/value pairs defined as var1==val1 And var2==val2 And ... and fills in the corresponding values. Lists of value pairs are also possible, as: {var1==val1 And var2==val2, var1==val3 And var2==val4} These values might be obtained through {Solve}.
Example: In> x^2+y^2 Where x==2 Out> y^2+4; In> x^2+y^2 Where x==2 And y==3 Out> 13; In> x^2+y^2 Where {x==2 And y==3} Out> {13}; In> x^2+y^2 Where {x==2 And y==3,x==4 And y==5} Out> {13,41};
-
eq1
AddTo
eq2¶ add an equation to a set of equations or set of set of equations
Parameters: eq – (set of) set of equations Given two (sets of) sets of equations, the command AddTo combines multiple sets of equations into one. A list {a,b} means that a is a solution, OR b is a solution. AddTo then acts as a AND operation: (a or b) and (c or d) => (a or b) Addto (c or d) => (a and c) or (a and d) or (b and c) or (b and d) This function is useful for adding an identity to an already existing set of equations. Suppose a solve command returned {a>=0 And x==a,a<0 And x== -a} from an expression x==Abs(a), then a new identity a==2 could be added as follows: In> a==2 AddTo {a>=0 And x==a,a<0 And x== -a} Out> {a==2 And a>=0 And x==a,a==2 And a<0 And x== -a}; Passing this set of set of identities back to solve, solve should recognize that the second one is not a possibility any more, since a==2 And a<0 can never be true at the same time.
Example: In> {A==2,c==d} AddTo {b==3 And d==2} Out> {A==2 And b==3 And d==2,c==d And b==3 And d==2}; In> {A==2,c==d} AddTo {b==3, d==2} Out> {A==2 And b==3,A==2 And d==2,c==d And b==3,c==d And d==2};
Control flow functions¶
-
MaxEvalDepth
(n)¶ set the maximum evaluation depth
Parameters: n – new maximum evaluation depth Use this command to set the maximum evaluation depth to
n
. The default value is 1000.The point of having a maximum evaluation depth is to catch any infinite recursion. For example, after the definition
f(x) := f(x)
, evaluating the expressionf(x)
would callf(x)
, which would callf(x)
, etc. The interpreter will halt if the maximum evaluation depth is reached. Also indirect recursion, e.g. the pair of definitionsf(x) := g(x)
andg(x) := f(x)
, will be caught.An example of an infinite recursion, caught because the maximum evaluation depth is reached
In> f(x) := f(x) Out> True; In> f(x) Error on line 1 in file [CommandLine] Max evaluation stack depth reached. Please use MaxEvalDepth to increase the stack size as needed.
However, a long calculation may cause the maximum evaluation depth to be reached without the presence of infinite recursion. The function
MaxEvalDepth()
is meant for these casesIn> 10 # g(0) <-- 1; Out> True; In> 20 # g(n_IsPositiveInteger) <-- \ 2 * g(n-1); Out> True; In> g(1001); Error on line 1 in file [CommandLine] Max evaluation stack depth reached. Please use MaxEvalDepth to increase the stack size as needed. In> MaxEvalDepth(10000); Out> True; In> g(1001); Out> 21430172143725346418968500981200036211228096234 1106721488750077674070210224987224498639675763139171 6255189345835106293650374290571384628087196915514939 7149607869135549648461970842149210124742283755908364 3060929499671638825347975351183310878921541258291423 92955373084335320859663305248773674411336138752;
-
Hold
(expr)¶ keep expression unevaluated
Parameters: expr – expression to keep unevaluated The expression
expr
is returned unevaluated. This is useful to prevent the evaluation of a certain expression in a context in which evaluation normally takes place.Example: In> Echo({ Hold(1+1), "=", 1+1 }); 1+1 = 2 Out> True;
-
Eval
(expr)¶ force evaluation of expression
Parameters: expr – expression to evaluate This function explicitly requests an evaluation of the expression
expr
, and returns the result of this evaluation.Example: In> a := x; Out> x; In> x := 5; Out> 5; In> a; Out> x; In> Eval(a); Out> 5;
The variable
a
is bound tox
, andx
is bound to 5. Hence evaluatinga
will givex
. Only when an extra evaluation ofa
is requested, the value 5 is returned. Note that the behavior would be different if we had exchanged the assignments. If the assignmenta := x
were given whilex
had the value 5, the variablea
would also get the value 5 because the assignment operator:=()
evaluates the right-hand side.
-
While
(pred) expr¶ loop while a condition is met
Parameters: - pred – predicate deciding whether to keep on looping
- expr – expression to loop over
Keep on evaluating
expr
whilepred
evaluates toTrue
. More precisely,While()
evaluates the predicatepred
, which should evaluate to eitherTrue
orFalse
. If the result isTrue
, the expressionexpr
is evaluated and then the predicatepred
is evaluated again. If it is stillTrue
, the expressionsexpr
andpred
are again evaluated and so on untilpred
evaluates toFalse
. At that point, the loop terminates andWhile()
returnsTrue
.In particular, if
pred
immediately evaluates toFalse
, the body is never executed.While()
is the fundamental looping construct on which all other loop commands are based. It is equivalent to thewhile
command in the programming language C.Example: In> x := 0; Out> 0; In> While (x! < 10^6) \ [ Echo({x, x!}); x++; ]; 0 1 1 1 2 2 3 6 4 24 5 120 6 720 7 5040 8 40320 9 362880 Out> True;
-
Until
(pred) expr¶ loop until a condition is met
Parameters: - pred – predicate deciding whether to stop
- expr – expression to loop over
Keep on evaluating
expr
untilpred
becomesTrue
. More precisely,Until()
first evaluates the expressionbody
. Then the predicatepred
is evaluated, which should yield eitherTrue
orFalse
. In the latter case, the expressionsexpr
andpred
are again evaluated and this continues as long as “pred” isFalse
. As soon aspred
yieldsTrue
, the loop terminates andUntil()
returnsTrue
.The main difference with
While()
is thatUntil()
always evaluatesexpr
at least once, butWhile()
may not evaluate it at all. Besides, the meaning of the predicate is reversed:While()
stops ifpred
isFalse
whileUntil()
stops ifpred
isTrue
. The commandUntil(pred) expr;
is equivalent topred; While(Not pred) body;
. In fact, the implementation ofUntil()
is based on the internal commandWhile()
. TheUntil()
command can be compared to thedo ... while
construct in the programming language C.Example: In> x := 0; Out> 0; In> Until (x! > 10^6) \ [ Echo({x, x!}); x++; ]; 0 1 1 1 2 2 3 6 4 24 5 120 6 720 7 5040 8 40320 9 362880 Out> True;
-
If
(pred, then[, else])¶ branch point
Parameters: - pred – predicate to test
- then – expression to evaluate if
pred
isTrue
- else – expression to evaluate if
pred
isFalse
This command implements a branch point. The predicate
pred
is evaluated, which should result in eitherTrue
orFalse
. In the first case, the expressionthen
is evaluated and returned. If the predicate yieldsFalse
, the expressionelse
(if present) is evaluated and returned. If there is noelse
branch, theIf()
expression returnsFalse
.The sign function is defined to be 1 if its argument is positive and -1 if its argument is negative. A possible implementation is:
In> mysign(x) := If (IsPositiveReal(x), 1, -1); Out> True; In> mysign(Pi); Out> 1; In> mysign(-2.5); Out> -1;
Note that this will give incorrect results, if
x
cannot be numerically approximated:In> mysign(a); Out> -1;
Hence a better implementation would be:
In> mysign(_x)_IsNumber(N(x)) <-- If(IsPositiveReal(x), 1, -1); Out> True;
-
SystemCall
(str)¶ pass a command to the shell
Parameters: str – the command to call The command contained in the string
str
is executed by the underlying operating system. The return value ofSystemCall()
isTrue
orFalse
according to the exit code of the command.The
SystemCall()
function is not allowed in the body of theSecure()
command.In a UNIX environment, the command
SystemCall("ls")
would print the contents of the current directory:In> SystemCall("ls") AUTHORS COPYING ChangeLog ... (truncated to save space) Out> True;
The standard UNIX command
test
returns success or failure depending on conditions. For example, the following command will check if a directory exists:In> SystemCall("test -d scripts/") Out> True;
Check that a file exists:
In> SystemCall("test -f COPYING") Out> True; In> SystemCall("test -f nosuchfile.txt") Out> False;
See also
-
Function
() func(args)¶ -
Function
(funcname, {args}) body declare or define a function
Parameters: - func(args) – function declaration, e.g.
f(x,y)
- args – list of atoms, formal arguments to the function
- body – expression comprising the body of the function
This command can be used to define a new function with named arguments.
The number of arguments of the new function and their names are determined by the list
args
. If the ellipsis...
follows the last atom inargs
, a function with a variable number of arguments is declared (usingRuleBaseListed()
). Note that the ellipsis cannot be the only element ofargs
and must be preceded by an atom.A function with variable number of arguments can take more arguments than elements in
args
; in this case, it obtains its last argument as a list containing all extra arguments.The short form of the
Function()
call merely declares aRuleBase()
for the new function but does not define any function body. This is a convenient shorthand forRuleBase()
andRuleBaseListed()
, when definitions of the function are to be supplied by rules. If the new function has been already declared with the same number of arguments (with or without variable arguments),Function()
returns false and does nothing.The second, longer form of the
Function()
call declares a function and also defines a function body. It is equivalent to a single rule such asfuncname(_arg1, _arg2) <-- body
. The rule will be declared at precedence 1025. Any previous rules associated withfuncname
(with the same arity) will be discarded. More complicated functions (with more than one body) can be defined by adding more rules.Example: This will declare a new function with two or more arguments, but define no rules for it. This is equivalent to
RuleBase ("f1", {x, y, ...})
:In> Function() f1(x,y,...); Out> True; In> Function() f1(x,y); Out> False;
This defines a function
FirstOf
which returns the first element of a list. Equivalent definitions would beFirstOf(_list) <-- list[1]
orFirstOf(list) := list[1]
:In> Function("FirstOf", {list}) list[1]; Out> True; In> FirstOf({a,b,c}); Out> a;
The following function will print all arguments to a string:
In> Function("PrintAll",{x, ...}) If(IsList(x), PrintList(x), ToString()Write(x)); Out> True; In> PrintAll(1): Out> " 1"; In> PrintAll(1,2,3); Out> " 1 2 3";
See also
TemplateFunction()
,Rule()
,RuleBase()
,RuleBaseListed()
,:=()
,Retract()
- func(args) – function declaration, e.g.
-
Macro
() func(args)¶ -
Macro
(funcname, {args}) body declare or define a macro
Parameters: - func(args) – function declaration, e.g.
f(x,y)
- args – list of atoms, formal arguments to the function
- body – expression comprising the body of the function
This does the same as
Function()
, but for macros. One can define a macro easily with this function, instead of having to useDefMacroRuleBase()
.Example: The following example defines a looping function
In> Macro("myfor",{init,pred,inc,body}) [@init;While(@pred)[@body;@inc;];True;]; Out> True; In> a:=10 Out> 10;
Here this new macro
myfor
is used to loop, using a variablea
from the calling environmentIn> myfor(i:=1,i<10,i++,Echo(a*i)) 10 20 30 40 50 60 70 80 90 Out> True; In> i Out> 10;
See also
- func(args) – function declaration, e.g.
-
For
(init, pred, incr) expr¶ C-style
for
loopParameters: - init – expression for performing the initialization
- pred – predicate deciding whether to continue the loop
- incr – expression to increment the counter
- expr – expression to loop over
This commands implements a C style
for
loop. First of all, the expressioninit
is evaluated. Then the predicatepred
is evaluated, which should returnTrue
orFalse
. Next, the loop is executed as long as the predicate yieldsTrue
. One traversal of the loop consists of the subsequent evaluations ofexpr
,incr
, andpred
. Finally,True
is returned.This command is most often used in a form such as
For(i=1, i<=10, i++) expr
, which evaluatesexpr
withi
subsequently set to 1, 2, 3, 4, 5, 6, 7, 8, 9, and 10.The expression
For(init, pred, incr) expr
is equivalent toinit; While(pred) [expr; incr;]
.Example: In> For (i:=1, i<=10, i++) Echo({i, i!}); 1 1 2 2 3 6 4 24 5 120 6 720 7 5040 8 40320 9 362880 10 3628800 Out> True;
-
ForEach
(var, list) expr¶ loop over all entries in list
Parameters: - var – looping variable
- list – list of values to assign to
var
- expr – expression to evaluate with different values of
var
The expression
expr
is evaluated multiple times. The first time,var
has the value of the first element of “list”, then it gets the value of the second element and so on.ForEach()
returnsTrue
.Example: In> ForEach(i,{2,3,5,7,11}) Echo({i, i!}); 2 2 3 6 5 120 7 5040 11 39916800 Out> True;
See also
-
Apply
(fn, arglist)¶ apply a function to arguments
Parameters: - fn – function to apply
- arglist – list of arguments
This function applies the function “fn” to the arguments in “arglist” and returns the result. The first parameter “fn” can either be a string containing the name of a function or a pure function. Pure functions, modeled after lambda-expressions, have the form “{varlist,body}”, where “varlist” is the list of formal parameters. Upon application, the formal parameters are assigned the values in “arglist” (the second parameter of {Apply}) and the “body” is evaluated.
Another way to define a pure function is with the Lambda construct. Here, in stead of passing in “{varlist,body}”, one can pass in “Lambda(varlist,body)”. Lambda has the advantage that its arguments are not evaluated (using lists can have undesirable effects because lists are evaluated). Lambda can be used everywhere a pure function is expected, in principle, because the function Apply is the only function dealing with pure functions. So all places where a pure function can be passed in will also accept Lambda.
An shorthand for {Apply} is provided by the {@} operator.
Example: In> Apply("+", {5,9}); Out> 14; In> Apply({{x,y}, x-y^2}, {Cos(a), Sin(a)}); Out> Cos(a)-Sin(a)^2; In> Apply(Lambda({x,y}, x-y^2), {Cos(a), Sin(a)}); Out> Cos(a)-Sin(a)^2 In> Lambda({x,y}, x-y^2) @ {Cos(a), Sin(a)} Out> Cos(a)-Sin(a)^2
See also
-
MapArgs
(expr, fn)¶ apply a function to all top-level arguments
Parameters: - expr – an expression to work on
- fn – an operation to perform on each argument
Every top-level argument in
expr
is substituted by the result of applyingfn
to this argument. Herefn
can be either the name of a function or a pure function (seeApply()
for more information on pure functions).Example: In> MapArgs(f(x,y,z),"Sin"); Out> f(Sin(x),Sin(y),Sin(z)); In> MapArgs({3,4,5,6}, {{x},x^2}); Out> {9,16,25,36};
See also
-
Subst
(from, to) expr¶ perform a substitution
Parameters: - from – expression to be substituted
- to – expression to substitute for “from”
- expr – expression in which the substitution takes place
This function substitutes every occurrence of
from
inexpr
byto
. This is a syntactical substitution: only places wherefrom
occurs as a subexpression are affected.Example: In> Subst(x, Sin(y)) x^2+x+1; Out> Sin(y)^2+Sin(y)+1; In> Subst(a+b, x) a+b+c; Out> x+c; In> Subst(b+c, x) a+b+c; Out> a+b+c;
The explanation for the last result is that the expression
a+b+c
is internally stored as(a+b)+c
. Hencea+b
is a subexpression, butb+c
is not.See also
-
WithValue
(var, val, expr)¶ temporary assignment during an evaluation
Parameters: - var – variable to assign to
- val – value to be assigned to “var”
- expr – expression to evaluate with “var” equal to “val”
First, the expression “val” is assigned to the variable “var”. Then, the expression “expr” is evaluated and returned. Finally, the assignment is reversed so that the variable “var” has the same value as it had before {WithValue} was evaluated.
The second calling sequence assigns the first element in the list of values to the first element in the list of variables, the second value to the second variable, etc.
Example: In> WithValue(x, 3, x^2+y^2+1); Out> y^2+10; In> WithValue({x,y}, {3,2}, x^2+y^2+1); Out> 14;
-
expression
/:
patterns¶ local simplification rules
Parameters: - expression – an expression
- patterns – a list of patterns
Sometimes you have an expression, and you want to use specific simplification rules on it that are not done by default. This can be done with the {/:} and the {/::} operators. Suppose we have the expression containing things such as {Ln(a*b)}, and we want to change these into {Ln(a)+Ln(b)}, the easiest way to do this is using the {/:} operator, as follows:
In> Sin(x)*Ln(a*b) Out> Sin(x)*Ln(a*b); In> % /: { Ln(_x*_y) <- Ln(x)+Ln(y) } Out> Sin(x)*(Ln(a)+Ln(b));
A whole list of simplification rules can be built up in the list, and they will be applied to the expression on the left hand side of {/:} .
- The forms the patterns can have are one of: ::
- pattern <- replacement {pattern,replacement} {pattern,postpredicate,replacement}
Note that for these local rules, {<-} should be used instead of {<–} which would be used in a global rule.
The {/:} operator traverses an expression much as {Subst} does, that is, top down, trying to apply the rules from the beginning of the list of rules to the end of the list of rules. If the rules cannot be applied to an expression, it will try subexpressions of that expression and so on.
It might be necessary sometimes to use the {/::} operator, which repeatedly applies the {/:} operator until the result doesn’t change any more. Caution is required, since rules can contradict each other, which could result in an infinite loop. To detect this situation, just use /: repeatedly on the expression. The repetitive nature should become apparent.
Example: In> Sin(u)*Ln(a*b) /: {Ln(_x*_y) <- Ln(x)+Ln(y)} Out> Sin(u)*(Ln(a)+Ln(b)); In> Sin(u)*Ln(a*b) /:: { a <- 2, b <- 3 } Out> Sin(u)*Ln(6);
See also
-
TraceStack
(expression)¶ show calling stack after an error occurs
Parameters: expression – an expression to evaluate TraceStack shows the calling stack after an error occurred. It shows the last few items on the stack, not to flood the screen. These are usually the only items of interest on the stack. This is probably by far the most useful debugging function in Yacas. It shows the last few things it did just after an error was generated somewhere.
For each stack frame, it shows if the function evaluated was a built-in function or a user-defined function, and for the user-defined function, the number of the rule it is trying whether it was evaluating the pattern matcher of the rule, or the body code of the rule.
This functionality is not offered by default because it slows down the evaluation code.
Example: Here is an example of a function calling itself recursively, causing Yacas to flood its stack: In> f(x):=f(Sin(x)) Out> True; In> TraceStack(f(2)) Debug> 982 : f (Rule # 0 in body) Debug> 983 : f (Rule # 0 in body) Debug> 984 : f (Rule # 0 in body) Debug> 985 : f (Rule # 0 in body) Debug> 986 : f (Rule # 0 in body) Debug> 987 : f (Rule # 0 in body) Debug> 988 : f (Rule # 0 in body) Debug> 989 : f (Rule # 0 in body) Debug> 990 : f (Rule # 0 in body) Debug> 991 : f (Rule # 0 in body) Debug> 992 : f (Rule # 0 in body) Debug> 993 : f (Rule # 0 in body) Debug> 994 : f (Rule # 0 in body) Debug> 995 : f (User function) Debug> 996 : Sin (Rule # 0 in pattern) Debug> 997 : IsList (Internal function) Error on line 1 in file [CommandLine] Max evaluation stack depth reached. Please use MaxEvalDepth to increase the stack size as needed.
See also
-
TraceExp
(expr)¶ evaluate with tracing enabled
Parameters: expr – expression to trace The expression “expr” is evaluated with the tracing facility turned on. This means that every subexpression, which is evaluated, is shown before and after evaluation. Before evaluation, it is shown in the form {TrEnter(x)}, where {x} denotes the subexpression being evaluated. After the evaluation the line {TrLeave(x,y)} is printed, where {y} is the result of the evaluation. The indentation shows the nesting level.
Note that this command usually generates huge amounts of output. A more specific form of tracing (eg. {TraceRule}) is probably more useful for all but very simple expressions.
Example: In> TraceExp(2+3); TrEnter(2+3); TrEnter(2); TrLeave(2, 2); TrEnter(3); TrLeave(3, 3); TrEnter(IsNumber(x)); TrEnter(x); TrLeave(x, 2); TrLeave(IsNumber(x),True); TrEnter(IsNumber(y)); TrEnter(y); TrLeave(y, 3); TrLeave(IsNumber(y),True); TrEnter(True); TrLeave(True, True); TrEnter(MathAdd(x,y)); TrEnter(x); TrLeave(x, 2); TrEnter(y); TrLeave(y, 3); TrLeave(MathAdd(x,y),5); TrLeave(2+3, 5); Out> 5;
See also
-
TraceRule
(template) expr¶ turn on tracing for a particular function
Parameters: - template – template showing the operator to trace
- expr – expression to evaluate with tracing on
The tracing facility is turned on for subexpressions of the form “template”, and the expression “expr” is evaluated. The template “template” is an example of the function to trace on. Specifically, all subexpressions with the same top-level operator and arity as “template” are shown. The subexpressions are displayed before (indicated with {TrEnter}) and after ({TrLeave}) evaluation. In between, the arguments are shown before and after evaluation ({TrArg}). Only functions defined in scripts can be traced.
This is useful for tracing a function that is called from within another function. This way you can see how your function behaves in the environment it is used in.
Example: In> TraceRule(x+y) 2+3*5+4; TrEnter(2+3*5+4); TrEnter(2+3*5); TrArg(2, 2); TrArg(3*5, 15); TrLeave(2+3*5, 17); TrArg(2+3*5, 17); TrArg(4, 4); TrLeave(2+3*5+4, 21); Out> 21;
See also
-
Time
(expr)¶ measure the time taken by a function
Parameters: expr – any expression The function {Time(expr)} evaluates the expression {expr} and prints the time in seconds needed for the evaluation. The time is printed to the current output stream. The built-in function {GetTime} is used for timing.
The result is the “user time” as reported by the OS, not the real (“wall clock”) time. Therefore, any CPU-intensive processes running alongside Yacas will not significantly affect the result of {Time}.
Example: In> Time(N(MathLog(1000),40)) 0.34 seconds taken Out> 6.9077552789821370520539743640530926228033;
See also
Predicates¶
A predicate is a function that returns a boolean value, i.e. True
or
False
. Predicates are often used in patterns, For instance, a rule
that only holds for a positive integer would use a pattern such as
{n_IsPositiveInteger}.
-
e1 != e2
test for “not equal”
Parameters: {e2 (e1},) – expressions to be compared Both expressions are evaluated and compared. If they turn out to be equal, the result is
False
. Otherwise, the result isTrue
. The expression {e1 != e2} is equivalent to {Not(e1 = e2)}.Example: In> 1 != 2; Out> True; In> 1 != 1; Out> False;
See also
=()
-
e1 = e2
test for equality of expressions
Parameters: {e2 (e1},) – expressions to be compared Both expressions are evaluated and compared. If they turn out to be equal, the result is
True
. Otherwise, the result isFalse
. The function {Equals} does the same. Note that the test is on syntactic equality, not mathematical equality. Hence even if the result isFalse
, the expressions can still be <i>mathematically</i> equal; see the examples below. Put otherwise, this function tests whether the two expressions would be displayed in the same way if they were printed.Example: In> e1 := (x+1) * (x-1); Out> (x+1)*(x-1); In> e2 := x^2 - 1; Out> x^2-1; In> e1 = e2; Out> False; In> Expand(e1) = e2; Out> True;
See also
=()
,Equals()
-
Not expr
logical negation
Parameters: expr – a boolean expression Not returns the logical negation of the argument expr. If {expr} is
False
it returnsTrue
, and if {expr} isTrue
, {Not expr} returnsFalse
. If the argument is neitherTrue
norFalse
, it returns the entire expression with evaluated arguments.Example: In> Not True Out> False; In> Not False Out> True; In> Not(a) Out> Not a;
See also
And()
,Or()
-
a1 And a2
logical conjunction
Parameters: ..., {a} (a}1,) – boolean values (may evaluate to True
orFalse
)This function returns
True
if all arguments are true. The {And} operation is “lazy”, i.e. it returnsFalse
as soon as aFalse
argument is found (from left to right). If an argument other thanTrue
orFalse
is encountered a new {And} expression is returned with all arguments that didn’t evaluate toTrue
orFalse
yet.Example: In> True And False Out> False; In> And(True,True) Out> True; In> False And a Out> False; In> True And a Out> And(a); In> And(True,a,True,b) Out> b And a;
See also
Or()
,Not()
-
a1 Or a2
logical disjunction
Parameters: ..., {a} (a}1,) – boolean expressions (may evaluate to True
orFalse
)This function returns
True
if an argument is encountered that is true (scanning from left to right). The {Or} operation is “lazy”, i.e. it returnsTrue
as soon as aTrue
argument is found (from left to right). If an argument other thanTrue
orFalse
is encountered, an unevaluated {Or} expression is returned with all arguments that didn’t evaluate toTrue
orFalse
yet.Example: In> True Or False Out> True; In> False Or a Out> Or(a); In> Or(False,a,b,True) Out> True;
See also
And()
,Not()
-
IsFreeOf
(var, expr)¶ test whether expression depends on variable
Parameters: - expr – expression to test
- var – variable to look for in “expr”
This function checks whether the expression “expr” (after being evaluated) depends on the variable “var”. It returns
False
if this is the case andTrue
otherwise. The second form test whether the expression depends on <i>any</i> of the variables named in the list. The result isTrue
if none of the variables appear in the expression andFalse
otherwise.Example: In> IsFreeOf(x, Sin(x)); Out> False; In> IsFreeOf(y, Sin(x)); Out> True; In> IsFreeOf(x, D(x) a*x+b); Out> True; In> IsFreeOf({x,y}, Sin(x)); Out> False; The third command returns :data:`True` because the expression {D(x) a*x+b} evaluates to {a}, which does not depend on {x}.
See also
-
IsZeroVector
(list)¶ test whether list contains only zeroes
Parameters: list – list to compare against the zero vector The only argument given to {IsZeroVector} should be a list. The result is
True
if the list contains only zeroes andFalse
otherwise.Example: In> IsZeroVector({0, x, 0}); Out> False; In> IsZeroVector({x-x, 1 - D(x) x}); Out> True;
See also
-
IsNonObject
(expr)¶ test whether argument is not an {Object()}
Parameters: expr – the expression to examine This function returns
True
if “expr” is not of the form {Object(...)} andFalse
otherwise.
-
IsEven
(n)¶ test for an even integer
Parameters: n – integer to test This function tests whether the integer “n” is even. An integer is even if it is divisible by two. Hence the even numbers are 0, 2, 4, 6, 8, 10, etc., and -2, -4, -6, -8, -10, etc.
Example: In> IsEven(4); Out> True; In> IsEven(-1); Out> False;
See also
IsOdd()
,IsInteger()
-
IsOdd
(n)¶ test for an odd integer
Parameters: n – integer to test This function tests whether the integer “n” is odd. An integer is odd if it is not divisible by two. Hence the odd numbers are 1, 3, 5, 7, 9, etc., and -1, -3, -5, -7, -9, etc.
Example: In> IsOdd(4); Out> False; In> IsOdd(-1); Out> True;
See also
IsEven()
,IsInteger()
-
IsEvenFunction
(expression, variable)¶ Return true if function is an even function, False otherwise
Parameters: - expression – mathematical expression
- variable – variable
These functions return True if Yacas can determine that the function is even or odd respectively. Even functions are defined to be functions that have the property: $$ f(x) = f(-x) $$ And odd functions have the property: $$ f(x) = -f(-x) $$ {Sin(x)} is an example of an odd function, and {Cos(x)} is an example of an even function.
Note
One can decompose a function into an even and an odd part $$ f(x) = f_{even}(x) + f_{odd}(x) $$ where $$ f_{even}(x) = (f(x)+f(-x))/2 $$ and $$ f_{odd}(x) = (f(x)-f(-x))/2 $$
-
IsFunction
(expr)¶ test for a composite object
Parameters: expr – expression to test This function tests whether “expr” is a composite object, i.e. not an atom. This includes not only obvious functions such as {f(x)}, but also expressions such as
x+5
and lists.Example: In> IsFunction(x+5); Out> True; In> IsFunction(x); Out> False;
-
IsAtom
(expr)¶ test for an atom
Parameters: expr – expression to test This function tests whether “expr” is an atom. Numbers, strings, and variables are all atoms.
Example: In> IsAtom(x+5); Out> False; In> IsAtom(5); Out> True;
See also
-
IsString
(expr)¶ test for an string
Parameters: expr – expression to test This function tests whether “expr” is a string. A string is a text within quotes, e.g. {“duh”}.
Example: In> IsString("duh"); Out> True; In> IsString(duh); Out> False;
See also
-
IsNumber
(expr)¶ test for a number
Parameters: expr – expression to test This function tests whether “expr” is a number. There are two kinds of numbers, integers (e.g. 6) and reals (e.g. -2.75 or 6.0). Note that a complex number is represented by the {Complex} function, so {IsNumber} will return
False
.Example: In> IsNumber(6); Out> True; In> IsNumber(3.25); Out> True; In> IsNumber(I); Out> False; In> IsNumber("duh"); Out> False;
See also
IsAtom()
,IsString()
,IsInteger()
,IsPositiveNumber()
,IsNegativeNumber()
,Complex()
-
IsList
(expr)¶ test for a list
Parameters: expr – expression to test This function tests whether “expr” is a list. A list is a sequence between curly braces, e.g. {{2, 3, 5}}.
Example: In> IsList({2,3,5}); Out> True; In> IsList(2+3+5); Out> False;
See also
-
IsNumericList
({list})¶ test for a list of numbers
Parameters: {list} – a list Returns
True
when called on a list of numbers or expressions that evaluate to numbers using {N()}. ReturnsFalse
otherwise.See also
-
IsBound
(var)¶ test for a bound variable
Parameters: var – variable to test This function tests whether the variable “var” is bound, i.e. whether it has been assigned a value. The argument “var” is not evaluated.
Example: In> IsBound(x); Out> False; In> x := 5; Out> 5; In> IsBound(x); Out> True;
See also
-
IsBoolean
(expression)¶ test for a Boolean value
Parameters: expression – an expression IsBoolean returns True if the argument is of a boolean type. This means it has to be either True, False, or an expression involving functions that return a boolean result, e.g. {=}, {>}, {<}, {>=}, {<=}, {!=}, {And}, {Not}, {Or}.
Example: In> IsBoolean(a) Out> False; In> IsBoolean(True) Out> True; In> IsBoolean(a And b) Out> True;
-
IsNegativeNumber
(n)¶ test for a negative number
Parameters: n – number to test {IsNegativeNumber(n)} evaluates to
True
if $n$ is (strictly) negative, i.e. if $n<0$. If {n} is not a number, the functions returnFalse
.Example: In> IsNegativeNumber(6); Out> False; In> IsNegativeNumber(-2.5); Out> True;
-
IsNegativeInteger
(n)¶ test for a negative integer
Parameters: n – integer to test This function tests whether the integer {n} is (strictly) negative. The negative integers are -1, -2, -3, -4, -5, etc. If {n} is not a integer, the function returns
False
.Example: In> IsNegativeInteger(31); Out> False; In> IsNegativeInteger(-2); Out> True;
-
IsPositiveNumber
(n)¶ test for a positive number
Parameters: n – number to test {IsPositiveNumber(n)} evaluates to
True
if $n$ is (strictly) positive, i.e. if $n>0$. If {n} is not a number the function returnsFalse
.Example: In> IsPositiveNumber(6); Out> True; In> IsPositiveNumber(-2.5); Out> False;
-
IsPositiveInteger
(n)¶ test for a positive integer
Parameters: n – integer to test This function tests whether the integer {n} is (strictly) positive. The positive integers are 1, 2, 3, 4, 5, etc. If {n} is not a integer, the function returns
False
.Example: In> IsPositiveInteger(31); Out> True; In> IsPositiveInteger(-2); Out> False;
-
IsNotZero
(n)¶ test for a nonzero number
Parameters: n – number to test {IsNotZero(n)} evaluates to
True
if {n} is not zero. In case {n} is not a number, the function returnsFalse
.Example: In> IsNotZero(3.25); Out> True; In> IsNotZero(0); Out> False;
-
IsNonZeroInteger
(n)¶ test for a nonzero integer
Parameters: n – integer to test This function tests whether the integer {n} is not zero. If {n} is not an integer, the result is
False
.Example: In> IsNonZeroInteger(0) Out> False; In> IsNonZeroInteger(-2) Out> True;
See also
-
IsInfinity
(expr)¶ test for an infinity
Parameters: expr – expression to test This function tests whether {expr} is an infinity. This is only the case if {expr} is either {Infinity} or {-Infinity}.
Example: In> IsInfinity(10^1000); Out> False; In> IsInfinity(-Infinity); Out> True;
See also
Integer()
-
IsPositiveReal
(expr)¶ test for a numerically positive value
Parameters: expr – expression to test This function tries to approximate “expr” numerically. It returns
True
if this approximation is positive. In case no approximation can be found, the function returnsFalse
. Note that round-off errors may cause incorrect results.Example: In> IsPositiveReal(Sin(1)-3/4); Out> True; In> IsPositiveReal(Sin(1)-6/7); Out> False; In> IsPositiveReal(Exp(x)); Out> False; The last result is because {Exp(x)} cannot be numerically approximated if {x} is not known. Hence Yacas can not determine the sign of this expression.
See also
-
IsNegativeReal
(expr)¶ test for a numerically negative value
Parameters: expr – expression to test This function tries to approximate {expr} numerically. It returns
True
if this approximation is negative. In case no approximation can be found, the function returnsFalse
. Note that round-off errors may cause incorrect results.Example: In> IsNegativeReal(Sin(1)-3/4); Out> False; In> IsNegativeReal(Sin(1)-6/7); Out> True; In> IsNegativeReal(Exp(x)); Out> False; The last result is because {Exp(x)} cannot be numerically approximated if {x} is not known. Hence Yacas can not determine the sign of this expression.
See also
-
IsConstant
(expr)¶ test for a constant
Parameters: expr – some expression {IsConstant} returns
True
if the expression is some constant or a function with constant arguments. It does this by checking that no variables are referenced in the expression. {Pi} is considered a constant.Example: In> IsConstant(Cos(x)) Out> False; In> IsConstant(Cos(2)) Out> True; In> IsConstant(Cos(2+x)) Out> False;
See also
IsNumber()
,IsInteger()
,VarList()
-
IsGaussianInteger
(z)¶ - test for a Gaussian integer
Parameters: z – a complex or real number This function returns
True
if the argument is a Gaussian integer andFalse
otherwise. A Gaussian integer is a generalization of integers into the complex plane. A complex number $a+b*I$ is a Gaussian integer if and only if $a$ and $b$ are integers.Example: In> IsGaussianInteger(5) Out> True; In> IsGaussianInteger(5+6*I) Out> True; In> IsGaussianInteger(1+2.5*I) Out> False;
See also
-
MatchLinear
(x, expr)¶ match an expression to a polynomial of degree one in a variable
Parameters: - x – variable to express the univariate polynomial in
- expr – expression to match
{MatchLinear} tries to match an expression to a linear (degree less than two) polynomial. The function returns
True
if it could match, and it stores the resulting coefficients in the variables “{a}” and “{b}” as a side effect. The function calling this predicate should declare local variables “{a}” and “{b}” for this purpose. {MatchLinear} tries to match to constant coefficients which don’t depend on the variable passed in, trying to find a form “{a*x+b}” with “{a}” and “{b}” not depending on {x} if {x} is given as the variable.Example: In> MatchLinear(x,(R+1)*x+(T-1)) Out> True; In> {a,b}; Out> {R+1,T-1}; In> MatchLinear(x,Sin(x)*x+(T-1)) Out> False;
See also
-
HasExpr
(expr, x)¶ check for expression containing a subexpression
Parameters: - expr – an expression
- x – a subexpression to be found
- list – list of function atoms to be considered “transparent”
The command {HasExpr} returns
True
if the expression {expr} contains a literal subexpression {x}. The expression is recursively traversed. The command {HasExprSome} does the same, except it only looks at arguments of a given {list} of functions. All other functions become “opaque” (as if they do not contain anything). {HasExprArith} is defined through {HasExprSome} to look only at arithmetic operations {+}, {-}, {*}, {/}. Note that since the operators “{+}” and “{-}” are prefix as well as infix operators, it is currently required to use {Atom(“+”)} to obtain the unevaluated atom “{+}”.Example: In> HasExpr(x+y*Cos(Ln(z)/z), z) Out> True; In> HasExpr(x+y*Cos(Ln(z)/z), Ln(z)) Out> True; In> HasExpr(x+y*Cos(Ln(z)/z), z/Ln(z)) Out> False; In> HasExprArith(x+y*Cos(Ln(x)/x), z) Out> False; In> HasExprSome({a+b*2,c/d},c/d,{List}) Out> True; In> HasExprSome({a+b*2,c/d},c,{List}) Out> False;
See also
-
HasFunc
(expr, func)¶ check for expression containing a function
Parameters: - expr – an expression
- func – a function atom to be found
- list – list of function atoms to be considered “transparent”
The command {HasFunc} returns
True
if the expression {expr} contains a function {func}. The expression is recursively traversed. The command {HasFuncSome} does the same, except it only looks at arguments of a given {list} of functions. Arguments of all other functions become “opaque” (as if they do not contain anything). {HasFuncArith} is defined through {HasFuncSome} to look only at arithmetic operations {+}, {-}, {*}, {/}. Note that since the operators “{+}” and “{-}” are prefix as well as infix operators, it is currently required to use {Atom(“+”)} to obtain the unevaluated atom “{+}”.Example: In> HasFunc(x+y*Cos(Ln(z)/z), Ln) Out> True; In> HasFunc(x+y*Cos(Ln(z)/z), Sin) Out> False; In> HasFuncArith(x+y*Cos(Ln(x)/x), Cos) Out> True; In> HasFuncArith(x+y*Cos(Ln(x)/x), Ln) Out> False; In> HasFuncSome({a+b*2,c/d},/,{List}) Out> True; In> HasFuncSome({a+b*2,c/d},*,{List}) Out> False;
See also
Constants¶
Yacas-specific constants¶
-
%
previous result
%
evaluates to the previous result on the command line.%
is a global variable that is bound to the previous result from the command line. Using%
will evaluate the previous result. (This uses the functionality offered by the {SetGlobalLazyVariable} command).Typical examples are
Simplify(%)
andPrettyForm(%)
to simplify and show the result in a nice form respectively.Example: In> Taylor(x,0,5)Sin(x) Out> x-x^3/6+x^5/120; In> PrettyForm(%) 3 5 x x x - -- + --- 6 120
See also
-
EndOfFile
¶ end-of-file marker
End of file marker when reading from file. If a file contains the expression {EndOfFile;} the operation will stop reading the file at that point.
Mathematical constants¶
-
True
¶ -
False
¶ boolean constants representing true and false
True
andFalse
are typically a result of boolean expressions such as2 < 3
orTrue And False
.
See also
And()
, Or()
, Not()
-
Infinity
¶ constant representing mathematical infinity
Infinity
represents infinitely large values. It can be the result of certain calculations.Note that for most analytic functions yacas understands
Infinity
as a positive number. ThusInfinity*2
will returnInfinity
, anda < Infinity
will evaluate toTrue
.Example: In> 2*Infinity Out> Infinity; In> 2<Infinity Out> True;
-
Pi
¶ mathematical constant, \(\pi\)
The constant represents the number π. It is available symbolically as
Pi
or numerically throughN(Pi)
.This is a cached constant which is recalculated only when precision is increased.
Example: In> Sin(3*Pi/2) Out> -1; In> Pi+1 Out> Pi+1; In> N(Pi) Out> 3.14159265358979323846;
See also
-
Undefined
¶ constant signifying an undefined result
Undefined
is a token that can be returned by a function when it considers its input to be invalid or when no meaningful answer can be given. The result is then undefined.Most functions also return
Undefined
when evaluated on it.Example: In> 2*Infinity Out> Infinity; In> 0*Infinity Out> Undefined; In> Sin(Infinity); Out> Undefined; In> Undefined+2*Exp(Undefined); Out> Undefined;
See also
-
GoldenRatio
¶ the golden ratio
The constant represents the golden ratio
\[\phi := \frac{1+\sqrt{5}}{2} = 1.6180339887\ldots\]It is available symbolically as
GoldenRatio
or numerically throughN(GoldenRatio)
.This is a cached constant which is recalculated only when precision is increased.
Example: In> x:=GoldenRatio - 1 Out> GoldenRatio-1; In> N(x) Out> 0.6180339887; In> N(1/GoldenRatio) Out> 0.6180339887; In> V(N(GoldenRatio,20)); CachedConstant: Info: constant GoldenRatio is being recalculated at precision 20 Out> 1.6180339887498948482;
See also
-
Catalan
¶ Catalan’s constant
The constant represents the Catalan’s constant
\[G := \beta(2) = \sum_{n=0}^\infty\frac{-1^n}{(2n+1)^2}=0.9159655941\ldots\]It is available symbolically as
Catalan
or numerically throughN(Catalan)
.This is a cached constant which is recalculated only when precision is increased.
Example: In> N(Catalan) Out> 0.9159655941; In> DirichletBeta(2) Out> Catalan; In> V(N(Catalan,20)) CachedConstant: Info: constant Catalan is being recalculated at precision 20 Out> 0.91596559417721901505;
See also
-
gamma
¶ Euler–Mascheroni constant \(\gamma\)
The constant represents the Euler–Mascheroni constant
\[\gamma := \lim_{n\to\infty}\left(-\ln(n)+\sum_{k=1}^n\frac{1}{k}\right)=0.5772156649\ldots\]It is available symbolically as
gamma
or numerically throughN(gamma)
.This is a cached constant which is recalculated only when precision is increased.
Note
Euler’s \(\Gamma(x)\) function is the capitalized
Gamma()
in yacas.Example: In> gamma+Pi Out> gamma+Pi; In> N(gamma+Pi) Out> 3.7188083184; In> V(N(gamma,20)) CachedConstant: Info: constant gamma is being recalculated at precision 20 GammaConstNum: Info: used 56 iterations at working precision 24 Out> 0.57721566490153286061;
See also
Variables¶
-
var
:=
expr¶ -
var[i]
:=
expr -
varlist
:=
exprlist -
fn
:=
expr assign a variable or a list; define a function
var := expr {var1, var2, ...} := {expr1, expr2, ...} var[i] := expr fn(arg1, arg2, ...) := expr
Parameters: - var – atom, variable which should be assigned
- expr – expression to assign to the variable or body of function
- i – index (can be integer or string)
- fn – atom, name of a new function to define
- arg2 (arg1,) – atoms, names of arguments of the new function {fn}
The
:=()
operator can be used in a number of ways. In all cases, some sort of assignment or definition takes place. The first form is the most basic one. It evaluates the expression on the right-hand side and assigns it to the variable named on the left-hand side. The left-hand side is not evaluated. The evaluated expression is also returned. The second form is a small extension, which allows one to do multiple assignments. The first entry in the list on the right-hand side is assigned to the first variable mentioned in the left-hand side, the second entry on the right-hand side to the second variable on the left-hand side, etc. The list on the right-hand side must have at least as many entries as the list on the left-hand side. Any excess entries are silently ignored. The result of the expression is the list of values that have been assigned. The third form allows one to change an entry in the list. If the index “i” is an integer, the “i”-th entry in the list is changed to the expression on the right-hand side. It is assumed that the length of the list is at least “i”. If the index “i” is a string, then “var” is considered to be an associative list (sometimes called hash table), and the key “i” is paired with the value “exp”. In both cases, the right-hand side is evaluated before the assignment and the result of the assignment isTrue
. The last form defines a function. For example, the assignment {fn(x) := x^2} removes any rules previously associated with {fn(x)} and defines the rule {fn(_x) <– x^2}. Note that the left-hand side may take a different form if {fn} is defined to be a prefix, infix or bodied function. This case is special since the right-hand side is not evaluated immediately, but only when the function {fn} is used. If this takes time, it may be better to force an immediate evaluation with {Eval} (see the last example). If the expression on the right hand side begins with {Eval()}, then it <i>will</i> be evaluated before defining the new function. A variant of the function definition can be used to make a function accepting a variable number of arguments. The last argumentSimple assignment:
In> a := Sin(x) + 3; Out> Sin(x)+3; In> a; Out> Sin(x)+3;
Multiple assignments:
In> {a,b,c} := {1,2,3}; Out> {1,2,3}; In> a; Out> 1; In> b+c; Out> 5;
Assignment to a list:
In> xs := { 1,2,3,4,5 }; Out> {1,2,3,4,5}; In> xs[3] := 15; Out> True; In> xs; Out> {1,2,15,4,5};
Building an associative list:
In> alist := {}; Out> {}; In> alist["cherry"] := "red"; Out> True; In> alist["banana"] := "yellow"; Out> True; In> alist["cherry"]; Out> "red"; In> alist; Out> {{"banana","yellow"},{"cherry","red"}};
Defining a function:
In> f(x) := x^2; Out> True; In> f(3); Out> 9; In> f(Sin(a)); Out> Sin(a)^2;
Defining a function with variable number of arguments:
In> f(x, ...) := If(IsList(x),Sum(x),x); Out> True; In> f(2); Out> 2; In> f(1,2,3); Out> 6;
Defining a new infix operator:
In> Infix("*&*",10); Out> True; In> x1 *&* x2 := x1/x2 + x2/x1; Out> True; In> Sin(a) *&* Cos(a); Out> Tan(1)+Cos(1)/Sin(1); In> Clear(a); Out> True; In> Sin(a) *&* Exp(a); Out> Sin(a)/Exp(a)+Exp(a)/Sin(a);
In the following example, it may take some time to compute the Taylor expansion. This has to be done every time the function {f} is called:
In> f(a) := Taylor(x,0,25) Sin(x); Out> True; In> f(1); Out> x-x^3/6+x^5/120-x^7/5040+x^9/362880- x^11/39916800+x^13/6227020800-x^15/ 1307674368000+x^17/355687428096000-x^19/ 121645100408832000+x^21/51090942171709440000 -x^23/25852016738884976640000+x^25 /15511210043330985984000000; In> f(2); Out> x-x^3/6+x^5/120-x^7/5040+x^9/362880- x^11/39916800+x^13/6227020800-x^15 /1307674368000+x^17/355687428096000-x^19/ 121645100408832000+x^21/51090942171709440000 -x^23/25852016738884976640000+x^25/ 15511210043330985984000000;
The remedy is to evaluate the Taylor expansion immediately. Now the expansion is computed only once:
In> f(a) := Eval(Taylor(x,0,25) Sin(x)); Out> True; In> f(1); Out> x-x^3/6+x^5/120-x^7/5040+x^9/362880- x^11/39916800+x^13/6227020800-x^15/ 1307674368000+x^17/355687428096000-x^19/ 121645100408832000+x^21/51090942171709440000 -x^23/25852016738884976640000+x^25 /15511210043330985984000000; In> f(2); Out> x-x^3/6+x^5/120-x^7/5040+x^9/362880- x^11/39916800+x^13/6227020800-x^15 /1307674368000+x^17/355687428096000-x^19/ 121645100408832000+x^21/51090942171709440000 -x^23/25852016738884976640000+x^25/ 15511210043330985984000000;
-
Set
(var, exp)¶ assignment
Parameters: - var – variable which should be assigned
- exp – expression to assign to the variable
The expression “exp” is evaluated and assigned it to the variable named “var”. The first argument is not evaluated. The value True is returned. The statement {Set(var, exp)} is equivalent to {var := exp}, but the {:=} operator has more uses, e.g. changing individual entries in a list.
Example: In> Set(a, Sin(x)+3); Out> True; In> a; Out> Sin(x)+3;
-
Clear
(var, ...)¶ undo an assignment
Parameters: var – name of the variable to be cleared All assignments made to the variables listed as arguments are undone. From now on, all these variables remain unevaluated (until a subsequent assignment is made). The result of the expression is True.
Example: In> a := 5; Out> 5; In> a^2; Out> 25; In> Clear(a); Out> True; In> a^2; Out> a^2;
-
Local
(var, ...)¶ declare new local variables
Parameters: var – name of the variable to be declared as local All variables in the argument list are declared as local variables. The arguments are not evaluated. The value
True
is returned. By default, all variables in Yacas are global. This means that the variable has the same value everywhere. But sometimes it is useful to have a private copy of some variable, either to prevent the outside world from changing it or to prevent accidental changes to the outside world. This can be achieved by declaring the variable local. Now only expressions within theProg()
block (or its syntactic equivalent, the[]
block) can access and change it. Functions called within this block cannot access the local copy unless this is specifically allowed withUnFence()
.Example: In> a := 3; Out> 3; In> [ a := 4; a; ]; Out> 4; In> a; Out> 4; In> [ Local(a); a := 5; a; ]; Out> 5; In> a; Out> 4;
In the first block,
a
is not declared local and hence defaults to be a global variable. Indeed, changing the variable inside the block also changes the value ofa
outside the block. However, in the second blocka
is defined to be local and now the value outside the block stays the same, even thougha
is assigned the value 5 inside the block.See also
LocalSymbols()
,Prog()
,[]()
,UnFence()
-
var
++
¶ increment variable
Parameters: var – variable to increment The variable with name
var
is incremented, i.e. the number 1 is added to it. The expressionx++
is equivalent to the assignmentx := x + 1
, except that the assignment returns the new value ofx
whilex++
always returnsTrue
. In this respect, Yacas’++
differs from the corresponding operator in the programming language C.Example: In> x := 5; Out> 5; In> x++; Out> True; In> x; Out> 6;
-
var
--
¶ decrement variable
Parameters: var – variable to decrement The variable with name
var
is decremented, i.e. the number 1 is subtracted from it. The expressionx--
is equivalent to the assignmentx := x - 1
, except that the assignment returns the new value ofx
whilex--
always returnsTrue
. In this respect, Yacas’--
differs from the corresponding operator in the programming language C.Example: In> x := 5; Out> 5; In> x--; Out> True; In> x; Out> 4;
-
Object
("pred", expr)¶ create an incomplete type
Parameters: - pred – name of the predicate to apply
- expr – expression on which
pred
should be applied
This function returns “obj” as soon as “pred” returns
True
when applied on “obj”. This is used to declare so-called incomplete types.Example: In> a := Object("IsNumber", x); Out> Object("IsNumber",x); In> Eval(a); Out> Object("IsNumber",x); In> x := 5; Out> 5; In> Eval(a); Out> 5;
See also
-
SetGlobalLazyVariable
(var, value)¶ global variable is to be evaluated lazily
Parameters: - var – variable (held argument)
- value – value to be set to (evaluated before it is assigned)
SetGlobalLazyVariable()
enforces that a global variable will re-evaluate when used. This functionality doesn’t survive ifClear(var)
is called afterwards. Places where this is used include the global variables%
andI
. The use of lazy in the name stems from the concept of lazy evaluation. The object the global variable is bound to will only be evaluated when called. The {SetGlobalLazyVariable} property only holds once: after that, the result of evaluation is stored in the global variable, and it won’t be reevaluated again:In> SetGlobalLazyVariable(a,Hold(Taylor(x,0,30)Sin(x))) Out> True
Then the first time you call
a
it evaluatesTaylor(...)
and assigns the result toa
. The next time you calla
it immediately returns the result.SetGlobalLazyVariable()
is called for%
each time%
changes. The following example demonstrates the sequence of execution:In> SetGlobalLazyVariable(test,Hold(Write("hello"))) Out> True
The text “hello” is not written out to screen yet. However, evaluating the variable
test
forces the expression to be evaluated:In> test = "hello" Out> True
Example: In> Set(a,Hold(2+3)) Out> True In> a Out> 2+3 In> SetGlobalLazyVariable(a,Hold(2+3)) Out> True In> a Out> 5
-
UniqueConstant
()¶ create a unique identifier
This function returns a unique constant atom each time you call it. The atom starts with a C character, and a unique number is appended to it.
Example: In> UniqueConstant() Out> C9 In> UniqueConstant() Out> C10
See also
-
LocalSymbols
(var1, var2, ...) expr¶ create unique local symbols with given prefix
Parameters: - var2, .. (var1,) – atoms, symbols to be made local
- expr – expression to execute
Given the symbols passed as the first arguments to
LocalSymbols()
, a set of unique local symbols will be created, typically of the form$<symbol><number>
, wheresymbol
was the symbol entered by the user, andnumber
is a unique number. This scheme is used to ensure that a generated symbol can not accidentally be entered by a user. This is useful in cases where a guaranteed free variable is needed, for example, in the macro-like functions (For()
,While()
etc.).Example: In> LocalSymbols(a,b)a+b Out> $a6+ $b6;
See also
Input/output and plotting¶
This chapter contains commands to use for input and output and plotting. All output commands write to the same destination stream, called the “current output”. This is initially the screen, but may be redirected by some commands. Similarly, most input commands read from the “current input” stream, which can also be redirected. The exception to this rule are the commands for reading script files, which simply read a specified file.
-
FullForm
(expr)¶ print an expression in LISP-format
Parameters: expr – expression to be printed in LISP-format Evaluates “expr”, and prints it in LISP-format on the current output. It is followed by a newline. The evaluated expression is also returned. This can be useful if you want to study the internal representation of a certain expression.
Example: In> FullForm(a+b+c); (+ (+ a b )c ) Out> a+b+c; In> FullForm(2*I*b^2); (* (Complex 0 2 )(^ b 2 )) Out> Complex(0,2)*b^2; The first example shows how the expression {a+b+c} is internally represented. In the second example, {2*I} is first evaluated to {Complex(0,2)} before the expression is printed.
See also
LispRead()
,Listify()
,Unlist()
-
Echo
(item)¶ high-level printing routine
Parameters: - item – the item to be printed
- list – a list of items to be printed
If passed a single item, {Echo} will evaluate it and print it to the current output, followed by a newline. If {item} is a string, it is printed without quotation marks. If there is one argument, and it is a list, {Echo} will print all the entries in the list subsequently to the current output, followed by a newline. Any strings in the list are printed without quotation marks. All other entries are followed by a space. {Echo} can be called with a variable number of arguments, they will all be printed, followed by a newline. {Echo} always returns
True
.Example: In> Echo(5+3); 8 Out> True; In> Echo({"The square of two is ", 2*2}); The square of two is 4 Out> True; In> Echo("The square of two is ", 2*2); The square of two is 4 Out> True; Note that one must use the second calling format if one wishes to print a list: In> Echo({a,b,c}); a b c Out> True; In> Echo({{a,b,c}}); {a,b,c} Out> True;
See also
-
PrettyForm
(expr)¶ print an expression nicely with ASCII art
Parameters: expr – an expression {PrettyForm} renders an expression in a nicer way, using ascii art. This is generally useful when the result of a calculation is more complex than a simple number.
Example: In> Taylor(x,0,9)Sin(x) Out> x-x^3/6+x^5/120-x^7/5040+x^9/362880; In> PrettyForm(%) 3 5 7 9 x x x x x - -- + --- - ---- + ------ 6 120 5040 362880 Out> True;
See also
EvalFormula()
,PrettyPrinter'Set()
-
EvalFormula
(expr)¶ print an evaluation nicely with ASCII art
Parameters: expr – an expression Show an evaluation in a nice way, using {PrettyPrinter’Set} to show ‘input = output’.
Example: In> EvalFormula(Taylor(x,0,7)Sin(x)) 3 5 x x Taylor( x , 0 , 5 , Sin( x ) ) = x - -- + --- 6 120
See also
-
TeXForm
(expr)¶ export expressions to $LaTeX$
Parameters: expr – an expression to be exported {TeXForm} returns a string containing a $LaTeX$ representation of the Yacas expression {expr}. Currently the exporter handles most expression types but not all.
-
CForm
(expr)¶ export expression to C++ code
Parameters: expr – expression to be exported {CForm} returns a string containing C++ code that attempts to implement the Yacas expression {expr}. Currently the exporter handles most expression types but not all.
-
IsCFormable
(expr)¶ check possibility to export expression to C++ code
Parameters: - expr – expression to be exported (this argument is not evaluated)
- funclist – list of “allowed” function atoms
{IsCFormable} returns
True
if the Yacas expression {expr} can be exported into C++ code. This is a check whether the C++ exporter {CForm} can be safely used on the expression. A Yacas expression is considered exportable if it contains only functions that can be translated into C++ (e.g. {UnList} cannot be exported). All variables and constants are considered exportable. The verbose option prints names of functions that are not exportable. The second calling format of {IsCFormable} can be used to “allow” certain function names that will be available in the C++ code.Example: In> IsCFormable(Sin(a1)+2*Cos(b1)) Out> True; In> V(IsCFormable(1+func123(b1))) IsCFormable: Info: unexportable function(s): func123 Out> False; This returned :data:`False` because the function {func123} is not available in C++. We can explicitly allow this function and then the expression will be considered exportable: In> IsCFormable(1+func123(b1), {func123}) Out> True;
-
Write
(expr, ...)¶ low-level printing routine
Parameters: expr – expression to be printed The expression “expr” is evaluated and written to the current output. Note that Write accept an arbitrary number of arguments, all of which are written to the current output (see second example). {Write} always returns
True
.Example: In> Write(1); 1Out> True; In> Write(1,2); 1 2Out> True; Write does not write a newline, so the {Out>} prompt immediately follows the output of {Write}.
See also
-
WriteString
(string)¶ low-level printing routine for strings
Parameters: string – the string to be printed The expression “string” is evaluated and written to the current output without quotation marks. The argument should be a string. WriteString always returns True.
Example: In> Write("Hello, world!"); "Hello, world!"Out> True; In> WriteString("Hello, world!"); Hello, world!Out> True; This example clearly shows the difference between Write and WriteString. Note that Write and WriteString do not write a newline, so the {Out>} prompt immediately follows the output.
-
Space
()¶ print one or more spaces
Parameters: nr – the number of spaces to print The command {Space()} prints one space on the current output. The second form prints {nr} spaces on the current output. The result is always True.
Example: In> Space(5); Out> True;
-
NewLine
()¶ print one or more newline characters
Parameters: nr – the number of newline characters to print The command {NewLine()} prints one newline character on the current output. The second form prints “nr” newlines on the current output. The result is always True.
Example: In> NewLine(); Out> True;
-
FromFile(name) body
connect current input to a file
Parameters: - name – string, the name of the file to read
- body – expression to be evaluated
The current input is connected to the file “name”. Then the expression “body” is evaluated. If some functions in “body” try to read from current input, they will now read from the file “name”. Finally, the file is closed and the result of evaluating “body” is returned.
Example: Suppose that the file {foo} contains 2 + 5; Then we can have the following dialogue: In> FromFile("foo") res := Read(); Out> 2+5; In> FromFile("foo") res := ReadToken(); Out> 2;
See also
ToFile()
,FromString()
,Read()
,ReadToken()
-
FromString(str) body;
connect current input to a string
Parameters: - str – a string containing the text to parse
- body – expression to be evaluated
The commands in “body” are executed, but everything that is read from the current input is now read from the string “str”. The result of “body” is returned.
Example: In> FromString("2+5; this is never read") \ res := Read(); Out> 2+5; In> FromString("2+5; this is never read") \ res := Eval(Read()); Out> 7;
See also
ToString()
,FromFile()
,Read()
,ReadToken()
-
ToFile(name) body
connect current output to a file
Parameters: - name – string, the name of the file to write the result to
- body – expression to be evaluated
The current output is connected to the file “name”. Then the expression “body” is evaluated. Everything that the commands in “body” print to the current output, ends up in the file “name”. Finally, the file is closed and the result of evaluating “body” is returned. If the file is opened again, the old contents will be overwritten. This is a limitation of {ToFile}: one cannot append to a file that has already been created.
Example: Here is how one can create a file with C code to evaluate an expression: In> ToFile("expr1.c") WriteString( CForm(Sqrt(x-y)*Sin(x)) ); Out> True; The file {expr1.c} was created in the current working directory and it contains the line sqrt(x-y)*sin(x) As another example, take a look at the following command: In> [ Echo("Result:"); \ PrettyForm(Taylor(x,0,9) Sin(x)); ]; Result: 3 5 7 9 x x x x x - -- + --- - ---- + ------ 6 120 5040 362880 Out> True; Now suppose one wants to send the output of this command to a file. This can be achieved as follows: In> ToFile("out") [ Echo("Result:"); \ PrettyForm(Taylor(x,0,9) Sin(x)); ]; Out> True; After this command the file {out} contains: Result: 3 5 7 9 x x x x x - -- + --- - ---- + ------ 6 120 5040 362880
See also
FromFile()
,ToString()
,Echo()
,Write()
,WriteString()
,PrettyForm()
,Taylor()
-
ToString() body
connect current output to a string
Parameters: body – expression to be evaluated The commands in “body” are executed. Everything that is printed on the current output, by {Echo} for instance, is collected in a string and this string is returned.
Example: In> str := ToString() [ WriteString( \ "The square of 8 is "); Write(8^2); ]; Out> "The square of 8 is 64";
See also
FromFile()
,ToString()
,Echo()
,Write()
,WriteString()
-
Read
()¶ read an expression from current input
Read an expression from the current input, and return it unevaluated. When the end of an input file is encountered, the token atom {EndOfFile} is returned.
Example: In> FromString("2+5;") Read(); Out> 2+5; In> FromString("") Read(); Out> EndOfFile;
See also
FromFile()
,FromString()
,LispRead()
,ReadToken()
,Write()
-
ToStdout() body
select initial output stream for output
Parameters: body – expression to be evaluated When using {ToString} or {ToFile}, it might happen that something needs to be written to the standard default initial output (typically the screen). {ToStdout} can be used to select this stream.
-
ReadCmdLineString
(prompt)¶ read an expression from command line and return in string
Parameters: prompt – string representing the prompt shown on screen This function allows for interactive input similar to the command line. When using this function, the history from the command line is also available. The result is returned in a string, so it still needs to be parsed. This function will typically be used in situations where one wants a custom read-eval-print loop.
Example: The following defines a function that when invoked keeps asking for an expression (the <i>read</i> step), and then takes the derivative of it (the <i>eval</i> step) and then uses PrettyForm to display the result (the <i>print</i> step). In> ReEvPr() := \ In> While(True) [ \ In> PrettyForm(Deriv(x) \ In> FromString(ReadCmdLineString("Deriv> "):";")Read()); \ In> ]; Out> True; Then one can invoke the command, from which the following interaction might follow: In> ReEvPr() Deriv> Sin(a^2*x/b) / 2 \ | a * x | 2 Cos| ------ | * a * b \ b / ---------------------- 2 b Deriv> Sin(x) Cos( x ) Deriv>
See also
-
LispRead
()¶ read expressions in LISP syntax
The function {LispRead} reads an expression in the LISP syntax from the current input, and returns it unevaluated. When the end of an input file is encountered, the special token atom {EndOfFile} is returned. The Yacas expression {a+b} is written in the LISP syntax as {(+ a b)}. The advantage of this syntax is that it is less ambiguous than the infix operator grammar that Yacas uses by default.
Example: In> FromString("(+ a b)") LispRead(); Out> a+b; In> FromString("(List (Sin x) (- (Cos x)))") \ LispRead(); Out> {Sin(x),-Cos(x)}; In> FromString("(+ a b)")LispRead() Out> a+b;
See also
FromFile()
,FromString()
,Read()
,ReadToken()
,FullForm()
,LispReadListed()
-
LispReadListed
()¶ read expressions in LISP syntax
The function {LispReadListed} reads a LISP expression and returns it in a list, instead of the form usual to Yacas (expressions). The result can be thought of as applying {Listify} to {LispRead}. The function {LispReadListed} is more useful for reading arbitrary LISP expressions, because the first object in a list can be itself a list (this is never the case for Yacas expressions where the first object in a list is always a function atom).
Example: In> FromString("(+ a b)")LispReadListed() Out> {+,a,b};
See also
FromFile()
,FromString()
,Read()
,ReadToken()
,FullForm()
,LispRead()
-
ReadToken
()¶ read a token from current input
Read a token from the current input, and return it unevaluated. The returned object is a Yacas atom (not a string). When the end of an input file is encountered, the token atom {EndOfFile} is returned. A token is for computer languages what a word is for human languages: it is the smallest unit in which a command can be divided, so that the semantics (that is the meaning) of the command is in some sense a combination of the semantics of the tokens. Hence {a := foo} consists of three tokens, namely {a}, {:=}, and {foo}. The parsing of the string depends on the syntax of the language. The part of the kernel that does the parsing is the “tokenizer”. Yacas can parse its own syntax (the default tokenizer) or it can be instructed to parse XML or C++ syntax using the directives {DefaultTokenizer} or {XmlTokenizer}. Setting a tokenizer is a global action that affects all {ReadToken} calls.
Example: In> FromString("a := Sin(x)") While \ ((tok := ReadToken()) != EndOfFile) \ Echo(tok); a := Sin ( x ) Out> True; We can read some junk too: In> FromString("-$3")ReadToken(); Out> -$; The result is an atom with the string representation {-$}. Yacas assumes that {-$} is an operator symbol yet to be defined. The "{3}" will be in the next token. (The results will be different if a non-default tokenizer is selected.)
See also
FromFile()
,FromString()
,Read()
,LispRead()
,DefaultTokenizer()
-
Load
(name)¶ evaluate all expressions in a file
Parameters: name – string, name of the file to load The file “name” is opened. All expressions in the file are read and evaluated. {Load} always returns {true}.
See also
Use()
,DefLoad()
,DefaultDirectory()
,FindFile()
-
Use
(name)¶ load a file, but not twice
Parameters: name – string, name of the file to load If the file “name” has been loaded before, either by an earlier call to {Use} or via the {DefLoad} mechanism, nothing happens. Otherwise all expressions in the file are read and evaluated. {Use} always returns {true}. The purpose of this function is to make sure that the file will at least have been loaded, but is not loaded twice.
-
DefLoad
(name)¶ load a {.def} file
Parameters: name – string, name of the file (without {.def} suffix) The suffix {.def} is appended to “name” and the file with this name is loaded. It should contain a list of functions, terminated by a closing brace } (the end-of-list delimiter). This tells the system to load the file “name” as soon as the user calls one of the functions named in the file (if not done so already). This allows for faster startup times, since not all of the rules databases need to be loaded, just the descriptions on which files to load for which functions.
-
FindFile
(name)¶ find a file in the current path
Parameters: name – string, name of the file or directory to find The result of this command is the full path to the file that would be opened when the command {Load(name)} would be invoked. This means that the input directories are subsequently searched for a file called “name”. If such a file is not found, {FindFile} returns an empty string. {FindFile(“”)} returns the name of the default directory (the first one on the search path).
See also
Load()
,DefaultDirectory()
-
PatchLoad
(name)¶ execute commands between {<?} and {?>} in file
Parameters: name – string, name of the file to “patch” {PatchLoad} loads in a file and outputs the contents to the current output. The file can contain blocks delimited by {<?} and {?>} (meaning “Yacas Begin” and “Yacas End”). The piece of text between such delimiters is treated as a separate file with Yacas instructions, which is then loaded and executed. All output of write statements in that block will be written to the same current output. This is similar to the way PHP works. You can have a static text file with dynamic content generated by Yacas.
See also
-
Nl
()¶ the newline character
This function returns a string with one element in it, namely a newline character. This may be useful for building strings to send to some output in the end. Note that the second letter in the name of this command is a lower case {L} (from “line”).
Example: In> WriteString("First line" : Nl() : "Second line" : Nl()); First line Second line Out> True;
See also
-
V
(expression)¶ set verbose output mode
Parameters: expression – expression to be evaluated in verbose mode The function {V(expression)} will evaluate the expression in verbose mode. Various parts of Yacas can show extra information about the work done while doing a calculation when using {V}. In verbose mode, {InVerboseMode()} will return
True
, otherwise it will returnFalse
.Example: In> OldSolve({x+2==0},{x}) Out> {{-2}}; In> V(OldSolve({x+2==0},{x})) Entering OldSolve From x+2==0 it follows that x = -2 x+2==0 simplifies to True Leaving OldSolve Out> {{-2}}; In> InVerboseMode() Out> False In> V(InVerboseMode()) Out> True
See also
-
InVerboseMode
()¶ check for verbose output mode
In verbose mode, {InVerboseMode()} will return
True
, otherwise it will returnFalse
.Example: In> InVerboseMode() Out> False In> V(InVerboseMode()) Out> True
See also
Echo()
,N()
,OldSolve()
,V()
-
Plot2D
(f(x))¶ adaptive two-dimensional plotting
Parameters: - f(x) – unevaluated expression containing one variables (function to be plotted)
- list – list of functions to plot
- {b (a},) – numbers, plotting range in the $x$ coordinate
- option – atom, option name
- value – atom, number or string (value of option)
The routine {Plot2D} performs adaptive plotting of one or several functions of one variable in the specified range. The result is presented as a line given by the equation $y=f(x)$. Several functions can be plotted at once. Various plotting options can be specified. Output can be directed to a plotting program (the default is to use {data}) to a list of values. The function parameter {f(x)} must evaluate to a Yacas expression containing at most one variable. (The variable does not have to be called {x}.) Also, {N(f(x))} must evaluate to a real (not complex) numerical value when given a numerical value of the argument {x}. If the function {f(x)} does not satisfy these requirements, an error is raised. Several functions may be specified as a list and they do not have to depend on the same variable, for example, {{f(x), g(y)}}. The functions will be plotted on the same graph using the same coordinate ranges. If you have defined a function which accepts a number but does not accept an undefined variable, {Plot2D} will fail to plot it. Use {NFunction} to overcome this difficulty. Data files are created in a temporary directory {/tmp/plot.tmp/} unless otherwise requested. File names and other information is printed if {InVerboseMode()} returns
True
on using {V()}. The current algorithm uses Newton-Cotes quadratures and some heuristics for error estimation (see <yacasdoc://Algo/3/1/>). The initial grid of {points+1} points is refined between any grid points $a$, $b$ if the integral $Integrate(x,a,b)f(x)$ is not approximated to the given precision by the existing grid. Default plotting range is {-5:5}. Range can also be specified as {x= -5:5} (note the mandatory space separating “{=}” and “{-}”); currently the variable name {x} is ignored in this case. Options are of the form {option=value}. Currently supported option names are: “points”, “precision”, “depth”, “output”, “filename”, “yrange”. Option values are either numbers or special unevaluated atoms such as {data}. If you need to use the names of these atoms in your script, strings can be used. Several option/value pairs may be specified (the function {Plot2D} has a variable number of arguments).- {yrange}: the range of ordinates to use for plotting, e.g. {yrange=0:20}. If no range is specified, the default is usually to leave the choice to the plotting backend.
- {points}: initial number of points (default 23) – at least that many points will be plotted. The initial grid of this many points will be adaptively refined.
- {precision}: graphing precision (default $10^(-6)$). This is interpreted as the relative precision of computing the integral of $f(x)-Min(f(x))$ using the grid points. For a smooth, non-oscillating function this value should be roughly 1/(number of screen pixels in the plot).
- {depth}: max. refinement depth, logarithmic (default 5) – means there will be at most $2^depth$ extra points per initial grid point.
- {output}: name of the plotting backend. Supported names: {data} (default). The {data} backend will return the data as a list of pairs such as {{{x1,y1}, {x2,y2}, ...}}.
- {filename}: specify name of the created data file. For example: {filename=”data1.txt”}. The default is the name {“output.data”}. Note that if several functions are plotted, the data files will have a number appended to the given name, for example {data.txt1}, {data.txt2}.
Other options may be supported in the future.
The current implementation can deal with a singularity within the plotting range only if the function {f(x)} returns {Infinity}, {-Infinity} or {Undefined} at the singularity. If the function {f(x)} generates a numerical error and fails at a singularity, {Plot2D} will fail if one of the grid points falls on the singularity. (All grid points are generated by bisection so in principle the endpoints and the {points} parameter could be chosen to avoid numerical singularities.)
See also
-
Plot3DS
(f(x, y))¶ three-dimensional (surface) plotting
Parameters: - f(x,y) – unevaluated expression containing two variables (function to be plotted)
- list – list of functions to plot
- {b}, {c}, {d (a},) – numbers, plotting ranges in the $x$ and $y$ coordinates
- option – atom, option name
- value – atom, number or string (value of option)
The routine {Plot3DS} performs adaptive plotting of a function of two variables in the specified ranges. The result is presented as a surface given by the equation $z=f(x,y)$. Several functions can be plotted at once, by giving a list of functions. Various plotting options can be specified. Output can be directed to a plotting program (the default is to use {data}), to a list of values. The function parameter {f(x,y)} must evaluate to a Yacas expression containing at most two variables. (The variables do not have to be called {x} and {y}.) Also, {N(f(x,y))} must evaluate to a real (not complex) numerical value when given numerical values of the arguments {x}, {y}. If the function {f(x,y)} does not satisfy these requirements, an error is raised. Several functions may be specified as a list but they have to depend on the same symbolic variables, for example, {{f(x,y), g(y,x)}}, but not {{f(x,y), g(a,b)}}. The functions will be plotted on the same graph using the same coordinate ranges. If you have defined a function which accepts a number but does not accept an undefined variable, {Plot3DS} will fail to plot it. Use {NFunction} to overcome this difficulty. Data files are created in a temporary directory {/tmp/plot.tmp/} unless otherwise requested. File names and other information is printed if {InVerboseMode()} returns
True
on using {V()}. The current algorithm uses Newton-Cotes cubatures and some heuristics for error estimation (see <yacasdoc://Algo/3/1/>). The initial rectangular grid of {xpoints+1}*{ypoints+1} points is refined within any rectangle where the integral of $f(x,y)$ is not approximated to the given precision by the existing grid. Default plotting range is {-5:5} in both coordinates. A range can also be specified with a variable name, e.g. {x= -5:5} (note the mandatory space separating “{=}” and “{-}”). The variable name {x} should be the same as that used in the function {f(x,y)}. If ranges are not given with variable names, the first variable encountered in the function {f(x,y)} is associated with the first of the two ranges. Options are of the form {option=value}. Currently supported option names are “points”, “xpoints”, “ypoints”, “precision”, “depth”, “output”, “filename”, “xrange”, “yrange”, “zrange”. Option values are either numbers or special unevaluated atoms such as {data}. If you need to use the names of these atoms in your script, strings can be used (e.g. {output=”data”}). Several option/value pairs may be specified (the function {Plot3DS} has a variable number of arguments).- {xrange}, {yrange}: optionally override coordinate ranges. Note that {xrange} is always the first variable and {yrange} the second variable, regardless of the actual variable names.
- {zrange}: the range of the $z$ axis to use for plotting, e.g. {zrange=0:20}. If no range is specified, the default is usually to leave the choice to the plotting backend. Automatic choice based on actual values may give visually inadequate plots if the function has a singularity.
- {points}, {xpoints}, {ypoints}: initial number of points (default 10 each) – at least that many points will be plotted in each coordinate. The initial grid of this many points will be adaptively refined. If {points} is specified, it serves as a default for both {xpoints} and {ypoints}; this value may be overridden by {xpoints} and {ypoints} values.
- {precision}: graphing precision (default $0.01$). This is interpreted as the relative precision of computing the integral of $f(x,y)-Min(f(x,y))$ using the grid points. For a smooth, non-oscillating function this value should be roughly 1/(number of screen pixels in the plot).
- {depth}: max. refinement depth, logarithmic (default 3) – means there will be at most $2^depth$ extra points per initial grid point (in each coordinate).
- {output}: name of the plotting backend. Supported names: {data} (default). The {data} backend will return the data as a list of triples such as {{{x1, y1, z1}, {x2, y2, z2}, ...}}.
Other options may be supported in the future.
The current implementation can deal with a singularity within the plotting range only if the function {f(x,y)} returns {Infinity}, {-Infinity} or {Undefined} at the singularity. If the function {f(x,y)} generates a numerical error and fails at a singularity, {Plot3DS} will fail only if one of the grid points falls on the singularity. (All grid points are generated by bisection so in principle the endpoints and the {xpoints}, {ypoints} parameters could be chosen to avoid numerical singularities.)
The {filename} option is optional if using graphical backends, but can be used to specify the location of the created data file.
Example: In> Plot3DS(a*b^2) Out> True; In> V(Plot3DS(Sin(x)*Cos(y),x=0:20, y=0:20,depth=3)) CachedConstant: Info: constant Pi is being recalculated at precision 10 CachedConstant: Info: constant Pi is being recalculated at precision 11 Plot3DS: using 1699 points for function Sin(x)*Cos(y) Plot3DS: max. used 8 subdivisions for Sin(x)*Cos(y) Plot3DS'datafile: created file '/tmp/plot.tmp/data1' Out> True;
See also
-
XmlExplodeTag
(xmltext)¶ convert XML strings to tag objects
Parameters: xmltext – string containing some XML tokens {XmlExplodeTag} parses the first XML token in {xmltext} and returns a Yacas expression. The following subset of XML syntax is supported currently:
- {<TAG [options]>} – an opening tag
- {</TAG [options]>} – a closing tag
- {<TAG [options] />} – an open/close tag
- plain (non-tag) text
The tag options take the form {paramname=”value”}.
If given an XML tag, {XmlExplodeTag} returns a structure of the form {XmlTag(name,params,type)}. In the returned object, {name} is the (capitalized) tag name, {params} is an assoc list with the options (key fields capitalized), and type can be either “Open”, “Close” or “OpenClose”.
If given a plain text string, the same string is returned.
Example: In> XmlExplodeTag("some plain text") Out> "some plain text"; In> XmlExplodeTag("<a name=\"blah blah\" align=\"left\">") Out> XmlTag("A",{{"ALIGN","left"}, {"NAME","blah blah"}},"Open"); In> XmlExplodeTag("</p>") Out> XmlTag("P",{},"Close"); In> XmlExplodeTag("<br/>") Out> XmlTag("BR",{},"OpenClose");
See also
-
XmlTokenizer
()¶ select the default syntax tokenizer for parsing the input
A “tokenizer” is an internal routine in the kernel that parses the input into Yacas expressions. This affects all input typed in by a user at the prompt and also the input redirected from files or strings using {FromFile} and {FromString} and read using {Read} or {ReadToken}. The Yacas environment currently supports some experimental tokenizers for various syntaxes. {DefaultTokenizer} switches to the tokenizer used for default Yacas syntax. {XmlTokenizer} switches to an XML syntax. Note that setting the tokenizer is a global side effect. One typically needs to switch back to the default tokenizer when finished reading the special syntax. Care needs to be taken when kernel errors are raised during a non-default tokenizer operation (as with any global change in the environment). Errors need to be caught with the {TrapError} function. The error handler code should re-instate the default tokenizer, or else the user will be unable to continue the session (everything a user types will be parsed using a non-default tokenizer). When reading XML syntax, the supported formats are the same as those of {XmlExplodeTag}. The parser does not validate anything in the XML input. After an XML token has been read in, it can be converted into an Yacas expression with {XmlExplodeTag}. Note that when reading XML, any plain text between tags is returned as one token. Any malformed XML will be treated as plain text.
Example: In> [XmlTokenizer(); q:=ReadToken(); \ DefaultTokenizer();q;] <a> Out> <a>;
Note that:
- after switching to {XmlTokenizer} the {In>} prompt disappeared; the user typed {<a>} and the {Out>} prompt with the resulting expression appeared.
- The resulting expression is an atom with the string representation {<a>}; it is <i>not</i> a string.
See also
OMRead()
, TrapError()
, XmlExplodeTag()
,
ReadToken()
, FromFile()
, FromString()
-
DefaultTokenizer
()¶ select the default syntax tokenizer for parsing the input
A “tokenizer” is an internal routine in the kernel that parses the input into Yacas expressions. This affects all input typed in by a user at the prompt and also the input redirected from files or strings using {FromFile} and {FromString} and read using {Read} or {ReadToken}. The Yacas environment currently supports some experimental tokenizers for various syntaxes. {DefaultTokenizer} switches to the tokenizer used for default Yacas syntax. {XmlTokenizer} switches to an XML syntax. Note that setting the tokenizer is a global side effect. One typically needs to switch back to the default tokenizer when finished reading the special syntax. Care needs to be taken when kernel errors are raised during a non-default tokenizer operation (as with any global change in the environment). Errors need to be caught with the {TrapError} function. The error handler code should re-instate the default tokenizer, or else the user will be unable to continue the session (everything a user types will be parsed using a non-default tokenizer). When reading XML syntax, the supported formats are the same as those of {XmlExplodeTag}. The parser does not validate anything in the XML input. After an XML token has been read in, it can be converted into an Yacas expression with {XmlExplodeTag}. Note that when reading XML, any plain text between tags is returned as one token. Any malformed XML will be treated as plain text.
See also
OMRead()
, TrapError()
, XmlExplodeTag()
,
ReadToken()
, FromFile()
, FromString()
-
OMForm
(expression)¶ convert Yacas expression to OpenMath
Parameters: expression – expression to convert {OMForm} prints an OpenMath representation of the input parameter {expression} to standard output. If a Yacas symbol does not have a mapping defined by {OMDef}, it is translated to and from OpenMath as the OpenMath symbol in the CD “yacas” with the same name as it has in Yacas.
Example: In> str:=ToString()OMForm(2+Sin(a*3)) Out> "<OMOBJ> <OMA> <OMS cd="arith1" name="plus"/> <OMI>2</OMI> <OMA> <OMS cd="transc1" name="sin"/> <OMA> <OMS cd="arith1" name="times"/> <OMV name="a"/> <OMI>3</OMI> </OMA> </OMA> </OMA> </OMOBJ> "; In> FromString(str)OMRead() Out> 2+Sin(a*3); In> OMForm(NotDefinedInOpenMath(2+3)) <OMOBJ> <OMA> <OMS cd="yacas" name="NotDefinedInOpenMath"/> <OMA> <OMS cd="arith1" name="plus"/> <OMI>2</OMI> <OMI>3</OMI> </OMA> </OMA> </OMOBJ> Out> True
See also
-
OMRead
()¶ read OpenMath expression and convert to Yacas
Parameters: expression – expression to convert {OMRead} reads an OpenMath expression from standard input and returns a normal Yacas expression that matches the input OpenMath expression. If a Yacas symbol does not have a mapping defined by {OMDef}, it is translated to and from OpenMath as the OpenMath symbol in the CD “yacas” with the same name as it has in Yacas.
Example: In> str:=ToString()OMForm(2+Sin(a*3)) Out> "<OMOBJ> <OMA> <OMS cd="arith1" name="plus"/> <OMI>2</OMI> <OMA> <OMS cd="transc1" name="sin"/> <OMA> <OMS cd="arith1" name="times"/> <OMV name="a"/> <OMI>3</OMI> </OMA> </OMA> </OMA> </OMOBJ> "; In> FromString(str)OMRead() Out> 2+Sin(a*3);
See also
-
OMDef
(yacasForm, cd, name)¶ define translations from Yacas to OpenMath and vice-versa.
Parameters: - yacasForm – string with the name of a Yacas symbol, or a Yacas expression
- cd – OpenMath Content Dictionary for the symbol
- name – OpenMath name for the symbol
- yacasToOM – rule for translating an application of that symbol in Yacas into an OpenMath expression
- omToYacas – rule for translating an OpenMath expression into an application of this symbol in Yacas
{OMDef} defines the translation rules for symbols between the Yacas representation and {OpenMath}. The first parameter, {yacasForm}, can be a string or an expression. The difference is that when giving an expression only the {omToYacas} translation is defined, and it uses the exact expression given. This is used for {OpenMath} symbols that must be translated into a whole subexpression in Yacas, such as {set1:emptyset} which gets translated to an empty list as follows: In> OMDef( {}, “set1”,”emptyset” ) Out> True In> FromString(“<OMOBJ><OMS cd=”set1” name=”emptyset”/></OMOBJ> ”)OMRead() Out> {} In> IsList(%) Out> True Otherwise, a symbol that is not inside an application (OMA) gets translated to the Yacas atom with the given name: In> OMDef( “EmptySet”, “set1”,”emptyset” ) Warning: the mapping for set1:emptyset was already defined as {} , but is redefined now as EmptySet Out> True In> FromString(“<OMOBJ><OMS cd=”set1” name=”emptyset”/></OMOBJ> ”)OMRead() Out> EmptySet The definitions for the symbols in the Yacas library are in the
*.rep
script subdirectories. In those modules for which the mappings are defined, there is a file called {om.ys} that contains the {OMDef} calls. Those files are loaded in {openmath.rep/om.ys}, so any new file must be added to the list there, at the end of the file. A rule is represented as a list of expressions. Since both OM and Yacas expressions are actually lists, the syntax is the same in both directions. There are two template forms that are expanded before the translation:- {$}: this symbol stands for the translation of the symbol applied in the original expression.
- {_path}: a path into the original expression (list) to extract an element, written as an underscore applied to an integer or a list of integers. Those integers are indexes into expressions, and integers in a list are applied recursively starting at the original expression. For example, {_2} means the second parameter of the expression, while {_{3,2,1}} means the first parameter of the second parameter of the third parameter of the original expression.
They can appear anywhere in the rule as expressions or subexpressions.
Finally, several alternative rules can be specified by joining them with the {|} symbol, and each of them can be annotated with a post-predicate applied with the underscore {_} symbol, in the style of Yacas’ simplification rules. Only the first alternative rule that matches is applied, so the more specific rules must be written first.
There are special symbols recognized by {OMForm} to output {OpenMath} constructs that have no specific parallel in Yacas, such as an OpenMath symbol having a {CD} and {name}: Yacas symbols have only a name. Those special symbols are:
- {OMS(cd, name)}: {<OMS cd=”cd” name=”name”>}
- {OMA(f x y ...)}: {<OMA>f x y ...</OMA>}
- {OMBIND(binderSymbol, bvars, expression)}: {<OMBIND>binderSymbol bvars expression</OMBIND>}, where {bvars} must be produced by using {OMBVAR(...)}.
- {OMBVAR(x y ...)}: {<OMBVAR>x y ...</OMBVAR>}
- {OME(...)}: {<OME>...</OME>}
When translating from OpenMath to Yacas, we just store unknown symbols as {OMS(“cd”, “name”)}. This way we don’t have to bother defining bogus symbols for concepts that Yacas does not handle, and we can evaluate expressions that contain them.
Example: In> OMDef( "Sqrt" , "arith1", "root", { $, _1, 2 }, $(_1)_(_2=2) | (_1^(1/_2)) ); Out> True In> OMForm(Sqrt(3)) <OMOBJ> <OMA> <OMS cd="arith1" name="root"/> <OMI>3</OMI> <OMI>2</OMI> </OMA> </OMOBJ> Out> True In> FromString("<OMOBJ><OMA><OMS cd=\"arith1\" name=\"root\"/><OMI>16</OMI><OMI>2</OMI></OMA></OMOBJ> ")OMRead() Out> Sqrt(16) In> FromString("<OMOBJ><OMA><OMS cd=\"arith1\" name=\"root\"/><OMI>16</OMI><OMI>3</OMI></OMA></OMOBJ> ")OMRead() Out> 16^(1/3) In> OMDef("Limit", "limit1", "limit", \ { $, _2, OMS("limit1", "under"), OMBIND(OMS("fns1", "lambda"), OMBVAR(_1), _4) }_(_3=Left) \ |{ $, _2, OMS("limit1", "above"), OMBIND(OMS("fns1", "lambda"), OMBVAR(_1), _4) }_(_3=Right) \ |{ $, _2, OMS("limit1", "both_sides"), OMBIND(OMS("fns1", "lambda"), OMBVAR(_1), _3) }, \ { $, _{3,2,1}, _1, Left, _{3,3}}_(_2=OMS("limit1", "below")) \ |{$, _{3,2,1}, _1, Right, _{3,3}}_(_2=OMS("limit1", "above")) \ |{$, _{3,2,1}, _1, _{3,3}} \ ); In> OMForm(Limit(x,0) Sin(x)/x) <OMOBJ> <OMA> <OMS cd="limit1" name="limit"/> <OMI>0</OMI> <OMS cd="limit1" name="both_sides"/> <OMBIND> <OMS cd="fns1" name="lambda"/> <OMBVAR> <OMV name="x"/> </OMBVAR> <OMA> <OMS cd="arith1" name="divide"/> <OMA> <OMS cd="transc1" name="sin"/> <OMV name="x"/> </OMA> <OMV name="x"/> </OMA> </OMBIND> </OMA> </OMOBJ> Out> True In> OMForm(Limit(x,0,Right) 1/x) <OMOBJ> <OMA> <OMS cd="limit1" name="limit"/> <OMI>0</OMI> <OMS cd="limit1" name="above"/> <OMBIND> <OMS cd="fns1" name="lambda"/> <OMBVAR> <OMV name="x"/> </OMBVAR> <OMA> <OMS cd="arith1" name="divide"/> <OMI>1</OMI> <OMV name="x"/> </OMA> </OMBIND> </OMA> </OMOBJ> Out> True In> FromString(ToString()OMForm(Limit(x,0,Right) 1/x))OMRead() Out> Limit(x,0,Right)1/x In> % Out> Infinity
String manipulation¶
-
StringMid'Set
(index, substring, string)¶ change a substring
Parameters: - index – index of substring to get
- substring – substring to store
- string – string to store substring in
Set (change) a part of a string. It leaves the original alone, returning a new changed copy.
Example: In> StringMid'Set(3,"XY","abcdef") Out> "abXYef";
See also
-
StringMid'Get
(index, length, string)¶ retrieve a substring
Parameters: - index – index of substring to get
- length – length of substring to get
- string – string to get substring from
{StringMid’Get} returns a part of a string. Substrings can also be accessed using the {[]} operator.
Example: In> StringMid'Get(3,2,"abcdef") Out> "cd"; In> "abcdefg"[2 .. 4] Out> "bcd";
See also
-
Atom
("string")¶ convert string to atom
Parameters: "string" – a string Returns an atom with the string representation given as the evaluated argument. Example: {Atom(“foo”);} returns {foo}.
Example: In> Atom("a") Out> a;
See also
-
String
(atom)¶ convert atom to string
Parameters: atom – an atom {String} is the inverse of {Atom}: turns {atom} into {“atom”}.
Example: In> String(a) Out> "a";
See also
-
ConcatStrings
(strings)¶ concatenate strings
Parameters: strings – one or more strings Concatenates strings.
Example: In> ConcatStrings("a","b","c") Out> "abc";
See also
-
PatchString
(string)¶ execute commands between {<?} and {?>} in strings
Parameters: string – a string to patch This function does the same as PatchLoad, but it works on a string instead of on the contents of a text file. See PatchLoad for more details.
Example: In> PatchString("Two plus three is <? Write(2+3); ?> "); Out> "Two plus three is 5 ";
See also
Probability and Statistics¶
Probability¶
Each distribution is represented as an entity. For each distribution
known to the system the consistency of parameters is checked. If the
parameters for a distribution are invalid, the functions return
Undefined
. For example, NormalDistribution(a,-1)
evaluates to
Undefined
, because of negative variance.
-
BernoulliDistribution
(p)¶ Bernoulli distribution
Parameters: p – number, probability of an event in a single trial A random variable has a Bernoulli distribution with probability
p
if it can be interpreted as an indicator of an event, wherep
is the probability to observe the event in a single trial. Numerical value ofp
must satisfy0 < p < 1
.See also
-
BinomialDistribution
(p, n)¶ binomial distribution
Parameters: - p – number, probability to observe an event in single trial
- n – number of trials
Suppose we repeat a trial
n
times, the probability to observe an event in a single trial isp
and outcomes in all trials are mutually independent. Then the number of trials when the event occurred is distributed according to the binomial distribution. The probability of that isBinomialDistribution(p,n)
. Numerical value ofp
must satisfy0 < p < 1
. Numerical value ofn
must be a positive integer.See also
-
tDistribution
(m)¶ Student’s $t$ distribution
Parameters: {m} – integer, number of degrees of freedom
-
PDF
(dist, x)¶ probability density function
Parameters: - dist – a distribution type
- x – a value of random variable
If
dist
is a discrete distribution, thenPDF
returns the probability for a random variable with distributiondist
to take a value ofx
. Ifdist
is a continuous distribution, thenPDF
returns the density function at pointx
.See also
CDF()
Statistics¶
-
ChiSquareTest
(observed, expected)¶ Pearson’s ChiSquare test
Parameters: - observed – list of observed frequencies
- expected – list of expected frequencies
- params – number of estimated parameters
ChiSquareTest
is intended to find out if our sample was drawn from a given distribution or not. To find this out, one has to calculate observed frequencies into certain intervals and expected ones. To calculate expected frequency the formula \(n_i=n p_i\) must be used, where \(p_i\) is the probability measure of \(i\)-th interval, and \(n\) is the total number of observations. If any of the parameters of the distribution were estimated, this number is given asparams
. The function returns a list of three local substitution rules. First of them contains the test statistic, the second contains the value of the parameters, and the last one contains the degrees of freedom. The test statistic is distributed asChiSquareDistribution()
.
Number theory¶
This chapter describes functions that are of interest in number theory. These functions typically operate on integers. Some of these functions work quite slowly.
-
IsPrime
(n)¶ test for a prime number
Parameters: n – integer to test
-
IsComposite
(n)¶ test for a composite number
Parameters: n – positive integer
-
IsCoprime
(m, n)¶ test if integers are coprime
Parameters: - m – positive integer
- n – positive integer
- list – list of positive integers
-
IsSquareFree
(n)¶ test for a square-free number
Parameters: n – positive integer
-
IsPrimePower
(n)¶ test for a power of a prime number
Parameters: n – integer to test
-
NextPrime
(i)¶ generate a prime following a number
Parameters: i – integer value
-
IsTwinPrime
(n)¶ test for a twin prime
Parameters: n – positive integer
-
IsIrregularPrime
(n)¶ test for an irregular prime
Parameters: n – positive integer
-
IsCarmichaelNumber
(n)¶ test for a Carmichael number
Parameters: n – positive integer
-
Factors
(x)¶ factorization
Parameters: x – integer or univariate polynomial
-
IsAmicablePair
(m, n)¶ test for a pair of amicable numbers
Parameters: - m – positive integer
- n – positive integer
-
Factor
(x)¶ factorization, in pretty form
Parameters: x – integer or univariate polynomial
-
Divisors
(n)¶ number of divisors
Parameters: n – positive integer
-
DivisorsSum
(n)¶ the sum of divisors
Parameters: n – positive integer
-
ProperDivisors
(n)¶ the number of proper divisors
Parameters: n – positive integer
-
ProperDivisorsSum
(n)¶ the sum of proper divisors
Parameters: n – positive integer
-
Moebius
(n)¶ the Moebius function
Parameters: n – positive integer
-
CatalanNumber
(n)¶ return the
n
-th Catalan NumberParameters: n – positive integer
-
FermatNumber
(n)¶ return the
n
-th Fermat NumberParameters: n – positive integer
-
HarmonicNumber
(n)¶ return the
n
-th Harmonic NumberParameters: - n – positive integer
- r – positive integer
-
StirlingNumber1
(n, m)¶ return the
n,m
-th Stirling Number of the first kindParameters: - n – positive integers
- m – positive integers
-
StirlingNumber1
(n, m) return the
n,m
-th Stirling Number of the second kindParameters: - n – positive integer
- m – positive integer
-
DivisorsList
(n)¶ the list of divisors
Parameters: n – positive integer
-
SquareFreeDivisorsList
(n)¶ the list of square-free divisors
Parameters: n – positive integer
-
MoebiusDivisorsList
(n)¶ the list of divisors and Moebius values
Parameters: n – positive integer
-
SumForDivisors
(var, n, expr)¶ loop over divisors
Parameters: - var – atom, variable name
- n – positive integer
- expr – expression depending on
var
-
RamanujanSum
(k, n)¶ compute the Ramanujan’s sum
Parameters: - k – positive integer
- n – positive integer
This function computes the Ramanujan’s sum, i.e. the sum of the
n
-th powers of thek
-th primitive roots of the unit:\[\sum_{l=1}^k\frac{\exp(2ln\pi\imath)}{k}\]where \(l\) runs thought the integers between 1 and
k-1
that are coprime to \(l\). The computation is done by using the formula in T. M. Apostol, <i>Introduction to Analytic Theory</i> (Springer-Verlag), Theorem 8.6.Todo
check the definition
-
PAdicExpand
(n, p)¶ p-adic expansion
Parameters: - n – number or polynomial to expand
- p – base to expand in
-
IsQuadraticResidue
(m, n)¶ functions related to finite groups
Parameters: - m – integer
- n – odd positive integer
-
GaussianFactors
(z)¶ factorization in Gaussian integers
Parameters: z – Gaussian integer
-
GaussianNorm
(z)¶ norm of a Gaussian integer
Parameters: z – Gaussian integer
-
IsGaussianUnit
(z)¶ test for a Gaussian unit
Parameters: z – a Gaussian integer
-
IsGaussianPrime
(z)¶ test for a Gaussian prime
Parameters: z – a complex or real number
-
GaussianGcd
(z, w)¶ greatest common divisor in Gaussian integers
Parameters: - z – Gaussian integer
- w – Gaussian integer
Programming in Yacas¶
This part of the manual is a somewhat in-depth explanation of the Yacas programming language and environment. It assumes that you have worked through the introductory tutorial. You should consult the function reference about how to use the various Yacas functions mentioned here.
This document should get you started programming in Yacas. There are some basic explanations and hands-on tutorials.
The Yacas architecture¶
Yacas is designed as a small core engine that interprets a library of scripts. The core engine provides the syntax parser and a number of hard-wired functions, such as {Set()} or {MathExp()} which cannot be redefined by the user. The script library resides in the scripts directory “{scripts/}” and contains higher-level definitions of functions and constants. The library scripts are on equal footing with any code the user executes interactively or any files the user loads.
Generally, all core functions have plain names and almost all are not “bodied” or infix operators. The file {corefunctions.h} in the source tree lists declarations of all kernel functions callable from Yacas; consult it for reference. For many of the core functions, the script library already provides convenient aliases. For instance, the addition operator “{+}” is defined in the script {scripts/standard} while the actual addition of numbers is performed through the built-in function {MathAdd}.
Startup, scripts and {.def} files¶
When Yacas is first started or restarted, it executes the script {yacasinit.ys} in the scripts directory. This script may load some other scripts. In order to start up quickly, Yacas does not execute all other library scripts at first run or at restart. It only executes the file {yacasinit.ys} and all {.def} files in the scripts. The {.def} files tell the system where it can find definitions for various library functions. Library is divided into “packages” stored in “repository” directories. For example, the function {ArcTan} is defined in the {stdfuncs} package; the library file is {stdfuncs.rep/}{code.ys} and the {.def} file is {stdfuncs.rep}{/code.ys.def}. The function {ArcTan} mentioned in the {.def} file, therefore Yacas will know to load the package {stdfuncs} when the user invokes {ArcTan}. This way Yacas knows where to look for any given function without actually loading the file where the function is defined.
There is one exception to the strategy of delayed loading of the library scripts. Namely, the syntax definitions of infix, prefix, postfix and bodied functions, such as {Infix(“*”,4)} cannot be delayed (it is currently in the file {stdopers.ys}). If it were delayed, the Yacas parser would encounter {1+2} (typed by the user) and generate a syntax error before it has a chance to load the definition of the operator “{+}”.
Object types¶
Yacas supports two basic kinds of objects: atoms and compounds. Atoms are (integer or real, arbitrary-precision) numbers such as {2.71828}, symbolic variables such as {A3} and character strings. Compounds include functions and expressions, e.g. {Cos(a-b)} and lists, e.g. {{1+a,2+b,3+c}}.
The type of an object is returned by the built-in function {Type}, for example:
In> Type(a);
Out> "";
In> Type(F(x));
Out> "F";
In> Type(x+y);
Out> "+";
In> Type({1,2,3});
Out> "List";
Internally, atoms are stored as strings and compounds as lists. (The Yacas lexical analyzer is case-sensitive, so {List} and {list} are different atoms.) The functions {String()} and {Atom()} convert between atoms and strings. A Yacas list {{1,2,3}} is internally a list {(List 1 2 3)} which is the same as a function call {List(1,2,3)} and for this reason the “type” of a list is the string {“List”}. During evaluation, atoms can be interpreted as numbers, or as variables that may be bound to some value, while compounds are interpreted as function calls.
Note that atoms that result from an {Atom()} call may be invalid and never evaluate to anything. For example, {Atom(3X)} is an atom with string representation “3X” but with no other properties.
Currently, no other lowest-level objects are provided by the core engine besides numbers, atoms, strings, and lists. There is, however, a possibility to link some externally compiled code that will provide additional types of objects. Those will be available in Yacas as “generic objects.” For example, fixed-size arrays are implemented in this way.
Yacas evaluation scheme¶
Evaluation of an object is performed either explicitly by the built-in command {Eval()} or implicitly when assigning variables or calling functions with the object as argument (except when a function does not evaluate that argument). Evaluation of an object can be explicitly inhibited using {Hold()}. To make a function not evaluate one of its arguments, a {HoldArg(funcname, argname)} must be declared for that function.
Internally, all expressions are either atoms or lists (perhaps nested). Use {FullForm()} to see the internal form of an expression. A Yacas list expression written as {{a, b}} is represented internally as {(List a b)}, equivalently to a function call {List(a,b)}.
Evaluation of an atom goes as follows: if the atom is bound locally as a variable, the object it is bound to is returned, otherwise, if it is bound as a global variable then that is returned. Otherwise, the atom is returned unevaluated. Note that if an atom is bound to an expression, that expression is considered as final and is not evaluated again.
Internal lists of atoms are generally interpreted in the following way: the first atom of the list is some command, and the atoms following in the list are considered the arguments. The engine first tries to find out if it is a built-in command (core function). In that case, the function is executed. Otherwise, it could be a user-defined function (with a “rule database”), and in that case the rules from the database are applied to it. If none of the rules are applicable, or if no rules are defined for it, the object is returned unevaluated.
Application of a rule to an expression transforms it into a different expression to which other rules may be applicable. Transformation by matching rules continues until no more rules are applicable, or until a “terminating” rule is encountered. A “terminating” rule is one that returns {Hold()} or {UnList()} of some expression. Calling these functions gives an unevaluated expression because it terminates the process of evaluation itself.
The main properties of this scheme are the following. When objects are assigned to variables, they generally are evaluated (except if you are using the {Hold()} function) because assignment {var := value} is really a function call to {Set(var, value)} and this function evaluates its second argument (but not its first argument). When referencing that variable again, the object which is its value will not be re-evaluated. Also, the default behavior of the engine is to return the original expression if it could not be evaluated. This is a desired behavior if evaluation is used for simplifying expressions.
One major design flaw in Yacas (one that other functional languages like LISP also have) is that when some expression is re-evaluated in another environment, the local variables contained in the expression to be evaluated might have a different meaning. In this case it might be useful to use the functions {LocalSymbols} and {TemplateFunction}. Calling
LocalSymbols(a,b)
a*b;
results in “{a}” and “{b}” in the multiplication being substituted with unique symbols that can not clash with other variables that may be used elsewhere. Use {TemplateFunction} instead of {Function} to define a function whose parameters should be treated as unique symbols.
Consider the following example:
In> f1(x):=Apply("+",{x,x});
Out> True
The function {f1} simply adds its argument to itself. Now calling this function with some argument:
In> f1(Sin(a))
Out> 2*Sin(a)
yields the expected result. However, if we pass as an argument an expression containing the variable {x}, things go wrong:
In> f1(Sin(x))
Out> 2*Sin(Sin(x))
This happens because within the function, {x} is bound to {Sin(x)}, and since it is passed as an argument to {Apply} it will be re-evaluated, resulting in {Sin(Sin(x))}. {TemplateFunction} solves this by making sure the arguments can not collide like this (by using {LocalSymbols}:
In> TemplateFunction("f2",{x}) Apply("+",{x,x});
Out> True
In> f2(Sin(a))
Out> 2*Sin(a)
In> f2(Sin(x))
Out> 2*Sin(x)
In general one has to be careful when functions like {Apply}, {Map} or {Eval} (or derivatives) are used.
Rules¶
<i>Rules</i> are special properties of functions that are applied when the function object is being evaluated. A function object could have just one rule bound to it; this is similar to a “subroutine” having a “function body” in usual procedural languages. However, Yacas function objects can also have several rules bound to them. This is analogous of having several alternative “function bodies” that are executed under different circumstances. This design is more suitable for symbolic manipulations.
A function is identified by its name as returned by {Type} and the number of arguments, or “arity”. The same name can be used with different arities to define different functions: {f(x)} is said to “have arity 1” and {f(x,y)} has arity 2. Each of these functions may possess its own set of specific rules, which we shall call a “rule database” of a function.
Each function should be first declared with the built-in command {RuleBase} as follows:
RuleBase("FunctionName",{argument list});
So, a new (and empty) rule database for {f(x,y)} could be created by typing {RuleBase(“f”,{x,y})}. The names for the arguments “x” and “y” here are arbitrary, but they will be globally stored and must be later used in descriptions of particular rules for the function {f}. After the new rulebase declaration, the evaluation engine of Yacas will begin to really recognize {f} as a function, even though no function body or equivalently no rules have been defined for it yet.
The shorthand operator {:=} for creating user functions that we illustrated in the tutorial is actually defined in the scripts and it makes the requisite call to the {RuleBase()} function. After a {RuleBase()} call you can specify parsing properties for the function; for example, you could make it an infix or bodied operator.
Now we can add some rules to the rule database for a function. A rule simply states that if a specific function object with a specific arity is encountered in an expression and if a certain predicate is true, then Yacas should replace this function with some other expression. To tell Yacas about a new rule you can use the built-in {Rule} command. This command is what does the real work for the somewhat more aesthetically pleasing {... # ... <– ...} construct we have seen in the tutorial. You do not have to call {RuleBase()} explicitly if you use that construct.
Here is the general syntax for a {Rule()} call:
Rule("foo", arity, precedence, pred) body;
This specifies that for function {foo} with given {arity} ({foo(a,b)} has arity 2), there is a rule that if {pred} is true, then {body} should be evaluated, and the original expression replaced by the result. Predicate and body can use the symbolic names of arguments that were declared in the {RuleBase} call.
All rules for a given function can be erased with a call to {Retract(funcname, arity)}. This is useful, for instance, when too many rules have been entered in the interactive mode. This call undefines the function and also invalidates the {RuleBase} declaration.
You can specify that function arguments are not evaluated before they are bound to the parameter: {HoldArg(“foo”,a)} would then declare that the a arguments in both {foo(a)} and {foo(a,b)} should not be evaluated before bound to {a}. Here the argument name {a} should be the same as that used in the {RuleBase()} call when declaring these functions. Inhibiting evaluation of certain arguments is useful for procedures performing actions based partly on a variable in the expression, such as integration, differentiation, looping, etc., and will be typically used for functions that are algorithmic and procedural by nature.
Rule-based programming normally makes heavy use of recursion and it is important to control the order in which replacement rules are to be applied. For this purpose, each rule is given a <i>precedence</i>. Precedences go from low to high, so all rules with precedence 0 will be tried before any rule with precedence 1.
You can assign several rules to one and the same function, as long as some of the predicates differ. If none of the predicates are true, the function is returned with its arguments evaluated.
This scheme is slightly slower for ordinary functions that just have
one rule (with the predicate True
), but it is a desired behavior for
symbolic manipulation. You can gradually build up your own functions,
incrementally testing their properties.
Examples of using rules¶
As a simple illustration, here are the actual {RuleBase()} and {Rule()} calls needed to define the factorial function:
In> RuleBase("f",{n});
Out> True;
In> Rule("f", 1, 10, n=0) 1;
Out> True;
In> Rule("f", 1, 20, IsInteger(n) And n>0) n*f(n-1);
Out> True;
This definition is entirely equivalent to the one in the tutorial. {f(4)} should now return 24, while {f(a)} should return just {f(a)} if {a} is not bound to any value.
The {Rule} commands in this example specified two rules for function
{f} with arity 1: one rule with precedence 10 and predicate {n=0}, and
another with precedence 20 and the predicate that returns True
only
if {n} is a positive integer. Rules with lowest precedence get
evaluated first, so the rule with precedence 10 will be tried before
the rule with precedence 20. Note that the predicates and the body use
the name “n” declared by the {RuleBase()} call.
After declaring {RuleBase()} for a function, you could tell the parser to treat this function as a postfix operator:
In> Postfix("f");
Out> True;
In> 4 f;
Out> 24;
There is already a function {Function} defined in the standard scripts that allows you to construct simple functions. An example would be
Function ("FirstOf", {list}) list[1] ;
which simply returns the first element of a list. This could also have been written as
Function("FirstOf", {list})
[
list[1] ;
];
As mentioned before, the brackets {[ ]} are also used to combine multiple operations to be performed one after the other. The result of the last performed action is returned.
Finally, the function {FirstOf} could also have been defined by typing
FirstOf(list):=list[1] ;
Structured programming and control flow¶
Some functions useful for control flow are already defined in Yacas’s standard library. Let’s look at a possible definition of a looping function {ForEach}. We shall here consider a somewhat simple-minded definition, while the actual {ForEach} as defined in the standard script “controlflow” is a little more sophisticated.
Function("ForEach",{foreachitem,
foreachlist,foreachbody})
[
Local(foreachi,foreachlen);
foreachlen:=Length(foreachlist);
foreachi:=0;
While (foreachi < foreachlen)
[
foreachi++;
MacroLocal(foreachitem);
MacroSet(foreachitem,
foreachlist[foreachi]);
Eval(foreachbody);
];
];
Bodied("ForEach");
UnFence("ForEach",3);
HoldArg("ForEach",foreachitem);
HoldArg("ForEach",foreachbody);
Functions like this should probably be defined in a separate file. You can load such a file with the command {Load(“file”)}. This is an example of a macro-like function. Let’s first look at the last few lines. There is a {Bodied(...)} call, which states that the syntax for the function {ForEach()} is {ForEach(item,{list}) body;} – that is, the last argument to the command {ForEach} should be outside its brackets. {UnFence(...)} states that this function can use the local variables of the calling function. This is necessary, since the body to be evaluated for each item will probably use some local variables from that surrounding.
Finally, {HoldArg(“function”,argument)} specifies that the argument “{argument}” should not be evaluated before being bound to that variable. This holds for {foreachitem} and {foreachbody}, since {foreachitem} specifies a variable to be set to that value, and {foreachbody} is the expression that should be evaluated <i>after</i> that variable is set.
Inside the body of the function definition there are calls to {Local(...)}. {Local()} declares some local variable that will only be visible within a block {[ ... ]}. The command {MacroLocal()} works almost the same. The difference is that it evaluates its arguments before performing the action on it. This is needed in this case, because the variable {foreachitem} is bound to a variable to be used as the loop iterator, and it is <i>the variable it is bound to</i> that we want to make local, not {foreachitem} itself. {MacroSet()} works similarly: it does the same as {Set()} except that it also first evaluates the first argument, thus setting the variable requested by the user of this function. The {Macro}... functions in the built-in functions generally perform the same action as their non-macro versions, apart from evaluating an argument it would otherwise not evaluate.
To see the function in action, you could type:
ForEach(i,{1,2,3}) [Write(i); NewLine();];
This should print 1, 2 and 3, each on a new line.
Note: the variable names “foreach...” have been chosen so they won’t get confused with normal variables you use. This is a major design flaw in this language. Suppose there was a local variable {foreachitem}, defined in the calling function, and used in {foreachbody}. These two would collide, and the interpreter would use only the last defined version. In general, when writing a function that calls {Eval()}, it is a good idea to use variable names that can not collide with user’s variables. This is generally the single largest cause of bugs when writing programs in Yacas. This issue should be addressed in the future.
Additional syntactic sugar¶
The parser is extended slightly to allow for fancier constructs.
Lists, e.g. {{a,b}}. This then is parsed into the internal notation {(List a b)} , but will be printed again as {{a,b};}
Statement blocks such as {[} statement1 {;} statement2{;];}. This is parsed into a Lisp object {(Prog} {(}statement1 {)} {(}statement2 {))}, and printed out again in the proper form.
Object argument accessors in the form of {expr[ index ]}. These are mapped internally to {Nth(expr,index)}. The value of {index}=0 returns the operator of the object, {index}=1 the first argument, etc. So, if {expr} is {foo(bar)}, then {expr[0]} returns {foo}, and {expr[1]} returns {bar}. Since lists of the form {{...}} are essentially the same as {List(...)}, the same accessors can be used on lists.
Function blocks such as
While (i < 10) [ Write(i); i:=i+1; ];
The expression directly following the {While(...)} block is added as a last argument to the {While(...)} call. So {While(a)b;} is parsed to the internal form {(While a b).}
This scheme allows coding the algorithms in an almost C-like syntax.
Strings are generally represented with quotes around them, e.g. “this is a string”. Backslash {} in a string will unconditionally add the next character to the string, so a quote can be added with {“} (a backslash-quote sequence).
Using “Macro rules” (e.g. {NFunction})¶
The Yacas language allows to have rules whose definitions are generated at runtime. In other words, it is possible to write rules (or “functions”) that, as a side-effect, will define other rules, and those other rules will depend on some parts of the expression the original function was applied to.
This is accomplished using functions {MacroRuleBase}, {MacroRule}, {MacroRulePattern}. These functions evaluate their arguments (including the rule name, predicate and body) and define the rule that results from this evaluation.
Normal, “non-Macro” calls such as {Rule()} will not evaluate their arguments and this is a desired feature. For example, suppose we defined a new predicate like this,
RuleBase("IsIntegerOrString, {x});
Rule("IsIntegerOrString", 1, 1, True)
IsInteger(x) And IsString(x);
If the {Rule()} call were to evaluate its arguments, then the “body”
argument, {IsInteger(x) And IsString(x)}, would be evaluated to
False
since {x} is an atom, so we would have defined the predicate
to be always False
, which is not at all what we meant to do. For
this reason, the {Rule} calls do not evaluate their arguments.
Consider however the following situation. Suppose we have a function {f(arglist)} where {arglist} is its list of arguments, and suppose we want to define a function {Nf(arglist)} with the same arguments which will evaluate {f(arglist)} and return only when all arguments from {arglist} are numbers, and return unevaluated {Nf(arglist)} otherwise. This can of course be done by a usual rule such as
Rule("Nf", 3, 0, IsNumericList({x,y,z}))
<-- "f" @ {x,y,z};
Here {IsNumericList} is a predicate that checks whether all elements of a given list are numbers. (We deliberately used a {Rule} call instead of an easier-to-read {<–} operator to make it easier to compare with what follows.)
However, this will have to be done for every function {f} separately. We would like to define a procedure that will define {Nf}, given <i>any</i> function {f}. We would like to use it like this:
NFunction("Nf", "f", {x,y,z});
After this function call we expect to be able to use the function {Nf}.
Here is how we could naively try to implement {NFunction} (and fail):
NFunction(new'name, old'name, arg'list) := [
MacroRuleBase(new'name, arg'list);
MacroRule(new'name, Length(arg'list), 0,
IsNumericList(arg'list)
)
new'name @ arg'list;
];
Now, this just does not do anything remotely right. {MacroRule}
evaluates its arguments. Since {arg’list} is an atom and not a list of
numbers at the time we are defining this, {IsNumericList(arg’list)}
will evaluate to False
and the new rule will be defined with a
predicate that is always False
, i.e. it will be never applied.
The right way to figure this out is to realize that the {MacroRule} call evaluates all its arguments and passes the results to a {Rule} call. So we need to see exactly what {Rule()} call we need to produce and then we need to prepare the arguments of {MacroRule} so that they evaluate to the right values. The {Rule()} call we need is something like this:
Rule("actual new name", <actual # of args>, 0,
IsNumericList({actual arg list})
) "actual new name" @ {actual arg list};
Note that we need to produce expressions such as {“new name” @ arg’list} and not <i>results</i> of evaluation of these expressions. We can produce these expressions by using {UnList()}, e.g.
UnList({Atom("@"), "Sin", {x}})
produces
"Sin" @ {x};
but not {Sin(x)}, and
UnList({IsNumericList, {1,2,x}})
produces the expression
IsNumericList({1,2,x});
which is not further evaluated.
Here is a second version of {NFunction()} that works:
NFunction(new'name, old'name, arg'list) := [
MacroRuleBase(new'name, arg'list);
MacroRule(new'name, Length(arg'list), 0,
UnList({IsNumericList, arg'list})
)
UnList({Atom("@"), old'name, arg'list});
];
Note that we used {Atom(“@”)} rather than just the bare atom {@} because {@} is a prefix operator and prefix operator names as bare atoms do not parse (they would be confused with applications of a prefix operator to what follows).
Finally, there is a more concise (but less general) way of defining {NFunction()} for functions with known number of arguments, using the backquoting mechanism. The backquote operation will first substitute variables in an expression, without evaluating anything else, and then will evaluate the resulting expression a second time. The code for functions of just one variable may look like this:
N1Function(new'name, old'name) :=
`( @new'name(x_IsNumber) <-- @old'name(x) );
This executes a little slower than the above version, because the backquote needs to traverse the expression twice, but makes for much more readable code.
Macro expansion¶
Yacas supports macro expansion (back-quoting). An expression can be
back-quoted by putting a \`
in front of it. Within the back-quoted
expression, all atoms that have a @
in front of them get replaced
with the value of that atom (treated as a variable), and then the
resulting expression is evaluated:
In> x:=y
Out> y
In> `(@x:=2)
Out> 2
In> x
Out> y
In> y
Out> 2
This is useful in cases where within an expression one sub-expression is not evaluated. For instance, transformation rules can be built dynamically, before being declared. This is a particularly powerful feature that allows a programmer to write programs that write programs. The idea is borrowed from Lisp.
As the above example shows, there are similarities with the
Macro...
functions, that serve the same purpose for specific
expressions. For example, for the above code, one could also have
called MacroSet
:
In> MacroSet(x,3)
Out> True;
In> x
Out> y;
In> y
Out> 3;
The difference is that MacroSet
, and in general the Macro...
functions, are faster than their back-quoted counterparts. This is
because with back-quoting, first a new expression is built before it
is evaluated. The advantages of back-quoting are readability and
flexibility (the number of Macro...
functions is limited, whereas
back-quoting can be used anywhere).
When an @
operator is placed in front of a function call, the
function call is replaced:
In> plus:=Add
Out> Add;
In> `(@plus(1,2,3))
Out> 6;
Application of pure functions is also possible by using macro expansion:
In> pure:={{a,b},a+b};
Out> {{a,b},a+b};
In> ` @pure(2,3);
Out> 5;
Pure (nameless) functions are useful for declaring a temporary function, that has functionality depending on the current environment it is in, or as a way to call driver functions. In the case of drivers (interfaces to specific functionality), a variable can be bound to a function to be evaluated to perform a specific task. That way several drivers can be around, with one bound to the variables holding the functions that will be called.
Scope of variable bindings¶
When setting variables or retrieving variable values, variables are automatically bound global by default. You can explicitly specify variables to be local to a block such as a function body; this will make them invisible outside the block. Blocks have the form {[} statement1{;} statement2{;} {]} and local variables are declared by the {Local()} function.
When entering a block, a new stack frame is pushed for the local variables; it means that the code inside a block doesn’t see the local variables of the <i>caller</i> either! You can tell the interpreter that a function should see local variables of the calling environment; to do this, declare UnFence(funcname, arity) on that function.
Evaluation of expressions¶
When programming in some language, it helps to have a mental model of what goes on behind the scenes when evaluating expressions, or in this case simplifying expressions.
This section aims to explain how evaluation (and simplification) of expressions works internally, in {Yacas}.
The LISP heritage¶
Representation of expressions¶
Much of the inner workings is based on how LISP-like languages are built up. When an expression is entered, or composed in some fashion, it is converted into a prefix form much like you get in LISP:
a+b -> (+ a b)
Sin(a) -> (Sin a)
Here the sub-expression is changed into a list of so-called “atoms”, where the first atom is a function name of the function to be invoked, and the atoms following are the arguments to be passed in as parameters to that function.
{Yacas} has the function {FullForm} to show the internal representation:
In> FullForm(a+b)
(+ a b )
Out> a+b;
In> FullForm(Sin(a))
(Sin a )
Out> Sin(a);
In> FullForm(a+b+c)
(+ (+ a b )c )
Out> a+b+c;
The internal representation is very close to what {FullForm} shows on screen. {a+b+c} would be {(+ (+ a b )c )} internally, or:
()
|
|
+ -> () -> c
|
|
+ -> a -> b
Evaluation¶
An expression like described above is done in the following manner: first the arguments are evaluated (if they need to be evaluated, {Yacas} can be told to not evaluate certain parameters to functions), and only then are these arguments passed in to the function for evaluation. They are passed in by binding local variables to the values, so these arguments are available as local values.
For instance, suppose we are evaluating 2*3+4
. This first gets
changed to the internal representation (+ (* 2 3 )4 )
. Then,
during evaluation, the top expression refers to function “{+}”. Its
arguments are (* 2 3)
and {4}. First (* 2 3)
gets evaluated.
This is a function call to the function *
with arguments {2} and
{3}, which evaluate to themselves. Then the function “{*}” is invoked
with these arguments. The {Yacas} standard script library has code
that accepts numeric input and performs the multiplication
numerically, resulting in {6}.
The second argument to the top-level “{+}” is {4}, which evaluates to itself.
Now, both arguments to the “{+}” function have been evaluated, and the results are {6} and {4}. Now the “{+}” function is invoked. This function also has code in the script library to actually perform the addition when the arguments are numeric, so the result is 10:
In> FullForm(Hold(2*3+4))
(+ (* 2 3 )4 )
Out> 2*3+4;
In> 2*3+4
Out> 10;
Note that in {Yacas}, the script language does not define a “{+}” function in the core. This and other functions are all implemented in the script library. The feature “when the arguments to “{+}” are numeric, perform the numeric addition” is considered to be a “policy” which should be configurable. It should not be a part of the core language.
It is surprisingly difficult to keep in mind that evaluation is bottom up, and that arguments are evaluated before the function call is evaluated. In some sense, you might feel that the evaluation of the arguments is part of evaluation of the function. It is not. Arguments are evaluated before the function gets called.
Suppose we define the function {f}, which adds two numbers, and traces itself, as:
In> f(a,b):= \
In> [\
In> Local(result);\
In> Echo("Enter f with arguments ",a,b);\
In> result:=a+b;\
In> Echo("Leave f with result ",result);\
In> result;\
In> ];
Out> True;
Then the following interaction shows this principle:
In> f(f(2,3),4)
Enter f with arguments 2 3
Leave f with result 5
Enter f with arguments 5 4
Leave f with result 9
Out> 9;
The first Enter/Leave combination is for {f(2,3)}, and only then is the outer call to {f} entered.
This has important consequences for the way {Yacas} simplifies expressions: the expression trees are traversed bottom up, as the lowest parts of the expression trees are simplified first, before being passed along up to the calling function.
{Yacas}-specific extensions for CAS implementations¶
{Yacas} has a few language features specifically designed for use when implementing a CAS.
The transformation rules¶
Working with transformation rules is explained in the introduction and tutorial book. This section mainly deals with how {Yacas} works with transformation rules under the hood.
A transformation rule consists of two parts: a condition that an expression should match, and a result to be substituted for the expression if the condition holds. The most common way to specify a condition is a pattern to be matched to an expression.
A pattern is again simply an expression, stored in internal format:
In> FullForm(a_IsInteger+b_IsInteger*(_x))
(+ (_ a IsInteger )(* (_ b IsInteger )(_ x )))
Out> a _IsInteger+b _IsInteger*_x;
{Yacas} maintains structures of transformation rules, and tries to
match them to the expression being evaluated. It first tries to match
the structure of the pattern to the expression. In the above case, it
tries to match to {a+b*x}. If this matches, local variables {a}, {b}
and {x} are declared and assigned the sub-trees of the expression
being matched. Then the predicates are tried on each of them: in this
case, {IsInteger(a)} and {IsInteger(b)} should both return True
.
Not shown in the above case, are post-predicates. They get evaluated
afterwards. This post-predicate must also evaluate to True
. If the
structure of the expression matches the structure of the pattern, and
all predicates evaluate to True
, the pattern matches and the
transformation rule is applied, meaning the right hand side is
evaluated, with the local variables mentioned in the pattern
assigned. This evaluation means all transformation rules are
re-applied to the right-hand side of the expression.
Note that the arguments to a function are evaluated first, and only then is the function itself called. So the arguments are evaluated, and then the transformation rules applied on it. The main function defines its parameters also, so these get assigned to local variables also, before trying the patterns with their associated local variables.
Here is an example making the fact that the names in a pattern are local variables more explicit:
In> f1(_x,_a) <-- x+a
Out> True;
In> f2(_x,_a) <-- [Local(a); x+a;];
Out> True;
In> f1(1,2)
Out> 3;
In> f2(1,2)
Out> a+1;
Using different rules in different cases¶
In a lot of cases, the algorithm to be invoked depends on the type of the arguments. Or the result depends on the form of the input expression. This results in the typical “case” or “switch” statement, where the code to evaluate to determine the result depends on the form of the input expression, or the type of the arguments, or some other conditions.
{Yacas} allows to define several transformation rules for one and the same function, if the rules are to be applied under different conditions.
Suppose the function {f} is defined, a factorial function:
10 # f(0) <-- 1;
20 # f(n_IsPositiveInteger) <-- n*f(n-1);
Then interaction can look like:
In> f(3)
Out> 6;
In> f(a)
Out> f(a);
If the left hand side is matched by the expression being considered, then the right hand side is evaluated. A subtle but important thing to note is that this means that the whole body of transformation rules is thus re-applied to the right-hand side of the {<–} operator.
Evaluation goes bottom-up, evaluating (simplifying) the lowest parts of a tree first, but for a tree that matches a transformation rule, the substitution essentially means return the result of evaluating the right-hand side. Transformation rules are re-applied, on the right hand side of the transformation rule, and the original expression can be thought of as been substituted by the result of evaluating this right-hand side, which is supposed to be a “simpler” expression, or a result closer to what the user wants.
Internally, the function {f} is built up to resemble the following pseudo-code:
f(n)
{
if (n = 1)
return 1;
else if (IsPositiveInteger(n))
return n*f(n-1);
else return f(n) unevaluated;
}
The transformation rules are thus combined into one big statement that gets executed, with each transformation rule being a if-clause in the statement to be evaluated. Transformation rules can be spread over different files, and combined in functional groups. This adds to the readability. The alternative is to write the full body of each function as one big routine, which becomes harder to maintain as the function becomes larger and larger, and hard or impossible to extend.
One nice feature is that functionality is easy to extend without modifying the original source code:
In> Ln(x*y)
Out> Ln(x*y);
In> Ln(_x*_y) <-- Ln(x) + Ln(y)
Out> True;
In> Ln(x*y)
Out> Ln(x)+Ln(y);
This is generally not advisable, due to the fact that it alters the behavior of the entire system. But it can be useful in some instances. For instance, when introducing a new function {f(x)}, one can decide to define a derivative explicitly, and a way to simplify it numerically:
In> f(_x)_InNumericMode() <-- Exp(x)
Out> True;
In> (Deriv(_x)f(_y)) <-- f(y)*(Deriv(x)y);
Out> True;
In> f(2)
Out> f(2);
In> N(f(2))
Out> 7.3890560989;
In> Exp(2)
Out> Exp(2);
In> N(Exp(2))
Out> 7.3890560989;
In> D(x)f(a*x)
Out> f(a*x)*a;
The “Evaluation is Simplification” hack¶
One of the ideas behind the {Yacas} scripting language is that evaluation is used for simplifying expressions. One consequence of this is that objects can be returned unevaluated when they can not be simplified further. This happens to variables that are not assigned, functions that are not defined, or function invocations where the arguments passed in as parameters are not actually handled by any code in the scripts. An integral that can not be performed by {Yacas} should be returned unevaluated:
In> 2+3
Out> 5;
In> a+b
Out> a+b;
In> Sin(a)
Out> Sin(a);
In> Sin(0)
Out> 0;
In> Integrate(x)Ln(x)
Out> x*Ln(x)-x;
In> Integrate(x)Ln(Sin(x))
Out> Integrate(x)Ln(Sin(x));
In> a!
Out> a!;
In> 3!
Out> 6;
Other languages usually do not allow evaluation of unbound variables, or undefined functions. In {Yacas}, these are interpreted as some yet undefined global variables or functions, and returned unevaluated.
Destructive operations¶
{Yacas} tries to keep as few copies of objects in memory as possible. Thus when assigning the value of one variable to another, a reference is copied, and both variables refer to the same memory, physically. This is relevant for programming; for example, one should use {FlatCopy} to actually make a new copy of an object. Another feature relevant to reference semantics is “destructive operations”; these are functions that modify their arguments rather than work on a copy. Destructive operations on lists are generally recognized because their name starts with “Destructive”, e.g. {DestructiveDelete}. One other destructive operation is assignment of a list element through {list[index] := ...}.
Some examples to illustrate destructive operations on lists:
In> x1:={a,b,c}
Out> {a,b,c};
A list {x1} is created.
In> FullForm(x1)
(List a b c )
Out> {a,b,c};
In> x2:=z:x1
Out> {z,a,b,c};
A new list {x2} is {z} appended to {x1}. The {:} operation creates a copy of {x1} before appending, so {x1} is unchanged by this.
In> FullForm(x2)
(List z a b c )
Out> {z,a,b,c};
In> x2[1]:=y
Out> True;
We have modified the first element of {x2}, but {x1} is still the same.
In> x2
Out> {y,a,b,c};
In> x1
Out> {a,b,c};
In> x2[2]:=A
Out> True;
We have modified the second element of {x2}, but {x1} is still the same.
In> x2
Out> {y,A,b,c};
In> x1
Out> {a,b,c};
In> x2:=x1
Out> {A,b,c};
Now {x2} and {x1} refer to the same list.
In> x2[1]:=A
Out> True;
We have modified the first element of {x2}, and {x1} is also modified.
In> x2
Out> {A,b,c};
In> x1
Out> {A,b,c};
A programmer should always be cautious when dealing with destructive operations. Sometimes it is not desirable to change the original expression. The language deals with it this way because of performance considerations. Operations can be made non-destructive by using {FlatCopy}:
In> x1:={a,b,c}
Out> {a,b,c};
In> DestructiveReverse(x1)
Out> {c,b,a};
In> x1
Out> {a};
In> x1:={a,b,c}
Out> {a,b,c};
In> DestructiveReverse(FlatCopy(x1))
Out> {c,b,a};
In> x1
Out> {a,b,c};
{FlatCopy} copies the elements of an expression only at the top level of nesting. This means that if a list contains sub-lists, they are not copied, but references to them are copied instead:
In> dict1:={}
Out> {};
In> dict1["name"]:="John";
Out> True;
In> dict2:=FlatCopy(dict1)
Out> {{"name","John"}};
In> dict2["name"]:="Mark";
Out> True;
In> dict1
Out> {{"name","Mark"}};
A workaround for this is to use {Subst} to copy the entire tree:
In> dict1:={}
Out> {};
In> dict1["name"]:="John";
Out> True;
In> dict2:=Subst(a,a)(dict1)
Out> {{"name","John"}};
In> dict2["name"]:="Mark";
Out> True;
In> dict1
Out> {{"name","John"}};
In> dict2
Out> {{"name","Mark"}};
Coding style¶
Introduction¶
This chapter intends to describe the coding style and conventions applied in Yacas in order to make sure the engine always returns the correct result. This is an attempt at fending off such errors by combining rule-based programming with a clear coding style which should make help avoid these mistakes.
Interactions of rules and types¶
One unfortunate disadvantage of rule-based programming is that rules can sometimes cooperate in unwanted ways.
One example of how rules can produce unwanted results is the rule
a*0 <-- 0
. This would always seem to be true. However, when a is
a vector, e.g. {a:={b,c,d}}, then a*0
should actually return
{{0,0,0}}, that is, a zero vector. The rule a*0 <-- 0
actually
changes the type of the expression from a vector to an integer! This
can have severe consequences when other functions using this
expressions as an argument expect a vector, or even worse, have a
definition of how to work on vectors, and a different one for working
on numbers.
When writing rules for an operator, it is assumed that the operator
working on arguments, e.g. {Cos} or *
, will always have the same
properties regardless of the arguments. The Taylor series expansion of
$Cos(a)$ is the same regardless of whether $a$ is a real number,
complex number or even a matrix. Certain trigonometric identities
should hold for the {Cos} function, regardless of the type of its
argument.
If a function is defined which does not adhere to these rules when applied to another type, a different function name should be used, to avoid confusion.
By default, if a variable has not been bound yet, it is assumed to be a number. If it is in fact a more complex object, e.g. a vector, then you can declare it to be an “incomplete type” vector, using {Object(“IsVector”,x)} instead of {x}. This expression will evaluate to {x} if and only if {x} is a vector at that moment of evaluation. Otherwise it returns unevaluated, and thus stays an incomplete type.
So this means the type of a variable is numeric unless otherwise stated by the user, using the “{Object}” command. No rules should ever work on incomplete types. It is just meant for delayed simplification.
The topic of implicit type of an object is important, since many rules need to assume something about their argument types.
Ordering of rules¶
The implementor of a rule set can specify the order in which rules should be tried. This can be used to let the engine try more specific rules (those involving more elements in the pattern) before trying less specific rules. Ordering of rules can be also explicitly given by precedence numbers. The Yacas engine will split the expression into subexpressions, and will try to apply all matching rules to a given subexpression in order of precedence.
A rule with precedence 100 is defined by the syntax such as
100 # f(_x + _y) <-- f(x) + f(y);
The problem mentioned above with a rule for vectors and scalars could be solved by making two rules:
- 1. $a*b$ (if $b$ is a vector and $a$ is a number) {<–} return vector of each component multiplied by $a$.
- $a*0$ {<–} $0$
So vector multiplication would be tried first.
The ordering of the precedence of the rules in the standard math scripts is currently:
- 50-60: Args are numbers: directly calculate. These are put in the beginning, so they are tried first. This is useful for quickly obtaining numeric results if all the arguments are numeric already, and symbolic transformations are not necessary.
- 100-199: tautologies. Transformations that do not change the type of the argument, and are always true.
- 200-399: type-specific transformations. Transformations for specific types of objects.
- 400-599: transformations on scalars (variables are assumed to be scalars). Meaning transformations that can potentially change the type of an argument.
Writing new library functions¶
When you implement new library functions, you need to make your new code compatible and consistent with the rest of the library. Here are some relevant considerations.
To evaluate or not to evaluate¶
Currently, a general policy in the library is that functions do nothing unless their arguments actually allow something to be evaluated. For instance, if the function expects a variable name but instead gets a list, or expects a list but instead gets a string, in most cases it seems to be a good idea to do nothing and return unevaluated. The unevaluated expression will propagate and will be easy to spot. Most functions can accomplish this by using type-checking predicates such as {IsInteger} in rules.
When dealing with numbers, Yacas tries to maintain exact answers as much as possible and evaluate to floating-point only when explicitly told so (using {N()}). The general evaluation strategy for numerical functions such as {Sin} or {Gamma} is the following:
- If {InNumericMode()} returns
True
and the arguments are numbers (perhaps complex numbers), the function should evaluate its result in floating-point to current precision. - Otherwise, if the arguments are such that the result can be calculated exactly, it should be evaluated and returned. E.g. {Sin(Pi/2)} returns {1}.
- Otherwise the function should return unevaluated (but usually with its arguments evaluated).
Here are some examples of this behavior:
In> Sin(3)
Out> Sin(3);
In> Gamma(8)
Out> 5040;
In> Gamma(-11/2)
Out> (64*Sqrt(Pi))/10395;
In> Gamma(8/7)
Out> Gamma(8/7);
In> N(Gamma(8/7))
Out> 0.9354375629;
In> N(Gamma(8/7+x))
Out> Gamma(x+1.1428571428);
In> Gamma(12/6+x)
Out> Gamma(x+2);
To implement this behavior, {Gamma} and other mathematical functions
usually have two variants: the “symbolic” one and the “numerical”
one. For instance, there are {Sin} and {MathSin}, {Ln} and
{Internal’LnNum}, {Gamma} and {Internal’GammaNum}. (Here {MathSin}
happens to be a core function but it is not essential.) The
“numerical” functions always evaluate to floating-point results. The
“symbolic” function serves as a front-end; it evaluates when the
result can be expressed exactly, or calls the “numerical” function if
{InNumericMode()} returns True
, and otherwise returns unevaluated.
The “symbolic” function usually has multiple rules while the “numerical” function is usually just one large block of number-crunching code.
Using {N()} and {InNumericMode()} in scripts¶
As a rule, {N()} should be avoided in code that implements basic numerical algorithms. This is because {N()} itself is implemented in the library and it may need to use some of these algorithms. Arbitrary-precision math can be handled by core functions such as {MathDivide}, {MathSin} and so on, without using {N()}. For example, if your code needs to evaluate $Sqrt(Pi)$ to many digits as an intermediate result, it is better to write {MathSqrt(Internal’Pi())} than {N(Sqrt(Pi))} because it makes for faster, more reliable code.
Using {Builtin’Precision’Set()}¶
The usual assumption is that numerical functions will evaluate floating-point results to the currently set precision. For intermediate calculations, a higher working precision is sometimes needed. In this case, your function should set the precision back to the original value at the end of the calculation and round off the result.
Using verbose mode¶
For routines using complicated algorithms, or when evaluation takes a
long time, it is usually helpful to print some diagnostic information,
so that the user can at least watch some progress. The current
convention is that if {InVerboseMode()} returns True
, functions may
print diagnostic information. (But do not print too much!). Verbose
mode is turned on by using the function {V(expression)}. The
expression is evaluated in verbose mode.
Procedural programming or rule-based programming?¶
Two considerations are relevant to this decision. First, whether to use multiple rules with predicates or one rule with multiple {If()}s. Consider the following sample code for the “double factorial” function :math:n!! := n(n-2)...` written using predicates and rules:
1# 0 !! <-- 1;
1# 1 !! <-- 1;
2# (n_IsEven) !! <-- 2^(n/2)*n!;
3# (n_IsOdd) !! <-- n*(n-2)!!;
and an equivalent code with one rule:
n!! := If(n=0 Or n=1, 1,
If(IsEven(n), 2^(n/2)*n!,
If(IsOdd(n), n*(n-2)!!, Hold(n!!)))
);
(Note: This is not the way $n!!$ is implemented in the library.) The first version is a lot more clear. Yacas is very quick in rule matching and evaluation of predicates, so the first version is (marginally) faster. So it seems better to write a few rules with predicates than one rule with multiple {If()} statements.
The second question is whether to use recursion or loops. Recursion makes code more elegant but it is slower and limited in depth. Currently the default recursion depth of $1000$ is enough for most casual calculations and yet catches infinite recursion errors relatively quickly. Because of clearer code, it seems better to use recursion in situations where the number of list elements will never become large. In numerical applications, such as evaluation of Taylor series, recursion usually does not pay off.
Reporting errors¶
Errors occurring because of invalid argument types should be reported only if absolutely necessary. (In the future there may be a static type checker implemented that will make explicit checking unnecessary.)
Errors of invalid values, e.g. a negative argument of real logarithm function, or a malformed list, mean that a human has probably made a mistake, so the errors need to be reported. “Internal errors”, i.e. program bugs, certainly need to be reported.
There are currently two facilities for reporting errors: a “hard” one and a “soft” one.
The “hard” error reporting facility is the function {Check}. For example, if {x}={-1}, then
Check(x>0,"bad x");
will immediately halt the execution of a Yacas script and print the error messsage. This is implemented as a C++ exception. A drawback of this mechanism is that the Yacas stack unwinding is not performed by the Yacas interpreter, so global variables such as {InNumericMode()}, {Verbose}, {Builtin’Precision’Set()} may keep the intermediate values they had been assigned just before the error occurred. Also, sometimes it is better for the program to be able to catch the error and continue.
Todo
the above will hopefully be solved soon, as we can now trap exceptions in the scripts.
The “soft” error reporting is provided by the functions {Assert} and {IsError}, e.g.
Assert("domain", x) x>0;
If(IsError("domain"), ...);
The error will be reported but execution will continue normally until some other function “handles” the error (prints the error message or does something else appropriate for that error). Here the string {“domain”} is the “error type” and {x} will be the information object for this error. The error object can be any expression, but it is probably a good idea to choose a short and descriptive string for the error type.
The function {GetErrorTableau()} returns an associative list that accumulates all reported error objects. When errors are “handled”, their objects should be removed from the list. The utility function {DumpErrors()} is a simple error handler that prints all errors and clears the list. Other handlers are {GetError} and {ClearError}. These functions may be used to handle errors when it is safe to do so.
The “soft” error reporting facility is safer and more flexible than the “hard” facility. However, the disadvantage is that errors are not reported right away and pointless calculations may continue for a while until an error is handled.
Advanced example 1: parsing expressions ({CForm})¶
In this chapter we show how Yacas represents expressions and how one can build functions that work on various types of expressions. Our specific example will be {CForm()}, a standard library function that converts Yacas expressions into C or C++ code. Although the input format of Yacas expressions is already very close to C and perhaps could be converted to C by means of an external text filter, it is instructive to understand how to use Yacas to parse its own expressions and produce the corresponding C code. Here we shall only design the core mechanism of {CForm()} and build a limited version that handles only expressions using the four arithmetic actions.
Recursive parsing of expression trees¶
As we have seen in the tutorial, Yacas represents all expressions as trees, or equivalently, as lists of lists. For example, the expression “{a+b+c+d+e}” is for Yacas a tree of depth 4 that could be visualized as
"+"
a "+"
b "+"
c "+"
d e
or as a nested list: ("+" a ("+" b ("+" c ("+" d e))))
.
Complicated expressions are thus built from simple ones in a general and flexible way. If we want a function that acts on sums of any number of terms, we only need to define this function on a single atom and on a sum of two terms, and the Yacas engine will recursively perform the action on the entire tree.
So our first try is to define rules for transforming an atom and for transforming sums and products. The result of {CForm()} will always be a string. We can use recursion like this:
In> 100 # CForm(a_IsAtom) <-- String(a);
Out> True;
In> 100 # CForm(_a + _b) <-- CForm(a) : \
" + " : CForm(b);
Out> True;
In> 100 # CForm(_a * _b) <-- CForm(a) : \
" * " : CForm(b);
Out> True;
We used the string concatenation operator “{:}” and we added spaces around the binary operators for clarity. All rules have the same precedence 100 because there are no conflicts in rule ordering so far: these rules apply in mutually exclusive cases. Let’s try converting some simple expressions now:
In> CForm(a+b*c);
Out> "a + b * c";
In> CForm(a+b*c*d+e+1+f);
Out> "a + b * c * d + e + 1 + f";
With only three rules, we were able to process even some complicated expressions. How did it work? We could illustrate the steps Yacas went through when simplifying {CForm(a+b*c)} roughly like this:
CForm(a+b*c)
... apply 2nd rule
CForm(a) : " + " : CForm(b*c)
... apply 1st rule and 3rd rule
"a" : " + " : CForm(b) : " * " : CForm(c)
... apply 1st rule
"a" : " + " : "b" : " * " : "c"
... concatenate strings
"a + b * c"
Handling precedence of infix operations¶
It seems that recursion will do all the work for us. The power of recursion is indeed great and extensive use of recursion is built into the design of Yacas. We might now add rules for more operators, for example, the unary addition, subtraction and division:
100 # CForm(+ _a) <-- "+ " : CForm(a);
100 # CForm(- _a) <-- "- " : CForm(a);
100 # CForm(_a - _b) <-- CForm(a) : " - "
: CForm(b);
100 # CForm(_a / _b) <-- CForm(a) : " / "
: CForm(b);
However, soon we find that we forgot about operator precedence. Our simple-minded {CForm()} gives wrong C code for expressions like this:
In> CForm( (a+b) * c );
Out> "a + b * c";
We need to get something like (a+b)*c
in this case. How would we add
a rule to insert parentheses around subexpressions? A simple way out
would be to put parentheses around every subexpression, replacing our
rules by something like this:
100 # CForm(_a + _b) <-- "(" : CForm(a)
: " + " : CForm(b) : ")";
100 # CForm(- _a) <-- "(- " : CForm(a)
: ")";
and so on. This will always produce correct C code, e.g. in our case “((a+b)*c)”, but generally the output will be full of unnecessary parentheses. It is instructive to find a better solution.
We could improve the situation by inserting parentheses only if the higher-order expression requires them; for this to work, we need to make a call such as {CForm(a+b)} aware that the enveloping expression has a multiplication by {c} around the addition {a+b}. This can be implemented by passing an extra argument to {CForm()} that will indicate the precedence of the enveloping operation. A compound expression that uses an infix operator must be bracketed if the precedence of that infix operator is higher than the precedence of the enveloping infix operation.
We shall define an auxiliary function also named “CForm” but with a second argument, the precedence of the enveloping infix operation. If there is no enveloping operation, we shall set the precedence to a large number, e.g. 60000, to indicate that no parentheses should be inserted around the whole expression. The new “CForm(expr, precedence)” will handle two cases: either parentheses are necessary, or unnecessary. For clarity we shall implement these cases in two separate rules. The initial call to “CForm(expr)” will be delegated to “CForm(expr, precedence)”.
The precedence values of infix operators such as “{+}” and “{*}” are defined in the Yacas library but may change in a future version. Therefore, we shall not hard-code these precedence values but instead use the function {OpPrecedence()} to determine them. The new rules for the “{+}” operation could look like this:
PlusPrec := OpPrecedence("+");
100 # CForm(_expr) <-- CForm(expr, 60000);
100 # CForm(_a + _b, _prec)_(PlusPrec>prec)
<-- "(" : CForm(a, PlusPrec) : " + "
: CForm(b, PlusPrec) : ")";
120 # CForm(_a + _b, _prec) <--
CForm(a, PlusPrec) : " + "
: CForm(b, PlusPrec);
and so on. We omitted the predicate for the last rule because it has a later precedence than the preceding rule.
The way we wrote these rules is unnecessarily repetitive but straightforward and it illustrates the central ideas of expression processing in Yacas. The standard library implements {CForm()} essentially in this way. In addition the library implementation supports standard mathematical functions, arrays and so on, and is somewhat better organized to allow easier extensions and avoid repetition of code.
Yacas programming pitfalls¶
No programming language is without programming pitfalls, and {Yacas} has its fair share of pitfalls.
All rules are global¶
All rules are global, and a consequence is that rules can clash or silently shadow each other, if the user defines two rules with the same patterns and predicates but different bodies.
For example:
In> f(0) <-- 1
Out> True;
In> f(x_IsConstant) <-- Sin(x)/x
Out> True;
This can happen in practice, if care is not taken. Here two transformation rules are defined which both have the same precedence (since their precedence was not explicitly set). In that case {Yacas} gets to decide which one to try first. Such problems can also occur where one transformation rule (possibly defined in some other file) has a wrong precedence, and thus masks another transformation rule. It is necessary to think of a scheme for assigning precedences first. In many cases, the order in which transformation rules are applied is important.
In the above example, because {Yacas} gets to decide which rule to try first, it is possible that f(0) invokes the second rule, which would then mask the first so the first rule is never called. Indeed, in {Yacas} version 1.0.51,
In> f(0)
Out> Undefined;
The order the rules are applied in is undefined if the precedences are the same. The precedences should only be the same if order does not matter. This is the case if, for instance, the two rules apply to different argument patters that could not possibly mask each other.
The solution could have been either:
In> 10 # f(0) <-- 1
Out> True;
In> 20 # f(x_IsConstant) <-- Sin(x)/x
Out> True;
In> f(0)
Out> 1;
or
In> f(0) <-- 1
Out> True;
In> f(x_IsConstant)_(x != 0) <-- Sin(x)/x
Out> True;
In> f(0)
Out> 1;
So either the rules should have distinct precedences, or they should have mutually exclusive predicates, so that they do not collide.
Objects that look like functions¶
An expression that looks like a “function”, for example {AbcDef(x,y)}, is in fact either a call to a “core function” or to a “user function”, and there is a huge difference between the behaviors. Core functions immediately evaluate to something, while user functions are really just symbols to which evaluation rules may or may not be applied.
For example:
In> a+b
Out> a+b;
In> 2+3
Out> 5;
In> MathAdd(a,b)
In function "MathAdd" :
bad argument number 1 (counting from 1)
The offending argument a evaluated to a
CommandLine(1) : Invalid argument
In> MathAdd(2,3)
Out> 5;
The {+} operator will return the object unsimplified if the arguments are not numeric. The {+} operator is defined in the standard scripts. {MathAdd}, however, is a function defined in the “core” to performs the numeric addition. It can only do this if the arguments are numeric and it fails on symbolic arguments. (The {+} operator calls {MathAdd} after it has verified that the arguments passed to it are numeric.)
A core function such as {MathAdd} can never return unevaluated, but an operator such as “{+}” is a “user function” which might or might not be evaluated to something.
A user function does not have to be defined before it is used. A consequence of this is that a typo in a function name or a variable name will always go unnoticed. For example:
In> f(x_IsInteger,y_IsInteger) <-- Mathadd(x,y)
Out> True;
In> f(1,2)
Out> Mathadd(1,2);
Here we made a typo: we should have written {MathAdd}, but wrote {Mathadd} instead. {Yacas} happily assumed that we mean a new and (so far) undefined “user function” {Mathadd} and returned the expression unevaluated.
In the above example it was easy to spot the error. But this feature becomes more dangerous when it this mistake is made in a part of some procedure. A call that should have been made to an internal function, if a typo was made, passes silently without error and returns unevaluated. The real problem occurs if we meant to call a function that has side-effects and we not use its return value. In this case we shall not immediately find that the function was not evaluated, but instead we shall encounter a mysterious bug later.
Guessing when arguments are evaluated and when not¶
If your new function does not work as expected, there is a good chance that it happened because you did not expect some expression which is an argument to be passed to a function to be evaluated when it is in fact evaluated, or vice versa.
For example:
In> p:=Sin(x)
Out> Sin(x);
In> D(x)p
Out> Cos(x);
In> y:=x
Out> x;
In> D(y)p
Out> 0;
Here the first argument to the differentiation function is not evaluated, so {y} is not evaluated to {x}, and {D(y)p} is indeed 0.
The confusing effect of {HoldArg}¶
The problem of distinguishing evaluated and unevaluated objects becomes worse when we need to create a function that does not evaluate its arguments.
Since in {Yacas} evaluation starts from the bottom of the expression tree, all “user functions” will appear to evaluate their arguments by default. But sometimes it is convenient to prohibit evaluation of a particular argument (using {HoldArg} or {HoldArgNr}).
For example, suppose we need a function {A(x,y)} that, as a side-effect, assigns the variable {x} to the sum of {x} and {y}. This function will be called when {x} already has some value, so clearly the argument {x} in {A(x,y)} should be unevaluated. It is possible to make this argument unevaluated by putting {Hold()} on it and always calling {A(Hold(x), y)}, but this is not very convenient and easy to forget. It would be better to define {A} so that it always keeps its first argument unevaluated.
If we define a rule base for {A} and declare {HoldArg},
Function() A(x,y);
HoldArg("A", x);
then we shall encounter a difficulty when working with the argument {x} inside of a rule body for {A}. For instance, the simple-minded implementation
A(_x, _y) <-- (x := x+y);
does not work:
In> [ a:=1; b:=2; A(a,b);]
Out> a+2;
In other words, the {x} inside the body of {A(x,y)} did not evaluate to {1} when we called the function {:=}. Instead, it was left unevaluated as the atom {x} on the left hand side of {:=}, since {:=} does not evaluate its left argument. It however evaluates its right argument, so the {y} argument was evaluated to {2} and the {x+y} became {a+2}.
The evaluation of {x} in the body of {A(x,y)} was prevented by the {HoldArg} declaration. So in the body, {x} will just be the atom {x}, unless it is evaluated again. If you pass {x} to other functions, they will just get the atom {x}. Thus in our example, we passed {x} to the function {:=}, thinking that it will get {a}, but it got an unevaluated atom {x} on the left side and proceeded with that.
We need an explicit evaluation of {x} in this case. It can be performed using {Eval}, or with backquoting, or by using a core function that evaluates its argument. Here is some code that illustrates these three possibilities:
A(_x, _y) <-- [ Local(z); z:=Eval(x); z:=z+y; ]
(using explicit evaluation) or
A(_x, _y) <-- `(@x := @x + y);
(using backquoting) or
A(_x, _y) <-- MacroSet(x, x+y);
(using a core function {MacroSet} that evaluates its first argument).
However, beware of a clash of names when using explicit evaluations (as explained above). In other words, the function {A} as defined above will not work correctly if we give it a variable also named {x}. The {LocalSymbols} call should be used to get around this problem.
Another caveat is that when we call another function that does not evaluate its argument, we need to substitute an explicitly evaluated {x} into it. A frequent case is the following: suppose we have a function {B(x,y)} that does not evaluate {x}, and we need to write an interface function {B(x)} which will just call {B(x,0)}. We should use an explicit evaluation of {x} to accomplish this, for example
B(_x) <-- `B(@x,0);
or
B(_x) <-- B @ {x, 0};
Otherwise {B(x,y)} will not get the correct value of its first parameter {x}.
Special behavior of {Hold}, {UnList} and {Eval}¶
When an expression is evaluated, all matching rules are applied to it repeatedly until no more rules match. Thus an expression is “completely” evaluated. There are, however, two cases when recursive application of rules is stopped at a certain point, leaving an expression not “completely” evaluated:
- The expression which is the result of a call to a Yacas core function is not evaluated further, even if some rules apply to it.
- The expression is a variable that has a value assigned to it; for example, the variable {x} might have the expression {y+1} as the value. That value is not evaluated again, so even if {y} has been assigned another value, say, {y=2} a Yacas expression such as {2*x+1} will evaluate to {2*(y+1)+1} and not to {7}. Thus, a variable can have some unevaluated expression as its value and the expression will not be re-evaluated when the variable is used.
The first possibility is mostly without consequence because almost all core functions return a simple atom that does not require further evaluation. However, there are two core functions that can return a complicated expression: {Hold} and {UnList}. Thus, these functions can produce arbitrarily complicated Yacas expressions that will be left unevaluated. For example, the result of
UnList({Sin, 0})
is the same as the result of
Hold(Sin(0))
and is the unevaluated expression {Sin(0)} rather than {0}.
Typically you want to use {UnList} because you need to construct a function call out of some objects that you have. But you need to call {Eval(UnList(...))} to actually evaluate this function call. For example:
In> UnList({Sin, 0})
Out> Sin(0);
In> Eval(UnList({Sin, 0}))
Out> 0;
In effect, evaluation can be stopped with {Hold} or {UnList} and can be explicitly restarted by using {Eval}. If several levels of un-evaluation are used, such as {Hold(Hold(...))}, then the same number of {Eval} calls will be needed to fully evaluate an expression.
In> a:=Hold(Sin(0))
Out> Sin(0);
In> b:=Hold(a)
Out> a;
In> c:=Hold(b)
Out> b;
In> Eval(c)
Out> a;
In> Eval(Eval(c))
Out> Sin(0);
In> Eval(Eval(Eval(c)))
Out> 0;
A function {FullEval} can be defined for “complete” evaluation of expressions, as follows:
LocalSymbols(x,y)
[
FullEval(_x) <-- FullEval(x,Eval(x));
10 # FullEval(_x,_x) <-- x;
20 # FullEval(_x,_y) <-- FullEval(y,Eval(y));
];
Then the example above will be concluded with:
In> FullEval(c);
Out> 0;
Correctness of parameters to functions is not checked¶
Because {Yacas} does not enforce type checking of arguments, it is possible to call functions with invalid arguments. The default way functions in {Yacas} should deal with situations where an action can not be performed, is to return the expression unevaluated. A function should know when it is failing to perform a task. The typical symptoms are errors that seem obscure, but just mean the function called should have checked that it can perform the action on the object.
For example:
In> 10 # f(0) <-- 1;
Out> True;
In> 20 # f(_n) <-- n*f(n-1);
Out> True;
In> f(3)
Out> 6;
In> f(1.3)
CommandLine(1): Max evaluation stack depth reached.
Here, the function {f} is defined to be a factorial function, but the function fails to check that its argument is a positive integer, and thus exhausts the stack when called with a non-integer argument. A better way would be to write
In> 20 # f(n_IsPositiveInteger) <-- n*f(n-1);
Then the function would have returned unevaluated when passed a non-integer or a symbolic expression.
Evaluating variables in the wrong scope¶
There is a subtle problem that occurs when {Eval} is used in a function, combined with local variables. The following example perhaps illustrates it:
In> f1(x):=[Local(a);a:=2;Eval(x);];
Out> True;
In> f1(3)
Out> 3;
In> f1(a)
Out> 2;
Here the last call should have returned {a}, but it returned {2}, because {x} was assigned the value {a}, and {a} was assigned locally the value of {2}, and {x} gets re-evaluated. This problem occurs when the expression being evaluated contains variables which are also local variables in the function body. The solution is to use the {LocalSymbols} function for all local variables defined in the body.
The following illustrates this:
In> f2(x):=LocalSymbols(a)[Local(a);a:=2;Eval(x);];
Out> True;
In> f1(3)
Out> 3;
In> f2(a)
Out> a;
Here {f2} returns the correct result. {x} was assigned the value {a}, but the {a} within the function body is made distinctly different from the one referred to by {x} (which, in a sense, refers to a global {a}), by using {LocalSymbols}.
This problem generally occurs when defining functions that re-evaluate one of its arguments, typically functions that perform a loop of some sort, evaluating a body at each iteration.
Debugging in Yacas¶
Introduction¶
When writing a code segment, it is generally a good idea to separate the problem into many small functions. Not only can you then reuse these functions on other problems, but it makes debugging easier too.
For debugging a faulty function, in addition to the usual trial-and-error method and the “print everything” method, Yacas offers some trace facilities. You can try to trace applications of rules during evaluation of the function ({TraceRule()}, {TraceExp()}) or see the stack after an error has occurred ({TraceStack()}).
There is also an interactive debugger, which shall be introduced in this chapter.
Finally, you may want to run a debugging version of Yacas. This version of the executable maintains more information about the operations it performs, and can report on this.
This chapter will start with the interactive debugger, as it is the easiest and most useful feature to use, and then proceed to explain the trace and profiling facilities. Finally, the internal workings of the debugger will be explained. It is highly customizable (in fact, most of the debugging code is written in Yacas itself), so for bugs that are really difficult to track one can write custom code to track it.
The trace facilities¶
The trace facilities are:
- {TraceExp} : traces the full expression, showing all calls to user- or system-defined functions, their arguments, and the return values. For complex functions this can become a long list of function calls.
- {TraceRule} : traces one single user-defined function (rule). It shows each invocation, the arguments passed in, and the returned values. This is useful for tracking the behavior of that function in the environment it is intended to be used in.
- {TraceStack} : shows a few last function calls before an error has occurred.
- {Profile} : report on statistics (number of times functions were called, etc.). Useful for performance analysis.
The online manual pages (e.g. {?TraceStack}) have more information about the use of these functions.
An example invocation of {TraceRule} is
In> TraceRule(x+y)2+3*5+4;
Which should then show something to the effect of
TrEnter(2+3*5+4);
TrEnter(2+3*5);
TrArg(2,2);
TrArg(3*5,15);
TrLeave(2+3*5,17);
TrArg(2+3*5,17);
TrArg(4,4);
TrLeave(2+3*5+4,21);
Out> 21;
Custom evaluation facilities¶
Yacas supports a special form of evaluation where hooks are placed when evaluation enters or leaves an expression.
This section will explain the way custom evaluation is supported in {Yacas}, and will proceed to demonstrate how it can be used by showing code to trace, interactively step through, profile, and write custom debugging code.
Debugging, tracing and profiling has been implemented in the debug.rep/ module, but a simplification of that code will be presented here to show the basic concepts.
The basic infrastructure for custom evaluation¶
The name of the function is {CustomEval}, and the calling sequence is:
CustomEval(enter,leave,error,expression);
Here, {expression} is the expression to be evaluated, {enter} some expression that should be evaluated when entering an expression, and {leave} an expression to be evaluated when leaving evaluation of that expression.
The {error} expression is evaluated when an error occurred. If an error occurs, this is caught high up, the {error} expression is called, and the debugger goes back to evaluating {enter} again so the situation can be examined. When the debugger needs to stop, the {error} expression is the place to call {CustomEval’Stop()} (see explanation below).
The {CustomEval} function can be used to write custom debugging tools. Examples are:
- a trace facility following entering and leaving functions
- interactive debugger for stepping through evaluation of an expression.
- profiler functionality, by having the callback functions do the bookkeeping on counts of function calls for instance.
In addition, custom code can be written to for instance halt evaluation and enter interactive mode as soon as some very specific situation occurs, like “stop when function foo is called while the function bar is also on the call stack and the value of the local variable x is less than zero”.
As a first example, suppose we define the functions TraceEnter(), TraceLeave() and {TraceExp()} as follows:
TraceStart() := [indent := 0;];
TraceEnter() :=
[
indent++;
Space(2*indent);
Echo("Enter ",CustomEval'Expression());
];
TraceLeave() :=
[
Space(2*indent);
Echo("Leave ",CustomEval'Result());
indent--;
];
Macro(TraceExp,{expression})
[
TraceStart();
CustomEval(TraceEnter(),
TraceLeave(),
CustomEval'Stop(),@expression);
];
allows us to have tracing in a very basic way. We can now call:
In> TraceExp(2+3)
Enter 2+3
Enter 2
Leave 2
Enter 3
Leave 3
Enter IsNumber(x)
Enter x
Leave 2
Leave True
Enter IsNumber(y)
Enter y
Leave 3
Leave True
Enter True
Leave True
Enter MathAdd(x,y)
Enter x
Leave 2
Enter y
Leave 3
Leave 5
Leave 5
Out> 5;
This example shows the use of {CustomEval’Expression} and {CustomEval’Result}. These functions give some extra access to interesting information while evaluating the expression. The functions defined to allow access to information while evaluating are:
- {CustomEval’Expression()} - return expression currently on the top call stack for evaluation.
- {CustomEval’Result()} - when the {leave} argument is called this function returns what the evaluation of the top expression will return.
- {CustomEval’Locals()} - returns a list with the current local variables.
- {CustomEval’Stop()} - stop debugging execution
A simple interactive debugger
The following code allows for simple interactive debugging:
DebugStart():=
[
debugging:=True;
breakpoints:={};
];
DebugRun():= [debugging:=False;];
DebugStep():=[debugging:=False;nextdebugging:=True;];
DebugAddBreakpoint(fname_IsString) <--
[ breakpoints := fname:breakpoints;];
BreakpointsClear() <-- [ breakpoints := {};];
Macro(DebugEnter,{})
[
Echo(">>> ",CustomEval'Expression());
If(debugging = False And
IsFunction(CustomEval'Expression()) And
Contains(breakpoints,
Type(CustomEval'Expression())),
debugging:=True);
nextdebugging:=False;
While(debugging)
[
debugRes:=
Eval(FromString(
ReadCmdLineString("Debug> "):";")
Read());
If(debugging,Echo("DebugOut> ",debugRes));
];
debugging:=nextdebugging;
];
Macro(DebugLeave,{})
[
Echo(CustomEval'Result(),
" <-- ",CustomEval'Expression());
];
Macro(Debug,{expression})
[
DebugStart();
CustomEval(DebugEnter(),
DebugLeave(),
debugging:=True,@expression);
];
This code allows for the following interaction:
In> Debug(2+3)
>>> 2+3
Debug>
The console now shows the current expression being evaluated, and a debug prompt for interactive debugging. We can enter {DebugStep()}, which steps to the next expression to be evaluated:
Debug> DebugStep();
>>> 2
Debug>
This shows that in order to evaluate {2+3} the interpreter first needs to evaluate {2}. If we step further a few more times, we arrive at:
>>> IsNumber(x)
Debug>
Now we might be curious as to what the value for {x} is. We can dynamically obtain the value for {x} by just typing it on the command line.
>>> IsNumber(x)
Debug> x
DebugOut> 2
{x} is set to {2}, so we expect {IsNumber} to return True
. Stepping
again:
Debug> DebugStep();
>>> x
Debug> DebugStep();
2 <-- x
True <-- IsNumber(x)
>>> IsNumber(y)
So we see this is true. We can have a look at which local variables are currently available by calling {CustomEval’Locals()}:
Debug> CustomEval'Locals()
DebugOut> {arg1,arg2,x,y,aLeftAssign,aRightAssign}
And when bored, we can proceed with {DebugRun()} which will continue the debugger until finished in this case (a more sophisticated debugger can add breakpoints, so running would halt again at for instance a breakpoint).
Debug> DebugRun()
>>> y
3 <-- y
True <-- IsNumber(y)
>>> True
True <-- True
>>> MathAdd(x,y)
>>> x
2 <-- x
>>> y
3 <-- y
5 <-- MathAdd(x,y)
5 <-- 2+3
Out> 5;
The above bit of code also supports primitive breakpointing, in that one can instruct the evaluator to stop when a function will be entered. The debugger then stops just before the arguments to the function are evaluated. In the following example, we make the debugger stop when a call is made to the {MathAdd} function:
In> Debug(2+3)
>>> 2+3
Debug> DebugAddBreakpoint("MathAdd")
DebugOut> {"MathAdd"}
Debug> DebugRun()
>>> 2
2 <-- 2
>>> 3
3 <-- 3
>>> IsNumber(x)
>>> x
2 <-- x
True <-- IsNumber(x)
>>> IsNumber(y)
>>> y
3 <-- y
True <-- IsNumber(y)
>>> True
True <-- True
>>> MathAdd(x,y)
Debug>
The arguments to {MathAdd} can now be examined, or execution continued.
One great advantage of defining much of the debugger in script code can be seen in the {DebugEnter} function, where the breakpoints are checked, and execution halts when a breakpoint is reached. In this case the condition for stopping evaluation is rather simple: when entering a specific function, stop. However, nothing stops a programmer from writing a custom debugger that could stop on any condition, halting at e very special case.
Profiling¶
A simple profiler that counts the number of times each function is called can be written such:
ProfileStart():=
[
profilefn:={};
];
10 # ProfileEnter()
_(IsFunction(CustomEval'Expression())) <--
[
Local(fname);
fname:=Type(CustomEval'Expression());
If(profilefn[fname]=Empty,profilefn[fname]:=0);
profilefn[fname] := profilefn[fname]+1;
];
Macro(Profile,{expression})
[
ProfileStart();
CustomEval(ProfileEnter(),True,
CustomEval'Stop(),@expression);
ForEach(item,profilefn)
Echo("Function ",item[1]," called ",
item[2]," times");
];
which allows for the interaction:
In> Profile(2+3)
Function MathAdd called 1 times
Function IsNumber called 2 times
Function + called 1 times
Out> True;
Advanced example 2: implementing a non-commutative algebra¶
We need to understand how to simplify expressions in Yacas, and the best way is to try writing our own algebraic expression handler. In this chapter we shall consider a simple implementation of a particular non-commutative algebra called the Heisenberg algebra. This algebra was introduced by Dirac to develop quantum field theory. We won’t explain any physics here, but instead we shall to delve somewhat deeper into the workings of Yacas.
The problem¶
Suppose we want to define special symbols $A(k)$ and $B(k)$ that we can multiply with each other or by a number, or add to each other, but not commute with each other, i.e. $A(k)*B(k) != B(k)*A(k)$. Here $k$ is merely a label to denote that $A(1)$ and $A(2)$ are two different objects. (In physics, these are called “creation” and “annihilation” operators for “bosonic quantum fields”.) Yacas already assumes that the usual multiplication operator “{*}” is commutative. Rather than trying to redefine {*}, we shall introduce a special multiplication sign “{**}” that we shall use with the objects $A(k)$ and $B(k)$; between usual numbers this would be the same as normal multiplication. The symbols $A(k)$, $B(k)$ will never be evaluated to numbers, so an expression such as {2 ** A(k1) ** B(k2) ** A(k3)} is just going to remain like that. (In physics, commuting numbers are called “classical quantities” or “c-numbers” while non-commuting objects made up of A(k) and B(k) are called “quantum quantities” or “q-numbers”.) There are certain commutation relations for these symbols: the $A$’s commute between themselves, $A(k)*A(l) = A(l)*A(k)$, and also the $B$’s, $B(k)*B(l) = B(l)*B(k)$. However, the $A$’s don’t commute with the $B$’s: $A(k)*B(l) - B(l)*A(k) = delta(k-l)$. Here the “{delta}” is a “classical” function (called the “Dirac $delta$-function”) but we aren’t going to do anything about it, just leave it symbolic.
We would like to be able to manipulate such expressions, expanding brackets, collecting similar terms and so on, while taking care to always keep the non-commuting terms in the correct order. For example, we want Yacas to automatically simplify {2**B(k1)**3**A(k2)} to {6**B(k1)**A(k2)}. Our goal is not to implement a general package to tackle complicated non-commutative operations; we merely want to teach Yacas about these two kinds of “quantum objects” called {A(k)} and {B(k)}, and we shall define one function that a physicist would need to apply to these objects. This function applied to any given expression containing $A$’s and $B$’s will compute something called a “vacuum expectation value”, or “VEV” for short, of that expression. This function has “classical”, i.e. commuting, values and is defined as follows: VEV of a commuting number is just that number, e.g. $VEV(4) = 4$, $VEV(delta(k-l)) = delta(k-l)$; and $VEV(X*A(k)) = 0$, $VEV(B(k)*X) = 0$ where $X$ is any expression, commutative or not. It is straightforward to compute VEV of something that contains $A$’s and $B$’s: one just uses the commutation relations to move all $B$’s to the left of all $A$’s, and then applies the definition of VEV, simply throwing out any remaining q-numbers.
First steps¶
The first thing that comes to mind when we start implementing this in Yacas is to write a rule such as
10 # A(_k)**B(_l) <-- B(l)**A(k)
+ delta(k-l);
However, this is not going to work right away. In fact this will
immediately give a syntax error because Yacas doesn’t know yet about
the new multiplication **
. Let’s fix that: we shall define a new
infix operator with the same precedence as multiplication.
RuleBase("**", {x,y});
Infix("**", OpPrecedence("*"));
Now we can use this new multiplication operator in expressions, and it doesn’t evaluate to anything – exactly what we need. But we find that things don’t quite work:
In> A(_k)**B(_l) <-- B(l)**A(k)+delta(k-l);
Out> True;
In> A(x)**B(y)
Out> B(l)**A(k)+delta(k-l);
Yacas doesn’t grok that {delta(k)}, {A(k)} and {B(k)} are functions. This can be fixed by declaring
RuleBase("A", {k});
RuleBase("B", {k});
RuleBase("delta", {k});
Now things work as intended:
In> A(y)**B(z)*2
Out> 2*(B(z)**A(y)+delta(y-z));
Structure of expressions¶
Are we done yet? Let’s try to calculate more things with our $A$’s and $B$’s:
In> A(k)*2**B(l)
Out> 2*A(k)**B(l);
In> A(x)**A(y)**B(z)
Out> A(x)**A(y)**B(z);
In> (A(x)+B(x))**2**B(y)*3
Out> 3*(A(x)+B(x))**2**B(y);
After we gave it slightly more complicated input, Yacas didn’t fully evaluate expressions containing the new {**} operation: it didn’t move constants {2} and {3} together, didn’t expand brackets, and, somewhat mysteriously, it didn’t apply the rule in the first line above – although it seems like it should have. Before we hurry to fix these things, let’s think some more about how Yacas represents our new expressions. Let’s start with the first line above:
In> FullForm( A(k)*2**B(l) )
(** (* 2 (A k ))(B l ))
Out> 2*A(k)**B(l);
What looks like {2*A(k)**B(l)} on the screen is really {(2*A(k)) ** B(l)} inside Yacas. In other words, the commutation rule didn’t apply because there is no subexpression of the form {A(...)**B(...)} in this expression. It seems that we would need many rules to exhaust all ways in which the adjacent factors {A(k)} and {B(l)} might be divided between subexpressions. We run into this difficulty because Yacas represents all expressions as trees of functions and leaves the semantics to us. To Yacas, the “{*}” operator is fundamentally no different from any other function, so {(a*b)*c} and {a*(b*c)} are two basically different expressions. It would take a considerable amount of work to teach Yacas to recognize all such cases as identical. This is a design choice and it was made by the author of Yacas to achieve greater flexibility and extensibility.
A solution for this problem is not to write rules for all possible cases (there are infinitely many cases) but to systematically reduce expressions to a <i>canonical form</i>. “Experience has shown that” (a phrase used when we can’t come up with specific arguments) symbolic manipulation of unevaluated trees is not efficient unless these trees are forced to a pattern that reflects their semantics.
We should choose a canonical form for all such expressions in a way that makes our calculations – namely, the function {VEV()} – easier. In our case, our expressions contain two kinds of ingredients: normal, commutative numbers and maybe a number of noncommuting symbols {A(k)} and {B(k)} multiplied together with the “{**}” operator. It will not be possible to divide anything by $A(k)$ or $B(k)$ – such division is undefined.
A possible canonical form for expressions with A’s and B’s is the following. All commutative numbers are moved to the left of the expression and grouped together as one factor; all non-commutative products are simplified to a single chain, all brackets expanded. A canonical expression should not contain any extra brackets in its non-commutative part. For example, (A(x)+B(x)*x)**B(y)*y**A(z) should be regrouped as a sum of two terms, (y)**(A(x)**(B(y))**A(z)) and (x*y)**(B(x)**(B(y))**A(z)). Here we wrote out all parentheses to show explicitly which operations are grouped. (We have chosen the grouping of non-commutative factors to go from left to right, however this does not seem to be an important choice.) On the screen this will look simply {y ** A(x) ** B(y)} and {x*y** B(x) ** B(y) ** A(z)} because we have defined the precedence of the “**” operator to be the same as that of the normal multiplication, so Yacas won’t insert any more parentheses.
This canonical form will allow Yacas to apply all the usual rules on the commutative factor while cleanly separating all non-commutative parts for special treatment. Note that a commutative factor such as {2*x} will be multiplied by a single non-commutative piece with “{**}”.
The basic idea behind the “canonical form” is this: we should define our evaluation rules in such a way that any expression containing {A(k)} and {B(k)} will be always automatically reduced to the canonical form after one full evaluation. All functions on our new objects will assume that the object is already in the canonical form and should return objects in the same canonical form.
Implementing the canonical form¶
Now that we have a design, let’s look at some implementation issues. We would like to write evaluation rules involving the new operator “{**}” as well as the ordinary multiplications and additions involving usual numbers, so that all “classical” numbers and all “quantum” objects are grouped together separately. This should be accomplished with rules that expand brackets, exchange the bracketing order of expressions and move commuting factors to the left. For now, we shall not concern ourselves with divisions and subtractions.
First, we need to distinguish “classical” terms from “quantum” ones. For this, we shall define a predicate {IsQuantum()} recursively, as follows:
/* Predicate IsQuantum(): will return
True if the expression contains A(k)
or B(k) and False otherwise */
10 # IsQuantum(A(_x)) <-- True;
10 # IsQuantum(B(_x)) <-- True;
/* Result of a binary operation may
be Quantum */
20 # IsQuantum(_x + _y) <-- IsQuantum(x)
Or IsQuantum(y);
20 # IsQuantum(+ _y) <-- IsQuantum(y);
20 # IsQuantum(_x * _y) <-- IsQuantum(x)
Or IsQuantum(y);
20 # IsQuantum(_x ** _y) <-- IsQuantum(x)
Or IsQuantum(y);
/* If none of the rules apply, the
object is not Quantum */
30 # IsQuantum(_x) <-- False;
Now we shall construct rules that implement reduction to the canonical form. The rules will be given precedences, so that the reduction proceeds by clearly defined steps. All rules at a given precedence benefit from all simplifications at earlier precedences.
/* First, replace * by ** if one of the
factors is Quantum to guard against
user error */
10 # (_x * _y)_(IsQuantum(x) Or
IsQuantum(y)) <-- x ** y;
/* Replace ** by * if neither of the
factors is Quantum */
10 # (_x ** _y)_(Not(IsQuantum(x) Or
IsQuantum(y))) <-- x * y;
/* Now we are guaranteed that ** is
used between Quantum values */
/* Expand all brackets involving
Quantum values */
15 # (_x + _y) ** _z <-- x ** z + y ** z;
15 # _z ** (_x + _y) <-- z ** x + z ** y;
/* Now we are guaranteed that there are
no brackets next to "**" */
/* Regroup the ** multiplications
toward the right */
20 # (_x ** _y) ** _z <-- x ** (y ** z);
/* Move classical factors to the left:
first, inside brackets */
30 # (x_IsQuantum ** _y)_(Not(IsQuantum(y)))
<-- y ** x;
/* Then, move across brackets:
y and z are already ordered
by the previous rule */
/* First, if we have Q ** (C ** Q) */
35 # (x_IsQuantum ** (_y ** _z))
_(Not(IsQuantum(y))) <-- y ** (x ** z);
/* Second, if we have C ** (C ** Q) */
35 # (_x ** (_y ** _z))_(Not(IsQuantum(x)
Or IsQuantum(y))) <-- (x*y) ** z;
After we execute this in Yacas, all expressions involving additions and multiplications are automatically reduced to the canonical form. Extending these rules to subtractions and divisions is straightforward.
Implementing commutation relations¶
But we still haven’t implemented the commutation relations. It is perhaps not necessary to have commutation rules automatically applied at each evaluation. We shall define the function {OrderBA()} that will bring all $B$’s to the left of all $A$’s by using the commutation relation. (In physics, this is called “normal-ordering”.) Again, our definition will be recursive. We shall assign it a later precedence than our quantum evaluation rules, so that our objects will always be in canonical form. We need a few more rules to implement the commutation relation and to propagate the ordering operation down the expression tree:
/* Commutation relation */
40 # OrderBA(A(_k) ** B(_l))
<-- B(l)**A(k) + delta(k-l);
40 # OrderBA(A(_k) ** (B(_l) ** _x))
<-- OrderBA(OrderBA(A(k)**B(l)) ** x);
/* Ordering simple terms */
40 # OrderBA(_x)_(Not(IsQuantum(x))) <-- x;
40 # OrderBA(A(_k)) <-- A(k);
40 # OrderBA(B(_k)) <-- B(k);
/* Sums of terms */
40 # OrderBA(_x + _y) <-- OrderBA(x)
+ OrderBA(y);
/* Product of a classical and
a quantum value */
40 # OrderBA(_x ** _y)_(Not(IsQuantum(x)))
<-- x ** OrderBA(y);
/* B() ** X : B is already at left,
no need to order it */
50 # OrderBA(B(_k) ** _x)<-- B(k)
** OrderBA(x);
/* A() ** X : need to order X first */
50 # OrderBA(A(_k) ** _x) <-- OrderBA(A(k)
** OrderBA(x));
These rules seem to be enough for our purposes. Note that the commutation relation is implemented by the first two rules; the first one is used by the second one which applies when interchanging factors A and B separated by brackets. This inconvenience of having to define several rules for what seems to be “one thing to do” is a consequence of tree-like structure of expressions in Yacas. It is perhaps the price we have to pay for conceptual simplicity of the design.
Avoiding infinite recursion¶
However, we quickly discover that our definitions don’t work. Actually, we have run into a difficulty typical of rule-based programming:
In> OrderBA(A(k)**A(l))
Error on line 1 in file [CommandLine]
Line error occurred on:
>>>
Max evaluation stack depth reached.
Please use MaxEvalDepth to increase the
stack size as needed.
This error message means that we have created an infinite recursion. It is easy to see that the last rule is at fault: it never stops applying itself when it operates on a term containing only $A$’s and no $B$’s. When encountering a term such as {A()**X}, the routine cannot determine whether {X} has already been normal-ordered or not, and it unnecessarily keeps trying to normal-order it again and again. We can circumvent this difficulty by using an auxiliary ordering function that we shall call {OrderBAlate()}. This function will operate only on terms of the form {A()**X} and only after {X} has been ordered. It will not perform any extra simplifications but instead delegate all work to {OrderBA()}.
50 # OrderBA(A(_k) ** _x) <-- OrderBAlate(
A(k) ** OrderBA(x));
55 # OrderBAlate(_x + _y) <-- OrderBAlate(
x) + OrderBAlate(y);
55 # OrderBAlate(A(_k) ** B(_l)) <--
OrderBA(A(k)**B(l));
55 # OrderBAlate(A(_k) ** (B(_l) ** _x))
<-- OrderBA(A(k)**(B(l)**x));
60 # OrderBAlate(A(_k) ** _x) <-- A(k)**x;
65 # OrderBAlate(_x) <-- OrderBA(x);
Now {OrderBA()} works as desired.
Implementing VEV()¶
Now it is easy to define the function {VEV()}. This function should first execute the normal-ordering operation, so that all $B$’s move to the left of $A$’s. After an expression is normal-ordered, all of its “quantum” terms will either end with an $A(k)$ or begin with a $B(k)$, or both, and {VEV()} of those terms will return $0$. The value of {VEV()} of a non-quantum term is just that term. The implementation could look like this:
100 # VEV(_x) <-- VEVOrd(OrderBA(x));
/* Everything is expanded now,
deal term by term */
100 # VEVOrd(_x + _y) <-- VEVOrd(x)
+ VEVOrd(y);
/* Now cancel all quantum terms */
110 # VEVOrd(x_IsQuantum) <-- 0;
/* Classical terms are left */
120 # VEVOrd(_x) <-- x;
To avoid infinite recursion in calling {OrderBA()}, we had to introduce an auxiliary function {VEVOrd()} that assumes its argument to be ordered.
Finally, we try some example calculations to test our rules:
In> OrderBA(A(x)*B(y))
Out> B(y)**A(x)+delta(x-y);
In> OrderBA(A(x)*B(y)*B(z))
Out> B(y)**B(z)**A(x)+delta(x-z)**B(y)
+delta(x-y)**B(z);
In> VEV(A(k)*B(l))
Out> delta(k-l);
In> VEV(A(k)*B(l)*A(x)*B(y))
Out> delta(k-l)*delta(x-y);
In> VEV(A(k)*A(l)*B(x)*B(y))
Out> delta(l-y)*delta(k-x)+delta(l-x)
*delta(k-y);
Things now work as expected. Yacas’s {Simplify()} facilities can be used on the result of {VEV()} if it needs simplification.
The Yacas Book of Algorithms¶
This book is a detailed description of the algorithms used in the Yacas system for exact symbolic and arbitrary-precision numerical computations. Very few of these algorithms are new, and most are well-known. The goal of this book is to become a compendium of all relevant issues of design and implementation of these algorithms.
Adaptive function plotting¶
Here we consider plotting of functions \(y=f(x)\).
There are two tasks related to preparation of plots of functions: first, to produce the numbers required for a plot, and second, to draw a plot with axes, symbols, a legend, perhaps additional illustrations and so on. Here we only concern ourselves with the first task, that of preparation of the numerical data for a plot. There are many plotting programs that can read a file with numbers and plot it in any desired manner.
Generating data for plots of functions generally does not require high-precision calculations. However, we need an algorithm that can be adjusted to produce data to different levels of precision. In some particularly ill-behaved cases, a precise plot will not be possible and we would not want to waste time producing data that is too accurate for what it is worth.
A simple approach to plotting would be to divide the interval into many equal subintervals and to evaluate the function on the resulting grid. Precision of the plot can be adjusted by choosing a larger or a smaller number of points.
However, this approach is not optimal. Sometimes a function changes rapidly near one point but slowly everywhere else. For example, \(f(x)=\frac{1}{x}\) changes very quickly at small \(x\). Suppose we need to plot this function between \(0\) and \(100\). It would be wasteful to use the same subdivision interval everywhere: a finer grid is only required over a small portion of the plotting range near \(x=0\).
The adaptive plotting routine Plot2D'adaptive()
uses a simple
algorithm to select the optimal grid to approximate a function of one
argument \(f(x)\). The algorithm repeatedly subdivides the grid
intervals near points where the existing grid does not represent the
function well enough. A similar algorithm for adaptive grid
refinement could be used for numerical integration. The idea is that
plotting and numerical integration require the same kind of detailed
knowledge about the behavior of the function.
The algorithm first splits the interval into a specified initial number of equal subintervals, and then repeatedly splits each subinterval in half until the function is well enough approximated by the resulting grid. The integer parameter {depth} gives the maximum number of binary splittings for a given initial interval; thus, at most \(2^depth\) additional grid points will be generated. The function {Plot2D’adaptive} should return a list of pairs of points {{{x1,y1}, {x2,y2}, ...}} to be used directly for plotting.
The adaptive plotting algorithm works like this:
- Given an interval (\(a\), \(c\)), we split it in half, \(b:=(a+c)/2\) and first compute \(f(x)\) at five grid points \(a\), \(a_1:=(a+b)/2\), \(b\), \(b_1:=(b+c)/2\), \(c\).
- If currently \(depth <= 0\), return this list of five points and values because we cannot refine the grid any more.
- Otherwise, check that the function does not oscillate too rapidly
on the interval \([a, c]\). The formal criterion is that the
five values are all finite and do not make a “zigzag” pattern such
as \((1,3,2,3,1)\). More formally, we use the following
procedure: For each three consecutive values, write “1” if the
middle value is larger than the other two, or if it is smaller than
the other two, or if one of them is not a number (e.g.
Infinity
orUndefined}
. If we have at most two ones now, then we consider the change of values to be “slow enough”. Otherwise it is not “slow enough”. In this case we need to refine the grid; go to step 5. Otherwise, go to step 4. - Check that the function values are smooth enough through the interval. Smoothness is controlled by a parameter \(\epsilon\). The meaning of the parameter \(\epsilon\) is the (relative) error of the numerical approximation of the integral of \(f(x)\) by the grid. A good heuristic value of \(\epsilon\) is 1/(the number of pixels on the screen) because it means that no pixels will be missing in the area under the graph. For this to work we need to make sure that we are actually computing the area under the graph; so we define \(g(x):=f(x)-f[0]\) where \(f[0]\) is the minimum of the values of \(f(x)\) on the five grid points \(a\), \(a_1\), \(b\), \(b_1\), and \(c\); the function \(g(x)\) is nonnegative and has the minimum value 0. Then we compute two different Newton-Cotes quadratures for \(\int_b^{b_1}g(x)\,\mathrm{d}x\) using these five points. (Asymmetric quadratures are chosen to avoid running into an accidental symmetry of the function; the first quadrature uses points \(a\), \(a_1\), \(b\), \(b_1\) and the second quadrature uses \(b\), \(b_1\), \(c\).) If the absolute value of the difference between these quadratures is less than \(epsilon\) * (value of the second quadrature), then we are done and we return the list of these five points and values.
- Otherwise, we need to refine the grid. We compute
Plot2D'adaptive()
recursively for the two halves of the interval, that is, for \([a, b]\) and \([b, c]\). We also decrease \(depth\) by 1 and multiply \(\epsilon\) by 2 because we need to maintain a constant absolute precision and this means that the relative error for the two subintervals can be twice as large. The resulting two lists for the two subintervals are concatenated (excluding the double value at point \(b\)) and returned.
This algorithm works well if the initial number of points and the \(depth\) parameter are large enough. These parameters can be adjusted to balance the available computing time and the desired level of detail in the resulting plot.
Singularities in the function are handled by the step 3. Namely, the
change in the sequence \(a\), \(a_1\), \(b\), \(b_1\),
\(c\) is always considered to be “too rapid” if one of these
values is a non-number (e.g. Infinity
or Undefined
). Thus,
the interval immediately adjacent to a singularity will be plotted at
the highest allowed refinement level. When preparing the plotting
data, the singular points are simply not printed to the data file, so
that a plotting programs does not encounter any problems.
Newton-Cotes quadratures¶
The meaning of Newton-Cotes quadrature coefficients is that an integral of a function \(f(x)\) is approximated by a sum
where \(a_k\) are the grid points, \(h:=a_1-a_0\) is the grid step, and \(c_k\) are the quadrature coefficients. It may seem surprising, but these coefficients \(c_k\) are independent of the function \(f(x)\) and can be precomputed in advance for a given grid \(a_k\). [The quadrature coefficients do depend on the relative separations of the grid. Here we assume a uniform grid with a constant step \(h=a_k-a_{k-1}\). Quadrature coefficients can also be found for non-uniform grids.]
The coefficients \(c_k\) for grids with a constant step \(h\) can be found, for example, by solving the following system of equations,
for \(p=0,1,\ldots,n\). This system of equations means that the quadrature correctly gives the integrals of \(p+1\) functions \(f(x)=x^p\), \(p=0,1,\ldots,n\) over the interval \((0,n)\). The solution of this system always exists and gives quadrature coefficients as rational numbers. For example, the well-known Simpson quadrature \(c_0=1/3,c_1=4/3,c_2=1/3\) is obtained with \(n=2\). An example of using this quadrature is the approximation
Newton-Cotes quadratures for partial intervals¶
In the same way it is possible to find quadratures for the integral over a subinterval rather than over the whole interval of \(x\). In the current implementation of the adaptive plotting algorithm, two quadratures are used: the 3-point quadrature (\(n=2\)) and the 4-point quadrature (\(n=3\)) for the integral over the first subinterval, \(\int_{a_0}^{a_1}(x,a[0],a[1])f(x)\,\mathrm{d}x\). Their coefficients are \(\left(\frac{5}{12},\frac{2}{3},-\frac{1}{12}\right)\) and \(\left(\frac{3}{8},\frac{19}{24},-\frac{5}{24},\frac{1}{24}\right)\). An example of using the first of these subinterval quadratures would be the approximation
These quadratures are intentionally chosen to be asymmetric to avoid an accidental cancellation when the function \(f(x)\) itself is symmetric. (Otherwise the error estimate could accidentally become exactly zero.)
Surface plotting¶
Here we consider plotting of functions \(z=f(x,y)\).
The task of surface plotting is to obtain a picture of a two-dimensional surface as if it were a solid object in three dimensions. A graphical representation of a surface is a complicated task. Sometimes it is required to use particular coordinates or projections, to colorize the surface, to remove hidden lines and so on. We shall only be concerned with the task of obtaining the data for a plot from a given function of two variables \(f(x,y)\). Specialized programs can take a text file with the data and let the user interactively produce a variety of surface plots.
The currently implemented algorithm in the function Plot3DS()
is
very similar to the adaptive plotting algorithm for two-dimensional
plots. A given rectangular plotting region \(a_1\leq x\leq a_2\),
\(b_1\leq y\leq b_2\) is subdivided to produce an equally spaced
rectangular grid of points. This is the initial grid which will be
adaptively refined where necessary. The refinement algorithm will
divide a given rectangle in four quarters if the available function
values indicate that the function does not change smoothly enough on
that rectangle.
The criterion of a “smooth enough” change is very similar to the procedure outlined in the previous section. The change is “smooth enough” if all points are finite, nonsingular values, and if the integral of the function over the rectangle is sufficiently well approximated by a certain low-order “cubature” formula.
The two-dimensional integral of the function is estimated using the following 5-point Newton-Cotes cubature:
1/12 | 0 | 1/12 |
0 | 2/3 | 0 |
1/12 | 0 | 1/12 |
An example of using this cubature would be the approximation
Similarly, an 8-point cubature with zero sum is used to estimate the error:
-1/3 | 2/3 | 1/6 |
-1/6 | -2/3 | -1/2 |
1/2 | 0 | 1/3 |
This set of coefficients was intentionally chosen to be asymmetric to avoid possible exact cancellations when the function itself is symmetric.
One minor problem with adaptive surface plotting is that the resulting
set of points may not correspond to a rectangular grid in the
parameter space \((x,y)\). This is because some rectangles from
the initial grid will need to be bisected more times than others. So,
unless adaptive refinement is disabled, the function Plot3DS()
produces a somewhat disordered set of points. However, most surface
plotting programs require that the set of data points be a rectangular
grid in the parameter space. So a smoothing and interpolation
procedure is necessary to convert a non-gridded set of data points
(“scattered” data) to a gridded set.
Parametric plots¶
Currently, parametric plots are not directly implemented in Yacas. However, it is possible to use Yacas to obtain numerical data for such plots. One can then use external programs to produce actual graphics.
A two-dimensional parametric plot is a line in a two-dimensional space, defined by two equations such as \(x=f(t),y=g(t)\). Two functions \(f, g\) and a range of the independent variable \(t\), for example, \(t_1\leq t\leq t_2\), need to be specified.
Parametric plots can be used to represent plots of functions in non-Euclidean coordinates. For example, to plot the function \(\rho=\cos(4\phi)^2\) in polar coordinates \((\rho,\phi)\), one can rewrite the Euclidean coordinates through the polar coordinates, \(x=\rho\cos(\phi),y = \rho\sin(\phi)\), and use the equivalent parametric plot with \(\phi\) as the parameter: \(x=\cos(4\phi)^2\cos(\phi), y = \cos(4\phi)^2\sin(\phi)\).
Sometimes higher-dimensional parametric plots are required. A line plot in three dimensions is defined by three functions of one variable, for example, \(x=f(t),y=g(t),z=h(t)\), and a range of the parameter \(t\). A surface plot in three dimensions is defined by three functions of two variables each, for example, \(x=f(u,v),y=g(u,v),z=h(u,v)\), and a rectangular domain in the \((u,v)\) space.
The data for parametric plots can be generated separately using the same adaptive plotting algorithms as for ordinary function plots, as if all functions such as \(f(t)\) or \(g(u,v)\) were unrelated functions. The result would be several separate data sets for the \(x,y,\ldots\) coordinates. These data sets could then be combined using an interactive plotting program.
The cost of arbitrary-precision computations¶
A computer algebra system absolutely needs to be able to perform computations with very large integer numbers. Without this capability, many symbolic computations (such as exact GCD of polynomials or exact solution of polynomial equations) would be impossible.
A different question is whether a CAS really needs to be able to evaluate, say, 10,000 digits of the value of a Bessel function of some 10,000-digit complex argument. It seems likely that no applied problem of natural sciences would need floating-point computations of special functions with such a high precision. However, arbitrary-precision computations are certainly useful in some mathematical applications; e.g. some mathematical identities can be first guessed by a floating-point computation with many digits and then proved.
Very high precision computations of special functions might be useful in the future. But it is already quite clear that computations with moderately high precision (say, 50 or 100 decimal digits) are useful for applied problems. For example, to obtain the leading asymptotic of an analytic function, we could expand it in series and take the first term. But we need to check that the coefficient at what we think is the leading term of the series does not vanish. This coefficient could be a certain “exact” number such as \((\cos(355)+1)^2\). This number is “exact” in the sense that it is made of integers and elementary functions. But we cannot say a priori that this number is nonzero. The problem of “zero determination” (finding out whether a certain “exact” number is zero) is known to be algorithmically unsolvable if we allow transcendental functions. The only practical general approach seems to be to compute the number in question with many digits. Usually a few digits are enough, but occasionally several hundred digits are needed.
Implementing an efficient algorithm that computes 100 digits of \(\sin(\frac{3}{7})\) already involves many of the issues that would also be relevant for a 10,000 digit computation. Modern algorithms allow evaluations of all elementary functions in time that is asymptotically logarithmic in the number of digits \(P\) and linear in the cost of long multiplication (usually denoted \(M(P)\)). Almost all special functions can be evaluated in time that is asymptotically linear in \(P\) and in \(M(P)\). (However, this asymptotic cost sometimes applies only to very high precision, e.g., \(P>1000\), and different algorithms need to be implemented for calculations in lower precision.)
In yacas we strive to implement all numerical functions to arbitrary
precision. All integer or rational functions return exact results,
and all floating-point functions return their value with \(P\)
correct decimal digits (assuming sufficient precision of the
arguments). The current value of \(P\) is accessed as
Builtin'Precision'Get()
and may be changed by
Builtin'Precision'Set()
.
Implementing an arbitrary-precision floating-point computation of a function \(f(x)\), such as \(f(x)=\exp(x)\), typically needs the following:
- An algorithm that will compute \(f(x)\) for a given value \(x\) to a user-specified precision of \(P\) (decimal) digits. Often, several algorithms must be implemented for different subdomains of the (\(x\),:math:P) space.
- An estimate of the computational cost of the algorithm(s), as a function of \(x\) and \(P\). This is needed to select the best algorithm for given \(x, P\).
- An estimate of the round-off error. This is needed to select the “working precision” which will typically be somewhat higher than the precision of the final result.
In calculations with machine precision where the number of digits is fixed, the problem of round-off errors is quite prominent. Every arithmetic operation causes a small loss of precision; as a result, a few last digits of the final value are usually incorrect. But if we have an arbitrary precision capability, we can always increase precision by a few more digits during intermediate computations and thus eliminate all round-off error in the final result. We should, of course, take care not to increase the working precision unnecessarily, because any increase of precision means slower calculations. Taking twice as many digits as needed and hoping that the result is precise is not a good solution.
Selecting algorithms for computations is the most non-trivial part of the implementation. We want to achieve arbitrarily high precision, so we need to find either a series, or a continued fraction, or a sequence given by explicit formula, that converges to the function in a controlled way. It is not enough to use a table of precomputed values or a fixed approximation formula that has a limited precision.
In the last 30 years, the interest in arbitrary-precision computations grew and many efficient algorithms for elementary and special functions were published. Most algorithms are iterative. Almost always it is very important to know in advance how many iterations are needed for given \(x\), \(P\). This knowledge allows to estimate the computational cost, in terms of the required precision \(P\) and of the cost of long multiplication \(M(P)\), and choose the best algorithm.
Typically all operations will fall into one of the following categories (sorted by the increasing cost):
- addition, subtraction: linear in \(P\);
- multiplication, division, integer power, integer root: linear in \(M(P)\);
- elementary functions: \(\exp(x)\), \(\ln(x)\), \(\sin(x)\), \(\arctan(x)\) etc.: \(M(P)\ln(P)\) or slower by some powers of \(\ln(P)\);
- transcendental functions: \(erf(x)\), \(\gamma(x)\) etc.: typically \(PM(P)\) or slower.
The cost of long multiplication \(M(P)\) is between \(O(P^2)\) for low precision and \(O(P\ln(P))\) for very high precision. In some cases, a different algorithm should be chosen if the precision is high enough to allow \(M(P)\) faster than \(O(P^2)\).
Some algorithms also need storage space (e.g. an efficient algorithm for summation of the Taylor series uses \(O(\ln(P))\) temporary \(P\)-digit numbers).
Below we shall normally denote by \(P\) the required number of decimal digits. The formulae frequently contain conspicuous factors of \(\ln(10)\), so it will be clear how to obtain analogous expressions for another base. (Most implementations use a binary base rather than a decimal base since it is more convenient for many calculations.)
Estimating convergence of a series¶
Analyzing convergence of a power series is usually not difficult. Here is a worked-out example of how we could estimate the required number of terms in the power series
if we need \(P\) decimal digits of precision in the result. To be specific, assume that \(|x|<1\). (A similar calculation can be done for any other bound on \(x\).)
Suppose we truncate the series after \(n\)-th term and the series converges “well enough” after that term. Then the error will be approximately equal to the first term we dropped. (This is what we really mean by “converges well enough” and this will generally be the case in all applications, because we would not want to use a series that does not converge well enough.)
The term we dropped is \(\frac{x^{n+1}}{(n+1)!}\). To estimate \(n!\) for large \(n\), one can use the inequality
(valid for all \(n\geq 47\)) which provides tight bounds for the growth of the factorial, or a weaker inequality which is somewhat easier to use,
(valid for all \(n\geq 6\)). The latter inequality is sufficient for most purposes.
If we use the upper bound on \(n!\) from this estimate, we find that the term we dropped is bounded by
We need this number to be smaller than \(10^{-P}\). This leads to an inequality
which we now need to solve for \(n\). The left hand side decreases with growing \(n\). So it is clear that the inequality will hold for large enough \(n\), say for \(n\geq n_0\) where \(n_0\) is an unknown (integer) value. We can take a logarithm of both sides, replace \(n\) with \(n_0\) and obtain the following equation for \(n_0\):
This equation cannot be solved exactly in terms of elementary functions; this is a typical situation in such estimates. However, we do not really need a very precise solution for \(n_0\); all we need is an estimate of its integer part. This is also a typical situation. It is acceptable if our approximate value of \(n_0\) comes out a couple of units higher than necessary, because a couple of extra terms of the Taylor series will not significantly slow down the algorithm (but it is important that we do not underestimate \(n_0\)). Finally, we are mostly interested in having a good enough answer for large values of \(P\).
We can try to guess the result. The largest term on the LHS grows as \(n_0\ln(n_0)\) and it should be approximately equal to \(P\ln(10)\); but \(\ln(n_0)\) grows very slowly, so this gives us a hint that \(n_0\) is proportional to \(P\ln(10)\). As a first try, we set \(n_0=P\ln(10)-2\) and compare the RHS with the LHS; we find that we have overshot by a factor \(\ln(P)-1+\ln(\ln(10))\), which is not a large factor. We can now compensate and divide \(n_0\) by this factor, so our second try is
(This approximation procedure is equivalent to solving the equation
by direct iteration, starting from \(x=P\ln(10)\).) If we substitute our second try for \(n_0\) into the equation, we shall find that we undershot a little bit (i.e. the LHS is a little smaller than the RHS), but our \(n_0\) is now smaller than it should be by a quantity that is smaller than 1 for large enough \(P\). So we should stop at this point and simply add 1 to this approximate answer. We should also replace \(\ln(\ln(10))-1\) by 0 for simplicity (this is safe because it will slightly increase \(n_0\).)
Our final result is that it is enough to take
terms in the Taylor series to compute \(\exp(x)\) for \(|x|<1\) to \(P\) decimal digits. (Of course, if \(x\) is much smaller than 1, many fewer terms will suffice.)
Estimating the round-off error¶
Catastrophic round-off error¶
Sometimes the round-off error of a particular method of computation becomes so large that the method becomes highly inefficient.
Consider the computation of \(\sin(x)\) by the truncated Taylor series
when \(x\) is large. We know that this series converges for all \(x\), no matter how large. Assume that \(x=10^M\) with \(M\geq 1\), and that we need \(P\) decimal digits of precision in the result.
First, we determine the necessary number of terms \(N\). The magnitude of the sum is never larger than \(1\). Therefore we need the \(N\)-th term of the series to be smaller than \(10^{-P}\). The inequality is \((2N+1)! > 10^{P+M(2N+1)}\). We obtain that \(2N+2>e10^M\) is a necessary condition, and if \(P\) is large, we find approximately
However, taking enough terms does not yet guarantee a good result. The terms of the series grow at first and then start to decrease. The sum of these terms is, however, small. Therefore there is some cancellation and we need to increase the working precision to avoid the round-off. Let us estimate the required working precision.
We need to find the magnitude of the largest term of the series. The ratio of the next term to the previous term is \(x/(2k(2k+1))\) and therefore the maximum will be when this ratio becomes equal to \(1\), i.e. for \(2k\approx\sqrt{x}\). Therefore the largest term is of order \(x^{\sqrt{x}}/\sqrt{x}!\) and so we need about \(M/2\sqrt{x}\) decimal digits before the decimal point to represent this term. But we also need to keep at least \(P\) digits after the decimal point, or else the round-off error will erase the significant digits of the result. In addition, we will have unavoidable round-off error due to \(O(P)\) arithmetic operations. So we should increase precision again by \(P+\ln(P)/\ln(10)\) digits plus a few guard digits.
As an example, to compute \(\sin(10)\) to \(P=50\) decimal digits with this method, we need a working precision of about \(60\) digits, while to compute \(\sin(10000)\) we need to work with about \(260\) digits. This shows how inefficient the Taylor series for \(\sin(x)\) becomes for large arguments \(x\). A simple transformation \(x=2\pi n+x'\) would have reduced \(x\) to at most 7, and the unnecessary computations with \(260\) digits would be avoided. The main cause of this inefficiency is that we have to add and subtract extremely large numbers to get a relatively small result of order \(1\).
We find that the method of Taylor series for \(\sin(x)\) at large \(x\) is highly inefficient because of round-off error and should be complemented by other methods. This situation seems to be typical for Taylor series.
Basic arbitrary-precision arithmetic¶
Yacas uses an internal math library (the yacasnumbers
library) which
comes with the source code. This reduces the dependencies of the yacas
system and improves portability. The internal math library is simple
and does not necessarily use the most optimal algorithms.
If \(P\) is the number of digits of precision, then multiplication and division take \(M(P)=O(P^2)\) operations in the internal math. (Of course, multiplication and division by a short integer takes time linear in \(P\).) Much faster algorithms (Karatsuba, Toom-Cook, FFT multiplication, Newton-Raphson division etc.) are implemented in {gmp}, {CLN} and some other libraries. The asymptotic cost of multiplication for very large precision is \(M(P)\approx O(P^{1.6})\) for the Karatsuba method and \(M(P)=O(P\ln(P)\ln(\ln(P)))\) for the FFT method. In the estimates of computation cost in this book we shall assume that \(M(P)\) is at least linear in \(P\) and maybe a bit slower.
The costs of multiplication may be different in various arbitrary-precision arithmetic libraries and on different computer platforms. As a rough guide, one can assume that the straightforward \(O(P^2)\) multiplication is good until 100-200 decimal digits, the asymptotically fastest method of FFT multiplication is good at the precision of about 5,000 or more decimal digits, and the Karatsuba multiplication is best in the middle range.
Warning: calculations with internal yacas math using precision exceeding 10,000 digits are currently impractically slow.
In some algorithms it is necessary to compute the integer parts of
expressions such as \(a\ln(b)/\ln(10)\) or \(a\ln(10)/\ln(2)\),
where \(a\), \(b\) are short integers of order
\(O(P)\). Such expressions are frequently needed to estimate the
number of terms in the Taylor series or similar parameters of the
algorithms. In these cases, it is important that the result is not
underestimated. However, it would be wasteful to compute
\(1000\ln(10)/\ln(2)\) in great precision only to discard most of
that information by taking the integer part of that number. It is
more efficient to approximate such constants from above by short
rational numbers, for example, \(\ln(10)/\ln(2) < 28738/8651\) and
\(\ln(2) < 7050/10171\). The error of such an approximation will be
small enough for practical purposes. The function BracketRational()
can be used to find optimal rational approximations.
The function IntLog()
(see below) efficiently computes the integer
part of a logarithm (for an integer base, not a natural logarithm). If
more precision is desired in calculating \(\ln(a)/\ln(b)\) for
integer \(a\), \(b\), one can compute \(IntLog(a^k,b)\)
for some integer \(k\) and then divide by \(k\).
How many digits of \(\sin(\exp(\exp(1000)))\) do we need?¶
Arbitrary-precision math is not omnipotent against overflow. Consider the problem of representing very large numbers such as \(x=\exp(\exp(1000))\). Suppose we need a floating-point representation of the number \(x\) with \(P\) decimal digits of precision. In other words, we need to express \(x\approx M10^E\), where the mantissa \(1<M<10\) is a floating-point number and the exponent \(E\) is an integer, chosen so that the relative precision is \(10^{-P}\). How much effort is needed to find \(M\) and \(E\)?
The exponent \(E\) is easy to obtain:
To compute the integer part \(\lfloor y\rfloor\) of a number \(y\) exactly, we need to approximate \(y\) with at least \(\ln(y)/\ln(10)\) floating-point digits. In our example, we find that we need 434 decimal digits to represent \(E\).
Once we found \(E\), we can write \(x=10^{E+m}\) where \(m=\exp(1000)/\ln(10)-E\) is a floating-point number, \(0<m<1\). Then \(M=10^m\). To find \(M\) with \(P\) (decimal) digits, we need \(m\) with also at least \(P\) digits. Therefore, we actually need to evaluate \(\exp(1000)/\ln(10)\) with \(434+P\) decimal digits before we can find \(P\) digits of the mantissa of \(x\). We ran into a perhaps surprising situation: one needs a high-precision calculation even to find the first digit of \(x\), because it is necessary to find the exponent \(E\) exactly as an integer, and \(E\) is a rather large integer. A normal double-precision numerical calculation would give an overflow error at this point.
Suppose we have found the number \(x=\exp(\exp(1000))\) with some precision. What about finding \(\sin(x)\)? Now, this is extremely difficult, because to find even the first digit of \(\sin(x)\) we have to evaluate \(x\) with absolute error of at most \(0.5\). We know, however, that the number \(x\) has approximately \(10^434\) digits before the decimal point. Therefore, we would need to calculate \(x\) with at least that many digits. Computations with \(10^434\) digits is clearly far beyond the capability of modern computers. It seems unlikely that even the sign of \(\sin(\exp(\exp(1000)))\) will be obtained in the near future [1].
[1] | It seems even less likely that the sign of \(\sin(\exp(\exp(1000)))\) would be of any use to anybody even if it could be computed. |
Suppose that \(N\) is the largest integer that our arbitrary-precision facility can reasonably handle. (For yacas internal math library, \(N\) is about \(10^10000\).) Then it follows that numbers \(x\) of order \(10^N\) can be calculated with at most one (1) digit of floating-point precision, while larger numbers cannot be calculated with any precision at all.
It seems that very large numbers can be obtained in practice only through exponentiation or powers. It is unlikely that such numbers will arise from sums or products of reasonably-sized numbers in some formula [2].
[2] | A factorial function can produce rapidly growing results, but exact factorials \(n!\) for large \(n\) are well represented by the Stirling formula which involves powers and exponentials. For example, suppose a program operates with numbers \(x\) of size \(N\) or smaller; a number such as \(10^N\) can be obtained only by multiplying \(O(N)\) numbers \(x\) together. But since \(N\) is the largest representable number, it is certainly not feasible to perform \(O(N)\) sequential operations on a computer. However, it is feasible to obtain \(N\)-th power of a small number, since it requires only \(O(Ln(N))\) operations. |
If numbers larger than \(10^N\) are created only by exponentiation operations, then special exponential notation could be used to represent them. For example, a very large number \(z\) could be stored and manipulated as an unevaluated exponential \(z=\exp(M10^E)\) where \(M\geq 1\) is a floating-point number with \(P\) digits of mantissa and \(E\) is an integer, \(\ln(N)<E<N\). Let us call such objects “exponentially large numbers” or “exp-numbers” for short.
In practice, we should decide on a threshold value \(N\) and promote a number to an exp-number when its logarithm exceeds \(N\).
Note that an exp-number \(z\) might be positive or negative, e.g. \(z= -\exp(M10^E)\).
Arithmetic operations can be applied to the exp-numbers. However, exp-large arithmetic is of limited use because of an almost certainly critical loss of precision. The power and logarithm operations can be meaningfully performed on exp-numbers \(z\). For example, if \(z=\exp(M10^E)\) and \(p\) is a normal floating-point number, then \(z^p=\exp(pM10^E)\) and \(\ln(z)=M10^E\). We can also multiply or divide two exp-numbers. But it makes no sense to multiply an exp-number \(z\) by a normal number because we cannot represent the difference between \(z\) and say \(2.52*z\). Similarly, adding \(z\) to anything else would result in a total underflow, since we do not actually know a single digit of the decimal representation of \(z\). So if \(z_1\) and \(z_2\) are exp-numbers, then \(z_1+z_2\) is simply equal to either \(z1\) or \(z2\) depending on which of them is larger.
We find that an exp-number \(z\) acts as an effective “infinity” compared with normal numbers. But exp-numbers cannot be used as a device for computing limits: the unavoidable underflow will almost certainly produce wrong results. For example, trying to verify
by substituting \(x=1/z\) with some exp-number \(z\) gives 0 instead of 1.
Taking a logarithm of an exp-number brings it back to the realm of normal, representable numbers. However, taking an exponential of an exp-number results in a number which is not representable even as an exp-number. This is because an exp-number \(z\) needs to have its exponent \(E\) represented exactly as an integer, but \(\exp(z)\) has an exponent of order \(O(z)\) which is not a representable number. The monstrous number \(\exp(z)\) could be only written as \(\exp(\exp(M10^E))\), i.e. as a “doubly exponentially large” number, or “2-exp-number” for short. Thus we obtain a hierarchy of iterated exp-numbers. Each layer is “unrepresentably larger” than the previous one.
The same considerations apply to very small numbers of the order \(10^{-N}\) or smaller. Such numbers can be manipulated as “exponentially small numbers”, i.e. expressions of the form \(Exp(-M*10^E)\) with floating-point mantissa \(M\geq 1\) and integer \(E\) satisfying \(\ln(N)<E<N\). Exponentially small numbers act as an effective zero compared with normal numbers.
Taking a logarithm of an exp-small number makes it again a normal representable number. However, taking an exponential of an exp-small number produces 1 because of underflow. To obtain a “doubly exponentially small” number, we need to take a reciprocal of a doubly exponentially large number, or take the exponent of an exponentially large negative power. In other words, \(\exp(-M10^E)\) is exp-small, while \(\exp(-\exp(M10^E))\) is 2-exp-small.
The practical significance of exp-numbers is rather limited. We cannot obtain even a single significant digit of an exp-number. A “computation” with exp-numbers is essentially a floating-point computation with logarithms of these exp-numbers. A practical problem that needs numbers of this magnitude can probably be restated in terms of more manageable logarithms of such numbers. In practice, exp-numbers could be useful not as a means to get a numerical answer, but as a warning sign of critical overflow or underflow [3].
[3] | Yacas currently does not implement exp-numbers or any other guards against overflow and underflow. If a decimal exponential becomes too large, an incorrect answer may result. |
Sparse representations¶
The sparse tree data structure¶
Yacas has a sparse tree object for use as a storage for storing (key,value) pairs for which the following properties hold:
(key, value1) + (key, value2) = (key, value1+value2)
(key1, value1) * (key2, value2) = (key1+key2, value1*value2)
The last is optional. For multivariate polynomials (described elsewhere) both hold, but for matrices, only the addition property holds. The function {MultiplyAddSparseTrees} (described below) should not be used in these cases.
Internal structure¶
A key is defined to be a list of integer numbers ($ n[1] $, ..., $ n[m] $). Thus for a two-dimensional key, one item in the sparse tree database could be reflected as the (key,value) pair { {{1,2},3} }, which states that element {(1,2)} has value {3}. (Note: this is not the way it is stored in the database!).
The storage is recursive. The sparse tree begins with a list of objects { {n1,tree1} } for values of {n1} for the first item in the key. The {tree1} part then contains a sub-tree for all the items in the database for which the value of the first item in the key is {n1}.
The above single element could be created with:
In> r:=CreateSparseTree({1,2},3)
Out> {{1,{{2,3}}}};
{CreateSparseTree} makes a database with exactly one item. Items can now be obtained from the sparse tree with {SparseTreeGet}.:
In> SparseTreeGet({1,2},r)
Out> 3;
In> SparseTreeGet({1,3},r)
Out> 0;
And values can also be set or changed:
In> SparseTreeSet({1,2},r,Current+5)
Out> 8;
In> r
Out> {{1,{{2,8}}}};
In> SparseTreeSet({1,3},r,Current+5)
Out> 5;
In> r
Out> {{1,{{3,5},{2,8}}}};
The variable {Current} represents the current value, and can be used to determine the new value. {SparseTreeSet} destructively modifies the original, and returns the new value. If the key pair was not found, it is added to the tree.
The sparse tree can be traversed, one element at a time, with {SparseTreeScan}:
In> SparseTreeScan(Hold({{k,v},Echo({k,v})}),2,r)
{1,3} 5
{1,2} 8
An example of the use of this function could be multiplying a sparse matrix with a sparse vector, where the entire matrix can be scanned with {SparseTreeScan}, and each non-zero matrix element $ A[i][j] $ can then be multiplied with a vector element $ v[j] $, and the result added to a sparse vector $ w[i] $, using the {SparseTreeGet} and {SparseTreeSet} functions. Multiplying two sparse matrices would require two nested calls to {SparseTreeScan} to multiply every item from one matrix with an element from the other, and add it to the appropriate element in the resulting sparse matrix.
When the matrix elements $ A[i][j] $ are defined by a function $ f(i,j) $ (which can be considered a dense representation), and it needs to be multiplied with a sparse vector $ v[j] $, it is better to iterate over the sparse vector $ v[j] $. Representation defines the most efficient algorithm to use in this case.
The API to sparse trees is:
- {CreateSparseTree(coefs,fact)} - Create a sparse tree with one monomial, where ‘coefs’ is the key, and ‘fact’ the value. ‘coefs’ should be a list of integers.
- {SparseTreeMap(op,depth,tree)} - Walk over the sparse tree, one element at a time, and apply the function “op” on the arguments (key,value). The ‘value’ in the tree is replaced by the value returned by the {op} function. ‘depth’ signifies the dimension of the tree (number of indices in the key).
- {SparseTreeScan(op,depth,tree)} - Same as SparseTreeMap, but without changing elements.
- {AddSparseTrees(depth,x,y)}, {MultiplyAddSparseTrees(depth,x,y,coefs,fact)} - Add sparse tree ‘y’ to sparse tree ‘x’, destructively. in the {MultiplyAdd} case, the monomials are treated as if they were multiplied by a monomial with coefficients with the (key,value) pair (coefs,fact). ‘depth’ signifies the dimension of the tree (number of indices in the key).
- {SparseTreeGet(key,tree)} - return value stored for key in the tree.
- {SparseTreeSet(key,tree,newvalue)} - change the value stored for the key to newvalue. If the key was not found then {newvalue} is stored as a new item. The variable {Current} is set to the old value (or zero if the key didn’t exist in the tree) before evaluating {newvalue}.
Implementation of multivariate polynomials¶
This section describes the implementation of multivariate polynomials in Yacas.
Concepts and ideas are taken from the books [Davenport <i>et al.</i> 1989] and [von zur Gathen <i>et al.</i> 1999].
Definitions¶
The following definitions define multivariate polynomials, and the functions defined on them that are of interest for using such multivariates.
A <i>term</i> is an object which can be written as
for $ m $ variables ($ x[1] $, ..., $ x[m] $). The numbers $ n[m] $ are integers. $ c $ is called a <i>coefficient</i>, and \(x[1]^n[1]*x[2]^n[2]* ... *x[m]^n[m]\) a <i>monomial</i>.
A <i>multivariate polynomial</i> is taken to be a sum over terms.
We write $ c[a]*x^a $ for a term, where $ a $ is a list of powers for the monomial, and $ c[a] $ the <i>coefficient</i> of the term.
It is useful to define an ordering of monomials, to be able to determine a canonical form of a multivariate.
For the currently implemented code the <i>lexicographic order</i> has been chosen:
- first an ordering of variables is chosen, ( $ x[1] $, ..., $ x[m] $)
- for the exponents of a monomial, $ a $ = ($ a[1] $, ..., $ a[m] $) the lexicographic order first looks at the first exponent, $ a[1] $, to determine which of the two monomials comes first in the multivariate. If the two exponents are the same, the next exponent is considered.
This method is called <i>lexicographic</i> because it is similar to the way words are ordered in a usual dictionary.
For all algorithms (including division) there is some freedom in the ordering of monomials. One interesting advantage of the lexicographic order is that it can be implemented with a recursive data structure, where the first variable, $ x[1] $ can be treated as the main variable, thus presenting it as a univariate polynomial in $ x[1] $ with all its terms grouped together.
Other orderings can be used, by re-implementing a part of the code dealing with multivariate polynomials, and then selecting the new code to be used as a driver, as will be described later on.
Given the above ordering, the following definitions can be stated:
For a non-zero <i>multivariate polynomial</i>
$$ f = Sum(a,a[max],a[min],c[a]*x^a) $$
with a monomial order:
- $ c[a]*x^a $ is a <i>term</i> of the multivariate.
- the <i>multidegree</i> of $ f $ is $ mdeg(f) := a[max] $.
- 1. the <i>leading coefficient</i> of $ f $ is $ lc(f):=c[mdeg(f)] $, for the first term with non-zero coefficient.
- the <i>leading monomial</i> of $ f $ is $ lm(f):=x^mdeg(f) $.
- the <i>leading term</i> of $ f $ is $ lt(f):=lc(f)*lm(f) $.
The above define access to the leading monomial, which is used for divisions, gcd calculations and the like. Thus an implementation needs be able to determine { {mdeg(f),lc(f)} } . Note the similarity with the (key,value) pairs described in the sparse tree section. $ mdeg(f) $ can be thought of as a ‘key’, and $ lc(f) $ as a ‘value’.
The <i>multicontent</i>, $ multicont(f) $, is defined to be a term that divides all the terms in $ f $, and is the term described by ($ Min(a) $, $ Gcd(c) $), with $ Gcd(c) $ the GCD of all the coefficients, and $ Min(a) $ the lowest exponents for each variable, occurring in $ f $ for which $ c $ is non-zero.
The <i>multiprimitive part</i> is then defined as $ pp(f):=f$/$multicont(f) $.
For a multivariate polynomial, the obvious addition and (distributive) multiplication rules hold
(a+b) + (c+d) := a+b+c+d
a*(b+c) := (a*b)+(a*c)
These are supported in the Yacas system through a multiply-add operation: $$ muadd(f,t,g) := f+t*g $$. This allows for both adding two polynomials ($ t:=1 $), or multiplication of two polynomials by scanning one polynomial, and multiplying each term of the scanned polynomial with the other polynomial, and adding the result to the polynomial that will be returned. Thus there should be an efficient {muadd} operation in the system.
Representation¶
For the representation of polynomials, on computers it is natural to do this in an array: ($a[1]$, $a[2]$, ..., $a[n]$) for a univariate polynomial, and the equivalent for multivariates. This is called a <i>dense</i> representation, because all the coefficients are stored, even if they are zero. Computers are efficient at dealing with arrays. However, in the case of multivariate polynomials, arrays can become rather large, requiring a lot of storage and processing power even to add two such polynomials. For instance, $ x^200*y^100*z^300+1 $ could take 6000000 places in an array for the coefficients. Of course variables could be substituted for the single factors, $ p:=x^200 $ etc., but it requires an additional ad hoc step.
An alternative is to store only the terms for which the coefficients are non-zero. This adds a little overhead to polynomials that could efficiently be stored in a dense representation, but it is still little memory, whereas large sparse polynomials are stored in acceptable memory too. It is of importance to still be able to add, multiply divide and get the leading term of a multivariate polynomial, when the polynomial is stored in a sparse representation.
For the representation, the data structure containing the {(exponents,coefficient)} pair can be viewed as a database holding {(key,value)} pairs, where the list of exponents is the key, and the coefficient of the term is the value stored for that key. Thus, for a variable set {{x,y}} the list {{{1,2},3}} represents $ 3*x*y^2 $.
Yacas stores multivariates internally as {MultiNomial (vars, terms)}, where {vars} is the ordered list of variables, and terms some object storing all the {(key, value)} pairs representing the terms. Note we keep the storage vague: the {terms} placeholder is implemented by other code, as a database of terms. The specific representation can be configured at startup (this is described in more detail below).
For the current version, Yacas uses the ‘sparse tree’ representation, which is a recursive sparse representation. For example, for a variable set {{x,y,z}}, the ‘terms’ object contains a list of objects of form {{deg,terms}}, one for each degree {deg} for the variable ‘x’ occurring in the polynomial. The ‘terms’ part of this object is then a sub-sparse tree for the variables {{y,z}}.
An explicit example:
In> MM(3*x^2+y)
Out> MultiNomial({x,y},{{2,{{0,3}}},{0,{{1,1}, {0,0}}}});
The first item in the main list is {{2,{{0,3}}}}, which states that there is a term of the form $ x^2*y^0*3 $. The second item states that there are two terms, $ x^0*y^1*1 $ and $ x^0*y^0*0 = 0 $.
This representation is sparse:
In> r:=MM(x^1000+x)
Out> MultiNomial({x},{{1000,1},{1,1}});
and allows for easy multiplication:
In> r*r
Out> MultiNomial({x},{{2000,1},{1001,2},{2,1},{0,0}});
In> NormalForm(%)
Out> x^2000+2*x^1001+x^2;
Internal code organization¶
The implementation of multivariates can be divided in three levels.
At the top level are the routines callable by the user or the rest of the system: MultiDegree, MultiDivide, MultiGcd, Groebner, etc. In general, this is the level implementing the operations actually desired.
The middle level does the book-keeping of the {MultiNomial(vars,terms)} expressions, using the functionality offered by the lowest level.
For the current system, the middle level is in {multivar.rep/ sparsenomial.ys}, and it uses the sparse tree representation implemented in {sparsetree.ys}.
The middle level is called the ‘driver’, and can be changed, or re-implemented if necessary. For instance, in case calculations need to be done for which dense representations are actually acceptable, one could write C++ implementing above-mentioned database structure, and then write a middle-level driver using the code. The driver can then be selected at startup. In the file ‘yacasinit.ys’ the default driver is chosen, but this can be overridden in the {.yacasrc} file or some file that is loaded, or at the command line, as long as it is done before the multivariates module is loaded (which loads the selected driver). Driver selection is as simple as setting a global variable to contain a file name of the file implementing the driver:
Set(MultiNomialDriver,
"multivar.rep/sparsenomial.ys");
where “multivar.rep/sparsenomial.ys” is the file implementing the driver (this is also the default driver, so the above command would not change any thing).
The choice was made for static configuration of the driver before the system starts up because it is expected that there will in general be one best way of doing it, given a certain system with a certain set of libraries installed on the operating system, and for a specific version of Yacas. The best version can then be selected at start up, as a configuration step. The advantage of static selection is that no overhead is imposed: there is no performance penalty for the abstraction layers between the three levels.
Driver interface¶
The driver should implement the following interface:
- {CreateTerm(vars,{exp,coef})} - create a multivariate polynomial with one term, in the variables defined in ‘var’, with the (key,value) pair (coefs,fact)
- {MultiNomialAdd(multi1, multi2) } - add two multivars, and (possibly) destructively modify multi1 to containthe result: [ multi1 := multi1 + multi2; multi1; ];
- {MultiNomialMultiplyAdd(multi1, multi2,exp,coef)} - add two multivars, and (possibly) destructively modify multi1 to contain the result. multi2 is considered multiplied by a term represented by the (key,value) pair (exp,coef). [ multi1 := multi1 + term * multi2; multi1; ];
- {MultiNomialNegate(multi)} - negate a multivar, returning -multi, and destructively changing the original. [ multi := - multi; multi1; ];
- {MultiNomialMultiply(multi1,multi2)} - Multiply two multivars, and (possibly) destructively modify multi1 to contain the result, returning the result: [ multi1 := multi1 * multi2; multi1; ];
- {NormalForm(multi)} - convert MultiNomial to normal form (as would be typed in be the user). This is part of the driver because the driver might be able to do this more efficiently than code above it which can use ScanMultiNomial.
- {MultiLeadingTerm(multi)} - return the (key,value) pair (mdeg(f),lc(f)) representing the leading term. This is all the information needed about the leading term, and thus the leading coefficient and multidegree can be extracted from it.
- {MultiDropLeadingZeroes(multi)} - remove leading terms with zero factors.
- {MultiTermLess(x,y)} - for two (key,value) pairs, return
True
if $x<y$, where the operation {<} is the one used for the representation, andFalse
otherwise. - {ScanMultiNomial(op,multi)} - traverse all the terms of the multivariate, applying the function ‘op’ to each (key,value) pair (exp,coef). The monomials are traversed in the ordering defined by MultiTermLess. ‘op’ should be a function accepting two arguments.
- {MultiZero(multi)} - return
True
if the multivariate is zero (all coefficients are zero),False
otherwise.
Integration¶
Integration can be performed by the function {Integrate}, which has two calling conventions:
- Integrate(variable) expression
- Integrate(variable, from, to) expression
Integrate can have its own set of rules for specific integrals, which might return a correct answer immediately. Alternatively, it calls the function AntiDeriv, to see if the anti-derivative can be determined for the integral requested. If this is the case, the anti-derivative is used to compose the output.
If the integration algorithm cannot perform the integral, the expression is returned unsimplified.
The integration algorithm¶
This section describes the steps taken in doing integration.
General structure¶
The integration starts at the function {Integrate}, but the task is delegated to other functions, one after the other. Each function can deem the integral unsolvable, and thus return the integral unevaluated. These different functions offer hooks for adding new types of integrals to be handled.
Expression clean-up¶
Integration starts by first cleaning up the expression, by calling {TrigSimpCombine} to simplify expressions containing multiplications of trigonometric functions into additions of trigonometric functions (for which the integration rules are trivial), and then passing the result to {Simplify}.
Generalized integration rules¶
For the function {AntiDeriv}, which is responsible for finding the anti-derivative of a function, the code splits up expressions according to the additive properties of integration, eg. integration of $ a+b $ is the same as integrating $ a $ + integrating $ b $.
- Polynomials which can be expressed as univariate polynomials in the variable to be integrated over are handled by one integration rule.
- Expressions of the form $ p*f(x) $, where $ p $ represents a univariate polynomial, and $ f(x) $ an integrable function, are handled by a special integration rule. This transformation rule has to be designed carefully not to invoke infinite recursion.
- Rational functions, $ f(x)/g(x) $ with both $ f(x) $ and $ g(x) $ univariate polynomials, is handled separately also, using partial fraction expansion to reduce rational function to a sum of simpler expressions.
Integration tables¶
For elementary functions, Yacas uses integration tables. For instance, the fact that the anti-derivative of $ Cos(x) $ is $ Sin(x) $ is declared in an integration table.
For the purpose of setting up the integration table, a few declaration functions have been defined, which use some generalized pattern matchers to be more flexible in recognizing expressions that are integrable.
Integrating simple functions of a variable¶
For functions like $ Sin(x) $ the anti-derivative can be declared with the function {IntFunc}.
The calling sequence for {IntFunc} is:
IntFunc(variable,pattern,antiderivative)
For instance, for the function $ Cos(x) $ there is a declaration:
IntFunc(x,Cos(_x),Sin(x));
The fact that the second argument is a pattern means that each occurrence of the variable to be matched should be referred to as {_x}, as in the example above.
IntFunc generalizes the integration implicitly, in that it will set up the system to actually recognize expressions of the form $ Cos(a*x+b) $, and return $ Sin(a*x+b)/a $ automatically. This means that the variables $ a $ and $ b $ are reserved, and can not be used in the pattern. Also, the variable used (in this case, {_x} is actually matched to the expression passed in to the function, and the variable {var} is the real variable being integrated over. To clarify: suppose the user wants to integrate $ Cos(c*y+d) $ over $ y $, then the following variables are set:
- {a} = $c$
- {b} = $d$
- {x} = $a*y+b$
- {var} = $x$
When functions are multiplied by constants, that situation is handled by the integration rule that can deal with univariate polynomials multiplied by functions, as a constant is a polynomial of degree zero.
Integrating functions containing expressions of the form $ a*x^2+b $¶
There are numerous expressions containing sub-expressions of the form $ a*x^2+b $ which can easily be integrated.
The general form for declaring anti-derivatives for such expressions is:
IntPureSquare(variable, pattern, sign2, sign0, antiderivative)
Here {IntPureSquare} uses {MatchPureSquared} to match the expression.
The expression is searched for the pattern, where the variable can match to a sub-expression of the form $ a*x^2+b $, and for which both $ a $ and $ b $ are numbers and $ a*sign2>0 $ and $ b*sign0>0 $.
As an example:
IntPureSquare(x,num_IsFreeOf(var)/(_x),
1,1,(num/(a*Sqrt(b/a)))*
ArcTan(var/Sqrt(b/a)));
declares that the anti-derivative of $ c/(a*x^2+b) $ is $$ (c/(a*Sqrt(b/a)))*ArcTan(x/Sqrt(b/a)) $$, if both $ a $ and $ b $ are positive numbers.
Transforms¶
Currently the only tranform defined is {LaplaceTransform}, which has the calling convention:
LaplaceTransform(var1,var2,func)
It has been setup much like the integration algorithm. If the transformation algorithm cannot perform the transform, the expression (in theory) is returned unsimplified. Some cases may still erroneously return {Undefined} or {Infinity}.
The {LaplaceTransform} algorithm¶
This section describes the steps taken in doing a Laplace transform.
General structure¶
{LaplaceTransform} is immediately handed off to {LapTran}. This is done because if the last {LapTran} rule is met, the Laplace transform couldn’t be found and it can then return {LaplaceTransform} unevaluated.
Operational Properties¶
The first rules that are matched against utilize the various operational properties of {LaplaceTransform}, such as:
- Linearity Properties
- Shift properties, i.e. multiplying the function by an exponential
- $ y * x^n = (-1)^n * Deriv(x,n) LaplaceTransform(x,x[2],y) $
- $ y/x = Integrate(x[2],x[2],Infinity) LapTran(x,x[2],y) $
The last operational property dealing with integration is not yet fully bug-tested, it sometimes returns {Undefined} or {Infinity} if the integral returns such.
Transform tables¶
For elementary functions, Yacas uses transform tables. For instance, the fact that the Laplace transform of $ Cos(t) $ is $ s/(s^2+1) $ is declared in a transform table.
For the purpose of setting up the transform table, a few declaration functions have been defined, which use some generalized pattern matchers to be more flexible in recognizing expressions that are transformable.
Transforming simple functions¶
For functions like $ Sin(t) $ the transform can be declared with the function {LapTranDef}.
The calling sequence for {LapTranDef} is:
LapTranDef(in, out)
Currently {in} must be a variable of {_t} and {out} must be a function of {s}. For instance, for the function $ Cos(t) $ there is a declaration:
LapTranDef(Cos(_t), s/(s^2+1));
The fact that the first argument is a pattern means that each occurrence of the variable to be matched should be referred to as {_t}, as in the example above.
{LapTranDef} generalizes the transform implicitly, in that it will set up the system to actually recognize expressions of the form $ Cos(a*t) $ and $Cos(t/a)$ , and return the appropriate answer. The way this is done is by three separate rules for case of {t} itself, {a*t} and {t/a}. This is similar to the {MatchLinear} function that {Integrate} uses, except {LaplaceTransforms} must have {b=0}.
Further Directions¶
Currenlty $Sin(t)*Cos(t)$ cannot be transformed, because it requires a convolution integral. This will be implemented soon. The inverse laplace transform will be implement soon also.
Finding real roots of polynomials¶
real roots¶
This section deals with finding roots of polynomials in the field of real numbers.
Without loss of generality, the coefficients \(a_i\) of a polynomial
can be considered to be rational numbers, as real-valued numbers are truncated in practice, when doing calculations on a computer.
Assuming that the leading coefficient \(a_n=1\), the polynomial \(p\) can also be written as
where \(p_i\) are the \(m\) distinct irreducible monic factors of the form \(p_i=x-x_i\), and \(n_i\) are multiplicities of the factors. Here the roots are \(x_i\) and some of them may be complex. However, complex roots of a polynomial with real coefficients always come in conjugate pairs, so the corresponding irreducible factors should be taken as \(p_i=x^2+c_ix+d_i\). In this case, there will be less than \(m\) irreducible factors, and all coefficients will be real.
square free decomposition¶
To find roots, it is useful to first remove the multiplicities, i.e. to convert the polynomial to one with multiplicity 1 for all irreducible factors, i.e. find the polynomial \(p_1\ldots p_m\). This is called the “square-free part” of the original polynomial \(p\).
The square-free part of the polynomial \(p\) can be easily found using the polynomial GCD algorithm. The derivative of a polynomial \(p\) can be written as:
The g.c.d. of \(p\) and \(p'\) equals
So if we divide \(p\) by \(Gcd(p,p')\), we get the square-free part of the polynomial:
In what follows we shall assume that all polynomials are square-free with rational coefficients. Given any polynomial, we can apply the functions {SquareFree} and {Rationalize} and reduce it to this form. The function {Rationalize} converts all numbers in an expression to rational numbers. The function {SquareFree} returns the square-free part of a polynomial. For example:
In> Expand((x+1.5)^5)
Out> x^5+7.5*x^4+22.5*x^3+33.75*x^2+25.3125*x +7.59375;
In> SquareFree(Rationalize(%))
Out> x/5+3/10;
In> Simplify(%*5)
Out> (2*x+3)/2;
In> Expand(%)
Out> x+3/2;
Sturm sequences¶
For a polynomial $ p(x) $ of degree $ n $, the Sturm sequence $ p[0] $, $ p[1] $, ... $ p[n] $ is defined by the following equations (following the book [Davenport <i>et al.</i> 1989]):
$$ p[0] = p(x) $$, $$ p[1] = p’(x) $$, $$ p[i] = -remainder(p[i-2],p[i-1]) $$, where $ remainder(p,q) $ is the remainder of division of polynomials $ p $, $ q $.
The polynomial $ p $ can be assumed to have no multiple factors, and thus $ p $ and $ p’ $ are relatively prime. The sequence of polynomials in the Sturm sequence are (up to a minus sign) the consecutive polynomials generated by Euclid’s algorithm for the calculation of a greatest common divisor for $ p $ and $ p’ $, so the last polynomial $p[n] $ will be a constant.
In Yacas, the function {SturmSequence(p)} returns the Sturm sequence of $ p $, assuming $ p $ is a univariate polynomial in $ x $, $ p = p(x) $.
variations in Sturm sequences¶
Given a Sturm sequence $ S = SturmSequence(p) $ of a polynomial $ p $, the <I>variation</I> in the Sturm sequence $ V(S,y) $ is the number of sign changes in the sequence $ p[0] $, $ p[1] $ , ... , $ p[n] $, evaluated at point $y$, and disregarding zeroes in the sequence.
Sturm’s theorem states that if $ a $ and $ b $ are two real numbers which are not roots of $ p $, and $ a < b $, then the number of roots between $ a $ and $ b $ is $ V(S,a) - V(S,b) $. A proof can be found in Knuth, <I>The Art of Computer Programming, Volume 2, Seminumerical Algorithms</I>.
For $ a $ and $ b $, the values $ -Infinity $ and $ Infinity $ can also be used. In these cases, $ V(S,Infinity) $ is the number of sign changes between the leading coefficients of the elements of the Sturm sequence, and $ V(S,-Infinity) $ the same, but with a minus sign for the leading coefficients for which the degree is odd.
Number of real roots¶
Thus, the number of real roots of a polynomial is $ V(S,-Infinity) - V(S,Infinity) $. The function {NumRealRoots(p)} returns the number of real roots of $ p $.
The function {SturmVariations(S,y)} returns the number of sign changes between the elements in the Sturm sequence $ S $, at point $ x = y $:
In> p:=x^2-1
Out> x^2-1;
In> S:=SturmSequence(p)
Out> {x^2-1,2*x,1};
In> SturmVariations(S,-Infinity)- \
SturmVariations(S,Infinity)
Out> 2;
In> NumRealRoots(p)
Out> 2;
In> p:=x^2+1
Out> x^2+1;
In> S:=SturmSequence(p)
Out> {x^2+1,2*x,-1};
In> SturmVariations(S,-Infinity)- \
SturmVariations(S,Infinity)
Out> 0;
In> NumRealRoots(p)
Out> 0;
Finding bounds on real roots¶
Armed with the variations in the Sturm sequence given in the previous section, we can now find the number of real roots in a range ($a$,$b$), for $ a < b $. We can thus bound all the roots by subdividing ranges until there is only one root in each range. To be able to start this process, we first need some upper bounds of the roots, or an interval that contains all roots. Davenport gives limits on the roots of a polynomial given the coefficients of the polynomial, as
$$ Abs(a) <= Max(Abs(a[n-1]/a[n]), Abs(a[n-2]/a[n])^(1/2), ... , Abs(a[0]/a[n])^(1/n) ) $$,
for all real roots $ a $ of $ p $. This gives the upper bound on the absolute value of the roots of the polynomial in question. if $ p(0) != 0 $, the minimum bound can be obtained also by considering the upper bound of $ p(1/x)*x^n $, and taking $ 1/bound $.
We thus know that given $$ a[max] = MaximumBound(p) $$ and $$ a[min] = MinimumBound(p) $$ for all roots $ a $ of polynomial, either $$ -a[max] <= a <= -a[min] $$ or $$ a[min] <= a <= a[max] $$.
Now we can start the search for the bounds on all roots. The search starts with initial upper and lower bounds on ranges, subdividing ranges until a range contains only one root, and adding that range to the resulting list of bounds. If, when dividing a range, the middle of the range lands on a root, care must be taken, because the bounds should not be on a root themselves. This can be solved by observing that if $ c $ is a root, $ p $ contains a factor $ x-c $, and thus taking $ p(x+c) $ results in a polynomial with all the roots shifted by a constant $ -c $, and the root $ c $ moved to zero, e.g. $ p(x+c) $ contains a factor $ x $. Thus a new ranges to the left and right of $ c $ can be determined by first calculating the minimum bound $ M $ of $ p(x+c)/x $. When the original range was ($a$,$b$), and $ c = (a+b)/2 $ is a root, the new ranges should become ($a$,$c-M$) and ($c+M$,$b$).
In Yacas, {MimimumBound(p)} returns the lower bound described above, and {MaximumBound(p)} returns the upper bound on the roots in $ p $. These bounds are returned as rational numbers. {BoundRealRoots(p)} returns a list with sublists with the bounds on the roots of a polynomial:
In> p:=(x+20)*(x+10)
Out> (x+20)*(x+10);
In> MinimumBound(p)
Out> 10/3;
In> MaximumBound(p)
Out> 60;
In> BoundRealRoots(p)
Out> {{-95/3,-35/2},{-35/2,-10/3}};
In> N(%)
Out> {{-31.6666666666,-17.5}, {-17.5,-3.3333333333}};
It should be noted that since all calculations are done with rational numbers, the algorithm for bounding the roots is very robust. This is important, as the roots can be very unstable for small variations in the coefficients of the polynomial in question (see Davenport).
Finding real roots given the bounds on the roots¶
Given the bounds on the real roots as determined in the previous section, two methods for finding roots are available: the secant method or the Newton method, where the function is locally approximated by a line, and extrapolated to find a new estimate for a root. This method converges quickly when “sufficiently” near a root, but can easily fail otherwise. The secant method can easily send the search to infinity.
The bisection method is more robust, but slower. It works by taking the middle of the range, and checking signs of the polynomial to select the half-range where the root is. As there is only one root in the range ($a$,$b$), in general it will be true that $ p(a)*p(b) < 0 $, which is assumed by this method.
Yacas finds the roots by first trying the secant method, starting in the middle of the range, $ c = (a+b)/2 $. If this fails the bisection method is tried.
The function call to find the real roots of a polynomial $ p $ in variable $ x $ is {FindRealRoots(p)}, for example:
In> p:=Expand((x+3.1)*(x-6.23))
Out> x^2-3.13*x-19.313;
In> FindRealRoots(p)
Out> {-3.1,6.23};
In> p:=Expand((x+3.1)^3*(x-6.23))
Out> x^4+3.07*x^3-29.109*x^2-149.8199\
In> *x-185.59793;
In> p:=SquareFree(Rationalize( \
In> Expand((x+3.1)^3*(x-6.23))))
Out> (-160000*x^2+500800*x+3090080)/2611467;
In> FindRealRoots(p)
Out> {-3.1,6.23};
Number theory algorithms¶
This chapter describes the algorithms used for computing various number-theoretic functions. We call “number-theoretic” any function that takes integer arguments, produces integer values, and is of interest to number theory.
Euclidean GCD algorithms¶
The main algorithm for the calculation of the GCD of two integers is the binary Euclidean algorithm. It is based on the following identities: $ Gcd(a,b) = Gcd(b,a) $, $ Gcd(a,b) = Gcd(a-b,b)$, and for odd $b$, $Gcd(2*a,b) = Gcd(a,b)$. Thus we can produce a sequence of pairs with the same GCD as the original two numbers, and each pair will be at most half the size of the previous pair. The number of steps is logarithmic in the number of digits in $a$, $b$. The only operations needed for this algorithm are binary shifts and subtractions (no modular division is necessary). The low-level function for this is {MathGcd}.
To speed up the calculation when one of the numbers is much larger than another, one could use the property $Gcd(a,b)=Gcd(a,Mod(a,b))$. This will introduce an additional modular division into the algorithm; this is a slow operation when the numbers are large.
Prime numbers: the Miller-Rabin test and its improvements¶
Small prime numbers
- EVAL “$p<=” : (ToString()Write(FastIsPrime(0))) : “$”
are simply stored in a precomputed table as an array of bits; the bits corresponding to prime numbers are set to 1. This makes primality testing on small numbers very quick. This is implemented by the function {FastIsPrime}.
Primality of larger numbers is tested by the function {IsPrime} that uses the Miller-Rabin algorithm. (FOOT Initial implementation and documentation was supplied by Christian Obrecht.) This algorithm is deterministic (guaranteed correct within a certain running time) for “small” numbers $n<3.4*10^13$ and probabilistic (correct with high probability but not guaranteed) for larger numbers. In other words, the Miller-Rabin test could sometimes flag a large number $n$ as prime when in fact $n$ is composite; but the probability for this to happen can be made extremely small. The basic reference is [Rabin 1980]. We also implemented some of the improvements suggested in [Davenport 1992].
The idea of the Miller-Rabin algorithm is to improve the Fermat primality test. If $n$ is prime, then for any $x$ we have $Gcd(n,x)=1$. Then by Fermat’s “little theorem”, $x^(n-1):=Mod(1,n)$. (This is really a simple statement; if $n$ is prime, then $n-1$ nonzero remainders modulo $n$: 1, 2, ..., $n-1$ form a cyclic multiplicative group.) Therefore we pick some “base” integer $x$ and compute $Mod(x^(n-1), n)$; this is a quick computation even if $n$ is large. If this value is not equal to $1$ for some base $x$, then $n$ is definitely not prime. However, we cannot test <i>every</i> base $x<n$; instead we test only some $x$, so it may happen that we miss the right values of $x$ that would expose the non-primality of $n$. So Fermat’s test sometimes fails, i.e. says that $n$ is a prime when $n$ is in fact not a prime. Also there are infinitely many integers called “Carmichael numbers” which are not prime but pass the Fermat test for every base.
The Miller-Rabin algorithm improves on this by using the property that for prime $n$ there are no nontrivial square roots of unity in the ring of integers modulo $n$ (this is Lagrange’s theorem). In other words, if $x^2:=Mod(1,n)$ for some $x$, then $x$ must be equal to $1$ or $-1$ modulo $n$. (Since $n-1$ is equal to $-1$ modulo $n$, we have $n-1$ as a trivial square root of unity modulo $n$. Note that even if $n$ is prime there may be nontrivial divisors of $1$, for example, $2*49:=Mod(1,97)$.)
We can check that $n$ is odd before applying any primality test. (A
test $n^2:=Mod(1,24)$ guarantees that $n$ is not divisible by $2$ or
$3$. For large $n$ it is faster to first compute $Mod(n,24)$ rather
than $n^2$, or test $n$ directly.) Then we note that in Fermat’s test
the number $n-1$ is certainly a composite number because $n-1$ is
even. So if we first find the largest power of $2$ in $n-1$ and
decompose $n-1=2^r*q$ with $q$ odd, then $x^(n-1):=Mod(a^(2^r),n)$
where $a:=Mod(x^q,n)$. (Here $r>=1$ since $n$ is odd.) In other words,
the number $Mod(x^(n-1),n)$ is obtained by repeated squaring of the
number $a$. We get a sequence of $r$ repeated squares: $a$, $a^2$,
...
, $a^(2^r)$. The last element of this sequence must be $1$ if
$n$ passes the Fermat test. (If it does not pass, $n$ is definitely a
composite number.) If $n$ passes the Fermat test, the last-but-one
element $a^(2^(r-1))$ of the sequence of squares is a square root of
unity modulo $n$. We can check whether this square root is
non-trivial (i.e. not equal to $1$ or $-1$ modulo $n$). If it is
non-trivial, then $n$ definitely cannot be a prime. If it is trivial
and equal to $1$, we can check the preceding element, and so on. If an
element is equal to $-1$, we cannot say anything, i.e. the test passes
($n$ is “probably a prime”).
This procedure can be summarized like this:
- 1. Find the largest power of $2$ in $n-1$ and an odd number $q$ such that $n-1=2^r*q$.
- 2. Select the “base number” $x<n$. Compute the sequence $a:=Mod(x^q,n)$, $a^2$, $a^4$, ..., $a^(2^r)$ by repeated squaring modulo $n$. This sequence contains at least two elements since $r>=1$.
- 3. If $a=1$ or $a=n-1$, the test passes on the base number $x$. Otherwise, the test passes if at least one of the elements of the sequence is equal to $n-1$ and fails if none of them are equal to $n-1$. This simplified procedure works because the first element that is equal to $1$ <i>must</i> be preceded by a $-1$, or else we would find a nontrivial root of unity.
Here is a more formal definition. An odd integer $n$ is called <i>strongly-probably-prime</i> for base $b$ if $b^q:=Mod(1,n)$ or $b^(q*2^i):=Mod(n-1,n)$ for some $i$ such that $0 <= i < r$, where $q$ and $r$ are such that $q$ is odd and $n-1 = q*2^r$.
A practical application of this procedure needs to select particular base numbers. It is advantageous (according to [Pomerance <i>et al.</i> 1980]) to choose <i>prime</i> numbers $b$ as bases, because for a composite base $b=p*q$, if $n$ is a strong pseudoprime for both $p$ and $q$, then it is very probable that $n$ is a strong pseudoprime also for $b$, so composite bases rarely give new information.
An additional check suggested by [Davenport 1992] is activated if $r>2$ (i.e. if $n:=Mod(1,8)$ which is true for only 1/4 of all odd numbers). If $i>=1$ is found such that $b^(q*2^i):=Mod(n-1,n)$, then $b^(q*2^(i-1))$ is a square root of $-1$ modulo $n$. If $n$ is prime, there may be only two different square roots of $-1$. Therefore we should store the set of found values of roots of $-1$; if there are more than two such roots, then we woill find some roots $s1$, $s2$ of $-1$ such that $s1+s2!=Mod(0,n)$. But $s1^2-s2^2:=Mod(0,n)$. Therefore $n$ is definitely composite, e.g. $Gcd(s1+s2,n)>1$. This check costs very little computational effort but guards against some strong pseudoprimes.
Yet another small improvement comes from [Damgard <i>et al.</i> 1993]. They found that the strong primality test sometimes (rarely) passes on composite numbers $n$ for more than $1/8$ of all bases $x<n$ if $n$ is such that either $3*n+1$ or $8*n+1$ is a perfect square, or if $n$ is a Carmichael number. Checking Carmichael numbers is slow, but it is easy to show that if $n$ is a large enough prime number, then neither $3*n+1$, nor $8*n+1$, nor any $s*n+1$ with small integer $s$ can be a perfect square. [If $s*n+1=r^2$, then $s*n=(r-1)*(r+1)$.] Testing for a perfect square is quick and does not slow down the algorithm. This is however not implemented in Yacas because it seems that perfect squares are too rare for this improvement to be significant.
If an integer is not “strongly-probably-prime” for a given base $b$, then it is a composite number. However, the converse statement is false, i.e. “strongly-probably-prime” numbers can actually be composite. Composite strongly-probably-prime numbers for base $b$ are called <i>strong pseudoprimes</i> for base $b$. There is a theorem that if $n$ is composite, then among all numbers $b$ such that $1 < b < n$, at most one fourth are such that $n$ is a strong pseudoprime for base $b$. Therefore if $n$ is strongly-probably-prime for many bases, then the probability for $n$ to be composite is very small.
For numbers less than $B=34155071728321$, exhaustive (FOOT And surely exhausting.) computations have shown that there are no strong pseudoprimes simultaneously for bases 2, 3, 5, 7, 11, 13 and 17. This gives a simple and reliable primality test for integers below $B$. If $n >= B$, the Rabin-Miller method consists in checking if $n$ is strongly-probably-prime for $k$ base numbers $b$. The base numbers are chosen to be consecutive “weak pseudoprimes” that are easy to generate (see below the function {NextPseudoPrime}).
In the implemented routine {RabinMiller}, the number of bases $k$ is chosen to make the probability of erroneously passing the test $p < 10^(-25)$. (Note that this is <i>not</i> the same as the probability to give an incorrect answer, because all numbers that do not pass the test are definitely composite.) The probability for the test to pass mistakenly on a given number is found as follows. Suppose the number of bases $k$ is fixed. Then the probability for a given composite number to pass the test is less than $p[f]=4^(-k)$. The probability for a given number $n$ to be prime is roughly $p[p]=1/Ln(n)$ and to be composite $p[c]=1-1/Ln(n)$. Prime numbers never fail the test. Therefore, the probability for the test to pass is $p[f]*p[c]+p[p]$ and the probability to pass erroneously is $$ p = (p[f]*p[c])/(p[f]*p[c]+p[p]) < Ln(n)*4^(-k) $$. To make $p<epsilon$, it is enough to select $k=1/Ln(4)*(Ln(n)-Ln(epsilon))$.
Before calling {MillerRabin}, the function {IsPrime} performs two quick checks: first, for $n>=4$ it checks that $n$ is not divisible by 2 or 3 (all primes larger than 4 must satisfy this); second, for $n>257$, it checks that $n$ does not contain small prime factors $p<=257$. This is checked by evaluating the GCD of $n$ with the precomputed product of all primes up to 257. The computation of the GCD is quick and saves time in case a small prime factor is present.
There is also a function {NextPrime(n)} that returns the smallest prime number larger than {n}. This function uses a sequence 5,7,11,13,... generated by the function {NextPseudoPrime}. This sequence contains numbers not divisible by 2 or 3 (but perhaps divisible by 5,7,...). The function {NextPseudoPrime} is very fast because it does not perform a full primality test.
The function {NextPrime} however does check each of these pseudoprimes using {IsPrime} and finds the first prime number.
Factorization of integers¶
When we find from the primality test that an integer $n$ is composite, we usually do not obtain any factors of $n$. Factorization is implemented by functions {Factor} and {Factors}. Both functions use the same algorithms to find all prime factors of a given integer $n$. (Before doing this, the primality checking algorithm is used to detect whether $n$ is a prime number.) Factorization consists of repeatedly finding a factor, i.e. an integer $f$ such that $Mod(n, f)=0$, and dividing $n$ by $f$. (Of course, each fastor $f$ needs to be factorized too.)
small prime factors¶
First we determine whether the number $n$ contains “small” prime factors $p<=257$. A quick test is to find the GCD of $n$ and the product of all primes up to $257$: if the GCD is greater than 1, then $n$ has at least one small prime factor. (The product of primes is precomputed.) If this is the case, the trial division algorithm is used: $n$ is divided by all prime numbers $p<=257$ until a factor is found. {NextPseudoPrime} is used to generate the sequence of candidate divisors $p$.
checking for prime powers¶
After separating small prime factors, we test whether the number $n$ is an integer power of a prime number, i.e. whether $n=p^s$ for some prime number $p$ and an integer $s>=1$. This is tested by the following algorithm. We already know that $n$ is not prime and that $n$ does not contain any small prime factors up to 257. Therefore if $n=p^s$, then $p>257$ and $2<=s<s[0]=Ln(n)/Ln(257)$. In other words, we only need to look for powers not greater than $s[0]$. This number can be approximated by the “integer logarithm” of $n$ in base 257 (routine {IntLog(n, 257)}).
Now we need to check whether $n$ is of the form $p^s$ for $s=2$, 3,
...
, $s[0]$. Note that if for example $n=p^24$ for some $p$, then
the square root of $n$ will already be an integer,
$n^(1/2)=p^12$. Therefore it is enough to test whether $n^(1/s)$ is an
integer for all <i>prime</i> values of $s$ up to $s[0]$, and then we
will definitely discover whether $n$ is a power of some other integer.
The testing is performed using the integer $n$-th root function
{IntNthRoot} which quickly computes the integer part of $n$-th root of
an integer number. If we discover that $n$ has an integer root $p$ of
order $s$, we have to check that $p$ itself is a prime power (we use
the same algorithm recursively). The number $n$ is a prime power if
and only if $p$ is itself a prime power. If we find no integer roots
of orders $s<=s[0]$, then $n$ is not a prime power.
Pollard’s “rho” algorithm¶
If the number $n$ is not a prime power, the Pollard “rho” algorithm is applied [Pollard 1978]. The Pollard “rho” algorithm takes an irreducible polynomial, e.g. $p(x)=x^2+1$ and builds a sequence of integers $x[k+1]:=Mod(p(x[k]),n)$, starting from $x[0]=2$. For each $k$, the value $x[2*k]-x[k]$ is attempted as possibly containing a common factor with $n$. The GCD of $x[2*k]-x[k]$ with $n$ is computed, and if $Gcd(x[2*k]-x[k],n)>1$, then that GCD value divides $n$.
The idea behind the “rho” algorithm is to generate an effectively random sequence of trial numbers $t[k]$ that may have a common factor with $n$. The efficiency of this algorithm is determined by the size of the smallest factor $p$ of $n$. Suppose $p$ is the smallest prime factor of $n$ and suppose we generate a random sequence of integers $t[k]$ such that $1<=t[k]<n$. It is clear that, on the average, a fraction $1/p$ of these integers will be divisible by $p$. Therefore (if $t[k]$ are truly random) we should need on the average $p$ tries until we find $t[k]$ which is accidentally divisible by $p$. In practice, of course, we do not use a truly random sequence and the number of tries before we find a factor $p$ may be significantly different from $p$. The quadratic polynomial seems to help reduce the number of tries in most cases.
But the Pollard “rho” algorithm may actually enter an infinite loop when the sequence $x[k]$ repeats itself without giving any factors of $n$. For example, the unmodified “rho” algorithm starting from $x[0]=2$ loops on the number $703$. The loop is detected by comparing $x[2*k]$ and $x[k]$. When these two quantities become equal to each other for the first time, the loop may not yet have occurred so the value of GCD is set to 1 and the sequence is continued. But when the equality of $x[2*k]$ and $x[k]$ occurs many times, it indicates that the algorithm has entered a loop. A solution is to randomly choose a different starting number $x[0]$ when a loop occurs and try factoring again, and keep trying new random starting numbers between 1 and $n$ until a non-looping sequence is found. The current implementation stops after 100 restart attempts and prints an error message, “failed to factorize number”.
A better (and faster) integer factoring algorithm needs to be implemented in Yacas.
overview of algorithms¶
Modern factoring algorithms are all probabilistic (i.e. they do not guarantee a particular finishing time) and fall into three categories:
- 1. Methods that work well (i.e. quickly) if there is a relatively small factor $p$ of $n$ (even if $n$ itself is large). Pollard’s “rho” algorithm belongs to this category. The fastest in this category is Lenstra’s elliptic curves method (ECM).
- 2. Methods that work equally quickly regardless of the size of factors (but slower with larger $n$). These are the continued fractions method and the various “sieve” methods. The current best is the “General Number Field Sieve” (GNFS) but it is quite a complicated algorithm requiring operations with high-order algebraic numbers. The next best one is the “Multiple Polynomial Quadratic Sieve” (MPQS).
- 3. Methods that are suitable only for numbers of special “interesting” form, e.g. Fermat numbers $2^(2^k)-1$ or generally numbers of the form $r^s+a$ where $s$ is large but $r$ and $a$ are very small integers. The best method seems to be the “Special Number Field Sieve” which is a faster variant of the GNFS adapted to the problem.
There is ample literature describing these algorithms.
The Jacobi symbol¶
A number $m$ is a “quadratic residue modulo $n$” if there exists a number $k$ such that $k^2:=Mod(m,n)$.
The Legendre symbol ($m$/$n$) is defined as $+1$ if $m$ is a quadratic residue modulo $n$ and $-1$ if it is a non-residue. The Legendre symbol is equal to $0$ if $m/n$ is an integer.
The Jacobi symbol $[m/n;]$ is defined as the product of the Legendre symbols of the prime factors $f[i]$ of $n=f[1]^p[1]*...*f[s]^p[s]$, $$ [m/n;] := [m/f[1];]^p[1]*...*[m/f[s];]^p[s] $$. (Here we used the same notation $[a/b;]$ for the Legendre and the Jacobi symbols; this is confusing but seems to be the current practice.) The Jacobi symbol is equal to $0$ if $m$, $n$ are not mutually prime (have a common factor). The Jacobi symbol and the Legendre symbol have values $+1$, $-1$ or $0$.
The Jacobi symbol can be efficiently computed without knowing the full factorization of the number $n$. The currently used method is based on the following four identities for the Jacobi symbol: * 1. $[a/1;] = 1$. * 2. $[2/b;] = (-1)^((b^2-1)/8)$. * 3. $[(a*b)/c;] = [a/c;]*[b/c;]$. * 4. If $a:=Mod(b,c)$, then $[a/c;]=[b/c;]$. * 5. If $a$, $b$ are both odd, then $[a/b;]=[b/a;] * (-1)^((a-1)*(b-1)/4)$.
Using these identities, we can recursively reduce the computation of the Jacobi symbol $[a/b;]$ to the computation of the Jacobi symbol for numbers that are on the average half as large. This is similar to the fast “binary” Euclidean algorithm for the computation of the GCD. The number of levels of recursion is logarithmic in the arguments $a$, $b$.
More formally, Jacobi symbol $[a/b;]$ is computed by the following algorithm. (The number $b$ must be an odd positive integer, otherwise the result is undefined.)
- 1. If $b=1$, return $1$ and stop. If $a=0$, return $0$ and stop. Otherwise, replace $[a/b;]$ by $[Mod(a,b)/b;]$ (identity 4).
- 2. Find the largest power of $2$ that divides $a$. Say, $a=2^s*c$ where $c$ is odd. Replace $[a/b;]$ by $[c/b;]*(-1)^(s*(b^2-1)/8)$ (identities 2 and 3).
- 3. Now that $c<b$, replace $[c/b;]$ by $[b/c;]*(-1)^((b-1)*(c-1)/4)$ (identity 5).
- Continue to step 1.
Note that the arguments $a$, $b$ may be very large integers and we should avoid performing multiplications of these numbers. We can compute $(-1)^((b-1)*(c-1)/4)$ without multiplications. This expression is equal to $1$ if either $b$ or $c$ is equal to 1 mod 4; it is equal to $-1$ only if both $b$ and $c$ are equal to 3 mod 4. Also, $(-1)^((b^2-1)/8)$ is equal to $1$ if either $b:=1$ or $b:=7$ mod 8, and it is equal to $-1$ if $b:=3$ or $b:=5$ mod 8. Of course, if $s$ is even, none of this needs to be computed.
Integer partitions¶
partitions of an integer¶
A partition of an integer $n$ is a way of writing $n$ as the sum of positive integers, where the order of these integers is unimportant. For example, there are 3 ways to write the number 3 in this way: $3=1+1+1$, $3=1+2$, $3=3$. The function {PartitionsP} counts the number of such partitions.
partitions of an integer!by Rademacher-Hardy-Ramanujan series¶
Large $n$
The first algorithm used to compute this function uses the Rademacher-Hardy-Ramanujan (RHR) theorem and is efficient for large $n$. (See for example [Ahlgren <i>et al.</i> 2001].) The number of partitions $P(n)$ is equal to an infinite sum:
$$ P(n) = 1/(Pi*Sqrt(2))*Sum(k,1,Infinity, Sqrt(k)*A(k,n)*S(k,n)) $$, where the functions $A$ and $S$ are defined as follows:
$$S(k,n) := Deriv(n) Sinh(Pi/k*Sqrt(2/3*(n-1/24)))/Sqrt(n-1/24)$$
$$ A(k,n) := Sum(l,1,k, delta(Gcd(l,k),1)*Exp(-2*Pi*I*(l*n)/k+Pi*I*B(k,l))) $$, where $delta(x,y)$ is the Kronecker delta function (so that the summation goes only over integers $l$ which are mutually prime with $k$) and $B$ is defined by $$ B(k,l) := Sum(j,1,k-1, j/k*(l*j/k-Floor(l*j/k)-1/2)) $$.
The first term of the series gives, at large $n$, the Hardy-Ramanujan asymptotic estimate, $$ P(n) <> P_0(n) := 1/(4*n*Sqrt(3))*Exp(Pi*Sqrt((2*n)/3))$$. The absolute value of each term decays quickly, so after $O(Sqrt(n))$ terms the series gives an answer that is very close to the integer result.
There exist estimates of the error of this series, but they are complicated. The series is sufficiently well-behaved and it is easier to determine the truncation point heuristically. Each term of the series is either 0 (when all terms in $A(k,n)$ happen to cancel) or has a magnitude which is not very much larger than the magnitude of the previous nonzero term. (But the series is not actually monotonic.) In the current implementation, the series is truncated when $Abs(A(k,n)*S(n)*Sqrt(k))$ becomes smaller than $0.1$ for the first time; in any case, the maximum number of calculated terms is $5+Sqrt(n)/2$. One can show that asymptotically for large $n$, the required number of terms is less than $mu/Ln(mu)$, where $mu:=Pi*Sqrt((2*n)/3)$.
[Ahlgren <i>et al.</i> 2001] mention that there exist explicit constants $B[1]$ and $B[2]$ such that $$ Abs(P(n)-Sum(k,1,B[1]*Sqrt(n),A(k,n))) < B[2]*n^(-1/4)$$.
The floating-point precision necessary to obtain the integer result must be at least the number of digits in the first term $P_0(n)$, i.e. $$ Prec > (Pi*Sqrt(2/3*n)-Ln(4*n*Sqrt(3)))/Ln(10) $$. However, Yacas currently uses the fixed-point precision model. Therefore, the current implementation divides the series by $P_0(n)$ and computes all terms to $Prec$ digits.
The RHR algorithm requires $O((n/Ln(n))^(3/2))$ operations, of which $O(n/Ln(n))$ are long multiplications at precision $Prec<>O(Sqrt(n))$ digits. The computational cost is therefore $O(n/Ln(n)*M(Sqrt(n)))$.
partitions of an integer!by recurrence relation¶
Small $n$
The second, simpler algorithm involves a recurrence relation $$ P[n] = Sum(k,1,n, (-1)^(k+1)*( P[n-k*(3*k-1)/2]+P[n-k*(3*k+1)/2] ) ) $$. The sum can be written out as $$ P(n-1)+P(n-2)-P(n-5)-P(n-7)+... $$, where 1, 2, 5, 7, ... is the “generalized pentagonal sequence” generated by the pairs $k*(3*k-1)/2$, $k*(3*k+1)/2$ for $k=1$, 2, ... The recurrence starts from $P(0)=1$, $P(1)=1$. (This is implemented as {PartitionsP’recur}.)
The sum is actually not over all $k$ up to $n$ but is truncated when the pentagonal sequence grows above $n$. Therefore, it contains only $O(Sqrt(n))$ terms. However, computing $P(n)$ using the recurrence relation requires computing and storing $P(k)$ for all $1<=k<=n$. No long multiplications are necessary, but the number of long additions of numbers with $Prec<>O(Sqrt(n))$ digits is $O(n^(3/2))$. Therefore the computational cost is $O(n^2)$. This is asymptotically slower than the RHR algorithm even if a slow $O(n^2)$ multiplication is used. With internal Yacas math, the recurrence relation is faster for $n<300$ or so, and for larger $n$ the RHR algorithm is faster.
Miscellaneous functionsdivisors
The function {Divisors} currently returns the number of divisors of integer, while {DivisorsSum} returns the sum of these divisors. (The current algorithms need to factor the number.) The following theorem is used:
Let \(p[1]^k[1]* ... *p[r]^k[r]\) be the prime factorization of $n$, where $r$ is the number of prime factors and $k[r]$ is the multiplicity of the $r$-th factor. Then
..math:: Divisors(n) =(k[1]+1)*...*(k[r]+1)
..math:: DivisorsSum(n) = ( ((p[1]^(k[1]+1) -1)/(p[1]-1)))*...*(p[r]^(k[r]+1)-1)/(p[r]-1) $$.
divisors!proper
The functions {ProperDivisors} and {ProperDivisorsSum} are functions that do the same as the above functions, except they do not consider the number $n$ as a divisor for itself. These functions are defined by:
$ ProperDivisors(n) = Divisors(n) - 1 $,
$ ProperDivisorsSum(n) = DivisorsSum(n) - n $.
Another number-theoretic function is {Moebius}, defined as follows: $Moebius(n)=(-1)^r$ if no factors of $n$ are repeated, $Moebius(n)=0$ if some factors are repeated, and $Moebius(n)=1$ if $n = 1$. This again requires to factor the number $n$ completely and investigate the properties of its prime factors. From the definition, it can be seen that if $n$ is prime, then $Moebius(n) = -1 $. The predicate {IsSquareFree(n)} then reduces to $Moebius(n)!=0$, which means that no factors of $n$ are repeated.
Gaussian integers¶
A “Gaussian integer” is a complex number of the form \(z = a+b*\imath\), where $a$ and $b$ are ordinary (rational) integers. (FOOT To distinguish ordinary integers from Gaussian integers, the ordinary integers (with no imaginary part) are called “rational integers”.) The ring of Gaussian integers is usually denoted by $Z$[$I$] in the mathematical literature. It is an example of a ring of algebraic integers.
The function {GaussianNorm} computes the norm $N(z)=a^2+b^2$ of $z$. The norm plays a fundamental role in the arithmetic of Gaussian integers, since it has the multiplicative property: $$ N(z.w) = N(z).N(w) $$.
A unit of a ring is an element that divides any other element of the ring. There are four units in the Gaussian integers: $1$, $-1$, $I$, $-I$. They are exactly the Gaussian integers whose norm is $1$. The predicate {IsGaussianUnit} tests for a Gaussian unit.
Two Gaussian integers $z$ and $w$ are “associated” is $z/w$ is a unit. For example, $2+I$ and $-1+2*I$ are associated.
A Gaussian integer is called prime if it is only divisible by the units and by its associates. It can be shown that the primes in the ring of Gaussian integers are:
- $1+i$ and its associates.
- The rational (ordinary) primes of the form $4*n+3$.
- 3. The factors $a+b*I$ of rational primes $p$ of the form $p=4*n+1$, whose norm is $p=a^2+b^2$.
For example, $7$ is prime as a Gaussian integer, while $5$ is not, since $ 5 = (2+I)*(2-I) $. Here $2+I$ is a Gaussian prime.
Factors¶
The ring of Gaussian integers is an example of an Euclidean ring, i.e. a ring where there is a division algorithm. This makes it possible to compute the greatest common divisor using Euclid’s algorithm. This is what the function {GaussianGcd} computes.
As a consequence, one can prove a version of the fundamental theorem of arithmetic for this ring: The expression of a Gaussian integer as a product of primes is unique, apart from the order of primes, the presence of units, and the ambiguities between associated primes.
The function {GaussianFactors} finds this expression of a Gaussian integer $z$ as the product of Gaussian primes, and returns the result as a list of pairs {{p,e}}, where $p$ is a Gaussian prime and $e$ is the corresponding exponent. To do that, an auxiliary function called {GaussianFactorPrime} is used. This function finds a factor of a rational prime of the form $4*n+1$. We compute $a := (2*n)!$ (mod p). By Wilson’s theorem $a^2$ is congruent to $-1$ (mod $p$), and it follows that $p$ divides $(a+I)*(a-I)=a^2+1$ in the Gaussian integers. The desired factor is then the {GaussianGcd} of $a+I$ and $p$. If the result is $a+b*I$, then $p=a^2+b^2$.
If $z$ is a rational (i.e. real) integer, we factor $z$ in the Gaussian integers by first factoring it in the rational integers, and after that by factoring each of the integer prime factors in the Gaussian integers.
If $z$ is not a rational integer, we find its possible Gaussian prime factors by first factoring its norm $N(z)$ and then computing the exponent of each of the factors of $N(z)$ in the decomposition of $z$.
References for Gaussian integers¶
- 1. G. H. Hardy and E. M. Wright, <i>An Introduction to the Theory of Numbers</i>. Oxford University Press (1945).
- 2. H. Pollard, <i>The theory of Algebraic Numbers</i>. Wiley, New York (1965).
A simple factorization algorithm for univariate polynomials¶
This section discusses factoring polynomials using arithmetic modulo prime numbers. Information was used from D. Knuth, <I>The Art of Computer Programming, Volume 2, Seminumerical Algorithms </I> and J.H. Davenport et. al., <I>Computer Algebra, SYSTEMS AND ALGORITHMS FOR ALGEBRAIC COMPUTATION</I>.
A simple factorization algorithm is developed for univariate polynomials. This algorithm is implemented as the function {BinaryFactors}. The algorithm was named the binary factoring algorithm since it determines factors to a polynomial modulo $2^n$ for successive values of $n$, effectively adding one binary digit to the solution in each iteration. No reference to this algorithm has been found so far in literature.
Berlekamp showed that polynomials can be efficiently factored when arithmetic is done modulo a prime. The Berlekamp algorithm is only efficient for small primes, but after that Hensel lifting can be used to determine the factors modulo larger numbers.
The algorithm presented here is similar in approach to applying the Berlekamp algorithm to factor modulo a small prime, and then factoring modulo powers of this prime (using the solutions found modulo the small prime by the Berlekamp algorithm) by applying Hensel lifting. However it is simpler in set up. It factors modulo 2, by trying all possible factors modulo 2 (two possibilities, if the polynomial is monic). This performs the same action usually left to the Berlekamp step. After that, given a solution modulo $2^n$, it will test for a solution $f_i$ modulo $2^n$ if $f_i$ or $f_i + 2^n$ are a solution modulo $2^(n+1)$.
This scheme raises the precision of the solution with one digit in binary representation. This is similar to the linear Hensel lifting algorithm, which factors modulo $p^n$ for some prime $p$, where $n$ increases by one after each iteration. There is also a quadratic version of Hensel lifting which factors modulo $p^2^n$, in effect doubling the number of digits (in p-adic expansion) of the solution after each iteration. However, according to “Davenport”, the quadratic algorithm is not necessarily faster.
The algorithm here thus should be equivalent in complexity to Hensel lifting linear version. This has not been verified yet.
Modular arithmetic¶
This section copies some definitions and rules from <I>The Art of Computer Programming, Volume 1, Fundamental Algorithms </I> regarding arithmetic modulo an integer.
Arithmetic modulo an integer $p$ requires performing the arithmetic operation and afterwards determining that integer modulo $p$. A number $x$ can be written as $$x=q*p+r$$ where $q$ is called the quotient, and $r$ remainder. There is some liberty in the range one chooses $r$ to be in. If $r$ is an integer in the range ${0,1, ... ,(p-1)}$ then it is the <i>modulo</i>, $r = Mod(x,p)$.
When $Mod(x,p) = Mod(y,p)$, the notation $Mod(x=y,p)$ is used. All arithmetic calculations are done modulo an integer $p$ in that case.
For calculations modulo some $p$ the following rules hold:
- If $Mod(a=b,p)$ and $Mod(x=y,p)$, then $Mod(a*x=b*y,p)$, $Mod(a+x=b+y,p)$, and $Mod(a-x=b-y,p)$. This means that for instance also $Mod(x^n,p) = Mod(Mod(x,p)^n,p)$
- Two numbers $x$ and $y$ are <i>relatively prime</i> if they don’t share a common factor, that is, if their greatest common denominator is one, $Gcd(x,y)=1$.
- If $Mod(a*x=b*y,p)$ and if $Mod(a=b,p)$, and if $a$ and $p$ are relatively prime, then $Mod(x=y,p)$. This is useful for dividing out common factors.
- $Mod(a=b,p)$ if and only if $Mod(a*n=b*n,n*p)$ when $n != 0$. Also, if $r$ and $s$ are relatively prime, then $Mod(a=b,r*s)$ only if $Mod(a=b,r)$ and $Mod(a=b,s)$. These rules are useful when the modulus is changed.
For polynomials $v_1(x)$ and $v_2(x)$ it further holds that $$ Mod((v_1(x)+v_2(x))^p = v_1(x)^p + v_2(x)^p,p) $$ This follows by writing out the expression, noting that the binomial coefficients that result are multiples of $p$, and thus their value modulo $p$ is zero ($p$ divides these coefficients), so only the two terms on the right hand side remain.
Some corollaries¶
One corollary of the rules for calculations modulo an integer is <i>Fermat’s theorem, 1640</i> : if $p$ is a prime number then $$Mod(a^p=a,p)$$ for all integers $a$ (for a proof, see Knuth).
An interesting corollary to this is that, for some prime integer $p$: $$ Mod(v(x)^p = v(x^p),p) $$. This follows from writing it out and using Fermat’s theorem to replace $a^p$ with $a$ where appropriate (the coefficients to the polynomial when written out, on the left hand side).
Factoring using modular arithmetic¶
The task is to factor a polynomial
$$ p(x) = a_n*x^n + ... + a_0 $$
into a form
$$ p(x) = C*g(x)*f_1(x)^p_1*f_2(x)^p_2 * ... * f_m(x)^p_m $$
Where $f_i(x)$ are irreducible polynomials of the form:
$$ f_i(x) = x+c_i $$
The part that could not be factorized is returned as $g(x)$, with a possible constant factor $C$.
The factors $f_i(x)$ and $g(x)$ are determined uniquely by requiring them to be monic. The constant $C$ accounts for a common factor.
The $c_i$ constants in the resulting solutions $f_i(x)$ can be rational numbers (or even complex numbers, if Gaussian integers are used).
Preparing the polynomial for factorization¶
The final factoring algorithm needs the input polynomial to be monic with integer coefficients (a polynomial is monic if its leading coefficient is one). Given a non-monic polynomial with rational coefficients, the following steps are performed:
Convert polynomial with rational coefficients to polynomial with integer coefficients
First the least common multiple $lcm$ of the denominators of the coefficients $p(x)$ has to be found, and the polynomial is multiplied by this number. Afterwards, the $C$ constant in the result should have a factor $1/lcm$.
The polynomial now only has integer coefficients.
Convert polynomial to a monic polynomial¶
The next step is to convert the polynomial to one where the leading coefficient is one. In order to do so, following “Davenport”, the following steps have to be taken:
- Multiply the polynomial by $a_n^(n-1)$
- Perform the substitution $x=(y/a_n)$
The polynomial is now a monic polynomial in $y$.
After factoring, the irreducible factors of $p(x)$ can be obtained by multiplying $C$ with $1/(a_n^(n-1))$, and replacing $y$ with $a_n*x$. The irreducible solutions $a_n*x+c_i$ can be replaced by $x+c_i/a_i$ after multiplying $C$ by $a_n$, converting the factors to monic factors.
After the steps described here the polynomial is now monic with integer coefficients, and the factorization of this polynomial can be used to determine the factors of the original polynomial $p(x)$.
Definition of division of polynomials¶
To factor a polynomial a division operation for polynomials modulo some integer is needed. This algorithm needs to return a quotient $q(x)$ and remainder $r(x)$ such that:
$$ Mod(p(x) = q(r)*d(x) + r(x),p) $$
for some polymomial $d(x)$ to be divided by, modulo some integer p. $d(x)$ is said to divide $p(x)$ (modulo $p$) if $r(x)$ is zero. It is then a factor modulo $p$.
For binary factoring algorithm it is important that if some monic $d(x)$ divides $p(x)$, then it also divides $p(x)$ modulo some integer $p$.
Define $deg(f(x))$ to be the degree of $f(x)$ and $lc(f(x))$ to be the leading coefficient of $f(x)$. Then, if $deg(p(x)) >= deg(d(x))$, one can compute an integer $s$ such that
$$ Mod(lc(d(x))*s = lc(p(x)),p) $$
If $p$ is prime, then
$$s = Mod(lc(p(x))*lc(d(x))^(p-2),p) $$
Because $Mod(a^(p-1) = 1,p)$ for any $a$. If $p$ is not prime but $d(x)$ is monic (and thus $lc(d(x)) = 1$),
$$s = lc(p(x)) $$
This identity can also be used when dividing in general (not modulo some integer), since the divisor is monic.
The quotient can then be updated by adding a term:
$term = s*x^(deg(p(x))-deg(d(x)))$
and updating the polynomial to be divided, $p(x)$, by subtracting $d(x)*term$. The resulting polynomial to be divided now has a degree one smaller than the previous.
When the degree of $p(x)$ is less than the degree of $d(x)$ it is returned as the remainder.
A full division algorithm for arbitrary integer $p>1$ with $lc(d(x)) = 1$ would thus look like:
divide(p(x),d(x),p)
q(x) = 0
r(x) = p(x)
while (deg(r(x)) >= deg(d(x)))
s = lc(r(x))
term = s*x^(deg(r(x))-deg(d(x)))
q(x) = q(x) + term
r(x) = r(x) - term*d(x) mod p
return {q(x),r(x)}
The reason we can get away with factoring modulo $2^n$ as opposed to factoring modulo some prime $p$ in later sections is that the divisor $d(x)$ is monic. Its leading coefficient is one and thus $q(x)$ and $r(x)$ can be uniquely determined. If $p$ is not prime and $lc(d(x))$ is not equal to one, there might be multiple combinations for which $p(x) = q(x)*d(x)+r(x)$, and we are interested in the combinations where $r(x)$ is zero. This can be costly to determine unless {q(x),r(x)} is unique. This is the case here because we are factoring a monic polynomial, and are thus only interested in cases where $lc(d(x)) = 1$.
Determining possible factors modulo 2¶
We start with a polynomial $p(x)$ which is monic and has integer coefficients.
It will be factored into a form:
$$ p(x) = g(x)*f_1(x)^p_1*f_2(x)^p_2 * ... * f_m(x)^p_m $$
where all factors $f_i(x)$ are monic also.
The algorithm starts by setting up a test polynomial, $p_test(x)$ which divides $p(x)$, but has the property that
$$ p_test(x) = g(x)*f_1(x)*f_2(x) * ... * f_m(x) $$
Such a polynomial is said to be <i>square-free</i>. It has the same factors as the original polynomial, but the original might have multiple of each factor, where $p_test(x)$ does not.
The square-free part of a polynomial can be obtained as follows:
$$ p_test(x) = p(x)/Gcd(p(x),D(x)p(x)) $$
It can be seen by simply writing this out that $p(x)$ and $D(x)p(x)$ will have factors $f_i(x)^(p_i-1)$ in common. these can thus be divided out.
It is not a requirement of the algorithm that the algorithm being worked with is square-free, but it speeds up computations to work with the square-free part of the polynomial if the only thing sought after is the set of factors. The multiplicity of the factors can be determined using the original $p(x)$.
Binary factoring then proceeds by trying to find potential solutions modulo $p=2$ first. There can only be two such solutions: $x+0$ and $x+1$.
A list of possible solutions $L$ is set up with potential solutions.
Determining factors modulo $2^n$ given a factorization modulo 2¶
At this point there is a list $L$ with solutions modulo $2^n$ for some $n$. The solutions will be of the form: $x+a$. The first step is to determine if any of the elements in $L$ divides $p(x)$ (not modulo any integer). Since $x+a$ divides $p_test(x)$ modulo $2^n$, both $x+a$ and $x+a-2^n$ have to be checked.
If an element in $L$ divides $p_test(x)$, $p_test(x)$ is divided by it, and a loop is entered to test how often it divides $p(x)$ to determine the multiplicity $p_i$ of the factor. The found factor $f_i(x) = x+c_i$ is added as a combination ($x+c_i$, $p_i$). $p(x)$ is divided by $f_i(x)^p_i$.
At this point there is a list $L$ of factors that divide $p_test(x)$ modulo $2^n$. This implies that for each of the elements $u$ in $L$, either $u$ or $u+2^n$ should divide $p_test(x)$ modulo $2^(n+1)$. The following step is thus to set up a new list with new elements that divide $p_test(x)$ modulo $2^(n+1)$.
The loop is re-entered, this time doing the calculation modulo $2^(n+1)$ instead of modulo $2^n$.
The loop is terminated if the number of factors found equals $deg(p_test(x))$, or if $2^n$ is larger than the smallest non-zero coefficient of $p_test(x)$ as this smallest non-zero coefficient is the product of all the smallest non-zero coefficients of the factors, or if the list of potential factors is zero.
The polynomial $p(x)$ can not be factored any further, and is added as a factor ($p(x)$, $1$).
The function {BinaryFactors}, when implemented, yields the following interaction in Yacas:
In> BinaryFactors((x+1)^4*(x-3)^2)
Out> {{x-3,2},{x+1,4}}
In> BinaryFactors((x-1/5)*(2*x+1/3))
Out> {{2,1},{x-1/5,1},{x+1/6,1}}
In> BinaryFactors((x-1123125)*(2*x+123233))
Out> {{2,1},{x-1123125,1},{x+123233/2,1}}
The binary factoring algorithm starts with a factorization modulo 2, and then each time tries to guess the next bit of the solution, maintaining a list of potential solutions. This list can grow exponentially in certain instances. For instance, factoring $(x-a)*(x-2*a)*(x-3*a)* ... $ implies a that the roots have common factors. There are inputs where the number of potential solutions (almost) doubles with each iteration. For these inputs the algorithm becomes exponential. The worst-case performance is therefore exponential. The list of potential solutions while iterating will contain a lot of false roots in that case.
Efficiently deciding if a polynomial divides another¶
Given the polynomial $p(x)$, and a potential divisor $$ f_i(x) = x-p $$ modulo some $q=2^n$ an expression for the remainder after division is
$$ rem(p)=Sum(i,0,n,a_i*p^i) $$
For the initial solutions modulo 2, where the possible solutions are $x$ and $x-1$. For $p=0$, $rem(0) = a_0$. For $p=1$, $rem(1) = Sum(i,0,n,a_i)$ .
Given a solution $x-p$ modulo $q=2^n$, we consider the possible solutions $Mod(x-p,2^(n+1))$ and $Mod(x-(p+2^n),2^n+1)$.
$x-p$ is a possible solution if $Mod(rem(p),2^(n+1)) = 0$.
$x-(p+q)$ is a possible solution if $Mod(rem(p+q),2^(n+1)) = 0$. Expanding $Mod(rem(p+q),2*q)$ yields:
$$ Mod(rem(p+q),2*q) = Mod(rem(p) + extra(p,q),2*q) $$
When expanding this expression, some terms grouped under $extra(p,q)$ have factors like $2*q$ or $q^2$. Since $q=2^n$, these terms vanish if the calculation is done modulo $2^(n+1)$.
The expression for $extra(p,q)$ then becomes $$ extra(p,q) = q*Sum(i,1,n/2, (2*i-1)*a(2*i)*p^(2*i-2)) $$
An efficient approach to determining if $x-p$ or $x-(p+q)$ divides $p(x)$ modulo $2^(n+1)$ is then to first calculate $Mod(rem(p),2*q)$. If this is zero, $x-p$ divides $p(x)$. In addition, if $Mod(rem(p)+extra(p,q),2*q)$ is zero, $x-(p+q)$ is a potential candidate.
Other efficiencies are derived from the fact that the operations are done in binary. Eg. if $q=2^n$, then $q_next=2^(n+1) = 2*q = q<<1 $ is used in the next iteration. Also, calculations modulo $2^n$ are equivalent to performing a bitwise and with $2^n-1$. These operations can in general be performed efficiently on todays hardware which is based on binary representations.
Extending the algorithm¶
Only univariate polynomials with rational coefficients have been considered so far. This could be extended to allow for roots that are complex numbers $a+I*b$ where both $a$ and $b$ are rational numbers.
For this to work the division algorithm would have to be extended to handle complex numbers with integer $a$ and $b$ modulo some integer, and the initial setup of the potential solutions would have to be extended to try $x+1+I$ and $x+I$ also. The step where new potential solutions modulo $2^(n+1)$ are determined should then also test for $x+I*2^n$ and $x+2^n+I*2^n$.
The same extension could be made for multivariate polynomials, although setting up the initial irreducible polynomials that divide $p_test(x)$ modulo 2 might become expensive if done on a polynomial with many variables ($2^(2^m-1)$ trials for $m$ variables).
Lastly, polynomials with real-valued coefficients <i>could</i> be factored, if the coefficients were first converted to rational numbers. However, for real-valued coefficients there exist other methods (Sturm sequences).
Newton iteration¶
What the {BinaryFactor} algorithm effectively does is finding a set of potential solutions modulo $2^(n+1)$ when given a set of potential solutions modulo $2^n$. There is a better algorithm that does something similar: Hensel lifting. Hensel lifting is a generalized form of Newton iteration, where given a factorization modulo $p$, each iteration returns a factorization modulo $p^2$.
Newton iteration is based on the following idea: when one takes a Taylor series expansion of a function:
$$f(x[0]+dx) := f(x[0]) + (D(x)f(x[0]))*dx + ... $$
Newton iteration then proceeds by taking only the first two terms in this series, the constant plus the constant times $dx$. Given some good initial value $x_0$, the function will is assumed to be close to a root, and the function is assumed to be almost linear, hence this approximation. Under these assumptions, if we want $f(x_0+dx)$ to be zero,
$$f(x[0]+dx) = f(x[0]) + (D(x)f(x[0]))*dx = 0 $$
This yields:
$$ dx := -f(x[0])/(D(x)f(x[0])) = 0 $$
And thus a next, better, approximation for the root is $x[1]:=x_0-f(x[0])/(D(x)f(x[0]))$, or more general:
$$ x[n+1] =x[n]-f(x[n])/(D(x)f(x[n]))$$
If the root has multiplicity one, a Newton iteration can converge <i>quadratically</i>, meaning the number of decimals precision for each iteration doubles.
As an example, we can try to find a root of $Sin(x)$ near $3$, which should converge to $Pi$.
Setting precision to 30 digits,:
In> Builtin'Precision'Set(30)
Out> True;
We first set up a function $dx(x)$:
In> dx(x):=Eval(-Sin(x)/(D(x)Sin(x)))
Out> True;
And we start with a good initial approximation to $Pi$, namely $3$. Note we should set {x} <i>after</i> we set dx(x), as the right hand side of the function definition is evaluated. We could also have used a different parameter name for the definition of the function $dx(x)$.:
In> x:=3
Out> 3;
We can now start the iteration:
In> x:=N(x+dx(x))
Out> 3.142546543074277805295635410534;
In> x:=N(x+dx(x))
Out> 3.14159265330047681544988577172;
In> x:=N(x+dx(x))
Out> 3.141592653589793238462643383287;
In> x:=N(x+dx(x))
Out> 3.14159265358979323846264338328;
In> x:=N(x+dx(x))
Out> 3.14159265358979323846264338328;
As shown, in this example the iteration converges quite quickly.
Finding roots of multiple equations in multiple variables using Newton iteration¶
One generalization, mentioned in W.H. Press et al., <i>NUMERICAL RECIPES in C, The Art of Scientific computing</i> is finding roots for multiple functions in multiple variables.
Given $N$ functions in $N$ variables, we want to solve
$$ f_i(x[1],...,x[N]) = 0 $$
for $i = 1 .. N $. If de denote by $X$ the vector $$ X := {x[1],x[2],...,x[N]} $$
and by $dX$ the delta vector, then one can write
$$ f_i(X+dX) := f_i(X)+Sum(j,1,N,(D(x_j)f_i(X)))*dx[j] $$
Setting $f_i(X+dX)$ to zero, one obtains
$$ Sum(j,1,N,a[i][j]*dx_j)=b[i]$$
where
$$a[i][j] := D(x_j)f_i(X)$$
and
$$b_i := -f_i(X)$$
So the generalization is to first initialize $X$ to a good initial value, calculate the matrix elements $a[i][j]$ and the vector $b[i]$, and then to proceed to calculate $dX$ by solving the matrix equation, and calculating
$$X[i+1] := X[i] + dX[i]$$
In the case of one function with one variable, the summation reduces to one term, so this linear set of equations was a lot simpler in that case. In this case we will have to solve this set of linear equations in each iteration.
As an example, suppose we want to find the zeroes for the following two functions:
$$f_1(a,x) := Sin(a*x)$$
and
$$f_2(a,x) := a-2$$
It is clear that the solution to this is $a=2$ and $x:=N*Pi/2$ for any integer value $N$.
We will do calculations with precision 30:
In> Builtin'Precision'Set(30)
Out> True;
And set up a vector of functions ${f_1(X),f_2(X)}$ where $X:={a,x}$:
In> f(a,x):={Sin(a*x),a-2}
Out> True;
Now we set up a function {matrix(a,x)} which returns the matrix $a[i][j]$:
In> matrix(a,x):=Eval({D(a)f(a,x),D(x)f(a,x)})
Out> True;
We now set up some initial values:
In> {a,x}:={1.5,1.5}
Out> {1.5,1.5};
The iteration converges a lot slower for this example, so we will loop 100 times:
In> For(ii:=1,ii<100,ii++)[{a,x}:={a,x}+\
N(SolveMatrix(matrix(a,x),-f(a,x)));]
Out> True;
In> {a,x}
Out> {2.,0.059667311457823162437151576236};
The value for $a$ has already been found. Iterating a few more times:
In> For(ii:=1,ii<100,ii++)[{a,x}:={a,x}+\
N(SolveMatrix(matrix(a,x),-f(a,x)));]
Out> True;
In> {a,x}
Out> {2.,-0.042792753588155918852832259721};
In> For(ii:=1,ii<100,ii++)[{a,x}:={a,x}+\
N(SolveMatrix(matrix(a,x),-f(a,x)));]
Out> True;
In> {a,x}
Out> {2.,0.035119151349413516969586788023};
the value for $x$ converges a lot slower this time, and to the uninteresting value of zero (a rather trivial zero of this set of functions). In fact for all integer values $N$ the value $N*Pi/2$ is a solution. Trying various initial values will find them.
Newton iteration on polynomials¶
von zur Gathen et al., <i>Modern Computer algebra</i> discusses taking the inverse of a polynomial using Newton iteration. The task is, given a polynomial $f(x)$, to find a polynomial $g(x)$ such that $f(x) = 1/g(x)$, modulo some power in x. This implies that we want to find a polynom $g$ for which:
$$h(g) = 1/g-f = 0$$
Applying a Newton iteration step $g[i+1] = g[i] - h(g[i])/(D(g)h(g[i]))$ to this expression yields:
$$g[i+1] = 2*g[i] - f*(g[i])^2$$
von zur Gathen then proves by induction that for $f(x)$ monic, and thus $f(0)=1$, given initial value $g_0(x) = 1$, that
$$Mod(f*g_i=1,x^(2^i))$$
Example:
suppose we want to find the polynomial $g(x)$ up to the 7th degree for which $Mod(f(x)*g(x) = 1,x^8)$, for the function
$$ f(x):=1+x+x^2/2+x^3/6+x^4/24 $$
First we define the function f:
In> f:=1+x+x^2/2+x^3/6+x^4/24
Out> x+x^2/2+x^3/6+x^4/24+1;
And initialize $g$ and $i$.:
In> g:=1
Out> 1;
In> i:=0
Out> 0;
Now we iterate, increasing $i$, and replacing $g$ with the new value for $g$:
In> [i++;g:=BigOh(2*g-f*g^2,x,2^i);]
Out> 1-x;
In> [i++;g:=BigOh(2*g-f*g^2,x,2^i);]
Out> x^2/2-x^3/6-x+1;
In> [i++;g:=BigOh(2*g-f*g^2,x,2^i);]
Out> x^7/72-x^6/72+x^4/24-x^3/6+x^2/2-x+1;
The resulting expression must thus be:
$$g(x):=x^7/72-x^6/72+x^4/24-x^3/6+x^2/2-x+1$$
We can easily verify this:
In> Expand(f*g)
Out> x^11/1728+x^10/576+x^9/216+(5*x^8)/576+1;
This expression is 1 modulo $x^8$, as can easily be shown:
In> BigOh(%,x,8)
Out> 1;
YAGY¶
Credits [*]¶
Original/primary authors¶
- Ayal Pinkus apinkus “AT” xs4all “DOT” nl
- This project was started by Ayal Pinkus who remains the main author and the primary maintainer.
- Serge Winitzki serge “AT” cosmos “DOT” phy “DOT” tufts “DOT” edu
- Added factorials over rationals, TeXForm, did a major overhaul of the introduction manual (actually, he wrote large part of the manual as it is), and initiated numerous improvements and test code for Yacas, and implemented yacas_client. Actually, Serge has been one of the larger contributors, and the main force behind the improved documentation.
- Jitse Niesen jn221 “AT” damtp “DOT” cam “DOT” ac “DOT” uk
- Reported some bugs, helped improve various parts of Yacas, and greatly improved the manual for Yacas.
Maintainer¶
Grzegorz Mazur teoretyk “AT” gmail “DOT” com
Contributors¶
- Jim Apple japple “AT” freeshell “DOT” org
- Reported bugs and supplied improved code for gcc 3.3.4
- Mark Arrasmith arrasmith “AT” math “DOT” twsu “DOT” edu
- Helped greatly in setting up the fltk-based graphicaluser interface, and fixed some bugs relating to limits regarding infinity.
- Fred Bacon bacon “AT” aerodyne “DOT” com
- Fixed some compiler errors on the newer gcc compiles. Reported some important bugs.
- Jay Belanger belanger “AT” truman “DOT” edu
- Reported some bugs and improved some of the GnuPlot code. He also wrote the yacas.el file, which allows you to run yacas from within emacs.
- Roberto Colistete Junior
- Is maintaining a version of Yacas for SymbianOS.
- Sebastian Ferraro sferraro “AT” criba “DOT” edu “DOT” ar
- Reported bugs and supplied improved code (determinants).
- John Fremlin
- Added some code for fast calculation of roots of a cubic polynomial.
- Peter Gilbert peterdgilbert “AT” gmail “DOT” com
- Made many improvements to the C++ code to make it conform more to standard C++ coding conventions (class interfaces looking more like stl), improved the regression test suite.
- James Gilbertson azurite “AT” telusplanet “DOT” net
- Win32 port, improved error reporting. Added initial version of Karatsuba multiplication, and added some matrix functions to the math library.
- Gabor Grothendieck
- Gabor is the maintainer of Ryacas, and gave valuable feedback on the new web site.
- Rene Grothmann 2004 “AT” rene-grothmann “DOT” de
- Married Euler to Yacas.
- Franz Hack franz.hack “AT” web “DOT” de
- Supplied a Delphi interface to the Yacas DLL.
- Ingrid Halters
- Helped improve the ease of use of the Yacas web site.
- Mark Hatsell mark “AT” autograph-maths “DOT” com
- Made the server code work on Windows.
- Joris van der Hoeven TeXmacs “AT” math “DOT” u-psud “DOT” fr
- Helped with texmacs support.
- Wolfgang Hšnig pocket_software “AT” web “DOT” de
- Created a port of Yacas that runs on PocketPC, to be found here.
- Daniel Richard G. straker “AT” MIT “DOT” EDU
- Added autoconf/automake scripts, made Sun/Sgi compilation possible, created a rpm spec file, many many many changes to clean up the source distribution.
- Igor Khavkine
- Added ‘Diverge’ and ‘Curl’, and implemented threading for the derivative operator (the gradient). Fixed GMP code.
- John Lapeyre
- Made some modifications to the make file, and improved some math code.
- Jonathan Leto jonathan “AT” leto “DOT” net
- Helped improve the integration algorithm, and helped extend the tests used for Yacas (finding numerous bugs).
- Vladimir Livshits livshits “AT” cs “DOT” stanford “DOT” edu
- Set up the initial sourceforge CVS repository, and updated the Windows version source code. He also greatly improved the logic theorem prover code.
- Eugenia Loli
- Helped build the BeOS version of Yacas.
Adolf Mathias adolf_mathias “AT” web “DOT” de
Grzegorz Mazur teoretyk “AT” gmail “DOT” com
- Pablo De Nápoli pdenapo “AT” yahoo “DOT” com
- Fixed the configure script so Yacas compiles under cygwin.
- Gopal Narayanan gopal “AT” debian “DOT” org
- Debian package maintainer. Made a man page for Yacas.
Marta Noga marta.noga “AT” gmail “DOT” com
- Christian Obrecht christian “DOT” obrecht “AT” wanadoo “DOT” fr
- Made a much better Limit, and made Yacas behave better at infinity.
- Alberto González Palomo
- Implemented a console-mode version of Yacas for AgendaVR. Changed the directory structure for the script files, and implemented initial support for OpenMath.
- Doreen Pinkus d “DOT” pinkus “AT” hccnet “DOT” nl
- Designed the second version of the Web site for Yacas.
- Mike Pinna mike “AT” autograph-maths “DOT” com
- Applied some bug fixes.
- Savario Prinz yacas “AT” mac “DOT” com
- Built a fantastic Mac version of Yacas.
- Dirk Reusch
- Added some linear algebra functions, and fixed some predicate functions.
- Daniel Rigby
- Brought a client-server structure to the EPOC32 version of Yacas.
- Juan Pablo Romero jpablo_romero “AT” hotmail “DOT” com
- Reported many bugs, made many suggestions for improvements, and supplied improved code (yacas scripts and makefile code).
- Robert V Schipper rvs “AT” achilles “DOT” nfia “DOT” org
- Ironed out a few bugs in Yacas.
- Schneelocke
- Reported an important bug in numeric calculations.
HenSiong Tan tan “AT” stat “DOT” psu “DOT” edu
- Yannick Versley yannick “AT” versley “DOT” de
- Sent some patches regarding bugs relating integration and differentiation.
Adrian V. qwert2003 “AT” users “DOT” sourceforge “DOT” net
- Ladislav Zejda
- Supplied patches to make Yacas work on Dec Alpha’s.
- Andrei Zorine
- Started the body of statistics code.
[*] | All with last-known email addresses mangled in an obvious way |
License¶
Yacas is Free Software – Free as in Freedom – so you can redistribute yacas or modify it under certain conditions. Yacas comes with ABSOLUTELY NO WARRANTY. See the GNU Lesser General Public License (LGPL) version 2.1 or (at your discretion) any later version for the full conditions. Yacas documentation is distributed under the terms of GNU Free Documentation License.
GNU Lesser General Public License¶
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
<one line to give the library's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
<signature of Ty Coon>, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!'
GNU Free Documentation License¶
GNU Free Documentation License
Version 1.1, March 2000
Copyright (C) 2000 Free Software Foundation, Inc.
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
0. PREAMBLE
The purpose of this License is to make a manual, textbook, or other
written document "free" in the sense of freedom: to assure everyone
the effective freedom to copy and redistribute it, with or without
modifying it, either commercially or noncommercially. Secondarily,
this License preserves for the author and publisher a way to get
credit for their work, while not being considered responsible for
modifications made by others.
This License is a kind of "copyleft", which means that derivative
works of the document must themselves be free in the same sense. It
complements the GNU General Public License, which is a copyleft
license designed for free software.
We have designed this License in order to use it for manuals for free
software, because free software needs free documentation: a free
program should come with manuals providing the same freedoms that the
software does. But this License is not limited to software manuals;
it can be used for any textual work, regardless of subject matter or
whether it is published as a printed book. We recommend this License
principally for works whose purpose is instruction or reference.
1. APPLICABILITY AND DEFINITIONS
This License applies to any manual or other work that contains a
notice placed by the copyright holder saying it can be distributed
under the terms of this License. The "Document", below, refers to any
such manual or work. Any member of the public is a licensee, and is
addressed as "you".
A "Modified Version" of the Document means any work containing the
Document or a portion of it, either copied verbatim, or with
modifications and/or translated into another language.
A "Secondary Section" is a named appendix or a front-matter section of
the Document that deals exclusively with the relationship of the
publishers or authors of the Document to the Document's overall subject
(or to related matters) and contains nothing that could fall directly
within that overall subject. (For example, if the Document is in part a
textbook of mathematics, a Secondary Section may not explain any
mathematics.) The relationship could be a matter of historical
connection with the subject or with related matters, or of legal,
commercial, philosophical, ethical or political position regarding
them.
The "Invariant Sections" are certain Secondary Sections whose titles
are designated, as being those of Invariant Sections, in the notice
that says that the Document is released under this License.
The "Cover Texts" are certain short passages of text that are listed,
as Front-Cover Texts or Back-Cover Texts, in the notice that says that
the Document is released under this License.
A "Transparent" copy of the Document means a machine-readable copy,
represented in a format whose specification is available to the
general public, whose contents can be viewed and edited directly and
straightforwardly with generic text editors or (for images composed of
pixels) generic paint programs or (for drawings) some widely available
drawing editor, and that is suitable for input to text formatters or
for automatic translation to a variety of formats suitable for input
to text formatters. A copy made in an otherwise Transparent file
format whose markup has been designed to thwart or discourage
subsequent modification by readers is not Transparent. A copy that is
not "Transparent" is called "Opaque".
Examples of suitable formats for Transparent copies include plain
ASCII without markup, Texinfo input format, LaTeX input format, SGML
or XML using a publicly available DTD, and standard-conforming simple
HTML designed for human modification. Opaque formats include
PostScript, PDF, proprietary formats that can be read and edited only
by proprietary word processors, SGML or XML for which the DTD and/or
processing tools are not generally available, and the
machine-generated HTML produced by some word processors for output
purposes only.
The "Title Page" means, for a printed book, the title page itself,
plus such following pages as are needed to hold, legibly, the material
this License requires to appear in the title page. For works in
formats which do not have any title page as such, "Title Page" means
the text near the most prominent appearance of the work's title,
preceding the beginning of the body of the text.
2. VERBATIM COPYING
You may copy and distribute the Document in any medium, either
commercially or noncommercially, provided that this License, the
copyright notices, and the license notice saying this License applies
to the Document are reproduced in all copies, and that you add no other
conditions whatsoever to those of this License. You may not use
technical measures to obstruct or control the reading or further
copying of the copies you make or distribute. However, you may accept
compensation in exchange for copies. If you distribute a large enough
number of copies you must also follow the conditions in section 3.
You may also lend copies, under the same conditions stated above, and
you may publicly display copies.
3. COPYING IN QUANTITY
If you publish printed copies of the Document numbering more than 100,
and the Document's license notice requires Cover Texts, you must enclose
the copies in covers that carry, clearly and legibly, all these Cover
Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on
the back cover. Both covers must also clearly and legibly identify
you as the publisher of these copies. The front cover must present
the full title with all words of the title equally prominent and
visible. You may add other material on the covers in addition.
Copying with changes limited to the covers, as long as they preserve
the title of the Document and satisfy these conditions, can be treated
as verbatim copying in other respects.
If the required texts for either cover are too voluminous to fit
legibly, you should put the first ones listed (as many as fit
reasonably) on the actual cover, and continue the rest onto adjacent
pages.
If you publish or distribute Opaque copies of the Document numbering
more than 100, you must either include a machine-readable Transparent
copy along with each Opaque copy, or state in or with each Opaque copy
a publicly-accessible computer-network location containing a complete
Transparent copy of the Document, free of added material, which the
general network-using public has access to download anonymously at no
charge using public-standard network protocols. If you use the latter
option, you must take reasonably prudent steps, when you begin
distribution of Opaque copies in quantity, to ensure that this
Transparent copy will remain thus accessible at the stated location
until at least one year after the last time you distribute an Opaque
copy (directly or through your agents or retailers) of that edition to
the public.
It is requested, but not required, that you contact the authors of the
Document well before redistributing any large number of copies, to give
them a chance to provide you with an updated version of the Document.
4. MODIFICATIONS
You may copy and distribute a Modified Version of the Document under
the conditions of sections 2 and 3 above, provided that you release
the Modified Version under precisely this License, with the Modified
Version filling the role of the Document, thus licensing distribution
and modification of the Modified Version to whoever possesses a copy
of it. In addition, you must do these things in the Modified Version:
A. Use in the Title Page (and on the covers, if any) a title distinct
from that of the Document, and from those of previous versions
(which should, if there were any, be listed in the History section
of the Document). You may use the same title as a previous version
if the original publisher of that version gives permission.
B. List on the Title Page, as authors, one or more persons or entities
responsible for authorship of the modifications in the Modified
Version, together with at least five of the principal authors of the
Document (all of its principal authors, if it has less than five).
C. State on the Title page the name of the publisher of the
Modified Version, as the publisher.
D. Preserve all the copyright notices of the Document.
E. Add an appropriate copyright notice for your modifications
adjacent to the other copyright notices.
F. Include, immediately after the copyright notices, a license notice
giving the public permission to use the Modified Version under the
terms of this License, in the form shown in the Addendum below.
G. Preserve in that license notice the full lists of Invariant Sections
and required Cover Texts given in the Document's license notice.
H. Include an unaltered copy of this License.
I. Preserve the section entitled "History", and its title, and add to
it an item stating at least the title, year, new authors, and
publisher of the Modified Version as given on the Title Page. If
there is no section entitled "History" in the Document, create one
stating the title, year, authors, and publisher of the Document as
given on its Title Page, then add an item describing the Modified
Version as stated in the previous sentence.
J. Preserve the network location, if any, given in the Document for
public access to a Transparent copy of the Document, and likewise
the network locations given in the Document for previous versions
it was based on. These may be placed in the "History" section.
You may omit a network location for a work that was published at
least four years before the Document itself, or if the original
publisher of the version it refers to gives permission.
K. In any section entitled "Acknowledgements" or "Dedications",
preserve the section's title, and preserve in the section all the
substance and tone of each of the contributor acknowledgements
and/or dedications given therein.
L. Preserve all the Invariant Sections of the Document,
unaltered in their text and in their titles. Section numbers
or the equivalent are not considered part of the section titles.
M. Delete any section entitled "Endorsements". Such a section
may not be included in the Modified Version.
N. Do not retitle any existing section as "Endorsements"
or to conflict in title with any Invariant Section.
If the Modified Version includes new front-matter sections or
appendices that qualify as Secondary Sections and contain no material
copied from the Document, you may at your option designate some or all
of these sections as invariant. To do this, add their titles to the
list of Invariant Sections in the Modified Version's license notice.
These titles must be distinct from any other section titles.
You may add a section entitled "Endorsements", provided it contains
nothing but endorsements of your Modified Version by various
parties--for example, statements of peer review or that the text has
been approved by an organization as the authoritative definition of a
standard.
You may add a passage of up to five words as a Front-Cover Text, and a
passage of up to 25 words as a Back-Cover Text, to the end of the list
of Cover Texts in the Modified Version. Only one passage of
Front-Cover Text and one of Back-Cover Text may be added by (or
through arrangements made by) any one entity. If the Document already
includes a cover text for the same cover, previously added by you or
by arrangement made by the same entity you are acting on behalf of,
you may not add another; but you may replace the old one, on explicit
permission from the previous publisher that added the old one.
The author(s) and publisher(s) of the Document do not by this License
give permission to use their names for publicity for or to assert or
imply endorsement of any Modified Version.
5. COMBINING DOCUMENTS
You may combine the Document with other documents released under this
License, under the terms defined in section 4 above for modified
versions, provided that you include in the combination all of the
Invariant Sections of all of the original documents, unmodified, and
list them all as Invariant Sections of your combined work in its
license notice.
The combined work need only contain one copy of this License, and
multiple identical Invariant Sections may be replaced with a single
copy. If there are multiple Invariant Sections with the same name but
different contents, make the title of each such section unique by
adding at the end of it, in parentheses, the name of the original
author or publisher of that section if known, or else a unique number.
Make the same adjustment to the section titles in the list of
Invariant Sections in the license notice of the combined work.
In the combination, you must combine any sections entitled "History"
in the various original documents, forming one section entitled
"History"; likewise combine any sections entitled "Acknowledgements",
and any sections entitled "Dedications". You must delete all sections
entitled "Endorsements."
6. COLLECTIONS OF DOCUMENTS
You may make a collection consisting of the Document and other documents
released under this License, and replace the individual copies of this
License in the various documents with a single copy that is included in
the collection, provided that you follow the rules of this License for
verbatim copying of each of the documents in all other respects.
You may extract a single document from such a collection, and distribute
it individually under this License, provided you insert a copy of this
License into the extracted document, and follow this License in all
other respects regarding verbatim copying of that document.
7. AGGREGATION WITH INDEPENDENT WORKS
A compilation of the Document or its derivatives with other separate
and independent documents or works, in or on a volume of a storage or
distribution medium, does not as a whole count as a Modified Version
of the Document, provided no compilation copyright is claimed for the
compilation. Such a compilation is called an "aggregate", and this
License does not apply to the other self-contained works thus compiled
with the Document, on account of their being thus compiled, if they
are not themselves derivative works of the Document.
If the Cover Text requirement of section 3 is applicable to these
copies of the Document, then if the Document is less than one quarter
of the entire aggregate, the Document's Cover Texts may be placed on
covers that surround only the Document within the aggregate.
Otherwise they must appear on covers around the whole aggregate.
8. TRANSLATION
Translation is considered a kind of modification, so you may
distribute translations of the Document under the terms of section 4.
Replacing Invariant Sections with translations requires special
permission from their copyright holders, but you may include
translations of some or all Invariant Sections in addition to the
original versions of these Invariant Sections. You may include a
translation of this License provided that you also include the
original English version of this License. In case of a disagreement
between the translation and the original English version of this
License, the original English version will prevail.
9. TERMINATION
You may not copy, modify, sublicense, or distribute the Document except
as expressly provided for under this License. Any other attempt to
copy, modify, sublicense or distribute the Document is void, and will
automatically terminate your rights under this License. However,
parties who have received copies, or rights, from you under this
License will not have their licenses terminated so long as such
parties remain in full compliance.
10. FUTURE REVISIONS OF THIS LICENSE
The Free Software Foundation may publish new, revised versions
of the GNU Free Documentation License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns. See
http://www.gnu.org/copyleft/.
Each version of the License is given a distinguishing version number.
If the Document specifies that a particular numbered version of this
License "or any later version" applies to it, you have the option of
following the terms and conditions either of that specified version or
of any later version that has been published (not as a draft) by the
Free Software Foundation. If the Document does not specify a version
number of this License, you may choose any version ever published (not
as a draft) by the Free Software Foundation.
ADDENDUM: How to use this License for your documents
To use this License in a document you have written, include a copy of
the License in the document and put the following copyright and
license notices just after the title page:
Copyright (c) YEAR YOUR NAME.
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.1
or any later version published by the Free Software Foundation;
with the Invariant Sections being LIST THEIR TITLES, with the
Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST.
A copy of the license is included in the section entitled "GNU
Free Documentation License".
If you have no Invariant Sections, write "with no Invariant Sections"
instead of saying which ones are invariant. If you have no
Front-Cover Texts, write "no Front-Cover Texts" instead of
"Front-Cover Texts being LIST"; likewise for Back-Cover Texts.
If your document contains nontrivial examples of program code, we
recommend releasing these examples in parallel under your choice of
free software license, such as the GNU General Public License,
to permit their use in free software.
Glossary¶
- arity
Arity is the number of arguments of a function. For example, the function {Cos(x)} has one argument and so we say that “{Cos} has arity 1”. Arity of a function can be 0, 1, 2, ...
{Yacas} allows to define functions with the same name but different arities, and different rules corresponding to these arities will be used. Also, it is possible to define a function with optional arguments, for example, {Plot2D} is one such function. Such functions can have any arity larger or equal to a certain minimum arity (the number of non-optional arguments).
See also
- array
An array is a container object that can hold a fixed number of other Yacas objects in it. Individual elements of an array can be accessed using the {[]} operation. Most list operations also work on arrays.
Arrays are faster than lists but the array size cannot be changed.
See also
- atom
Atoms are basic Yacas objects that are used to represent symbols, numbers, and function names. An atom has a string representation which is shown when it is displayed. For example, {3.14159}, {x}, {A123}, {+}, {“good morning”} are atoms.
Atoms can be of type string, number, or symbol. For example, {y1} is a symbolic atom, {954} is a number atom, and {“”} is an (empty) string atom. Symbolic atoms are normally used in {Yacas} to denote mathematical unknowns and function names. Number and string atoms are used to denote values.
A symbolic atom can be bound to a value (in which case it becomes a variable), or to a rule or several rules (in which case it becomes a function). An atom can also have a property object.
- bodied function
Bodied functions have all arguments except the first one inside parentheses and the first argument outside the argument list, for example:
Integrate(x) Sin(x);
- CAS
- Abbreviation for “computer algebra system”. {Yacas} is a CAS.
- constant
- cached constant
Constants such as
Pi
orGoldenRatio
are symbolic atoms that are specially interpreted by yacas. For example, there are simplification rules that transform expressions such asSin(Pi)
into0
. When requesting a numerical evaluation of a constant, the numerical value is given to the current value as set withN()
.Some constants take a long time to compute and therefore they are cached at the highest precision computed so far. These are the cached constants.
See also
N()
,CachedConstant()
,Pi
,GoldenRatio
,CatalanConstant
,gamma
- equation
To denote symbolic equations, the operator {==} is used. This operator does not assign or compare its sides. For example, the expression {Sin(x)==1} is kept unevaluated and can be passed as argument to functions. For example, In> Solve(Sin(x)==1, x) Out> {x==Pi/2};
The symbolic equation operator {==} is also useful to represent solutions of equations or to specify substitutions, give options, and so on.
- function
A function is a symbolic atom that is bound to a rule or several rules. A function can have none, one, or more arguments. Functions can also have a variable number of arguments. Arguments of functions are arbitrary Yacas objects.
Functions can be evaluated, that is, the rules bound to them may be executed. For example,
Cos(Pi+0)
is an expression that contains two functions and four atoms. The atomPi
is a symbolic atom which is normally not bound to anything. The atom0
is a numeric atom.The atoms
Cos
and+
are symbolic atoms which are bound to appropriate simplification rules. So these two atoms are functions. Note that these functions have different syntax.Cos
is a normal function which takes its arguments in parentheses. The atom+
is a function with special syntax because+
is placed between its arguments and no parentheses are used.The rules to which
+
is bound are such that the expressionPi+0
is evaluated to the symbolic atomPi
. The rules forCos
are such that the expressionCos(Pi)
is evaluated to the numeric atom-1
. The example {Yacas} session is:In> Cos(Pi+0) Out> -1
Some functions are built-in and implemented in C++, while others are library functions.
The built-in functions are usually less flexible than the library functions because they cannot be left unevaluated. Given incorrect arguments, a built-in function will generate an error. However, a user-defined function may simply return unevaluated in such cases.
See also
Function()
,Rule()
,<--()
- list
A list is a basic {Yacas} container object. A list is written as e.g. {{a, b, c}} or {{}} (empty list). Individual elements of a list can be accessed using the {[]} operation. Lists can be concatenated, and individual elements can be removed or inserted.
Lists are ubiquitous in {Yacas}. Most data structures in the standard library is based on lists.
Lists are also used internally to represent {Yacas} expressions. For example, the expression {Cos(x+1)} is represented internally as a nested list: In> FullForm( Cos(x+1) ) (Cos (+ x 1 )) Out> Cos(x+1);
See also
- matrix
A matrix is represented as a list of lists. Matrices are represented in the “row-major” order: a matrix is a list of rows, and each row is a list of its elements.
Some basic linear algebra operations on matrices are supported.
See also
- operator
Operators are functions that have special syntax declared for them. An operator can be “bodied”, infix, prefix or postfix. Because of this, operators must have precedence.
Apart from the syntax, operators are exactly the same as any other functions, they can have rules bound to them in the same way.
- precedence
Precedence is a property of the syntax of an operator that specifies how it is parsed. Only operators, i.e. functions with special syntax, can have precedence. Precedence values are nonnegative integers: 0, 1, ... Lower numbers bind more tightly.
For example, the operator “{+}” binds less tightly (i.e. has a <i>higher</i> precedence value) than the operator “{*}” and so the expression {a+b*c} is parsed as {a+(b*c)}, as one would expect.
Infix operators can have different left-side and right-side precedence. For example, the infix operator “{-}” has left precedence
*EVAL OpLeftPrecedence("-") and right precedence *EVAL OpRightPrecedence("-")
– this allows us to parse expressions such as {a-b+c} correctly, as $(a-b)+c$, and not as $a-(b+c)$.See also
Bodied()
,OpPrecedence()
,OpLeftPrecedence()
,OpRightPrecedence()
- property
Properties are special additional objects (tags) that can be tied to expressions. For example, the expression {1+x} may be tagged by an expression {y} by the command
In> a:= ExtraInfo'Set(1+x,y); Out> 1+x;
Now {a} refers to an expression {1+x} which is different from all other copies of {1+x} because it is tagged by {y}.
See also
- rule
Rules are the principal mechanism of expression evaluation in {Yacas}. A rule specifies that a certain symbolic expression is to be replaced by another expression. If no rule that matches a given symbolic expression can be found, the expression is left unevaluated. This is usually the desired behavior for a CAS. For example, a user can type
In> func1(x+0) Out> func1(x);
and use an undefined function {func1}. Since no rules are defined for the function {func1}, it is not evaluated, but its argument has been simplified.
Only expressions containing functions can be evaluated by rules. (Atoms are evaluated only if they are bound to a value.)
Several rules can be defined for a given function. Rules can be erased or added at run time.
- string
A string is an atom with character string value, for example, {“abcd”}. Individual characters of the string can be accessed using the {[]} operation. Some string manipulation functions are supported.
See also
- syntax
{Yacas} uses an infix syntax similar to C or Fortran. However, the syntax is entirely user-defined and very flexible. Infix, prefix, postfix operators can be defined, as well as “bodied” functions. This allows to write mathematical expressions more comfortably, for example
In> D(x) Sin(x)+1 Out> Cos(x);
Functions with special syntax can have different precedence.
See also
- threaded function
Threaded function applied to a list
In> Cos({Pi/2, Pi/4}) Out> {0,Sqrt(1/2)}
- variable
Variables are symbolic atoms bound to a “value”. Value is any Yacas object, such as an atom or a list. For example, after executing
In> a := 1 Out> 1;
the symbolic atom {a} becomes a variable bound to a value, the numeric atom {1}.
- warranty
- {Yacas} is Free Software (“logiciel libre”) and comes with {NO WARRANTY}. See <the appropriate section of the GPL|yacasdoc://refprog/1/2/> for full information.