forked from coneill-math/m2r
-
Notifications
You must be signed in to change notification settings - Fork 0
/
README.Rmd
361 lines (240 loc) · 12.6 KB
/
README.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
---
output:
md_document:
variant: markdown_github
---
```{r, echo = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#",
fig.path = "tools/README-",
dpi = 250
)
```
# __m2r__ – Macaulay2 in R
[![CRAN_Status_Badge](http://www.r-pkg.org/badges/version/m2r)](https://cran.r-project.org/package=m2r)
<!-- [![Travis build status](https://travis-ci.org/dkahle/m2r.svg?branch=master)](https://travis-ci.org/dkahle/m2r) -->
<!-- [![AppVeyor build status](https://ci.appveyor.com/api/projects/status/github/dkahle/m2r?branch=master&svg=true)](https://ci.appveyor.com/project/dkahle/m2r) -->
## Overview
__m2r__ is a new R package that provides a persistent connection between [R](https://www.r-project.org) and [Macaulay2 (M2)](http://www.math.uiuc.edu/Macaulay2/).
The package grew out of a collaboration at the [2016 Mathematics Research Community](http://www.ams.org/programs/research-communities/mrc-16) on algebraic statistics, funded by the [National Science Foundation](http://www.nsf.gov) through the [American Mathematical Society](http://www.ams.org/home/page).
If you have a feature request, please file an issue!
## Getting started
__m2r__ is loaded like any other R package:
```{r load_m2}
library(m2r)
```
When loaded, __m2r__ initializes a persistent connection to a back-end Macaulay2 session. The basic function in R that accesses this connection is `m2()`, which simply accepts a character string that is run by the Macaulay2 session.
```{r basic_connection}
m2("1 + 1")
```
You can see the persistence by setting variables and accessing them across different `m2()` calls:
```{r persistence}
m2("a = 1")
m2("a")
```
You can check the variables defined in the M2 session with `m2_ls()`:
```{r m2_ls}
m2_ls()
```
You can also check if variables exist with `m2_exists()`:
```{r m2_exists}
m2_exists("a")
m2_exists(c("a","b"))
```
Apart from the basic connection to M2, __m2r__ has basic data structures and methods to reference and manipulate the M2 objects within R. For more on this, see the __m2r__ internals section below.
## Rings, ideals, and Grobner bases
__m2r__ currently has basic support for [rings](https://en.wikipedia.org/wiki/Ring_(mathematics)) (think: [polynomial rings](https://en.wikipedia.org/wiki/Polynomial_ring)):
```{r ring}
(QQtxyz <- ring("t", "x", "y", "z", coefring = "QQ"))
```
and [ideals](https://en.wikipedia.org/wiki/Ideal_(ring_theory)) of rings:
```{r ideal}
(I <- ideal("t^4 - x", "t^3 - y", "t^2 - z"))
```
You can compute [Grobner bases](https://en.wikipedia.org/wiki/Gröbner_basis) as well. The basic function to do this is `gb()`:
```{r ideal_grobner}
gb(I)
```
Perhaps an easier way to do this is just to list off the polynomials as character strings:
```{r basic_grobner}
gb("t^4 - x", "t^3 - y", "t^2 - z")
```
The result is an `mpolyList` object, from the [__mpoly__ package](https://github.com/dkahle/mpoly). You can see the M2 code by adding `code = TRUE`:
```{r grobner_code}
gb("t^4 - x", "t^3 - y", "t^2 - z", code = TRUE)
```
You can compute the basis respective of different [monomial orders](https://en.wikipedia.org/wiki/Monomial_order) as well. The default ordering is the one in the respective ring, which defaults to `grevlex`; however, changing the order is as simple as changing the ring.
```{r grobner_order}
ring("x", "y", "t", "z", coefring = "QQ", order = "lex")
gb("t^4 - x", "t^3 - y", "t^2 - z")
```
On a technical level, `ring()`, `ideal()`, and `gb()` use [nonstandard evaluation rules](http://adv-r.had.co.nz/Computing-on-the-language.html). A more stable way to use these functions is to use their standard evaluation versions `ring_()`, `ideal_()`, and `gb_()`. Each accepts first a data structure describing the relevant object of interest first as its own object. For example, at a basic level this simply changes the previous syntax to
```{r gb_}
use_ring(QQtxyz)
poly_chars <- c("t^4 - x", "t^3 - y", "t^2 - z")
gb_(poly_chars)
```
`gb_()` is significantly easier to code with than `gb()` in the sense that its inputs and outputs are more predictable, so we strongly recommend that you use `gb_()`, especially inside of other functions and packages.
As far as other kinds of computations are concerned, we present a potpurri of examples below.
Ideal saturation:
```{r saturation}
ring("x", coefring = "QQ")
I <- ideal("(x-1) x (x+1)")
saturate(I, "x") # = (x-1) (x+1)
```
Radicalization:
```{r radical}
I <- ideal("x^2")
radical(I)
```
Primary decomposition:
```{r primary-decomposition}
ring("x", "y", "z", coefring = "QQ")
I <- ideal("x z", "y z")
primary_decomposition(I)
```
Dimension:
```{r dimension}
ring("x", "y", coefring = "QQ")
I <- ideal("y - (x+1)")
dimension(I)
```
## Factoring integers and polynomials
You can compute [prime decompositions](https://en.wikipedia.org/wiki/Integer_factorization) of integers with `factor_n()`:
```{r factor_n}
(x <- 2^5 * 3^4 * 5^3 * 7^2 * 11^1)
factor_n(x)
```
You can also [factor polynomials](https://en.wikipedia.org/wiki/Factorization) over rings using `factor_poly()`:
```{r factor_poly}
factor_poly("x^4 - y^4")
```
## Smith normal form of a matrix
The Smith normal form of a matrix _M_ here refers to the decomposition of an integer matrix _D = PMQ_, where _D_, _P_, and _Q_ are integer matrices and _D_ is diagonal. _P_ and _Q_ are unimodular matrices (their determinants are -1 or 1), so they are invertible. This is somewhat like a singular value decomposition for integer matrices.
```{r snf}
M <- matrix(c(
2, 4, 4,
-6, 6, 12,
10, -4, -16
), nrow = 3, byrow = TRUE)
(mats <- snf(M))
P <- mats$P; D <- mats$D; Q <- mats$Q
P %*% M %*% Q # = D
solve(P) %*% D %*% solve(Q) # = M
det(P)
det(Q)
```
## __m2r__ internals: pointers, reference and value functions, and `m2` objects
At a basic level, __m2r__ works by passing strings between R and M2. Originating at the R side, these strings are properly formated M2 code constructed from the inputs to the R functions. That code goes to M2, is evaluated there, and then "exported" with M2's function `toExternalString()`. The resulting string often, but not always, produces the M2 code needed to recreate the object resulting from the evaluation, and in that sense is M2's version of R's `dput()`. That string is passed back into R and parsed there into R-style data structures, typically [S3-classed lists](http://adv-r.had.co.nz/OO-essentials.html#s3).
The R-side parsing of the external string from M2 is an expensive process because it is currently implemented in R. Consequently (and for other reasons, too!), in some cases you'll want to do a M2 computation from R, but leave the output in M2. Since you will ultimately want something in R referencing the result, nearly every __m2r__ function that performs M2 computations has a pointer version. As a simple naming convention, the name of the function that returns the pointer, called the reference function, is determined by the name of the ordinary function, called the value function, by appending a `.`.
For example, we've seen that `factor_n()` computes the prime decomposition of a number. The corresponding reference function is `factor_n.()`:
```{r ref_factor_n}
(x <- 2^5 * 3^4 * 5^3 * 7^2 * 11^1)
factor_n.(x)
```
All value functions simply wrap reference functions and parse the output with `m2_parse()`, a general M2 parser, often followed by a little more parsing. `m2_parse()` typically creates an object of class `m2` so that R knows what kind of thing it is. For example:
```{r ref_factor_n_class}
class(factor_n.(x))
```
Even more, `m2_parse()` often creates objects that have an inheritance structure that references `m2` somewhere in the middle of its class structure, with specific structure preceding and general structure succeeding (examples below). Apart from its class, the general principle we follow here for the object itself is this: if the M2 object has a direct analogue in R, it is parsed into that kind of R object and additional M2 properties are kept as metadata (attributes); if there is no direct analogue in R, the object is an `NA` with metadata.
Perhaps the easiest way to see this is with a matrix. `m2_matrix()` creates a matrix on the M2 side from input on the R side. In the following, to make things more clear we use [__magrittr__'s pipe operator](https://github.com/tidyverse/magrittr), with which the following calls are semantically equivalent: `g(f(x))` and `x %>% f %>% g`.
```{r objects_1}
library(magrittr)
mat <- matrix(c(1,2,3,4,5,6), nrow = 3, ncol = 2)
mat %>% m2_matrix. # = m2_matrix.(mat)
mat %>% m2_matrix. %>% m2_parse
mat %>% m2_matrix. %>% m2_parse %>% str
mat %>% m2_matrix # = m2_parse(m2_matrix.(mat))
```
It may be helpful to think of every `m2` object as being a missing value (`NA`, a `logical(1)`) with two M2 attributes: their name (`m2_name`) and a capture-all named list (`m2_meta`). These can be accessed with `m2_name()` and `m2_meta()`. For example, a ring, having no analogous object in R, is an `NA` with attributes:
```{r objects_2}
r <- ring("x", "y", coefring = "QQ")
str(r)
class(r)
m2_name(r)
m2_meta(r)
```
But a matrix of integers isn't:
```{r objects_3}
mat <- m2_matrix(matrix(c(1,2,3,4,5,6), nrow = 3, ncol = 2))
str(mat)
class(mat)
m2_name(mat)
m2_meta(mat)
```
Since a matrix of integers is an object in R, it's represented as one, and consequently we can compute with it directly as it if it were a matrix; it is. On the other hand, since a ring is not, it's an `NA`. When dealing with M2, object like rings, that is to say objects without R analogues, are more common than those like integer matrices.
## Creating your own __m2r__ wrapper
We've already wrapped a number of Macaulay2 functions; for a list of functions in __m2r__, check out `ls("package:m2r")`. But the list is very far from exhaustive. To create your own wrapper function of a Macaulay2 command, you'll need to create an R file that looks like the one below. This will create both value (e.g. `f`) and reference/pointer (e.g. `f.`) versions of the function. As a good example of these at work, see the scripts for [`factor_n()`](https://github.com/musicman3320/m2r/blob/master/R/factor_n.R) or [`factor_poly()`](https://github.com/musicman3320/m2r/blob/master/R/factor_poly.R).
```{r creating-your-own, eval=FALSE}
#' Function documentation header
#'
#' Function header explanation, can run several lines. Function
#' header explanation, can run several lines. Function header
#' explanation, can run several lines.
#'
#' @param esntl_parm_1 esntl_parm_1 description
#' @param esntl_parm_2 esntl_parm_2 description
#' @param code return only the M2 code? (default: \code{FALSE})
#' @param parse_parm_1 parse_parm_1 description
#' @param parse_parm_2 parse_parm_2 description
#' @param ... ...
#' @name f
#' @return (value version) parsed output or (reference/dot version)
#' \code{m2_pointer}
#' @examples
#'
#' \dontrun{ requires Macaulay2 be installed
#'
#' # put examples here
#' 1 + 1
#'
#' }
#'
# value version of f (standard user version)
#' @rdname f
#' @export
f <- function(esntl_parm_1, esntl_parm_2, code = FALSE, parse_parm_1, parse_parm_2, ...) {
# run m2
args <- as.list(match.call())[-1]
eargs <- lapply(args, eval, envir = parent.frame())
pointer <- do.call(f., eargs)
if(code) return(invisible(pointer))
# parse output
parsed_out <- m2_parse(pointer)
# more parsing, like changing classes and such
TRUE
# return
TRUE
}
# reference version of f (returns pointer to m2 object)
#' @rdname f
#' @export
f. <- function(esntl_parm_1, esntl_parm_2, code = FALSE, ...) {
# basic arg checking
TRUE
# create essential parameters to pass to m2 this step regularizes input to m2, so it
# is the one that deals with pointers, chars, rings, ideals, mpolyLists, etc.
TRUE
# construct m2_code from regularized essential parameters
TRUE
# message
if(code) { message(m2_code); return(invisible(m2_code)) }
# run m2 and return pointer
m2.(m2_code)
}
```
## Acknowledgements
This material is based upon work supported by the National Science Foundation under Grant Nos. [1321794](https://nsf.gov/awardsearch/showAward?AWD_ID=1321794) and [1622449](https://nsf.gov/awardsearch/showAward?AWD_ID=1622449).
## Installation
Here's how you can install the current _developmental_ version of __m2r__.
```{r setup-1, eval=FALSE}
if (!requireNamespace("devtools")) install.packages("devtools")
devtools::install_github("coneill-math/m2r")
```
For __m2r__ to find Macaulay2, you'll need to set an environmental variable in your `~/.Renviron` file. To do that, run this:
```{r setup-2, eval=FALSE}
if (!requireNamespace("usethis")) install.packages("usethis")
usethis::edit_r_environ()
```
And, in the text file that opens, add a line such as `M2=/Applications/Macaulay2-1.10/bin`.