diff --git a/.github/workflows/test-all.yaml b/.github/workflows/test-all.yaml index c0428946..edd5c2ac 100644 --- a/.github/workflows/test-all.yaml +++ b/.github/workflows/test-all.yaml @@ -12,6 +12,10 @@ on: description: 'Run nCompile tests' required: false default: 'yes' + run_nCompile_features: + description: 'Run nCompile feature tests' + required: false + default: 'yes' run_nClass: description: 'Run nClass tests' required: false @@ -52,6 +56,29 @@ jobs: testthat::test_dir("nCompiler/tests/testthat/specificOp_tests", reporter = "summary") shell: Rscript {0} + test-nCompile-features: + runs-on: ubuntu-latest + container: + image: rocker/r2u:latest + if: github.event.inputs.run_tests == 'yes' || github.event.inputs.run_nCompile_features == 'yes' + steps: + - uses: actions/checkout@v3 + - name: SessionInfo + run: R -q -e 'sessionInfo()' + - name: Package Dependencies + run: R -q -e 'remotes::install_deps("nCompiler", dependencies=TRUE)' + - name: Install inline + run: R -q -e 'remotes::install_cran("inline")' + - name: Build Package + run: | + R CMD build nCompiler + R CMD INSTALL --install-tests nCompiler_*.tar.gz + - name: Run nCompile and other tests + run: | + library(nCompiler) + testthat::test_dir("nCompiler/tests/testthat/predefined_tests", reporter = "summary") + shell: Rscript {0} + test-nClass: runs-on: ubuntu-latest container: diff --git a/archived_tests/tests_to_be_fixed/test-predefined-NOTWORKING.R_noscan b/archived_tests/tests_to_be_fixed/test-predefined-NOTWORKING.R_noscan deleted file mode 100644 index 034bc3b7..00000000 --- a/archived_tests/tests_to_be_fixed/test-predefined-NOTWORKING.R_noscan +++ /dev/null @@ -1,790 +0,0 @@ -# See generatePredefinedCpp.R for instructions on adding predefined nClasses to the package. -# This file is not in the R source directory. It is in the GitHub repository -# above the package directory. - -# Note: doing nCompile with generate_predefined = TRUE -# may no longer result in a useable object because it will be missing interface calls. -# These are omitted because we don't want them when copying over to package source (inst). -# Hence one can check a predefined nClass either by setting NFinternals(predefined) <- FALSE -# or by including something else in the nCompile that is not predefined and hence -# will trigger inclusion of the interface calls. Tests here take the latter approach. -# -library(nCompiler) - -foo <- nFunction( - fun=function(x) {return(x+1)}, - argTypes = list(x='numericScalar'), - returnType='numericScalar', - name="myfoofun" -) -cfoo <- nCompile(foo) -cfoo(2) - -foo <- nFunction( - fun=function(x) {return(x+1)}, - argTypes = list(x='numericScalar'), - returnType='numericScalar', - name="myfoofun", - predefined=file.path(tempdir(), 'foo_fun_pre') -) -cfoo <- nCompile(foo, control=list(generate_predefined=TRUE)) -cfoo(2) - -foo <- nFunction( - fun=function(x) {return(x+1)}, - argTypes = list(x='numericScalar'), - returnType='numericScalar', - name="myfoofun", - predefined=file.path(tempdir(), 'foo_fun_pre') -) -cfoo <- nCompile(foo) -cfoo(2) - -foo <- nFunction( - fun=function(x) {return(x+1)}, - argTypes = list(x='numericScalar'), - returnType='numericScalar', - name="myfoofun", - predefined=file.path(tempdir(), 'foo_fun_pre') -) -cfoo <- nCompile(foo, control=list(generate_predefined=TRUE), package = TRUE) -cfoo(2) - -foo <- nFunction( - fun=function(x) {return(x+1)}, - argTypes = list(x='numericScalar'), - returnType='numericScalar', - name="myfoofun", - predefined=file.path(tempdir(), 'foo_fun_pre') -) -cfoo <- nCompile(foo, package=TRUE) -cfoo(2) - -### - -foo <- nClass(Cpublic = list( - bar=nFunction( - fun=function(x){return(x+1)}, - argTypes = list(x='numericScalar'), - returnType = 'numericScalar')), - classname = "my_foo" - ) -# Generate new code. -ctest <- nCompile(foo) -obj <- ctest$new() -obj$bar(2) - -test_that("test_predefined class works", -{ - # dummy nClass to include in nCompile so that interface calls are included. - foo <- nClass(Cpublic = list( - bar=nFunction( - fun=function(x){return(x+1)}, - argTypes = list(x='numericScalar'), - returnType = 'numericScalar')), - classname = "foo_test_predefined", - predefined = file.path(tempdir(), "test_predefined_dir") - ) - # Generate new code. - ctest <- nCompile(foo, control = list(generate_predefined = TRUE)) - obj <- ctest$new() - obj$bar(2) - - ctest <- nCompile(foo, control = list(generate_predefined = TRUE), package=TRUE) - obj <- ctest$new() - obj$bar(2) - - # Use existing (predefined) code - ctest <- nCompile(foo, control = list(generate_predefined = FALSE)) - obj <- ctest$new() - obj$bar(2) - - ctest <- nCompile(foo, control = list(generate_predefined = FALSE), package=TRUE) - obj <- ctest$new() - obj$bar(2) - -# Below here is old - ctest <- ctest$test_predefined - obj <- ctest$new() - obj$a <- 1.2 - expect_equal(obj$a, 1.2) - # uncompiled - obj <- test_predefined$new() - obj$a <- 1.2 - expect_equal(obj$a, 1.2) - - # Use existing (predefined) code - ctest <- nCompile(foo, control = list(generate_predefined = FALSE)) - obj <- ctest$new() - obj$bar(2) - obj$a <- 1.2 - expect_equal(obj$a, 1.2) - - # Default to existing (predefined) code - # We could add a test to confirm that it is actually the predefined code that is used. - ctest <- nCompile(test_predefined) - obj <- ctest$new() - obj$a <- 1.2 - expect_equal(obj$a, 1.2) - - # Use in a class as member data - nc1 <- nClass( - classname = "nc1", - Cpublic = list( - x = 'test_predefined' - )) - Cnc1 <- nCompile(nc1, test_predefined) - obj <- Cnc1$nc1$new() - obj$x <- Cnc1$test_predefined$new() - obj$x$a <- 1.2 - expect_equal(obj$x$a, 1.2) - #uncompiled - obj <- nc1$new() - obj$x <- test_predefined$new() - obj$x$a <- 1.2 - expect_equal(obj$x$a, 1.2) - - # Use in a class as return object - nc1 <- nClass( - classname = "nc1", - Cpublic = list( - foo = nFunction( - fun = function() { - return(test_predefined$new()) - }, - returnType = 'test_predefined' - ) - )) - Cnc1 <- nCompile(nc1, test_predefined) - obj <- Cnc1$nc1$new() - x <- obj$foo() - x$a <- 1.2 - expect_equal(x$a, 1.2) - #uncompiled - obj <- nc1$new() - x <- obj$foo() - x$a <- 1.2 - expect_equal(x$a, 1.2) - - # Use in a class as input object - nc1 <- nClass( - classname = "nc1", - Cpublic = list( - foo = nFunction( - fun = function(x) { - x$a <- x$a + 1 - return(x) - }, - argTypes = list(x = 'test_predefined'), - returnType = 'test_predefined' - ) - )) - Cnc1 <- nCompile(nc1, test_predefined) - obj <- Cnc1$nc1$new() - x <- Cnc1$test_predefined$new() - x$a <- 1.2 - x2 <- obj$foo(x) - expect_equal(x2$a, 2.2) - #uncompiled - obj <- nc1$new() - x <- test_predefined$new() - x$a <- 1.2 - x2 <- obj$foo(x) - expect_equal(x2$a, 2.2) - - # Use in a function - f1 <- nFunction( - fun = function(x) { - x$a <- x$a + 1 - return(x) - }, - argTypes = list(x = 'test_predefined'), - returnType = 'test_predefined' - ) - Cf1 <- nCompile(f1, test_predefined) - x <- Cnc1$test_predefined$new() - x$a <- 1.2 - x2 <- Cf1$f1(x) - expect_equal(x2$a, 2.2) - # uncompiled - x <- test_predefined$new() - x$a <- 1.2 - x2 <- f1(x) - expect_equal(x2$a, 2.2) - - # Call C++ test function that relies on correctly reading the header - get_test_predefined <- nFunction( - fun = function() { - cppLiteral("std::shared_ptr A = make_test_predefined();") - cppLiteral("return(A);") - }, - returnType = 'test_predefined') - C_gtp <- nCompile(get_test_predefined, test_predefined) - x <- C_gtp$get_test_predefined() - x$a <- 1.2 - expect_equal(x$a, 1.2) -}) - -test_that("predefined derivClass class works", -{ - # dummy to include interface calls - foo <- nClass(Cpublic = list(foo=nFunction(fun=function(){}))) - # Generate new code. - ctest <- nCompile(derivClass, foo, control = list(generate_predefined = TRUE)) - ctest <- ctest$derivClass - # using the object will not work because the interface calls are now omitted - obj <- ctest$new() - obj$gradient <- matrix(1:4, nrow = 2) - expect_equal(obj$gradient, matrix(1:4, nrow = 2)) - # uncompiled - obj <- derivClass$new() - obj$gradient <- matrix(1:4, nrow = 2) - expect_equal(obj$gradient, matrix(1:4, nrow = 2)) - - # Use existing (predefined) code - ctest <- nCompile(derivClass, control = list(generate_predefined = FALSE)) - obj <- ctest$new() - obj$gradient <- matrix(1:4, nrow = 2) - expect_equal(obj$gradient, matrix(1:4, nrow = 2)) - - # Default to existing (predefined) code - # We could add a test to confirm that it is actually the predefined code that is used. - ctest <- nCompile(derivClass) - obj <- ctest$new() - obj$gradient <- matrix(1:4, nrow = 2) - expect_equal(obj$gradient, matrix(1:4, nrow = 2)) - - # Use in a class as member data - nc1 <- nClass( - classname = "nc1", - Cpublic = list( - x = 'derivClass' - )) - Cnc1 <- nCompile(nc1, derivClass) - obj <- Cnc1$nc1$new() - obj$x <- Cnc1$derivClass$new() - obj$x$gradient <- matrix(1:4, nrow = 2) - expect_equal(obj$x$gradient, matrix(1:4, nrow = 2)) - #uncompiled - obj <- nc1$new() - obj$x <- derivClass$new() - obj$x$gradient <- matrix(1:4, nrow = 2) - expect_equal(obj$x$gradient, matrix(1:4, nrow = 2)) - - # Use in a class as return object - nc1 <- nClass( - classname = "nc1", - Cpublic = list( - foo = nFunction( - fun = function(x = 'numericVector') { - ans <- derivClass$new() - ans$value <- x - return(ans) - }, - returnType = 'derivClass' - ) - )) - Cnc1 <- nCompile(nc1, derivClass) - obj <- Cnc1$nc1$new() - x <- obj$foo() - x$gradient <- matrix(1:4, nrow = 2) - expect_equal(x$gradient, matrix(1:4, nrow = 2)) - #uncompiled - obj <- nc1$new() - x <- obj$foo() - x$gradient <- matrix(1:4, nrow = 2) - expect_equal(x$gradient, matrix(1:4, nrow = 2)) - - # Use in a class as input object - nc1 <- nClass( - classname = "nc1", - Cpublic = list( - foo = nFunction( - fun = function(x) { - x$gradient <- x$gradient + 1 - return(x) - }, - argTypes = list(x = 'derivClass'), - returnType = 'derivClass' - ) - )) - Cnc1 <- nCompile(nc1, derivClass) - obj <- Cnc1$nc1$new() - x <- Cnc1$derivClass$new() - x$gradient <- matrix(1:4, nrow = 2) - x2 <- obj$foo(x) - expect_equal(x2$gradient, matrix(2:5, nrow = 2)) - #uncompiled - obj <- nc1$new() - x <- derivClass$new() - x$gradient <- matrix(1:4, nrow = 2) - x2 <- obj$foo(x) - expect_equal(x2$gradient, matrix(2:5, nrow = 2)) - - # Use in a function - f1 <- nFunction( - fun = function(x) { - x$gradient <- x$gradient + 1 - return(x) - }, - argTypes = list(x = 'derivClass'), - returnType = 'derivClass' - ) - Cf1 <- nCompile(f1, derivClass) - x <- Cnc1$derivClass$new() - x$gradient <- matrix(1:4, nrow = 2) - x2 <- obj$foo(x) - expect_equal(x2$gradient, matrix(2:5, nrow = 2)) - # uncompiled - x <- derivClass$new() - x$gradient <- matrix(1:4, nrow = 2) - x2 <- obj$foo(x) - expect_equal(x2$gradient, matrix(2:5, nrow = 2)) - - # Call C++ test function that relies on correctly reading the header - get_derivClass <- nFunction( - fun = function() { - cppLiteral("std::shared_ptr A = make_derivClass();") - cppLiteral("return(A);") - }, - returnType = 'derivClass') - C_get <- nCompile(get_derivClass, derivClass) - x <- C_get$get_derivClass() - x$gradient <- matrix(1:4, nrow = 2) - expect_equal(x$gradient, matrix(1:4, nrow = 2)) -}) - -test_that("predefined EigenDecomp class works", -{ - # dummy to include interface calls - foo <- nClass(Cpublic = list(foo=nFunction(fun=function(){}))) - # Generate new code. - ctest <- nCompile(EigenDecomp, foo, control = list(generate_predefined = TRUE)) - ctest <- ctest$EigenDecomp - obj <- ctest$new() - obj$vectors <- matrix(1:4, nrow = 2) - expect_equal(obj$vectors, matrix(1:4, nrow = 2)) - # uncompiled - obj <- EigenDecomp$new() - obj$vectors <- matrix(1:4, nrow = 2) - expect_equal(obj$vectors, matrix(1:4, nrow = 2)) - - ## STOPPED HERE - SOMETHING WRONG. - - # Use existing (predefined) code - ctest <- nCompile(EigenDecomp, control = list(generate_predefined = FALSE)) - obj <- ctest$new() - obj$vectors <- matrix(1:4, nrow = 2) - expect_equal(obj$vectors, matrix(1:4, nrow = 2)) - - # Default to existing (predefined) code - # We could add a test to confirm that it is actually the predefined code that is used. - ctest <- nCompile(EigenDecomp) - obj <- ctest$new() - obj$vectors <- matrix(1:4, nrow = 2) - expect_equal(obj$vectors, matrix(1:4, nrow = 2)) - - # Use in a class as member data - nc1 <- nClass( - classname = "nc1", - Cpublic = list( - x = 'EigenDecomp' - )) - Cnc1 <- nCompile(nc1, EigenDecomp) - obj <- Cnc1$nc1$new() - obj$x <- Cnc1$EigenDecomp$new() - obj$x$vectors <- matrix(1:4, nrow = 2) - expect_equal(obj$x$vectors, matrix(1:4, nrow = 2)) - #uncompiled - obj <- nc1$new() - obj$x <- EigenDecomp$new() - obj$x$vectors <- matrix(1:4, nrow = 2) - expect_equal(obj$x$vectors, matrix(1:4, nrow = 2)) - - # Use in a class as return object - nc1 <- nClass( - classname = "nc1", - Cpublic = list( - foo = nFunction( - fun = function() { - return(EigenDecomp$new()) - }, - returnType = 'EigenDecomp' - ) - )) - Cnc1 <- nCompile(nc1, EigenDecomp) - obj <- Cnc1$nc1$new() - x <- obj$foo() - x$vectors <- matrix(1:4, nrow = 2) - expect_equal(x$vectors, matrix(1:4, nrow = 2)) - #uncompiled - obj <- nc1$new() - x <- obj$foo() - x$vectors <- matrix(1:4, nrow = 2) - expect_equal(x$vectors, matrix(1:4, nrow = 2)) - - # Use in a class as input object - nc1 <- nClass( - classname = "nc1", - Cpublic = list( - foo = nFunction( - fun = function(x) { - x$vectors <- x$vectors + 1 - return(x) - }, - argTypes = list(x = 'EigenDecomp'), - returnType = 'EigenDecomp' - ) - )) - Cnc1 <- nCompile(nc1, EigenDecomp) - obj <- Cnc1$nc1$new() - x <- Cnc1$EigenDecomp$new() - x$vectors <- matrix(1:4, nrow = 2) - x2 <- obj$foo(x) - expect_equal(x2$vectors, matrix(2:5, nrow = 2)) - #uncompiled - obj <- nc1$new() - x <- EigenDecomp$new() - x$vectors <- matrix(1:4, nrow = 2) - x2 <- obj$foo(x) - expect_equal(x2$vectors, matrix(2:5, nrow = 2)) - - # Use in a function - f1 <- nFunction( - fun = function(x) { - x$vectors <- x$vectors + 1 - return(x) - }, - argTypes = list(x = 'EigenDecomp'), - returnType = 'EigenDecomp' - ) - Cf1 <- nCompile(f1, EigenDecomp) - x <- Cnc1$EigenDecomp$new() - x$vectors <- matrix(1:4, nrow = 2) - x2 <- obj$foo(x) - expect_equal(x2$vectors, matrix(2:5, nrow = 2)) - # uncompiled - x <- EigenDecomp$new() - x$vectors <- matrix(1:4, nrow = 2) - x2 <- obj$foo(x) - expect_equal(x2$vectors, matrix(2:5, nrow = 2)) - - # Call C++ test function that relies on correctly reading the header - get_EigenDecomp <- nFunction( - fun = function() { - cppLiteral("std::shared_ptr A = make_EigenDecomp();") - cppLiteral("return(A);") - }, - returnType = 'EigenDecomp') - C_get <- nCompile(get_EigenDecomp, EigenDecomp) - x <- C_get$get_EigenDecomp() - x$vectors <- matrix(1:4, nrow = 2) - expect_equal(x$vectors, matrix(1:4, nrow = 2)) - - doEigen <- nFunction( - fun = function(x, symmetric, valuesOnly) { - eigX <- nEigen(x, symmetric, valuesOnly) - return(eigX) - }, - argTypes = list(x = 'numericMatrix', symmetric = 'logicalScalar', valuesOnly = 'logicalScalar'), - returnType = 'EigenDecomp') - comp <- nCompile(doEigen, EigenDecomp) - - # test non-symmetric eigen decomposition - x <- matrix(as.numeric(c(2, 4, 1, 3)), nrow = 2) - Cres <- comp$doEigen(x, FALSE, FALSE) - Rres <- eigen(x) - expect_equal(as.numeric(Cres$values), as.numeric(Rres$values)) - for(i in 1:nrow(x)) { - # allow Cvec == Rvec or Cvec == -Rvec - Cvec = as.numeric(Cres$vectors[,i]) - Rvec = as.numeric(Rres$vectors[,i]) - expect_true(any( - isTRUE(all.equal(Cvec, Rvec)), - isTRUE(all.equal(Cvec, -Rvec)) - )) - } - - # test values only - x <- matrix(as.numeric(c(2, 4, 1, 3)), nrow = 2) - Cres <- comp$doEigen(x = x, symmetric = FALSE, valuesOnly = TRUE) - Rres <- eigen(x, only.values = TRUE) - expect_equal(as.numeric(Cres$values), as.numeric(Rres$values)) - expect_equal(Cres$vectors, matrix(data = 0, nrow = 0, ncol = 0)) - - # test symmetric eigen decomposition - xsym <- x + t(x) - Cres <- comp$doEigen(x = xsym, symmetric = TRUE, valuesOnly = FALSE) - Rres <- eigen(xsym) - expect_equal(as.numeric(Cres$values), as.numeric(Rres$values)) - for(i in 1:nrow(x)) { - # allow Cvec == Rvec or Cvec == -Rvec - Cvec = as.numeric(Cres$vectors[,i]) - Rvec = as.numeric(Rres$vectors[,i]) - expect_true(any( - isTRUE(all.equal(Cvec, Rvec)), - isTRUE(all.equal(Cvec, -Rvec)) - )) - } - - # verify nEigen works with tensor expression inputs - doEigenOp <- nFunction( - fun = function(x, y, z, symmetric, valuesOnly) { - eigX <- nEigen(x * y + z, symmetric, valuesOnly) - return(eigX) - }, - argTypes = list(x = 'numericMatrix', - y = 'numericMatrix', - z = 'numericMatrix', - symmetric = 'logicalScalar', - valuesOnly = 'logicalScalar'), - returnType = 'EigenDecomp' - ) - comp <- nCompile(doEigenOp, EigenDecomp) - y <- matrix(as.numeric(c(4, 2, 1, 3)), nrow = 2) - z <- matrix(rnorm(n = 4), nrow = 2) - Cres <- comp$doEigenOp(x, y, z, FALSE, FALSE) - Rres <- eigen(x * y + z) - expect_equal(as.numeric(Cres$values), as.numeric(Rres$values)) - for(i in 1:nrow(x)) { - # allow Cvec == Rvec or Cvec == -Rvec - Cvec = as.numeric(Cres$vectors[,i]) - Rvec = as.numeric(Rres$vectors[,i]) - expect_true(any( - isTRUE(all.equal(Cvec, Rvec)), - isTRUE(all.equal(Cvec, -Rvec)) - )) - } -}) - -test_that("predefined SVDDecomp class works", { - # dummy to include interface calls - foo <- nClass(Cpublic = list(foo=nFunction(fun=function(){}))) - # Generate new code. - ctest <- nCompile(SVDDecomp, foo, control = list(generate_predefined = TRUE)) - ctest <- ctest$SVDDecomp - obj <- ctest$new() - obj$v <- matrix(1:4, nrow = 2) - expect_equal(obj$v, matrix(1:4, nrow = 2)) - # uncompiled - obj <- SVDDecomp$new() - obj$v <- matrix(1:4, nrow = 2) - expect_equal(obj$v, matrix(1:4, nrow = 2)) - - # Use existing (predefined) code - ctest <- nCompile(SVDDecomp, control = list(generate_predefined = FALSE)) - obj <- ctest$new() - obj$v <- matrix(1:4, nrow = 2) - expect_equal(obj$v, matrix(1:4, nrow = 2)) - - # Default to existing (predefined) code - # We could add a test to confirm that it is actually the predefined code that is used. - ctest <- nCompile(SVDDecomp) - obj <- ctest$new() - obj$v <- matrix(1:4, nrow = 2) - expect_equal(obj$v, matrix(1:4, nrow = 2)) - - # Use in a class as member data - nc1 <- nClass( - classname = "nc1", - Cpublic = list( - x = 'SVDDecomp' - )) - Cnc1 <- nCompile(nc1, SVDDecomp) - obj <- Cnc1$nc1$new() - obj$x <- Cnc1$SVDDecomp$new() - obj$x$v <- matrix(1:4, nrow = 2) - expect_equal(obj$x$v, matrix(1:4, nrow = 2)) - #uncompiled - obj <- nc1$new() - obj$x <- SVDDecomp$new() - obj$x$v <- matrix(1:4, nrow = 2) - expect_equal(obj$x$v, matrix(1:4, nrow = 2)) - - # Use in a class as return object - nc1 <- nClass( - classname = "nc1", - Cpublic = list( - foo = nFunction( - fun = function() { - return(SVDDecomp$new()) - }, - returnType = 'SVDDecomp' - ) - )) - Cnc1 <- nCompile(nc1, SVDDecomp) - obj <- Cnc1$nc1$new() - x <- obj$foo() - x$v <- matrix(1:4, nrow = 2) - expect_equal(x$v, matrix(1:4, nrow = 2)) - #uncompiled - obj <- nc1$new() - x <- obj$foo() - x$v <- matrix(1:4, nrow = 2) - expect_equal(x$v, matrix(1:4, nrow = 2)) - - # Use in a class as input object - nc1 <- nClass( - classname = "nc1", - Cpublic = list( - foo = nFunction( - fun = function(x) { - x$v <- x$v + 1 - return(x) - }, - argTypes = list(x = 'SVDDecomp'), - returnType = 'SVDDecomp' - ) - )) - Cnc1 <- nCompile(nc1, SVDDecomp) - obj <- Cnc1$nc1$new() - x <- Cnc1$SVDDecomp$new() - x$v <- matrix(1:4, nrow = 2) - x2 <- obj$foo(x) - expect_equal(x2$v, matrix(2:5, nrow = 2)) - #uncompiled - obj <- nc1$new() - x <- SVDDecomp$new() - x$v <- matrix(1:4, nrow = 2) - x2 <- obj$foo(x) - expect_equal(x2$v, matrix(2:5, nrow = 2)) - - # Use in a function - f1 <- nFunction( - fun = function(x) { - x$v <- x$v + 1 - return(x) - }, - argTypes = list(x = 'SVDDecomp'), - returnType = 'SVDDecomp' - ) - Cf1 <- nCompile(f1, SVDDecomp) - x <- Cnc1$SVDDecomp$new() - x$v <- matrix(1:4, nrow = 2) - x2 <- obj$foo(x) - expect_equal(x2$v, matrix(2:5, nrow = 2)) - # uncompiled - x <- SVDDecomp$new() - x$v <- matrix(1:4, nrow = 2) - x2 <- obj$foo(x) - expect_equal(x2$v, matrix(2:5, nrow = 2)) - - # Call C++ test function that relies on correctly reading the header - get_SVDDecomp <- nFunction( - fun = function() { - cppLiteral("std::shared_ptr A = make_SVDDecomp();") - cppLiteral("return(A);") - }, - returnType = 'SVDDecomp') - C_get <- nCompile(get_SVDDecomp, SVDDecomp) - x <- C_get$get_SVDDecomp() - x$v <- matrix(1:4, nrow = 2) - expect_equal(x$v, matrix(1:4, nrow = 2)) - - # update tests! - doSVD <- nFunction( - fun = function(x, vectors) { - svdX <- nSvd(x = x, vectors = vectors) - return(svdX) - }, - argTypes = list(x = 'numericMatrix', vectors = 'integer'), - returnType = 'SVDDecomp') - comp <- nCompile(doSVD, SVDDecomp) - - # test singular value decomposition: values only - x <- matrix(as.numeric(c(2, 4, 1, 3, 6, 5)), nrow = 2) - Cres <- comp$doSVD(x = x, vectors = 0) - Rres <- svd(x, nu = 0, nv = 0) - expect_equal(as.numeric(Cres$d), as.numeric(Rres$d)) - expect_equal(Cres$u, matrix(0, nrow = 0, ncol = 0)) - expect_equal(Cres$v, matrix(0, nrow = 0, ncol = 0)) - - # test singular value decomposition: thin - x <- matrix(as.numeric(c(2, 4, 1, 3, 6, 5)), nrow = 2) - Cres <- comp$doSVD(x = x, vectors = 1) - Rres <- svd(x) - expect_equal(as.numeric(Cres$d), as.numeric(Rres$d)) - for(i in 1:min(dim(x))) { - # allow Cvec == Rvec or Cvec == -Rvec for... - # left singular vectors - Cvec = as.numeric(Cres$u[,i]) - Rvec = as.numeric(Rres$u[,i]) - expect_true(any( - isTRUE(all.equal(Cvec, Rvec)), - isTRUE(all.equal(Cvec, -Rvec)) - )) - # right singular vectors - Cvec = as.numeric(Cres$v[,i]) - Rvec = as.numeric(Rres$v[,i]) - expect_true(any( - isTRUE(all.equal(Cvec, Rvec)), - isTRUE(all.equal(Cvec, -Rvec)) - )) - } - - # test singular value decomposition: full - x <- matrix(as.numeric(c(2, 4, 1, 3, 6, 5)), nrow = 2) - Cres <- comp$doSVD(x = x, vectors = 2) - Rres <- svd(x, nu = nrow(x), nv = ncol(x)) - expect_equal(as.numeric(Cres$d), as.numeric(Rres$d)) - for(i in 1:nrow(x)) { - # allow Cvec == Rvec or Cvec == -Rvec for... - # left singular vectors - Cvec = as.numeric(Cres$u[,i]) - Rvec = as.numeric(Rres$u[,i]) - expect_true(any( - isTRUE(all.equal(Cvec, Rvec)), - isTRUE(all.equal(Cvec, -Rvec)) - )) - } - for(i in 1:ncol(x)) { - # allow Cvec == Rvec or Cvec == -Rvec for... - # right singular vectors - Cvec = as.numeric(Cres$v[,i]) - Rvec = as.numeric(Rres$v[,i]) - expect_true(any( - isTRUE(all.equal(Cvec, Rvec)), - isTRUE(all.equal(Cvec, -Rvec)) - )) - } - - # verify nSvd works with tensor expression inputs - doSvdOp <- nFunction( - fun = function(x, y, z, vectors) { - svdX <- nSvd(x * y + z, vectors) - return(svdX) - }, - argTypes = list(x = 'numericMatrix', - y = 'numericMatrix', - z = 'numericMatrix', - vectors = 'integer'), - returnType = 'SVDDecomp' - ) - comp <- nCompile(doSvdOp, SVDDecomp) - y <- matrix(as.numeric(c(4, 2, 1, 3)), nrow = 2) - z <- matrix(rnorm(n = 4), nrow = 2) - Cres <- comp$doSvdOp(x[1:2, 1:2], y, z, vectors = 2) - Rres <- svd(x = x[1:2, 1:2] * y + z, nu = nrow(z),nv = ncol(z)) - expect_equal(as.numeric(Cres$d), as.numeric(Rres$d)) - for(i in 1:nrow(y)) { - # allow Cvec == Rvec or Cvec == -Rvec for... - # left singular vectors - Cvec = as.numeric(Cres$u[,i]) - Rvec = as.numeric(Rres$u[,i]) - expect_true(any( - isTRUE(all.equal(Cvec, Rvec)), - isTRUE(all.equal(Cvec, -Rvec)) - )) - } - for(i in 1:ncol(y)) { - # allow Cvec == Rvec or Cvec == -Rvec for... - # right singular vectors - Cvec = as.numeric(Cres$v[,i]) - Rvec = as.numeric(Rres$v[,i]) - expect_true(any( - isTRUE(all.equal(Cvec, Rvec)), - isTRUE(all.equal(Cvec, -Rvec)) - )) - } - -}) diff --git a/generatePredefinedCpp.R b/generatePredefinedCpp.R index f6c0b9e0..e69de29b 100644 --- a/generatePredefinedCpp.R +++ b/generatePredefinedCpp.R @@ -1,187 +0,0 @@ -## Newer predefined system (as of 3/15/22). -# How to to create a new predefined class: -# 1. Enter the class definition in a file in the R directory. -# Some are in zzz_NC_predefined.R but they can be anywhere after nClass has been defined. -# -# 2. Include a classname in the nClass call, so it is not randomly generated. -# Include a argument like predefined="my_predefined_local" in the nClass call, -# with a character value that is different from classname. -# Be sure the nClass is an exported R object in the package (usually). -# The class name will be used for the C++ code that will be generated each time you use the nClass. -# The predefined name will be used for permanent files of generated C++ that you will copy to package source code and modify. -# The example here will be my_predefined <- nClass(classname = "my_predefined", predefined = "my_predefined_pkg", ...) -# -# 3. build nCompiler again with the new nClass included. -# -# 4. Load nCompiler and do nCompile(my_predefined, control = list(generate_predefined = TRUE)). -# Be sure you know where code is being generated (default is tempdir(), can be changed by dir argument to nCompile). -# Note that the results from nCompile(my_predefined, control = list(generate_predefined = TRUE)) will not result in -# usable objects right in the current R session. That is because the generated code will lack the interface calls such as -# get_value, set_value, and call_method. The reason is that these must only be created once in any compilation, -# and when the predefined nClass is used later, it should not be the place where those functions are defined. -# If you want to test the predefined class, include some other nFunction or nClass in the call to nCompile. -# Since that will not be predefined, it will trigger inclusion of the interface functions. -# See test-predefined.R for examples. -# -# 5. Copy my_predefined.h (or my_predefined_c_.h) to my_predefined_pkg.h in package source code (i.e. inst/include/nCompiler). -# -# 6. Copy the cpp file, which will have an automatically generated name like "nCompiler_units_1.cpp", to -# my_predfined_pkg.cpp in the package source code. (Note these names match the predefined argument in the nClass class.) -# -# 7. (DEPRECATED - DO NOT DO THIS. LEAVING IT HERE FOR NOTES) -# In my_predefined.cpp, there will be a line like -# #include "my_predefined.h" -# Change that to -# #include "my_predefined_pkg.h" -# i.e. replace the classname with the predefined name. -# -# 8. In my_predefined.h and my_predefined.cpp, there will be #ifndef protections against -# multiple #includes. They will look like -# #ifndef __test_predefined_H -# #define __test_predefined_H -# and -# #ifndef __test_predefined_CPP -# #define __test_predefined_CPP -# -# Change each of these to have a unique name in some way, e.g. -# #ifndef __test_predefined_H_PREDEF -# #define __test_predefined_H_PREDEF -# and similarly for the CPP one -# -# 8. If any other files (which must be included from nCompiler_Eigen_fxns.h) -# in inst/include/nCompiler that need to use my_predefined, -# put the code using my_predefined inside a code block like this: -# #ifdef PREDEFINED_my_predefined -# #include PREDEFINED_HEADER(PREDEFINED_test_predefined) -# -# #endif -# -# (The C++ pre-processor variable PREDEFINED_my_predefined will be set by generated code -# automatically when you use the predefined class later. In this case it will be 'my_predefined_pkg') -# -# Note that it will not work to put such code just anywhere. It needs to be in one of the files that -# will be arranged to be included after the predefined header, so that -# PREDEFINED_my_predefined has been defined for the C++ preprocessor -# Currently the only file included at that time is nCompiler_Eigen_fxns.h, which is simply a set -# of other include statements. So it should be possible to add to those as needed (and possibly rename). -# -# 9. build nCompiler again so that the package includes the new .h and .cpp files. -# -# 10. You should now be able to use my_predefined as a type in nClasses and nFunctions. -# -# 11. When calling nCompile with code that uses my_predefined, include my_predefined as a compilation unit -# (i.e. an input) to nCompile. Otherwise it will not be used and there will be an error. -# -# Note: It is still possible to set control = list(generate_predefined = TRUE) and use -# locally generated versions of the predefined classes, not the versions that you have -# copied into package sources and installed. However, other functions that uses the classes -# will not work. -# -# Note: During experimental work, in steps 5 and 6 you can copy the files directly to the installed directory -# and then not need to rebuild and resintall the package in order to try them. - -################################ -### OLDER SYSTEM (as of 3/15/22) -## This script: -## 1. Reads files in nCompiler/R that have predefined nClass definitions. -## 2. Generates the .cpp and .h content in inst -#library(nCompiler) - -#set_nOption("use_nCompLocal", TRUE) - -## if(!("generatePredefinedCpp.R" %in% list.files())) -## stop("You need source generatePredefinedCpp.R with its directory as your working directory.") - -## predefined_filebase <- "predefined" -## clear_predefined_files <- function(compileAttributes = TRUE) { -## dir1 = file.path("nCompiler","src") -## dir2 = file.path("nCompiler","inst","include","nCompiler") -## for(dir in c(dir1, dir2)) { -## for(file in paste0(predefined_filebase, c(".h", ".cpp"))) { -## dirfile <- file.path(dir, file) -## if(file.exists(dirfile)) -## file.remove(dirfile) -## } -## } -## if(compileAttributes) -## compileAttributes("nCompiler") -## } - -## predefFiles <- c("zzz_NC_Predefined.R") -## sourceDir <- file.path("nCompiler", "R") - -## predefinedNames <- character() -## predefinedRcppPackets <- list() - -## getRcppPacket <- function(parsed, className) { -## env <- new.env() -## eval(parsed, envir = env) -## RcppPacket <- nCompile_nClass(env[[className]], -## control = list(endStage = 'writeCpp', -## filename = predefined_filebase)) -## RcppPacket -## } - -## for(pF in predefFiles) { -## ## pF <- predefFiles[1] -## pathedFile <- file.path(sourceDir, pF) -## parsed <- parse(file = pathedFile) -## for(iP in seq_along(parsed)) { -## ##iP <- 1; -## thisParsed <- parsed[[iP]] - -## if(is.call(thisParsed)) { -## if(deparse(thisParsed[[1]]) == "<-") { -## if(is.call(thisParsed[[3]])) { -## if(deparse(thisParsed[[3]][[1]]) == "nClass") { -## className <- deparse(thisParsed[[2]]) -## predefinedNames <- append(predefinedNames, className) -## predefinedRcppPackets <- c(predefinedRcppPackets, -## list(getRcppPacket(thisParsed, className))) -## } -## } -## } -## } -## } -## } - -## sepContents <- function(x, element, label) -## c("//--------------------------------------", -## "//--------------------------------------", -## paste0("// ", label), -## "//--------------------------------------", -## x[[element]] -## ) - -## combinedPacket <- nCompiler:::Rcpp_nCompilerPacket( -## cppContent = c("//GENERATED BY generatedPredefinedCpp.R. DO NOT EDIT BY HAND", -## unlist(mapply(sepContents, -## x = predefinedRcppPackets, -## label = predefinedNames, -## MoreArgs = list(element = "cppContent"), -## SIMPLIFY=FALSE, -## USE.NAMES = FALSE))), -## hContent = c("//GENERATED BY generatedPredefinedCpp.R. DO NOT EDIT BY HAND", -## unlist(mapply(sepContents, -## x = predefinedRcppPackets, -## label = predefinedNames, -## MoreArgs = list(element = "hContent"), -## SIMPLIFY=FALSE, -## USE.NAMES = FALSE))), -## filebase = predefined_filebase -## ) - -## # Remove "// [[Rcpp::depends(nCompiler)]]" -## # This should occur once per cppDef in cppContent -## removeBool <- combinedPacket$cppContent == "// [[Rcpp::depends(nCompiler)]]" -## combinedPacket$cppContent[removeBool] <- "" -## # This should not occur in hContent, but we can defensively check anyway -## removeBool <- combinedPacket$hContent == "// [[Rcpp::depends(nCompiler)]]" -## combinedPacket$hContent[removeBool] <- "" - -## ## remove any old files -## clear_predefined_files(compileAttributes = FALSE) -## nCompiler:::writeCpp_nCompiler(combinedPacket, dir = file.path("nCompiler","inst","include","nCompiler")) -## nCompiler:::writeCpp_nCompiler(combinedPacket, dir = file.path("nCompiler","src")) -## require(Rcpp) -## compileAttributes("nCompiler") diff --git a/nCompiler/R/NC_Compile.R b/nCompiler/R/NC_Compile.R index b650ce59..ac8ad828 100644 --- a/nCompiler/R/NC_Compile.R +++ b/nCompiler/R/NC_Compile.R @@ -49,8 +49,10 @@ nCompile_nClass <- function(NC, if(is_predefined) { predefined_dir <- NCinternals(NC)$predefined if(!is.character(predefined_dir)) - stop("There is a predefined nClass whose predefined field is not character. It should give the filename base of the predefined nClass.") - regular_filename <- NCinternals(NC)$cpp_classname + stop("There is a predefined nClass whose predefined field is not character. ", + "It should give the directory path of the predefined nClass. ", + "The classname argument to nClass gives the base for filenames in that directory.") + regular_filename <- NCinternals(NC)$cpp_classname } if(is_predefined && isFALSE(controlFull$generate_predefined)) { RcppPacket <- loadRcppPacket(predefined_dir, regular_filename) diff --git a/nCompiler/R/NF_Compile.R b/nCompiler/R/NF_Compile.R index 5cdad17d..7b414643 100644 --- a/nCompiler/R/NF_Compile.R +++ b/nCompiler/R/NF_Compile.R @@ -61,7 +61,9 @@ nCompile_nFunction <- function(NF, if(is_predefined) { predefined_dir <- NFinternals(NF)$predefined if(!is.character(predefined_dir)) - stop("There is a predefined nFunction whose predefined field is not character. It should give the filename base of the predefined nFunction.") + stop("There is a predefined nFunction whose predefined field is not character. ", + "It should give the directory path of the predefined nFunction. ", + "The name argument to nFunction gives the base for filenames in that directory.") regular_filename <- NFinternals(NF)$cpp_code_name } if(is_predefined && isFALSE(controlFull$generate_predefined)) { diff --git a/nCompiler/R/Rcpp_nCompiler.R b/nCompiler/R/Rcpp_nCompiler.R index 0370fb68..7ac73934 100644 --- a/nCompiler/R/Rcpp_nCompiler.R +++ b/nCompiler/R/Rcpp_nCompiler.R @@ -36,11 +36,13 @@ cppDefs_2_RcppPacket <- function(cppDef, allCppDefs <- cppDef$getInternalDefs() - Hincludes <- allCppDefs |> lapply(\(x) x$getHincludes()) |> unlist(use.names=FALSE) - CPPincludes <- allCppDefs |> lapply(\(x) x$getCPPincludes()) |> unlist(use.names=FALSE) - Hpreamble <- allCppDefs |> lapply(\(x) x$getHpreamble()) |> unlist(use.names=FALSE) - CPPpreamble <- allCppDefs |> lapply(\(x) x$getCPPpreamble()) |> unlist(use.names=FALSE) - CPPusings <- allCppDefs |> lapply(\(x) x$getCPPusings()) |> unlist(use.names=FALSE) + prep <- function(x) x |> unlist(use.names=FALSE) |> unique() + + Hincludes <- allCppDefs |> lapply(\(x) x$getHincludes()) |> prep() + CPPincludes <- allCppDefs |> lapply(\(x) x$getCPPincludes()) |> prep() + Hpreamble <- allCppDefs |> lapply(\(x) x$getHpreamble()) |> prep() + CPPpreamble <- allCppDefs |> lapply(\(x) x$getCPPpreamble()) |> prep() + CPPusings <- allCppDefs |> lapply(\(x) x$getCPPusings()) |> prep() compileInfos <- allCppDefs |> lapply(\(x) x$getCompileInfo()) ## Fields like copyFiles can be extracted directly from the compileInfos @@ -48,13 +50,6 @@ cppDefs_2_RcppPacket <- function(cppDef, ## (Whereas fields like Hincludes etc. may be modified by cppDef.) copyFiles <- compileInfos |> lapply(\(x) x$copyFiles) |> unlist(use.names=FALSE) |> unique() - ## These unique()s could be moved to the piping workflows above. - Hincludes <- unique(Hincludes) - CPPincludes <- unique(CPPincludes) - Hpreamble <- unique(Hpreamble) - CPPpreamble <- unique(CPPpreamble) - CPPusings <- unique(CPPusings) - selfCPP <- paste0('"', filebase, '.cpp"') CPPincludes <- CPPincludes[ CPPincludes != selfCPP ] @@ -71,7 +66,8 @@ cppDefs_2_RcppPacket <- function(cppDef, writeLines("") writeCode(x$generate()) }, split = debugCpp) ## for debugging to send handler output to console - ) + ), + use.names = FALSE ) hCode <- @@ -82,7 +78,8 @@ cppDefs_2_RcppPacket <- function(cppDef, writeLines("") writeCode(x$generate(declaration=TRUE)) }) - ) + ), + use.names = FALSE ) preamble <- unique(c(Hpreamble, CPPpreamble)) @@ -149,8 +146,8 @@ collate_nCompiler_CppCode <- function(preamble = character(), ) ## usings are included as is - ## code is included as is - + ## code is included as is + ## close ifndef closeifndefOut <- if(length(ifndefName) > 0) { '#endif' @@ -181,10 +178,10 @@ collate_nCompiler_CppCode <- function(preamble = character(), ## We make a copy of Rcpp::sourceCpp and replace its closure ## with a new environment that has a custom version of system() ## that provides the arguments we need. -## The parent environment of that one is Rcpp's namespace, +## The parent environment of that one is Rcpp's namespace, ## so that non-exported functions will be found. -## -## (Although a namespace is closed, we can still set it as a +## +## (Although a namespace is closed, we can still set it as a ## closure or parent environment.) sourceCppEnv <- new.env() parent.env(sourceCppEnv) <- environment(Rcpp::sourceCpp) @@ -193,7 +190,7 @@ environment(QuietSourceCpp) <- sourceCppEnv sourceCppEnv$system <- function(...) { sourceCpp_verbose <- isTRUE(nOptions("sourceCpp_verbose")) # We would want to use system2, but Rcpp::sourceCpp uses system - system(..., ignore.stderr = !sourceCpp_verbose, ignore.stdout = !sourceCpp_verbose) + system(..., ignore.stderr = !sourceCpp_verbose, ignore.stdout = !sourceCpp_verbose) } ## Manage a call to Rcpp's sourceCpp() @@ -343,7 +340,7 @@ writeCpp_nCompiler <- function(Rcpp_packet, makeStandardFiles <- is.null(con) if(is.null(Rcpp_packet$preamble)) Rcpp_packet$preamble <- "" - if(include_cpp) { + if(include_cpp) { if(makeStandardFiles) { dir.create(dir, showWarnings = FALSE) cppfile <- paste0(Rcpp_packet$filebase, @@ -364,7 +361,7 @@ writeCpp_nCompiler <- function(Rcpp_packet, writeLines(Rcpp_packet$cppContent$body, con) if(makeStandardFiles) close(con) - } + } if(include_h) { if(makeStandardFiles) { if(header.dir != dir) @@ -405,7 +402,7 @@ compileCpp_nCompiler <- function(Rcpp_packet, packetList = FALSE, returnList = FALSE, ## force result list even for a singleton ...) { - if(!dir.exists(dir)) + if(!dir.exists(dir)) stop(paste0("directory ", dir, " does not exist.")) if(is.character(Rcpp_packet)) { # backward compatibility, to be deprecated perhaps cppfile <- Rcpp_packet @@ -415,7 +412,7 @@ compileCpp_nCompiler <- function(Rcpp_packet, ".cpp") } cppfilepath <- file.path(dir, cppfile) - + exported <- sourceCpp_nCompiler(file = cppfilepath, cacheDir = cacheDir, env = env, @@ -472,13 +469,13 @@ saveRcppPacket <- function(RcppPacket, dir, name = NULL) { stop("RcppPacket must have a filebase element or name must be provided") } } - + # Normalize the directory path for platform independence dir <- normalizePath(dir, mustWork = FALSE) - + # Create directory if it doesn't exist dir.create(dir, recursive = TRUE, showWarnings = FALSE) - + # Helper function to write content safely writePacketElement <- function(content, filename) { filepath <- normalizePath(file.path(dir, filename), mustWork = FALSE) @@ -510,7 +507,7 @@ saveRcppPacket <- function(RcppPacket, dir, name = NULL) { dput(content, file = filepath) } } - + # Write each element to its own file writePacketElement(RcppPacket$preamble, paste0(name, "_preamble.txt")) writePacketElement(RcppPacket$cppContent, paste0(name, "_cppContent.txt")) @@ -518,7 +515,7 @@ saveRcppPacket <- function(RcppPacket, dir, name = NULL) { writePacketElement(RcppPacket$filebase, paste0(name, "_filebase.txt")) writePacketElement(RcppPacket$post_cpp_compiler, paste0(name, "_post_cpp_compiler.txt")) writePacketElement(RcppPacket$copyFiles, paste0(name, "_copyFiles.txt")) - + # Write a manifest file listing what was saved manifest <- list( saved_at = Sys.time(), @@ -533,10 +530,10 @@ saveRcppPacket <- function(RcppPacket, dir, name = NULL) { copyFiles = paste0(name, "_copyFiles.txt") ) ) - + manifest_path <- normalizePath(file.path(dir, paste0(name, "_manifest.txt")), mustWork = FALSE) dput(manifest, file = manifest_path) - + invisible(normalizePath(dir)) } @@ -549,7 +546,20 @@ saveRcppPacket <- function(RcppPacket, dir, name = NULL) { loadRcppPacket <- function(dir, name) { # Normalize and check if directory exists dir <- normalizePath(dir, mustWork = TRUE) - + + # If name is missing, try to find a manifest file and use its base name + if (missing(name)) { + manifest_files <- list.files(dir, pattern = "_manifest\\.txt$", full.names = FALSE) + if (length(manifest_files) == 0) { + stop("No manifest files (from saved RcppPackets) found in directory: ", dir) + } + if (length(manifest_files) > 1) { + stop("More than one manifest files (from saved RcppPackets) found in directory: ", dir, ". Provide a name argument to choose one.") + } + # Use the first manifest file found + name <- sub("_manifest\\.txt$", "", manifest_files[1]) + } + # Load manifest first to check what files should exist manifest_file <- normalizePath(file.path(dir, paste0(name, "_manifest.txt")), mustWork = FALSE) if (file.exists(manifest_file)) { @@ -566,49 +576,48 @@ loadRcppPacket <- function(dir, name) { copyFiles = paste0(name, "_copyFiles.txt") )) } - + # Helper function to read content safely readPacketElement <- function(filename) { filepath <- normalizePath(file.path(dir, filename), mustWork = FALSE) if (!file.exists(filepath)) { return(NULL) } - + # Check if file is empty if (file.size(filepath) == 0) { - return(character(0)) + return(NULL) } - + # Try to detect the content type first_line <- readLines(filepath, n = 1, warn = FALSE) - + if (length(first_line) == 0) { return(character(0)) } - + # Check if it's a structured file (cppContent/hContent) if (first_line == "### OPENER ###") { lines <- readLines(filepath, warn = FALSE) opener_start <- which(lines == "### OPENER ###") body_start <- which(lines == "### BODY ###") - + if (length(opener_start) == 1 && length(body_start) == 1) { opener_lines <- if (body_start > opener_start + 1) { lines[(opener_start + 1):(body_start - 1)] } else { character(0) } - + body_lines <- if (length(lines) > body_start) { lines[(body_start + 1):length(lines)] } else { character(0) } - return(list(opener = opener_lines, body = body_lines)) } } - + # Check if it looks like dput output if (grepl("^(list|c|character|NULL|[0-9])", first_line)) { tryCatch({ @@ -618,11 +627,11 @@ loadRcppPacket <- function(dir, name) { return(readLines(filepath, warn = FALSE)) }) } - + # Default: read as character lines return(readLines(filepath, warn = FALSE)) } - + # Load each element RcppPacket <- Rcpp_nCompilerPacket( preamble = readPacketElement(manifest$files$preamble), @@ -632,7 +641,7 @@ loadRcppPacket <- function(dir, name) { post_cpp_compiler = readPacketElement(manifest$files$post_cpp_compiler), copyFiles = readPacketElement(manifest$files$copyFiles) ) - + return(RcppPacket) } @@ -644,13 +653,13 @@ loadRcppPacket <- function(dir, name) { listRcppPackets <- function(dir) { # Normalize path, but allow non-existent directories dir <- normalizePath(dir, mustWork = FALSE) - + if (!dir.exists(dir)) { return(character(0)) } - + manifest_files <- list.files(dir, pattern = "_manifest\\.txt$", full.names = FALSE) packet_names <- sub("_manifest\\.txt$", "", manifest_files) - + return(packet_names) } diff --git a/nCompiler/R/cppDefs_core.R b/nCompiler/R/cppDefs_core.R index 83917b6f..0c508328 100644 --- a/nCompiler/R/cppDefs_core.R +++ b/nCompiler/R/cppDefs_core.R @@ -316,8 +316,8 @@ addGenericInterface_impl <- function(self) { ">")) # It is ok to have multiple virtual inheritance from genericInterfaceBaseC, # but we clean it up here for slightly simpler code. - if("virtual public genericInterfaceBaseC" %in% self$inherit) { - self$inherit <- self$inherit[-which(self$inherit == "virtual public genericInterfaceBaseC")] + if("virtual public genericInterfaceBaseC" %in% self$inheritance) { + self$inheritance <- self$inheritance[-which(self$inheritance == "virtual public genericInterfaceBaseC")] } # self$Hincludes <- c(self$Hincludes, # nCompilerIncludeFile("nCompiler_class_interface.h")) @@ -632,7 +632,7 @@ cppClassClass <- R6::R6Class( } else { # Ensure inheritance from genericInterfaceBaseC so our custom Exporter in C++ # can always dynamic_pointer_cast to shared_ptr. - if(!("virtual public genericInterfaceBaseC" %in% self$inherit)) { + if(!("virtual public genericInterfaceBaseC" %in% self$inheritance)) { self$addInheritance("virtual public genericInterfaceBaseC") } # These will always end up included and possibly multiple times, diff --git a/nCompiler/R/zzz_NC_Predefined.R b/nCompiler/R/zzz_NC_Predefined.R index b74460e9..4998a11e 100644 --- a/nCompiler/R/zzz_NC_Predefined.R +++ b/nCompiler/R/zzz_NC_Predefined.R @@ -1,10 +1,12 @@ -# predefined classes can be included as compilation objection in a call +# predefined classes can be included as compilation object in a call # to nCompile just like others. # The difference is that pre-existing C++ code will be used to implement them. -# Include control list element generate_predefined = TRUE to -# use newly generated code instead of the pre-existing code. -# However, code in the C++ headers that uses the predefined might then break. - +# Include control list element generate_predefined = or quoted expression to evaluate to get the directory. +# This is the directory of the saved RcppPacket +# saved by saveRcppPacket when nCompile is called with control element generate_predefined=TRUE +# +# Futher work is needed on how built-in features will use predefined classes. +# The current idea is that the feature, or a wrapper to it, will also live in a predefined. ## #' @export test_predefined <- nClass( diff --git a/nCompiler/tests/testthat/predefined_tests/test-predefined.R b/nCompiler/tests/testthat/predefined_tests/test-predefined.R new file mode 100644 index 00000000..df921525 --- /dev/null +++ b/nCompiler/tests/testthat/predefined_tests/test-predefined.R @@ -0,0 +1,278 @@ +library(testthat) +library(nCompiler) + +# nFunction tests +test_that("saving and loading RcppPacket works for nFunction", +{ + foo <- nFunction( + name = "test_RcppPacket_foo", + function(x=double(1)) {return(x+1); returnType(double(1))} + ) + cppDef <- nCompile(foo, control=list(return_cppDefs=TRUE)) + RcppPacket <- nCompiler:::cppDefs_2_RcppPacket(cppDef[[1]]) + + dir <- file.path(tempdir(), "test_RcppPacket_dir") + + dir.create(dir, showWarnings=FALSE, recursive=FALSE) + nCompiler:::saveRcppPacket(RcppPacket, dir, "test_RcppPacket") + restored_packet <- nCompiler:::loadRcppPacket(dir, "test_RcppPacket") + expect_true(identical(RcppPacket, restored_packet)) + unlink(dir, recursive = TRUE) + + dir.create(dir, showWarnings=FALSE, recursive=FALSE) + nCompiler:::saveRcppPacket(RcppPacket, dir) + restored_packet <- nCompiler:::loadRcppPacket(dir) + expect_true(identical(RcppPacket, restored_packet)) + unlink(dir, recursive = TRUE) +}) + +test_that("generating and compiling a predefined nFunction works", +{ + foo <- nFunction( + name = "test_predefined_nFunction", + function(x=double(1)) {return(x+1); returnType(double(1))}, + predefined=TRUE + ) + expect_error(nCompile(foo)) + + + foo <- nFunction( + name = "test_predefined_nFunction", + function(x=double(1)) {return(x+1); returnType(double(1))}, + predefined=file.path(tempdir(), "test_predefined_nFunction_dir") + ) + dir <- file.path(tempdir(), "use_predefined_testdir") + cfoo <- nCompile(foo, dir=dir, control=list(generate_predefined=TRUE)) + expect_equal(cfoo(1:3), 2:4) + dir2 <- file.path(tempdir(), "use_predefined_testdir2") + loading_output <- capture_output(cfoo2 <- nCompile(foo, dir=dir2)) + expect_true(grepl("^Loading RcppPacket", loading_output)) + expect_equal(cfoo2(1:3), 2:4) + unlink(dir, recursive = TRUE) + unlink(dir2, recursive = TRUE) + unlink(NFinternals(foo)$predefined, recursive=TRUE) +}) + +test_that("generating and compiling a predefined nFunction works via packaging", +{ + foo <- nFunction( + name = "test_predefined_nFunction", + function(x=double(1)) {return(x+1); returnType(double(1))}, + predefined=file.path(tempdir(), "test_predefined_nFunction_dir") + ) + dir <- file.path(tempdir(), "use_predefined_testdir") + cfoo <- nCompile(foo, dir=dir, control=list(generate_predefined=TRUE), package=TRUE) + expect_equal(cfoo(1:3), 2:4) + dir2 <- file.path(tempdir(), "use_predefined_testdir2") + loading_output <- capture_output(cfoo2 <- nCompile(foo, dir=dir2, package=TRUE)) + expect_true(grepl("^Loading RcppPacket", loading_output)) + expect_equal(cfoo2(1:3), 2:4) + unlink(dir, recursive = TRUE) + unlink(dir2, recursive = TRUE) + unlink(NFinternals(foo)$predefined, recursive=TRUE) +}) + +test_that("One predefined nFunctions can use another predefined", +{ + foo <- nFunction( + name = "test_predefined_foo_nF", + function(x=double(1)) {return(x+1); returnType(double(1))}, + predefined=file.path(tempdir(), "test_predefined_foo_dir") + ) + bar <- nFunction( + name = "test_predefined_bar_nF", + function(x=double(1)) {return(foo(x+1)); returnType(double(1))}, + predefined=file.path(tempdir(), "test_predefined_bar_dir") + ) + dir <- file.path(tempdir(), "use_predefined_testdir") + comp <- nCompile(foo, bar, dir=dir, control=list(generate_predefined=TRUE)) + expect_equal(comp$bar(1:3), 3:5) + dir2 <- file.path(tempdir(), "use_predefined_testdir2") + loading_output <- capture_output(comp2 <- nCompile(foo, bar, dir=dir2)) + text_matches <- gregexpr("Loading RcppPacket", loading_output)[[1]] + expect_true(length(text_matches)==2) + expect_equal(comp2$bar(1:3), 3:5) + unlink(dir, recursive = TRUE) + unlink(dir2, recursive = TRUE) + unlink(NFinternals(foo)$predefined, recursive=TRUE) +}) + +test_that("One predefined nFunctions can use another predefined via packaging", +{ + foo <- nFunction( + name = "test_predefined_foo_nF", + function(x=double(1)) {return(x+1); returnType(double(1))}, + predefined=file.path(tempdir(), "test_predefined_foo_dir") + ) + bar <- nFunction( + name = "test_predefined_bar_nF", + function(x=double(1)) {return(foo(x+1)); returnType(double(1))}, + predefined=file.path(tempdir(), "test_predefined_bar_dir") + ) + dir <- file.path(tempdir(), "use_predefined_testdir") + comp <- nCompile(foo, bar, dir=dir, control=list(generate_predefined=TRUE), package=TRUE) + expect_equal(comp$bar(1:3), 3:5) + dir2 <- file.path(tempdir(), "use_predefined_testdir2") + loading_output <- capture_output(comp2 <- nCompile(foo, bar, dir=dir2, package=TRUE)) + text_matches <- gregexpr("Loading RcppPacket", loading_output)[[1]] + expect_true(length(text_matches)==2) + expect_equal(comp2$bar(1:3), 3:5) + unlink(dir, recursive = TRUE) + unlink(dir2, recursive = TRUE) + unlink(NFinternals(foo)$predefined, recursive=TRUE) +}) + +############ +# nClass tests +test_that("saving and loading RcppPacket works for nClass", +{ + foo <- nClass( + classname = "test_RcppPacket_foo_nC", + Cpublic = list( + bar = nFunction( + function(x=double(1)) {return(x+1); returnType(double(1))} + ) + ) + ) + cppDef <- nCompile(foo, control=list(return_cppDefs=TRUE)) + RcppPacket <- nCompiler:::cppDefs_2_RcppPacket(cppDef[[1]]) + + dir <- file.path(tempdir(), "test_RcppPacket_dir") + + dir.create(dir, showWarnings=FALSE, recursive=FALSE) + nCompiler:::saveRcppPacket(RcppPacket, dir, "test_RcppPacket") + restored_packet <- nCompiler:::loadRcppPacket(dir, "test_RcppPacket") + expect_true(identical(RcppPacket, restored_packet)) + unlink(dir, recursive = TRUE) + + dir.create(dir, showWarnings=FALSE, recursive=FALSE) + nCompiler:::saveRcppPacket(RcppPacket, dir) + restored_packet <- nCompiler:::loadRcppPacket(dir) + expect_true(identical(RcppPacket, restored_packet)) + unlink(dir, recursive = TRUE) +}) + +cat("add unload of DLLs to test-predefined\n") + +test_that("generating and compiling a predefined nClass works", +{ + foo <- nClass( + classname = "test_predefined_nC", + Cpublic = list( + bar = nFunction( + function(x=double(1)) {return(x+1); returnType(double(1))} + ) + ), + predefined=TRUE + ) + expect_error(nCompile(foo)) + + foo <- nClass( + classname = "test_predefined_nC", + Cpublic = list( + bar = nFunction( + function(x=double(1)) {return(x+1); returnType(double(1))} + ) + ), + predefined=file.path(tempdir(), "test_predefined_nC_dir") + ) + + dir <- file.path(tempdir(), "use_predefined_nC_testdir") + cfoo <- nCompile(foo, dir=dir, control=list(generate_predefined=TRUE)) + obj <- cfoo$new() + expect_equal(obj$bar(1:3), 2:4) + dir2 <- file.path(tempdir(), "use_predefined_nC_testdir2") + loading_output <- capture_output(cfoo2 <- nCompile(foo, dir=dir2)) + obj2 <- cfoo2$new() + expect_true(grepl("^Loading RcppPacket", loading_output)) + expect_equal(obj2$bar(1:3), 2:4) + rm(obj, obj2); gc() + unlink(dir, recursive = TRUE) + unlink(dir2, recursive = TRUE) + unlink(NCinternals(foo)$predefined, recursive=TRUE) +}) + +test_that("generating and compiling a predefined nClass works through packaging", +{ + foo <- nClass( + classname = "test_predefined_nC", + Cpublic = list( + bar = nFunction( + function(x=double(1)) {return(x+1); returnType(double(1))} + ) + ), + predefined=file.path(tempdir(), "test_predefined_nC_dir") + ) + + dir <- file.path(tempdir(), "use_predefined_nC_testdir") + cfoo <- nCompile(foo, dir=dir, control=list(generate_predefined=TRUE), package=TRUE) + obj <- cfoo$new() + expect_equal(obj$bar(1:3), 2:4) + dir2 <- file.path(tempdir(), "use_predefined_nC_testdir2") + loading_output <- capture_output(cfoo2 <- nCompile(foo, dir=dir2, package=TRUE)) + obj2 <- cfoo2$new() + expect_true(grepl("^Loading RcppPacket", loading_output)) + expect_equal(obj2$bar(1:3), 2:4) + rm(obj, obj2); gc() + unlink(dir, recursive = TRUE) + unlink(dir2, recursive = TRUE) + unlink(NCinternals(foo)$predefined, recursive=TRUE) +}) + +cat("type declaration in code of returnType(foo()) needs fixing\n") + +test_that("One predefined nClass can use another, separately and by inheritance", +{ + for(package in c(FALSE, TRUE)) { + foo_base <- nClass( + classname = "test_predefined_nC_foo_base", + Cpublic = list( + give_one = nFunction( + function() { + return(1.0); returnType(double()) + } + ) + ) + , compileInfo = list(interface='none', createFromR = FALSE) + , predefined=file.path(tempdir(), "test_predefined_nC_foo_base_dir") + ) + + foo <- nClass( + classname = "test_predefined_nC_foo", + inherit = foo_base, + Cpublic = list( + bar = nFunction( + function(x=double(1)) {return(x+1); returnType(double(1))} + ) + ) + , predefined=file.path(tempdir(), "test_predefined_nC_foo_dir") + ) + + use_foo <- nClass( + classname = "test_predefined_nC_usefoo", + Cpublic = list( + make_foo = nFunction( + function() {return(foo$new()); returnType('foo')} + ) + ) + , predefined=file.path(tempdir(), "test_predefined_nC_use_foo") + ) + + dir <- file.path(tempdir(), "use_predefined_nC_testdir2") + + comp <- nCompile(foo, foo_base, use_foo, dir=dir, control=list(generate_predefined=TRUE),package=package) + obj <- comp$use_foo$new() + expect_equal(obj$make_foo()$bar(1:3), 2:4) + dir2 <- file.path(tempdir(), "use_predefined_nC_testdir2") + loading_output <- capture_output(comp2 <- nCompile(foo, foo_base, use_foo, dir=dir2,package=package)) + obj2 <- comp2$use_foo$new() + expect_true(grepl("^Loading RcppPacket", loading_output)) + expect_equal(obj2$make_foo()$bar(1:3), 2:4) + rm(obj, obj2); gc() + unlink(dir, recursive = TRUE) + unlink(dir2, recursive = TRUE) + unlink(NCinternals(foo)$predefined, recursive=TRUE) + unlink(NCinternals(foo_base)$predefined, recursive=TRUE) + unlink(NCinternals(use_foo)$predefined, recursive=TRUE) + } +})