In mathematics, a fixed point of a function is an element that gets mapped to itself by that function. For example, the function
\[ f : \mathbb{R} \rightarrow \mathbb{R} \] \[ f(x) = x^2 \]
maps the elements 0 and 1 to themselves, since \(f(0) = 0^2 = 0\) and \(f(1) = 1^2 = 1\).
To illustrate the concept, we could define a function fixed_points
which maps functions to the set of their fixed points. We start, however, by defining a function approx_eq
, which takes two vectors as input, does a pairwise check of equality within a given tolerance, and returns a boolean vector.
library(tidyverse)
library(gganimate)
approx_eq <- function(x, y, tol = 1e-2) {
map2_lgl(x, y, ~isTRUE(all.equal(.x, .y, tolerance = tol)))
}
This roundabout solution is necessary in order to deal with the curious non-type-stable nature of all.equal
which returns either TRUE
or a character string explaining why the two elements are not equal!
With that in place, we now define fixed_points
, which takes as input a function f
and a domain x
over which to evaluate f
. It then returns all unique elements x
that satisfy approx_eq(x, f(x))
.
fixed_points <- function(f, x, ..., tol = 1e-2) {
f_x <- f(x, ...)
equal <- approx_eq(x, f_x, tol = tol)
unique(x[equal])
}
f <- function(x) x^3
(fp <- fixed_points(f, -10:10))
## [1] -1 0 1
fp == f(fp)
## [1] TRUE TRUE TRUE
So we see that the function \(f(x) = x^3\) has the fixed points \({-1, 0, 1}\) over the interval \(-10 \leq x \leq 10\).
For convenience, and to see what’s really going on with the fixed points for various functions, we can define a function that plots a function and its fixed points (ggplot2
provides the convenient function stat_function
for plotting arbitrary functions).
plot_fixed_points <- function(f, domain, ...) {
fp <- fixed_points(f, domain, ...)
ggplot(data.frame(x = domain), aes(x)) +
geom_hline(yintercept = 0, size = 0.1) +
geom_vline(xintercept = 0, size = 0.1) +
stat_function(fun = f, color = "blue") +
stat_function(fun = function(x) x, color = "red") +
annotate("point", x = fp, y = fp) +
annotate("text", x = fp, y = fp,
label = sprintf("(%0.1f,%0.1f)", fp, fp),
size = 3, hjust = -0.1, vjust = 2,
check_overlap = TRUE) +
coord_equal(ylim = range(domain)) +
theme_minimal()
}
Let’s try it out on some common functions:
domain <- -10:10
plot_fixed_points(function(x) x^2, domain)
plot_fixed_points(function(x) x^3, domain)
id <- function(x) x
plot_fixed_points(id, domain)
const_3 <- function(x) 3
plot_fixed_points(const_3, domain)
plot_fixed_points(abs, domain)
plot_fixed_points(sign, domain)
g <- function(x) x^4 + 3 * x^3 + x^2
domain2 <- seq(-4, 2, length.out = 1000)
plot_fixed_points(g, domain2)
plot_fixed_points(sin, domain)
plot_fixed_points(cos, domain2)
plot_fixed_points(function(x) x * (1 + sin(x)),
seq(0, 20, 0.01))
The key point to notice is that the fixed points are precisely those points where the graph of the function intersects the graph of the identity function (i.e. the 45° line).
Nothing stops us from applying fixed_points
to non-numeric arguments. For example, we can confirm that the fixed points of toupper
evaluated on all upper- and lower-case letters are exactly all the upper-case letters.
fixed_points(toupper, c(letters, LETTERS))
## [1] "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q"
## [18] "R" "S" "T" "U" "V" "W" "X" "Y" "Z"
Attractive fixed points
A related concept is that of attractive fixed points. As discussed in the Wikipedia article, if we punch in any number into a calculator and then repeatedly evaluate the cosine of that number, we will eventually get approximately 0.739085133.
afp <- cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(-1)))))))))))))
afp
## [1] 0.7375069
We can illustrate this process with a nice animated graph.
xs <- accumulate(1:10, ~cos(.x), .init = -1) %>%
list(., .) %>%
transpose() %>%
flatten() %>%
flatten_dbl()
df <- data_frame(
x = head(xs, -1),
y = c(0, tail(xs, -2)),
frame = seq_along(x)
)
p <- plot_fixed_points(cos, domain) +
coord_equal(ylim = c(-1, 1), xlim = c(-1, 1)) +
geom_path(data = df,
aes(x, y, frame = frame, cumulative = TRUE),
color = "orange")