mpoly is a simple collection of tools to help deal
with multivariate polynomials symbolically and functionally in
R. Polynomials are defined with the mp()
function:
library("mpoly")
# Registered S3 methods overwritten by 'ggplot2':
# method from
# [.quosures rlang
# c.quosures rlang
# print.quosures rlang
mp("x + y")
# x + y
mp("(x + 4 y)^2 (x - .25)")
# x^3 - 0.25 x^2 + 8 x^2 y - 2 x y + 16 x y^2 - 4 y^2
Term orders are available with the reorder function:
<- mp("(x + y)^2 (1 + x)"))
(p # x^3 + x^2 + 2 x^2 y + 2 x y + x y^2 + y^2
reorder(p, varorder = c("y","x"), order = "lex")
# y^2 x + y^2 + 2 y x^2 + 2 y x + x^3 + x^2
reorder(p, varorder = c("x","y"), order = "glex")
# x^3 + 2 x^2 y + x y^2 + x^2 + 2 x y + y^2
Vectors of polynomials (mpolyList
’s) can be specified in
the same way:
mp(c("(x+y)^2", "z"))
# x^2 + 2 x y + y^2
# z
You can extract pieces of polynoimals using the standard
[
operator, which works on its terms:
1]
p[# x^3
1:3]
p[# x^3 + x^2 + 2 x^2 y
-1]
p[# x^2 + 2 x^2 y + 2 x y + x y^2 + y^2
There are also many other functions that can be used to piece apart polynomials, for example the leading term (default lex order):
LT(p)
# x^3
LC(p)
# [1] 1
LM(p)
# x^3
You can also extract information about exponents:
exponents(p)
# [[1]]
# x y
# 3 0
#
# [[2]]
# x y
# 2 0
#
# [[3]]
# x y
# 2 1
#
# [[4]]
# x y
# 1 1
#
# [[5]]
# x y
# 1 2
#
# [[6]]
# x y
# 0 2
multideg(p)
# x y
# 3 0
totaldeg(p)
# [1] 3
monomials(p)
# x^3
# x^2
# 2 x^2 y
# 2 x y
# x y^2
# y^2
Arithmetic is defined for both polynomials (+
,
-
, *
and ^
)…
<- mp("x + y")
p1
<- mp("x - y")
p2
+ p2
p1 # 2 x
- p2
p1 # 2 y
* p2
p1 # x^2 - y^2
^2
p1# x^2 + 2 x y + y^2
… and vectors of polynomials:
<- mp(c("x", "y")))
(ps1 # x
# y
<- mp(c("2 x", "y + z")))
(ps2 # 2 x
# y + z
+ ps2
ps1 # 3 x
# 2 y + z
- ps2
ps1 # -1 x
# -1 z
* ps2
ps1 # 2 x^2
# y^2 + y z
You can compute derivatives easily:
<- mp("x + x y + x y^2")
p
deriv(p, "y")
# x + 2 x y
gradient(p)
# y^2 + y + 1
# 2 y x + x
You can turn polynomials and vectors of polynomials into functions
you can evaluate with as.function()
. Here’s a basic example
using a single multivariate polynomial:
<- as.function(mp("x + 2 y")) # makes a function with a vector argument
f # f(.) with . = (x, y)
f(c(1,1))
# [1] 3
<- as.function(mp("x + 2 y"), vector = FALSE) # makes a function with all arguments
f # f(x, y)
f(1, 1)
# [1] 3
Here’s a basic example with a vector of multivariate polynomials:
<- mp(c("x", "2 y")))
(p # x
# 2 y
<- as.function(p)
f # f(.) with . = (x, y)
f(c(1,1))
# [1] 1 2
<- as.function(p, vector = FALSE)
f # f(x, y)
f(1, 1)
# [1] 1 2
Whether you’re working with a single multivariate polynomial or a
vector of them (mpolyList
), if it/they are actually
univariate polynomial(s), the resulting function is vectorized. Here’s
an example with a single univariate polynomial.
<- as.function(mp("x^2"))
f # f(.) with . = x
f(1:3)
# [1] 1 4 9
<- matrix(1:4, 2))
(mat # [,1] [,2]
# [1,] 1 3
# [2,] 2 4
f(mat) # it's vectorized properly over arrays
# [,1] [,2]
# [1,] 1 9
# [2,] 4 16
Here’s an example with a vector of univariate polynomials:
<- mp(c("t", "t^2")))
(p # t
# t^2
<- as.function(p)
f f(1)
# [1] 1 1
f(1:3)
# [,1] [,2]
# [1,] 1 1
# [2,] 2 4
# [3,] 3 9
You can use this to visualize a univariate polynomials like this:
library("tidyverse"); theme_set(theme_classic())
<- as.function(mp("(x-2) x (x+2)"))
f # f(.) with . = x
<- seq(-2.5, 2.5, .1)
x
qplot(x, f(x), geom = "line")
For multivariate polynomials, it’s a little more complicated:
<- as.function(mp("x^2 - y^2"))
f # f(.) with . = (x, y)
<- seq(-2.5, 2.5, .1)
s <- expand.grid(x = s, y = s)
df $f <- apply(df, 1, f)
dfqplot(x, y, data = df, geom = "raster", fill = f)
Using tidyverse-style coding
(install tidyverse packages with
install.packages("tidyverse")
), this looks a bit
cleaner:
<- as.function(mp("x^2 - y^2"), vector = FALSE)
f # f(x, y)
seq(-2.5, 2.5, .1) %>%
list("x" = ., "y" = .) %>%
cross_df() %>%
mutate(f = f(x, y)) %>%
ggplot(aes(x, y, fill = f)) +
geom_raster()
Grobner bases are no longer implemented in mpoly; they’re now in m2r.
# polys <- mp(c("t^4 - x", "t^3 - y", "t^2 - z"))
# grobner(polys)
Homogenization and dehomogenization:
<- mp("x + 2 x y + y - z^3"))
(p # x + 2 x y + y - z^3
<- homogenize(p))
(hp # x t^2 + 2 x y t + y t^2 - z^3
dehomogenize(hp, "t")
# x + 2 x y + y - z^3
homogeneous_components(p)
# x + y
# 2 x y
# -1 z^3
mpoly can make use of many pieces of the
polynom and orthopolynom packages with
as.mpoly()
methods. In particular, many special polynomials
are available.
You can construct Chebyshev polynomials as follows:
chebyshev(1)
# Loading required package: polynom
#
# Attaching package: 'polynom'
# The following object is masked from 'package:mpoly':
#
# LCM
# x
chebyshev(2)
# -1 + 2 x^2
chebyshev(0:5)
# 1
# x
# 2 x^2 - 1
# 4 x^3 - 3 x
# 8 x^4 - 8 x^2 + 1
# 16 x^5 - 20 x^3 + 5 x
And you can visualize them:
<- seq(-1, 1, length.out = 201); N <- 5
s <- chebyshev(0:N))
(chebPolys # 1
# x
# 2 x^2 - 1
# 4 x^3 - 3 x
# 8 x^4 - 8 x^2 + 1
# 16 x^5 - 20 x^3 + 5 x
<- as.function(chebPolys)(s) %>% cbind(s, .) %>% as.data.frame()
df names(df) <- c("x", paste0("T_", 0:N))
<- df %>% gather(degree, value, -x)
mdf qplot(x, value, data = mdf, geom = "path", color = degree)
<- seq(-1, 1, length.out = 201); N <- 5
s <- jacobi(0:N, 2, 2))
(jacPolys # 1
# 5 x
# 17.5 x^2 - 2.5
# 52.5 x^3 - 17.5 x
# 144.375 x^4 - 78.75 x^2 + 4.375
# 375.375 x^5 - 288.75 x^3 + 39.375 x
<- as.function(jacPolys)(s) %>% cbind(s, .) %>% as.data.frame
df names(df) <- c("x", paste0("P_", 0:N))
<- df %>% gather(degree, value, -x)
mdf qplot(x, value, data = mdf, geom = "path", color = degree) +
coord_cartesian(ylim = c(-25, 25))
<- seq(-1, 1, length.out = 201); N <- 5
s <- legendre(0:N))
(legPolys # 1
# x
# 1.5 x^2 - 0.5
# 2.5 x^3 - 1.5 x
# 4.375 x^4 - 3.75 x^2 + 0.375
# 7.875 x^5 - 8.75 x^3 + 1.875 x
<- as.function(legPolys)(s) %>% cbind(s, .) %>% as.data.frame
df names(df) <- c("x", paste0("P_", 0:N))
<- df %>% gather(degree, value, -x)
mdf qplot(x, value, data = mdf, geom = "path", color = degree)
<- seq(-3, 3, length.out = 201); N <- 5
s <- hermite(0:N))
(hermPolys # 1
# x
# x^2 - 1
# x^3 - 3 x
# x^4 - 6 x^2 + 3
# x^5 - 10 x^3 + 15 x
<- as.function(hermPolys)(s) %>% cbind(s, .) %>% as.data.frame
df names(df) <- c("x", paste0("He_", 0:N))
<- df %>% gather(degree, value, -x)
mdf qplot(x, value, data = mdf, geom = "path", color = degree)
<- seq(-5, 20, length.out = 201); N <- 5
s <- laguerre(0:N))
(lagPolys # 1
# -1 x + 1
# 0.5 x^2 - 2 x + 1
# -0.1666667 x^3 + 1.5 x^2 - 3 x + 1
# 0.04166667 x^4 - 0.6666667 x^3 + 3 x^2 - 4 x + 1
# -0.008333333 x^5 + 0.2083333 x^4 - 1.666667 x^3 + 5 x^2 - 5 x + 1
<- as.function(lagPolys)(s) %>% cbind(s, .) %>% as.data.frame
df names(df) <- c("x", paste0("L_", 0:N))
<- df %>% gather(degree, value, -x)
mdf qplot(x, value, data = mdf, geom = "path", color = degree) +
coord_cartesian(ylim = c(-25, 25))
Bernstein
polynomials are not in polynom or
orthopolynom but are available in
mpoly with bernstein()
:
bernstein(0:4, 4)
# x^4 - 4 x^3 + 6 x^2 - 4 x + 1
# -4 x^4 + 12 x^3 - 12 x^2 + 4 x
# 6 x^4 - 12 x^3 + 6 x^2
# -4 x^4 + 4 x^3
# x^4
<- seq(0, 1, length.out = 101)
s <- 5 # number of bernstein polynomials to plot
N <- bernstein(0:N, N))
(bernPolys # -1 x^5 + 5 x^4 - 10 x^3 + 10 x^2 - 5 x + 1
# 5 x^5 - 20 x^4 + 30 x^3 - 20 x^2 + 5 x
# -10 x^5 + 30 x^4 - 30 x^3 + 10 x^2
# 10 x^5 - 20 x^4 + 10 x^3
# -5 x^5 + 5 x^4
# x^5
<- as.function(bernPolys)(s) %>% cbind(s, .) %>% as.data.frame
df names(df) <- c("x", paste0("B_", 0:N))
<- df %>% gather(degree, value, -x)
mdf qplot(x, value, data = mdf, geom = "path", color = degree)
You can use the bernstein_approx()
function to compute
the Bernstein polynomial approximation to a function. Here’s an
approximation to the standard normal density:
<- bernstein_approx(dnorm, 15, -1.25, 1.25)
p round(p, 4)
# -0.1624 x^2 + 0.0262 x^4 - 0.002 x^6 + 0.0001 x^8 + 0.3796
<- seq(-3, 3, length.out = 101)
x <- data.frame(
df x = rep(x, 2),
y = c(dnorm(x), as.function(p)(x)),
which = rep(c("actual", "approx"), each = 101)
)# f(.) with . = x
qplot(x, y, data = df, geom = "path", color = which)
You can construct Bezier polynomials
for a given collection of points with bezier()
:
<- data.frame(x = c(-1,-2,2,1), y = c(0,1,1,0))
points <- bezier(points))
(bezPolys # -10 t^3 + 15 t^2 - 3 t - 1
# -3 t^2 + 3 t
And viewing them is just as easy:
<- as.function(bezPolys)(s) %>% as.data.frame
df
ggplot(aes(x = x, y = y), data = df) +
geom_point(data = points, color = "red", size = 4) +
geom_path(data = points, color = "red", linetype = 2) +
geom_path(size = 2)
Weighting is available also:
<- data.frame(x = c(1,-2,2,-1), y = c(0,1,1,0))
points <- bezier(points))
(bezPolys # -14 t^3 + 21 t^2 - 9 t + 1
# -3 t^2 + 3 t
<- as.function(bezPolys, weights = c(1,5,5,1))(s) %>% as.data.frame
df
ggplot(aes(x = x, y = y), data = df) +
geom_point(data = points, color = "red", size = 4) +
geom_path(data = points, color = "red", linetype = 2) +
geom_path(size = 2)
To make the evaluation of the Bezier polynomials stable,
as.function()
has a special method for Bezier polynomials
that makes use of de
Casteljau’s algorithm. This allows bezier()
to be used
as a smoother:
<- seq(0, 1, length.out = 201)
s <- as.function(bezier(cars))(s) %>% as.data.frame
df qplot(speed, dist, data = cars) +
geom_path(data = df, color = "red")
I’m starting to put in methods for some other R functions:
set.seed(1)
<- 101
n <- data.frame(x = seq(-5, 5, length.out = n))
df $y <- with(df, -x^2 + 2*x - 3 + rnorm(n, 0, 2))
df
<- lm(y ~ x + I(x^2), data = df)
mod <- mod %>% as.mpoly %>% round)
(p # 1.983 x - 1.01 x^2 - 2.709
qplot(x, y, data = df) +
stat_function(fun = as.function(p), colour = 'red')
# f(.) with . = x
<- seq(-5, 5, length.out = n)
s <- expand.grid(x = s, y = s) %>%
df mutate(z = x^2 - y^2 + 3*x*y + rnorm(n^2, 0, 3))
<- lm(z ~ poly(x, y, degree = 2, raw = TRUE), data = df))
(mod #
# Call:
# lm(formula = z ~ poly(x, y, degree = 2, raw = TRUE), data = df)
#
# Coefficients:
# (Intercept)
# -0.070512
# poly(x, y, degree = 2, raw = TRUE)1.0
# -0.004841
# poly(x, y, degree = 2, raw = TRUE)2.0
# 1.005307
# poly(x, y, degree = 2, raw = TRUE)0.1
# 0.001334
# poly(x, y, degree = 2, raw = TRUE)1.1
# 3.003755
# poly(x, y, degree = 2, raw = TRUE)0.2
# -0.999536
as.mpoly(mod)
# -0.004840798 x + 1.005307 x^2 + 0.001334122 y + 3.003755 x y - 0.9995356 y^2 - 0.07051218
From CRAN: install.packages("mpoly")
From Github (dev version):
# install.packages("devtools")
::install_github("dkahle/mpoly") devtools
This material is based upon work partially supported by the National Science Foundation under Grant No. 1622449.