Skip to content

Commit e571e8f

Browse files
tjzurlinCopilot
andauthored
tmp directory model compilation (#51)
* Initial commit for writing temp model fix * Use winslash in normalizePath to remove need to grep for windows-specific path slashes. * Define source file on which the hash is determined. The original file is still used for model compilation. * Fix temp directory bug to ensure source file is hashed before model compilation of copied model file to temp. * Don't show warning for creating test directory for running tests in same session. * Run styler * Hash the original source file into the right location during the init so we always have a baseline on the source model version. * run styler * Rebuild package documentation and fix notes following check. 1. Include mention of LICENSE file in DESCTIPTION and 2. add paper directory to .Rbuildignore as non-standard R package directory * Update man/Model-class.Rd Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update R/MCSim_model.R Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Document package after fixing typo * Remove unused file.copied variable when copying source file to model file in temp folder --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 30073a5 commit e571e8f

10 files changed

Lines changed: 94 additions & 23 deletions

File tree

.Rbuildignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
^\.covrignore$
44
^\.github$
55
^.*\.Rproj$
6+
^paper$
67
^\_pkgdown.yml$
78
.clang-format
89
README.md

DESCRIPTION

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ Imports:
1515
methods,
1616
tools
1717
URL: https://CRAN.R-project.org/package=MCSimMod, https://github.com/USEPA/MCSimMod
18-
License: GPL-3
18+
License: GPL-3 + file LICENSE
1919
Encoding: UTF-8
2020
LazyData: true
2121
Roxygen: list(markdown = TRUE)
22-
RoxygenNote: 7.3.2
22+
RoxygenNote: 7.3.3
2323
Suggests:
2424
knitr,
2525
rmarkdown,

R/MCSim_model.R

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@ Model <- setRefClass("Model",
3333
#' @field paths List of character strings that are names of files associated with the model.
3434
#' @field writeTemp Boolean specifying whether to write model files to a temporary directory. If value is TRUE, model files will be written to a temporary directory; if value is FALSE, model files will be written to the same directory that contains the model specification file.
3535
#' @field verboseOutput Boolean specifying whether to write translator messages to standard output. If value is TRUE, messages will be written to standard output; if value is FALSE, messages will be written to files in a temporary directory.
36+
#' @field recompiled Boolean specifying if the model has been recompiled due to change in source file
3637
mName = "character", mString = "character", initParms = "function",
3738
initStates = "function", Outputs = "ANY", parms = "numeric", Y0 = "numeric",
38-
paths = "list", writeTemp = "logical", verboseOutput = "logical"
39+
paths = "list", writeTemp = "logical", verboseOutput = "logical", recompiled = "logical"
3940
),
4041
methods = list(
4142
initialize = function(...) {
@@ -53,20 +54,25 @@ Model <- setRefClass("Model",
5354
}
5455
file <- tempfile(pattern = "mcsimmod_", fileext = ".model")
5556
writeLines(mString, file)
57+
source_file <- normalizePath(file, winslash = "/")
58+
file <- source_file
5659
} else {
5760
if (writeTemp == TRUE) {
58-
source_file <- normalizePath(paste0(mName, ".model"))
61+
source_file <- normalizePath(paste0(mName, ".model"), winslash = "/")
5962
temp_directory <- tempdir()
6063
file <- file.path(temp_directory, basename(source_file))
61-
file_copied <- file.copy(from = source_file, to = file)
64+
file.copy(from = source_file, to = file)
65+
file <- normalizePath(file, winslash = "/")
6266
} else {
63-
file <- normalizePath(paste0(mName, ".model"))
67+
source_file <- normalizePath(paste0(mName, ".model"), winslash = "/")
68+
file <- source_file
6469
}
6570
}
6671
mList <- .fixPath(file)
72+
sList <- .fixPath(source_file)
6773
mName <<- mList$mName
6874
mPath <- mList$mPath
69-
75+
sPath <- sList$mPath
7076

7177
paths <<- list(
7278
dll_name = paste0(mName, "_model"),
@@ -75,17 +81,26 @@ Model <- setRefClass("Model",
7581
dll_file = file.path(mPath, paste0(mName, "_model", .Platform$dynlib.ext)),
7682
inits_file = file.path(mPath, paste0(mName, "_model_inits.R")),
7783
model_file = file.path(mPath, paste0(mName, ".model")),
84+
source_file = file.path(sPath, paste0(mName, ".model")),
7885
hash_file = file.path(mPath, paste0(mName, "_model.md5"))
7986
)
87+
88+
# Calculate and save initial hash during initialization
89+
# This allows loadModel to immediately check if source has changed
90+
if (!file.exists(paths$hash_file)) {
91+
initial_hash <- as.character(tools::md5sum(paths$source_file))
92+
write(initial_hash, file = paths$hash_file)
93+
}
8094
},
8195
loadModel = function(force = FALSE) {
8296
"Translate (if necessary) the model specification text to C, compile (if necessary) the resulting C file to create a dynamic link library (DLL) file (on Windows) or a shared object (SO) file (on Unix), and then load all essential information about the Model object into memory (for use in the current R session)."
8397
hash_exists <- file.exists(paths$hash_file)
8498
if (hash_exists) {
85-
hash_has_changed <- .fileHasChanged(paths$model_file, paths$hash_file)
99+
hash_has_changed <- .fileHasChanged(paths$source_file, paths$hash_file)
86100
} else {
87101
hash_has_changed <- TRUE
88102
}
103+
recompiled <<- FALSE
89104

90105
# Conditions for compiling a model:
91106
# 1. The DLL (on Windows) or SO (on Unix) associated with the model
@@ -98,7 +113,12 @@ Model <- setRefClass("Model",
98113
# specification file has been changed since the last translation and
99114
# compiling.
100115
if (!file.exists(paths$dll_file) | (force) | (!hash_exists) | (hash_exists & hash_has_changed)) {
101-
compileModel(paths$model_file, paths$c_file, paths$dll_name, paths$dll_file, hash_file = paths$hash_file, verbose_output = verboseOutput)
116+
# When using writeTemp=TRUE, ensure the model file is updated from source before compilation
117+
if (writeTemp & (paths$source_file != paths$model_file)) {
118+
file.copy(from = paths$source_file, to = paths$model_file, overwrite = TRUE)
119+
}
120+
compileModel(paths$model_file, paths$c_file, paths$dll_name, paths$dll_file, source_file = paths$source_file, hash_file = paths$hash_file, verbose_output = verboseOutput)
121+
recompiled <<- TRUE
102122
}
103123

104124
# Load the compiled model (DLL).

R/compileModel.R

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
#' @param c_file Name of a C source code file to be created by compiling the MCSim model specification file.
99
#' @param dll_name Name of a DLL or SO file without the extension (".dll" or ".so").
1010
#' @param dll_file Name of the same DLL or SO file with the appropriate extension (".dll" or ".so").
11-
#' @param hash_file Name of a file containing a hash key for determining if `model_file` has changed since the previous translation and compilation.
11+
#' @param source_file Name of the original source file to use for hash calculation. Defaults to \code{model_file} for backward compatibility. When \code{writeTemp=TRUE} in \code{createModel()}, this should be set to the original source file path to ensure hash tracking works correctly when the source file is separate from the compiled model file.
12+
#' @param hash_file Name of a file containing a hash key for determining if `source_file` has changed since the previous translation and compilation.
1213
#' @param verbose_output Boolean specifying whether to write translator messages to standard output. If value is TRUE, messages will be written to standard output; if value is FALSE, messages will be written to files in a temporary directory.
1314
#' @returns No return value. Creates files and saves them in locations specified by function arguments.
1415
#' @import tools
1516
#' @useDynLib MCSimMod, .registration=TRUE
1617
#' @export
17-
compileModel <- function(model_file, c_file, dll_name, dll_file, hash_file = NULL, verbose_output = FALSE) {
18+
compileModel <- function(model_file, c_file, dll_name, dll_file, source_file = model_file, hash_file = NULL, verbose_output = FALSE) {
1819
# Unload DLL if it has been loaded.
1920
mList <- .fixPath(model_file)
2021
model_name <- mList$mName
@@ -120,7 +121,7 @@ compileModel <- function(model_file, c_file, dll_name, dll_file, hash_file = NUL
120121
"event",
121122
"root"
122123
)
123-
for (idx in seq(length(item_to_replace))) {
124+
for (idx in seq_along(item_to_replace)) {
124125
lines <- gsub(
125126
paste0("\\b", item_to_replace[idx], "\\b"),
126127
paste0(item_to_replace[idx], "_", model_name),
@@ -151,7 +152,7 @@ compileModel <- function(model_file, c_file, dll_name, dll_file, hash_file = NUL
151152
"initStates",
152153
"initState"
153154
)
154-
for (idx in seq(length(item_to_replace))) {
155+
for (idx in seq_along(item_to_replace)) {
155156
lines <- gsub(
156157
paste0("\\b", item_to_replace[idx], "\\b"),
157158
paste0(item_to_replace[idx], "_", model_name),
@@ -181,10 +182,10 @@ compileModel <- function(model_file, c_file, dll_name, dll_file, hash_file = NUL
181182
normalizePath(out_file), ".\n"
182183
)
183184

184-
# If hash file name was provided, create a hash (md5 sum) for the model file
185+
# If hash file name was provided, create a hash (md5 sum) for the source file
185186
# and print a message about its location.
186187
if (!is.null(hash_file)) {
187-
file_hash <- as.character(md5sum(model_file))
188+
file_hash <- as.character(tools::md5sum(source_file))
188189
write(file_hash, file = hash_file)
189190
message(
190191
"Hash created and saved in the file ", normalizePath(hash_file),

R/fileHasChanged.R

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
#-----------------
22
# compareHash
33
#----------------
4-
# Private function to determine if the .model file has changed
4+
# Private function to determine if the source .model file has changed
55

6-
.fileHasChanged <- function(model_file, hash_file) {
6+
.fileHasChanged <- function(source_file, hash_file) {
77
# Calculate hash for current model file
8-
current_hash <- as.character(md5sum(model_file))
8+
current_hash <- tools::md5sum(source_file)
99

1010
# Read saved hash
1111
saved_hash <- readLines(hash_file, n = 1)

R/fixPath.R

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@
77
.fixPath <- function(file) {
88
new.mName <- strsplit(basename(file), "[.]")[[1]][1]
99
new.mPath <- dirname(file)
10-
if (.Platform$OS.type == "windows") {
11-
new.mPath <- gsub("\\\\", "/", utils::shortPathName(new.mPath))
12-
}
1310

1411
has_space <- grepl(" ", new.mPath)
1512
if (has_space == T) {

man/Model-class.Rd

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/compileModel.Rd

Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-compareHash.R

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,50 @@ testthat::test_that("test_compareHash", {
2222

2323
model$cleanup()
2424
})
25+
26+
testthat::test_that("test_tmp_compileModel", {
27+
# Test writeTemp=TRUE functionality with proper source/compilation file separation
28+
# Create a proper source file in a permanent location (not temp)
29+
source_dir <- file.path(tempdir(), "sourceDir")
30+
dir.create(source_dir, showWarnings = FALSE)
31+
mName <- file.path(source_dir, "test_model")
32+
mString <- readLines(file.path(testthat::test_path(), "data", "exponential.model"))
33+
writeLines(mString, paste0(mName, ".model"))
34+
35+
# Verify source file exists
36+
testthat::expect_true(file.exists(paste0(mName, ".model")))
37+
38+
# Create model with writeTemp=TRUE - this should use the source file
39+
# and create compilation files in temp directory
40+
model <- createModel(mName, writeTemp = TRUE)
41+
42+
# Verify paths are properly separated
43+
testthat::expect_true(model$paths$source_file != model$paths$model_file)
44+
testthat::expect_true(file.exists(model$paths$source_file))
45+
testthat::expect_true(file.exists(model$paths$model_file))
46+
47+
# First load - should compile
48+
model$loadModel()
49+
testthat::expect_true(file.exists(model$paths$hash_file)) # Check if hash was created
50+
testthat::expect_true(model$recompiled) # Should be TRUE on first compile
51+
52+
# Second load - should NOT recompile (no changes)
53+
model$loadModel()
54+
testthat::expect_false(.fileHasChanged(model$paths$source_file, model$paths$hash_file))
55+
testthat::expect_false(model$recompiled) # Should be FALSE, no recompile needed
56+
57+
# Edit source file (what users actually edit)
58+
write("# File is edited", file = model$paths$source_file, append = TRUE, sep = "\n")
59+
testthat::expect_true(.fileHasChanged(model$paths$source_file, model$paths$hash_file))
60+
61+
# Third load after edit - should recompile
62+
model$loadModel()
63+
testthat::expect_true(model$recompiled) # Should be TRUE, source changed
64+
65+
# Fourth load - should NOT recompile (our bug fix test!)
66+
model$loadModel()
67+
testthat::expect_false(.fileHasChanged(model$paths$source_file, model$paths$hash_file))
68+
testthat::expect_false(model$recompiled) # Should be FALSE, hash should now match
69+
70+
model$cleanup()
71+
})

tests/testthat/test-sim.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ testthat::test_that("Model$absoluteModel", {
4545
# Use absolute path of temp directory,
4646
# Test to make sure changing the file returns a changed path
4747

48-
dir.create(file.path(tempdir(), "testDir"))
48+
dir.create(file.path(tempdir(), "testDir"), showWarnings = FALSE)
4949
mName <- tempfile(pattern = "mcsimmod_", tmpdir = file.path(tempdir(), "testDir"))
5050
mString <- readLines(file.path(testthat::test_path(), "data", "exponential.model"))
5151
writeLines(mString, paste0(mName, ".model"))

0 commit comments

Comments
 (0)