├── .github
├── .gitignore
└── workflows
│ ├── R-CMD-check.yaml
│ └── test-coverage.yaml
├── vignettes
└── .gitignore
├── LICENSE
├── data
└── Maryland.rda
├── tests
├── testthat.R
└── testthat
│ ├── invalid_xml.xml
│ ├── test_xml_less_than.xml
│ ├── test-translate_extrema.R
│ ├── test-translate_math_funs.R
│ ├── test-output_utils.R
│ ├── test-priors.R
│ ├── test-simulator_utils.R
│ ├── test-translate_time_builtins.R
│ ├── test-stat_funs.R
│ ├── test-comparison_operators.R
│ ├── test-stan_postprocessing.R
│ ├── test-summaries.R
│ ├── test-prior_checks.R
│ ├── test-Vensim_workarounds.R
│ ├── SEIR_C_meas.stan
│ ├── test-arrange_variables.R
│ ├── test-what_if_from_time.R
│ ├── test_stan_files
│ ├── SEIR_pois_N.stan
│ ├── SEIR_nbin_data_init.stan
│ ├── SE3I3R_pois.stan
│ ├── LV.stan
│ ├── SEIR_age_pois.stan
│ └── SEIR_age_nbin.stan
│ ├── SEIR_normal.stan
│ ├── SEIR_nbinom.stan
│ ├── test-arrays.R
│ ├── SEIR_nbinom_data_param.stan
│ ├── 2d_pop.xmile
│ ├── test-interpreters.R
│ ├── test-stan_params.R
│ ├── test-stan_data.R
│ ├── test-stan_utils.R
│ ├── test-posterior_fun.R
│ ├── test-sanitise_xml.R
│ ├── test-xmile_graph_funs.R
│ ├── test-extract_variables.R
│ ├── test-logical_operators.R
│ ├── SEjIkR.xmile
│ ├── test-if_else_builtins.R
│ ├── test-SBC.R
│ ├── test-generate_igraph_inputs.R
│ └── test_models
│ └── SEIR_simlin.stmx
├── inst
└── models
│ └── SIR_diagram.png
├── .gitignore
├── .Rbuildignore
├── R
├── regex_patterns.R
├── fun_gen_utils.R
├── translate_extrema.R
├── translate_time_builtins.R
├── comparison_operators.R
├── data.R
├── par_trans.R
├── simulator_utils.R
├── sanitise_xml.R
├── translate_math_funs.R
├── output_utils.R
├── summaries.R
├── logical_operators.R
├── utils.R
├── stan_data.R
├── stan_params.R
├── xmile_graph_funs.R
├── Vensim_workarounds.R
├── extract_variables.R
├── stat_funs.R
├── create_stan_function.R
├── arrange_variables.R
├── what_if_from_time.R
├── prior_checks.R
├── priors.R
├── stan_postprocessing.R
├── generate_igraph_inputs.R
├── stan_model.R
├── posterior_fun.R
├── interpreters.R
├── arrays.R
├── if_else_builtins.R
├── read_xmile.R
├── impact_inputs.R
└── SBC.R
├── man
├── inv.Rd
├── expit.Rd
├── logit.Rd
├── reexports.Rd
├── sd_stocks.Rd
├── sd_constants.Rd
├── sd_pulse_v.Rd
├── sd_pulse_s.Rd
├── sd_net_change.Rd
├── Maryland.Rd
├── extract_timeseries_var.Rd
├── sd_fixed_delay.Rd
├── sd_pulse_train.Rd
├── sd_conf_intervals.Rd
├── sd_interpret_estimates.Rd
├── sd_simulate.Rd
├── extract_timeseries_stock.Rd
├── sd_impact_inputs.Rd
├── xmile_to_deSolve.Rd
├── stan_ode_function.Rd
├── create_stan_function.Rd
├── sd_what_if_from_time.Rd
├── sd_measurements.Rd
├── sd_data_generator_fun.Rd
├── read_xmile.Rd
├── sd_prior_checks.Rd
├── sd_prior.Rd
├── sd_posterior_fun.Rd
├── sd_sensitivity_run.Rd
├── sd_Bayes.Rd
└── sd_loglik_fun.Rd
├── readsdr.Rproj
├── cran-comments.md
├── NAMESPACE
├── LICENSE.md
├── DESCRIPTION
└── NEWS.md
/.github/.gitignore:
--------------------------------------------------------------------------------
1 | *.html
2 |
--------------------------------------------------------------------------------
/vignettes/.gitignore:
--------------------------------------------------------------------------------
1 | *.html
2 | *.R
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | YEAR: 2019
2 | COPYRIGHT HOLDER: Jair Andrade
3 |
--------------------------------------------------------------------------------
/data/Maryland.rda:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jandraor/readsdr/HEAD/data/Maryland.rda
--------------------------------------------------------------------------------
/tests/testthat.R:
--------------------------------------------------------------------------------
1 | library(testthat)
2 | library(readsdr)
3 |
4 | test_check("readsdr")
5 |
--------------------------------------------------------------------------------
/inst/models/SIR_diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jandraor/readsdr/HEAD/inst/models/SIR_diagram.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .Rproj.user
2 | .Rhistory
3 | .RData
4 | .Ruserdata
5 | inst/doc
6 | doc
7 | Meta
8 | **/.DS_Store
9 | **/.Rapp.history
10 |
--------------------------------------------------------------------------------
/tests/testthat/invalid_xml.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | a < b
5 |
6 |
7 |
2 |
3 |
4 | a < b
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.Rbuildignore:
--------------------------------------------------------------------------------
1 | ^.*\.Rproj$
2 | ^\.Rproj\.user$
3 | ^\.github$
4 | ^README\.Rmd$
5 | ^doc$
6 | ^Meta$
7 | ^cran-comments\.md$
8 | ^CRAN-RELEASE$
9 | ^LICENSE\.md$
10 | ^CRAN-SUBMISSION$
11 |
--------------------------------------------------------------------------------
/R/regex_patterns.R:
--------------------------------------------------------------------------------
1 | get_pattern_regex <- function(pattern) {
2 |
3 | if(pattern == "net_flow") return ("net_flow\\((.+?)\\)")
4 |
5 | if(pattern == "var_trans") return ("log|exp|sqrt|inv\\(.+?\\)")
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/R/fun_gen_utils.R:
--------------------------------------------------------------------------------
1 | # Function generators utils
2 |
3 | extract_meas_pars <- function(meas_data_mdl) {
4 |
5 | meas_mdl <- lapply(meas_data_mdl, function(meas_obj) meas_obj$formula)
6 | detected_mp <- lapply(meas_mdl, extract_extra_params) %>% remove_NULL()
7 | detected_mp[!duplicated(detected_mp)]
8 | }
9 |
--------------------------------------------------------------------------------
/tests/testthat/test-translate_extrema.R:
--------------------------------------------------------------------------------
1 | context("Translate extrema")
2 |
3 |
4 | test_that("translate_MIN() returns the expected translation", {
5 | expect_equal(translate_MIN("Min(0,1)"), "min(0,1)")
6 | })
7 |
8 | test_that("translate_MAX() returns the expected translation", {
9 | expect_equal(translate_MAX("mAx(0,1)"), "max(0,1)")
10 | })
11 |
--------------------------------------------------------------------------------
/man/inv.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/par_trans.R
3 | \name{inv}
4 | \alias{inv}
5 | \title{Inverse of a number}
6 | \usage{
7 | inv(x)
8 | }
9 | \arguments{
10 | \item{x}{A real number}
11 | }
12 | \value{
13 | A real number
14 | }
15 | \description{
16 | Inverse of a number
17 | }
18 | \examples{
19 | inv(0.5) # Should return 2
20 | }
21 |
--------------------------------------------------------------------------------
/tests/testthat/test-translate_math_funs.R:
--------------------------------------------------------------------------------
1 | test_that("translate_SQRT returns the expected string", {
2 | expect_equal(translate_SQRT("SQRT(x)"), "sqrt(x)")
3 | })
4 |
5 | test_that("translate_EXP returns the expected string", {
6 |
7 | eq <- "EXP(-par_alpha*P)"
8 | actual <- translate_EXP(eq)
9 | expected <- "exp(-par_alpha*P)"
10 |
11 | expect_equal(actual, expected)
12 | })
13 |
--------------------------------------------------------------------------------
/man/expit.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/par_trans.R
3 | \name{expit}
4 | \alias{expit}
5 | \title{Expit transformation}
6 | \usage{
7 | expit(x)
8 | }
9 | \arguments{
10 | \item{x}{A real number}
11 | }
12 | \value{
13 | A number in the range 0 to 1
14 | }
15 | \description{
16 | Expit transformation
17 | }
18 | \examples{
19 | expit(-3)
20 | }
21 |
--------------------------------------------------------------------------------
/tests/testthat/test-output_utils.R:
--------------------------------------------------------------------------------
1 | test_that("sd_net_change() returns the expected data.frame", {
2 | test_output <- data.frame(time = seq(0, 2, by = 0.25),
3 | C = c(0, rep(5,4), rep(20, 4)))
4 |
5 | actual_df <- sd_net_change(test_output, "C")
6 | expected_df <- data.frame(time = 1:2, value = c(5, 15), var = "delta_C")
7 |
8 | expect_equal(actual_df, expected_df)
9 | })
10 |
--------------------------------------------------------------------------------
/man/logit.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/par_trans.R
3 | \name{logit}
4 | \alias{logit}
5 | \title{Logit transformation}
6 | \usage{
7 | logit(p)
8 | }
9 | \arguments{
10 | \item{p}{A real number that represents a probability}
11 | }
12 | \value{
13 | An unconstrained real number
14 | }
15 | \description{
16 | Logit transformation
17 | }
18 | \examples{
19 | logit(0.5)
20 | }
21 |
--------------------------------------------------------------------------------
/readsdr.Rproj:
--------------------------------------------------------------------------------
1 | Version: 1.0
2 |
3 | RestoreWorkspace: Default
4 | SaveWorkspace: Default
5 | AlwaysSaveHistory: Default
6 |
7 | EnableCodeIndexing: Yes
8 | UseSpacesForTab: Yes
9 | NumSpacesForTab: 2
10 | Encoding: UTF-8
11 |
12 | RnwWeave: Sweave
13 | LaTeX: pdfLaTeX
14 |
15 | AutoAppendNewline: Yes
16 | StripTrailingWhitespace: Yes
17 |
18 | BuildType: Package
19 | PackageUseDevtools: Yes
20 | PackageInstallArgs: --no-multiarch --with-keep.source
21 |
--------------------------------------------------------------------------------
/R/translate_extrema.R:
--------------------------------------------------------------------------------
1 | translate_extrema <- function(equation) {
2 | translate_MIN(equation) %>%
3 | translate_MAX()
4 | }
5 |
6 | translate_MIN <- function(equation) {
7 | pattern <- stringr::regex("\\bMIN\\b", ignore_case = TRUE)
8 | stringr::str_replace_all(equation, pattern, "min")
9 | }
10 |
11 | translate_MAX <- function(equation) {
12 | pattern <- stringr::regex("\\bMAX\\b", ignore_case = TRUE)
13 | stringr::str_replace_all(equation, pattern, "max")
14 | }
15 |
--------------------------------------------------------------------------------
/R/translate_time_builtins.R:
--------------------------------------------------------------------------------
1 | translate_time_builtins <- function(equation) {
2 | new_equation <- equation %>% translate_time() %>%
3 | translate_dt()
4 | }
5 |
6 |
7 | translate_time <- function(equation) {
8 | new_equation <- stringr::str_replace_all(equation, "\\bTime\\b|\\bTIME\\b",
9 | "time")
10 | }
11 |
12 | translate_dt <- function(equation) {
13 | new_equation <- stringr::str_replace_all(equation, "\\bDT\\b", "timestep()")
14 | }
15 |
--------------------------------------------------------------------------------
/R/comparison_operators.R:
--------------------------------------------------------------------------------
1 | translate_comparison_operators <- function(equation) {
2 | equal_translated <- translate_equal_sign(equation)
3 | not_equal_translated <- translate_not_equal_sign(equal_translated)
4 | }
5 |
6 | translate_equal_sign <- function(equation) {
7 | new_equation <- stringr::str_replace_all(equation, "(?|=|!)=(?!=)", "==")
8 | }
9 |
10 | translate_not_equal_sign <- function(equation) {
11 | new_equation <- stringr::str_replace_all(equation, "<>", "!=")
12 | }
13 |
--------------------------------------------------------------------------------
/cran-comments.md:
--------------------------------------------------------------------------------
1 | ## Test environments
2 | * local macOS install, R 4.3.1
3 | * Windows Server 2022 (on Github actions), R 4.3.3
4 | * macOS 12.7.3 (on Github actions), R 4.3.3
5 | * ubuntu 22.04.4 (on Github actions), R-devel
6 | * ubuntu 22.04.4 (on Github actions), R 4.3.3
7 | * ubuntu 22.04.4 (on Github actions), R 4.2.3
8 |
9 | ## R CMD check results
10 |
11 | 0 errors | 0 warnings | 0 notes
12 |
13 | ## Downstream dependencies
14 |
15 | There are currently no downstream dependencies for this package.
16 |
--------------------------------------------------------------------------------
/R/data.R:
--------------------------------------------------------------------------------
1 | #' Influenza in Maryland during the 1918 pandemic
2 | #'
3 | #'
4 | #' @format
5 | #' A data frame with 91 rows and 6 columns:
6 | #' \describe{
7 | #' \item{Date}{Date}
8 | #' \item{Baltimore}{Cases reported in the Baltimore}
9 | #' \item{Cumberland}{Cases reported in the Cumberland}
10 | #' \item{Lonaconing}{Cases reported in the Lonaconing}
11 | #' \item{Frederick}{Cases reported in the Frederick}
12 | #' \item{Salisbury}{Cases reported in the Salisbury}
13 | #'
14 | #' }
15 | #' @source
16 | "Maryland"
17 |
--------------------------------------------------------------------------------
/man/reexports.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/utils.R
3 | \docType{import}
4 | \name{reexports}
5 | \alias{reexports}
6 | \alias{\%>\%}
7 | \alias{timestep}
8 | \title{Objects exported from other packages}
9 | \keyword{internal}
10 | \description{
11 | These objects are imported from other packages. Follow the links
12 | below to see their documentation.
13 |
14 | \describe{
15 | \item{deSolve}{\code{\link[deSolve:deSolve-internal]{timestep}}}
16 |
17 | \item{magrittr}{\code{\link[magrittr:pipe]{\%>\%}}}
18 | }}
19 |
20 |
--------------------------------------------------------------------------------
/man/sd_stocks.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/summaries.R
3 | \name{sd_stocks}
4 | \alias{sd_stocks}
5 | \title{Summarise the information of a model's stocks in a data frame}
6 | \usage{
7 | sd_stocks(mdl)
8 | }
9 | \arguments{
10 | \item{mdl}{A list which is the output from read_xmile.}
11 | }
12 | \value{
13 | A data frame.
14 | }
15 | \description{
16 | Summarise the information of a model's stocks in a data frame
17 | }
18 | \examples{
19 | path <- system.file("models", "SIR.stmx", package = "readsdr")
20 | mdl <- read_xmile(path)
21 | sd_stocks(mdl)
22 | }
23 |
--------------------------------------------------------------------------------
/man/sd_constants.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/summaries.R
3 | \name{sd_constants}
4 | \alias{sd_constants}
5 | \title{Summarise the information of a model's constants in a data frame}
6 | \usage{
7 | sd_constants(mdl)
8 | }
9 | \arguments{
10 | \item{mdl}{A list which is the output from read_xmile.}
11 | }
12 | \value{
13 | A data frame.
14 | }
15 | \description{
16 | Summarise the information of a model's constants in a data frame
17 | }
18 | \examples{
19 | path <- system.file("models", "SIR.stmx", package = "readsdr")
20 | mdl <- read_xmile(path)
21 | sd_constants(mdl)
22 | }
23 |
--------------------------------------------------------------------------------
/man/sd_pulse_v.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/if_else_builtins.R
3 | \name{sd_pulse_v}
4 | \alias{sd_pulse_v}
5 | \title{Replicate the behaviour of the PULSE function from Vensim}
6 | \usage{
7 | sd_pulse_v(time, startPulse, duration)
8 | }
9 | \arguments{
10 | \item{time}{A number}
11 |
12 | \item{startPulse}{A number}
13 |
14 | \item{duration}{A number}
15 | }
16 | \value{
17 | A number
18 | }
19 | \description{
20 | Replicate the behaviour of the PULSE function from Vensim
21 | }
22 | \examples{
23 | timestep <- function() 0.25 # replicates timestep() from deSolve
24 | sd_pulse_v(1, 1, 2)
25 | }
26 |
--------------------------------------------------------------------------------
/R/par_trans.R:
--------------------------------------------------------------------------------
1 | #' Expit transformation
2 | #'
3 | #' @param x A real number
4 | #'
5 | #' @return A number in the range 0 to 1
6 | #' @export
7 | #'
8 | #' @examples
9 | #' expit(-3)
10 | expit <- function(x) 1 / (1 + exp(-x))
11 |
12 |
13 | #' Logit transformation
14 | #'
15 | #' @param p A real number that represents a probability
16 | #'
17 | #' @return An unconstrained real number
18 | #' @export
19 | #'
20 | #' @examples
21 | #' logit(0.5)
22 | logit <- function(p) log(p / (1 - p))
23 |
24 |
25 | #' Inverse of a number
26 | #'
27 | #' @param x A real number
28 | #'
29 | #' @return A real number
30 | #' @export
31 | #'
32 | #' @examples
33 | #' inv(0.5) # Should return 2
34 | inv <- function(x) 1/x
35 |
--------------------------------------------------------------------------------
/man/sd_pulse_s.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/if_else_builtins.R
3 | \name{sd_pulse_s}
4 | \alias{sd_pulse_s}
5 | \title{Replicate the behaviour of the PULSE function from Stella}
6 | \usage{
7 | sd_pulse_s(time, volume, start_p, interval)
8 | }
9 | \arguments{
10 | \item{time}{A number}
11 |
12 | \item{volume}{A number}
13 |
14 | \item{start_p}{A number}
15 |
16 | \item{interval}{A number}
17 | }
18 | \value{
19 | A number
20 | }
21 | \description{
22 | This function must be placed inside the object that will be passed as the
23 | argument \code{func} to deSolve's \code{ode} function.
24 | }
25 | \examples{
26 | timestep <- function() 0.25 # replicates timestep() from deSolve
27 | sd_pulse_s(2, 1, 2, 0)
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/R/simulator_utils.R:
--------------------------------------------------------------------------------
1 | configure_meas_models <- function(meas_mdl, meas_params, prior_vals) {
2 |
3 | for(meas_par_obj in meas_params) {
4 |
5 | # Parameter's name before the transformation
6 | before_name <- stringr::str_remove(meas_par_obj$par_name,
7 | paste0(meas_par_obj$par_trans, "_"))
8 |
9 | trans_value <- execute_trans(prior_vals[[meas_par_obj$par_name]],
10 | meas_par_obj$par_trans)
11 |
12 | configured_mdl <- lapply(trans_value, function(val) {
13 |
14 | lapply(meas_mdl, function(meas_obj) {
15 |
16 | stringr::str_replace(meas_obj, before_name,
17 | as.character(val))
18 | })
19 | })
20 | }
21 |
22 | configured_mdl
23 | }
24 |
--------------------------------------------------------------------------------
/man/sd_net_change.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/output_utils.R
3 | \name{sd_net_change}
4 | \alias{sd_net_change}
5 | \title{Estimate the net change of a stock in discrete times}
6 | \usage{
7 | sd_net_change(sim_df, cumulative_var)
8 | }
9 | \arguments{
10 | \item{sim_df}{A data frame with the simulation output}
11 |
12 | \item{cumulative_var}{A string that indicates to which variable the discrete
13 | change will be estimated}
14 | }
15 | \value{
16 | A dataframe.
17 | }
18 | \description{
19 | Estimate the net change of a stock in discrete times
20 | }
21 | \examples{
22 | test_output <- data.frame(time = seq(0, 2, by = 0.25),
23 | C = c(0, rep(5,4), rep(20, 4)))
24 | sd_net_change(test_output, "C")
25 | }
26 |
--------------------------------------------------------------------------------
/tests/testthat/test-priors.R:
--------------------------------------------------------------------------------
1 | test_that("sd_prior() returns the expected list", {
2 |
3 | actual <- sd_prior("par_beta", "lognormal", c(0, 1))
4 |
5 | expected <- list(par_name = "par_beta",
6 | dist = "lognormal",
7 | type = "constant",
8 | mu = 0,
9 | sigma = 1,
10 | min = 0)
11 |
12 | expect_equal(actual, expected)
13 |
14 | actual <- sd_prior("par_rho", "normal", c(0, 1), min = 0)
15 |
16 | expected <- list(par_name = "par_rho",
17 | dist = "normal",
18 | type = "constant",
19 | mu = 0,
20 | sigma = 1,
21 | min = 0)
22 |
23 | expect_equal(actual, expected)
24 | })
25 |
26 |
27 |
--------------------------------------------------------------------------------
/man/Maryland.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/data.R
3 | \docType{data}
4 | \name{Maryland}
5 | \alias{Maryland}
6 | \title{Influenza in Maryland during the 1918 pandemic}
7 | \format{
8 | A data frame with 91 rows and 6 columns:
9 | \describe{
10 | \item{Date}{Date}
11 | \item{Baltimore}{Cases reported in the Baltimore}
12 | \item{Cumberland}{Cases reported in the Cumberland}
13 | \item{Lonaconing}{Cases reported in the Lonaconing}
14 | \item{Frederick}{Cases reported in the Frederick}
15 | \item{Salisbury}{Cases reported in the Salisbury}
16 |
17 | }
18 | }
19 | \source{
20 |
21 | }
22 | \usage{
23 | Maryland
24 | }
25 | \description{
26 | Influenza in Maryland during the 1918 pandemic
27 | }
28 | \keyword{datasets}
29 |
--------------------------------------------------------------------------------
/R/sanitise_xml.R:
--------------------------------------------------------------------------------
1 | sanitise_xml <- function(xml_as_text) {
2 | # pattern equations xml
3 | pattern_eq <- stringr::regex("(.+?)", dotall = TRUE)
4 | equations <- stringr::str_match_all(xml_as_text, pattern_eq)[[1]][, 2]
5 | n_eq <- length(equations)
6 |
7 | # Sanitised equations
8 | snt_equations <- equations %>% stringr::str_replace_all(">", ">") %>%
9 | stringr::str_replace_all("<", "<")
10 | sanitised_xml <- paste0("", snt_equations, "")
11 |
12 | pos_tags <- stringr::str_locate_all(xml_as_text, pattern_eq)[[1]]
13 |
14 | for(i in seq_len(nrow(pos_tags))) {
15 | pos_tags <- stringr::str_locate_all(xml_as_text, pattern_eq)[[1]]
16 | pos <- pos_tags[i, ]
17 | stringr::str_sub(xml_as_text, pos[[1]], pos[[2]]) <- sanitised_xml[[i]]
18 | }
19 |
20 | xml_as_text
21 | }
22 |
--------------------------------------------------------------------------------
/tests/testthat/test-simulator_utils.R:
--------------------------------------------------------------------------------
1 | test_that("configure_meas_models() returns the expected list", {
2 |
3 | meas_mdl <- list("y ~ neg_binomial_2(net_flow(C), phi)")
4 |
5 | meas_params <- list(list(par_name = "inv_phi",
6 | dist = "exponential",
7 | beta = 5,
8 | min = 0,
9 | type = "meas_par",
10 | par_trans = "inv"))
11 |
12 | prior_vals <- list(inv_phi = c(0.62781087, 0.04486981))
13 |
14 | actual <- configure_meas_models(meas_mdl, meas_params, prior_vals)
15 |
16 | expected <- list(list("y ~ neg_binomial_2(net_flow(C), 1.59283639036068)"),
17 | list("y ~ neg_binomial_2(net_flow(C), 22.2867001219751)"))
18 |
19 | expect_equal(actual, expected)
20 | })
21 |
--------------------------------------------------------------------------------
/tests/testthat/test-translate_time_builtins.R:
--------------------------------------------------------------------------------
1 | test_that("translate_time_builtins() returns the expected translation", {
2 | test_equation <- "ifelse(TIME > 0, DT, 0)"
3 | actual_val <- translate_time_builtins(test_equation)
4 | expected_val <- "ifelse(time > 0, timestep(), 0)"
5 | expect_equal(actual_val, expected_val)
6 | })
7 |
8 | test_that("translate_time() returns the expected translation", {
9 | test_equation <- "Time + startTime"
10 | actual_val <- translate_time(test_equation)
11 | expected_val <- "time + startTime"
12 | expect_equal(actual_val, expected_val)
13 | })
14 |
15 | test_that("translate_dt() returns the expected translation", {
16 | test_equation <- "volume / DT"
17 | actual_val <- translate_dt(test_equation)
18 | expected_val <- "volume / timestep()"
19 | expect_equal(actual_val, expected_val)
20 | })
21 |
--------------------------------------------------------------------------------
/R/translate_math_funs.R:
--------------------------------------------------------------------------------
1 | translate_math_funs <- function(equation) {
2 |
3 | new_equation <- translate_ABS(equation)
4 | new_equation <- translate_SQRT(new_equation)
5 | new_equation <- translate_EXP(new_equation)
6 | new_equation
7 | }
8 |
9 | translate_ABS <- function(equation) {
10 |
11 | fun_name <- "ABS"
12 | downcase_fun_name(fun_name, equation)
13 | }
14 |
15 | translate_SQRT <- function(equation) {
16 |
17 | fun_name <- "SQRT"
18 | downcase_fun_name(fun_name, equation)
19 | }
20 |
21 | translate_EXP <- function(equation) {
22 |
23 | fun_name <- "EXP"
24 | downcase_fun_name(fun_name, equation)
25 | }
26 |
27 | downcase_fun_name <- function(fun_name, equation) {
28 |
29 | pattern <- paste0("\\b", fun_name, "\\b")
30 | rgx <- stringr::regex(pattern, ignore_case = TRUE)
31 | stringr::str_replace_all(equation, rgx, tolower(fun_name))
32 | }
33 |
--------------------------------------------------------------------------------
/man/extract_timeseries_var.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/stan_postprocessing.R
3 | \name{extract_timeseries_var}
4 | \alias{extract_timeseries_var}
5 | \title{Extract the values over time of a variable from a Stan fit}
6 | \usage{
7 | extract_timeseries_var(var_name, posterior_df)
8 | }
9 | \arguments{
10 | \item{var_name}{A string that indicates the variable's name for which the
11 | function will construct the timeseries.}
12 |
13 | \item{posterior_df}{A Stan fit object converted into a data frame}
14 | }
15 | \value{
16 | A data frame
17 | }
18 | \description{
19 | Extract the values over time of a variable from a Stan fit
20 | }
21 | \examples{
22 | posterior_df <- data.frame(`var[1]` = rep(0, 2), `var[2]` = rep(1, 2),
23 | check.names = FALSE)
24 | extract_timeseries_var("var", posterior_df)
25 | }
26 |
--------------------------------------------------------------------------------
/man/sd_fixed_delay.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/delay.R
3 | \name{sd_fixed_delay}
4 | \alias{sd_fixed_delay}
5 | \title{Fixed delay}
6 | \usage{
7 | sd_fixed_delay(var, time, delay, init, .memory)
8 | }
9 | \arguments{
10 | \item{var}{A string that indicates the delayed variable.}
11 |
12 | \item{time}{A number that indicates current simulation time.}
13 |
14 | \item{delay}{A number that indicates the delay time.}
15 |
16 | \item{init}{A number that indicates the function's output value of at the
17 | start of the simulation.}
18 |
19 | \item{.memory}{A data frame that keeps past values of delayed variables.}
20 | }
21 | \value{
22 | A number.
23 | }
24 | \description{
25 | Fixed delay
26 | }
27 | \examples{
28 | .memory <- data.frame(time = 3, inflow = 3)
29 | rownames(.memory) <- 3
30 | sd_fixed_delay("inflow", 5, 2, 0, .memory)
31 | }
32 |
--------------------------------------------------------------------------------
/man/sd_pulse_train.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/if_else_builtins.R
3 | \name{sd_pulse_train}
4 | \alias{sd_pulse_train}
5 | \title{PULSE TRAIN}
6 | \usage{
7 | sd_pulse_train(time, start_pulse, duration_pulse, repeat_pt, end_pulse)
8 | }
9 | \arguments{
10 | \item{time}{A numeric argument that indicates the current simulation time}
11 |
12 | \item{start_pulse}{A numeric argument that indicates the start of the pulse}
13 |
14 | \item{duration_pulse}{A numeric argument that indicates the width of the pulse}
15 |
16 | \item{repeat_pt}{A numeric argument that indicates the repetition pattern}
17 |
18 | \item{end_pulse}{A numeric argument that indicates the end of the sequence}
19 | }
20 | \value{
21 | 1 during the pulse, 0 otherwise.
22 | }
23 | \description{
24 | PULSE TRAIN
25 | }
26 | \examples{
27 | sd_pulse_train(5, 5, 3, 10, 20)
28 | }
29 |
--------------------------------------------------------------------------------
/tests/testthat/test-stat_funs.R:
--------------------------------------------------------------------------------
1 | test_that("translate_NORMAL returns the expected string", {
2 | expect_equal(translate_NORMAL("NORMAL(1,10)", "isee"),
3 | "rnorm(1,1,10)")
4 |
5 | expect_equal(translate_NORMAL("x + NORMAL(1,10)", "isee"),
6 | "x + rnorm(1,1,10)")
7 | })
8 |
9 | test_that("translate_NORMAL throws an error when NORMAL has more than two params", {
10 | expect_error(translate_NORMAL("NORMAL(0,1,1)", "isee"),
11 | "readsdr is restricted to translate NORMAL functions with only two parameters: mean, std_dev.")
12 | })
13 |
14 | test_that("translate_NORMAL returns the expected string for an equation from Vensim", {
15 |
16 | equation <- "RANDOM_NORMAL(0,200,Mean_of_Demand,Sd_of_Demand,0)"
17 | actual <- translate_NORMAL(equation, "Vensim")
18 | expected <- "truncnorm::rtruncnorm(1,0,200,Mean_of_Demand,Sd_of_Demand)"
19 |
20 | expect_equal(actual, expected)
21 | })
22 |
--------------------------------------------------------------------------------
/NAMESPACE:
--------------------------------------------------------------------------------
1 | # Generated by roxygen2: do not edit by hand
2 |
3 | export("%>%")
4 | export(create_stan_function)
5 | export(expit)
6 | export(extract_timeseries_stock)
7 | export(extract_timeseries_var)
8 | export(inv)
9 | export(logit)
10 | export(read_xmile)
11 | export(sd_Bayes)
12 | export(sd_conf_intervals)
13 | export(sd_constants)
14 | export(sd_data_generator_fun)
15 | export(sd_fixed_delay)
16 | export(sd_impact_inputs)
17 | export(sd_interpret_estimates)
18 | export(sd_loglik_fun)
19 | export(sd_measurements)
20 | export(sd_net_change)
21 | export(sd_posterior_fun)
22 | export(sd_prior)
23 | export(sd_prior_checks)
24 | export(sd_pulse_s)
25 | export(sd_pulse_train)
26 | export(sd_pulse_v)
27 | export(sd_sensitivity_run)
28 | export(sd_simulate)
29 | export(sd_stocks)
30 | export(sd_what_if_from_time)
31 | export(stan_ode_function)
32 | export(timestep)
33 | export(xmile_to_deSolve)
34 | importFrom(deSolve,timestep)
35 | importFrom(magrittr,"%>%")
36 |
--------------------------------------------------------------------------------
/R/output_utils.R:
--------------------------------------------------------------------------------
1 | #' Estimate the net change of a stock in discrete times
2 | #'
3 | #' @param sim_df A data frame with the simulation output
4 | #' @param cumulative_var A string that indicates to which variable the discrete
5 | #' change will be estimated
6 | #'
7 | #' @return A dataframe.
8 | #' @export
9 | #'
10 | #' @examples
11 | #' test_output <- data.frame(time = seq(0, 2, by = 0.25),
12 | #' C = c(0, rep(5,4), rep(20, 4)))
13 | #' sd_net_change(test_output, "C")
14 |
15 | sd_net_change <- function(sim_df, cumulative_var) {
16 |
17 | temp_df <- sim_df[, c("time", cumulative_var)]
18 | temp_df <- temp_df[temp_df$time - trunc(temp_df$time) == 0, ]
19 | cml_vals <- temp_df[ , cumulative_var]
20 | temp_df$value <- cml_vals - dplyr::lag(cml_vals )
21 | temp_df <- dplyr::slice(temp_df, -1)
22 | temp_df$var <- paste0("delta_", cumulative_var)
23 |
24 | temp_df[, c("time", "value", "var")]
25 | }
26 |
--------------------------------------------------------------------------------
/man/sd_conf_intervals.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/interpreters.R
3 | \name{sd_conf_intervals}
4 | \alias{sd_conf_intervals}
5 | \title{Calculate confidence intervals}
6 | \usage{
7 | sd_conf_intervals(estimates, par_list, hsn, conf_level = 0.95)
8 | }
9 | \arguments{
10 | \item{estimates}{A list or data frame}
11 |
12 | \item{par_list}{A list}
13 |
14 | \item{hsn}{Hessian matrix}
15 |
16 | \item{conf_level}{A numeric input indicating the confidence level}
17 | }
18 | \value{
19 | A data frame.
20 | }
21 | \description{
22 | Calculate confidence intervals
23 | }
24 | \examples{
25 | estimates <- c(-0.2630303, 1.5788579)
26 | par_list <- list(list(par_name = "par_inv_R0",
27 | par_trans = "expit"),
28 | list(par_name = "I0",
29 | par_trans = "exp"))
30 | hsn <- matrix(c(3513.10521, -493.5469626,
31 | -493.5469626, 88.4871290), ncol = 2)
32 | sd_conf_intervals(estimates, par_list, hsn)
33 | }
34 |
--------------------------------------------------------------------------------
/man/sd_interpret_estimates.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/interpreters.R
3 | \name{sd_interpret_estimates}
4 | \alias{sd_interpret_estimates}
5 | \title{Interpret estimates}
6 | \usage{
7 | sd_interpret_estimates(estimates, par_list)
8 | }
9 | \arguments{
10 | \item{estimates}{A list or data frame}
11 |
12 | \item{par_list}{A list}
13 | }
14 | \value{
15 | A data frame
16 | }
17 | \description{
18 | Interpret estimates
19 | }
20 | \examples{
21 | estimates <- c(par_beta = 0,
22 | par_rho = 0.8472979,
23 | I0 = 0,
24 | inv_phi = -2.302585)
25 |
26 | par_list <- list(list(par_name = "par_beta",
27 | par_trans = "exp"),
28 | list(par_name = "par_rho",
29 | par_trans = "expit"),
30 | list(par_name = "I0",
31 | par_trans = "exp"),
32 | list(par_name = "phi",
33 | par_trans = c("exp", "inv")))
34 | sd_interpret_estimates(estimates, par_list)
35 | }
36 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2019 Jair Andrade
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/R/summaries.R:
--------------------------------------------------------------------------------
1 |
2 | #' Summarise the information of a model's constants in a data frame
3 | #'
4 | #' @param mdl A list which is the output from read_xmile.
5 | #'
6 | #' @return A data frame.
7 | #' @export
8 | #'
9 | #' @examples
10 | #' path <- system.file("models", "SIR.stmx", package = "readsdr")
11 | #' mdl <- read_xmile(path)
12 | #' sd_constants(mdl)
13 | sd_constants <- function(mdl) {
14 | consts_list <- lapply(mdl$description$constants, function(const_list) {
15 | data.frame(name = const_list$name, value = const_list$value)
16 | })
17 |
18 | do.call("rbind", consts_list)
19 | }
20 |
21 | #' Summarise the information of a model's stocks in a data frame
22 | #'
23 | #' @inheritParams sd_constants
24 | #'
25 | #' @return A data frame.
26 | #' @export
27 | #'
28 | #' @examples
29 | #' path <- system.file("models", "SIR.stmx", package = "readsdr")
30 | #' mdl <- read_xmile(path)
31 | #' sd_stocks(mdl)
32 | sd_stocks <- function(mdl) {
33 | lvls_list <- lapply(mdl$description$levels, function(lvl_list) {
34 | data.frame(name = lvl_list$name, init_value = lvl_list$initValue)
35 | })
36 | do.call("rbind", lvls_list)
37 | }
38 |
--------------------------------------------------------------------------------
/man/sd_simulate.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/simulators.R
3 | \name{sd_simulate}
4 | \alias{sd_simulate}
5 | \title{Simulate a System Dynamics model}
6 | \usage{
7 | sd_simulate(
8 | ds_inputs,
9 | start_time = NULL,
10 | stop_time = NULL,
11 | timestep = NULL,
12 | integ_method = "euler"
13 | )
14 | }
15 | \arguments{
16 | \item{ds_inputs}{A list of deSolve inputs generated by read_xmile}
17 |
18 | \item{start_time}{A number indicating the time at which the simulation begins.}
19 |
20 | \item{stop_time}{A number indicating the time at which the simulation ends.}
21 |
22 | \item{timestep}{A number indicating the time interval for the simulation.
23 | Also known as \code{dt}.}
24 |
25 | \item{integ_method}{A string indicating the integration method. It can be
26 | either "euler" or "rk4"}
27 | }
28 | \value{
29 | a data frame
30 | }
31 | \description{
32 | Simulate a System Dynamics model
33 | }
34 | \examples{
35 | path <- system.file("models", "SIR.stmx", package = "readsdr")
36 | ds_inputs <- xmile_to_deSolve(path)
37 | sd_simulate(ds_inputs, 0, 1, 0.25, "rk4")
38 | }
39 |
--------------------------------------------------------------------------------
/tests/testthat/test-comparison_operators.R:
--------------------------------------------------------------------------------
1 | context("Translate equal sign")
2 |
3 | test_that("translate_equal_sign() translates equal operator", {
4 | actual_val <- translate_equal_sign('a = b')
5 | expected_val <- 'a == b'
6 | expect_equal(actual_val, expected_val)
7 | })
8 |
9 | test_that("translate_equal_sign() does not misinterpret greater or equal to", {
10 | actual_val <- translate_equal_sign('a >= b')
11 | expected_val <- 'a >= b'
12 | expect_equal(actual_val, expected_val)
13 | })
14 |
15 | test_that("translate_equal_sign ignores the correct equal operator", {
16 | actual_val <- translate_equal_sign('a == b')
17 | expected_val <- 'a == b'
18 | expect_equal(actual_val, expected_val)
19 | })
20 |
21 | test_that("translate_equal_sign ignores the not equal operator", {
22 | actual_val <- translate_equal_sign('a != b')
23 | expected_val <- 'a != b'
24 | expect_equal(actual_val, expected_val)
25 | })
26 |
27 | context("Translate not equal sign")
28 |
29 | test_that("translate_not_equal_sign() translates the not equal operator", {
30 | actual_val <- translate_not_equal_sign('a <> b')
31 | expected_val <- 'a != b'
32 | expect_equal(actual_val, expected_val)
33 | })
34 |
--------------------------------------------------------------------------------
/tests/testthat/test-stan_postprocessing.R:
--------------------------------------------------------------------------------
1 | test_that("extract_timeseries_var() returns the expected data frame", {
2 | test_df <- data.frame(`var[1]` = rep(0, 2),
3 | `var[2]` = rep(1, 2),
4 | check.names = FALSE)
5 |
6 | expected_df <- data.frame(iter = rep(1:2, 2),
7 | time = rep(1:2, each = 2),
8 | variable = "var",
9 | value = c(0, 0, 1, 1))
10 |
11 | expect_equal(extract_timeseries_var("var", test_df), expected_df)
12 |
13 | })
14 |
15 | test_that("extract_timeseries_stock() returns the expected data frame", {
16 | test_df <- data.frame(`yhat[1,2]` = rep(0, 2),
17 | `yhat[2,2]` = rep(1, 2),
18 | check.names = FALSE)
19 |
20 | expected_df <- data.frame(iter = rep(1:2, 2),
21 | time = rep(1:2, each = 2),
22 | stock = "S2",
23 | value = c(0, 0, 1, 1))
24 |
25 | test_stocks <- c("S1", "S2")
26 |
27 | expect_equal(extract_timeseries_stock("S2", test_df, test_stocks, "yhat"),
28 | expected_df)
29 | })
30 |
--------------------------------------------------------------------------------
/tests/testthat/test-summaries.R:
--------------------------------------------------------------------------------
1 | mdl <- list(description = list(
2 | constants = list(
3 | list(name = "birth_rate",
4 | value = 0.1),
5 | list(name = "death_rate",
6 | value = 0.1)
7 | ),
8 | levels = list(
9 | list(name = "Population",
10 | initValue = 100),
11 | list(name = "Deaths",
12 | initValue = 0)
13 | )))
14 |
15 | test_that("sd_constants() returns the expected data frame", {
16 | expect_is(sd_constants(mdl), "data.frame")
17 | expect_equal(sd_constants(mdl),
18 | data.frame(name = c("birth_rate", "death_rate"),
19 | value = c(0.1,0.1)))
20 | })
21 |
22 | test_that("sd_stocks() returns the expected data frame", {
23 | expect_is(sd_stocks(mdl), "data.frame")
24 | expect_equal(sd_stocks(mdl),
25 | data.frame(name = c("Population", "Deaths"),
26 | init_value = c(100, 0)))
27 |
28 | filepath <- system.file("models/", "SIR.stmx", package = "readsdr")
29 | mdl <- read_xmile(filepath)
30 | actual_df <- sd_stocks(mdl)
31 | expected_df <- data.frame(name = "Susceptible", "Infected", "Recovered",
32 | init_value = c(990, 10, 0))
33 | })
34 |
--------------------------------------------------------------------------------
/man/extract_timeseries_stock.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/stan_postprocessing.R
3 | \name{extract_timeseries_stock}
4 | \alias{extract_timeseries_stock}
5 | \title{Extract the values over time of a stock from a Stan fit}
6 | \usage{
7 | extract_timeseries_stock(stock_name, posterior_df, all_stocks, ODE_output)
8 | }
9 | \arguments{
10 | \item{stock_name}{A string that indicates the stock's name for which the
11 | function will construct the timeseries.}
12 |
13 | \item{posterior_df}{A Stan fit object converted into a data frame}
14 |
15 | \item{all_stocks}{A vector of strings that contains the names of all the
16 | stocks in the model. This vector must have the same order as the differential
17 | equations in the Stan code.}
18 |
19 | \item{ODE_output}{A string that indicates the name of the variable where
20 | model's output in stored in Stan.}
21 | }
22 | \value{
23 | A data frame
24 | }
25 | \description{
26 | Extract the values over time of a stock from a Stan fit
27 | }
28 | \examples{
29 | posterior_df <- data.frame(`yhat[1,2]` = rep(0, 2), `yhat[2,2]` = rep(1, 2),
30 | check.names = FALSE)
31 | stocks <- c("S1", "S2")
32 | extract_timeseries_stock("S2", posterior_df, stocks, "yhat")
33 | }
34 |
--------------------------------------------------------------------------------
/man/sd_impact_inputs.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/impact_inputs.R
3 | \name{sd_impact_inputs}
4 | \alias{sd_impact_inputs}
5 | \title{Construct inputs for performing structural analysis via the impact method}
6 | \usage{
7 | sd_impact_inputs(desc_list)
8 | }
9 | \arguments{
10 | \item{desc_list}{Element 'description' from the list returned by \code{read_xmile()}}
11 | }
12 | \value{
13 | A list of three elements. The first element, \code{flows}, is a data
14 | frame that lists all the stock-flow links in the model. Further, this data
15 | frame describes the equation that governs the link and whether the link is
16 | an inflow (+) or an outflow (-). The second element, \code{pathways}, is a
17 | data frame that lists all the pathways among stocks. The third element,
18 | \code{velocities}, is a data frame in which each row corresponds to a
19 | stock. Each row consists of two columns (name & equation).
20 | }
21 | \description{
22 | Construct inputs for performing structural analysis via the impact method
23 | }
24 | \examples{
25 | filepath <- system.file("models/", "SIR.stmx", package = "readsdr")
26 | mdl <- read_xmile(filepath)
27 | desc_list <- mdl$description
28 | sd_impact_inputs(desc_list)
29 | }
30 |
--------------------------------------------------------------------------------
/R/logical_operators.R:
--------------------------------------------------------------------------------
1 |
2 | translate_logical_operators <- function(equation, vendor) {
3 | equation %>%
4 | translate_AND(vendor) %>%
5 | translate_OR(vendor) %>%
6 | translate_NOT(vendor)
7 | }
8 |
9 | translate_AND <- function(equation, vendor) {
10 | if(vendor == "Vensim") {
11 | equation <- stringr::str_replace_all(equation, ":AND:", "&")
12 | }
13 |
14 | if(vendor == "isee") {
15 | pattern <- stringr::regex("AND(?=\\(.+\\))", ignore_case = TRUE)
16 | equation <- stringr::str_replace_all(equation, pattern, "&")
17 | }
18 |
19 | equation
20 | }
21 |
22 | translate_OR <- function(equation, vendor) {
23 | if(vendor == "Vensim") {
24 | equation <- stringr::str_replace_all(equation, ":OR:", "|")
25 | }
26 |
27 | if(vendor == "isee") {
28 | pattern <- stringr::regex("OR(?=\\(.+\\))", ignore_case = TRUE)
29 | equation <- stringr::str_replace_all(equation , pattern, "|")
30 | }
31 |
32 | equation
33 | }
34 |
35 | translate_NOT <- function(equation, vendor) {
36 |
37 | if(vendor == "Vensim") {
38 | equation <- stringr::str_replace_all(equation, ":NOT:", "!")
39 | }
40 |
41 | if(vendor == "isee") {
42 | pattern <- stringr::regex("NOT(?=\\(.+\\))", ignore_case = TRUE)
43 | equation <- stringr::str_replace_all(equation , pattern, "!")
44 | }
45 | equation
46 | }
47 |
--------------------------------------------------------------------------------
/tests/testthat/test-prior_checks.R:
--------------------------------------------------------------------------------
1 | test_that("prior_checks() returns the expected list", {
2 |
3 | filepath <- system.file("models/", "SEIR.stmx", package = "readsdr")
4 |
5 | meas_mdl <- list("y ~ neg_binomial_2(net_flow(C), phi)")
6 |
7 | estimated_params <- list(
8 | sd_prior("par_beta", "lognormal", c(0, 1)),
9 | sd_prior("par_rho", "beta", c(2, 2)),
10 | sd_prior("I0", "lognormal", c(0, 1), "init"))
11 |
12 | set.seed(666)
13 |
14 | actual <- sd_prior_checks(filepath, meas_mdl, estimated_params, n_draws = 2,
15 | start_time = 0, stop_time = 5,
16 | integ_method = "rk4", timestep = 1/32)
17 |
18 | df1 <- data.frame(iter = c(1,2),
19 | par_beta = c(2.124021, 7.495888),
20 | par_rho = c(0.4005852, 0.9374636),
21 | I0 = c(0.1089491, 2.1348496),
22 | inv_phi = c(0.5222716, 0.3047205))
23 |
24 | df2 <- data.frame(iter = rep(c(1, 2), each = 5),
25 | time = rep(1:5, 2),
26 | var_name = "y",
27 | measurement = c(0, 0, 0, 0, 2, 5, 27, 30, 406, 544))
28 |
29 | expected <- list(parameters = df1,
30 | measurements = df2)
31 |
32 | expect_equal(actual, expected, tolerance = 1e-7)
33 | })
34 |
--------------------------------------------------------------------------------
/man/xmile_to_deSolve.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/read_xmile.R
3 | \name{xmile_to_deSolve}
4 | \alias{xmile_to_deSolve}
5 | \title{Parse XMILE to deSolve components}
6 | \usage{
7 | xmile_to_deSolve(filepath)
8 | }
9 | \arguments{
10 | \item{filepath}{A string that indicates a path to a file with extension .stmx
11 | or .xmile. Vensim files (.mdl) are not xmile files. They must be exported
12 | from Vensim with extension .xmile}
13 | }
14 | \value{
15 | This function returns a list with at least four elements.
16 | \emph{stocks}, a numeric vector that contains initial values. \emph{consts},
17 | a numeric vector with the model's constants. \emph{func}, the function that
18 | wraps the model's equations. \emph{sim_params}, a list with control
19 | parameters. If the model includes a table or graphical function, this
20 | function returns the element \emph{graph_funs}, a list with these functions.
21 | }
22 | \description{
23 | \code{xmile_to_deSolve} returns a list that serves as an input for
24 | deSolve's ODE function.
25 | }
26 | \details{
27 | This function extracts the xml from the file specified via \code{filepath}
28 | to generate a list with the necessary elements to simulate with
29 | \link[deSolve]{deSolve}.
30 | }
31 | \examples{
32 | path <- system.file("models", "SIR.stmx", package = "readsdr")
33 | xmile_to_deSolve(path)
34 | }
35 |
--------------------------------------------------------------------------------
/DESCRIPTION:
--------------------------------------------------------------------------------
1 | Package: readsdr
2 | Type: Package
3 | Title: Translate Models from System Dynamics Software into 'R'
4 | Version: 0.3.0.9001
5 | Authors@R: person("Jair", "Andrade", email = "jair.albert.andrade@gmail.com",
6 | role = c("aut", "cre"),
7 | comment = c(ORCID = "0000-0002-1412-7868"))
8 | Description: The goal of 'readsdr' is to bridge the design capabilities from
9 | specialised System Dynamics software with the powerful numerical tools
10 | offered by 'R' libraries. The package accomplishes this goal by parsing
11 | 'XMILE' files ('Vensim' and 'Stella') models into 'R' objects to construct
12 | networks (graph theory); 'ODE' functions for 'Stan'; and inputs to simulate
13 | via 'deSolve' as described in Duggan (2016) .
14 | License: MIT + file LICENSE
15 | Encoding: UTF-8
16 | LazyData: true
17 | RoxygenNote: 7.3.1
18 | Suggests:
19 | testthat (>= 2.1.0),
20 | igraph,
21 | knitr,
22 | rmarkdown,
23 | ggplot2,
24 | tidyr,
25 | truncnorm
26 | Imports:
27 | stringr,
28 | xml2,
29 | purrr,
30 | dplyr,
31 | rlang,
32 | stringi,
33 | magrittr,
34 | stats,
35 | deSolve,
36 | utils,
37 | future,
38 | future.apply,
39 | progressr
40 | BugReports: https://github.com/jandraor/readsdr/issues
41 | VignetteBuilder: knitr
42 | Depends:
43 | R (>= 4.1.0)
44 |
--------------------------------------------------------------------------------
/man/stan_ode_function.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/stan_ode_function.R
3 | \name{stan_ode_function}
4 | \alias{stan_ode_function}
5 | \title{Create Stan ODE function}
6 | \usage{
7 | stan_ode_function(
8 | filepath,
9 | func_name,
10 | pars = NULL,
11 | const_list = NULL,
12 | extra_funs = NULL,
13 | XMILE_structure
14 | )
15 | }
16 | \arguments{
17 | \item{filepath}{A string that indicates a path to a file with extension .stmx
18 | or .xmile. Vensim files (.mdl) are not xmile files. They must be exported
19 | from Vensim with extension .xmile}
20 |
21 | \item{func_name}{A string for naming the ODE function}
22 |
23 | \item{pars}{A character vector that indicates which constants will be
24 | considered as parameters in the ODE function}
25 |
26 | \item{const_list}{A list in which each element's name is the name of the
27 | constant to override and the element's value correspond to the new value.}
28 |
29 | \item{extra_funs}{A vector of strings. Each string corresponds to a
30 | user-defined function.}
31 |
32 | \item{XMILE_structure}{A list.}
33 | }
34 | \value{
35 | A string with the code containing a function with the model's
36 | equations in the format required by cmdstan 2.24+.
37 | }
38 | \description{
39 | Create Stan ODE function
40 | }
41 | \examples{
42 | path <- system.file("models", "SIR.stmx", package = "readsdr")
43 | stan_ode_function(path, "my_model")
44 | }
45 |
--------------------------------------------------------------------------------
/tests/testthat/test-Vensim_workarounds.R:
--------------------------------------------------------------------------------
1 | test_that("extract_vars_in_stocks handles DELAY FIXED from Vensim", {
2 | test_xml <- xml2::read_xml('
3 |
4 |
5 |
6 |
7 |
8 | out=
9 | DELAY_FIXED(inflow, 2, 0)
10 |
11 |
12 |
13 |
14 | 0
15 |
16 |
17 | inflow
18 |
19 |
20 | outflow
21 |
22 |
23 |
24 | 3 + STEP(3, 2)
25 |
26 |
27 |
28 | ')
29 |
30 | stocks_xml <- xml2::xml_find_all(test_xml, ".//d1:stock")
31 |
32 | vars_and_consts <- list(
33 | variables = list(
34 | list(name = "inflow",
35 | equation = "3+ifelse(time>=3,3,0)")),
36 | constants = list(),
37 | builtin_stocks = list())
38 |
39 | actual_obj <- extract_vars_in_stocks(stocks_xml, vars_and_consts)
40 |
41 | expected_obj <- list(
42 | variables = list(
43 | list(name = "inflow",
44 | equation = "3+ifelse(time>=3,3,0)"),
45 | list(name = "outflow",
46 | equation = "sd_fixed_delay('inflow',time,2,0,.memory)")),
47 | constants = list(),
48 | builtin_stocks = list())
49 |
50 | expect_equal(actual_obj, expected_obj)
51 | })
52 |
--------------------------------------------------------------------------------
/R/utils.R:
--------------------------------------------------------------------------------
1 | #' @importFrom magrittr %>%
2 | #' @export
3 | magrittr::`%>%`
4 |
5 | #' @importFrom deSolve timestep
6 | #' @export
7 | deSolve::timestep
8 |
9 | remove_NULL <- function(x.list) {
10 | x.list[unlist(lapply(x.list, length) != 0)]
11 | }
12 |
13 | get_names <- function(obj_list, name_var = "name") {
14 | sapply(obj_list, function(obj) obj[[name_var]])
15 | }
16 |
17 | get_raw_names <- function(obj_list, name_var) {
18 |
19 | purrr::map_chr(obj_list, function(obj) {
20 |
21 | name <- obj[[name_var]]
22 |
23 | if("par_trans" %in% names(obj)) {
24 |
25 | name <- stringr::str_remove(name, paste0(obj$par_trans, "_"))
26 | }
27 |
28 | name
29 | })
30 | }
31 |
32 | as_row_list <- function(df) do.call(function(...) Map(list,...), df)
33 |
34 | execute_trans <- function(val, trans_type, return_type = "numeric") {
35 |
36 | if(trans_type == "inv" & return_type == "numeric") return(1/val)
37 |
38 | if(trans_type == "inv" & return_type == "text") return(paste0("1/", val))
39 | }
40 |
41 | is_string_numeric <- function(x) suppressWarnings(ifelse(!is.na(as.numeric(x)),
42 | TRUE, FALSE))
43 |
44 | # This function guarantees that consts and auxs have identical properties
45 | format_consts_as_vars <- function(constants) {
46 |
47 | lapply(constants, function(const) {
48 | list(name = const$name, equation = const$value)
49 | })
50 | }
51 |
52 | df2list <- function(df) do.call(function(...) Map(list,...), df)
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/.github/workflows/R-CMD-check.yaml:
--------------------------------------------------------------------------------
1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
3 | on:
4 | push:
5 | branches: [main, master]
6 | pull_request:
7 | branches: [main, master]
8 |
9 | name: R-CMD-check
10 |
11 | jobs:
12 | R-CMD-check:
13 | runs-on: ${{ matrix.config.os }}
14 |
15 | name: ${{ matrix.config.os }} (${{ matrix.config.r }})
16 |
17 | strategy:
18 | fail-fast: false
19 | matrix:
20 | config:
21 | - {os: macos-latest, r: 'release'}
22 | - {os: windows-latest, r: 'release'}
23 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'}
24 | - {os: ubuntu-latest, r: 'release'}
25 | - {os: ubuntu-latest, r: 'oldrel-1'}
26 |
27 | env:
28 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
29 | R_KEEP_PKG_SOURCE: yes
30 |
31 | steps:
32 | - uses: actions/checkout@v3
33 |
34 | - uses: r-lib/actions/setup-pandoc@v2
35 |
36 | - uses: r-lib/actions/setup-r@v2
37 | with:
38 | r-version: ${{ matrix.config.r }}
39 | http-user-agent: ${{ matrix.config.http-user-agent }}
40 | use-public-rspm: true
41 |
42 | - uses: r-lib/actions/setup-r-dependencies@v2
43 | with:
44 | extra-packages: any::rcmdcheck
45 | needs: check
46 |
47 | - uses: r-lib/actions/check-r-package@v2
48 | with:
49 | upload-snapshots: true
50 |
--------------------------------------------------------------------------------
/tests/testthat/SEIR_C_meas.stan:
--------------------------------------------------------------------------------
1 | // Code generated by the R package readsdr v0.3.0.9001
2 | // See more info at github https://github.com/jandraor/readsdr
3 | functions {
4 | vector X_model(real time, vector y, array[] real params) {
5 | vector[5] dydt;
6 | real S_to_E;
7 | real E_to_I;
8 | real I_to_R;
9 | real C_in;
10 | S_to_E = params[1]*y[1]*y[3]/10000;
11 | E_to_I = 0.5*y[2];
12 | I_to_R = 0.5*y[3];
13 | C_in = params[2]*E_to_I;
14 | dydt[1] = -S_to_E;
15 | dydt[2] = S_to_E-E_to_I;
16 | dydt[3] = E_to_I-I_to_R;
17 | dydt[4] = I_to_R;
18 | dydt[5] = C_in;
19 | return dydt;
20 | }
21 | }
22 | data {
23 | int n_obs;
24 | array[n_obs] int y;
25 | array[n_obs] real ts;
26 | }
27 | parameters {
28 | real par_beta;
29 | real par_rho;
30 | real I0;
31 | }
32 | transformed parameters{
33 | array[n_obs] vector[5] x; // Output from the ODE solver
34 | array[2] real params;
35 | vector[5] x0; // init values
36 | x0[1] = (10000) - I0; // S
37 | x0[2] = 0; // E
38 | x0[3] = I0; // I
39 | x0[4] = 0; // R
40 | x0[5] = I0; // C
41 | params[1] = par_beta;
42 | params[2] = par_rho;
43 | x = ode_rk45(X_model, x0, 0, ts, params);
44 | }
45 | model {
46 | par_beta ~ lognormal(0, 1);
47 | par_rho ~ beta(2, 2);
48 | I0 ~ lognormal(0, 1);
49 | y ~ poisson(x[:, 5]);
50 | }
51 | generated quantities {
52 | real log_lik;
53 | array[n_obs] int sim_y;
54 | log_lik = poisson_lpmf(y | x[:, 5]);
55 | sim_y = poisson_rng(x[:, 5]);
56 | }
57 |
--------------------------------------------------------------------------------
/tests/testthat/test-arrange_variables.R:
--------------------------------------------------------------------------------
1 | test_that("arrange_variables() returns variables in computational order", {
2 |
3 | variables <- list(
4 | list(name = "births",
5 | equation = "population*birthRate"),
6 | list(name = "birthRate",
7 | equation = "birthRate2")
8 | )
9 |
10 | ordered_vars <- arrange_variables(variables)
11 | expected_list <- c(variables[2], variables[1])
12 | expect_equal(ordered_vars, expected_list)
13 | })
14 |
15 | test_that("arrange_variables() works when there are duplicated equations", {
16 |
17 | variables <- list(
18 | list(name = "births",
19 | equation = "population*(growth_rate_1+growth_rate_2)"),
20 | list(name = "growth_rate_1",
21 | equation = "growth_rate_base"),
22 | list(name = "growth_rate_2",
23 | equation = "growth_rate_base")
24 | )
25 | expected_list <- c(variables[2], variables[3], variables[1])
26 | ordered_vars <- arrange_variables(variables)
27 | expect_equal(ordered_vars, expected_list)
28 | })
29 |
30 | test_that("arrange_variables() returns an empty list if the input is an empty list", {
31 | variables <- list()
32 | actual_val <- arrange_variables(list())
33 | expected_val <- list()
34 | expect_equal(actual_val, expected_val)
35 | })
36 |
37 | test_that("arrange_variables() returns an error should the rhs does not contain variables", {
38 |
39 | test_list <- list(list(name = "c", equation = "a+b"),
40 | list(name = "a", equation = "3"))
41 |
42 | expect_error(arrange_variables(test_list))
43 | })
44 |
--------------------------------------------------------------------------------
/.github/workflows/test-coverage.yaml:
--------------------------------------------------------------------------------
1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
3 | on:
4 | push:
5 | branches: [main, master]
6 | pull_request:
7 | branches: [main, master]
8 |
9 | name: test-coverage
10 |
11 | jobs:
12 | test-coverage:
13 | runs-on: ubuntu-latest
14 | env:
15 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
16 |
17 | steps:
18 | - uses: actions/checkout@v3
19 |
20 | - uses: r-lib/actions/setup-r@v2
21 | with:
22 | use-public-rspm: true
23 |
24 | - uses: r-lib/actions/setup-r-dependencies@v2
25 | with:
26 | extra-packages: any::covr
27 | needs: coverage
28 |
29 | - name: Test coverage
30 | run: |
31 | covr::codecov(
32 | quiet = FALSE,
33 | clean = FALSE,
34 | install_path = file.path(Sys.getenv("RUNNER_TEMP"), "package")
35 | )
36 | shell: Rscript {0}
37 |
38 | - name: Show testthat output
39 | if: always()
40 | run: |
41 | ## --------------------------------------------------------------------
42 | find ${{ runner.temp }}/package -name 'testthat.Rout*' -exec cat '{}' \; || true
43 | shell: bash
44 |
45 | - name: Upload test results
46 | if: failure()
47 | uses: actions/upload-artifact@v3
48 | with:
49 | name: coverage-test-failures
50 | path: ${{ runner.temp }}/package
51 |
--------------------------------------------------------------------------------
/man/create_stan_function.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/create_stan_function.R
3 | \name{create_stan_function}
4 | \alias{create_stan_function}
5 | \title{Create a Stan's ODE function from an XMILE file}
6 | \usage{
7 | create_stan_function(
8 | filepath,
9 | func_name,
10 | pars = NULL,
11 | override.consts = NULL,
12 | additional_funs = NULL
13 | )
14 | }
15 | \arguments{
16 | \item{filepath}{A string that indicates a path to a file with extension .stmx
17 | or .xmile. Vensim files (.mdl) are not xmile files. They must be exported
18 | from Vensim with extension .xmile}
19 |
20 | \item{func_name}{A string for naming the ODE function}
21 |
22 | \item{pars}{A character vector that indicates which constants will be
23 | considered as parameters in the ODE function}
24 |
25 | \item{override.consts}{A list in which each element is a name-value pair that
26 | replaces values of constants.}
27 |
28 | \item{additional_funs}{A vector of strings. Each string corresponds to a
29 | user-defined function.}
30 | }
31 | \value{
32 | A string with the code containing the model's equations in the
33 | format required by Stan.
34 | }
35 | \description{
36 | \code{create_stan_function} returns a string with the code for a Stan's ODE function
37 | }
38 | \details{
39 | This function extracts the xml from the file specified via \code{filepath} to
40 | generate the code for an equivalent model in Stan.
41 | }
42 | \examples{
43 | path <- system.file("models", "SIR.stmx", package = "readsdr")
44 | create_stan_function(path, "my_model")
45 | }
46 |
--------------------------------------------------------------------------------
/tests/testthat/test-what_if_from_time.R:
--------------------------------------------------------------------------------
1 | test_that("sd_what_if_from_time() returns the expected output", {
2 |
3 | filepath <- system.file("models/", "SIR.stmx", package = "readsdr")
4 | mdl <- read_xmile(filepath)
5 | ds_components <- mdl$deSolve_components
6 | output <- sd_what_if_from_time(3, par_list = list(c = 4),
7 | ds_inputs = ds_components,
8 | start_time = 0, stop_time = 5,
9 | integ_method = "rk4",
10 | timestep = 1 / 16)
11 | expect_is(output, "data.frame")
12 |
13 | actual_val <- tail(output[, "Infected"], 1)
14 | expect_equal(actual_val , 253.706683332, tolerance = 1e-9)
15 | })
16 |
17 | test_that("sd_what_from_time() works with the up_to_time parameter", {
18 |
19 | filepath <- system.file("models/", "SIR.stmx", package = "readsdr")
20 | mdl <- read_xmile(filepath)
21 | ds_components <- mdl$deSolve_components
22 | output <- sd_what_if_from_time(time = 3, up_to_time = 5,
23 | par_list = list(c = 4),
24 | ds_inputs = ds_components,
25 | start_time = 0, stop_time = 10,
26 | integ_method = "rk4",
27 | timestep = 1 / 16)
28 |
29 | expect_is(output, "data.frame")
30 |
31 | actual_val <- tail(output[, "Infected"], 1)
32 | expect_equal(actual_val, 63.4511067818, tolerance = 1e-9)
33 | })
34 |
--------------------------------------------------------------------------------
/R/stan_data.R:
--------------------------------------------------------------------------------
1 | # Stan's data block for ODE models
2 | stan_data <- function(meas_mdl, unk_inits, data_params, data_inits,
3 | n_difeq = NULL, forecast) {
4 |
5 | external_params <- c(data_params, data_inits)
6 |
7 | decl <- " int n_obs;"
8 |
9 | data_decl <- lapply(meas_mdl, construct_data_decl) |>
10 | paste(collapse = "\n")
11 |
12 | final_decl <- " array[n_obs] real ts;"
13 |
14 | body_block <- paste(decl, data_decl, final_decl, sep = "\n")
15 |
16 | if(!unk_inits) {
17 | body_block <- paste(body_block,
18 | stringr::str_glue(" vector[{n_difeq}] x0;"),
19 | sep = "\n")
20 | }
21 |
22 | if(!is.null(external_params)) {
23 |
24 | data_params_lines <- stringr::str_glue(" real {external_params};") |>
25 | paste(collapse = "\n")
26 |
27 | body_block <- paste(body_block, data_params_lines, sep = "\n")
28 | }
29 |
30 | if(forecast) body_block <- paste(body_block, " int n_fcst;",
31 | sep = "\n")
32 |
33 | paste("data {", body_block, "}", sep = "\n")
34 | }
35 |
36 | construct_data_decl <- function(meas_obj) {
37 |
38 | decomposed_meas <- decompose_meas(meas_obj)
39 | lhs <- decomposed_meas$lhs
40 | rhs <- decomposed_meas$rhs
41 | type <- get_dist_type(rhs)
42 |
43 | meas_size <- determine_meas_size(rhs)
44 |
45 | # meas_ipt_ln = measurement input line
46 |
47 | if(meas_size == 1) meas_ipt_ln <- stringr::str_glue(" {type} {lhs};")
48 |
49 | if(meas_size == Inf) meas_ipt_ln <- stringr::str_glue(" array[n_obs] {type} {lhs};")
50 |
51 | meas_ipt_ln
52 | }
53 |
--------------------------------------------------------------------------------
/R/stan_params.R:
--------------------------------------------------------------------------------
1 |
2 | stan_params <- function(prior) {
3 |
4 | prior <- sort_estimated_pars(prior)
5 |
6 | parameter_lines <- sapply(prior, build_parameter_line) |>
7 | paste(collapse = "\n")
8 |
9 | paste(
10 | "parameters {",
11 | parameter_lines,
12 | "}", sep = "\n")
13 | }
14 |
15 | sort_estimated_pars <- function(prior) {
16 |
17 | par_names <- get_names(prior, "par_name")
18 |
19 | bound_dependencies <- lapply(prior, \(prior_obj) {
20 |
21 | bounds <- c(prior_obj$min, prior_obj$max)
22 |
23 | eq <- ".placeholder"
24 |
25 | if(is.character(bounds)) eq <- paste(bounds, collapse = "+")
26 |
27 | list(name = prior_obj$par_name,
28 | equation = eq) # For arrange_variables()
29 | })
30 |
31 | sorted_pars <- arrange_variables(bound_dependencies) |> get_names()
32 |
33 | prior[match(sorted_pars, par_names)]
34 | }
35 |
36 |
37 | build_parameter_line <- function(prior_obj) {
38 |
39 | obj_elems <- names(prior_obj)
40 |
41 | decl <- " real" # declaration
42 |
43 | if(all(c("min", "max") %in% obj_elems)) {
44 |
45 | #bounds
46 | bds <- stringr::str_glue("")
47 |
48 | decl <- paste0(decl, bds)
49 | }
50 |
51 | if("min" %in% obj_elems & !"max" %in% obj_elems) {
52 | bds <- stringr::str_glue("")
53 |
54 | decl <- paste0(decl, bds)
55 | }
56 |
57 | if(!"min" %in% obj_elems & "max" %in% obj_elems) {
58 | bds <- stringr::str_glue("")
59 |
60 | decl <- paste0(decl, bds)
61 | }
62 |
63 | par_name <- prior_obj$par_name
64 |
65 | stringr::str_glue("{decl} {par_name};")
66 | }
67 |
--------------------------------------------------------------------------------
/R/xmile_graph_funs.R:
--------------------------------------------------------------------------------
1 | translate_graph_func <- function(gf_xml) {
2 |
3 | ypts_xml <- gf_xml |> xml2::xml_find_first(".//d1:ypts")
4 |
5 | ypts <- xml2::xml_text(ypts_xml) |>
6 | stringr::str_split(",", simplify = TRUE) |> as.vector() |> as.numeric()
7 |
8 | length_y <- length(ypts)
9 |
10 | xpts_xml <- gf_xml |> xml2::xml_find_first(".//d1:xpts")
11 |
12 | if(length(xpts_xml) > 0) {
13 |
14 | x_points <- xml2::xml_text(xpts_xml) |>
15 | stringr::str_split(",", simplify = TRUE) |> as.vector() |> as.numeric()
16 | } else{
17 |
18 | xscale_xml <- gf_xml |> xml2::xml_find_first(".//d1:xscale")
19 |
20 | x_min <- xml2::xml_attr(xscale_xml, "min") |> as.numeric()
21 | x_max <- xml2::xml_attr(xscale_xml, "max") |> as.numeric()
22 |
23 | x_points <- seq(x_min, x_max, length.out = length_y)
24 | }
25 |
26 | graph_fun <- stats::approxfun(
27 | x = x_points,
28 | y = ypts,
29 | method = "linear",
30 | yleft = ypts[[1]],
31 | yright = ypts[[length_y]])
32 | }
33 |
34 | translate_Vensim_graph_func <- function(equation){
35 | match_output <- stringr::str_match(
36 | equation, "WITHLOOKUP\\((\\w+),\\(\\[.+\\],(.+)\\)\\)")
37 |
38 | match_data_points <- match_output[[3]] %>%
39 | stringr::str_match_all("\\((.+?),(.+?)\\),")
40 |
41 | x_points <- match_data_points[[1]][, 2] %>% as.numeric()
42 | y_points <- match_data_points[[1]][, 3] %>% as.numeric()
43 |
44 | graph_fun <- stats::approxfun(
45 | x = x_points,
46 | y = y_points,
47 | method = "linear",
48 | yleft = y_points[[1]],
49 | yright = y_points[[length(y_points)]])
50 |
51 | list(input = match_output[[2]],
52 | graph_fun = graph_fun)
53 | }
54 |
--------------------------------------------------------------------------------
/man/sd_what_if_from_time.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/what_if_from_time.R
3 | \name{sd_what_if_from_time}
4 | \alias{sd_what_if_from_time}
5 | \title{What if from time t we change the value of some parameters}
6 | \usage{
7 | sd_what_if_from_time(
8 | time,
9 | up_to_time = Inf,
10 | par_list,
11 | ds_inputs,
12 | start_time = NULL,
13 | stop_time = NULL,
14 | timestep = NULL,
15 | integ_method = "euler"
16 | )
17 | }
18 | \arguments{
19 | \item{time}{Time at which the parameter values change}
20 |
21 | \item{up_to_time}{Time from which the original values are restored.}
22 |
23 | \item{par_list}{A list that indicates which parameters change from time t.
24 | For instance, if you wanted to change the value of parameter \code{c} to 4,
25 | you would provide the \code{list(c = 4)}}
26 |
27 | \item{ds_inputs}{A list of deSolve inputs generated by read_xmile}
28 |
29 | \item{start_time}{A number indicating the time at which the simulation begins.}
30 |
31 | \item{stop_time}{A number indicating the time at which the simulation ends.}
32 |
33 | \item{timestep}{A number indicating the time interval for the simulation.
34 | Also known as \code{dt}.}
35 |
36 | \item{integ_method}{A string indicating the integration method. It can be
37 | either "euler" or "rk4"}
38 | }
39 | \value{
40 | A data frame
41 | }
42 | \description{
43 | What if from time t we change the value of some parameters
44 | }
45 | \examples{
46 | filepath <- system.file("models/", "SIR.stmx", package = "readsdr")
47 | mdl <- read_xmile(filepath)
48 | ds_components <- mdl$deSolve_components
49 | output <- sd_what_if_from_time(3, Inf, list(c = 4), ds_components)
50 | }
51 |
--------------------------------------------------------------------------------
/man/sd_measurements.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/simulator_measurements.R
3 | \name{sd_measurements}
4 | \alias{sd_measurements}
5 | \title{Generate measurements}
6 | \usage{
7 | sd_measurements(
8 | n_meas,
9 | meas_model,
10 | ds_inputs,
11 | start_time = NULL,
12 | stop_time = NULL,
13 | timestep = NULL,
14 | integ_method = "euler"
15 | )
16 | }
17 | \arguments{
18 | \item{n_meas}{Number of measurements. An integer.}
19 |
20 | \item{meas_model}{Measurement model. A list of strings, in which each string
21 | corresponds to sampling statement in Stan language.}
22 |
23 | \item{ds_inputs}{A list of deSolve inputs generated by read_xmile}
24 |
25 | \item{start_time}{A number indicating the time at which the simulation begins.}
26 |
27 | \item{stop_time}{A number indicating the time at which the simulation ends.}
28 |
29 | \item{timestep}{A number indicating the time interval for the simulation.
30 | Also known as \code{dt}.}
31 |
32 | \item{integ_method}{A string indicating the integration method. It can be
33 | either "euler" or "rk4"}
34 | }
35 | \value{
36 | A data frame.
37 | }
38 | \description{
39 | Generate measurements
40 | }
41 | \examples{
42 | filepath <- system.file("models/", "SEIR.stmx", package = "readsdr")
43 | mdl <- read_xmile(filepath)
44 |
45 | mm1 <- "y ~ poisson(C)"
46 | meas_model <- list(mm1)
47 |
48 | sd_measurements(n_meas = 2,
49 | meas_model = meas_model,
50 | ds_inputs = mdl$deSolve_components,
51 | start_time = 0,
52 | stop_time = 10,
53 | timestep = 1/16,
54 | integ_method = "rk4")
55 | }
56 |
--------------------------------------------------------------------------------
/tests/testthat/test_stan_files/SEIR_pois_N.stan:
--------------------------------------------------------------------------------
1 | // Code generated by the R package readsdr v0.3.0.9001
2 | // See more info at github https://github.com/jandraor/readsdr
3 | functions {
4 | vector X_model(real time, vector y, array[] real params) {
5 | vector[5] dydt;
6 | real S_to_E;
7 | real E_to_I;
8 | real I_to_R;
9 | real C_in;
10 | S_to_E = params[1]*y[1]*y[3]/params[3];
11 | E_to_I = 0.5*y[2];
12 | I_to_R = 0.5*y[3];
13 | C_in = params[2]*E_to_I;
14 | dydt[1] = -S_to_E;
15 | dydt[2] = S_to_E-E_to_I;
16 | dydt[3] = E_to_I-I_to_R;
17 | dydt[4] = I_to_R;
18 | dydt[5] = C_in;
19 | return dydt;
20 | }
21 | }
22 | data {
23 | int n_obs;
24 | array[n_obs] int y;
25 | array[n_obs] real ts;
26 | real N;
27 | }
28 | parameters {
29 | real par_beta;
30 | real par_rho;
31 | real I0;
32 | }
33 | transformed parameters{
34 | array[n_obs] vector[5] x; // Output from the ODE solver
35 | array[3] real params;
36 | vector[5] x0; // init values
37 | array[n_obs] real delta_x_1;
38 | x0[1] = N - I0; // S
39 | x0[2] = 0; // E
40 | x0[3] = I0; // I
41 | x0[4] = 0; // R
42 | x0[5] = I0; // C
43 | params[1] = par_beta;
44 | params[2] = par_rho;
45 | params[3] = N;
46 | x = ode_rk45(X_model, x0, 0, ts, params);
47 | delta_x_1[1] = x[1, 5] - x0[5] + 1e-5;
48 | for (i in 1:n_obs-1) {
49 | delta_x_1[i + 1] = x[i + 1, 5] - x[i, 5] + 1e-5;
50 | }
51 | }
52 | model {
53 | par_beta ~ lognormal(0, 1);
54 | par_rho ~ beta(2, 2);
55 | I0 ~ lognormal(0, 1);
56 | y ~ poisson(delta_x_1);
57 | }
58 | generated quantities {
59 | real log_lik;
60 | array[n_obs] int sim_y;
61 | log_lik = poisson_lpmf(y | delta_x_1);
62 | sim_y = poisson_rng(delta_x_1);
63 | }
64 |
--------------------------------------------------------------------------------
/tests/testthat/SEIR_normal.stan:
--------------------------------------------------------------------------------
1 | // Code generated by the R package readsdr v0.3.0.9001
2 | // See more info at github https://github.com/jandraor/readsdr
3 | functions {
4 | vector X_model(real time, vector y, array[] real params) {
5 | vector[5] dydt;
6 | real S_to_E;
7 | real E_to_I;
8 | real I_to_R;
9 | real C_in;
10 | S_to_E = params[1]*y[1]*y[3]/10000;
11 | E_to_I = 0.5*y[2];
12 | I_to_R = 0.5*y[3];
13 | C_in = params[2]*E_to_I;
14 | dydt[1] = -S_to_E;
15 | dydt[2] = S_to_E-E_to_I;
16 | dydt[3] = E_to_I-I_to_R;
17 | dydt[4] = I_to_R;
18 | dydt[5] = C_in;
19 | return dydt;
20 | }
21 | }
22 | data {
23 | int n_obs;
24 | array[n_obs] real y;
25 | array[n_obs] real ts;
26 | }
27 | parameters {
28 | real par_beta;
29 | real par_rho;
30 | real I0;
31 | real tau;
32 | }
33 | transformed parameters{
34 | array[n_obs] vector[5] x; // Output from the ODE solver
35 | array[2] real params;
36 | vector[5] x0; // init values
37 | array[n_obs] real delta_x_1;
38 | x0[1] = (10000) - I0; // S
39 | x0[2] = 0; // E
40 | x0[3] = I0; // I
41 | x0[4] = 0; // R
42 | x0[5] = I0; // C
43 | params[1] = par_beta;
44 | params[2] = par_rho;
45 | x = ode_rk45(X_model, x0, 0, ts, params);
46 | delta_x_1[1] = x[1, 5] - x0[5] + 1e-5;
47 | for (i in 1:n_obs-1) {
48 | delta_x_1[i + 1] = x[i + 1, 5] - x[i, 5] + 1e-5;
49 | }
50 | }
51 | model {
52 | par_beta ~ lognormal(0, 1);
53 | par_rho ~ beta(2, 2);
54 | I0 ~ lognormal(0, 1);
55 | tau ~ exponential(0.2);
56 | y ~ normal(delta_x_1, tau);
57 | }
58 | generated quantities {
59 | real log_lik;
60 | array[n_obs] real sim_y;
61 | log_lik = normal_lpdf(y | delta_x_1, tau);
62 | sim_y = normal_rng(delta_x_1, tau);
63 | }
64 |
--------------------------------------------------------------------------------
/tests/testthat/test_stan_files/SEIR_nbin_data_init.stan:
--------------------------------------------------------------------------------
1 | // Code generated by the R package readsdr v0.3.0.9001
2 | // See more info at github https://github.com/jandraor/readsdr
3 | functions {
4 | vector X_model(real time, vector y, array[] real params) {
5 | vector[5] dydt;
6 | real S_to_E;
7 | real E_to_I;
8 | real I_to_R;
9 | real C_in;
10 | S_to_E = params[1]*y[1]*y[3]/10000;
11 | E_to_I = 0.5*y[2];
12 | I_to_R = 0.5*y[3];
13 | C_in = params[2]*E_to_I;
14 | dydt[1] = -S_to_E;
15 | dydt[2] = S_to_E-E_to_I;
16 | dydt[3] = E_to_I-I_to_R;
17 | dydt[4] = I_to_R;
18 | dydt[5] = C_in;
19 | return dydt;
20 | }
21 | }
22 | data {
23 | int n_obs;
24 | array[n_obs] int y;
25 | array[n_obs] real ts;
26 | real I0;
27 | }
28 | parameters {
29 | real par_beta;
30 | real par_rho;
31 | real inv_phi;
32 | }
33 | transformed parameters{
34 | array[n_obs] vector[5] x; // Output from the ODE solver
35 | array[2] real params;
36 | vector[5] x0; // init values
37 | array[n_obs] real delta_x_1;
38 | real phi;
39 | phi = 1 / inv_phi;
40 | x0[1] = (10000) - I0; // S
41 | x0[2] = 0; // E
42 | x0[3] = I0; // I
43 | x0[4] = 0; // R
44 | x0[5] = I0; // C
45 | params[1] = par_beta;
46 | params[2] = par_rho;
47 | x = ode_rk45(X_model, x0, 0, ts, params);
48 | delta_x_1[1] = x[1, 5] - x0[5] + 1e-5;
49 | for (i in 1:n_obs-1) {
50 | delta_x_1[i + 1] = x[i + 1, 5] - x[i, 5] + 1e-5;
51 | }
52 | }
53 | model {
54 | par_beta ~ lognormal(0, 1);
55 | par_rho ~ beta(2, 2);
56 | inv_phi ~ exponential(5);
57 | y ~ neg_binomial_2(delta_x_1, phi);
58 | }
59 | generated quantities {
60 | real log_lik;
61 | array[n_obs] int sim_y;
62 | log_lik = neg_binomial_2_lpmf(y | delta_x_1, phi);
63 | sim_y = neg_binomial_2_rng(delta_x_1, phi);
64 | }
65 |
--------------------------------------------------------------------------------
/man/sd_data_generator_fun.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/SBC.R
3 | \name{sd_data_generator_fun}
4 | \alias{sd_data_generator_fun}
5 | \title{Function factory for SBC}
6 | \usage{
7 | sd_data_generator_fun(
8 | filepath,
9 | estimated_params,
10 | meas_mdl,
11 | start_time = NULL,
12 | stop_time = NULL,
13 | timestep = NULL,
14 | integ_method = "euler"
15 | )
16 | }
17 | \arguments{
18 | \item{filepath}{A string that indicates a path to a file with extension .stmx
19 | or .xmile. Vensim files (.mdl) are not xmile files. They must be exported
20 | from Vensim with extension .xmile}
21 |
22 | \item{estimated_params}{A list of lists. Each sublist describes each
23 | parameter that will be estimated in the inference stage. To construct this
24 | description, the user can avail of the function `sd_prior`.}
25 |
26 | \item{meas_mdl}{A list of strings. Each string corresponds to a sampling
27 | statement written in Stan language.}
28 |
29 | \item{start_time}{A number indicating the time at which the simulation begins.}
30 |
31 | \item{stop_time}{A number indicating the time at which the simulation ends.}
32 |
33 | \item{timestep}{A number indicating the time interval for the simulation.
34 | Also known as \code{dt}.}
35 |
36 | \item{integ_method}{A string indicating the integration method. It can be
37 | either "euler" or "rk4"}
38 | }
39 | \value{
40 | A function.
41 | }
42 | \description{
43 | Function factory for SBC
44 | }
45 | \examples{
46 | filepath <- system.file("models/", "SEIR.stmx", package = "readsdr")
47 | meas_mdl <- list("y ~ poisson(net_flow(C))")
48 | estimated_params <- list(
49 | sd_prior("par_beta", "lognormal", c(0, 1)),
50 | sd_prior("par_rho", "beta", c(2, 2)),
51 | sd_prior("I0", "lognormal", c(0, 1), "init"))
52 | sd_data_generator_fun(filepath, estimated_params, meas_mdl)
53 | }
54 |
--------------------------------------------------------------------------------
/tests/testthat/SEIR_nbinom.stan:
--------------------------------------------------------------------------------
1 | // Code generated by the R package readsdr v0.3.0.9001
2 | // See more info at github https://github.com/jandraor/readsdr
3 | functions {
4 | vector X_model(real time, vector y, array[] real params) {
5 | vector[5] dydt;
6 | real S_to_E;
7 | real E_to_I;
8 | real I_to_R;
9 | real C_in;
10 | S_to_E = params[1]*y[1]*y[3]/10000;
11 | E_to_I = 0.5*y[2];
12 | I_to_R = 0.5*y[3];
13 | C_in = params[2]*E_to_I;
14 | dydt[1] = -S_to_E;
15 | dydt[2] = S_to_E-E_to_I;
16 | dydt[3] = E_to_I-I_to_R;
17 | dydt[4] = I_to_R;
18 | dydt[5] = C_in;
19 | return dydt;
20 | }
21 | }
22 | data {
23 | int n_obs;
24 | array[n_obs] int y;
25 | array[n_obs] real ts;
26 | }
27 | parameters {
28 | real par_beta;
29 | real par_rho;
30 | real I0;
31 | real inv_phi;
32 | }
33 | transformed parameters{
34 | array[n_obs] vector[5] x; // Output from the ODE solver
35 | array[2] real params;
36 | vector[5] x0; // init values
37 | array[n_obs] real delta_x_1;
38 | real phi;
39 | phi = 1 / inv_phi;
40 | x0[1] = (10000) - I0; // S
41 | x0[2] = 0; // E
42 | x0[3] = I0; // I
43 | x0[4] = 0; // R
44 | x0[5] = I0; // C
45 | params[1] = par_beta;
46 | params[2] = par_rho;
47 | x = ode_rk45(X_model, x0, 0, ts, params);
48 | delta_x_1[1] = x[1, 5] - x0[5] + 1e-5;
49 | for (i in 1:n_obs-1) {
50 | delta_x_1[i + 1] = x[i + 1, 5] - x[i, 5] + 1e-5;
51 | }
52 | }
53 | model {
54 | par_beta ~ lognormal(0, 1);
55 | par_rho ~ beta(2, 2);
56 | I0 ~ lognormal(0, 1);
57 | inv_phi ~ exponential(5);
58 | y ~ neg_binomial_2(delta_x_1, phi);
59 | }
60 | generated quantities {
61 | real log_lik;
62 | array[n_obs] int sim_y;
63 | log_lik = neg_binomial_2_lpmf(y | delta_x_1, phi);
64 | sim_y = neg_binomial_2_rng(delta_x_1, phi);
65 | }
66 |
--------------------------------------------------------------------------------
/tests/testthat/test-arrays.R:
--------------------------------------------------------------------------------
1 | test_that("array_equations() returns the expected list", {
2 |
3 | dims_obj <- list(global_dims = list(Age = c("1", "2")),
4 | dictionary = list(I = c("Age")))
5 |
6 | vendor <- "isee"
7 | dim_names <- "Age"
8 |
9 | aux_obj <- list(name = "I_to_R",
10 | equation = "par_gamma*I")
11 |
12 | actual <- array_equations(aux_obj, dims_obj, dim_names, vendor)
13 |
14 | expected <- list(equations = c("par_gamma*I_1", "par_gamma*I_2"),
15 | are_const = c(FALSE, FALSE),
16 | elems = c("1", "2"))
17 |
18 | expect_equal(actual, expected)
19 |
20 | dims_obj <- list(global_dims = list(Age = 1:2),
21 | dictionary = list(lambda = "Age",
22 | S = "Age"))
23 |
24 | aux_obj <- list(name = "S_to_I",
25 | equation = "lambda*S")
26 |
27 | actual <- array_equations(aux_obj, dims_obj, dim_names, vendor)
28 |
29 | expected <- list(equations = c("lambda_1*S_1", "lambda_2*S_2"),
30 | are_const = c(FALSE, FALSE),
31 | elems = c("1", "2"))
32 |
33 | expect_equal(actual, expected)
34 | })
35 |
36 | test_that("devectorise_equation() returns the expected output", {
37 |
38 | dims_list <- list(Region = c("Westeros", "Essos"),
39 | Age = c("Young", "Old"))
40 |
41 | raw_equation <- "Population[Region,Age] * growth_rate[Region,Age]"
42 | actual <- devectorise_equation(raw_equation, dims_list)
43 |
44 | expected <- c("Population_Westeros_Young * growth_rate_Westeros_Young",
45 | "Population_Westeros_Old * growth_rate_Westeros_Old",
46 | "Population_Essos_Young * growth_rate_Essos_Young",
47 | "Population_Essos_Old * growth_rate_Essos_Old")
48 |
49 | expect_equal(actual, expected)
50 | })
51 |
--------------------------------------------------------------------------------
/NEWS.md:
--------------------------------------------------------------------------------
1 | # readsdr (development version)
2 |
3 | ## New features
4 |
5 | * Support the translation of *EXP*
6 |
7 | # readsdr 0.3.0
8 |
9 | ## Breaking changes
10 |
11 | * Deprecate **stan_data()** & **stan_transformed_data()**.
12 |
13 | ## New features
14 |
15 | * Add Maryland data.
16 |
17 | * Add **sd_posterior_fun()**.
18 |
19 | * Add **sd_Bayes()**.
20 |
21 | * Support the translation of Vensim's *DELAY_N*.
22 |
23 | * Support the translation of Stella's *DELAYN* with four parameters.
24 |
25 | * Add **sd_measurements()**.
26 |
27 | * Support the translation of *RANDOM NORMAL* from Vensim.
28 |
29 | * Add support to bidimensional vectors in Vensim.
30 |
31 | * *read_xmile()* now translates Vensim's *DELAY FIXED*.
32 |
33 | * Add **sd_what_if_from_time()**
34 |
35 | * Add **sd_impact_inputs()**
36 |
37 | * Add **sd_loglik_fun()**
38 |
39 | * Add **sd_net_change()**
40 |
41 | ## Minor improvements and fixes
42 |
43 | * Fix bug in the translation of graphical functions.
44 |
45 | * Parallelisation of *sd_sensitivity_run()* supported in Windows.
46 |
47 | * Support Stella's *apply all* for uni-dimensional vectors.
48 |
49 | * *read_xmile()* returns the element graph only if the argument *graph* is set to *TRUE*.
50 |
51 | * Fix bug (incorrect division due to missing parentheses) in the translation of *SMOOTH*.
52 |
53 | # readsdr 0.2.0
54 |
55 | * Add support to translation of SMOOTH functions from Vensim & Stella.
56 | * Add sd_sensitivity_run(). This function supports parallelisation in
57 | Unix-based systems.
58 | * Add flexibility to read_xmile() by allowing the user to override values of
59 | constants and initial values of stocks.
60 | * Add sd_constants() and sd_stocks() to summarise model's information in data frames.
61 | * Add stan_ode_function()
62 | * Add stan_data()
63 | * Add support to unidimensional vectors in Stella.
64 | * Support the translation of the following math functions: ABS & SQRT.
65 |
--------------------------------------------------------------------------------
/R/Vensim_workarounds.R:
--------------------------------------------------------------------------------
1 | extract_vars_in_stocks <- function(stocks_xml, vars_and_consts, inits_vector) {
2 |
3 | vars <- vars_and_consts$variables
4 | consts <- vars_and_consts$constants
5 | b_stocks <- vars_and_consts$builtin_stocks
6 | new_elems <- lapply(stocks_xml, extract_delay_vars, consts, inits_vector) %>%
7 | remove_NULL()
8 |
9 | new_vars <- purrr::map(new_elems, "variable_list") %>%
10 | unlist(recursive = FALSE)
11 |
12 | if(length(new_vars) > 0) vars <- c(vars, new_vars)
13 |
14 | new_stocks <- purrr::map(new_elems, "stock_list") %>%
15 | unlist(recursive = FALSE)
16 |
17 | if(length(new_stocks) > 0) b_stocks <- c(b_stocks, new_stocks)
18 |
19 | vars_and_consts$variables <- vars
20 | vars_and_consts$builtin_stocks <- b_stocks
21 |
22 | vars_and_consts
23 | }
24 |
25 | extract_delay_vars <- function(stock_xml, consts, inits_vector) {
26 |
27 | delay_vars <- list(variable_list = NULL,
28 | stock_list = NULL)
29 |
30 | eq <- xml2::xml_find_all(stock_xml, ".//d1:eqn") %>% xml2::xml_text()
31 |
32 | if(grepl("\\bDELAY_FIXED\\b", eq)) {
33 |
34 | var_name <- stock_xml %>% xml2::xml_attr("name") %>%
35 | sanitise_elem_name() %>% check_elem_name()
36 |
37 | eq <- sanitise_aux_equation(eq, "Vensim")
38 |
39 | var_list <- list(list(name = var_name,
40 | equation = translate_delay(eq, "Vensim")))
41 |
42 | delay_vars$variable_list <- var_list
43 |
44 | return(delay_vars)
45 | }
46 |
47 | stl_delayn <- stringr::str_detect(eq, "\\bDELAY_N\\b")
48 |
49 | if(stl_delayn) {
50 |
51 | var_name <- stock_xml %>% xml2::xml_attr("name") %>%
52 | sanitise_elem_name() %>% check_elem_name()
53 |
54 | DELAYN_translation <- translate_DELAYN(var_name, eq, "Vensim", consts,
55 | inits_vector)
56 |
57 | return(DELAYN_translation)
58 | }
59 |
60 | NULL
61 | }
62 |
--------------------------------------------------------------------------------
/tests/testthat/SEIR_nbinom_data_param.stan:
--------------------------------------------------------------------------------
1 | // Code generated by the R package readsdr v0.3.0.9001
2 | // See more info at github https://github.com/jandraor/readsdr
3 | functions {
4 | vector X_model(real time, vector y, array[] real params) {
5 | vector[5] dydt;
6 | real S_to_E;
7 | real E_to_I;
8 | real I_to_R;
9 | real C_in;
10 | S_to_E = params[1]*y[1]*y[3]/10000;
11 | E_to_I = 0.5*y[2];
12 | I_to_R = params[3]*y[3];
13 | C_in = params[2]*E_to_I;
14 | dydt[1] = -S_to_E;
15 | dydt[2] = S_to_E-E_to_I;
16 | dydt[3] = E_to_I-I_to_R;
17 | dydt[4] = I_to_R;
18 | dydt[5] = C_in;
19 | return dydt;
20 | }
21 | }
22 | data {
23 | int n_obs;
24 | array[n_obs] int y;
25 | array[n_obs] real ts;
26 | real par_gamma;
27 | }
28 | parameters {
29 | real par_beta;
30 | real par_rho;
31 | real I0;
32 | real inv_phi;
33 | }
34 | transformed parameters{
35 | array[n_obs] vector[5] x; // Output from the ODE solver
36 | array[3] real params;
37 | vector[5] x0; // init values
38 | array[n_obs] real delta_x_1;
39 | real phi;
40 | phi = 1 / inv_phi;
41 | x0[1] = (10000) - I0; // S
42 | x0[2] = 0; // E
43 | x0[3] = I0; // I
44 | x0[4] = 0; // R
45 | x0[5] = I0; // C
46 | params[1] = par_beta;
47 | params[2] = par_rho;
48 | params[3] = par_gamma;
49 | x = ode_rk45(X_model, x0, 0, ts, params);
50 | delta_x_1[1] = x[1, 5] - x0[5] + 1e-5;
51 | for (i in 1:n_obs-1) {
52 | delta_x_1[i + 1] = x[i + 1, 5] - x[i, 5] + 1e-5;
53 | }
54 | }
55 | model {
56 | par_beta ~ lognormal(0, 1);
57 | par_rho ~ beta(2, 2);
58 | I0 ~ lognormal(0, 1);
59 | inv_phi ~ exponential(5);
60 | y ~ neg_binomial_2(delta_x_1, phi);
61 | }
62 | generated quantities {
63 | real log_lik;
64 | array[n_obs] int sim_y;
65 | log_lik = neg_binomial_2_lpmf(y | delta_x_1, phi);
66 | sim_y = neg_binomial_2_rng(delta_x_1, phi);
67 | }
68 |
--------------------------------------------------------------------------------
/tests/testthat/2d_pop.xmile:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Vensim
5 | Ventana Systems, Inc.
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | 0
17 | 100
18 | 1
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | init_pop[Region,Age]
42 |
43 |
44 | Net growth
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | Population[Region,Age] * Growth_rate[Region,Age]
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | 0.01,0.1;0.05,0.05;
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | 80,20;50,50;
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/R/extract_variables.R:
--------------------------------------------------------------------------------
1 | #' Extract variables from an equation
2 | #'
3 | #' \code{extract_variables} returns the unique elements in an equation.
4 | #'
5 | #' This function assumes an expression in the canonical form \code{y = f(x)},
6 | #' where x is a vector of n elements. \code{extract_variables} identify all
7 | #' arguments in \code{f}. This is how it should be used:
8 | #'
9 | #' #' lhs <- "z"
10 | #' rhs <- "x ^ 2 + 2 * x * y + y ^ 2"
11 | #' extract_variables(lhs, rhs)
12 | #'
13 | #'
14 | #' @param lhs A string with the name of the equation's subject, represented by
15 | #' \code{y} in the canonical form.
16 | #' @param rhs The equation or expression for the equation's subject, represented
17 | #' by \code{f(x)} in the canonical form.
18 | #'
19 | #' @return A character vector with the unique elements in the \code{rhs}
20 | #' @noRd
21 |
22 | extract_variables <- function(lhs, rhs) {
23 |
24 | rhs <- stringr::str_replace(rhs, "\\.memory", "")
25 |
26 | raw_elements <- stringr::str_split(rhs, "\\b")[[1]] %>%
27 | stringi::stri_remove_empty()
28 |
29 | # Elements that start with alphabetical characters
30 | elems_alpha <- raw_elements[stringr::str_detect(raw_elements, "^[A-Za-z].*")]
31 |
32 | # Filtering out functions min & max
33 | detected_vars <- stringr::str_remove_all(elems_alpha, "\\bmin\\b|\\bmax\\b")
34 | detected_vars <- detected_vars[detected_vars != ""]
35 |
36 | # Filtering out graph functions
37 | potential_gf <- paste0("f_", lhs)
38 | detected_vars <- detected_vars[detected_vars != potential_gf]
39 |
40 | # Filtering out ifelse
41 | detected_vars <- detected_vars[detected_vars != "ifelse"]
42 |
43 | # Filtering out fixed delay
44 | detected_vars <- detected_vars[detected_vars != "sd_fixed_delay"]
45 |
46 | # Filtering out words reserved for RNG
47 | detected_vars <- detected_vars[!detected_vars %in% c("rnorm", "rtruncnorm",
48 | "truncnorm")]
49 |
50 | unique(detected_vars)
51 | }
52 |
--------------------------------------------------------------------------------
/man/read_xmile.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/read_xmile.R
3 | \name{read_xmile}
4 | \alias{read_xmile}
5 | \title{Read an XMILE file into R}
6 | \usage{
7 | read_xmile(filepath, stock_list = NULL, const_list = NULL, graph = FALSE)
8 | }
9 | \arguments{
10 | \item{filepath}{A string that indicates a path to a file with extension .stmx
11 | or .xmile. Vensim files (.mdl) are not xmile files. They must be exported
12 | from Vensim with extension .xmile}
13 |
14 | \item{stock_list}{A list in which each element's name is the name of the
15 | stock to override and the element's value correspond to the new init value.}
16 |
17 | \item{const_list}{A list in which each element's name is the name of the
18 | constant to override and the element's value correspond to the new value.}
19 |
20 | \item{graph}{A boolean parameter that indicates whether \code{read_xmile}
21 | returns a graph for the model.}
22 | }
23 | \value{
24 | This function returns a list with three elements. The first element,
25 | \emph{description}, is a list that contains the simulation parameters, and
26 | the names and equations (including graphical functions) for each stock or
27 | level, variable and constant. The second element, \emph{deSolve_components},
28 | is a list that contains initial values, constants and the function for
29 | simulating via deSolve. The third element (optional), \emph{igraph} contains
30 | the data.frames for creating a graph with igraph.
31 | }
32 | \description{
33 | \code{read_xmile} returns a list for constructing deSolve functions and graphs
34 | }
35 | \details{
36 | This function extracts the xml from the file specified via \code{filepath}
37 | to generate a list of objects. Such a list contains a summary of the model,
38 | the inputs for simulating through \link[deSolve]{deSolve}, and the inputs for
39 | creating a \link[igraph]{igraph} object.
40 | }
41 | \examples{
42 | path <- system.file("models", "SIR.stmx", package = "readsdr")
43 | read_xmile(path)
44 | }
45 |
--------------------------------------------------------------------------------
/man/sd_prior_checks.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/prior_checks.R
3 | \name{sd_prior_checks}
4 | \alias{sd_prior_checks}
5 | \title{Prior predictive checks}
6 | \usage{
7 | sd_prior_checks(
8 | filepath,
9 | meas_mdl,
10 | estimated_params,
11 | n_draws,
12 | start_time = NULL,
13 | stop_time = NULL,
14 | timestep = NULL,
15 | integ_method = "euler"
16 | )
17 | }
18 | \arguments{
19 | \item{filepath}{A string that indicates a path to a file with extension .stmx
20 | or .xmile. Vensim files (.mdl) are not xmile files. They must be exported
21 | from Vensim with extension .xmile}
22 |
23 | \item{meas_mdl}{A list of strings. Each string corresponds to a sampling
24 | statement written in Stan language.}
25 |
26 | \item{estimated_params}{A list of lists. Each sublist describes each
27 | parameter that will be estimated in the inference stage. To construct this
28 | description, the user can avail of the function `sd_prior`.}
29 |
30 | \item{n_draws}{An integer that indicates how many time-series will be
31 | returned.}
32 |
33 | \item{start_time}{A number indicating the time at which the simulation begins.}
34 |
35 | \item{stop_time}{A number indicating the time at which the simulation ends.}
36 |
37 | \item{timestep}{A number indicating the time interval for the simulation.
38 | Also known as \code{dt}.}
39 |
40 | \item{integ_method}{A string indicating the integration method. It can be
41 | either "euler" or "rk4"}
42 | }
43 | \value{
44 | A list of two data frames.
45 | }
46 | \description{
47 | Prior predictive checks
48 | }
49 | \examples{
50 | filepath <- system.file("models/", "SEIR.stmx", package = "readsdr")
51 | meas_mdl <- list("y ~ neg_binomial_2(net_flow(C), phi)")
52 | estimated_params <- list(
53 | sd_prior("par_beta", "lognormal", c(0, 1)),
54 | sd_prior("par_rho", "beta", c(2, 2)),
55 | sd_prior("I0", "lognormal", c(0, 1), "init"))
56 | sd_prior_checks(filepath, meas_mdl, estimated_params, n_draws = 2,
57 | start_time = 0, stop_time = 5,
58 | integ_method = "rk4", timestep = 1/32)
59 | }
60 |
--------------------------------------------------------------------------------
/tests/testthat/test-interpreters.R:
--------------------------------------------------------------------------------
1 | test_that("sd_interpret_estimates() returns the expected data frame", {
2 |
3 | estimates <- c(par_beta = 0,
4 | par_rho = 0.8472979,
5 | I0 = 0,
6 | inv_phi = -2.3025855)
7 |
8 | par_list <- list(list(par_name = "par_beta",
9 | par_trans = "exp"),
10 | list(par_name = "par_rho",
11 | par_trans = "expit"),
12 | list(par_name = "I0",
13 | par_trans = "exp"),
14 | list(par_name = "phi",
15 | par_trans = c("exp", "inv")))
16 |
17 | actual <- sd_interpret_estimates(estimates, par_list)
18 |
19 | expected <- data.frame(par_beta = 1, par_rho = 0.7, I0 = 1, phi = 10)
20 |
21 | expect_equal(actual, expected, tol = 1e-4)
22 |
23 |
24 | estimates <- data.frame(c(0, 0.6931472),
25 | c(0.8472979, 0.8472979),
26 | c(0, 0.4054651),
27 | c(-2.3025855, -2.3025855))
28 |
29 | actual <- sd_interpret_estimates(estimates, par_list)
30 |
31 | expected <- data.frame(par_beta = c(1, 2),
32 | par_rho = 0.7,
33 | I0 = c(1, 1.5),
34 | phi = 10)
35 |
36 | expect_equal(actual, expected, tol = 1e-4)
37 | })
38 |
39 | test_that("sd_conf_intervals returns the expected data frame", {
40 |
41 | estimates <- c(-0.2630303135, 1.5788579359)
42 |
43 | par_list <- list(list(par_name = "par_inv_R0",
44 | par_trans = "expit"),
45 | list(par_name = "I0",
46 | par_trans = "exp"))
47 |
48 | hsn <- matrix(c(3513.105214, -493.5469626, -493.5469626, 88.48712899),
49 | ncol = 2)
50 |
51 | actual <- sd_conf_intervals(estimates, par_list, hsn)
52 |
53 | expected <- data.frame(X1 = c("par_inv_R0", "I0"),
54 | X2 = c(0.4172404, 3.0986791),
55 | X3 = c(0.4521597, 7.5893045))
56 |
57 | names(expected) <- c("parameter", "2.5%", "97.5%")
58 |
59 | expect_equal(actual, expected)
60 | })
61 |
--------------------------------------------------------------------------------
/tests/testthat/test-stan_params.R:
--------------------------------------------------------------------------------
1 | #------------------stan_params()------------------------------------------------
2 |
3 | test_that("stan_params() returns the expected string", {
4 |
5 | prior <- list(sd_prior("par_beta", "lognormal", c(0, 1)),
6 | sd_prior("par_rho", "beta", c(2, 2)),
7 | sd_prior("I0", "lognormal", c(0, 1), "init"))
8 |
9 | actual <- stan_params(prior)
10 |
11 | expected <- paste(
12 | "parameters {",
13 | " real par_beta;",
14 | " real par_rho;",
15 | " real I0;",
16 | "}", sep = "\n")
17 |
18 | expect_equal(actual, expected)
19 | })
20 |
21 | test_that("stan_params() allows constraining parameters based on the prior", {
22 |
23 | actual <- stan_params(list(list(par_name = "par_alpha",
24 | dist = "normal",
25 | mu = 0,
26 | sigma = 1,
27 | type = "constant",
28 | min = 0)))
29 |
30 | expected <- paste(
31 | "parameters {",
32 | " real par_alpha;",
33 | "}", sep = "\n")
34 |
35 | expect_equal(actual, expected)
36 | })
37 |
38 | test_that("stan_params() allows constraining parameters based on other parameters", {
39 |
40 | prior_list <- list(sd_prior("par_alpha", "beta", c(2, 2)),
41 | sd_prior("par_beta", "beta", c(2, 2), max = "par_alpha"))
42 |
43 | actual <- stan_params(prior_list)
44 |
45 | expected <- paste(
46 | "parameters {",
47 | " real par_alpha;",
48 | " real par_beta;",
49 | "}", sep = "\n")
50 |
51 | expect_equal(actual, expected)
52 |
53 | # Test 2
54 |
55 | prior_list <- list(sd_prior("par_beta", "beta", c(2, 2), max = "par_alpha"),
56 | sd_prior("par_alpha", "beta", c(2, 2)))
57 |
58 | actual <- stan_params(prior_list)
59 |
60 | expected <- paste(
61 | "parameters {",
62 | " real par_alpha;",
63 | " real par_beta;",
64 | "}", sep = "\n")
65 |
66 | expect_equal(actual, expected)
67 | })
68 |
--------------------------------------------------------------------------------
/man/sd_prior.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/priors.R
3 | \name{sd_prior}
4 | \alias{sd_prior}
5 | \title{Specify priors for the estimated parameters.}
6 | \usage{
7 | sd_prior(par_name, dist, dist_pars, type = "constant", min = NULL, max = NULL)
8 | }
9 | \arguments{
10 | \item{par_name}{A string indicating the name of the estimated parameter.}
11 |
12 | \item{dist}{A string indicating the name of the prior distribution. This name
13 | should be consistent with Stan language. For instance, "normal" indicates
14 | the normal distribution in Stan language.}
15 |
16 | \item{dist_pars}{A numeric vector. For instance, if \code{dist} = "normal",
17 | then \code{dist_pars} will be a vector of size 2 corresponding to
18 | the \emph{location} (mean) and \emph{scale} (standard deviation).}
19 |
20 | \item{type}{A string. It can be either 'constant' or 'init'. It is 'constant'
21 | by default. 'init' refers to parameters that have only affect the model at
22 | time 0.}
23 |
24 | \item{min}{An optional numeric or a string value indicating the estimated
25 | parameter's lower bound. This value overrides the inferred bound from the
26 | prior distribution. For instance, specifying a beta distribution for the
27 | estimated parameter inherently sets the lower bound to 0. Providing a
28 | value to \code{min} will override this default with the supplied value. If
29 | the supplied value is a string, it should be the name of another estimated
30 | parameter.}
31 |
32 | \item{max}{An optional numeric value or a string indicating the estimated
33 | parameter's upper bound. This value overrides the inferred bound from the
34 | prior distribution. For instance, specifying a beta distribution for the
35 | estimated parameter inherently sets the upper bound to \code{1}. Providing
36 | a value to \code{max} will override this default with the supplied value.
37 | If the supplied value is a string, it should be the name of another
38 | estimated parameter.}
39 | }
40 | \value{
41 | A list
42 | }
43 | \description{
44 | \code{sd_prior} returns a list characterising the features of the prior.
45 | }
46 | \examples{
47 | sd_prior("par_beta", "lognormal", c(0, 1))
48 | sd_prior("par_rho", "normal", c(0, 1), min = 0)
49 | }
50 |
--------------------------------------------------------------------------------
/man/sd_posterior_fun.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/posterior_fun.R
3 | \name{sd_posterior_fun}
4 | \alias{sd_posterior_fun}
5 | \title{Posterior function}
6 | \usage{
7 | sd_posterior_fun(
8 | filepath,
9 | meas_data_mdl,
10 | estimated_params,
11 | start_time = NULL,
12 | stop_time = NULL,
13 | timestep = NULL,
14 | integ_method = "euler",
15 | const_list = NULL
16 | )
17 | }
18 | \arguments{
19 | \item{filepath}{A string that indicates a path to a file with extension .stmx
20 | or .xmile. Vensim files (.mdl) are not xmile files. They must be exported
21 | from Vensim with extension .xmile}
22 |
23 | \item{meas_data_mdl}{A list of lists. Each second-level list corresponds to
24 | a sampling statement along with its measurements. Here is an example: \cr
25 | \code{list(formula = "y ~ neg_binomial_2(net_flow(C), phi)",
26 | measurements = 1:10))}}
27 |
28 | \item{estimated_params}{A list of lists. Each sublist describes each
29 | parameter that will be estimated in the inference stage. To construct this
30 | description, the user can avail of the function `sd_prior`.}
31 |
32 | \item{start_time}{A number indicating the time at which the simulation begins.}
33 |
34 | \item{stop_time}{A number indicating the time at which the simulation ends.}
35 |
36 | \item{timestep}{A number indicating the time interval for the simulation.
37 | Also known as \code{dt}.}
38 |
39 | \item{integ_method}{A string indicating the integration method. It can be
40 | either "euler" or "rk4"}
41 |
42 | \item{const_list}{A list in which each element's name is the name of the
43 | constant to override and the element's value correspond to the new value.}
44 | }
45 | \value{
46 | A function
47 | }
48 | \description{
49 | Posterior function
50 | }
51 | \examples{
52 | filepath <- system.file("models/", "SEIR.stmx", package = "readsdr")
53 | meas_data_mdl <- list(list(formula = "y ~ neg_binomial_2(net_flow(C), phi)",
54 | measurements = 1:10))
55 | estimated_params <- list(
56 | sd_prior("par_beta", "lognormal", c(0, 1)),
57 | sd_prior("par_rho", "beta", c(2, 2)),
58 | sd_prior("I0", "lognormal", c(0, 1), "init"))
59 | fun <- sd_posterior_fun(filepath, meas_data_mdl, estimated_params)
60 | }
61 |
--------------------------------------------------------------------------------
/tests/testthat/test-stan_data.R:
--------------------------------------------------------------------------------
1 |
2 | test_that("stan_data() returns the expected string", {
3 |
4 | mm1 <- "y ~ neg_binomial_2(net_flow(C), phi)"
5 | meas_mdl <- list(mm1)
6 |
7 | expected_string <- paste(
8 | "data {",
9 | " int n_obs;",
10 | " array[n_obs] int y;",
11 | " array[n_obs] real ts;",
12 | "}", sep = "\n")
13 |
14 | expect_equal(stan_data(meas_mdl, TRUE, NULL, NULL,
15 | forecast = FALSE), expected_string)
16 |
17 | })
18 |
19 | test_that("stan_data() declares the vector for init values", {
20 |
21 | mm1 <- "y ~ neg_binomial_2(net_flow(C), phi)"
22 | meas_mdl <- list(mm1)
23 |
24 | expected_string <- paste(
25 | "data {",
26 | " int n_obs;",
27 | " array[n_obs] int y;",
28 | " array[n_obs] real ts;",
29 | " vector[5] x0;",
30 | "}", sep = "\n")
31 |
32 | expect_equal(stan_data(meas_mdl, FALSE, NULL, NULL, 5,
33 | forecast = FALSE),
34 | expected_string)
35 | })
36 |
37 | test_that("stan_data() handles single measurements", {
38 |
39 | meas_mdl <- list("y ~ lognormal(log(Hares), sigma_1)",
40 | "z ~ lognormal(log(Lynx), sigma_2)",
41 | "y0 ~ lognormal(log(Hares[0]), sigma_1)")
42 |
43 | actual <- stan_data(meas_mdl, TRUE, NULL, NULL,
44 | forecast = FALSE)
45 |
46 | expected <- paste(
47 | "data {",
48 | " int n_obs;",
49 | " array[n_obs] real y;",
50 | " array[n_obs] real z;",
51 | " real y0;",
52 | " array[n_obs] real ts;",
53 | "}", sep = "\n")
54 |
55 | expect_equal(actual, expected)
56 | })
57 |
58 | # construct_data_decl()---------------------------------------------------------
59 |
60 | test_that("construct_data_decl() returns the expected string", {
61 |
62 | meas_obj <- "y ~ neg_binomial_2(net_flow(C), phi)"
63 | actual <- construct_data_decl(meas_obj)
64 | expected <- " array[n_obs] int y;"
65 |
66 | expect_equal(actual, expected)
67 | })
68 |
69 | test_that("construct_data_decl() handles single measurement", {
70 |
71 | meas_obj <- "y0 ~ lognormal(log(Hares[0]), sigma_1)"
72 | actual <- construct_data_decl(meas_obj)
73 |
74 | expected <- " real y0;"
75 |
76 | expect_equal(actual, expected)
77 | })
78 |
--------------------------------------------------------------------------------
/man/sd_sensitivity_run.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/simulators.R
3 | \name{sd_sensitivity_run}
4 | \alias{sd_sensitivity_run}
5 | \title{Perform a sensitivity run on a System Dynamics model}
6 | \usage{
7 | sd_sensitivity_run(
8 | ds_inputs,
9 | consts_df = NULL,
10 | stocks_df = NULL,
11 | start_time = NULL,
12 | stop_time = NULL,
13 | timestep = NULL,
14 | integ_method = "euler",
15 | multicore = FALSE,
16 | n_cores = NULL,
17 | reporting_interval = 1
18 | )
19 | }
20 | \arguments{
21 | \item{ds_inputs}{A list of deSolve inputs generated by read_xmile}
22 |
23 | \item{consts_df}{A data frame that contains the values of constants to
24 | simulate. Each column corresponds to a constant and each row to an
25 | iteration. If \code{stocks_df} is also supplied, both data frames must have
26 | the same number of rows.}
27 |
28 | \item{stocks_df}{A data frame that containts the initial value of stocks to
29 | be explored. Each column corresponds to a stock and each row to an
30 | iteration. If \code{consts_df} is also supplied, both data frames must have
31 | the same number of rows.}
32 |
33 | \item{start_time}{A number indicating the time at which the simulation begins.}
34 |
35 | \item{stop_time}{A number indicating the time at which the simulation ends.}
36 |
37 | \item{timestep}{A number indicating the time interval for the simulation.
38 | Also known as \code{dt}.}
39 |
40 | \item{integ_method}{A string indicating the integration method. It can be
41 | either "euler" or "rk4"}
42 |
43 | \item{multicore}{A boolean value that indicates whether the process
44 | is parallelised.}
45 |
46 | \item{n_cores}{An integer indicating the number of cores for the parallel run.}
47 |
48 | \item{reporting_interval}{A real number indicating the interval at which the
49 | simulation results are returned. The default is set to \code{1}. For
50 | instance, if the simulation runs from 0 to 10. This function returns the
51 | results at times 0, 1, 2, ..., 10.}
52 | }
53 | \value{
54 | A data frame
55 | }
56 | \description{
57 | \code{sd_sensitivity_run} returns a data frame with the simulation of a
58 | model for several iterations of different inputs.
59 | }
60 | \examples{
61 | path <- system.file("models", "SIR.stmx", package = "readsdr")
62 | ds_inputs <- xmile_to_deSolve(path)
63 | consts_df <- data.frame(i = c(0.25, 0.30))
64 | sd_sensitivity_run(ds_inputs, consts_df)
65 | }
66 |
--------------------------------------------------------------------------------
/R/stat_funs.R:
--------------------------------------------------------------------------------
1 | translate_stat_funs <- function(equation, vendor) {
2 | translate_NORMAL(equation, vendor)
3 | }
4 |
5 | translate_NORMAL <- function(equation, vendor) {
6 | new_equation <- equation
7 |
8 | if(vendor == "isee") {
9 | detection_pattern <- "\\bNORMAL\\b"
10 | pattern_found <- stringr::str_detect(equation, detection_pattern)
11 |
12 | if(pattern_found) {
13 |
14 | validation_pattern <- stringr::regex("NORMAL\\((.+?)\\)",
15 | dotall = TRUE, ignore_case = TRUE)
16 |
17 | validation_match <- stringr::str_match(equation, validation_pattern)
18 | params <- stringr::str_split(validation_match[[2]], ",")
19 | n_params <- length(params[[1]])
20 |
21 | if(n_params != 2) {
22 | stop("readsdr is restricted to translate NORMAL functions with only two parameters: mean, std_dev.",
23 | call. = FALSE)
24 | }
25 |
26 | pattern_normal <- stringr::regex("NORMAL\\((.+?),(.+?)\\)",
27 | dotall = TRUE, ignore_case = TRUE)
28 |
29 | string_match <- stringr::str_match(equation, pattern_normal)
30 | norm_mean <- string_match[[2]]
31 | norm_sd <- string_match[[3]]
32 | replacement <- stringr::str_glue("rnorm(1,{norm_mean},{norm_sd})")
33 |
34 | new_equation <- stringr::str_replace(equation, pattern_normal,
35 | replacement)
36 | }
37 | }
38 |
39 | if(vendor == "Vensim") {
40 |
41 | detection_pattern <- "\\bRANDOM_NORMAL\\b"
42 | pattern_found <- stringr::str_detect(equation, detection_pattern)
43 |
44 | if(pattern_found) {
45 |
46 | pattern_normal <- stringr::regex("RANDOM_NORMAL\\((.+?),(.+?),(.+?),(.+?),(.+?)\\)",
47 | dotall = TRUE, ignore_case = TRUE)
48 |
49 | string_match <- stringr::str_match(equation, pattern_normal)
50 | min_val <- string_match[[2]]
51 | max_val <- string_match[[3]]
52 | norm_mean <- string_match[[4]]
53 | norm_sd <- string_match[[5]]
54 | replacement <- stringr::str_glue("truncnorm::rtruncnorm(1,{min_val},{max_val},{norm_mean},{norm_sd})")
55 |
56 | new_equation <- stringr::str_replace(equation, pattern_normal,
57 | replacement)
58 |
59 | }
60 | }
61 |
62 | new_equation
63 | }
64 |
65 |
--------------------------------------------------------------------------------
/tests/testthat/test-stan_utils.R:
--------------------------------------------------------------------------------
1 |
2 | test_that("get_dist_obj() declares the vector for init values", {
3 |
4 | rhs <- "neg_binomial_2(net_flow(C), phi)"
5 |
6 | actual <- get_dist_obj(rhs)
7 |
8 | expected <- list(dist_name = "neg_binomial_2",
9 | mu = "net_flow(C)",
10 | phi = "phi")
11 |
12 | expect_equal(actual, expected)
13 | })
14 |
15 | test_that("get_meas_params() deals with a given concentration parameter", {
16 |
17 | meas_mdl <- list("y ~ neg_binomial_2(net_flow(C), 10)")
18 | estimated_params <- list(sd_prior("par_beta", "lognormal", c(0, 1)))
19 |
20 | actual <- get_meas_params(meas_mdl, estimated_params)
21 | expected <- estimated_params
22 |
23 | expect_equal(actual, expected)
24 | })
25 |
26 | test_that("get_meas_params() handles priors for measurement parameters", {
27 |
28 | meas_mdl <- list("y1 ~ lognormal(log(Lynx), sigma_1)")
29 |
30 | estimated_params <- list(sd_prior("par_alpha", "normal", c(1, 0.5),
31 | min = 0),
32 | sd_prior("sigma_1", "lognormal", c(-1, 1)))
33 |
34 | actual <- get_meas_params(meas_mdl, estimated_params)
35 |
36 | expected <- list(sd_prior("par_alpha", "normal", c(1, 0.5),
37 | min = 0),
38 | list(par_name = "sigma_1",
39 | dist = "lognormal",
40 | type = "meas_par",
41 | mu = -1,
42 | sigma = 1,
43 | min = 0))
44 |
45 | expect_equal(actual, expected)
46 | })
47 |
48 | test_that("translate_stock_text() returns the expected object", {
49 |
50 | lvl_names <- c("Hares", "Lynx")
51 | delta_counter <- 1
52 | stock_txt <- "log(Hares[0])"
53 |
54 | actual <- translate_stock_text(stock_txt, delta_counter, lvl_names)
55 |
56 | expected <- list(stock_txt = "log(x0[1])",
57 | delta_counter = 1)
58 |
59 | expect_equal(actual, expected)
60 | })
61 |
62 | #---------tidy_meas_params()------------------------------------------------
63 |
64 | test_that("tidy_meas_params() returns the expected list", {
65 |
66 | meas_obj <- "y ~ neg_binomial_2(net_flow(C), phi)"
67 | actual <- tidy_meas_params(meas_obj, list())
68 |
69 | expected <- list(
70 | list(par_name = "inv_phi",
71 | dist = "exponential",
72 | beta = 5,
73 | min = 0,
74 | type = "meas_par",
75 | par_trans = "inv"))
76 |
77 | expect_equal(actual, expected)
78 | })
79 |
--------------------------------------------------------------------------------
/tests/testthat/test-posterior_fun.R:
--------------------------------------------------------------------------------
1 | test_that("sd_posterior_fun() returns the expected function", {
2 |
3 | filepath <- system.file("models/", "SEIR.stmx", package = "readsdr")
4 |
5 | meas_data_mdl <- list(list(formula = "y ~ neg_binomial_2(net_flow(C), phi)",
6 | measurements = 1:10))
7 |
8 | estimated_params <- list(
9 | sd_prior("par_beta", "lognormal", c(0, 1)),
10 | sd_prior("par_rho", "beta", c(2, 2)),
11 | sd_prior("I0", "lognormal", c(0, 1), "init"))
12 |
13 | actual_obj <- sd_posterior_fun(filepath, meas_data_mdl, estimated_params)
14 |
15 | actual_fun <- actual_obj$fun
16 |
17 | actual_val <- actual_fun(c(log(1), logit(0.75), log(1), log(3)))
18 |
19 | expected_val <- -302.0369419999466345
20 |
21 | expect_equal(actual_val, expected_val)
22 | })
23 |
24 | test_that("log_prior_fun_generator() returns the expected function", {
25 |
26 | estimated_params <- list(
27 | sd_prior("par_beta", "lognormal", c(0, 1)),
28 | sd_prior("par_rho", "beta", c(2, 2)),
29 | sd_prior("I0", "lognormal", c(0, 1), "init"))
30 |
31 | meas_data_mdl <- list(list(formula = "y ~ neg_binomial_2(net_flow(C), phi)",
32 | measurements = 1:10))
33 |
34 | log_prior_fun <- log_prior_fun_generator(estimated_params, meas_data_mdl)
35 |
36 | actual_val <- log_prior_fun(c(log(1), logit(0.75), log(1), log(3)))
37 |
38 | expected_val <- dlnorm(1, 0, 1, log = TRUE) + dbeta(0.75, 2, 2, log = TRUE) +
39 | dlnorm(1, 0, 1, log = TRUE) + dexp(1/3, rate = 5, log = TRUE)
40 |
41 | expect_equal(actual_val, expected_val)
42 | })
43 |
44 | test_that("build_prior_expr() returns the expected string", {
45 |
46 | par_obj <- list(par_name = "par_beta",
47 | dist = "lognormal",
48 | mu = 0,
49 | sigma = 1,
50 | par_trans = "exp",
51 | index = 1)
52 |
53 | actual <- build_prior_expr(par_obj)
54 |
55 | expected <- " dlnorm(exp(pars[[1]]), meanlog = 0, sdlog = 1, log = TRUE)"
56 |
57 | expect_equal(actual, expected)
58 |
59 | })
60 |
61 | test_that("constrain_pars() returns the expected string", {
62 |
63 | par_obj <- list(par_trans = "exp",
64 | index = 1)
65 |
66 | actual <- constrain_pars(par_obj)
67 |
68 | expected <- "exp(pars[[1]])"
69 |
70 | expect_equal(actual, expected)
71 |
72 |
73 | par_obj <- list(par_trans = c("exp", "inv"),
74 | index = 4)
75 |
76 | actual <- constrain_pars(par_obj)
77 |
78 | expected <- "inv(exp(pars[[4]]))"
79 |
80 | expect_equal(actual, expected)
81 | })
82 |
--------------------------------------------------------------------------------
/R/create_stan_function.R:
--------------------------------------------------------------------------------
1 | #' Create a Stan's ODE function from an XMILE file
2 | #'
3 | #' \code{create_stan_function} returns a string with the code for a Stan's ODE function
4 | #'
5 | #' This function extracts the xml from the file specified via \code{filepath} to
6 | #' generate the code for an equivalent model in Stan.
7 | #'
8 | #' @param override.consts A list in which each element is a name-value pair that
9 | #' replaces values of constants.
10 | #' @param additional_funs A vector of strings. Each string corresponds to a
11 | #' user-defined function.
12 | #' @inheritParams read_xmile
13 | #' @inheritParams stan_ode_function
14 | #'
15 | #' @return A string with the code containing the model's equations in the
16 | #' format required by Stan.
17 | #' @export
18 | #'
19 | #' @examples
20 | #' path <- system.file("models", "SIR.stmx", package = "readsdr")
21 | #' create_stan_function(path, "my_model")
22 |
23 | create_stan_function <- function (filepath, func_name, pars = NULL,
24 | override.consts = NULL,
25 | additional_funs = NULL) {
26 |
27 | XMILE_structure <- extract_structure_from_XMILE(filepath)
28 |
29 | levels <- XMILE_structure$levels
30 | variables <- XMILE_structure$variables
31 | constants <- XMILE_structure$constants
32 |
33 | n_stocks <- length(levels)
34 | level_names <- get_names(levels)
35 |
36 | function_name_line <- paste0(" real[] ", func_name, "(real time,")
37 | diff_eq_declaration_line <- paste0(" real dydt[", n_stocks, "];")
38 |
39 | const_names <- get_names(constants)
40 |
41 | purrr::walk(override.consts, function(const_list) {
42 | pos <- which(const_list$name == const_names)
43 | constants[[pos]]$value <<- const_list$value
44 | })
45 |
46 | constants <- set_unknowns(pars, constants)
47 |
48 | vars_declaration <- get_auxs_declaration(variables)
49 |
50 | vars_equations <- get_equations(variables, constants, level_names)
51 |
52 | counter <- 0
53 | diff_eq <- get_diffeq(levels)
54 |
55 | stan_function <- paste(
56 | "functions {",
57 | function_name_line ,
58 | " real[] y,",
59 | " real[] params,",
60 | " real[] x_r,",
61 | " int[] x_i) {",
62 | diff_eq_declaration_line,
63 | vars_declaration,
64 | vars_equations,
65 | diff_eq,
66 | " return dydt;",
67 | " }",
68 | sep = "\n")
69 |
70 | if(!is.null(additional_funs)) {
71 | af_text <- paste(additional_funs, sep = "\n")
72 | stan_function <- paste(stan_function, af_text, sep = "\n")
73 | }
74 |
75 | stan_function <- paste(stan_function, "}", sep = "\n")
76 | }
77 |
78 |
79 |
--------------------------------------------------------------------------------
/tests/testthat/test-sanitise_xml.R:
--------------------------------------------------------------------------------
1 | test_that("sanitise_xml() escapes comparison operators for a single equation", {
2 | test_text <- '
3 |
4 |
5 |
6 | IF_THEN_ELSE(a > b :AND: a <= c , a, 0)
7 |
8 |
9 | '
10 |
11 | actual_output <- sanitise_xml(test_text)
12 |
13 | expected_output <- '
14 |
15 |
16 |
17 | IF_THEN_ELSE(a > b :AND: a <= c , a, 0)
18 |
19 |
20 | '
21 |
22 | expect_equal(actual_output, expected_output)
23 | })
24 |
25 | test_that("sanitise_xml() escapes comparison operators for multiple equation", {
26 | test_text <- '
27 |
28 |
29 |
30 | IF_THEN_ELSE(a > b :AND: a <= c , a, 0)
31 |
32 |
33 |
34 |
35 | IF_THEN_ELSE(b > a :AND: c <= a , 0, a)
36 |
37 |
38 |
39 |
40 | IF_THEN_ELSE(b < a :AND: c >= a , b, a)
41 |
42 |
43 | '
44 |
45 | actual_output <- sanitise_xml(test_text)
46 |
47 | expected_output <- '
48 |
49 |
50 |
51 | IF_THEN_ELSE(a > b :AND: a <= c , a, 0)
52 |
53 |
54 |
55 |
56 | IF_THEN_ELSE(b > a :AND: c <= a , 0, a)
57 |
58 |
59 |
60 |
61 | IF_THEN_ELSE(b < a :AND: c >= a , b, a)
62 |
63 |
64 | '
65 |
66 | expect_equal(actual_output, expected_output)
67 | })
68 |
69 | test_that("sanitise_xml() does not alter the original xml code", {
70 | test_text <- '
71 |
72 |
73 |
74 | a + b
75 |
76 |
77 | a + c
78 |
79 |
80 | b + c
81 |
82 |
83 | '
84 |
85 | actual_output <- sanitise_xml(test_text)
86 |
87 | expected_output <- '
88 |
89 |
90 |
91 | a + b
92 |
93 |
94 | a + c
95 |
96 |
97 | b + c
98 |
99 |
100 | '
101 |
102 | expect_equal(actual_output, expected_output)
103 | })
104 |
105 |
106 |
--------------------------------------------------------------------------------
/tests/testthat/test-xmile_graph_funs.R:
--------------------------------------------------------------------------------
1 | test_that("translate_Vensim_graph_func() returns the expected object", {
2 | test_equation <- "WITHLOOKUP(Price,([(0,10)-(50,100)],(5,100),(10,73),(15,57),(20,45),(25,35),(30,28),(35,22),(40,18),(45,14),(50,10)))"
3 |
4 | actual_obj <- translate_Vensim_graph_func(test_equation)
5 | expected_obj <- list(
6 | input = "Price",
7 | graph_fun = approxfun(
8 | x = seq(5, 50, 5),
9 | y = c(100, 73, 57, 45, 35, 28, 22, 18, 14, 10),
10 | method = "linear",
11 | yleft = 100,
12 | yright = 10))
13 |
14 | comparison_result <- all.equal(actual_obj, expected_obj,
15 | check.environment = FALSE)
16 |
17 | expect_equal(comparison_result, TRUE)
18 | })
19 |
20 | test_that("translate_graph_func() returns the expected object", {
21 | test_gf_xml <- xml2::read_xml('
22 |
23 |
24 |
25 | Price
26 |
27 |
28 |
29 | 100,73,57,45,35,28,22,18,14,10
30 |
31 |
32 |
33 | ') |>
34 | xml2::xml_find_first(".//d1:gf")
35 |
36 | actual_obj <- translate_graph_func(test_gf_xml)
37 |
38 | expected_obj <- approxfun(
39 | x = seq(5, 50, 5),
40 | y = c(100, 73, 57, 45, 35, 28, 22, 18, 14, 10),
41 | method = "linear",
42 | yleft = 100,
43 | yright = 10)
44 |
45 | comparison_result <- all.equal(actual_obj, expected_obj)
46 |
47 | expect_equal(comparison_result, TRUE)
48 | })
49 |
50 | test_that("translate_graph_func() ignores yscale when ypts is defined", {
51 |
52 | test_gf_xml <- xml2::read_xml('
53 |
54 |
55 |
56 | TIME
57 |
58 |
59 | 1,53,105,157,209,261,313,365,417,469,521
60 | 4695,4745,4755,4796,4841,4862,4932,5025,5125,5209,5303
61 |
62 | People
63 |
64 |
65 | ') |>
66 | xml2::xml_find_first(".//d1:gf")
67 |
68 |
69 | actual_obj <- translate_graph_func(test_gf_xml)
70 |
71 | expected_obj <- approxfun(
72 | x = c(1, 53, 105, 157, 209, 261, 313, 365, 417, 469, 521),
73 | y = c(4695, 4745, 4755, 4796, 4841, 4862, 4932, 5025, 5125, 5209, 5303),
74 | method = "linear",
75 | yleft = 4695,
76 | yright = 5303)
77 |
78 | comparison_result <- all.equal(actual_obj, expected_obj)
79 |
80 | expect_equal(comparison_result, TRUE)
81 |
82 | })
83 |
--------------------------------------------------------------------------------
/tests/testthat/test_stan_files/SE3I3R_pois.stan:
--------------------------------------------------------------------------------
1 | // Code generated by the R package readsdr v0.3.0.9001
2 | // See more info at github https://github.com/jandraor/readsdr
3 | functions {
4 | vector X_model(real time, vector y, array[] real params) {
5 | vector[11] dydt;
6 | real S_to_E;
7 | real dly_E_to_I_1_out;
8 | real dly_E_to_I_2_out;
9 | real dly_E_to_I_3_out;
10 | real dly_S_to_E_1_out;
11 | real dly_S_to_E_2_out;
12 | real dly_S_to_E_3_out;
13 | real I_to_R;
14 | real E_to_I;
15 | real C_in;
16 | S_to_E = params[1]*y[7]*y[9]/10000;
17 | dly_E_to_I_1_out = y[1]/((2)/3.0);
18 | dly_E_to_I_2_out = y[2]/((2)/3.0);
19 | dly_E_to_I_3_out = y[3]/((2)/3.0);
20 | dly_S_to_E_1_out = y[4]/((2)/3.0);
21 | dly_S_to_E_2_out = y[5]/((2)/3.0);
22 | dly_S_to_E_3_out = y[6]/((2)/3.0);
23 | I_to_R = dly_E_to_I_3_out;
24 | E_to_I = dly_S_to_E_3_out;
25 | C_in = params[2]*E_to_I;
26 | dydt[1] = E_to_I - dly_E_to_I_1_out;
27 | dydt[2] = dly_E_to_I_1_out - dly_E_to_I_2_out;
28 | dydt[3] = dly_E_to_I_2_out - dly_E_to_I_3_out;
29 | dydt[4] = S_to_E - dly_S_to_E_1_out;
30 | dydt[5] = dly_S_to_E_1_out - dly_S_to_E_2_out;
31 | dydt[6] = dly_S_to_E_2_out - dly_S_to_E_3_out;
32 | dydt[7] = -S_to_E;
33 | dydt[8] = S_to_E-E_to_I;
34 | dydt[9] = E_to_I-I_to_R;
35 | dydt[10] = I_to_R;
36 | dydt[11] = C_in;
37 | return dydt;
38 | }
39 | }
40 | data {
41 | int n_obs;
42 | array[n_obs] int y;
43 | array[n_obs] real ts;
44 | }
45 | parameters {
46 | real par_beta;
47 | real par_rho;
48 | real I0;
49 | }
50 | transformed parameters{
51 | array[n_obs] vector[11] x; // Output from the ODE solver
52 | array[2] real params;
53 | vector[11] x0; // init values
54 | array[n_obs] real delta_x_1;
55 | x0[1] = ((0.5)*I0 * 1/(0.5)) / (3); // dly_E_to_I_1
56 | x0[2] = ((0.5)*I0 * 1/(0.5)) / (3); // dly_E_to_I_2
57 | x0[3] = ((0.5)*I0 * 1/(0.5)) / (3); // dly_E_to_I_3
58 | x0[4] = 0; // dly_S_to_E_1
59 | x0[5] = 0; // dly_S_to_E_2
60 | x0[6] = 0; // dly_S_to_E_3
61 | x0[7] = (10000) - I0; // S
62 | x0[8] = 0; // E
63 | x0[9] = I0; // I
64 | x0[10] = 0; // R
65 | x0[11] = 0; // C
66 | params[1] = par_beta;
67 | params[2] = par_rho;
68 | x = ode_rk45(X_model, x0, 0, ts, params);
69 | delta_x_1[1] = x[1, 11] - x0[11] + 1e-5;
70 | for (i in 1:n_obs-1) {
71 | delta_x_1[i + 1] = x[i + 1, 11] - x[i, 11] + 1e-5;
72 | }
73 | }
74 | model {
75 | par_beta ~ lognormal(0, 1);
76 | par_rho ~ beta(2, 2);
77 | I0 ~ lognormal(0, 1);
78 | y ~ poisson(delta_x_1);
79 | }
80 | generated quantities {
81 | real log_lik;
82 | array[n_obs] int sim_y;
83 | log_lik = poisson_lpmf(y | delta_x_1);
84 | sim_y = poisson_rng(delta_x_1);
85 | }
86 |
--------------------------------------------------------------------------------
/R/arrange_variables.R:
--------------------------------------------------------------------------------
1 | #' Arrange variables
2 | #'
3 | #' \code{arrange_variables} returns a list of auxiliary variables sorted in
4 | #' computational order
5 | #'
6 | #' This function iterates over each element until the variables are ordered in
7 | #' computational order. This is how should be used:
8 | #'
9 | #' unordered_vars <- list(list(name = "b", equation = "a + 1"),
10 | #' list(name = "a", equation = "alpha"))
11 | #' ordered_vars <- arrange_variables(unordered_vars)
12 | #'
13 | #' @param var_list A list of lists. Each second-level list corresponds to a
14 | #' variable in the model and must be a name-equation pair. Given that variables
15 | #' depend on other elements in the model, equations cannot consist of a single
16 | #' scalar.
17 | #'
18 | #' @return A list consisting of the elements in \code{var_list} but arranged in
19 | #' computational order.
20 | #'
21 | #' @noRd
22 |
23 | arrange_variables <- function(var_list) {
24 |
25 | if (length(var_list) == 0L) return (var_list)
26 |
27 | var_names <- sapply(var_list, function(varElem) varElem$name)
28 |
29 | n_equations <- length(var_names)
30 | states <- rep(0, n_equations) # Flag that indicates whether the var is defined
31 | names(states) <- var_names
32 | equations <- sapply(var_list, function(var_obj) var_obj$equation)
33 |
34 | equations_df <- data.frame(stringsAsFactors = FALSE, equation = equations) %>%
35 | dplyr::group_by(equation) %>%
36 | dplyr::mutate("ocurrence" = dplyr::row_number()) %>%
37 | dplyr::ungroup()
38 |
39 | equations_df$id <- paste(equations_df$equation, equations_df$ocurrence,
40 | sep = "_")
41 |
42 | equations_df$ocurrence <- NULL
43 |
44 | aux_ids <- equations_df$id
45 | sorted_variables <- vector(mode = "list", length = n_equations)
46 | current_pos <- 1
47 |
48 | while (length(aux_ids) > 0) {
49 |
50 | id <- aux_ids[1]
51 | pos_equation <- which(id == equations_df$id)
52 | equation <- var_list[[pos_equation]]$equation
53 | other_ids <- aux_ids[-1]
54 | lhs <- var_list[[pos_equation]]$name
55 | rh_vars <- extract_variables(lhs, equation)
56 |
57 | if(length(rh_vars) == 0L) {
58 |
59 | msg <- paste0("There are no variables in the RHS of `", lhs, "`. RHS: ",
60 | equation)
61 |
62 | stop(msg, call. = FALSE)
63 | }
64 |
65 | undefined_vars <- sapply(rh_vars, function(var){
66 | ifelse(var %in% var_names && states[var] == 0, TRUE, FALSE)
67 | })
68 |
69 | n_und_var <- sum(undefined_vars)
70 |
71 | if(n_und_var == 0) {
72 | sorted_variables[current_pos] <- var_list[pos_equation]
73 | states[lhs] <- 1
74 | aux_ids <- aux_ids[-1]
75 | current_pos <- current_pos + 1
76 | }
77 |
78 | if(n_und_var > 0) aux_ids <- c(other_ids, id)
79 | }
80 |
81 | sorted_variables
82 | }
83 |
--------------------------------------------------------------------------------
/tests/testthat/test_stan_files/LV.stan:
--------------------------------------------------------------------------------
1 | // Code generated by the R package readsdr v0.3.0.9001
2 | // See more info at github https://github.com/jandraor/readsdr
3 | functions {
4 | vector X_model(real time, vector y, array[] real params) {
5 | vector[2] dydt;
6 | real H_births;
7 | real H_deaths;
8 | real L_deaths;
9 | real L_births;
10 | H_births = params[1]*y[1];
11 | H_deaths = params[3]*y[1]*y[2];
12 | L_deaths = params[2]*y[2];
13 | L_births = params[4]*y[1]*y[2];
14 | dydt[1] = H_births-H_deaths;
15 | dydt[2] = L_births-L_deaths;
16 | return dydt;
17 | }
18 | }
19 | data {
20 | int n_obs;
21 | array[n_obs] real y;
22 | array[n_obs] real z;
23 | real y0;
24 | real z0;
25 | array[n_obs] real ts;
26 | int n_fcst;
27 | }
28 | parameters {
29 | real par_alpha;
30 | real par_gamma;
31 | real par_beta;
32 | real par_delta;
33 | real sigma_1;
34 | real sigma_2;
35 | real H0;
36 | real L0;
37 | }
38 | transformed parameters{
39 | array[n_obs] vector[2] x; // Output from the ODE solver
40 | array[4] real params;
41 | vector[2] x0; // init values
42 | x0[1] = H0; // Hares
43 | x0[2] = L0; // Lynx
44 | params[1] = par_alpha;
45 | params[2] = par_gamma;
46 | params[3] = par_beta;
47 | params[4] = par_delta;
48 | x = ode_rk45(X_model, x0, 0, ts, params);
49 | }
50 | model {
51 | par_alpha ~ normal(1, 0.5);
52 | par_gamma ~ normal(1, 0.5);
53 | par_beta ~ normal(0.5, 0.5);
54 | par_delta ~ normal(0.5, 0.5);
55 | sigma_1 ~ lognormal(-1, 1);
56 | sigma_2 ~ lognormal(-1, 1);
57 | H0 ~ lognormal(2.30258509299405, 1);
58 | L0 ~ lognormal(2.30258509299405, 1);
59 | y ~ lognormal(log(x[:, 1]), sigma_1);
60 | z ~ lognormal(log(x[:, 2]), sigma_2);
61 | y0 ~ lognormal(log(x0[1]), sigma_1);
62 | z0 ~ lognormal(log(x0[2]), sigma_1);
63 | }
64 | generated quantities {
65 | real log_lik;
66 | array[n_obs] real sim_y;
67 | array[n_obs] real sim_z;
68 | real sim_y0;
69 | real sim_z0;
70 | array[n_fcst] real fcst_y;
71 | array[n_fcst] real fcst_z;
72 | array[n_fcst] vector[2] x_fcst; // Forecast
73 | array[n_fcst] real t_fcst;
74 | vector[2] x_fcst0; // Forecast init values
75 | log_lik = lognormal_lpdf(y | log(x[:, 1]), sigma_1) +
76 | lognormal_lpdf(z | log(x[:, 2]), sigma_2) +
77 | lognormal_lpdf(y0 | log(x0[1]), sigma_1) +
78 | lognormal_lpdf(z0 | log(x0[2]), sigma_1);
79 | // Simulate forecast
80 | x_fcst0 = x[n_obs, :];
81 | t_fcst = linspaced_array(n_fcst, 1, n_fcst);
82 | x_fcst = ode_rk45(X_model, x_fcst0, 0, t_fcst, params);
83 | sim_y = lognormal_rng(log(x[:, 1]), sigma_1);
84 | sim_z = lognormal_rng(log(x[:, 2]), sigma_2);
85 | sim_y0 = lognormal_rng(log(x0[1]), sigma_1);
86 | sim_z0 = lognormal_rng(log(x0[2]), sigma_1);
87 | fcst_y = lognormal_rng(log(x_fcst[:, 1]), sigma_1);
88 | fcst_z = lognormal_rng(log(x_fcst[:, 2]), sigma_2);
89 | }
90 |
--------------------------------------------------------------------------------
/R/what_if_from_time.R:
--------------------------------------------------------------------------------
1 | #' What if from time t we change the value of some parameters
2 | #'
3 | #' @param time Time at which the parameter values change
4 | #' @param up_to_time Time from which the original values are restored.
5 | #' @param par_list A list that indicates which parameters change from time t.
6 | #' For instance, if you wanted to change the value of parameter \code{c} to 4,
7 | #' you would provide the \code{list(c = 4)}
8 | #'
9 | #' @inheritParams sd_simulate
10 | #'
11 | #' @return A data frame
12 | #' @export
13 | #'
14 | #' @examples
15 | #' filepath <- system.file("models/", "SIR.stmx", package = "readsdr")
16 | #' mdl <- read_xmile(filepath)
17 | #' ds_components <- mdl$deSolve_components
18 | #' output <- sd_what_if_from_time(3, Inf, list(c = 4), ds_components)
19 | sd_what_if_from_time <- function(time, up_to_time = Inf, par_list, ds_inputs,
20 | start_time = NULL, stop_time = NULL,
21 | timestep = NULL, integ_method = "euler") {
22 |
23 | stopifnot("'up_to_time' must be greater than 'time'" = up_to_time > time)
24 |
25 | unchanged_consts <- ds_inputs$consts
26 |
27 | if(!is.null(start_time)) {
28 | ds_inputs$sim_params$start <- start_time
29 | }
30 |
31 | if(!is.null(stop_time)) {
32 | ds_inputs$sim_params$stop <- stop_time
33 | } else {
34 | stop_time <- ds_inputs$sim_params$stop
35 | }
36 |
37 | if(!is.null(timestep)) {
38 | ds_inputs$sim_params$dt <- timestep
39 | }
40 |
41 | mid_time <- time
42 |
43 | first_run <- sd_simulate(ds_inputs, stop_time = mid_time,
44 | integ_method = integ_method)
45 |
46 | last_row <- utils::tail(first_run, 1)
47 | stk_names <- names(ds_inputs$stocks)
48 | new_stocks <- last_row[, stk_names] %>% unlist()
49 | ds_inputs$stocks <- new_stocks
50 |
51 | par_names <- names(par_list)
52 | ds_inputs$consts[par_names] <- unlist(par_list)
53 |
54 |
55 | sr_stop_time <- min(up_to_time, stop_time) # second run stop time
56 |
57 | second_run <- sd_simulate(ds_inputs,
58 | start_time = mid_time,
59 | stop_time = sr_stop_time,
60 | integ_method = integ_method)
61 |
62 | current_output <- rbind(utils::head(first_run, -1), second_run)
63 |
64 | if(is.infinite(up_to_time)) return(current_output)
65 |
66 | last_row <- utils::tail(current_output, 1)
67 | stk_names <- names(ds_inputs$stocks)
68 | new_stocks <- last_row[, stk_names] %>% unlist()
69 | ds_inputs$stocks <- new_stocks
70 | ds_inputs$consts <- unchanged_consts
71 |
72 | third_run <- sd_simulate(ds_inputs,
73 | start_time = sr_stop_time,
74 | stop_time = stop_time,
75 | integ_method = integ_method)
76 |
77 | rbind(utils::head(current_output, -1), third_run)
78 | }
79 |
--------------------------------------------------------------------------------
/R/prior_checks.R:
--------------------------------------------------------------------------------
1 | #' Prior predictive checks
2 | #'
3 | #' @param n_draws An integer that indicates how many time-series will be
4 | #' returned.
5 | #' @inheritParams read_xmile
6 | #' @inheritParams sd_Bayes
7 | #' @inheritParams sd_simulate
8 | #'
9 | #' @return A list of two data frames.
10 | #' @export
11 | #'
12 | #' @examples
13 | #' filepath <- system.file("models/", "SEIR.stmx", package = "readsdr")
14 | #' meas_mdl <- list("y ~ neg_binomial_2(net_flow(C), phi)")
15 | #' estimated_params <- list(
16 | #' sd_prior("par_beta", "lognormal", c(0, 1)),
17 | #' sd_prior("par_rho", "beta", c(2, 2)),
18 | #' sd_prior("I0", "lognormal", c(0, 1), "init"))
19 | #' sd_prior_checks(filepath, meas_mdl, estimated_params, n_draws = 2,
20 | #' start_time = 0, stop_time = 5,
21 | #' integ_method = "rk4", timestep = 1/32)
22 | sd_prior_checks <- function(filepath, meas_mdl, estimated_params, n_draws,
23 | start_time = NULL,
24 | stop_time = NULL,
25 | timestep = NULL,
26 | integ_method = "euler") {
27 |
28 | pars_names <- get_names(estimated_params, "par_name")
29 |
30 | estimated_params <- get_meas_params(meas_mdl, estimated_params)
31 | unk_types <- sapply(estimated_params,
32 | function(prior_obj) prior_obj$type)
33 |
34 | idx_meas <- which(unk_types == "meas_par")
35 | n_meas_par <- length(idx_meas)
36 |
37 | prior_fun_list <- prior_fun_factory(estimated_params, n_draws)
38 | prior_vals <- lapply(prior_fun_list,
39 | function(prior_fun) prior_fun())
40 |
41 | df1 <- cbind(data.frame(iter = 1:n_draws), as.data.frame(prior_vals))
42 |
43 |
44 | mdl_structure <- extract_structure_from_XMILE(filepath, pars_names)
45 | ds_inputs <- get_deSolve_elems(mdl_structure)
46 |
47 | if(!(integ_method %in% c("euler", "rk4"))) stop("Invalid integration method")
48 |
49 | ds_inputs <- update_sim_params(ds_inputs, start_time, stop_time, timestep)
50 |
51 | if(n_meas_par > 0) {
52 |
53 | meas_params <- estimated_params[idx_meas]
54 |
55 | # List of configured measurement models.
56 | meas_mdl_conf <- configure_meas_models(meas_mdl, meas_params, prior_vals)
57 | }
58 |
59 | df2 <- purrr::map_dfr(1:n_draws, function(i) {
60 |
61 | for(param in pars_names) ds_inputs$consts[[param]] <- prior_vals[[param]][[i]]
62 |
63 | par_list <- unlist(purrr::transpose(prior_vals)[i], recursive = FALSE)
64 | readsdr_env <- list2env(par_list)
65 |
66 | ds_inputs$stocks <- purrr::map_dbl(ds_inputs$stocks, function(x) {
67 |
68 | eval(parse(text = x), envir = readsdr_env)
69 | })
70 |
71 | measurement_df <- sd_measurements(1, meas_mdl_conf[[i]],
72 | ds_inputs,
73 | integ_method = integ_method)
74 | measurement_df$iter <- i
75 | measurement_df
76 | })
77 |
78 |
79 | list(parameters = df1,
80 | measurements = df2)
81 | }
82 |
83 |
84 |
--------------------------------------------------------------------------------
/tests/testthat/test-extract_variables.R:
--------------------------------------------------------------------------------
1 | test_that("extract_variables() returns variables in equations that have numbers & exponents", {
2 | test_lhs <- "test_var"
3 | equation <- "gamma * (1 + beta) ^ alpha"
4 | actual_vars <- extract_variables(test_lhs, equation)
5 | actual_vars <- sort(actual_vars)
6 | expected_vars <- c("alpha", "beta", "gamma")
7 | expect_equal(actual_vars, expected_vars)
8 | })
9 |
10 | test_that("extract_variables() ignores min function", {
11 | test_lhs <- "test_var"
12 | equation <- "min(minimiser, maximiser)"
13 | actual_vars <- extract_variables(test_lhs, equation)
14 | actual_vars <- sort(actual_vars)
15 | expected_vars <- c("maximiser", "minimiser")
16 | expect_equal(actual_vars, expected_vars)
17 | })
18 |
19 | test_that("extract_variables() ignores max function", {
20 | test_lhs <- "test_var"
21 | equation <- "max(minimiser, maximiser)"
22 | actual_vars <- extract_variables(test_lhs, equation)
23 | actual_vars <- sort(actual_vars)
24 | expected_vars <- c("maximiser", "minimiser")
25 | expect_equal(actual_vars, expected_vars)
26 | })
27 |
28 | test_that("extract_variables() ignores graph functions", {
29 | test_lhs <- "test_var"
30 | equation <- "f_test_var(a + b)"
31 | actual_vars <- extract_variables(test_lhs, equation)
32 | actual_vars <- sort(actual_vars)
33 | expected_vars <- c("a", "b")
34 | expect_equal(actual_vars, expected_vars)
35 | })
36 |
37 | test_that("extract_variables() returns unique elements", {
38 | test_lhs <- "test_var"
39 | equation <- "a^2 + 2*a*b + b^2"
40 | actual_vars <- extract_variables(test_lhs, equation)
41 | actual_vars <- sort(actual_vars)
42 | expected_vars <- c("a", "b")
43 | expect_equal(actual_vars, expected_vars)
44 | })
45 |
46 | test_that("extract_variables() ignores ifelse functions", {
47 | test_lhs <- "test_var"
48 | equation <- "ifelse(a + b, a, b)"
49 | actual_vars <- extract_variables(test_lhs, equation)
50 | actual_vars <- sort(actual_vars)
51 | expected_vars <- c("a", "b")
52 | expect_equal(actual_vars, expected_vars)
53 | })
54 |
55 | test_that("extract_variables() ignores logical operators", {
56 | test_lhs <- "test_var"
57 | equation <- "ifelse(!(a>b)&a>c|cb:AND:a>c"
5 | actual_val <- translate_AND(test_equation, "Vensim")
6 | expected_val <- 'a>b&a>c'
7 | expect_equal(actual_val, expected_val)
8 | })
9 |
10 | test_that("translate_AND() returns for the correct translation for expressions from Stella", {
11 | test_equation <- "ifelse(a>5AND(a<10),1,0)"
12 | actual_val <- translate_AND(test_equation, "isee")
13 | expected_val <- 'ifelse(a>5&(a<10),1,0)'
14 | expect_equal(actual_val, expected_val)
15 | })
16 |
17 | test_that("translate_AND() returns for the correct translation for compounded expressions from Stella", {
18 | test_equation <- "ifelse(a>5AND(a<10)AND(a<15),0.1,0)"
19 | actual_val <- translate_AND(test_equation, "isee")
20 | expected_val <- "ifelse(a>5&(a<10)&(a<15),0.1,0)"
21 | expect_equal(actual_val, expected_val)
22 | })
23 |
24 | context("Translate OR")
25 |
26 | test_that("translate_OR() returns for the correct translation for expressions from VENSIM", {
27 | test_equation <- "a>b:OR:a>c"
28 | actual_val <- translate_OR(test_equation, "Vensim")
29 | expected_val <- 'a>b|a>c'
30 | expect_equal(actual_val, expected_val)
31 | })
32 |
33 | test_that("translate_OR() returns for the correct translation for expressions from Stella", {
34 | test_equation <- "ifelse(a>5OR(a<10),1,0)"
35 | actual_val <- translate_OR(test_equation, "isee")
36 | expected_val <- 'ifelse(a>5|(a<10),1,0)'
37 | expect_equal(actual_val, expected_val)
38 | })
39 |
40 | test_that("translate_OR() dealw with cases for expressions from Stella", {
41 | test_equation <- "ifelse(a>5or(a<10),1,0)"
42 | actual_val <- translate_OR(test_equation, "isee")
43 | expected_val <- 'ifelse(a>5|(a<10),1,0)'
44 | expect_equal(actual_val, expected_val)
45 | })
46 |
47 | test_that("translate_OR() returns for the correct translation for compounded expressions from Stella", {
48 | test_equation <- "ifelse(a>5OR(a<10)OR(a<15),0.1,0)"
49 | actual_val <- translate_OR(test_equation, "isee")
50 | expected_val <- "ifelse(a>5|(a<10)|(a<15),0.1,0)"
51 | expect_equal(actual_val, expected_val)
52 | })
53 |
54 | context("Translate NOT")
55 |
56 | test_that("translate_NOT() returns for the correct translation for expressions from Stella", {
57 | test_equation <- "NOT(time=3)"
58 | actual_val <- translate_NOT(test_equation, "isee")
59 | expected_val <- '!(time=3)'
60 | expect_equal(actual_val, expected_val)
61 | })
62 |
63 | test_that("translate_NOT() returns for the correct translation for compounded expressions from Stella", {
64 | test_equation <- "ifelse(NOT(time=3),0,1)"
65 | actual_val <- translate_NOT(test_equation, "isee")
66 | expected_val <- 'ifelse(!(time=3),0,1)'
67 | expect_equal(actual_val, expected_val)
68 | })
69 |
70 | test_that("translate_NOT() returns for the correct translation for expressions from Vensim", {
71 | test_equation <- "ifelse(:NOT:Time=3,0,1)"
72 | actual_val <- translate_NOT(test_equation, "Vensim")
73 | expected_val <- 'ifelse(!Time=3,0,1)'
74 | expect_equal(actual_val, expected_val)
75 | })
76 |
77 |
--------------------------------------------------------------------------------
/R/priors.R:
--------------------------------------------------------------------------------
1 | #' Specify priors for the estimated parameters.
2 | #'
3 | #'\code{sd_prior} returns a list characterising the features of the prior.
4 | #'
5 | #' @param par_name A string indicating the name of the estimated parameter.
6 | #' @param dist A string indicating the name of the prior distribution. This name
7 | #' should be consistent with Stan language. For instance, "normal" indicates
8 | #' the normal distribution in Stan language.
9 | #' @param dist_pars A numeric vector. For instance, if \code{dist} = "normal",
10 | #' then \code{dist_pars} will be a vector of size 2 corresponding to
11 | #' the \emph{location} (mean) and \emph{scale} (standard deviation).
12 | #' @param type A string. It can be either 'constant' or 'init'. It is 'constant'
13 | #' by default. 'init' refers to parameters that have only affect the model at
14 | #' time 0.
15 | #' @param min An optional numeric or a string value indicating the estimated
16 | #' parameter's lower bound. This value overrides the inferred bound from the
17 | #' prior distribution. For instance, specifying a beta distribution for the
18 | #' estimated parameter inherently sets the lower bound to 0. Providing a
19 | #' value to \code{min} will override this default with the supplied value. If
20 | #' the supplied value is a string, it should be the name of another estimated
21 | #' parameter.
22 | #' @param max An optional numeric value or a string indicating the estimated
23 | #' parameter's upper bound. This value overrides the inferred bound from the
24 | #' prior distribution. For instance, specifying a beta distribution for the
25 | #' estimated parameter inherently sets the upper bound to \code{1}. Providing
26 | #' a value to \code{max} will override this default with the supplied value.
27 | #' If the supplied value is a string, it should be the name of another
28 | #' estimated parameter.
29 | #' @return A list
30 | #' @export
31 | #'
32 | #' @examples
33 | #' sd_prior("par_beta", "lognormal", c(0, 1))
34 | #' sd_prior("par_rho", "normal", c(0, 1), min = 0)
35 | sd_prior <- function(par_name, dist, dist_pars, type = "constant", min = NULL,
36 | max = NULL) {
37 |
38 | supported_dists <- c("beta", "exponential", "lognormal", "normal")
39 |
40 | if(!dist %in% supported_dists) {
41 |
42 | msg <- paste0("sd_prior() does not support the '", dist, "' distribution.")
43 | stop(msg, call. = FALSE)
44 | }
45 |
46 | dist_obj <- list(par_name = par_name,
47 | dist = dist,
48 | type = type)
49 |
50 | if(dist == "beta") {
51 |
52 | dist_obj$alpha <- dist_pars[[1]]
53 | dist_obj$beta <- dist_pars[[2]]
54 | dist_obj$min <- 0
55 | dist_obj$max <- 1
56 | }
57 |
58 | if(dist == "exponential") {
59 |
60 | dist_obj$beta <- dist_pars[[1]]
61 | dist_obj$min <- 0
62 | }
63 |
64 | if(dist == "lognormal") {
65 |
66 | dist_obj$mu <- dist_pars[[1]]
67 | dist_obj$sigma <- dist_pars[[2]]
68 | dist_obj$min <- 0
69 | }
70 |
71 | if(dist == "normal") {
72 |
73 | dist_obj$mu <- dist_pars[[1]]
74 | dist_obj$sigma <- dist_pars[[2]]
75 | }
76 |
77 | if(!is.null(min)) dist_obj$min <- min
78 | if(!is.null(max)) dist_obj$max <- max
79 |
80 | dist_obj
81 | }
82 |
--------------------------------------------------------------------------------
/man/sd_Bayes.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/sd_bayes.R
3 | \name{sd_Bayes}
4 | \alias{sd_Bayes}
5 | \title{Create Stan file for Bayesian inference}
6 | \usage{
7 | sd_Bayes(
8 | filepath,
9 | meas_mdl,
10 | estimated_params,
11 | data_params = NULL,
12 | data_inits = NULL,
13 | const_list = NULL,
14 | forecast = FALSE
15 | )
16 | }
17 | \arguments{
18 | \item{filepath}{A string that indicates a path to a file with extension .stmx
19 | or .xmile. Vensim files (.mdl) are not xmile files. They must be exported
20 | from Vensim with extension .xmile}
21 |
22 | \item{meas_mdl}{A list of strings. Each string corresponds to a sampling
23 | statement written in Stan language.}
24 |
25 | \item{estimated_params}{A list of lists. Each sublist describes each
26 | parameter that will be estimated in the inference stage. To construct this
27 | description, the user can avail of the function `sd_prior`.}
28 |
29 | \item{data_params}{An optional string vector defining which model
30 | parameters will be configured through the Stan data block. That is, the
31 | user will provide fixed values for such parameters at every Stan run.}
32 |
33 | \item{data_inits}{An optional string vector defining which model
34 | parameters that \strong{only affect initial values} (of stocks) will be
35 | configured through the Stan data block. That is, the user will provide fixed
36 | values for such parameters at every Stan run.}
37 |
38 | \item{const_list}{A list in which each element's name is the name of the
39 | constant to override and the element's value correspond to the new value.}
40 |
41 | \item{forecast}{An optional boolean that indicates whether the Stan file
42 | supports a forecast. If \code{TRUE}, the \strong{data} block requires the
43 | user to supply an integer value for \code{n_fcst}. This variable corresponds
44 | to the number of periods that will be predicted.}
45 | }
46 | \value{
47 | A string
48 | }
49 | \description{
50 | Create Stan file for Bayesian inference
51 | }
52 | \section{Negative binomial measurement component}{
53 |
54 |
55 | While this package aims to avoid making decisions for users whenever
56 | possible, I have taken the liberty to automate the transformation of phi
57 | (the concentration parameter) when using the Negative Binomial distribution
58 | (\href{https://mc-stan.org/docs/functions-reference/nbalt.html}{alternative parameterisation})
59 | as a measurement component. \code{sd_Bayes()} automatically creates an
60 | inverse phi parameter for computational efficiency, which will be subject to
61 | inference (instead of phi). Additionally, I have provided a default prior for
62 | this inv_phi but users can override it as needed.
63 | }
64 |
65 | \section{Time}{
66 |
67 |
68 | Simulation of the ordinary differential equation (ODE) model starts at time 0.
69 | }
70 |
71 | \examples{
72 | filepath <- system.file("models/", "SEIR.stmx", package = "readsdr")
73 | mm1 <- "y ~ neg_binomial_2(net_flow(C), phi)"
74 | meas_mdl <- list(mm1)
75 | estimated_params <- list(
76 | sd_prior("par_beta", "lognormal", c(0, 1)),
77 | sd_prior("par_rho", "beta", c(2, 2)),
78 | sd_prior("I0", "lognormal", c(0, 1), "init"))
79 | sd_Bayes(filepath, meas_mdl, estimated_params)
80 | }
81 |
--------------------------------------------------------------------------------
/R/stan_postprocessing.R:
--------------------------------------------------------------------------------
1 |
2 | #' Extract the values over time of a variable from a Stan fit
3 | #'
4 | #' @param var_name A string that indicates the variable's name for which the
5 | #' function will construct the timeseries.
6 | #' @param posterior_df A Stan fit object converted into a data frame
7 | #'
8 | #' @return A data frame
9 | #' @export
10 | #'
11 | #' @examples
12 | #' posterior_df <- data.frame(`var[1]` = rep(0, 2), `var[2]` = rep(1, 2),
13 | #' check.names = FALSE)
14 | #' extract_timeseries_var("var", posterior_df)
15 | extract_timeseries_var <- function(var_name, posterior_df) {
16 | posterior_cols <- colnames(posterior_df)
17 | pattern <- stringr::str_glue("{var_name}\\[.+\\]")
18 | pos_search <- grep(pattern, posterior_cols)
19 | search_cols <- posterior_cols[pos_search]
20 | search_df <- posterior_df[, search_cols]
21 |
22 | var_ts <- purrr::imap_dfr(search_df, function(col, label) {
23 | pattern <- "\\[(\\d+)\\]"
24 | match_output <- stringr::str_match(label, pattern)
25 | time_var <- as.numeric(match_output[[2]])
26 |
27 | data.frame(stringsAsFactors = FALSE,
28 | iter = seq_along(col),
29 | time = time_var,
30 | value = col)
31 | })
32 |
33 | var_ts$variable <- var_name
34 | var_ts <- var_ts[ , c(1:2,4, 3)]
35 |
36 | var_ts
37 | }
38 |
39 |
40 | #' Extract the values over time of a stock from a Stan fit
41 | #'
42 | #' @param stock_name A string that indicates the stock's name for which the
43 | #' function will construct the timeseries.
44 | #' @param all_stocks A vector of strings that contains the names of all the
45 | #' stocks in the model. This vector must have the same order as the differential
46 | #' equations in the Stan code.
47 | #' @param ODE_output A string that indicates the name of the variable where
48 | #' model's output in stored in Stan.
49 | #'
50 | #' @inheritParams extract_timeseries_var
51 | #'
52 | #' @return A data frame
53 | #' @export
54 | #'
55 | #' @examples
56 | #' posterior_df <- data.frame(`yhat[1,2]` = rep(0, 2), `yhat[2,2]` = rep(1, 2),
57 | #' check.names = FALSE)
58 | #' stocks <- c("S1", "S2")
59 | #' extract_timeseries_stock("S2", posterior_df, stocks, "yhat")
60 | extract_timeseries_stock <- function(stock_name, posterior_df, all_stocks,
61 | ODE_output) {
62 |
63 | posterior_cols <- colnames(posterior_df)
64 | pos_stock <- which(stock_name == all_stocks)
65 | pattern <- stringr::str_glue("{ODE_output}\\[\\d+,{pos_stock}\\]")
66 | pos_search <- grep(pattern, posterior_cols)
67 | search_cols <- posterior_cols[pos_search]
68 | search_df <- posterior_df[, search_cols]
69 |
70 | stock_ts <- purrr::imap_dfr(search_df, function(col, label) {
71 |
72 | pattern <- stringr::str_glue("{ODE_output}\\[(\\d+),\\d+\\]")
73 | match_output <- stringr::str_match(label, pattern)
74 | time_var <- as.numeric(match_output[[2]])
75 |
76 | data.frame(stringsAsFactors = FALSE,
77 | iter = seq_along(col),
78 | time = time_var,
79 | value = col)
80 | })
81 |
82 | stock_ts$stock <- stock_name
83 | stock_ts <- stock_ts[, c(1:2, 4, 3)]
84 | stock_ts
85 | }
86 |
--------------------------------------------------------------------------------
/tests/testthat/SEjIkR.xmile:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Vensim
5 | Ventana Systems, Inc.
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | 1
17 | 100
18 | 0.0078125
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | 0
27 |
28 |
29 | C in
30 |
31 |
32 |
33 |
34 |
35 |
36 | 0
37 |
38 |
39 | S to E
40 |
41 |
42 | E to I
43 |
44 |
45 |
46 |
47 |
48 |
49 | E_to_I=
50 | DELAY_N(S_to_E, 1/par_sigma, 0, j)
51 |
52 |
53 |
54 |
55 |
56 |
57 | I0
58 |
59 |
60 | E to I
61 |
62 |
63 | I to R
64 |
65 |
66 |
67 |
68 |
69 |
70 | I_to_R=
71 | DELAY_N(E_to_I, 1/par_gamma, par_gamma * I0, k)
72 |
73 |
74 |
75 |
76 |
77 |
78 | 0
79 |
80 |
81 | I to R
82 |
83 |
84 |
85 |
86 |
87 |
88 | N - I0
89 |
90 |
91 | S to E
92 |
93 |
94 |
95 |
96 |
97 | par_rho * E_to_I
98 |
99 |
100 |
101 |
102 | par_beta * S * I / N
103 |
104 |
105 |
106 |
107 | 1
108 |
109 |
110 |
111 |
112 | 2
113 |
114 |
115 |
116 |
117 | 2
118 |
119 |
120 |
121 |
122 | 10000
123 |
124 |
125 |
126 |
127 | 1
128 |
129 |
130 |
131 |
132 | 0.5
133 |
134 |
135 |
136 |
137 | 0.75
138 |
139 |
140 |
141 |
142 | 0.5
143 |
144 |
145 |
146 |
147 |
--------------------------------------------------------------------------------
/man/sd_loglik_fun.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/loglik_fun.R
3 | \name{sd_loglik_fun}
4 | \alias{sd_loglik_fun}
5 | \title{Generate a log-likelihood function for an SD model}
6 | \usage{
7 | sd_loglik_fun(
8 | filepath,
9 | unknown_pars,
10 | meas_data_mdl,
11 | neg_log = FALSE,
12 | supplied_pars = NULL,
13 | start_time = NULL,
14 | stop_time = NULL,
15 | timestep = NULL,
16 | integ_method = "euler",
17 | const_list = NULL
18 | )
19 | }
20 | \arguments{
21 | \item{filepath}{A string that indicates a path to a file with extension .stmx
22 | or .xmile. Vensim files (.mdl) are not xmile files. They must be exported
23 | from Vensim with extension .xmile}
24 |
25 | \item{unknown_pars}{A list of lists. Each second-level list contains at least
26 | the element name \code{name}, which corresponds to the parameter's name
27 | subject to estimation. In addition to the element \code{name}, users can
28 | incorporate in the sub-list the elements \code{min} and \code{max}. The
29 | value of \code{min} can only be \code{0}, whereas the value of \code{max}
30 | can only be \code{1}.}
31 |
32 | \item{meas_data_mdl}{A list of lists. Each second-level list corresponds to
33 | a sampling statement along with its measurements. Here is an example: \cr
34 | \code{list(formula = "y ~ neg_binomial_2(net_flow(C), phi)",
35 | measurements = 1:10))}}
36 |
37 | \item{neg_log}{A boolean that indicates whether the log-likelihood function
38 | returns a positive or negative value. If \code{TRUE}, the function
39 | returns a positive value (for minimisation optimisers). If
40 | \code{FALSE}, the function returns the original log-likelihood.}
41 |
42 | \item{supplied_pars}{A string vector indicating the name of parameters whose
43 | values will be supplied to the function. These values will not be subject to
44 | optimisation.}
45 |
46 | \item{start_time}{A number indicating the time at which the simulation begins.}
47 |
48 | \item{stop_time}{A number indicating the time at which the simulation ends.}
49 |
50 | \item{timestep}{A number indicating the time interval for the simulation.
51 | Also known as \code{dt}.}
52 |
53 | \item{integ_method}{A string indicating the integration method. It can be
54 | either "euler" or "rk4"}
55 |
56 | \item{const_list}{A list in which each element's name is the name of the
57 | constant to override and the element's value correspond to the new value.}
58 | }
59 | \value{
60 | A list of three elements. The first element, \code{fun}, corresponds
61 | to the log likelihood function. The second element, \code{par_names},
62 | indicates the order in which the unknowns are returned. The third element,
63 | \code{sim_params}, corresponds to the simulation parameters (start time,
64 | stop time, and the integration step or dt) employed by the solver function.
65 | }
66 | \description{
67 | Generate a log-likelihood function for an SD model
68 | }
69 | \examples{
70 | filepath <- system.file("models/", "SEIR.stmx", package = "readsdr")
71 | unknown_pars <- list(list(par_name = "par_beta", min = 0))
72 | meas_data_mdl <- list(list(formula = "y ~ neg_binomial_2(net_flow(C), phi)",
73 | measurements = 1:10))
74 | fun_obj <- sd_loglik_fun(filepath, unknown_pars, meas_data_mdl, neg_log = FALSE,
75 | start_time = 0, stop_time = 10, timestep = 1/32)
76 | }
77 |
--------------------------------------------------------------------------------
/R/generate_igraph_inputs.R:
--------------------------------------------------------------------------------
1 | get_igraph_inputs <- function(model_structure) {
2 |
3 | levels <- model_structure$levels
4 | variables <- model_structure$variables
5 | constants <- model_structure$constants
6 |
7 | nodes_df <- generate_nodes_df(levels, variables, constants)
8 | edges_df <- generate_edges_df(levels, variables, constants)
9 |
10 | list(
11 | nodes = nodes_df,
12 | edges = edges_df
13 | )
14 | }
15 |
16 | generate_nodes_df <- function(stocks, variables, constants) {
17 | const_names <- sapply(constants, function(const) const$name)
18 |
19 | stocks_df <- purrr::map_df(stocks, function(stock) {
20 |
21 | data.frame(name = stock$name,
22 | type = "stock",
23 | equation = stock$equation,
24 | stringsAsFactors = F)
25 | })
26 |
27 | variables_df <- purrr::map_df(variables, function(variable) {
28 | equation <- variable$equation
29 | extracted_vars <- extract_variables(variable$name, equation)
30 |
31 | if("time" %in% extracted_vars) stop("A variable depends on time",
32 | call. = FALSE)
33 |
34 | if("rnorm" %in% extracted_vars) stop("Translation of rnorm is not supported for graphs",
35 | call. = FALSE)
36 |
37 | detected_consts <- extracted_vars[extracted_vars %in% const_names]
38 | n_det_consts <- length(detected_consts)
39 |
40 | if(n_det_consts > 0) {
41 |
42 | for(det_const in detected_consts){
43 |
44 | regex_pattern <- stringr::regex(paste0("\\b", det_const,"\\b"))
45 | pos_const <- which(det_const == const_names)
46 | const_value <- constants[[pos_const]]$value
47 |
48 | const_value <- ifelse(is.numeric(const_value),
49 | as.character(round(const_value, 10)),
50 | const_value)
51 |
52 | equation <- stringr::str_replace_all(
53 | equation, regex_pattern, const_value)
54 | }
55 | }
56 |
57 | data.frame(name = variable$name,
58 | type = "variable",
59 | equation = equation,
60 | stringsAsFactors = F)
61 | })
62 |
63 | dplyr::bind_rows(stocks_df, variables_df)
64 | }
65 |
66 | generate_edges_df <- function(stocks, variables, constants) {
67 |
68 | stocks_edges <- purrr::map_df(stocks, construct_stock_edge)
69 |
70 | const_names <- sapply(constants, function(constant) constant$name)
71 |
72 | variables_edges <- purrr::map_df(variables, const_names = const_names,
73 | construct_var_edge)
74 |
75 | dplyr::bind_rows(stocks_edges, variables_edges)
76 | }
77 |
78 | construct_var_edge <- function(variable, const_names) {
79 | rhs <- extract_variables(variable$name, variable$equation)
80 | rhs <- rhs[!rhs %in% const_names ] %>% unique()
81 |
82 | if(length(rhs) == 0L) return(NULL)
83 |
84 | data.frame(from = rhs, to = rep(variable$name, length(rhs)),
85 | type = "info_link", stringsAsFactors = F)
86 | }
87 |
88 | construct_stock_edge <- function(stock_obj) {
89 | rhs <- extract_variables(stock_obj$name, stock_obj$equation)
90 |
91 | if(length(rhs) == 0L) return(NULL)
92 |
93 | data.frame(from = rhs,
94 | to = rep(stock_obj$name, length(rhs)),
95 | type = "flow",
96 | stringsAsFactors = F)
97 | }
98 |
--------------------------------------------------------------------------------
/R/stan_model.R:
--------------------------------------------------------------------------------
1 | stan_model <- function(estimated_params, meas_mdl, lvl_names) {
2 |
3 | prior_lines <- sapply(estimated_params, construct_prior_line) |>
4 | paste(collapse = "\n")
5 |
6 | likelihood_lines <- get_likelihood_lines(meas_mdl, lvl_names)
7 |
8 | block_body <- paste(prior_lines, likelihood_lines, sep = "\n")
9 |
10 | paste("model {",
11 | block_body,
12 | "}", sep = "\n")
13 | }
14 |
15 | construct_prior_line <- function(prior_obj) {
16 |
17 | dist_args <- get_dist_args(prior_obj$dist)
18 |
19 | dist_pars <- prior_obj[dist_args] |>
20 | as.numeric() |>
21 | paste(collapse = ", ")
22 |
23 | stringr::str_glue(" {prior_obj$par_name} ~ {prior_obj$dist}({dist_pars});")
24 |
25 | }
26 |
27 | get_likelihood_lines <- function(meas_mdl, lvl_names) {
28 |
29 | delta_counter <- 1
30 | n_meas <- length(meas_mdl)
31 |
32 | meas_lines <- vector(mode = "character", length = n_meas)
33 |
34 | for(i in seq_len(n_meas)) {
35 |
36 | meas_obj <- meas_mdl[[i]]
37 | ll_obj <- construct_likelihood_line(meas_obj, delta_counter, lvl_names)
38 |
39 | if(length(ll_obj$line) == 0) {
40 |
41 | msg <- stringr::str_glue("Failed to translate '{meas_obj}' in the model block")
42 | stop(msg, call. = FALSE)
43 | }
44 |
45 | meas_lines[[i]] <- ll_obj$line
46 | delta_counter <- ll_obj$delta_counter
47 | }
48 |
49 | paste(meas_lines, collapse = "\n")
50 |
51 | }
52 |
53 | construct_likelihood_line <- function(meas_obj, delta_counter, lvl_names) {
54 |
55 | decomposed_meas <- decompose_meas(meas_obj)
56 | lhs <- decomposed_meas$lhs
57 | dist_obj <- get_dist_obj(decomposed_meas$rhs)
58 | translation_obj <- translate_lik_rhs(dist_obj, delta_counter, lvl_names)
59 | new_rhs <- translation_obj$rhs
60 |
61 | list(line = as.character(stringr::str_glue(" {lhs} ~ {new_rhs};")),
62 | delta_counter = translation_obj$delta_counter)
63 | }
64 |
65 | translate_lik_rhs <- function(dist_obj, delta_counter, lvl_names) {
66 |
67 | return_obj <- list(rhs = NULL,
68 | delta_counter = NULL)
69 |
70 | stock_txt <- dist_obj[[2]]
71 |
72 | translation_obj <- translate_stock_text(stock_txt, delta_counter, lvl_names)
73 |
74 | return_obj$delta_counter <- translation_obj$delta_counter
75 | stock_txt <- translation_obj$stock_txt
76 |
77 | if(dist_obj$dist_name == "normal") {
78 |
79 | new_mu <- stock_txt
80 | return_obj$rhs <- stringr::str_glue("normal({new_mu}, {dist_obj$sigma})")
81 | return(return_obj)
82 | }
83 |
84 | if(dist_obj$dist_name == "neg_binomial_2") {
85 |
86 | new_mu <- stock_txt
87 | return_obj$rhs <- stringr::str_glue("neg_binomial_2({new_mu}, {dist_obj$phi})")
88 | return(return_obj)
89 | }
90 |
91 | if(dist_obj$dist_name == "poisson") {
92 |
93 | new_lambda <- stock_txt
94 | return_obj$rhs <- stringr::str_glue("poisson({new_lambda})")
95 | return(return_obj)
96 | }
97 |
98 | if(dist_obj$dist_name == "lognormal") {
99 |
100 | new_mu <- stock_txt
101 | return_obj$rhs <- stringr::str_glue("lognormal({new_mu}, {dist_obj$sigma})")
102 | return(return_obj)
103 | }
104 |
105 | msg <- paste0("translate_lik_rhs() does not support the ",
106 | dist_obj$dist_name, " distribution.")
107 |
108 | stop(msg, call. = FALSE)
109 | }
110 |
--------------------------------------------------------------------------------
/R/posterior_fun.R:
--------------------------------------------------------------------------------
1 | #' Posterior function
2 | #'
3 | #' @inheritParams read_xmile
4 | #' @inheritParams sd_Bayes
5 | #' @inheritParams sd_loglik_fun
6 | #'
7 | #' @return A function
8 | #' @export
9 | #'
10 | #' @examples
11 | #' filepath <- system.file("models/", "SEIR.stmx", package = "readsdr")
12 | #' meas_data_mdl <- list(list(formula = "y ~ neg_binomial_2(net_flow(C), phi)",
13 | #' measurements = 1:10))
14 | #' estimated_params <- list(
15 | #' sd_prior("par_beta", "lognormal", c(0, 1)),
16 | #' sd_prior("par_rho", "beta", c(2, 2)),
17 | #' sd_prior("I0", "lognormal", c(0, 1), "init"))
18 | #' fun <- sd_posterior_fun(filepath, meas_data_mdl, estimated_params)
19 | sd_posterior_fun <- function(filepath, meas_data_mdl, estimated_params,
20 | start_time = NULL, stop_time = NULL,
21 | timestep = NULL, integ_method = "euler",
22 | const_list = NULL) {
23 |
24 |
25 | ll_fun_obj <- sd_loglik_fun(filepath = filepath,
26 | unknown_pars = estimated_params,
27 | meas_data_mdl = meas_data_mdl,
28 | start_time = start_time,
29 | stop_time = stop_time,
30 | timestep = timestep,
31 | integ_method = integ_method,
32 | const_list = const_list)
33 |
34 | log_lik_fun <- ll_fun_obj$fun
35 |
36 | log_prior_fun <- log_prior_fun_generator(estimated_params, meas_data_mdl)
37 |
38 | fun <- function(pars) log_lik_fun(pars) + log_prior_fun(pars)
39 |
40 | list(fun = fun, par_list = ll_fun_obj$par_list)
41 | }
42 |
43 | log_prior_fun_generator <- function(estimated_params, meas_data_mdl) {
44 |
45 | meas_pars <- extract_meas_pars(meas_data_mdl)
46 |
47 | if(length(meas_pars) > 0) estimated_params <- c(estimated_params, meas_pars)
48 |
49 | par_list <- get_par_list(estimated_params)
50 |
51 | idx <- lapply(seq_len(length(estimated_params)), function(i) list(index = i))
52 |
53 | par_specs <- Map(function(x, y, z) {
54 | x[['par_trans']] <- NULL
55 | c(x, y[2], z)
56 | }, estimated_params, par_list, idx)
57 |
58 | expr_list <- lapply(par_specs, build_prior_expr)
59 |
60 | body_text <- paste(expr_list, collapse = " + \n")
61 |
62 | body_func <- paste("{", body_text, "}", sep = "\n")
63 |
64 | rlang::new_function(args = rlang::exprs(pars = ),
65 | body = rlang::parse_expr(body_func))
66 | }
67 |
68 | build_prior_expr <- function(par_obj) {
69 |
70 | dist_name <- par_obj$dist
71 |
72 | arg_names_Stan <- get_dist_args(dist_name)
73 | arg_list <- par_obj[arg_names_Stan]
74 |
75 | arg_names_R <- get_dist_args(dist_name, "R")
76 | dist_args_txt <- paste(arg_names_R, arg_list, sep = " = ")
77 |
78 | fun_name <- Stan_to_R(dist_name, "d")
79 |
80 | par_expr <- constrain_pars(par_obj)
81 | log_arg <- "log = TRUE"
82 |
83 | args_txt <- paste(c(par_expr, dist_args_txt, log_arg), collapse = ", ")
84 |
85 |
86 | stringr::str_glue(" {fun_name}({args_txt})")
87 |
88 | }
89 |
90 | constrain_pars <- function(par_obj) {
91 |
92 | trans_vector <- par_obj$par_trans
93 | idx <- par_obj$index
94 |
95 | par_expr <- stringr::str_glue("pars[[{idx}]]") # unconstrained par
96 |
97 | for(cons in trans_vector) par_expr <- stringr::str_glue("{cons}({par_expr})")
98 |
99 | par_expr
100 | }
101 |
--------------------------------------------------------------------------------
/R/interpreters.R:
--------------------------------------------------------------------------------
1 | #' Interpret estimates
2 | #'
3 | #' @param estimates A list or data frame
4 | #' @param par_list A list
5 | #'
6 | #' @return A data frame
7 | #' @export
8 | #'
9 | #' @examples
10 | #' estimates <- c(par_beta = 0,
11 | #' par_rho = 0.8472979,
12 | #' I0 = 0,
13 | #' inv_phi = -2.302585)
14 | #'
15 | #' par_list <- list(list(par_name = "par_beta",
16 | #' par_trans = "exp"),
17 | #' list(par_name = "par_rho",
18 | #' par_trans = "expit"),
19 | #' list(par_name = "I0",
20 | #' par_trans = "exp"),
21 | #' list(par_name = "phi",
22 | #' par_trans = c("exp", "inv")))
23 | #' sd_interpret_estimates(estimates, par_list)
24 | sd_interpret_estimates <- function(estimates, par_list) {
25 |
26 | if(is.numeric(estimates)) estimate_list <- list(estimates)
27 |
28 | if(is.data.frame(estimates)) estimate_list <- df2list(estimates)
29 |
30 | purrr::map_dfr(estimate_list, decode_estimates, par_list)
31 | }
32 |
33 | decode_estimates <- function(estimate_list, par_list) {
34 |
35 | lapply(seq_along(par_list), function(i) {
36 |
37 | par_obj <- par_list[[i]]
38 |
39 | if(length(par_obj$par_trans) == 1L) val <- do.call(par_obj$par_trans,
40 | list(estimate_list[[i]]))
41 |
42 | if(length(par_obj$par_trans) > 1L) {
43 |
44 | val <- estimate_list[[i]]
45 |
46 | for(j in seq_along(par_obj$par_trans)) {
47 |
48 | val <- do.call(par_obj$par_trans[[j]], list(val))
49 | }
50 | }
51 |
52 | val <- list(val)
53 | names(val) <- par_obj$par_name
54 | val
55 | }) -> output_list
56 |
57 | data.frame(output_list)
58 | }
59 |
60 | #' Calculate confidence intervals
61 | #'
62 | #' @param hsn Hessian matrix
63 | #' @param conf_level A numeric input indicating the confidence level
64 | #'
65 | #' @inheritParams sd_interpret_estimates
66 | #'
67 | #' @return A data frame.
68 | #' @export
69 | #'
70 | #' @examples
71 | #' estimates <- c(-0.2630303, 1.5788579)
72 | #' par_list <- list(list(par_name = "par_inv_R0",
73 | #' par_trans = "expit"),
74 | #' list(par_name = "I0",
75 | #' par_trans = "exp"))
76 | #' hsn <- matrix(c(3513.10521, -493.5469626,
77 | #' -493.5469626, 88.4871290), ncol = 2)
78 | #' sd_conf_intervals(estimates, par_list, hsn)
79 | sd_conf_intervals <- function(estimates, par_list, hsn, conf_level = 0.95) {
80 |
81 | alpha <- 1 - conf_level
82 |
83 | cov_matrix <- solve(hsn)
84 |
85 | SE <- sqrt(diag(cov_matrix)) # Standard errors
86 |
87 | lower_bounds <- sd_interpret_estimates(estimates +
88 | stats::qnorm(alpha / 2) * SE,
89 | par_list)
90 |
91 | upper_bounds <- sd_interpret_estimates(estimates +
92 | stats::qnorm(1 - alpha / 2) * SE,
93 | par_list)
94 |
95 | df <- data.frame(lb = as.numeric(lower_bounds[1, ,]),
96 | ub = as.numeric(upper_bounds[1, ,]))
97 |
98 | min_max <- apply(df, 1, function(x) c(min(x), max(x)))
99 |
100 | result_df <- data.frame(parameter = colnames(lower_bounds),
101 | lb = min_max[1, ],
102 | ub = min_max[2, ])
103 |
104 | colnames(result_df) <- c("parameter",
105 | paste0(round(100 * alpha / 2,1), "%"),
106 | paste0(round(100 * (1 - alpha / 2),2), "%"))
107 |
108 | result_df
109 | }
110 |
111 |
--------------------------------------------------------------------------------
/R/arrays.R:
--------------------------------------------------------------------------------
1 | array_equations <- function(aux_obj, dims_obj, dim_names, vendor) {
2 |
3 | dims_dict <- dims_obj$dictionary
4 | glob_dims <- dims_obj$global_dims
5 |
6 | n_dims <- length(dim_names)
7 |
8 | dims_list <- lapply(dim_names, function(dim_name) glob_dims[[dim_name]])
9 | names(dims_list) <- dim_names
10 | elems <- combine_dims(dims_list)
11 |
12 | raw_equation <- aux_obj$equation
13 | aux_name <- aux_obj$name
14 |
15 | if(vendor == "Vensim") {
16 |
17 | vector_pattern <- create_array_pattern(dims_list)
18 | is_an_array <- stringr::str_detect(raw_equation, vector_pattern)
19 |
20 | if(is_an_array) {
21 |
22 | clean_equation <- raw_equation %>%
23 | stringr::str_replace_all(";",",")
24 |
25 | clean_equation <- substr(clean_equation,1, nchar(clean_equation) - 1)
26 | equations <- stringr::str_split(clean_equation, ",")[[1]]
27 | are_const <- !is.na(suppressWarnings(as.numeric(equations)))
28 | }
29 |
30 | if(!is_an_array) {
31 |
32 | devec_eqs <- devectorise_equation(raw_equation, dims_list)
33 |
34 | equations <- sapply(devec_eqs, sanitise_aux_equation, vendor,
35 | USE.NAMES = FALSE)
36 |
37 | are_const <- !is.na(suppressWarnings(as.numeric(equations)))
38 | }
39 | }
40 |
41 | if(vendor == "isee") {
42 |
43 | equations <- sanitise_aux_equation(raw_equation, vendor)
44 | are_const <- !is.na(suppressWarnings(as.numeric(equations)))
45 |
46 | if(!are_const) {
47 |
48 | eq_vars <- extract_variables(lhs = aux_name, equations)
49 |
50 | arrayed_vars <- names(dims_dict)
51 |
52 | for(var_in_eq in eq_vars) {
53 |
54 | if(var_in_eq %in% arrayed_vars) {
55 |
56 | var_dims <- dims_dict[[var_in_eq]]
57 | dims_idx <- paste(var_dims, collapse = ",")
58 |
59 | replacement <- stringr::str_glue("{var_in_eq}[{dims_idx}]")
60 | pattern <- stringr::str_glue("\\b{var_in_eq}\\b")
61 | unvectorised_eq <- stringr::str_replace_all(equations, pattern,
62 | replacement)
63 | devec_eqs <- devectorise_equation(unvectorised_eq, dims_list)
64 |
65 | equations <- sapply(devec_eqs, sanitise_aux_equation, vendor,
66 | USE.NAMES = FALSE)
67 |
68 | are_const <- !is.na(suppressWarnings(as.numeric(equations)))
69 |
70 | }
71 |
72 | }
73 | }
74 |
75 | }
76 |
77 | list(equations = equations,
78 | are_const = are_const,
79 | elems = elems)
80 | }
81 |
82 |
83 | devectorise_equation <- function(raw_equation, dims_list) {
84 |
85 | dim_names <- names(dims_list)
86 | pattern <- paste0("\\[", paste(dim_names, collapse = ","), "\\]")
87 |
88 | elems <- combine_dims(dims_list)
89 | replacement <- paste0("_", elems)
90 |
91 | stringr::str_replace_all(raw_equation, pattern, replacement)
92 |
93 | }
94 |
95 | combine_dims <- function(dims_list) {
96 |
97 | rev_dims_list <- rev(dims_list)
98 | rev_combs_df <- expand.grid(rev_dims_list, stringsAsFactors = FALSE)
99 | combs_df <- rev(rev_combs_df)
100 | do.call(paste, c(combs_df, sep = "_"))
101 |
102 | }
103 |
104 | create_array_pattern <- function(dims_list) {
105 |
106 | n_dims <- length(dims_list)
107 | dim1_length <- length(dims_list[[1]])
108 | rgx_elems <- rep(".+?", dim1_length)
109 | rgx_array <- paste(rgx_elems, collapse = ",")
110 |
111 | if(n_dims == 2) {
112 | rgx_row <- paste0(rgx_array, ";")
113 | rgx_matrix <- rep(rgx_row, length(dims_list[[2]]))
114 | rgx_array <- paste(rgx_matrix, collapse = "")
115 | }
116 |
117 | rgx_array
118 |
119 | }
120 |
--------------------------------------------------------------------------------
/R/if_else_builtins.R:
--------------------------------------------------------------------------------
1 |
2 | #' PULSE TRAIN
3 | #'
4 | #' @param time A numeric argument that indicates the current simulation time
5 | #' @param start_pulse A numeric argument that indicates the start of the pulse
6 | #' @param duration_pulse A numeric argument that indicates the width of the pulse
7 | #' @param repeat_pt A numeric argument that indicates the repetition pattern
8 | #' @param end_pulse A numeric argument that indicates the end of the sequence
9 | #'
10 | #' @return 1 during the pulse, 0 otherwise.
11 | #' @export
12 | #'
13 | #' @examples
14 | #' sd_pulse_train(5, 5, 3, 10, 20)
15 | sd_pulse_train <- function(time, start_pulse, duration_pulse,
16 | repeat_pt, end_pulse) {
17 |
18 | if(time < start_pulse | time > end_pulse) return (0)
19 |
20 | start_candidates <- seq(start_pulse, time, repeat_pt)
21 | pos <- findInterval(time, start_candidates)
22 | optim_start <- start_candidates[pos] # Avoids unnecessary previous intervals
23 |
24 | optim_end <- min(time, end_pulse) # Avoids unnecessary forward intervals
25 | pt_statement <- create_pt_statement(optim_start, duration_pulse,
26 | repeat_pt, optim_end)
27 |
28 | eval(parse(text = pt_statement))
29 | }
30 |
31 | #' Create Pulse Train statement
32 | #'
33 | #' @param start_pt Numeric
34 | #' @param duration_pt Numeric
35 | #' @param repeat_pt Numeric
36 | #' @param end_pt Numeric
37 | #'
38 | #' @return A string
39 | #'
40 | #' @noRd
41 | create_pt_statement <- function(start_pt, duration_pt, repeat_pt, end_pt) {
42 |
43 | if(duration_pt == 0L) {
44 |
45 | pulse_points <- stringr::str_glue("seq({start_pt}, {end_pt}, {repeat_pt})")
46 | pt_statement <- stringr::str_glue("ifelse(time %in% {pulse_points}, 1, 0)")
47 | return(pt_statement)
48 | }
49 |
50 | intervals_start <- seq(from = start_pt, to = end_pt, by = repeat_pt)
51 | intervals_should_end <- intervals_start + duration_pt
52 | intervals_actual_end <- ifelse(intervals_should_end > end_pt, end_pt,
53 | intervals_should_end)
54 |
55 | comparison_end_intv <- mapply(c, intervals_should_end, intervals_actual_end,
56 | SIMPLIFY = FALSE, USE.NAMES = FALSE)
57 |
58 | conditions <- purrr::map2_chr(intervals_start, comparison_end_intv, ~ {
59 | operator <- ifelse(.y[[1]] == .y[[2]], "<", "<=")
60 |
61 | stringr::str_glue("(time >= {.x} & time {operator} {.y[[2]]})")
62 | })
63 |
64 | pt_condition <- paste(conditions, collapse = " | ")
65 | stringr::str_glue("ifelse({pt_condition}, 1, 0)")
66 | }
67 |
68 |
69 | #' Replicate the behaviour of the PULSE function from Stella
70 | #'
71 | #' This function must be placed inside the object that will be passed as the
72 | #' argument \code{func} to deSolve's \code{ode} function.
73 | #'
74 | #' @param time A number
75 | #' @param volume A number
76 | #' @param start_p A number
77 | #' @param interval A number
78 | #'
79 | #' @return A number
80 | #' @export
81 | #'
82 | #' @examples
83 | #' timestep <- function() 0.25 # replicates timestep() from deSolve
84 | #' sd_pulse_s(2, 1, 2, 0)
85 | #'
86 | sd_pulse_s <- function(time, volume, start_p, interval) {
87 | pulse_s_statement <- get_pulse_s_statement(volume, start_p, interval)
88 | eval(parse(text = pulse_s_statement))
89 | }
90 |
91 | #' Replicate the behaviour of the PULSE function from Vensim
92 | #'
93 | #' @param time A number
94 | #' @param startPulse A number
95 | #' @param duration A number
96 | #'
97 | #' @return A number
98 | #' @export
99 | #'
100 | #' @examples
101 | #' timestep <- function() 0.25 # replicates timestep() from deSolve
102 | #' sd_pulse_v(1, 1, 2)
103 | sd_pulse_v <- function(time, startPulse, duration) {
104 | pulse_v_statement <- get_pulse_v_statement(startPulse, duration)
105 | eval(parse(text = pulse_v_statement))
106 | }
107 |
--------------------------------------------------------------------------------
/tests/testthat/test-if_else_builtins.R:
--------------------------------------------------------------------------------
1 | context("sd_pulse_train")
2 |
3 | test_that("sd_pulse_train() returns 1 at the start of the pulse", {
4 | actual_val <- sd_pulse_train(5, 5, 3, 10, 20)
5 | expected_val <- 1
6 | expect_equal(actual_val, expected_val)
7 | })
8 |
9 | test_that("sd_pulse_train() returns 1 at the middle of the first pulse", {
10 | actual_val <- sd_pulse_train(6.5, 5, 3, 10, 20)
11 | expected_val <- 1
12 | expect_equal(actual_val, expected_val)
13 | })
14 |
15 | test_that("sd_pulse_train() returns 0 after the end of the first pulse", {
16 | actual_val <- sd_pulse_train(8, 5, 3, 10, 20)
17 | expected_val <- 0
18 | expect_equal(actual_val, expected_val)
19 | })
20 |
21 | test_that("sd_pulse_train() returns 0 after the sequence end", {
22 | actual_val <- sd_pulse_train(21, 5, 3, 10, 20)
23 | expected_val <- 0
24 | expect_equal(actual_val, expected_val)
25 | })
26 |
27 | test_that("sd_pulse_train() returns 1 if the interval is cut", {
28 | actual_val <- sd_pulse_train(17, 5, 3, 10, 17)
29 | expected_val <- 1
30 | expect_equal(actual_val, expected_val)
31 | })
32 |
33 | test_that("sd_pulse_train() returns 0 for non-integer increments", {
34 | actual_val <- sd_pulse_train(6.3, 5, 0, 0.8, 10)
35 | expected_val <- 0
36 | expect_equal(actual_val, expected_val)
37 | })
38 |
39 | test_that("sd_pulse_train() returns 1 for non-integer increments", {
40 | actual_val <- sd_pulse_train(5.8, 5, 0, 0.8, 10)
41 | expected_val <- 1
42 | expect_equal(actual_val, expected_val)
43 | })
44 |
45 | test_that("sd_pulse_train() works for intervals of size 0", {
46 | actual_val <- sd_pulse_train(6, 5, 0, 1, 10)
47 | expected_val <- 1
48 | expect_equal(actual_val, expected_val)
49 | })
50 |
51 | test_that("sd_pulse_train() deals with values after the end ", {
52 | actual_val <- sd_pulse_train(22, 2, 3, 10, 20)
53 | expected_val <- 0
54 | expect_equal(actual_val, expected_val)
55 | })
56 |
57 | context("create_pt_statement")
58 |
59 | test_that("create_pt_statement() returns the correct condition", {
60 | actual_val <- create_pt_statement(5, 3, 10, 20)
61 | expected_val <- "ifelse((time >= 5 & time < 8) | (time >= 15 & time < 18), 1, 0)"
62 | expect_equal(actual_val, expected_val)
63 | })
64 |
65 | test_that("create_pt_statement() returns the correct condition when the end of intervals is greater than the end time", {
66 | actual_val <- create_pt_statement(5, 3, 10, 17)
67 | expected_val <- "ifelse((time >= 5 & time < 8) | (time >= 15 & time <= 17), 1, 0)"
68 | expect_equal(actual_val, expected_val)
69 | })
70 |
71 | test_that("create_pt_statement() deals with intervals equal to 0", {
72 | actual_val <- create_pt_statement(5, 0, 1, 10)
73 | expected_val <- "ifelse(time %in% seq(5, 10, 1), 1, 0)"
74 | expect_equal(actual_val, expected_val)
75 | })
76 |
77 | # sd_pulse_s ===================================================================
78 | context("sd_pulse_s")
79 |
80 | test_that("sd_pulse_s() returns 1 in an interval equal to zero", {
81 | e <- new.env()
82 | e$timestep <- function() 0.25
83 | environment(sd_pulse_s) <- e
84 | actual_val <- sd_pulse_s(2, 1, 2, 0)
85 | expected_val <- 4
86 | expect_equal(actual_val, expected_val)
87 | })
88 |
89 | test_that("sd_pulse_s() returns 0 in an interval equal to zero", {
90 | timestep <- function() 0.25
91 | actual_val <- sd_pulse_s(1, 1, 2, 0)
92 | expected_val <- 0
93 | expect_equal(actual_val, expected_val)
94 | })
95 |
96 | # sd_pulse_v ===================================================================
97 | context("sd_pulse_v")
98 |
99 | test_that("sd_pulse_v() behaves like a pulse from Vensim", {
100 | expect_equal(sd_pulse_v(0, 1, 2), 0)
101 | expect_equal(sd_pulse_v(1, 1, 2), 1)
102 | expect_equal(sd_pulse_v(2, 1, 2), 1)
103 | expect_equal(sd_pulse_v(3, 1, 2), 0)
104 | expect_equal(sd_pulse_v(4, 1, 2), 0)
105 | })
106 |
--------------------------------------------------------------------------------
/tests/testthat/test_stan_files/SEIR_age_pois.stan:
--------------------------------------------------------------------------------
1 | // Code generated by the R package readsdr v0.3.0.9001
2 | // See more info at github https://github.com/jandraor/readsdr
3 | functions {
4 | vector X_model(real time, vector y, array[] real params) {
5 | vector[20] dydt;
6 | real E_to_I_A;
7 | real E_to_I_B;
8 | real E_to_I_C;
9 | real E_to_I_D;
10 | real I_to_R_A;
11 | real I_to_R_B;
12 | real I_to_R_C;
13 | real I_to_R_D;
14 | real total_N;
15 | real var_psi_A;
16 | real var_psi_B;
17 | real var_psi_C;
18 | real var_psi_D;
19 | real C_in_A;
20 | real C_in_B;
21 | real C_in_C;
22 | real C_in_D;
23 | real S_to_E_A;
24 | real S_to_E_B;
25 | real S_to_E_C;
26 | real S_to_E_D;
27 | E_to_I_A = 0.5*y[5];
28 | E_to_I_B = 0.5*y[6];
29 | E_to_I_C = 0.5*y[7];
30 | E_to_I_D = 0.5*y[8];
31 | I_to_R_A = 0.5*y[9];
32 | I_to_R_B = 0.5*y[10];
33 | I_to_R_C = 0.5*y[11];
34 | I_to_R_D = 0.5*y[12];
35 | total_N = 264132+614909+2020683+2640994;
36 | var_psi_A = params[1]*y[9]+1.5451053*y[10]+1.0079616*y[11]+0.4403311*y[12];
37 | var_psi_B = 1.5451053*y[9]+7.2500922*y[10]+1.0689326*y[11]+0.4042651*y[12];
38 | var_psi_C = 1.0079616*y[9]+1.0689326*y[10]+1.9051576*y[11]+0.8288393*y[12];
39 | var_psi_D = 0.4403311*y[9]+0.4042651*y[10]+0.8288393*y[11]+0.9137116*y[12];
40 | C_in_A = params[2]*E_to_I_A;
41 | C_in_B = params[2]*E_to_I_B;
42 | C_in_C = params[2]*E_to_I_C;
43 | C_in_D = params[2]*E_to_I_D;
44 | S_to_E_A = var_psi_A*y[1]/total_N;
45 | S_to_E_B = var_psi_B*y[2]/total_N;
46 | S_to_E_C = var_psi_C*y[3]/total_N;
47 | S_to_E_D = var_psi_D*y[4]/total_N;
48 | dydt[1] = -S_to_E_A;
49 | dydt[2] = -S_to_E_B;
50 | dydt[3] = -S_to_E_C;
51 | dydt[4] = -S_to_E_D;
52 | dydt[5] = S_to_E_A-E_to_I_A;
53 | dydt[6] = S_to_E_B-E_to_I_B;
54 | dydt[7] = S_to_E_C-E_to_I_C;
55 | dydt[8] = S_to_E_D-E_to_I_D;
56 | dydt[9] = E_to_I_A-I_to_R_A;
57 | dydt[10] = E_to_I_B-I_to_R_B;
58 | dydt[11] = E_to_I_C-I_to_R_C;
59 | dydt[12] = E_to_I_D-I_to_R_D;
60 | dydt[13] = I_to_R_A;
61 | dydt[14] = I_to_R_B;
62 | dydt[15] = I_to_R_C;
63 | dydt[16] = I_to_R_D;
64 | dydt[17] = C_in_A;
65 | dydt[18] = C_in_B;
66 | dydt[19] = C_in_C;
67 | dydt[20] = C_in_D;
68 | return dydt;
69 | }
70 | }
71 | data {
72 | int n_obs;
73 | array[n_obs] int y_A;
74 | array[n_obs] int y_B;
75 | array[n_obs] int y_C;
76 | array[n_obs] int y_D;
77 | array[n_obs] real ts;
78 | vector[20] x0;
79 | }
80 | parameters {
81 | real k_AA;
82 | real par_rho;
83 | }
84 | transformed parameters{
85 | array[n_obs] vector[20] x; // Output from the ODE solver
86 | array[2] real params;
87 | array[n_obs] real delta_x_1;
88 | array[n_obs] real delta_x_2;
89 | array[n_obs] real delta_x_3;
90 | array[n_obs] real delta_x_4;
91 | params[1] = k_AA;
92 | params[2] = par_rho;
93 | x = ode_rk45(X_model, x0, 0, ts, params);
94 | delta_x_1[1] = x[1, 17] - x0[17] + 1e-5;
95 | delta_x_2[1] = x[1, 18] - x0[18] + 1e-5;
96 | delta_x_3[1] = x[1, 19] - x0[19] + 1e-5;
97 | delta_x_4[1] = x[1, 20] - x0[20] + 1e-5;
98 | for (i in 1:n_obs-1) {
99 | delta_x_1[i + 1] = x[i + 1, 17] - x[i, 17] + 1e-5;
100 | delta_x_2[i + 1] = x[i + 1, 18] - x[i, 18] + 1e-5;
101 | delta_x_3[i + 1] = x[i + 1, 19] - x[i, 19] + 1e-5;
102 | delta_x_4[i + 1] = x[i + 1, 20] - x[i, 20] + 1e-5;
103 | }
104 | }
105 | model {
106 | k_AA ~ normal(0, 10);
107 | par_rho ~ beta(2, 2);
108 | y_A ~ poisson(delta_x_1);
109 | y_B ~ poisson(delta_x_2);
110 | y_C ~ poisson(delta_x_3);
111 | y_D ~ poisson(delta_x_4);
112 | }
113 | generated quantities {
114 | real log_lik;
115 | array[n_obs] int sim_y_A;
116 | array[n_obs] int sim_y_B;
117 | array[n_obs] int sim_y_C;
118 | array[n_obs] int sim_y_D;
119 | log_lik = poisson_lpmf(y_A | delta_x_1) +
120 | poisson_lpmf(y_B | delta_x_2) +
121 | poisson_lpmf(y_C | delta_x_3) +
122 | poisson_lpmf(y_D | delta_x_4);
123 | sim_y_A = poisson_rng(delta_x_1);
124 | sim_y_B = poisson_rng(delta_x_2);
125 | sim_y_C = poisson_rng(delta_x_3);
126 | sim_y_D = poisson_rng(delta_x_4);
127 | }
128 |
--------------------------------------------------------------------------------
/R/read_xmile.R:
--------------------------------------------------------------------------------
1 | #' Read an XMILE file into R
2 | #'
3 | #'\code{read_xmile} returns a list for constructing deSolve functions and graphs
4 | #'
5 | #' This function extracts the xml from the file specified via \code{filepath}
6 | #' to generate a list of objects. Such a list contains a summary of the model,
7 | #' the inputs for simulating through \link[deSolve]{deSolve}, and the inputs for
8 | #' creating a \link[igraph]{igraph} object.
9 | #' @param filepath A string that indicates a path to a file with extension .stmx
10 | #' or .xmile. Vensim files (.mdl) are not xmile files. They must be exported
11 | #' from Vensim with extension .xmile
12 | #' @param stock_list A list in which each element's name is the name of the
13 | #' stock to override and the element's value correspond to the new init value.
14 | #'
15 | #' @param const_list A list in which each element's name is the name of the
16 | #' constant to override and the element's value correspond to the new value.
17 | #'
18 | #' @param graph A boolean parameter that indicates whether \code{read_xmile}
19 | #' returns a graph for the model.
20 | #'
21 | #' @return This function returns a list with three elements. The first element,
22 | #' \emph{description}, is a list that contains the simulation parameters, and
23 | #' the names and equations (including graphical functions) for each stock or
24 | #' level, variable and constant. The second element, \emph{deSolve_components},
25 | #' is a list that contains initial values, constants and the function for
26 | #' simulating via deSolve. The third element (optional), \emph{igraph} contains
27 | #' the data.frames for creating a graph with igraph.
28 | #' @export
29 | #'
30 | #' @examples
31 | #' path <- system.file("models", "SIR.stmx", package = "readsdr")
32 | #' read_xmile(path)
33 | read_xmile <- function(filepath, stock_list = NULL, const_list = NULL,
34 | graph = FALSE) {
35 |
36 | model_structure <- extract_structure_from_XMILE(filepath,
37 | const_list = const_list)
38 |
39 | if(!is.null(stock_list)) {
40 | stocks_override <- names(stock_list)
41 | lvl_names <- sapply(model_structure$levels,
42 | function(lvl_obj) lvl_obj$name)
43 |
44 | for(i in seq_len(length(stock_list))) {
45 | stk <- stocks_override[[i]]
46 | pos_stk <- which(stk == lvl_names)
47 | model_structure$levels[[pos_stk]]$initValue <- stock_list[[stk]]
48 | }
49 | }
50 |
51 | deSolve_components <- get_deSolve_elems(model_structure)
52 |
53 | output <- list(
54 | description = model_structure,
55 | deSolve_components = deSolve_components)
56 |
57 | if(graph == TRUE) {
58 |
59 | igraph_inputs <- tryCatch(
60 | error = function(cnd) {
61 | warning("This model cannot be converted into a graph (network)",
62 | call. = FALSE)
63 | NULL
64 | },
65 | get_igraph_inputs(model_structure)
66 | )
67 |
68 | output$graph_dfs <- igraph_inputs
69 | }
70 |
71 | output
72 |
73 | }
74 |
75 | #' Parse XMILE to deSolve components
76 | #'
77 | #' \code{xmile_to_deSolve} returns a list that serves as an input for
78 | #' deSolve's ODE function.
79 | #'
80 | #' This function extracts the xml from the file specified via \code{filepath}
81 | #' to generate a list with the necessary elements to simulate with
82 | #' \link[deSolve]{deSolve}.
83 | #'
84 | #' @inheritParams read_xmile
85 | #'
86 | #' @return This function returns a list with at least four elements.
87 | #' \emph{stocks}, a numeric vector that contains initial values. \emph{consts},
88 | #' a numeric vector with the model's constants. \emph{func}, the function that
89 | #' wraps the model's equations. \emph{sim_params}, a list with control
90 | #' parameters. If the model includes a table or graphical function, this
91 | #' function returns the element \emph{graph_funs}, a list with these functions.
92 | #'
93 | #' @export
94 | #'
95 | #' @examples
96 | #' path <- system.file("models", "SIR.stmx", package = "readsdr")
97 | #' xmile_to_deSolve(path)
98 | xmile_to_deSolve <- function(filepath) {
99 | model_structure <- extract_structure_from_XMILE(filepath)
100 | deSolve_components <- get_deSolve_elems(model_structure)
101 | }
102 |
--------------------------------------------------------------------------------
/tests/testthat/test_stan_files/SEIR_age_nbin.stan:
--------------------------------------------------------------------------------
1 | // Code generated by the R package readsdr v0.3.0.9001
2 | // See more info at github https://github.com/jandraor/readsdr
3 | functions {
4 | vector X_model(real time, vector y, array[] real params) {
5 | vector[20] dydt;
6 | real E_to_I_A;
7 | real E_to_I_B;
8 | real E_to_I_C;
9 | real E_to_I_D;
10 | real I_to_R_A;
11 | real I_to_R_B;
12 | real I_to_R_C;
13 | real I_to_R_D;
14 | real total_N;
15 | real var_psi_A;
16 | real var_psi_B;
17 | real var_psi_C;
18 | real var_psi_D;
19 | real C_in_A;
20 | real C_in_B;
21 | real C_in_C;
22 | real C_in_D;
23 | real S_to_E_A;
24 | real S_to_E_B;
25 | real S_to_E_C;
26 | real S_to_E_D;
27 | E_to_I_A = 0.5*y[5];
28 | E_to_I_B = 0.5*y[6];
29 | E_to_I_C = 0.5*y[7];
30 | E_to_I_D = 0.5*y[8];
31 | I_to_R_A = 0.5*y[9];
32 | I_to_R_B = 0.5*y[10];
33 | I_to_R_C = 0.5*y[11];
34 | I_to_R_D = 0.5*y[12];
35 | total_N = 264132+614909+2020683+2640994;
36 | var_psi_A = params[1]*y[9]+1.5451053*y[10]+1.0079616*y[11]+0.4403311*y[12];
37 | var_psi_B = 1.5451053*y[9]+7.2500922*y[10]+1.0689326*y[11]+0.4042651*y[12];
38 | var_psi_C = 1.0079616*y[9]+1.0689326*y[10]+1.9051576*y[11]+0.8288393*y[12];
39 | var_psi_D = 0.4403311*y[9]+0.4042651*y[10]+0.8288393*y[11]+0.9137116*y[12];
40 | C_in_A = params[2]*E_to_I_A;
41 | C_in_B = params[2]*E_to_I_B;
42 | C_in_C = params[2]*E_to_I_C;
43 | C_in_D = params[2]*E_to_I_D;
44 | S_to_E_A = var_psi_A*y[1]/total_N;
45 | S_to_E_B = var_psi_B*y[2]/total_N;
46 | S_to_E_C = var_psi_C*y[3]/total_N;
47 | S_to_E_D = var_psi_D*y[4]/total_N;
48 | dydt[1] = -S_to_E_A;
49 | dydt[2] = -S_to_E_B;
50 | dydt[3] = -S_to_E_C;
51 | dydt[4] = -S_to_E_D;
52 | dydt[5] = S_to_E_A-E_to_I_A;
53 | dydt[6] = S_to_E_B-E_to_I_B;
54 | dydt[7] = S_to_E_C-E_to_I_C;
55 | dydt[8] = S_to_E_D-E_to_I_D;
56 | dydt[9] = E_to_I_A-I_to_R_A;
57 | dydt[10] = E_to_I_B-I_to_R_B;
58 | dydt[11] = E_to_I_C-I_to_R_C;
59 | dydt[12] = E_to_I_D-I_to_R_D;
60 | dydt[13] = I_to_R_A;
61 | dydt[14] = I_to_R_B;
62 | dydt[15] = I_to_R_C;
63 | dydt[16] = I_to_R_D;
64 | dydt[17] = C_in_A;
65 | dydt[18] = C_in_B;
66 | dydt[19] = C_in_C;
67 | dydt[20] = C_in_D;
68 | return dydt;
69 | }
70 | }
71 | data {
72 | int n_obs;
73 | array[n_obs] int y_A;
74 | array[n_obs] int y_B;
75 | array[n_obs] int y_C;
76 | array[n_obs] int y_D;
77 | array[n_obs] real ts;
78 | vector[20] x0;
79 | }
80 | parameters {
81 | real k_AA;
82 | real par_rho;
83 | real inv_phi;
84 | }
85 | transformed parameters{
86 | array[n_obs] vector[20] x; // Output from the ODE solver
87 | array[2] real params;
88 | array[n_obs] real delta_x_1;
89 | array[n_obs] real delta_x_2;
90 | array[n_obs] real delta_x_3;
91 | array[n_obs] real delta_x_4;
92 | real phi;
93 | phi = 1 / inv_phi;
94 | params[1] = k_AA;
95 | params[2] = par_rho;
96 | x = ode_rk45(X_model, x0, 0, ts, params);
97 | delta_x_1[1] = x[1, 17] - x0[17] + 1e-5;
98 | delta_x_2[1] = x[1, 18] - x0[18] + 1e-5;
99 | delta_x_3[1] = x[1, 19] - x0[19] + 1e-5;
100 | delta_x_4[1] = x[1, 20] - x0[20] + 1e-5;
101 | for (i in 1:n_obs-1) {
102 | delta_x_1[i + 1] = x[i + 1, 17] - x[i, 17] + 1e-5;
103 | delta_x_2[i + 1] = x[i + 1, 18] - x[i, 18] + 1e-5;
104 | delta_x_3[i + 1] = x[i + 1, 19] - x[i, 19] + 1e-5;
105 | delta_x_4[i + 1] = x[i + 1, 20] - x[i, 20] + 1e-5;
106 | }
107 | }
108 | model {
109 | k_AA ~ normal(0, 10);
110 | par_rho ~ beta(2, 2);
111 | inv_phi ~ exponential(5);
112 | y_A ~ neg_binomial_2(delta_x_1, phi);
113 | y_B ~ neg_binomial_2(delta_x_2, phi);
114 | y_C ~ neg_binomial_2(delta_x_3, phi);
115 | y_D ~ neg_binomial_2(delta_x_4, phi);
116 | }
117 | generated quantities {
118 | real log_lik;
119 | array[n_obs] int sim_y_A;
120 | array[n_obs] int sim_y_B;
121 | array[n_obs] int sim_y_C;
122 | array[n_obs] int sim_y_D;
123 | log_lik = neg_binomial_2_lpmf(y_A | delta_x_1, phi) +
124 | neg_binomial_2_lpmf(y_B | delta_x_2, phi) +
125 | neg_binomial_2_lpmf(y_C | delta_x_3, phi) +
126 | neg_binomial_2_lpmf(y_D | delta_x_4, phi);
127 | sim_y_A = neg_binomial_2_rng(delta_x_1, phi);
128 | sim_y_B = neg_binomial_2_rng(delta_x_2, phi);
129 | sim_y_C = neg_binomial_2_rng(delta_x_3, phi);
130 | sim_y_D = neg_binomial_2_rng(delta_x_4, phi);
131 | }
132 |
--------------------------------------------------------------------------------
/tests/testthat/test-SBC.R:
--------------------------------------------------------------------------------
1 | test_that("sd_data_generator_fun() returns the expected function", {
2 |
3 | filepath <- system.file("models/", "SEIR.stmx", package = "readsdr")
4 |
5 | meas_mdl <- list("y ~ poisson(net_flow(C))")
6 |
7 | estimated_params <- list(
8 | sd_prior("par_beta", "lognormal", c(0, 1)),
9 | sd_prior("par_rho", "beta", c(2, 2)),
10 | sd_prior("I0", "lognormal", c(0, 1), "init"))
11 |
12 | actual_fun <- sd_data_generator_fun(filepath, estimated_params, meas_mdl,
13 | start_time = 0, stop_time = 10,
14 | timestep = 1/32, integ_method = "rk4")
15 |
16 | set.seed(200)
17 | actual_list <- actual_fun()
18 |
19 | expected <- list(
20 | variables = list(
21 | par_beta = 1.088452,
22 | par_rho = 0.5636847,
23 | I0 = 1.541193),
24 | generated = list(
25 | y = c(0, 0, 0, 0, 1, 1, 0, 2, 2, 2),
26 | n_obs = 10,
27 | n_params = 2,
28 | n_difeq = 5,
29 | t0 = 0,
30 | ts = 1:10))
31 |
32 | expect_equal(actual_list, expected, tolerance = 1e-6)
33 |
34 | # Negative Binomial test
35 | meas_mdl <- list("y ~ neg_binomial_2(net_flow(C), phi)")
36 |
37 | estimated_params <- list(
38 | sd_prior("par_beta", "lognormal", c(0, 1)),
39 | sd_prior("par_rho", "beta", c(2, 2)),
40 | sd_prior("I0", "lognormal", c(0, 1), "init"))
41 |
42 | actual_fun <- sd_data_generator_fun(filepath, estimated_params, meas_mdl,
43 | start_time = 0, stop_time = 10,
44 | timestep = 1/32, integ_method = "rk4")
45 |
46 | set.seed(300)
47 | actual_list <- actual_fun()
48 |
49 | expected <- list(
50 | variables = list(
51 | par_beta = 3.950297,
52 | par_rho = 0.7321697,
53 | I0 = 1.605586,
54 | inv_phi = 0.1033722),
55 | generated = list(
56 | y = c(3, 3, 15, 19, 71, 155, 332, 193, 803, 2453),
57 | n_obs = 10,
58 | n_params = 2,
59 | n_difeq = 5,
60 | t0 = 0,
61 | ts = 1:10))
62 |
63 | expect_equal(actual_list, expected, tolerance = 1e-6)
64 | })
65 |
66 | test_that("sd_data_generator_fun() returns the expected function for a vectorised model", {
67 |
68 | filepath <- system.file("models/", "SEIR_age.stmx", package = "readsdr")
69 |
70 | ag <- c("A", "B", "C", "D") # age_groups
71 |
72 | measurements <- stringr::str_glue("y_{ag} ~ poisson(net_flow(C_{ag}))")
73 | meas_mdl <- as.list(measurements)
74 |
75 | estimated_params <- list(
76 | sd_prior("k_AA", "normal", c(0, 10), min = 0),
77 | sd_prior("par_rho", "beta", c(2, 2)))
78 |
79 | actual_fun <- sd_data_generator_fun(filepath, estimated_params, meas_mdl,
80 | start_time = 0, stop_time = 10,
81 | timestep = 1/32, integ_method = "rk4")
82 |
83 | set.seed(111)
84 | actual_list <- actual_fun()
85 |
86 | expected <- list(
87 | variables = list(
88 | k_AA = 2.352207,
89 | par_rho = 0.4073203),
90 | generated = list(
91 | y_A = c(0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
92 | y_B = c(0, 0, 0, 1, 0, 0, 0, 0, 0, 0),
93 | y_C = c(1, 0, 0, 0, 1, 0, 0, 0, 0, 1),
94 | y_D = c(0, 0, 0, 0, 0, 1, 0, 0, 1, 2),
95 | n_obs = 10,
96 | n_params = 2,
97 | n_difeq = 20,
98 | t0 = 0,
99 | ts = 1:10))
100 |
101 | expect_equal(actual_list, expected, tolerance = 1e-6)
102 |
103 | })
104 |
105 | test_that("prior_fun_factory() returns the expected list of functions", {
106 |
107 | estimated_params <- list(
108 | sd_prior("par_beta", "lognormal", c(0, 1)),
109 | sd_prior("par_rho", "beta", c(2, 2)),
110 | sd_prior("I0", "lognormal", c(0, 1), "init"))
111 |
112 | fun_list <- prior_fun_factory(estimated_params, 1)
113 |
114 | set.seed(123)
115 |
116 | val1 <- fun_list[[1]]()
117 | val2 <- fun_list[[2]]()
118 | val3 <- fun_list[[3]]()
119 |
120 | actual <- c(val1, val2, val3)
121 |
122 | factory1 <- function() function() rlnorm(1)
123 | factory2 <- function() function() rbeta(1, 2, 2)
124 |
125 | fun1 <- factory1()
126 | fun2 <- factory2()
127 | fun3 <- factory1()
128 |
129 | set.seed(123)
130 |
131 | val1 <- fun1()
132 | val2 <- fun2()
133 | val3 <- fun3()
134 |
135 | expected <- c(val1, val2, val3)
136 |
137 | expect_equal(actual, expected)
138 | })
139 |
--------------------------------------------------------------------------------
/R/impact_inputs.R:
--------------------------------------------------------------------------------
1 |
2 | #' Construct inputs for performing structural analysis via the impact method
3 | #'
4 | #' @param desc_list Element 'description' from the list returned by \code{read_xmile()}
5 | #'
6 | #' @return A list of three elements. The first element, \code{flows}, is a data
7 | #' frame that lists all the stock-flow links in the model. Further, this data
8 | #' frame describes the equation that governs the link and whether the link is
9 | #' an inflow (+) or an outflow (-). The second element, \code{pathways}, is a
10 | #' data frame that lists all the pathways among stocks. The third element,
11 | #' \code{velocities}, is a data frame in which each row corresponds to a
12 | #' stock. Each row consists of two columns (name & equation).
13 | #' @export
14 | #'
15 | #' @examples
16 | #' filepath <- system.file("models/", "SIR.stmx", package = "readsdr")
17 | #' mdl <- read_xmile(filepath)
18 | #' desc_list <- mdl$description
19 | #' sd_impact_inputs(desc_list)
20 | sd_impact_inputs <- function(desc_list) {
21 |
22 | flows_df <- flow_equations(desc_list)
23 | pathways_df <- pathways(flows_df)
24 | velocities_df <- velocity_equations(desc_list)
25 |
26 | list(flows = flows_df,
27 | pathways = pathways_df,
28 | velocities = velocities_df)
29 | }
30 |
31 | # Construct velocity equations in terms of stocks and constants
32 | velocity_equations <- function(desc_list) {
33 |
34 | levels <- desc_list$levels
35 | stock_names <- purrr::map_chr(levels, "name")
36 |
37 | consts <- desc_list$constants
38 | const_names <- purrr::map_chr(consts, "name")
39 |
40 | var_obj <- desc_list$variables
41 |
42 | elem_names <- c(stock_names, const_names)
43 |
44 | equations <- purrr::map_chr(levels, construct_velocity_equation,
45 | elem_names, var_obj)
46 |
47 |
48 | data.frame(stock = stock_names, equation = equations)
49 | }
50 |
51 | construct_velocity_equation <- function(lvl_obj, elem_names, var_obj) {
52 |
53 | lvl_name <- lvl_obj$name
54 | equation <- lvl_obj$equation
55 |
56 | disentangle_equation(lvl_name, equation, elem_names, var_obj)
57 | }
58 |
59 | disentangle_equation <- function(lvl_name, equation, elem_names, var_obj) {
60 |
61 | var_names <- purrr::map_chr(var_obj, "name")
62 | elems_rhs <- extract_variables(lvl_name, equation)
63 | validation <- sum(!(elems_rhs %in% elem_names))
64 |
65 | if(validation == 0) return(equation)
66 |
67 | if(validation > 0) {
68 |
69 | auxs <- elems_rhs[!(elems_rhs %in% elem_names)]
70 |
71 | for(i in seq_along(auxs)) {
72 |
73 | aux <- auxs[i]
74 | pos_aux <- which(var_names == aux)
75 | aux_eq <- var_obj[[pos_aux]]$equation
76 | aux_eq <- stringr::str_glue("({aux_eq})")
77 | regex_pattern <- stringr::regex(paste0("\\b", aux,"\\b"))
78 | equation <- stringr::str_replace_all(equation, regex_pattern, aux_eq)
79 | }
80 |
81 | disentangle_equation(lvl_name, equation, elem_names, var_obj)
82 |
83 | }
84 | }
85 |
86 | flow_equations <- function(desc_list) {
87 |
88 | levels <- desc_list$levels
89 | lvl_names <- purrr::map_chr(levels, "name")
90 |
91 | consts <- desc_list$constants
92 | const_names <- purrr::map_chr(consts, "name")
93 |
94 | elem_names <- c(lvl_names, const_names)
95 |
96 | var_obj <- desc_list$variables
97 |
98 | purrr::map_df(desc_list$levels, function(lvl_obj) {
99 |
100 | lvl_name <- lvl_obj$name
101 | stock_col <- data.frame(stock = lvl_name)
102 | cbind(stock_col, decompose_net_flow(lvl_obj$equation))
103 | }) -> flow_df
104 |
105 | flow_list <- purrr::transpose(flow_df)
106 |
107 | sapply(flow_list, function(flow_obj) {
108 | disentangle_equation(flow_obj$stock, flow_obj$flow, elem_names, var_obj)
109 | }) -> eqs
110 |
111 | flow_df$equation <- eqs
112 |
113 | flow_df
114 | }
115 |
116 | decompose_net_flow <- function(flow_eq) {
117 |
118 | val <- stringr::str_starts(flow_eq, "-", negate = TRUE)
119 |
120 | if(val) flow_eq <- paste0("+", flow_eq)
121 |
122 | flows <- extract_variables("", flow_eq)
123 | signs <- stringr::str_extract_all(flow_eq, "\\+|\\-")[[1]]
124 |
125 | data.frame(flow = flows, sign = signs)
126 | }
127 |
128 | pathways <- function(flows_df) {
129 |
130 | lvl_names <- unique(flows_df$stock)
131 | flows_list <- purrr::transpose(flows_df)
132 |
133 | purrr::map_df(flows_list, function(flow_obj) {
134 |
135 | to <- flow_obj$stock
136 | through <- flow_obj$flow
137 | flow_vars <- extract_variables(to, flow_obj$equation)
138 | from <- flow_vars[flow_vars %in% lvl_names]
139 |
140 | data.frame(from = from, to = to, through = through)
141 | })
142 | }
143 |
--------------------------------------------------------------------------------
/tests/testthat/test-generate_igraph_inputs.R:
--------------------------------------------------------------------------------
1 | context("Generate igraph inputs")
2 |
3 | structure_m1 <- list(
4 | parameters = NULL,
5 | levels = list(
6 | list(name = "Population",
7 | equation = "net_growth",
8 | initValue = 100)
9 | ),
10 | variables = list(
11 | list(name = "net_growth",
12 | equation = "Population*growth_rate")
13 | ),
14 | constants = list(
15 | list(name = "growth_rate",
16 | value = 0.01)
17 | ))
18 |
19 | #get_igraph_inputs()------------------------------------------------------------
20 |
21 | test_that("get_igraph_inputs() returns the expected elements", {
22 | expect_named(get_igraph_inputs(structure_m1), c("nodes", "edges"))
23 | })
24 |
25 | test_that("get_igraph_inputs() valid inputs for igraph", {
26 | graph_dfs <- get_igraph_inputs(structure_m1)
27 |
28 | gr <- igraph::graph_from_data_frame(graph_dfs$edges, directed = T,
29 | vertices = graph_dfs$nodes)
30 | expect_is(gr, "igraph")
31 | })
32 |
33 |
34 |
35 | stocks <- list(list(name = "population",
36 | equation = "births",
37 | initValue = 100))
38 |
39 |
40 | variables <- list(list(name = "births", equation = "population*birthRate"),
41 | list(name = "birthRate", equation = "birthRate2"))
42 |
43 | constants <- list(list(name = "birthRate2", value = 0.1))
44 |
45 | stocks2 <- stocks
46 | variables2 <- list(list(name = "births", equation = "population*birthRate"))
47 | constants2 <- list(list(name = "birthRate", value = 0.1))
48 |
49 | # generate_edges_df()-----------------------------------------------------------
50 |
51 | test_that("generate_edges_df() returns the correct number of edges", {
52 | e_df <- with(structure_m1, generate_edges_df(levels, variables, constants))
53 | expect_equal(nrow(e_df), 2)
54 | })
55 |
56 | test_that("generate_edges_df() returns the correct number of flows", {
57 | e_df <- with(structure_m1, generate_edges_df(levels, variables, constants))
58 | e_df <- e_df[e_df$type == "flow", ]
59 | expect_equal(nrow(e_df), 1)
60 | })
61 |
62 | test_that("generate_edges_df() ignores info-links whose tail is a constant", {
63 | edges_df <- generate_edges_df(stocks, variables, constants)
64 | expect_equal(nrow(edges_df), 3)
65 | })
66 |
67 | # generate_nodes_df()-----------------------------------------------------------
68 |
69 | test_that("generate_nodes_df() returns a df with the correct columns", {
70 | actual_val <- with(structure_m1,
71 | generate_nodes_df(levels, variables, constants))
72 |
73 | expect_named(actual_val, c("name", "type", "equation"))
74 | })
75 |
76 | test_that("generate_nodes_df() returns the correct number of nodes", {
77 | df <- with(structure_m1, generate_nodes_df(levels, variables, constants))
78 | expect_equal(nrow(df), 2)
79 | })
80 |
81 | test_that("generate_nodes_df() replaces auxiliar consts with their value in equations", {
82 | nodes_df <- generate_nodes_df(stocks2, variables2, constants2)
83 | expect_equal(nodes_df[[2, "equation"]], "population*0.1")
84 | })
85 |
86 | test_that("generate_nodes_df() throws an error should a variable directly
87 | depends on time", {
88 |
89 | variables <- list(
90 | list(name = "net_growth",
91 | equation = "ifelse(time>1Population*growth_rate,0)")
92 | )
93 | expect_error(
94 | generate_nodes_df(structure_m1$levels, variables, structure_m1$constants),
95 | "A variable depends on time")
96 | })
97 |
98 | # construct_var_edge()----------------------------------------------------------
99 |
100 | test_that("construct_var_edge() ignores scalars in equations", {
101 | var_obj <- list(name = "w",
102 | equation = "x*y/(k+1/z)")
103 |
104 | const_names_test <- c("k")
105 | actual <- construct_var_edge(var_obj, const_names_test)
106 | expected <- data.frame(from = c("x", "y", "z"), to = "w", type = "info_link",
107 | stringsAsFactors = FALSE)
108 | expect_equal(actual, expected)
109 | })
110 |
111 | test_that("construct_var_edge() accounts for repeated variables", {
112 | var_obj <- list(name = "z",
113 | equation = "(x+y)/x")
114 |
115 | const_names_test <- c("k")
116 | actual <- construct_var_edge(var_obj, const_names_test)
117 | expected <- data.frame(from = c("x", "y"), to = "z", type = "info_link",
118 | stringsAsFactors = FALSE)
119 | expect_equal(actual, expected)
120 | })
121 |
122 | # construct_stock_edge()--------------------------------------------------------
123 |
124 | test_that('construct_stock_edge returns NULL constant-alike stocks', {
125 | stock_obj <- list(name = "test_stock",
126 | equation = "0")
127 | expect_equal(construct_stock_edge(stock_obj), NULL)
128 | } )
129 |
--------------------------------------------------------------------------------
/R/SBC.R:
--------------------------------------------------------------------------------
1 | #' Function factory for SBC
2 | #' @return A function.
3 | #' @export
4 |
5 | #' @inheritParams read_xmile
6 | #' @inheritParams sd_Bayes
7 | #' @inheritParams sd_simulate
8 | #' @examples
9 | #' filepath <- system.file("models/", "SEIR.stmx", package = "readsdr")
10 | #' meas_mdl <- list("y ~ poisson(net_flow(C))")
11 | #' estimated_params <- list(
12 | #' sd_prior("par_beta", "lognormal", c(0, 1)),
13 | #' sd_prior("par_rho", "beta", c(2, 2)),
14 | #' sd_prior("I0", "lognormal", c(0, 1), "init"))
15 | #' sd_data_generator_fun(filepath, estimated_params, meas_mdl)
16 | sd_data_generator_fun <- function(filepath, estimated_params, meas_mdl,
17 | start_time = NULL,
18 | stop_time = NULL,
19 | timestep = NULL,
20 | integ_method = "euler") {
21 |
22 | pars_names <- get_names(estimated_params, "par_name")
23 |
24 | estimated_params <- get_meas_params(meas_mdl, estimated_params)
25 |
26 | prior_fun_list <- prior_fun_factory(estimated_params, 1)
27 |
28 | mdl_structure <- extract_structure_from_XMILE(filepath, pars_names)
29 | ds_inputs <- get_deSolve_elems(mdl_structure)
30 |
31 | if(!(integ_method %in% c("euler", "rk4"))) stop("Invalid integration method")
32 |
33 | ds_inputs <- update_sim_params(ds_inputs, start_time, stop_time, timestep)
34 |
35 | n_stocks <- length(ds_inputs$stocks)
36 |
37 | unk_types <- sapply(estimated_params, function(prior_obj) prior_obj$type)
38 | n_consts <- sum(unk_types == "constant")
39 |
40 | idx_meas <- which(unk_types == "meas_par")
41 | n_meas_par <- length(idx_meas)
42 |
43 | data_fun <- function() {
44 |
45 | prior_vals <- lapply(prior_fun_list, function(prior_fun) prior_fun())
46 |
47 | for(param in pars_names) ds_inputs$consts[[param]] <- prior_vals[[param]]
48 |
49 | readsdr_env <- list2env(prior_vals)
50 |
51 | ds_inputs$stocks <- purrr::map_dbl(ds_inputs$stocks, function(x) {
52 |
53 | eval(parse(text = x), envir = readsdr_env)
54 | })
55 |
56 | if(n_meas_par > 0) {
57 |
58 | meas_params <- estimated_params[idx_meas]
59 |
60 | for(meas_par_obj in meas_params) {
61 |
62 | # Parameter's name before the transformation
63 | before_name <- stringr::str_remove(meas_par_obj$par_name,
64 | paste0(meas_par_obj$par_trans, "_"))
65 |
66 | trans_value <- execute_trans(prior_vals[[meas_par_obj$par_name]],
67 | meas_par_obj$par_trans)
68 |
69 | meas_mdl <- lapply(meas_mdl, function(meas_obj) {
70 |
71 | stringr::str_replace(meas_obj, before_name,
72 | as.character(trans_value))
73 | })
74 | }
75 | }
76 |
77 | measurement_df <- sd_measurements(1, meas_mdl, ds_inputs,
78 | start_time = start_time,
79 | stop_time = stop_time,
80 | timestep = timestep,
81 | integ_method = integ_method)
82 |
83 | n_obs <- length(unique(measurement_df$time))
84 |
85 | split_df <- split(measurement_df, measurement_df$var_name)
86 | meas_list <- lapply(split_df, function(df) df$measurement)
87 | names(meas_list) <- unique(measurement_df$var_name)
88 |
89 | specs_list <- list(
90 | n_obs = n_obs,
91 | n_params = n_consts,
92 | n_difeq = n_stocks,
93 | t0 = 0,
94 | ts = 1:n_obs)
95 |
96 | list(variables = prior_vals,
97 | generated = c(meas_list, specs_list))
98 | }
99 | }
100 |
101 | prior_fun_factory <- function(estimated_params, n_draws) {
102 |
103 | n_params <- length(estimated_params)
104 |
105 | fun_list <- vector("list", n_params)
106 |
107 | for (i in 1:n_params) {
108 |
109 | par_obj <- estimated_params[[i]]
110 |
111 | dist_name <- par_obj$dist
112 |
113 | arg_names_R <- get_dist_args(dist_name, "R")
114 | arg_names_Stan <- get_dist_args(dist_name)
115 | arg_list <- par_obj[arg_names_Stan]
116 |
117 | fun_name <- Stan_to_R(dist_name)
118 |
119 | fun_list[[i]] <- fun_factory(fun_name, arg_names_R, arg_list, n_draws)
120 | names(fun_list)[i] <- par_obj$par_name
121 | }
122 |
123 | fun_list
124 | }
125 |
126 | fun_factory <- function(fun_name, arg_names_R, arg_list, n_draws) {
127 |
128 | dist_args_txt <- paste(arg_names_R, arg_list, sep = " = ")
129 | n_arg <- paste0("n = ", n_draws)
130 | args_txt <- paste(c(n_arg, dist_args_txt), collapse = ", ")
131 | body_fun <- stringr::str_glue("{fun_name}({args_txt })")
132 |
133 | rlang::new_function(args = NULL, body = rlang::parse_expr(body_fun))
134 | }
135 |
--------------------------------------------------------------------------------
/tests/testthat/test_models/SEIR_simlin.stmx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | SEIR
5 | Simlin
6 | Simlin
7 |
8 |
9 | 0
10 | 100
11 | 0.015625
12 |
13 |
14 |
15 |
16 | 9999
17 | s_to_e
18 |
19 |
20 | 0
21 | s_to_e
22 | e_to_i
23 |
24 |
25 | 1
26 | e_to_i
27 | i_to_r
28 |
29 |
30 | 0
31 | i_to_r
32 |
33 |
34 | par_beta * s * i / n
35 |
36 |
37 | par_sigma * e
38 |
39 |
40 | par_gamma * i
41 |
42 |
43 | 10000
44 |
45 |
46 | 1
47 |
48 |
49 | 0.5
50 |
51 |
52 | 0.5
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 | s
81 | s to e
82 |
83 |
84 | i
85 | s to e
86 |
87 |
88 |
89 | n
90 | s to e
91 |
92 |
93 |
94 | par beta
95 | s to e
96 |
97 |
98 | e
99 | e to i
100 |
101 |
102 | i
103 | i to r
104 |
105 |
106 |
107 | par gamma
108 | i to r
109 |
110 |
111 |
112 | par sigma
113 | e to i
114 |
115 |
116 |
117 |
118 |
--------------------------------------------------------------------------------