stan-dev / stan-dev/rstan

read_csv_header() can match more than one line

Open
#927 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
R
Stars
1.1k
Forks
266
Avg merge
2h 56m
Merged PRs (30d)
1

Description

Summary:

If the path to the output file in the Stan CSV matches one of the regexes in read_csv_header(), then the function throws an error.

Description:

read_csv_header() uses regexes to extract information like the number of warmup iterations from the Stan CSV. Because this is done in a while() loop, if multiple lines (prior to the header) match a regex, then the last match will be used. I happened to include the term within in the name one of my models (I was experimenting with within-chain parallelization), and noticed that read_stan_csv() was throwing an error. It turns out that the output file name was matching the thin regex, and because the result was a character, integer coercion resulted in an NA value that propagated to iter.count.

It seems that this could possibly be avoided by replacing the regex "#.*thin" with "#[[:blank:]]*thin". Another solution would be to avoid including "reserved words" in the model name.

Reproducible Steps:

The following reprex demonstrates the issue, and shows the solution proposed above.

csv_lines <- c(
  "# model = redcard_within_model",
  "# method = sample (Default)",
  "#   sample",
  "#     num_samples = 1000 (Default)",
  "#     num_warmup = 1000 (Default)",
  "#     save_warmup = 0 (Default)",
  "#     thin = 1 (Default)",
  "# output",
  "#   file = redcard_within-202103251619-1-16b40e.csv",
  "lp__,accept_stat__,stepsize__,treedepth__,n_leapfrog__,divergent__,energy__,beta.1,beta.2"
)
tmp <- tempfile(fileext = ".csv")
writeLines(csv_lines, tmp)
rstan:::read_csv_header(tmp)
#> Warning in rstan:::read_csv_header(tmp): NAs introduced by coercion to integer
#> range
#> [1] "lp__,accept_stat__,stepsize__,treedepth__,n_leapfrog__,divergent__,energy__,beta.1,beta.2"
#> attr(,"iter.count")
#> [1] NA
#> attr(,"lineno")
#> [1] 10

g <- function(f, comment.char = '#') {
  con <- file(f, 'r')
  niter <- 0
  iter.count <- NA
  save.warmup <- FALSE
  sample.count <- NA_integer_
  thin <- NULL
  while (length(input <- readLines(con, n = 1)) > 0) {
    niter <- niter + 1
    if (!grepl(comment.char, input)) break;
    if (grepl("# iter=",input))
      iter.count <- as.integer(gsub("# iter=","",input))
    if (grepl("#.*num_samples",input)){
      sample.count <- as.integer(gsub("[^0-9]*([0-9]*).*","\\1",input))
    }
    if (grepl("#.*num_warmup",input)){
      warmup.count <- as.integer(gsub("[^0-9]*([0-9]*).*","\\1",input))
    } else {
      warmup.count <- 0L
    }
    if (grepl("#[[:blank:]]*thin", input)){
      thin <- as.integer(gsub("[^0-9]*([0-9]*).*","\\1",input))
    }
    if (grepl("#.*save_warmup",input)){
      save.warmup <- !grepl("0",input)
    }
    if (grepl("#.*output_sample",input)){
      iter.count <- as.numeric(gsub("[^0-9]*([0-9]*).*","\\1",input))
    }
    
  }
  header <- input
  if(is.na(iter.count)){
    if(save.warmup)
      iter.count <- warmup.count + sample.count
    else
      iter.count <- sample.count
  }
  if(!is.null(thin)){
    iter.count <- iter.count %/% thin
  }
  attr(header, "iter.count") <- iter.count
  attr(header, "lineno") <- niter
  close(con)
  header
}

g(tmp)
#> [1] "lp__,accept_stat__,stepsize__,treedepth__,n_leapfrog__,divergent__,energy__,beta.1,beta.2"
#> attr(,"iter.count")
#> [1] 1000
#> attr(,"lineno")
#> [1] 10
RStan Version:

2.21.2

R Version:

R version 4.0.3 (2020-10-10)

Operating System:

Ubuntu 18.04

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the read_csv_header() entry point and reproduce the issue with the supplied csv_lines example, focusing on matching the thin metadata line. Done means a model or output filename containing "thin" no longer produces an NA iter.count, while the actual # thin header still yields the expected iteration count.

Written by the indexing model from the issue text.

Assessment

Tech stack
r
Domain
data
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.