# Numerical evaluation of expressions

Cadabra is primarily a symbolic computer algebra system, in the sense that it focuses on symbolic expressions, not the numerical value they take when all symbols in them are replaced with values. However, Cadabra does have functionality to evaluate expressions numerically as well, using either a call through SymPy, or using its own internal expression evaluator. We will here focus on the latter, as it is by far the fastest. Let us start with a simple example to understand the basics. The following code creates a Cadabra expression containing just $\cos(x)$, and then numerically evaluates that expression for 100 values of $x$ in the range $[0, 2\pi]$.import numpy as np
import matplotlib.pyplot as plt

ex := 2\cos(2 x);
xv = np.linspace(0, np.pi*2, 100)
exv = nevaluate(ex, {$x$: xv} );

\(\displaystyle{}2\cos\left(2x\right)\)

2\cos(2x)

<cadabra2.NTensor object at 0xffff79053a30>

The

`nevaluate`

function returns an `NTensor`

, which is Cadabra's
object to store numerical values of tensors. It can be converted to a numpy
array by wrapping it in `np.array`, after which you can plot it:plt.plot( xv, np.array(exv) );

To understand

`nevaluate`

, take a close look at the arguments. The first argument
is the expression we want to evaluate. The second argument is a dictionary, in wich
we list the symbols appearing in the expression (here just $x$) and the values that
each such symbol takes (here it's the values in the array `xv`

).## More complicated examples

The example above evaluated a function of a single variable over a range of values of that variable. We can also use this to evaluate functions of multiple variables. The example below shows this.ex:= \cos(x) \sin(y);
xv = np.linspace(0, np.pi, 100)
yv = np.linspace(0, np.pi, 100)
z = np.array( nevaluate(ex, {$x$: xv, $y$: yv} ) )

\(\displaystyle{}\cos{x} \sin{y}\)

\cos(x) \sin(y)

The

`z`

variable is now a two-dimensional array, the first axis of which is the
$x$-axis and the second the $y$-axis (more on this order below). We can plot such a
data set by creating a meshgrid from the $x$ and $y$ values, and then feeding the lot
into `plot_surface`

:XV, YV=np.meshgrid(xv, yv)
plt.figure(figsize=(20, 10))
ax = plt.axes(projection='3d')
plt.figure().subplots_adjust(top=1, bottom=0, left=0, right=1, wspace=0)
ax.plot_surface(XV, YV, z, rstride=1, cstride=1, cmap='terrain', edgecolor=None);

The order in which the axis of the result of

`nevaluate`

should be interpreted
is determined by the order in which you list them in the values dictionary.
Compare the following:z1 = np.array( nevaluate(ex, {$x$: xv, $y$: yv} ))
z2 = np.array( nevaluate(ex, {$y$: yv, $x$: xv} ))

z1[10,20];
z2[10,20];

0.5633046988744114

0.2512712531759243

## Supported elementary functions

At present the`nevaluate`

function supports expressions with the following building blocks:
multiplication/division, addition/subtraction, trigonometric functions, hyperbolic trigonometric functions,
logarithms, exponential, square root.