56  Scalar functions

This section uses these add-on packages:

using CalculusWithJulia
using Plots
gr()
using ForwardDiff
using SymPy
using Roots
using QuadGK
using JSON

Also, these methods from the Contour package:

import Contour: contours, levels, level, lines, coordinates

Consider a function \(f: R^n \rightarrow R\). It has multiple arguments for its input (an \(x_1, x_2, \dots, x_n\)) and only one, scalar, value for an output. Some simple examples might be:

\[\begin{align*} f(x,y) &= x^2 + y^2\\ g(x,y) &= x \cdot y\\ h(x,y) &= \sin(x) \cdot \sin(y) \end{align*}\]

For two examples from real life consider the elevation Point Query Service (of the USGS) returns the elevation in international feet or meters for a specific latitude/longitude within the United States. The longitude can be associated to an \(x\) coordinate, the latitude to a \(y\) coordinate, and the elevation a \(z\) coordinate, and as long as the region is small enough, the \(x\)-\(y\) coordinates can be thought to lie on a plane. (A flat earth assumption.)

Similarly, a weather map, say of the United States, may show the maximum predicted temperature for a given day. This describes a function that take a position (\(x\), \(y\)) and returns a predicted temperature (\(z\)).

Mathematically, we may describe the values \((x,y)\) in terms of a point, \(P=(x,y)\) or a vector \(\vec{v} = \langle x, y \rangle\) using the identification of a point with a vector. As convenient, we may write any of \(f(x,y)\), \(f(P)\), or \(f(\vec{v})\) to describe the evaluation of \(f\) at the value \(x\) and \(y\)


Returning to the task at hand, in Julia, defining a scalar function is straightforward, the syntax following mathematical notation:

f(x,y) = x^2 + y^2
g(x,y) = x * y
h(x,y) = sin(x) * sin(y)
h (generic function with 1 method)

To call a scalar function for specific values of \(x\) and \(y\) is also similar to the mathematical case:

f(1,2), g(2, 3), h(3,4)
(5, 6, -0.10679997423758245)

It may be advantageous to have the values as a vector or a point, as in v=[x,y]. Splatting can be used to turn a vector or tuple into two arguments:

v = [1,2]
f(v...)
5

Alternatively, the function may be defined using a vector argument:

f(v) = v[1]^2 + v[2]^2
f (generic function with 2 methods)

A style required for other packages within the Julia ecosystem, as there are many advantages to passing containers of values: they can have arbitrary length, they can be modified inside a function, the functions can be more generic, etc.

More verbosely, but avoiding index notation, we can use multiline functions:

function g(v)
    x, y = v
    x * y
end
g (generic function with 2 methods)

Then we have

f(v), g([2,3])
(5, 6)

More elegantly, perhaps – and the approach we will use in this section – is to mirror the mathematical notation through multiple dispatch. If we define j for multiple variables, say with:

j(x,y) = x^2 - 2x*y^2
j (generic function with 1 method)

Then we can define an alternative method with just a single variable and use splatting to turn it into multiple variables:

j(v) = j(v...)
j (generic function with 2 methods)

The we can call j with a vector or point:

j([1,2])
-7

or by passing in the individual components:

j(1,2)
-7

Following a calculus perspective, we take up the question of how to visualize scalar functions within Julia? Further, how to describe the change in the function between nearby values?

56.1 Visualizing scalar functions

Suppose for the moment that \(f:R^2 \rightarrow R\). The equation \(z = f(x,y)\) may be visualized by the set of points in \(3\)-dimensions \(\{(x,y,z): z = f(x,y)\}\). This will render as a surface, and that surface will pass a “vertical line test”, in that each \((x,y)\) value corresponds to at most one \(z\) value. We will see alternatives for describing surfaces beyond through a function of the form \(z=f(x,y)\). These are similar to how a curve in the \(x\)-\(y\) plane can be described by a function of the form \(y=f(x)\) but also through an equation of the form \(F(x,y) = c\) or through a parametric description, such as is used for planar curves. For now though we focus on the case where \(z=f(x,y)\).

In Julia, plotting such a surface requires a generalization to plotting a univariate function where, typically, a grid of evenly spaced values is given between some \(a\) and \(b\), the corresponding \(y\) or \(f(x)\) values are found, and then the points are connected in a dot-to-dot manner.

Here, a two-dimensional grid of \(x\)-\(y\) values needs specifying, and the corresponding \(z\) values found. As the grid will be assumed to be regular only the \(x\) and \(y\) values need specifying, the set of pairs can be computed. The \(z\) values, it will be seen, are easily computed. This cloud of points is plotted and each cell in the \(x\)-\(y\) plane is plotted with a surface giving the \(x\)-\(y\)-\(z\), \(3\)-dimensional, view. One way to plot such a surface is to tessalate the cell and then for each triangle, represent a plane made up of the \(3\) boundary points.

Here is an example:

𝒇(x, y) = x^2 + y^2
𝒇 (generic function with 1 method)
xs = range(-2, 2, length=100)
ys = range(-2, 2, length=100)

surface(xs, ys, 𝒇)

The surface function will generate the surface.

Note

Using surface as a function name is equivalent to plot(xs, ys, f, seriestype=:surface).

We can also use surface(xs, ys, zs) where zs is not a vector, but rather a matrix of values corresponding to a grid described by the xs and ys. A matrix is a rectangular collection of values indexed by row and column through indices i and j. Here the values in zs should satisfy: the \(i\)th row and \(j\)th column entry should be \(z_{ij} = f(x_i, y_j)\) where \(x_i\) is the \(i\)th entry from the xs and \(y_j\) the \(j\)th entry from the ys.

We can generate this using a comprehension:

zs = [𝒇(x,y) for y in ys, x in xs]
surface(xs, ys, zs)

If remembering that the \(y\) values go first, and then the \(x\) values in the above is too hard, then an alternative can be used. Broadcasting f.(xs,ys) may not make sense, were the xs and ys not of commensurate lengths, and when it does, this call pairs off xs and ys values and passes them to f. What is desired here is different, where for each xs value there are pairs for each of the ys values. The syntax xs' can be viewed as creating a row vector, where xs is a column vector. Broadcasting will create a matrix of values in this case. So the following is identical to the above:

surface(xs, ys, 𝒇.(xs', ys))