Skip to content

Commit 90c3ae3

Browse files
Add matrix, array and matrix multiplication for integer64 (#195)
* add matrix, array, %*%.integer64 refactor str.integer64 for consistent display of matrices refactor colSums and rowSums to be consistent to base * fix sys.call level in target_class_for_Ops * fix tests to run with ubuntu-latest (3.6) and macos-latest (release) * fix matrix multiplication for macos * manual debug macos * debug macos * debug macos * debug macos * debug macos * fix for macos * Update R/integer64.R Co-authored-by: Michael Chirico <michaelchirico4@gmail.com> * Update R/integer64.R Co-authored-by: Michael Chirico <michaelchirico4@gmail.com> * rename `dimO` to `obj_dim` * `dim` or `dims` is allowed in error message try fix tests for ubuntu latest * remove dependency from package `patrick` for testing * remove package `patrick` from DESCRIPTION * Revert "remove package `patrick` from DESCRIPTION" This reverts commit 98220d3. * Revert "remove dependency from package `patrick` for testing" This reverts commit 2ba1592. * use skip_unless_r() * backport `errorCondition()` and `warningCondition()` * debug of test-ancient * Revert "debug of test-ancient" This reverts commit 8382964. * try fixing ancient-test * reduce duplication in tests * small fix for ignore_attr * implicit assignment of `obj_dim` * ws * revert keep.names= again * restore sync of method + generic signatures * restore sync of method + generic signatures * helpers added `choose_sys_call()` and `withCallingHandlers_and_choose_call()` * add named arguments for base calls of default methods colSums and rowSums * extend binary operator contract table * add nice calls in condition messages for colSums and rowSums default methods * move S3 generics and defaults to patch64.R * convert `.onLoad()` into if statement * remove unnecessary `as.integer` in tests * update matrix64.Rd * style on backports * try to use local() for clarity * reduce redundancy with {patrick} * do we even need local()? * comments * fix patrick translation, remove expect_no_warning() by default * remove more expect_no_warning() * more post-patrick fixes, readability * typo integer->integer64 * Need to pass '...' to grepl() * trying with substitute() * missing ')' * avoid rep_len() for ancient * documentation of `choose_sys_call()` * add `as.integer64(x)` in `as.matrix.integer64` * mark rep_len for TODO * simplify --------- Co-authored-by: Michael Chirico <michaelchirico4@gmail.com> Co-authored-by: Michael Chirico <chiricom@google.com>
1 parent 51221fe commit 90c3ae3

15 files changed

Lines changed: 644 additions & 84 deletions

File tree

.devcontainer/r-350/.Rprofile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ expect_warning <- function(object, regexp = NULL, ...) {
7676
}
7777

7878
if (is.null(regexp)) return(invisible())
79-
if (!any(grepl(regexp, warnings))) {
79+
if (!any(grepl(regexp, warnings, ...))) {
8080
stop(sprintf("FAILURE: expect_warning() regex mismatch.\n Expected: %s\n Actual: %s", regexp, toString(warnings)))
8181
}
8282
}

NAMESPACE

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ S3method(abs,integer64)
2929
S3method(all,integer64)
3030
S3method(all.equal,integer64)
3131
S3method(any,integer64)
32-
S3method(aperm,integer64)
32+
S3method(array,default)
33+
S3method(array,integer64)
3334
S3method(as.bitstring,integer64)
3435
S3method(as.character,integer64)
3536
S3method(as.data.frame,integer64)
@@ -51,11 +52,14 @@ S3method(as.integer64,logical)
5152
S3method(as.integer64,raw)
5253
S3method(as.list,integer64)
5354
S3method(as.logical,integer64)
55+
S3method(base::`%*%`,integer64)
5456
S3method(base::anyNA,integer64)
57+
S3method(base::aperm,integer64)
5558
S3method(base::as.Date,integer64)
5659
S3method(base::as.POSIXct,integer64)
5760
S3method(base::as.POSIXlt,integer64)
5861
S3method(base::as.complex,integer64)
62+
S3method(base::as.matrix,integer64)
5963
S3method(base::as.numeric,integer64)
6064
S3method(base::as.raw,integer64)
6165
S3method(c,integer64)
@@ -99,6 +103,8 @@ S3method(log10,integer64)
99103
S3method(log2,integer64)
100104
S3method(match,default)
101105
S3method(match,integer64)
106+
S3method(matrix,default)
107+
S3method(matrix,integer64)
102108
S3method(max,integer64)
103109
S3method(mean,integer64)
104110
S3method(median,integer64)
@@ -193,6 +199,7 @@ export(abs.integer64)
193199
export(all.equal.integer64)
194200
export(all.integer64)
195201
export(any.integer64)
202+
export(array)
196203
export(as.bitstring)
197204
export(as.bitstring.integer64)
198205
export(as.character.integer64)
@@ -267,6 +274,7 @@ export(log.integer64)
267274
export(match)
268275
export(match.default)
269276
export(match.integer64)
277+
export(matrix)
270278
export(max.integer64)
271279
export(mean.integer64)
272280
export(median.integer64)
@@ -415,6 +423,7 @@ importFrom(methods,is)
415423
importFrom(stats,cor)
416424
importFrom(stats,median)
417425
importFrom(stats,quantile)
426+
importFrom(utils,getS3method)
418427
importFrom(utils,head)
419428
importFrom(utils,packageDescription)
420429
importFrom(utils,strOptions)

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
- `as.integer64` gets `Date`, `POSIXct`, `POSXlt`, `complex`, `raw`, and `difftime` methods.
5656
1. `as.integer64.character` now supports hexadecimal (base 16) input when prefixed with "0x" or "-0x", e.g. `as.integer64("0x7FFFFFFFFFFFFFFF")`. Thanks @hcirellu for a PR which completes work begun by @marcpaterno.
5757
1. `sortcache`, `sortordercache` and `ordercache` get a new argument `na.last`.
58+
1. `matrix`, `array`, `%*%` and `as.matrix` get an `integer64` method (#45). Thanks @hcirellu.
5859

5960
## BUG FIXES
6061

R/bit64-package.R

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@
116116
#' | double | / | integer64 | -> | integer64 | / | long double | -> | double |
117117
#' | integer64 | ^ | double | -> | integer64 | / | long double | -> | double |
118118
#' | double | ^ | integer64 | -> | integer64 | / | long double | -> | double |
119+
#' | integer64 | %*% | double | -> | integer64 | %*% | integer64 | -> | integer64 |
120+
#' | double | %*% | integer64 | -> | integer64 | %*% | integer64 | -> | integer64 |
121+
#' | integer64 | %*% | complex | -> | double | %*% | complex | -> | complex |
122+
#' | complex | %*% | integer64 | -> | complex | %*% | double | -> | complex |
119123
#'
120124
#' # Creating and testing S3 class 'integer64'
121125
#'
@@ -697,7 +701,7 @@
697701
#' @importFrom graphics barplot par title
698702
#' @importFrom methods as is
699703
#' @importFrom stats cor median quantile
700-
#' @importFrom utils head packageDescription strOptions tail
704+
#' @importFrom utils head packageDescription strOptions tail getS3method
701705
#' @export : :.default :.integer64
702706
#' @export [.integer64 [[.integer64 [[<-.integer64 [<-.integer64
703707
#' @export %in% %in%.default

R/integer64.R

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,7 @@ NULL
114114
#' @seealso [`[`][base::Extract] [integer64()]
115115
#' @examples
116116
#' as.integer64(1:12)[1:3]
117-
#' x <- as.integer64(1:12)
118-
#' dim(x) <- c(3, 4)
117+
#' x <- matrix(as.integer64(1:12), nrow = 3L)
119118
#' x
120119
#' x[]
121120
#' x[, 2:3]
@@ -881,7 +880,7 @@ str.integer64 = function(object, vec.len=strO$vec.len, give.head=TRUE, give.leng
881880
vec.len = 2L*vec.len
882881
n = length(object)
883882
displayObject = object[seq_len(min(vec.len, length(object)))]
884-
883+
885884
cat(
886885
if (isTRUE(give.head)) {
887886
if (length(object) == 0L && is.null(dim(object))) {
@@ -891,14 +890,13 @@ str.integer64 = function(object, vec.len=strO$vec.len, give.head=TRUE, give.leng
891890
"integer64 ",
892891
if (length(object) > 1L && is.null(dim(object))) {
893892
if (isTRUE(give.length)) paste0("[1:", n, "] ") else " "
894-
} else if (!is.null(dim(object))) {
895-
dimO = dim(object)
896-
if (prod(dimO) != n)
897-
stop(gettextf("dims [product %d] do not match the length of object [%d]", prod(dimO), n, domain="R"))
898-
if (length(dimO) == 1L) {
893+
} else if (!is.null(obj_dim <- dim(object))) {
894+
if (prod(obj_dim) != n)
895+
stop(gettextf("dims [product %d] do not match the length of object [%d]", prod(obj_dim), n, domain="R"), domain=NA)
896+
if (length(obj_dim) == 1L) {
899897
paste0("[", n, "(1d)] ")
900898
} else {
901-
paste0("[", paste(vapply(dimO, function(el) {if (el < 2L) as.character(el) else paste0("1:", el)}, ""), collapse = ", "), "] ")
899+
paste0("[", toString(vapply(obj_dim, function(el) if (el < 2L) as.character(el) else paste0("1:", el), "")), "] ")
902900
}
903901
}
904902
)
@@ -1189,6 +1187,32 @@ seq.integer64 = function(from=NULL, to=NULL, by=NULL, length.out=NULL, along.wit
11891187
ret
11901188
}
11911189

1190+
1191+
# helper for determining the target class for Ops methods
1192+
target_class_for_Ops = function(e1, e2) {
1193+
if(missing(e2)) {
1194+
if (!is.numeric(unclass(e1)) && !is.logical(e1) && !is.complex(e1))
1195+
stop(errorCondition(gettext("non-numeric argument to mathematical function", domain = "R"), call=sys.call(sys.nframe() - 1L)))
1196+
1197+
if (is.complex(e1)) {
1198+
"complex"
1199+
} else {
1200+
"integer64"
1201+
}
1202+
} else {
1203+
if (!is.numeric(unclass(e1)) && !is.logical(e1) && !is.complex(e1))
1204+
stop(errorCondition(gettext("non-numeric argument to binary operator", domain = "R"), call=sys.call(sys.nframe() - 1L)))
1205+
if (!is.numeric(unclass(e2)) && !is.logical(e2) && !is.complex(e2))
1206+
stop(errorCondition(gettext("non-numeric argument to binary operator", domain = "R"), call=sys.call(sys.nframe() - 1L)))
1207+
1208+
if (is.complex(e1) || is.complex(e2)) {
1209+
"complex"
1210+
} else {
1211+
"integer64"
1212+
}
1213+
}
1214+
}
1215+
11921216
#' @rdname xor.integer64
11931217
#' @export
11941218
`+.integer64` <- function(e1, e2) {

R/matrix64.R

Lines changed: 105 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -19,64 +19,136 @@
1919
#' @param x An array of integer64 numbers.
2020
#' @param na.rm,dims Same interpretation as in [colSums()].
2121
#' @param ... Passed on to subsequent methods.
22+
#' @param data,nrow,ncol,byrow,dimnames,dim Arguments for `matrix()` and `array()`.
2223
#' @examples
23-
#' A = as.integer64(1:6)
24-
#' dim(A) = 3:2
24+
#' A = matrix(as.integer64(1:6), 3)
2525
#'
2626
#' colSums(A)
2727
#' rowSums(A)
2828
#' aperm(A, 2:1)
2929
#' @name matrix64
3030
NULL
3131

32-
#' @rdname matrix64
33-
#' @export
34-
colSums = function(x, na.rm=FALSE, dims=1L) UseMethod("colSums")
35-
#' @rdname matrix64
36-
#' @export
37-
colSums.default = function(x, na.rm=FALSE, dims=1L) base::colSums(x, na.rm, dims)
3832

3933
#' @rdname matrix64
40-
#' @export
41-
colSums.integer64 = function(x, na.rm=FALSE, dims=1L) {
42-
n_dim = length(dim(x))
43-
stopifnot(
44-
`dims= should be a length-1 integer between 1 and length(dim(x))-1L` =
45-
length(dims) == 1L && dims > 0L && dims < n_dim
46-
)
47-
MARGIN = tail(seq_len(n_dim), -dims)
48-
ret = apply(x, MARGIN, sum, na.rm = na.rm)
49-
class(ret) = "integer64"
34+
#' @exportS3Method matrix integer64
35+
matrix.integer64 = function(data=NA_integer64_, ...) {
36+
if (!length(data)) data = NA_integer64_
37+
ret = withCallingHandlers_and_choose_call(
38+
base::matrix(data=data, ...),
39+
c("matrix", "matrix.integer64")
40+
)
41+
class(ret) = class(data)
5042
ret
5143
}
5244

5345
#' @rdname matrix64
54-
#' @export
55-
rowSums = function(x, na.rm=FALSE, dims=1L) UseMethod("rowSums")
46+
#' @exportS3Method array integer64
47+
array.integer64 = function(data=NA_integer64_, ...) {
48+
if (!length(data)) data = NA_integer64_
49+
ret = withCallingHandlers_and_choose_call(
50+
base::array(data=data, ...),
51+
c("array", "array.integer64")
52+
)
53+
class(ret) = class(data)
54+
ret
55+
}
56+
5657
#' @rdname matrix64
5758
#' @export
58-
rowSums.default = function(x, na.rm=FALSE, dims=1L) base::rowSums(x, na.rm, dims)
59+
colSums.integer64 = function(x, na.rm=FALSE, dims=1L) {
60+
dn = dim(x)
61+
if (!is.array(x) || length(dn) < 2L)
62+
stop(errorCondition(gettext("'x' must be an array of at least two dimensions", domain="R-base"), call=choose_sys_call(c("colSums", "colSums.integer64"))))
63+
if (length(dims) != 1L || dims < 1L || dims > length(dn) - 1L)
64+
stop(errorCondition(gettext("invalid 'dims'", domain="R-base"), call=choose_sys_call(c("colSums", "colSums.integer64"))))
65+
66+
ret = apply(x, seq_along(dn)[-seq_len(dims)], sum, na.rm=na.rm)
67+
class(ret) = class(x)
68+
ret
69+
}
5970

6071
#' @rdname matrix64
6172
#' @export
6273
rowSums.integer64 = function(x, na.rm=FALSE, dims=1L) {
63-
n_dim = length(dim(x))
64-
stopifnot(
65-
`dims= should be a length-1 integer between 1 and length(dim(x))-1L` =
66-
length(dims) == 1L && dims > 0L && dims < n_dim
67-
)
68-
MARGIN = seq_len(dims)
69-
ret = apply(x, MARGIN, sum, na.rm = na.rm)
70-
class(ret) = "integer64"
74+
dn = dim(x)
75+
if (!is.array(x) || length(dn) < 2L)
76+
stop(errorCondition(gettext("'x' must be an array of at least two dimensions", domain="R-base"), call=choose_sys_call(c("rowSums", "rowSums.integer64"))))
77+
if (length(dims) != 1L || dims < 1L || dims > length(dn) - 1L)
78+
stop(errorCondition(gettext("invalid 'dims'", domain="R-base"), call=choose_sys_call(c("rowSums", "rowSums.integer64"))))
79+
80+
ret = apply(x, seq_len(dims), sum, na.rm=na.rm)
81+
class(ret) = class(x)
7182
ret
7283
}
7384

7485
#' @rdname matrix64
7586
#' @param a,perm Passed on to [aperm()].
76-
#' @export
87+
#' @exportS3Method base::aperm integer64
7788
aperm.integer64 = function(a, perm, ...) {
78-
class(a) = minusclass(class(a), "integer64")
79-
ret = aperm(a, perm, ...)
80-
class(ret) = plusclass(class(a), "integer64")
89+
ret = NextMethod()
90+
class(ret) = class(a)
8191
ret
8292
}
93+
94+
#' @exportS3Method base::`%*%` integer64
95+
`%*%.integer64` = function(x, y) {
96+
if (!is.integer64(x) && !is.integer64(y))
97+
return(x%*%y)
98+
99+
target_class = target_class_for_Ops(x, y)
100+
if (target_class != "integer64") {
101+
if (is.integer64(x)) {
102+
for (cc in class(y)) {
103+
f = getS3method("%*%", cc, optional=TRUE)
104+
if (!is.null(f))
105+
return(f(.as_double_integer64(x, keep.attributes=TRUE), y))
106+
}
107+
x = .as_double_integer64(x, keep.attributes=TRUE)
108+
} else {
109+
y = .as_double_integer64(y, keep.attributes=TRUE)
110+
}
111+
return(x%*%y)
112+
}
113+
114+
dx = dim(x)
115+
dy = dim(y)
116+
if (length(dx) > 2L || length(dy) > 2L)
117+
stop("non-conformable arguments", domain="R")
118+
if (length(dx) <= 1L && length(dy) <= 1L) {
119+
dx = c(1L, length(x))
120+
if (length(x) == length(y)) {
121+
dy = c(length(y), 1L)
122+
} else {
123+
dy = c(1L, length(y))
124+
}
125+
}
126+
if (length(dx) <= 1L)
127+
dx = c(1L, dy[1L])
128+
if (length(dy) <= 1L)
129+
dy = c(dx[2L], 1L)
130+
if (dx[2L] != dy[1L])
131+
stop("non-conformable arguments", domain="R")
132+
dim(x) = dx
133+
dim(y) = dy
134+
135+
if (is.double(x)) {
136+
ret = .Call(C_matmult_double_integer64, x, structure(as.integer64(y), dim=dy), double(dx[1L]*dy[2L]))
137+
} else if (is.double(y)) {
138+
ret = .Call(C_matmult_integer64_double, structure(as.integer64(x), dim=dx), y, double(dx[1L]*dy[2L]))
139+
} else {
140+
ret = .Call(C_matmult_integer64_integer64, structure(as.integer64(x), dim=dx), structure(as.integer64(y), dim=dy), double(dx[1L]*dy[2L]))
141+
}
142+
dim(ret) = c(dx[1L], dy[2L])
143+
oldClass(ret) = "integer64"
144+
ret
145+
}
146+
147+
#' @exportS3Method base::as.matrix integer64
148+
as.matrix.integer64 = function(x, ...) {
149+
if (is.matrix(x)) {
150+
x
151+
} else {
152+
array(x, c(length(x), 1L), if (!is.null(names(x))) list(names(x), NULL))
153+
}
154+
}

R/patch64.R

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,48 @@ order <- function(...) UseMethod("order")
130130
#' @rdname bit64S3
131131
#' @export
132132
order.default <- function(...) base::order(...)
133+
134+
#' @rdname matrix64
135+
#' @export matrix
136+
matrix = function(data=NA, nrow=1L, ncol=1L, byrow=FALSE, dimnames=NULL) UseMethod("matrix")
137+
#' @exportS3Method matrix default
138+
matrix.default = function(...) {
139+
withCallingHandlers_and_choose_call(
140+
base::matrix(...),
141+
c("matrix", "matrix.default")
142+
)
143+
}
144+
145+
#' @rdname matrix64
146+
#' @export array
147+
array = function(data=NA, dim=length(data), dimnames=NULL) UseMethod("array")
148+
#' @exportS3Method array default
149+
array.default = function(...) {
150+
withCallingHandlers_and_choose_call(
151+
base::array(...),
152+
c("array", "array.default")
153+
)
154+
}
155+
156+
#' @rdname matrix64
157+
#' @export
158+
colSums = function(x, na.rm=FALSE, dims=1L) UseMethod("colSums")
159+
#' @rdname matrix64
160+
#' @export
161+
colSums.default = function(x, na.rm=FALSE, dims=1L)
162+
withCallingHandlers_and_choose_call(
163+
base::colSums(x=x, na.rm=na.rm, dims=dims),
164+
c("colSums", "colSums.default")
165+
)
166+
167+
#' @rdname matrix64
168+
#' @export
169+
rowSums = function(x, na.rm=FALSE, dims=1L) UseMethod("rowSums")
170+
#' @rdname matrix64
171+
#' @export
172+
rowSums.default = function(x, na.rm=FALSE, dims=1L)
173+
withCallingHandlers_and_choose_call(
174+
base::rowSums(x=x, na.rm=na.rm, dims=dims),
175+
c("rowSums", "rowSums.default")
176+
)
177+

0 commit comments

Comments
 (0)