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.9.2 can be downloaded from https://github.com/grzegorzmazur/yacas/archive/v1.9.2.zip or https://github.com/grzegorzmazur/yacas/archive/v1.9.2.tar.gz, while the current development version is accessible from https://github.com/grzegorzmazur/yacas/archive/develop.zip.
Compilation¶
Common build options¶
- ENABLE_CYACAS_CONSOLE
- Build text console for the native yacas engine. Enabled by default.
- ENABLE_CYACAS_GUI
- Build graphical interface for the native yacas engine. Requires Qt 5.5. Enabled by default.
- ENABLE_CYACAS_GUI_PRIVATE_CODEMIRROR
- Use bundled copy of CodeMirror. Enabled by default.
- ENABLE_CYACAS_GUI_PRIVATE_MATHJAX
- Use bundled copy of MathJax. Enabled by default.
- ENABLE_CYACAS_KERNEL
- Build native yacas kernel for Jupyter Notebook. Requires Boost, ZeroMQ and zmqpp. Disabled by default.
- ENABLE_JYACAS
- Build the Java yacas engine and text console for it. Disabled by default.
- ENABLE_DOCS
- Generate HTML documentation. Disabled by default.
MacOS X¶
Open
Terminal
windowChange directory to the yacas source directory
Execute
mkdir build cd build cmake -G Xcode [-Dcommon_option=value ...] ..
Open generated project in
Xcode
and build the Release variant
Microsoft Windows¶
Open
Command Prompt
windowChange directory to the yacas source directory
Execute
mkdir build cd build cmake -G "Visual Studio 14 2015 Win64" [-Dcommon_option=value ...] ..
Open generated project in
Visual Studio
and build the Release variant
Linux¶
Open
Terminal
windowChange directory to the yacas source directory
Execute
mkdir build cd build cmake -DCMAKE_BUILD_TYPE=Release [-Dcommon_option=value ...] .. 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
yacas-online¶
build yacas using emscripten
mkdir build_js cd build_js cmake -DCMAKE_TOOLCHAIN_FILE=<EMSCRIPTEN_ROOT>/cmake/Modules/Platform/Emscripten.cmake \ -DENABLE_CYACAS_GUI=No -DENABLE_CYACAS_KERNEL=No -DENABLE_JYACAS=No \ -DENABLE_DOCS=No -DCMAKE_BUILD_TYPE=Release .. make cd ..
where
<EMSCRIPTEN_ROOT>
stands for the Emscripten root directorycopy
build_js/cyacas/yacas/yacas.js
build_js/cyacas/yacas/yacas.js.mem
cyacas/yacas-gui/resources/yacas-online.html
cyacas/yacas-gui/resources/jquery/
cyacas/yacas-gui/resources/mathbar/
cyacas/yacas-gui/resources/plot3d/
cyacas/yacas-gui/resources/yacas_gui/
to the installation directory
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
.
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]
.
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 bodied 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:
D(x) Sin(x);
(taking a derivative)D(x) D(x) Sin(x);
(taking a derivative twice)
The D()
function also accepts an argument specifying how many times the
derivative has to be taken. In that case, the above expressions can also be
written as:
D(x,1) Sin(x);
(taking a derivative)D(x,2) Sin(x);
(taking a derivative twice)
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 yacas will do so, 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 predicates. 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:
Length(m)
(return the length of a list)Reverse(m)
(return the string reversed)Concat(m,m)
(concatenate two strings)m[1]:=d
(setting the first element of the list to a new value,d
, as can be verified by evaluatingm
)
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 rules. 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: 10 # f(0) <-- 1;
. You can verify
that this already works for input value zero, with f(0)
.
Now we come to the more complex line 20 # f(n_IsIntegerGreaterThanZero) <--
n*f(n-1);
Now we realize we need a function IsGreaterThanZero()
, so we
define this function, with IsIntegerGreaterThanZero(_n) <-- (IsInteger(n) And
n > 0);
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)\ldots 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:
20 # f(_n)_(IsIntegerGreaterThanZero(n)) <-- n*f(n-1);
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:
10 # IsIntegerGreaterThanZero(n_IsInteger)_(n>0) <-- True;
20 # IsIntegerGreaterThanZero(_n) <-- False;
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 rule expression has the following form:
[precedence #] pattern [_ postpredicate] <-- replacement;
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):
10 # _x + 0 <-- x;
20 # _x - _x <-- 0;
ArcSin(Sin(_x)) <-- x;
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 bodied 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
not 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 \(\imath\) 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+\imath b\). 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
Param base: integer, base to convert to/from Param number: integer, number to write out in a different base Param “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
Param expression: expression to evaluate Param 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
Param 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
Param x: number or polynomial to expand in continued fractions Param 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 byrest
. 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
Param 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
Param 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
Param 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
Param 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)¶ -
Min
(list) minimum of a number of values
This function returns the minimum value of its argument(s). If the first calling sequence is used, the smaller of
x
andy
is returned. If one uses the second form, the smallest of the entries inlist
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 In> Min(Pi, Exp(1)) Out> Exp(1)
-
Max
(x, y)¶ -
Max
(list) maximum of a number of values
This function returns the maximum value of its argument(s). If the first calling sequence is used, the larger of
x
andy
is returned. If one uses the second form, the largest of the entries inlist
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
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
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=6])¶ search for integer relations between reals
Param xlist: list of numbers Param 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_1+\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”
Param e1: expression to be compared Param 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”
Param e1: expression to be compared Param 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”
Param e1: expression to be compared Param 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”
Param e1: expression to be compared Param 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
Param 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
Param 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;
Elementary functions¶
-
Sin
(x)¶ trigonometric sine function
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
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
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
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
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
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
Example: In> Exp(0) Out> 1; In> Exp(I*Pi) Out> -1; In> N(Exp(1)) Out> 2.7182818284;
-
Ln
(x)¶ natural logarithm
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
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);
Calculus¶
In this chapter, some facilities for doing calculus are described. These include functions implementing differentiation, integration, calculating limits etc.
-
D
(variable[, n=1]) expression¶ derivative
Param variable: variable Param expression: expression to take derivatives of Param n: order Returns: n
-th derivative ofexpression
with respect tovariable
-
D
(variable) expression derivative
Param variable: variable Param list: a list of variables Param expression: expression to take derivatives of Param 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
Param vector: vector field to take the curl of Param basis: list of variables forming the basis This function takes the curl of the vector field
vector
with respect to the variablesbasis
. 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]}
. Bothvector
andbasis
should be lists of length 3.
-
Diverge
(vector, basis)¶ divergence of a vector field
Param vector: vector field to calculate the divergence of Param basis: list of variables forming the basis This function calculates the divergence of the vector field
vector
with respect to the variablesbasis
. The divergence is defined asDiverge(f,x) = D(x[1]) f[1] + ... + D(x[n]) f[n]
, wheren
is the length of the listsvector
andbasis
. These lists should have equal length.
-
HessianMatrix
(function, var)¶ create the Hessian matrix
Param function: a function in \(n\) variables Param 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 :math:` Deriv(x[i]) Deriv(x[j]) f(x)`. If the second 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 ) | \ /
-
JacobianMatrix
(functions, variables)¶ calculate the Jacobian matrix of \(n\) functions in \(n\) variables
Param functions: an \(n\)-dimensional vector of functions Param 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 ) ) ) | \ /
-
Integrate
(var) expr¶ -
Integrate
(var, x1, x2) expr integral
Param expr: expression to integrate Param var: atom, variable to integrate over Param x1: first point of definite integration Param 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
Param var: variable Param val: number or Infinity
Param dir: direction ( Left
orRight
)Param 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;
-
Add
(val1, val2, ...)¶ -
Add
(list) find sum of a list of values
Param val1 val2: expressions Param 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;
-
Multiply
(val1, val2, ...)¶ -
Multiply
(list) product of a list of values
Param val1 val2: expressions Param list: list of expressions to add Multiply all arguments and returns their product. It accepts any number of arguments. The arguments can be also passed as a list.
Example: In> Multiply(2,3,4); Out> 24 In> Multiply(1 .. 10) Out> 3628800
-
Sum
(var, from, to, body)¶ find sum of a sequence
Param var: variable to iterate over Param from: integer value to iterate from Param to: integer value to iterate up to Param 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
Param list: list of values to multiply Param var: variable to iterate over Param from: integer value to iterate from Param to: integer value to iterate up to Param 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
Param var: variable Param at: point to get Taylor series around Param order: order of approximation Param 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
Param var: variable Param at: point to get inverse Taylor series around Param order: order of approximation Param 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\)
Param f: function of var
Param g: function of var
Param var: a variable Param newvar: a new variable to express the result in Param 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()
-
BigOh
(poly, var, degree)¶ drop all terms of a certain order in a polynomial
Param poly: a univariate polynomial Param var: a free variable Param 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
Param xlist: list of argument values Param ylist: list of function values Param 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
Param m: integer Param n: integer, half-integer, or list Param a}, {b: 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 :math:`a*(a+1)*…` which is terminated at the least integer not greater than :math:`b`. The arguments :math:`a` and :math:`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
Param n}, {m: 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
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
-
KroneckerDelta
(i, j)¶ -
KroneckerDelta
({i, j, ...}) Kronecker delta
Calculates the Kronecker delta, which gives \(1\) if all arguments are equal and \(0\) otherwise.
-
LeviCivita
(list)¶ totally anti-symmetric Levi-Civita symbol
Param list: a list of integers \(1,\ldots,n\) in some order LeviCivita()
implements the Levi-Civita symbol.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
Param 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
-
Fibonacci
(n)¶ Fibonacci sequence
The function returns \(n\)-th Fibonacci number
Example: In> Fibonacci(4) Out> 3 In> Fibonacci(8) Out> 21 In> Table(Fibonacci(i), i, 1, 10, 1) Out> {1,1,2,3,5,8,13,21,34,55}
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
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
FactorialSimplify()
,LnCombine()
,LnExpand()
,RadSimp()
,TrigSimpCombine()
-
RadSimp
(expr)¶ simplify expression with nested radicals
This function tries to write the expression
expr
as a sum of roots of integers: \(\sqrt{e_1} + \sqrt{e_2} + ...\), where \(e_1,e_2\) and so on are natural numbers. The expressionexpr
may not contain free variables.It does this by trying all possible combinations for \(e_1,e_2,\ldots\). Every possibility is numerically evaluated using
N()
and compared with the numerical evaluation ofexpr
. If the approximations are equal (up to a certain margin), this possibility is returned. Otherwise, the expression is returned unevaluated.Note
Due to the use of numerical approximations, there is a small chance that the expression returned by
RadSimp()
is close but not equal toexpr
:In> RadSimp(Sqrt(1+10^(-6))) Out> 1;
Note
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;
See also
-
FactorialSimplify
(expression)¶ simplify hypergeometric expressions containing factorials
FactorialSimplify()
takes an expression that may contain factorials, and tries to simplify it. An expression like \(\frac{(n+1)!}{n!}\) would simplify to \((n+1)\).See also
Simplify()
,()
-
LnExpand
(expr)¶ expand a logarithmic expression using standard logarithm rules
LnExpand()
takes an expression of the form \(\ln(expr)\), and applies logarithm rules to expand this into multipleLn()
expressions where possible. An expression like \(\ln(ab^n)\) would be expanded to \(\ln(a)+n\ln(b)\). If the logarithm of an integer is discovered, it is factorised usingFactors()
and expanded as thoughLnExpand()
had been given the factorised form. So \(\ln(18)\) goes to \(\ln(2)+2\ln(3)\).See also
-
LnCombine
(expr)¶ combine logarithmic expressions using standard logarithm rules
LnCombine()
findsLn()
terms in the expression it is given, and combines them using logarithm rules. It is intended to be the converse ofLnExpand()
.See also
-
TrigSimpCombine
(expr)¶ combine products of trigonometric functions
This function applies the product rules of trigonometry, e.g. \(\cos{u}\sin{v} = \frac{1}{2}(\sin(v-u) + \sin(v+u))\). As a result, all products of the trigonometric functions
Cos()
andSin()
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 instanceIntegrate()
, 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
Param eq: equation to solve Param 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}
Param eq: single identity equation Param var: single variable Param eqlist: list of identity equations Param 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
Param expr: expression to make zero Param 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
Param var: variable (or subexpression) to substitute Param value: new value of “var” Param 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
Param poly: a polynomial in “var” Param 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
Param A: coefficient matrix Param 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
Param expr: an expression to find a zero for Param var: free variable to adjust to find a zero Param initial: initial value for “var” to use in the search Param accuracy: minimum required accuracy of the result Param min: minimum value for “var” to use in the search Param 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
Param 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
Param 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
Param 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
Auxilliary Functions¶
-
expr
Where
x==v¶ substitute result into expression
Param expr: expression to evaluate Param x: variable to set Param 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 asvar1==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 throughSolve()
.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
Param 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};
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
Param expr1,expr2: expressions containing a function to solve for This function currently can solve second order homogeneous linear equations with real constant coefficient. 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 asy(n)
, wheren
is any positive 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
Param eqn: equation to test Param 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
Param 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
-
WronskianMatrix
(func, var)¶ create the Wronskian matrix
Param func: an \(n\)-dimensional vector of functions Param 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;
Propositional logic theorem prover¶
-
CanProve
(proposition)¶ try to prove statement
Param 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 \(\neg p\) is false. It continues to simplify \(\neg p\) using the rules:
- \(\neg\neg x \to x\) (eliminate double negation),
- \(x\Rightarrow y \to \neg x \vee y\) (eliminate implication),
- \(\neg (x \wedge y) \to \neg x \vee \neg y\) (De Morgan’s law),
- \(\neg (x \vee y) \to \neg x \wedge \neg y\) (De Morgan’s law),
- \((x \wedge y) \vee z \to (x \vee z) \wedge (y \vee z)\) (distribution),
- \(x \vee (y \wedge z) \to (x \vee y) \wedge (x \vee z)\) (distribution),
and the obvious other rules, such as, \(1 \vee x \to 1\) 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 \vee Y) \wedge (\neg p \vee Z) \to (Y \vee Z)\). If this combination \((Y\vee 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)¶ -
t1
.
t2¶ dot product of tensors
Param t1,t2: tensors (currently only vectors and matrices are supported) Dot()
returns the dot product (aka inner product) of two tensorst1
andt2
. The last index oft1
and the first index oft2
are contracted. CurrentlyDot()
works only for vectors and matrices. Inner product of two vectors, a matrix with a vector (and vice versa) or two matrices yields respectively 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()
-
CrossProduct
(u, v)¶ -
u
X
v¶ cross outer product of vectors
Param u, v: three-dimensional vectors The cross product of the vectors
u
andv
is returned. Bothu
andv
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)¶ -
t1
o
t2¶ outer tensor product
Param t1,t2: tensors (currently only vectors are supported) Outer()
returns the outer product of two tensorst1
andt2
. CurrentlyOuter()
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
Param 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
Param k: index of the base vector to construct Param 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
Param 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
Param n: number of rows Param 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
Param 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
Param 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
Param 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()
,InProduct()
-
OrthonormalBasis
(W)¶ create an orthonormal basis
Param 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
OrthogonalBasis()
,InProduct()
,Normalize()
-
Normalize
(v)¶ normalize a vector
Param 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
InProduct()
,CrossProduct()
-
Transpose
(M)¶ get transpose of a matrix
Param 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
Param 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
Param 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
Param 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
Param M: a matrix Param i}, {j: 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
Param M: a matrix Param i}, {j: 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
Param mat: a square matrix Param 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
Param M: a matrix Param 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
-
Sparsity
(matrix)¶ get the sparsity of a matrix
Param 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;
Predicates¶
-
IsScalar
(expr)¶ test for a scalar
Param 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
Param expr: expression to test Param 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
Param expr: expression to test Param 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
Param expr: expression to test Param 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
Param A: a square matrix IsHermitian(A) returns
True
if {A} is Hermitian andFalse
otherwise. \(A\) is a Hermitian matrix iff Conjugate( Transpose \(A\) )=:math: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
Param A: square matrix {IsOrthogonal(A)} returns
True
if {A} is orthogonal andFalse
otherwise. \(A\) is orthogonal iff \(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
Param 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
Param 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
Param A: a matrix {IsSymmetric(A)} returns
True
if {A} is symmetric andFalse
otherwise. \(A\) is symmetric iff Transpose (\(A\)) =:math: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
Param A: a square matrix {IsSkewSymmetric(A)} returns
True
if {A} is skew symmetric andFalse
otherwise. \(A\) is skew symmetric iff \(Transpose(A)\) =:math:-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
Param 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
Param 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
Eigenproblem¶
-
CharacteristicEquation
(matrix, var)¶ get characteristic polynomial of a matrix
Param matrix: a matrix Param 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
Param 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
Param matrix: a square matrix Param 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
Matrix decompositions¶
-
Cholesky
(A)¶ find the Cholesky decomposition
Param 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
-
LU
(A)¶ find the LU decomposition
Param A: square matrix LU()
performs LU decomposition of a matrix.Example: In> A := {{1,2}, {3,4}} Out> {{1,2},{3,4}} In> {l,u} := LU(A) Out> {{{1,0},{3,1}},{{1,2},{0,-2}}} In> IsLowerTriangular(l) Out> True In> IsUpperTriangular(u) Out> True In> l * u Out> {{1,2},{3,4}}
See also
LDU()
,IsLowerTriangular()
,IsUpperTriangular()
-
LDU
(A)¶ find the LDU decomposition
Param A: square matrix LDU()
performs LDU decomposition of a matrix.Example: In> A := {{1,2}, {3,4}} Out> {{1,2},{3,4}} In> {l,d,u} := LDU(A) Out> {{{1,0},{3,1}},{{1,0},{0,-2}},{{1,2},{0,1}}} In> IsLowerTriangular(l) Out> True In> IsDiagonal(d) Out> True In> IsUpperTriangular(u) Out> True In> l * d * u Out> {{1,2},{3,4}}
See also
LU()
,IsDiagonal()
,IsLowerTriangular()
,IsUpperTriangular()
Special matrices¶
-
VandermondeMatrix
(vector)¶ create the Vandermonde matrix
Param 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 ) | \ /
-
HilbertMatrix
(n)¶ create a Hilbert matrix
Param 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
Param 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
Param 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 ) | \ /
-
SylvesterMatrix
(poly1, poly2, variable)¶ calculate the Sylvester matrix of two polynomials
Param poly1: polynomial Param poly2: polynomial Param 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 :math:` a = 3 :math:`.
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
Param expr: a polynomial expression Param var: a variable Param 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
Param expr: a polynomial Param 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
Param expr: a polynomial Param var: a variable occurring in {expr} Param 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
Param 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
Param 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)¶ -
LeadingCoef
(poly, var) leading coefficient of a polynomial
This function returns the leading coefficient of
poly
, regarded as a polynomial in the variablevar
. The leading coefficient is the coefficient of the term of highest degree. If only one variable appears in the expressionpoly
, 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
(poly, var) monic part of a polynomial
This function returns the monic part of
poly
, regarded as a polynomial in the variablevar
. 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 expressionpoly
, 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
Param p: a polynomial in {x} Given a polynomial \(p = p_1^{n_1}\ldots p_m^{n_m}\) with irreducible polynomials \(p_i\), return the square-free version part (with all the factors having multiplicity 1): \(p_1\ldots 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
Param p: a polynomial in {x} Given a polynomial \(p\) having square-free decomposition \(p = p_1^{n_1}\ldots 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
This command turns the polynomial
expr
, considered as a univariate polynomial invar
, into the Horner form. A polynomial in normal form is an expression such as \(c_0 + c_1x + \ldots + c_nx^n\). If one converts this polynomial into Horner form, one gets the equivalent expression \((\ldots( c_nx + c_{n-1}) x + \ldots + 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
This command tries to expand all the brackets by repeatedly using the distributive laws \(a(b+c) = ab + ac\) and \((a+b)c = ac + bc\). 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
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)¶ -
OrthoP
(n, a, b, x) Legendre and Jacobi orthogonal polynomials
The first calling format with two arguments evaluates the Legendre polynomial of degree
n
at the pointx
. The second form does the same for the Jacobi polynomial with parametersa
andb
, 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) = \frac{2n+a+b-1}{2n+a+b-2}\frac{a^2-b^2+x(2n+a+b-2)(n+a+b)}{2n(n+a+b)}P(n-1,a,b,x) - \frac{(n+a-1)(n+b-1)(2n+a+b)}{n(n+a+b)(2n+a+b-2)}P(n-2,a,b,x)\) for \(n > 1\), with \(P(0,a,b,x) = 1\), \(P(1,a,b,x) = \frac{a-b}{2}+x(1+\frac{a+b}{2})\).
-
OrthoH
(n, x)¶ Hermite orthogonal polynomials
This function evaluates the Hermite polynomial of degree
n
at the pointx
.The Hermite polynomials are orthogonal with respect to the weight function \(\exp(\frac{-x^2}{2})\) on the entire real axis. They satisfy the recurrence relation \(H(n,x) = 2xH(n-1,x) - 2(n-1)H(n-2,x)\) for \(n > 1\), with \(H(0,x) = 1\), \(H(1,x) = 2x\).
Example: In> OrthoH(3, x); Out> x*(8*x^2-12); In> OrthoH(6, 0.5); Out> 31;
See also
-
OrthoG
(n, a, x)¶ Gegenbauer orthogonal polynomials
This function evaluates the Gegenbauer (or ultraspherical) polynomial with parameter
a
and degreen
at the pointx
. The parametera
should be greater than \(-\frac{1}{2}\).The Gegenbauer polynomials are orthogonal with respect to the weight function \((1-x^2)^{a-\frac{1}{2}}\) on the interval \([-1,1]\). Hence they are connected to the Jacobi polynomials via \(G(n, a, x) = P(n, a-\frac{1}{2}, a-\frac{1}{2}, x)\). They satisfy the recurrence relation \(G(n,a,x) = 2(1+\frac{a-1}{n})xG(n-1,a,x)-(1+2\frac{a-2}{n})G(n-2,a,x)\) for \(n>1\), with \(G(0,a,x) = 1\), \(G(1,a,x) = 2x\).
-
OrthoL
(n, a, x)¶ Laguerre orthogonal polynomials
This function evaluates the Laguerre polynomial with parameter
a
and degreen
at the pointx
. The parametera
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+\frac{a-1-x}{n})L(n-1,a,x) -(1-\frac{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)¶ -
OrthoU
(n, x)¶ Chebyshev polynomials
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 pointx
. (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)^{-\frac{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) = 2xT(n-1,x) - T(n-2,x)\), \(U(n,x) = 2xU(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) = 2x\).
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
-
OrthoPSum
(c, x)¶ -
OrthoPSum
(c, a, b, x) -
OrthoGSum
(c, a, x)¶ -
OrthoHSum
(c, x)¶ -
OrthoLSum
(c, a, x)¶ -
OrthoTSum
(c, x)¶ -
OrthoUSum
(c, x)¶ sums of series of orthogonal polynomials
These functions evaluate the sum of series of orthogonal polynomials at the point
x
, with given list of coefficientsc
of the series and fixed polynomial parametersa
,b
(if applicable). The list of coefficients starts with the lowest order, so that for exampleOrthoLSum(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
-
OrthoPoly
(name, n, params, x)¶ internal function for constructing orthogonal polynomials
This function is used internally to construct orthogonal polynomials. It returns the
n
-th polynomial from the familyname
with parametersparams
at the pointx
. All known families are stored in the association list returned by the functionKnownOrthoPoly()
. Thename
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 ordern
and the extra parametersp
, and returns a list of two lists: the first list contains the coefficients{A,B}
of then=1
polynomial, i.e. \(A+Bx\); the second list contains the coefficients{A,B,C}
in the recurrence relation, i.e. \(P_n = (A+Bx)P_{n-1}+CP_{n-2}\).Note
There are only 3 coefficients in the second list, because none of the considered polynomials use \(C+Dx\) instead of \(C\) in the recurrence relation. This is assumed in the implementation.
If the argument
x
is numerical, the functionOrthoPolyNumeric()
is called. Otherwise, the functionOrthoPolyCoeffs()
computes a list of coefficients, andEvaluateHornerScheme()
converts this list into a polynomial expression.
-
OrthoPolySum
(name, c, params, x)¶ internal function for computing series of orthogonal polynomials
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 familyname
with parametersparams
at the pointx
, wherec
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 functionOrthoPolySumNumeric()
is called. Otherwise, the functionOrthoPolySumCoeffs()
computes the list of coefficients of the resulting polynomial, andEvaluateHornerScheme()
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
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
Example: In> Tail({a,b,c}) Out> {b,c};
-
Length
(list)¶ -
Length
(string) 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
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> 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
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
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
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 listThe 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)
Param 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
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
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 functionsUnList()
andHold()
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
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
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
This command deletes the
n
-th element fromlist
. The first parameter should be a list, whilen
should be a positive integer less than or equal to the length oflist
. The entry with indexn
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
The expression
expr
is inserted just before then
-th entry inlist
. The first parameterlist
should be a list, whilen
should be a positive integer less than or equal to the length oflist
plus one. The expressionexpr
is placed between the entries inlist
with indicesn-1
andn
. There are two border line cases: ifn
is 1, the expressionexpr
is placed in front of the list (just as by the:()
operator); ifn
equals the length oflist
plus one, the expressionexpr
is placed at the end of the list (just as byAppend()
). 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
-
Replace
(list, n, expr)¶ replace an entry in a list
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
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
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
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
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
-
RemoveDuplicates
(list)¶ remove any duplicates from a list
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
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
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
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
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
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
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
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()
UnFlatten()
is the inverse operation ofFlatten()
. Given a list, it can be turned into an expression representing for instance the addition of these elements by callingUnFlatten()
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
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
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
The command
VarList()
returns a list of all variables that appear in the expressionexpr
. The expression is traversed recursively.The command
VarListSome()
looks only at arguments of functions in thelist
. 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}
, butVarListSome(a*Sin(b-c), {*})
will not look at arguments ofSin()
and will return{a,Sin(b-c)}
. HereSin(b-c)
is considered a variable because the functionSin()
does not belong tolist
.The command “func:VarListArith returns a list of all variables that appear arithmetically in the expression
expr
. This is implemented throughVarListSome()
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 useAtom("+")
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
The command
FuncList()
returns a list of all function atoms that appear in the expressionexpr
. 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
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
The command
FuncListSome()
does the same asFuncList()
, except it only looks at arguments of a givenlist
of functions. All other functions become opaque (as if they do not contain any other functions). For example,FuncList(a + Sin(b-c))
will see that the expression has a{-}
operation and return {{+,Sin,-}}, butFuncListSome(a + Sin(b-c), {+})
will not look at arguments ofSin()
and will return{+,Sin}
.Note that since the operators
+
and-
are prefix as well as infix operators, it is currently required to useAtom("+")
to obtain the unevaluated atom+
.Example: In> FuncListSome({a+b*2,c/d},{List}) Out> {List,+,/};
-
PrintList
(list[, padding])¶ print list with padding
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
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
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¶
Destructive commands run faster than their nondestructive counterparts because the latter copy the list before they alter it.
-
DestructiveAppend
(list, expr)¶ destructively append an entry to a list
This is the destructive counterpart of
Append()
. This command yields the same result as the corresponding call toAppend()
, 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.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};
-
DestructiveDelete
(list, n)¶ delete an element destructively from a list
This is the destructive counterpart of :func`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 tolist
, it will now be bound to the list with then
-th entry removed.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
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.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
-
DestructiveReplace
(list, n, expr)¶ replace an entry destructively in a list
Param list: list of which an entry should be replaced Param n: index of entry to replace Param expr: expression to replace the n
-th entry withThis 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.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
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.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
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
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
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
The association list
alist
is searched for an entry stored with indexkey
. If such an entry is found, it is returned. Otherwise the atomEmpty
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 asalist[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
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
(alist, key)¶ -
AssocDelete
(alist, {key, value}) delete an entry in an association list
The key {
key
} in the association listalist
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
This command returns
list
after it is sorted usingcompare
to compare elements. The functioncompare
should accept two arguments, which will be elements oflist
, and compare them. It should returnTrue
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
This command returns
list
after it is sorted usingcompare
to compare elements. The functioncompare
should accept two arguments, which will be elements oflist
, and compare them. It should returnTrue
if in the sorted list the second argument should come after the first one, andFalse
otherwise.The function
HeapSort()
uses theheapsort 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
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
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
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
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¶
The functions below operate on a global stack, currently implemented as a list
that is not accessible externally (it is protected through
LocalSymbols()
).
-
GlobalPop
()¶ -
GlobalPop
(var) restore variables using a global 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.See also
-
GlobalPush
(expr)¶ save variables using a global stack
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
Param g: graph Return adjacency list of graph
g
.See also
-
AdjacencyMatrix
(g)¶ adjacency matrix
Param 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¶ -
list
:
item -
list
:
list -
string
:
string prepend or append item to list, or concatenate lists or strings
Example: In> a:b:c:{} Out> {a,b,c}; In> "This":"Is":"A":"String" Out> "ThisIsAString";
See also
-
fn
@
arglist¶ apply a function
This function is a shorthand for
Apply()
. It applies the functionfn
to the argument(s) inarglist
and returns the result.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
This function is a shorthand for
MapSingle()
. It successively applies the functionfn
to all the entries inlist
and returns a list containing the results. The parameterfn
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
This command returns the list
{n, n+1, n+2, ..., m}
. Ifm
is smaller thann
, the empty list is returned.Note
The
..
operator should be surrounded by spaces to keep the parser happy. So one should write1 .. 4
instead of1..4
.
-
NFunction
(newname, funcname, arglist)¶ make wrapper for numeric functions
This function will define a function named
newname()
with the same arguments as an existing function namedfuncname()
. The new function will evaluate and return the expressionfuncname(arglist)
only when all items in the argument listarglist
are numbers, and return unevaluated otherwise. This can be useful e.g. 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 atomInfinity
for some arguments), then the new function will returnUndefined
.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()
which cannot be evaluated unless the argument 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 wrappert1()
aroundt()
which will not try to evaluatet()
unless the argument 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
Control flow functions¶
Evaluation control¶
-
MaxEvalDepth
(n)¶ set the 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 cases:In> 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
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
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.
Conditional execution¶
-
If
(pred, then[, else])¶ branch point
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;
Loops¶
-
While
(pred) expr¶ loop while a condition is met
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
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;
-
For
(init, pred, incr) expr¶ C-style
for
loopThis 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
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
-
Function
() func(args)¶ -
Function
(funcname, {args}) body declare or define a 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()
-
Macro
() func(args)¶ -
Macro
(funcname, {args}) body declare or define a macro
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
-
Apply
(fn, arglist)¶ apply a function to arguments
This function applies the function
fn
to the arguments inarglist
and returns the result. The first parameterfn
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}
, wherevarlist
is the list of formal parameters. Upon application, the formal parameters are assigned the values inarglist
(the second parameter ofApply()
) and thebody
is evaluated.Another way to define a pure function is with the Lambda construct. Here, instead of passing in
{varlist,body}
, one can pass inLambda(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 functionApply()
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
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
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)¶ -
WithValue
(varlist, vallist, expr) temporary assignment during an evaluation
First, the expression
val
is assigned to the variablevar
. Then, the expressionexpr
is evaluated and returned. Finally, the assignment is reversed so that the variablevar
has the same value as it had beforeWithValue()
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
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 asLn(a*b)
, and we want to change these intoLn(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 asSubst()
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
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”
Param e1}, {e2: 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
Param e1, e2: 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 mathematically 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)¶ -
Not
expr logical negation
Param expr: a boolean expression Not returns the logical negation of the argument expr. If
expr
isFalse
it returnsTrue
, and ifexpr
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
Param a}1, …, {a}: 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
Param a}1, …, {a}: 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
Param expr: expression to test Param 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 any 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
Param 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()}
Param 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
Param 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
Param 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
Param expression: mathematical expression Param 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) = \frac{f(x)+f(-x)}{2}\) and \(f_{odd}(x) = \frac{f(x)-f(-x)}{2}\)
-
IsFunction
(expr)¶ test for a composite object
This function tests whether
expr
is a composite object, i.e. not an atom. This includes not only obvious functions such asf(x)
, but also expressions such asx+5
and lists.Example: In> IsFunction(x+5); Out> True; In> IsFunction(x); Out> False;
-
IsAtom
(expr)¶ test for an atom
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
Param 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
Param 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
Param 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
Param {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
Param 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
Param 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
Param 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
Param 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
Param 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
Param 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
Param 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
Param 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
Param 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
Param 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
Param 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
Param 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
Param 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
Param x: variable to express the univariate polynomial in Param 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
Param expr: an expression Param x: a subexpression to be found Param 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
Param expr: an expression Param func: a function atom to be found Param 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
Param var: atom, variable which should be assigned Param expr: expression to assign to the variable or body of function Param i: index (can be integer or string) Param fn: atom, name of a new function to define Param arg1, arg2: 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 will 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
Param var: variable which should be assigned Param 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
Param 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
Param 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
Param 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
Param 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
Param pred: name of the predicate to apply Param expr: expression on which pred
should be appliedThis 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
Param var: variable (held argument) Param 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
Param var1, var2, ..: atoms, symbols to be made local Param 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> :math:`a6+ :math:`b6;
See also
Input/output¶
This chapter contains commands to use for input and output. 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
Param 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, \(2i\) is first evaluated to
Complex(0,2)
before the expression is printed.See also
LispRead()
,Listify()
,Unlist()
-
Echo
(item)¶ -
Echo
(items) high-level printing routine
Param item: the item to be printed Param items: 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
Param 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
Param 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
Param expr: an expression to be exported TeXForm()
returns a string containing LaTeX representation of the yacas expressionexpr
. Currently the exporter handles most expression types but not all.
-
CForm
(expr)¶ export expression to C code
Param expr: expression to be exported CForm()
returns a string containing C code that attempts to implement the yacas expressionexpr
. Currently the exporter handles most expression types but not all.
-
IsCFormable
(expr)¶ -
IsCFormable
(expr, funclist) check possibility to export expression to C code
Param expr: expression to be exported (this argument is not evaluated) Param funclist: list of “allowed” function atoms IsCFormable()
returnsTrue
if the yacas expressionexpr
can be exported into C code. This is a check whether the C exporterCForm()
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 ofIsCFormable()
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
False
because the functionfunc123()
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
Param expr: expression to be printed The expression
expr
is evaluated and written to the current output. Note thatWrite()
accepts an arbitrary number of arguments, all of which are written to the current output (see second example).Write()
always returnsTrue
.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 ofWrite()
.See also
-
WriteString
(string)¶ low-level printing routine for strings
Param 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 returnsTrue
.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()
andWriteString()
. Note thatWrite()
andWriteString()
do not write a newline, so theOut>
prompt immediately follows the output.
-
Space
()¶ -
Space
(n) print one or more spaces
Param n: the number of spaces to print Space()
prints one space on the current output. The second form printsn
spaces on the current output. The result is alwaysTrue
.Example: In> Space(5); Out> True;
-
NewLine
()¶ -
NewLine
(n) print one or more newline characters
Param n: the number of newline characters to print NewLine()
prints a newline character on the current output. The second form printsn
newlines on the current output. The result is alwaysTrue
.Example: In> NewLine(); Out> True;
-
FromFile
(name) body¶ connect current input to a file
Param name: name of the file to read Param body: expression to be evaluated The current input is connected to the file
name
. Then the expressionbody
is evaluated. If some functions inbody
try to read from current input, they will read from the filename
. Finally, the file is closed and the result of evaluatingbody
is returned.Example: Suppose that the file
foo
contains2 + 5;
:In> FromFile("foo") res := Read(); Out> 2+5; In> FromFile("foo") res := ReadToken(); Out> 2;
See also
-
FromString
(str) body¶ connect current input to a string
Param str: a string containing the text to parse Param body: expression to be evaluated The commands in
body
are executed, but every read is done from the stringstr
. The result of evaluatingbody
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
-
ToFile
(name) body¶ connect current output to a file
Param name: name of the file to write the result to Param body: expression to be evaluated The current output is connected to the file
name
. Then the expressionbody
is evaluated. Everything that the commands inbody
prints ends up in the filename
. Finally, the file is closed and the result of evaluatingbody
is returned. If the file is opened again, the old contents will be overwritten. This is a limitation ofToFile()
: 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 linesqrt(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
Param body: expression to be evaluated The commands in
body
are executed. Everything that is printed, byEcho()
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
-
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
-
ToStdout
() body¶ select initial output stream for output
Param body: expression to be evaluated When using
ToString()
orToFile()
, it might happen that something needs to be written to the (initial) standard output (typically the screen).ToStdout()
can be used to select this stream.
-
ReadCmdLineString
(prompt)¶ read an expression from command line and return in string
Param 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 read step), and then takes the derivative of it (the eval step) and then uses
PrettyForm()
to display the result (the print 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
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 atomEndOfFile
is returned. The yacas expressiona+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
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 applyingListify()
toLispRead()
. The functionLispReadListed()
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. The3
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
Param name: name of the file to load The file
name
is opened. All expressions in the file are read and evaluated.Load()
always returnsTrue
.See also
Use()
,DefLoad()
,DefaultDirectory()
,FindFile()
-
Use
(name)¶ load a file, but not twice
Param name: name of the file to load If the file
name
has been loaded before, either by an earlier call toUse()
or via theDefLoad()
mechanism, nothing happens. Otherwise all expressions in the file are read and evaluated.Use()
always returnsTrue
. 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
fileParam name: name of the file (without the .def
suffix)The suffix
.def
is appended toname
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 filename
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
Param 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 fileParam 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?>
. 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.
Example: In> WriteString("First line" : Nl() : "Second line" : Nl()); First line Second line Out> True;
See also
-
V
(expression)¶ set verbose output mode
Param expression: expression to be evaluated in verbose mode V()
will evaluate the expression in verbose mode. Various parts of yacas can show extra information about the work done while doing a calculation when usingV()
. In verbose mode,InVerboseMode()
will returnTrue
, 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 returnTrue
, otherwise it will returnFalse
.Example: In> InVerboseMode() Out> False In> V(InVerboseMode()) Out> True
See also
Echo()
,N()
,OldSolve()
,V()
-
XmlExplodeTag
(xmltext)¶ convert XML strings to tag objects
Param 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 not 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
Param 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
Param 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.
Param yacasForm: string with the name of a Yacas symbol, or a Yacas expression Param cd: OpenMath Content Dictionary for the symbol Param name: OpenMath name for the symbol Param yacasToOM: rule for translating an application of that symbol in Yacas into an OpenMath expression Param 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", { :math:`, _1, 2 }, :math:`(_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", \ { :math:`, _2, OMS("limit1", "under"), OMBIND(OMS("fns1", "lambda"), OMBVAR(_1), _4) }_(_3=Left) \ |{ :math:`, _2, OMS("limit1", "above"), OMBIND(OMS("fns1", "lambda"), OMBVAR(_1), _4) }_(_3=Right) \ |{ :math:`, _2, OMS("limit1", "both_sides"), OMBIND(OMS("fns1", "lambda"), OMBVAR(_1), _3) }, \ { :math:`, _{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
Plotting¶
-
Plot2D
(expr[, x0:x1][, option=value[, ...]])¶ -
Plot2D
({expr1, expr2, ..., exprn}[, x0:x1][, option=value[, ...]]) adaptive two-dimensional plotting
Param f(x): unevaluated expression containing one variables (function to be plotted) Param list: list of functions to plot Param a}, {b: numbers, plotting range in the \(x\) coordinate Param option: atom, option name Param 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()} returnsTrue
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 \(x0\), \(x1\) 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 thez 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
(expr[, option=value[, ...]])¶ -
Plot3DS
({expr1, expr2, ..., exprn}[, option=value[, ...]]) three-dimensional (surface) plotting
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()} returnsTrue
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
String manipulation¶
-
StringMid'Set
(index, substring, string)¶ change a substring
Param index: index of substring to get Param substring: substring to store Param 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
Param index: index of substring to get Param length: length of substring to get Param 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
Param “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
Param 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
Param strings: one or more strings Concatenates strings.
Example: In> ConcatStrings("a","b","c") Out> "abc";
See also
-
PatchString
(string)¶ execute commands between
<?
and?>
in stringsParam 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. SeePatchLoad()
for more details.Example: In> PatchString("Two plus three is <? Write(2+3); ?> "); Out> "Two plus three is 5 ";
See also
Random numbers¶
Simple interface¶
Advanced interface¶
-
RngCreate
()¶ -
RngCreate
(seed) -
RngCreate
([seed=seed][, engine=engine][, dist=dist]) -
RngCreate
({seed, engine, dist}) create a pseudo-random generator
RngCreate()
returns a list which is a well-formed RNG object. Its value should be saved in a variable and used to callRng()
andRngSeed()
.Engines:
default
advanced
Distributions:
default
(the same asflat
)flat
(uniform)gauss
(normal)
-
RngSeed
(r, seed)¶ (re)seed pseudo-random number generator
RngSeed()
re-initializes the RNG objectr
with the seed valueseed
. The seed value should be a positive integer.
See also
-
Rng
(r)¶ generate pseudo-random number
Rng(r)()
returns a floating-point random number between 0 and 1 and updates the RNG objectr
. (Currently, the Gaussian option makes a RNG return a complex random number instead of a real random number.)See also
Auxilliary functions¶
-
RandomIntegerMatrix
(rows, cols, from, to)¶ generate a matrix of random integers
This function generates a
rows x cols
matrix of random integers. All entries lie betweenfrom
andto
, 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
(n, from, to)¶ generate a vector of random integers
This function generates a list with
n
random integers. All entries lie betweenfrom
andto
, 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
RandomPoly()
generates a random polynomial in the variablevar
, of degreedeg
, with integer coefficients ranging fromcoefmin
tocoefmax
(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
Probability and Statistics¶
Probability¶
Distributions¶
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
Param 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
Param p: number, probability to observe an event in single trial Param 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
-
ChiSquareDistribution
(m)¶
-
DiscreteUniformDistribution
(a, b)¶
-
ExponentialDistribution
(l)¶
-
GeometricDistribution
(p)¶
-
NormalDistribution
(m, s2)¶
-
PoissonDistribution
(l)¶
-
tDistribution
(m)¶ Student’s \(t\) distribution
Param m: number of degrees of freedom
Functions¶
-
PDF
(dist, x)¶ probability density function
Param dist: a distribution type Param 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
Statistics¶
-
ChiSquareTest
(observed, expected, params)¶ Pearson’s ChiSquare test
Param observed: list of observed frequencies Param expected: list of expected frequencies Param params: number of estimated parameters Chi-squared test 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 as
params
. 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()
.
Special functions¶
-
Gamma
(x)¶ -
Note
Euler’s constant is represented by
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;
See also
()
,gamma
-
Zeta
(x)¶ -
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
()
-
Bernoulli
(n)¶ -
Bernoulli
(n, x)
-
Euler
(n)¶ -
Euler
(n, x) Euler numbers and Euler polynomials
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)¶ -
Example: In> LambertW(0) Out> 0; In> N(LambertW(-0.24/Sqrt(3*Pi))) Out> -0.0851224014;
See also
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
Param n: integer to test
-
IsComposite
(n)¶ test for a composite number
Param n: positive integer
-
IsCoprime
(m, n)¶ test if integers are coprime
Param m: positive integer Param n: positive integer Param list: list of positive integers
-
IsSquareFree
(n)¶ test for a square-free number
Param n: positive integer
-
IsPrimePower
(n)¶ test for a power of a prime number
Param n: integer to test
-
NextPrime
(i)¶ generate a prime following a number
Param i: integer value
-
IsTwinPrime
(n)¶ test for a twin prime
Param n: positive integer
-
IsIrregularPrime
(n)¶ test for an irregular prime
Param n: positive integer
-
IsCarmichaelNumber
(n)¶ test for a Carmichael number
Param n: positive integer
-
Factors
(x)¶ factorization
Param x: integer or univariate polynomial
-
IsAmicablePair
(m, n)¶ test for a pair of amicable numbers
Param m: positive integer Param n: positive integer
-
Factor
(x)¶ factorization, in pretty form
Param x: integer or univariate polynomial
-
Divisors
(n)¶ number of divisors
Param n: positive integer
-
DivisorsSum
(n)¶ the sum of divisors
Param n: positive integer
-
ProperDivisors
(n)¶ the number of proper divisors
Param n: positive integer
-
ProperDivisorsSum
(n)¶ the sum of proper divisors
Param n: positive integer
-
Moebius
(n)¶ the Moebius function
Param n: positive integer
-
CatalanNumber
(n)¶ return the
n
-th Catalan NumberParam n: positive integer
-
FermatNumber
(n)¶ return the
n
-th Fermat NumberParam n: positive integer
-
HarmonicNumber
(n)¶ return the
n
-th Harmonic NumberParam n: positive integer Param r: positive integer
-
StirlingNumber1
(n, m)¶ return the
n,m
-th Stirling Number of the first kindParam n: positive integers Param m: positive integers
-
StirlingNumber1
(n, m) return the
n,m
-th Stirling Number of the second kindParam n: positive integer Param m: positive integer
-
DivisorsList
(n)¶ the list of divisors
Param n: positive integer
-
SquareFreeDivisorsList
(n)¶ the list of square-free divisors
Param n: positive integer
-
MoebiusDivisorsList
(n)¶ the list of divisors and Moebius values
Param n: positive integer
-
SumForDivisors
(var, n, expr)¶ loop over divisors
Param var: atom, variable name Param n: positive integer Param expr: expression depending on var
-
RamanujanSum
(k, n)¶ compute the Ramanujan’s sum
Param k: positive integer Param 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
Param n: number or polynomial to expand Param p: base to expand in
-
IsQuadraticResidue
(m, n)¶ functions related to finite groups
Param m: integer Param n: odd positive integer
-
GaussianFactors
(z)¶ factorization in Gaussian integers
Param z: Gaussian integer
-
GaussianNorm
(z)¶ norm of a Gaussian integer
Param z: Gaussian integer
-
IsGaussianUnit
(z)¶ test for a Gaussian unit
Param z: a Gaussian integer
-
IsGaussianPrime
(z)¶ test for a Gaussian prime
Param z: a complex or real number
-
GaussianGcd
(z, w)¶ greatest common divisor in Gaussian integers
Param z: Gaussian integer Param w: Gaussian integer
Numerical methods¶
-
NIntegrate
(x, x0, x1) expr¶ numerical integration
Param x: integration variable Param x0: lower integration limit Param x1: upper integration limit Param expr: integrand Numerically integrate
expr
overx
fromx0
tox1
.See also
Physics¶
Assorted physics-related functions
Quantum Physics¶
-
ClebschGordan
({j1, m1}, {j2, m2}, {J, M})¶ Clebsch-Gordan coefficient
Calculates the Clebsch-Gordan coefficient \(\langle j_1m_1j_2m_2\vert JM\rangle\)
Debugging¶
-
TraceStack
(expression)¶ show calling stack after an error occurs
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.
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
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
Param template: template showing the operator to trace Param 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
Miscellaneous¶
-
Time
(expr)¶ measure the time taken by a function
Param expr: any expression The function
Time()
evaluates the expressionexpr
and prints the time in seconds needed for the evaluation. The time is printed to the current output stream. The built-in functionGetTime()
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
-
SystemCall
(str)¶ pass a command to the shell
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
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¶
Rules 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 precedence. 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 after 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 the variable it is bound to 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 any 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 results 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 caller 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:
- \(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()
. 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)ldots 1` 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 :func: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 :math: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> 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 :func`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)\ne 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. \(\mathrm{VEV}(4) = 4\),
\(\mathrm{VEV}(\delta(k-l)) = \delta(k-l)\); and
\(\mathrm{VEV}(X*A(k)) = 0\), \(\mathrm{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 canonical form. “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 the 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(k) ** 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(k)**X
and only after X
has
been ordered. It will not perform any extra simplifications but
instead delegate all work to :func: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\) times the 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,\ldots, 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_{ij}\) 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_{ij}\) 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 [DST88] and [vzGG99].
Definitions¶
The following definitions define multivariate polynomials, and the functions defined on them that are of interest for using such multivariates.
A term 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 coefficient, and \(x_1^{n_1}x_2^{n_2}\ldots x_m^{n_m}\) a monomial.
A multivariate polynomial is taken to be a sum over terms.
We write \(c_ax^a\) for a term, where \(a\) is a list of powers for the monomial, and \(c_a\) the coefficient of the term.
It is useful to define an ordering of monomials, to be able to determine the canonical form of a multivariate. For the currently implemented code the lexicographic order has been chosen:
- First, the ordering of variables is chosen, (\(x_1\), …, \(x_m\))
- For the exponents of a monomial, \(a = (a_1,\ldots, 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 lexicographic 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 multivariate polynomial
with a monomial order:
- \(c_ax^a\) is a term of the multivariate.
- the multidegree of \(f\) is \(\operatorname{mdeg}(f) := a_{max}\).
- the leading coefficient of \(f\) is \(\operatorname{lc}(f):=c_{\operatorname{mdeg}(f)}\), for the first term with non-zero coefficient.
- the leading monomial of \(f\) is \(\operatorname{lm}(f):=x^{\operatorname{mdeg}(f)}\).
- the leading term of \(f\) is \(\operatorname{lt}(f):=\operatorname{lc}(f)\operatorname{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 \((\operatorname{mdeg}(f),\operatorname{lc}(f)\). Note the similarity with the (key,value) pairs described in the sparse tree section. \(\operatorname{mdeg}(f)\) can be thought of as a key, and \(\operatorname{lc}(f)\) as a value.
The multicontent, \(\operatorname{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 :math:min(a)` the lowest exponents for each variable, occurring in \(f\) for which \(c\) is non-zero.
The multiprimitive part is then defined as \(\operatorname{pp}(f):=\frac{f}{\operatorname{multicont}(f)}\).
For a multivariate polynomial, the obvious addition and (distributive) multiplication rules hold
- \((a+b) + c = a+(b+c)\)
- \(a(b+c) = ab+ac\)
These are supported in the Yacas system through a multiply-add operation: \(\operatorname{muadd}(f,t,g) := f+tg\). 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 \(\operatorname{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,\ldots, a_n)\) for a univariate polynomial, and the equivalent for multivariates. This is called a dense 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 \(3xy^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^2y^03\). The second item states that
there are two terms, \(x^0y^11\) and \(x^0y^00 = 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 invar
, with the (key,value) pair (coefs,fact)MultiNomialAdd(multi1, multi2)
- add two multivars, and (possibly) destructively modifymulti1
to contain the result:[ multi1 := multi1 + multi2; multi1; ];
MultiNomialMultiplyAdd(multi1, multi2,exp,coef)
- add two multivars, and (possibly) destructively modifymulti1
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 modifymulti1
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 useScanMultiNomial()
.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, returnTrue
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 functionop
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)
- returnTrue
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¶
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\) and \(b\) separately
and adding the integrals.
- 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 \(pf(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)\) being 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()
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(ax+b)\),
and return \(\sin(ax+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(cy+d)\) over \(y\), then the
following variables are set:
a
= \(c\)b
= \(d\)x
= \(ay+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 \(ax^2+b\)¶
There are numerous expressions containing sub-expressions of the form \(ax^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 \(ax^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 \(\frac{c}{a*x^2+b}\) is
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
- \(\mathcal{L}\lbrace yx^n\rbrace = (-1)^n \frac{d^n}{dx^n} \mathcal{L}\lbrace y\rbrace\)
- \(\mathcal{L}\lbrace \frac{y}{x}\rbrace = \int_s^\infty\mathcal{L}\lbrace y\rbrace(\sigma)d\sigma\)
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 \(\frac{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(at)\)
and \(\cos(\frac{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 GCD 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,\dots,p_n\) is defined by the following equations (following [DST88]):
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()
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\) of a polynomial \(p\), the variation in the Sturm sequence \(V(S,y)\) is the number of sign changes in the sequence \(p_0,p_1,\ldots,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 \(-\infty\) and \(\infty\) can also be used. In these cases, \(V(S,\infty)\) is the number of sign changes between the leading coefficients of the elements of the Sturm sequence, and \(V(S,-\infty)\) 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,-\infty) -
V(S,\infty)\). The function NumRealRoots()
returns the number of
real roots of \(p\).
The function SturmVariations()
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
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)\ne0\) the minimum bound can be obtained also by considering the upper bound of \(p(\frac{1}{x})x^n\), and taking \(\frac{1}{bound}\).
We thus know that given
and
for all roots \(a\) of polynomial, either
or
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 \(\frac{p(x+c)}{x}\). When the original range was \((a,b)\), and \(c = \frac{a+b}{2}\) is a root, the new ranges should become \((a,c-M)\) and \((c+M,b)\).
In yacas, MimimumBound()
returns the lower bound described above,
and MaximumBound()
returns the upper bound on the roots in \(p\).
These bounds are returned as rational numbers. BoundRealRoots()
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 = \frac{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()
, 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(2a,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,a \bmod 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 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. [1] This algorithm is
deterministic (guaranteed correct within a certain running time) for
small numbers \(n<3.4\cdot10^{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
[Rab80]. We also implemented some
of the improvements suggested in [Dav92].
[1] | Initial implementation and documentation was supplied by Christian Obrecht. |
The idea of the Miller-Rabin algorithm is to improve on 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}:=1 \bmod n\). (This is really a simple statement; if \(n\) is prime, then \(n-1\) nonzero remainders modulo \(n\): \(1, 2, \ldots, n-1\) form a cyclic multiplicative group.) Therefore we pick some “base” integer \(x\) and compute \(x^{n-1} \bmod 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 every 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:=1 \bmod 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\dot49:=1 \bmod 97\).)
We can check that \(n\) is odd before applying any primality test. (A test \(n^2:=1 \bmod 24\) guarantees that \(n\) is not divisible by 2 or 3. For large \(n\) it is faster to first compute \(n \bmod 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^rq\) with \(q\) odd, then \(x^{n-1}:=a^{2^r}\bmod n\) where \(a:=x^q \bmod n\). (Here \(r\ge 1\) since \(n\) is odd.) In other words, the number \(x^{n-1}\bmod n\) is obtained by repeated squaring of the number \(a\). We get a sequence of \(r\) repeated squares: \(a, a^2,\ldots,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:
- Find the largest power of 2 in \(n-1\) and an odd number \(q\) such that \(n-1=2^rq\).
- Select the “base number” \(x<n\). Compute the sequence \(a:=x^q \bmod n\), \(a^2, a^4,\ldots, a^{2^r}\) by repeated squaring modulo \(n\). This sequence contains at least two elements since \(r\ge 1\).
- 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 must 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 strongly-probably-prime for base \(b\) if \(b^q:=1 \bmod n\) or \(b^{q2^i}:=n-1 \bmod n\) for some \(i\) such that \(0\le i < r\), where \(q\) and \(r\) are such that \(q\) is odd and \(n-1 = q2^r\).
A practical application of this procedure needs to select particular base numbers. It is advantageous (according to [PSW80] to choose prime numbers \(b\) as bases, because for a composite base \(b=pq\), 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 [Dav92] is activated if \(r>2\) (i.e. if \(n:=1\bmod 8\) which is true for only 1/4 of all odd numbers). If \(i\ge 1\) is found such that \(b^{q2^i}:=n-1\bmod n\), then \(b^{q2^{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 will find some roots \(s_1\), \(s_2\) of -1 such that \(s_1+s_2\ne 0 \bmod n\). But \(s_1^2-s_2^2:=0 \bmod n\). Therefore \(n\) is definitely composite, e.g. \(\gcd(s_1+s_2,n)>1\). This check costs very little computational effort but guards against some strong pseudoprimes.
Yet another small improvement comes from [DLP93]. 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 :math:n` is such that either \(3n+1\) or \(8n+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 \(3n+1\), nor \(8n+1\), nor any \(sn+1\) with small integer \(s\) can be a perfect square. [If \(sn+1=r^2\), then \(sn=(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 strong pseudoprimes 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 [2]
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\ge B\), the Rabin-Miller method consists of 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()
).
[2] | And surely exhausting. |
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 not 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=\frac{1}{\ln{n}}\) and to be composite
\(p_c=1-\frac{1}{\ln{n}}\). Prime numbers never fail the test.
Therefore, the probability for the test to pass is \(p_fp_c+p_p\)
and the probability to pass erroneously is
To make \(p<\epsilon\), it is enough to select \(k=\frac{1}{\ln{4}(\ln{n}-\ln{\epsilon})}\).
Before calling MillerRabin()
, the function IsPrime()
performs two
quick checks: first, for \(n\ge4\) 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\le257\). 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()
that returns the smallest
prime number larger than \(n\). This function uses a sequence
\(5,7,11,13,\ldots\) 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 \(n\bmod 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\le257\). 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\le257\) 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\ge1\). 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\le s<s_0=\frac{\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,\ldots,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^\frac{1}{2}=p^{12}\). Therefore it is enough to test whether
\(n^\frac{1}{s}\) is an integer for all prime 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\le s_0\), then \(n\) is not
a prime power.
Pollard’s rho algorithm¶
If the number \(n\) is not a prime power, the Pollard’s rho algorithm is applied [PD75]. The Pollard rho algorithm takes an irreducible polynomial, e.g. \(p(x)=x^2+1\) and builds a sequence of integers \(x_{k+1}:=p(x_k)\bmod n\), starting from \(x_0=2\). For each \(k\), the value \(x_{2k}-x_k\) is attempted as possibly containing a common factor with \(n\). The GCD of \(x_{2k}-x_k\) with \(n\) is computed, and if \(\gcd(x_{2k}-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\leq t_k<n\). It is clear that, on the average, a fraction \(\frac{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_{2k}\) 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_{2k}\) 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:
- 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).
- 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).
- 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:=m\bmod n\).
The Legendre symbol \((\frac{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 \(\frac{m}{n}\) is an integer.
The Jacobi symbol \((\frac{m}{n})\) is defined as the product of the Legendre symbols of the prime factors \(f_i\) of \(n=f_1^{p_1}\ldots f_s^{p_s}\)
(Here we used the same notation \((\frac{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 identities for the Jacobi symbol:
- \((\frac{a}{1}) = 1\),
- \((\frac{2}{b}) = (-1)^{\frac{b^2-1}{8}}\),
- \((\frac{ab}{c}) = (\frac{a}{c})(\frac{b}{c})\),
- If \(a:=b\bmod c\), then \((\frac{a}{c})=(\frac{b}{c})\),
- If \(a\), \(b\) are both odd, then \((\frac{a}{b})=(\frac{b}{a}) (-1)^\frac{(a-1)(b-1)}{4}\).
Using these identities, we can recursively reduce the computation of the Jacobi symbol \((\frac{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 \((\frac{a}{b})\) is computed by the following algorithm. (The number \(b\) must be an odd positive integer, otherwise the result is undefined.)
- If \(b=1\), return \(1\) and stop. If \(a=0\), return \(0\) and stop. Otherwise, replace \((\frac{a}{b})\) by \((\frac{a\bmod b}{b})\) (identity 4).
- Find the largest power of \(2\) that divides \(a\). Say, \(a=2^{sc}\) where \(c\) is odd. Replace \((\frac{a}{b})\) by \((\frac{c}{b})(-1)^\frac{s(b^2-1)}{8}\) (identities 2 and 3).
- Now that \(c<b\), replace \((\frac{c}{b})\) by \((\frac{b}{c})(-1)^\frac{(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)^\frac{(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)^\frac{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:
where the functions \(A\) and \(S\) are defined as follows:
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
The first term of the series gives, at large \(n\), the Hardy-Ramanujan asymptotic estimate,
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 \(|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+\frac{\sqrt{n}}{2}\). One can show that asymptotically for large \(n\), the required number of terms is less than \(\frac{\mu}{\ln{\mu}}\), where \(\mu:=\pi\sqrt{\frac{2n}{3}}\).
[Ahlgren <i>et al.</i> 2001] mention that there exist explicit constants \(B_1\) and \(B_2\) such that
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.
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\left(\left(\frac{n}{\ln(n)}\right)^\frac{3}{2}\right)\) operations, of which \(O(\frac{n}{\ln(n)})\) are long multiplications at precision \(Prec\sim O(\sqrt{n})\) digits. The computational cost is therefore \(O(\frac{n}{\ln(n)}M(\sqrt{n}))\).
partitions of an integer by recurrence relation¶
Small \(n\)
The second, simpler algorithm involves a recurrence relation
The sum can be written out as
where \(1, 2, 5, 7, \ldots\) is the generalized pentagonal sequence
generated by the pairs \(\frac{k(3k-1)}{2}\), \(\frac{k(3k+1)}{2}\)
for \(k=1,2,\ldots\). 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\le k\le n\). No long multiplications are necessary, but the number of long additions of numbers with \(Prec\sim O(\sqrt{n})\) digits is \(O(n^\frac{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 functions¶
divisors¶
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}\ldots p_r^{k_r}\) be the prime factorization of \(n\), where \(r\) is the number of prime factors and \(k_i\) is the multiplicity of the \(i\)-th factor. Then
proper divisors¶
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:
Another number-theoretic function is Moebius()
, defined as follows:
\(\mathrm{Moebius}(n)=(-1)^r\) if no factors of \(n\) are repeated,
\(\mathrm{Moebius}(n)=0\) if some factors are repeated, and
\(\mathrm{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 \(\mathrm{Moebius}(n) = -1\). The predicate IsSquareFree()
then
reducess to \(\mathrm{Moebius}(n)\ne0\), 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. [3] The ring of Gaussian integers is usually denoted by \(\mathbb{Z}[\imath]\) in the mathematical literature. It is an example of a ring of algebraic integers.
[3] | To distinguish ordinary integers from Gaussian integers, the ordinary integers (with no imaginary part) are called rational 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
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\),
\(\imath\), \(-\imath\). 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 \(\frac{z}{w}\) is a unit. For example, \(2+\imath\) and \(-1+2\imath\) 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+\imath\) and its associates.
- The rational (ordinary) primes of the form \(4n+3\).
- The factors \(a+b\imath\) of rational primes \(p\) of the form \(p=4n+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+\imath)(2-\imath)\). Here \(2+\imath\) 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 :func: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 \(4n+1\). We compute \(a := (2n)!\bmod p\).
By Wilson’s theorem \(a^2\) is congruent to \(-1\) (mod \(p\)),
and it follows that \(p\) divides \((a+\imath)(a-\imath)=a^2+1\) in
the Gaussian integers. The desired factor is then the GaussianGcd()
of
\(a+\imath\) and \(p\). If the result is \(a+b\imath\), 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\).
A simple factorization algorithm for univariate polynomials¶
This section discusses factoring polynomials using arithmetic modulo prime numbers. Information was used from [Knu97] and [DST88].
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
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,\ldots,p-1\) then it is the modulo, \(r = x \bmod p\).
When \(x\bmod p = y\bmod p\), the notation \(x=y\pmod 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 \(a=b\pmod p\) and \(x=y\pmod p\), then \(ax=by\pmod p\), \(a+x=b+y\pmod p\), and \(a-x=b-y\pmod p\). This means that for instance also \(x^n\bmod p = (x\bmod p)^n\bmod p\).
- Two numbers \(x\) and \(y\) are relatively prime if they don’t share a common factor, that is, if their greatest common denominator is one, \(\gcd(x,y)=1\).
- If \(ax=by\pmod p\) and if \(a=b\pmod p\), and if \(a\) and \(p\) are relatively prime, then \(x=y\pmod p\). This is useful for dividing out common factors.
- \(a=b\pmod p\) if and only if \(an=bn\pmod np\) when \(n\ne0\). Also, if \(r\) and \(s\) are relatively prime, then \(a=b\pmod rs\) only if \(a=b\pmod r\) and \(a=b\pmod s\). These rules are useful when the modulus is changed.
For polynomials \(v_1(x)\) and \(v_2(x)\) it further holds that
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 Fermat’s little theorem: if \(p\) is a prime number then \(a^p=a\pmod p\) for all integers \(a\) (for a proof, see Knuth).
An interesting corollary to this is that, for some prime integer \(p\):
This follows from writing it out and using Fermat’s little 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
into a form
Where \(f_i(x)\) are irreducible polynomials of the form:
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 \(\frac{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=\frac{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 \(\frac{1}{a_n^{n-1}}\), and replacing \(y\) with \(a_nx\). The irreducible solutions \(a_nx+c_i\) can be replaced by \(x+\frac{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:
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 \(\mathrm{deg}(f(x))\) to be the degree of \(f(x)\) and \(\mathrm{lc}(f(x))\) to be the leading coefficient of \(f(x)\). Then, if \(\mathrm{deg}(p(x))\ge \mathrm{deg}(d(x))\), one can compute an integer \(s\) such that
If \(p\) is prime, then
Because \(a^{p-1} = 1\pmod p\) for any \(a\). If \(p\) is not prime but \(d(x)\) is monic (and thus \(\mathrm{lc}(d(x)) = 1\),
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:
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 \(\mathrm{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 \(\mathrm{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 \(\mathrm{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:
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
Such a polynomial is said to be square-free. 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:
It can be seen by simply writing this out that \(p(x)\) and \(\frac{d}{dx}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 \(\mathrm{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()
, 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-2a)(x-3a)\ldots(x-na)\) 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
modulo some \(q=2^n\) an expression for the remainder after division is
For the initial solutions modulo 2, where the possible solutions are \(x\) and \(x-1\). For \(p=0\), \(\mathrm{rem}(0) = a_0\). For \(p=1\), \(\mathrm{rem}(1) = \sum_{i=0}^na_i\).
Given a solution \(x-p\) modulo \(q=2^n\), we consider the possible solutions \(x-p\bmod 2^{n+1}\) and \(x-(p+2^n)\bmod 2^{n+1}\).
\(x-p\) is a possible solution if \(\mathrm{rem}(p)\bmod 2^{n+1} = 0\).
\(x-(p+q)\) is a possible solution if \(\mathrm{rem}(p+q)\bmod 2^{n+1} = 0\). Expanding \(\mathrm{rem}(p+q)\bmod 2q\) yields:
When expanding this expression, some terms grouped under \(\mathrm{extra}(p,q)\) have factors like \(2q\) or \(q^2\). Since \(q=2^n\), these terms vanish if the calculation is done modulo \(2^{n+1}\).
The expression for \(\mathrm{extra}(p,q)\) then becomes
An efficient approach to determining if \(x-p\) or \(x-(p+q)\) divides \(p(x)\) modulo \(2^{n+1}\) is then to first calculate \(\mathrm{rem}(p)\bmod 2q\). If this is zero, \(x-p\) divides \(p(x)\). In addition, if \(\mathrm{rem}(p)+\mathrm{extra}(p,q)\bmod 2q\) 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} = 2q = 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+b\imath\) 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+\imath\) and \(x+\imath\) also. The step where new potential solutions modulo \(2^{n+1}\) are determined should then also test for \(x+2^n\imath\) and \(x+2^n+2^n\imath\).
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 could 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:
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,
This yields:
And thus a next, better, approximation for the root is
or more general:
If the root has multiplicity one, a Newton iteration can converge quadratically, 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
after 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
for \(i = 1,\ldots N\). If de denote by \(X\) the vector
and by \(dX\) the delta vector, then one can write
Setting \(f_i(X+dX)\) to zero, one obtains
sum_{j=1}^Na_{ij}dx_j = b_i
where
and
So the generalization is to first initialize \(X\) to a good initial value, calculate the matrix elements \(a_{ij}\) and the vector \(b_i\), and then to proceed to calculate \(dX\) by solving the matrix equation, and calculating
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:
and
It is clear that the solution to this is \(a=2\) and \(x:=N\frac{\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_{ij}\):
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 \(\frac{N\pi}{2}\) is a solution. Trying various initial values will find them.
Newton iteration on polynomials¶
von zur Gathen et al., [vzGG99] 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) = \frac{1}{g(x)}\), modulo some power in \(x\). This implies that we want to find a polynomial \(g\) for which:
Applying a Newton iteration step \(g_{i+1} = g_i - \frac{h(g_i)}{\frac{d}{dg}h(g_i)}\) to this expression yields:
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
Example:
suppose we want to find the polynomial \(g(x)\) up to the 7-th degree for which \(f(x)g(x) = 1\pmod{x^8}\), for the function
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:
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;
References¶
[DLP93] | Ivan Damgård, Peter Landrock, and Carl Pomerance. Average case error estimates for the strong probable prime test. Mathematics of Computation, 61(203):177–194, 1993. URL: http://www.jstor.org/stable/2152945, doi:10.2307/2152945. |
[Dav92] | J. H. Davenport. Primality testing revisited. In P. S. Wang, editor, Proceedings of International Symposium on Symbolic and Algebraic Computation (ISSAC’92), 123–129. ACM Press, 1992. |
[DST88] | J. H. Davenport, Y. Siret, and E. Tournier. Computer Algebra, Systems and Algorithms for Algebraic Computation. Academic Press, New York, NY, USA, 1988. ISBN 0-122-04230-1. |
[Knu97] | Donald E. Knuth. The Art of Computer Programming, Volume 2 (3rd Ed.): Seminumerical Algorithms. Addison-Wesley Longman Publishing Co., Inc., Boston, MA, USA, 1997. ISBN 0-201-89684-2. |
[PD75] | H. Pollard and H. G. Diamond. The Theory of Algebraic Numbers. Wiley, New York, 1975. |
[PSW80] | Carl Pomerance, J. L. Selfridge, and Samuel S. Wagstaff, Jr. The pseudoprimes to $25\cdot 10^9$. Mathematics of Computation, 35(151):1003–1026, 1980. doi:10.2307/2006210. |
[Rab80] | Michael O Rabin. Probabilistic algorithm for testing primality. Journal of Number Theory, 12(1):128 – 138, 1980. URL: http://www.sciencedirect.com/science/article/pii/0022314X80900840, doi:http://dx.doi.org/10.1016/0022-314X(80)90084-0. |
[vzGG99] | Joachim von zur Gathen and Jürgen Gerhard. Modern Computer Algebra. Cambridge University Press, New York, NY, USA, 1999. ISBN 0-521-64176-4. |
Credits¶
Last-known email addresses mangled in an obvious way.
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.
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()
has one argument and so we say thatCos()
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 higher precedence value) than the operator*
and so the expressiona+b*c
is parsed asa+(b*c)
, as one would expect.Infix operators can have different left-side and right-side precedence – 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 expression1+x
which is different from all other copies of1+x
because it is tagged byy
.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 GNU Lesser General Public License for more information.