Introduction

The mvp package provides some functionality for fast manipulation of multivariate polynomials, using the Standard Template library of C++, commonly known as the STL. It is comparable in speed to the spray package for sparse arrays, while retaining the symbolic capabilities of the mpoly package (Kahle 2013). The mvp package uses the excellent print and coercion methods of mpoly. The mvp package provides improved speed over mpoly, the ability to handle negative powers, and a more sophisticated substitution mechanism.

The STL map class

A map is a sorted associative container that contains key-value pairs with unique keys. It is interesting here because search and insertion operations have logarithmic complexity. Multivariate polynomials are considered to be the sum of a finite number of terms, each multiplied by a coefficient. A term is something like \(x^2y^3z\). We may consider this term to be the map

{"x" -> 2, "y" -> 3, "z" -> 7}

the map takes symbols to their (integer) power, and it is understood that powers are nonzero. A mvp object is a map from terms to their coeffients; thus \(7xy^2 -3x^2z^5\) would be

{{"x" -> 2, "y" -> 3, "z" -> 1} -> 7, {"x" -> 2, "z" ->5} -> -7}

and we understand that coefficients are nonzero. In C++ the declarations would be

typedef vector <signed int> mypowers;  
typedef vector <string> mynames;  

typedef map <string, signed int> term; 
typedef map <term, double> mvp; 

Thus a term maps a string to a (signed) integer, and a mvp maps terms to doubles.

One reason why the map class is fast is that the order in which the keys are stored is undefined: the compiler may store them in the order which it regards as most propitious. This is not an issue for the maps considered here as addition and multiplication are commutative and associative.

Note also that constant terms are handled with no difficulty (constants are simply maps from the empty map to its value), as is the zero polynomial (which is simply an empty map).

The package in use

Consider a simple multivariate polynomial \(3xy+z^3+xy^6z\) and its representation in the following R session:

library("mvp",quietly=TRUE)
p <- as.mvp("3x y + z^3 + x y^6 z")
p
#> mvp object algebraically equal to
#> 3 x y  +  x y^6 z  +  z^3

Coercion and printing are accomplished by the mpoly package (there is no way I could improve upon Kahle’s work). Note carefully that the printed representation of the mvp object is created by the mpoly package and the print method can rearrange both the terms of the polynomial (\(3xy+z^3+xy^6z = z^3+3xy+xy^6z\), for example) and the symbols within a term (\(3xy=3yx\), for example) to display the polynomial in a human-friendly form.

However, note carefully that such rearranging does not affect the mathematical properties of the polynomial itself. In the `mvp package, the order of the terms is not preserved (or even defined) in the internal representation of the object; and neither is the order of the symbols within a single term. Although this might sound odd, if we consider a marginally more involved situation, such as

M <- as.mvp("3 stoat*goat^6 -4 + 7 stoatboat^3 * bloat -9 float*boat*goat*gloat^6")
M
#> mvp object algebraically equal to
#> -4  +  7 bloat stoatboat^3  -  9 boat float gloat^6 goat  +  3 goat^6 stoat
dput(M)
#> structure(list(names = list(character(0), c("bloat", "stoatboat"
#> ), c("boat", "float", "gloat", "goat"), c("goat", "stoat")), 
#>     power = list(integer(0), c(1L, 3L), c(1L, 1L, 6L, 1L), c(6L, 
#>     1L)), coeffs = c(-4, 7, -9, 3)), class = "mvp")

it is not clear that any human-discernable ordering is preferable to any other, and we would be better off letting the compiler decide a propitious ordering. In any event, the mpoly package can specify a print order:

print(M,order="lex", varorder=c("stoat","goat","boat","bloat","gloat","float","stoatboat"))
#> mvp object algebraically equal to
#> 3 stoat goat^6  -  9 goat boat gloat^6 float  +  7 bloat stoatboat^3  -  4

Arithmetic operations

The arithmetic operations *, +, - and ^ work as expected:

S1 <- rmvp(5,2,2,4)
S2 <- rmvp(5,2,2,4)
S1
#> mvp object algebraically equal to
#> a^3  +  5 b c  +  4 b^2 d  +  2 c^3  +  3 d^3
S2
#> mvp object algebraically equal to
#> 2 a d^2  +  a^2 d^2  +  4 a^3  +  5 b c  +  3 b^2 c
S1+S2
#> mvp object algebraically equal to
#> 2 a d^2  +  a^2 d^2  +  5 a^3  +  10 b c  +  3 b^2 c  +  4 b^2 d  +  2 c^3  +  3 d^3
S1*S2
#> mvp object algebraically equal to
#> 10 a b c d^2  +  8 a b^2 d^3  +  4 a c^3 d^2  +  6 a d^5  +  5 a^2 b c d^2  +  4 a^2 b^2 d^3  +  2 a^2 c^3 d^2  +  3 a^2 d^5  +  25 a^3 b c  +  3 a^3 b^2 c  +  16 a^3 b^2 d  +  8 a^3 c^3  +  12 a^3 d^3  +  2 a^4 d^2  +  a^5 d^2  +  4 a^6  +  15 b c d^3  +  10 b c^4  +  9 b^2 c d^3  +  25 b^2 c^2  +  6 b^2 c^4  +  20 b^3 c d  +  15 b^3 c^2  +  12 b^4 c d
S1^2
#> mvp object algebraically equal to
#> 10 a^3 b c  +  8 a^3 b^2 d  +  4 a^3 c^3  +  6 a^3 d^3  +  a^6  +  30 b c d^3  +  20 b c^4  +  25 b^2 c^2  +  16 b^2 c^3 d  +  24 b^2 d^4  +  40 b^3 c d  +  16 b^4 d^2  +  12 c^3 d^3  +  4 c^6  +  9 d^6

Substitution

The package has two substitution functionalities. Firstly, we can substitute one or more variables for a numeric value. Define a mvp object:

S3 <- as.mvp("x + 5x^4*y + 8y^2*x*z^3")
S3
#> mvp object algebraically equal to
#> x  +  8 x y^2 z^3  +  5 x^4 y

And then we may substitute \(x=1\):

subs(S3,x=1)
#> mvp object algebraically equal to
#> 1  +  5 y  +  8 y^2 z^3

Note the natural R idiom, and that the return value is another mvp object. We may subsitute for the other variables:

subs(S3,x=1,y=2,z=3)
#> [1] 875

(in this case, the default behaviour is to return a “dropped” version of the resulting polynomial, that is, coerced to a scalar). The other mode of substitution is to replace a variable by another polynomial:

subsmvp(S3,"z",as.mvp("a^2+2b^3"))
#> mvp object algebraically equal to
#> 96 a^2 b^6 x y^2  +  48 a^4 b^3 x y^2  +  8 a^6 x y^2  +  64 b^9 x y^2  +  x  +  5 x^4 y

Differentiation

Differentiation is implemented. First we have the deriv() method:

S <- rmvp(5,4,6,4)
S
#> mvp object algebraically equal to
#> 2 a^3 b^5 d^7  +  4 a^12 d^6  +  3 b^2 c^5 d^11  +  b^6 c^8 d^6  +  5 c^11 d^7
deriv(S,letters[1:3])
#> mvp object algebraically equal to
#> 0
deriv(S,rev(letters[1:3]))  # should be the same.
#> mvp object algebraically equal to
#> 0

Also a slightly different form:aderiv(), here used to evaluate \(\frac{\partial^6S}{\partial a^3\partial b\partial c^2}\):

aderiv(S,a=3,b=1,c=2)
#> mvp object algebraically equal to
#> 0

Negative powers

The mvp package handles negative powers, although the idiom is not perfect and I’m still working on it. There is the invert() function:

p <- as.mvp("1+x+x^2 y")
p
#> mvp object algebraically equal to
#> 1  +  x  +  x^2 y
invert(p)
#> mvp object algebraically equal to
#> 1  +  x^-2 y^-1  +  x^-1

In the above, p is a regular multivariate polynomial which includes negative powers. It obeys the same arithmetic rules as other mvp objects:

p + as.mvp("z^6")

We can see the generating function for a chess knight:

knight(2)
#> mvp object algebraically equal to
#> a^-2 b^-1  +  a^-2 b  +  a^-1 b^-2  +  a^-1 b^2  +  a b^-2  +  a b^2  +  a^2 b^-1  +  a^2 b

How many ways are there for a 4D knight to return to its starting square after four moves? Answer:

constant(knight(4)^4)
#> [1] 12528

Some timings

library("spray")
library("mpoly")
a1 <- rmvp(30,9,9,26)
a2 <- as.mpoly(a1)
a3 <- mvp_to_spray(a1)
system.time(ignore <- a1^3)  # mvp
#>    user  system elapsed 
#>   0.264   0.006   0.272
system.time(ignore <- a2^3)  # mpoly
#>    user  system elapsed 
#>   1.366   0.019   1.388
system.time(ignore <- a3^3)  # spray
#>    user  system elapsed 
#>   0.020   0.001   0.026

References

Kahle, D. 2013. “Mpoly: Multivariate Polynomials in R.” R Journal 5 (1):162.