Подтвердить что ты не робот

Чтение файла шаблона и запись его на диск после некоторых изменений

Мне нужно прочитать файл шаблона test.txt, изменить содержимое и затем записать на диск измененную копию с именем foo`i`.in (i - номер итерации). Поскольку мне нужно выполнить эту операцию много раз (миллион раз не было бы необычным), предпочтительными были бы эффективные решения. Файл шаблона выглядит так:

1 
bar.out 
       70.000000000000000 
        2.000000000000000 
       14.850000000000000 
     8000.000000000000000 
      120.000000000000000 
       60.000000000000000 
        0.197500000000000 
        0.197500000000000 
        2.310000000000000 
        0.200000000000000 
        0.000000000000000 
        1.000000000000000 
        0.001187700000000 
       22.000000000000000 
        1.400000000000000 
        1.000000000000000 
        0.010000000000000 
100 
        0.058600000000000 
       -0.217000000000000 
        0.078500000000000 
       -0.110100000000000 
30 
      500.000000000000000 
T 

Мне не нужно изменять все строки, только некоторые из них. В частности, мне нужно изменить bar.out на bar`i`.out, где i - индекс итерации. Мне также необходимо изменить некоторые числовые строки со следующими значениями:

parameters <- data.frame(index = c(1:10, 13:16, 21:22), variable = c("P1", 
                      "P2", "T1", "s", "D", "L", "C1", "C2", "VA", 
                      "pw", "m", "mw", "Cp", "Z", "ff_N", "ff_M"),
                      value = c(65, 4, 16.85, 7900, 110, 60, 0.1975, .1875, 2.31,
                                 0.2, 0.0011877, 22.0, 1.4, 1.0, 0.0785, -0.1101))

Все остальные строки должны оставаться неизменными, включая последнюю строку T. Таким образом, предполагая, что я на первой итерации, ожидаемый вывод представляет собой текстовый файл с именем foo1.in с содержимым (точный формат номера не важен, если все значимые цифры в parameters$value включены в foo1.in):

1 
bar1.out 
       65.000000000000000 
        4.000000000000000
       16.850000000000000 
     7900.000000000000000 
      110.000000000000000 
       60.000000000000000 
        0.197500000000000 
        0.187500000000000 
        2.310000000000000 
        0.200000000000000 
        0.000000000000000 
        1.000000000000000 
        0.001187700000000 
       22.000000000000000 
        1.400000000000000 
        1.000000000000000 
        0.010000000000000 
100 
        0.058600000000000 
       -0.217000000000000 
        0.078500000000000 
       -0.110100000000000 
30 
      500.000000000000000 
T 

Модификация foo.in и bar.out проста:

template  <- "test.txt"
infile    <- "foo.in"
string1 <- "bar.out"
iteration <- 1

# build string1
elements <- strsplit(string1, "\\.")[[1]]
elements[1] <- paste0(elements[1], iteration)
string1 <- paste(elements, collapse = ".")

# build infile name
elements <- strsplit(infile, "\\.")[[1]]
elements[1] <- paste0(elements[1], iteration)
infile<- paste(elements, collapse = ".")

Теперь я хотел бы прочитать файл шаблона и изменить только намеченные строки. Первая проблема, с которой я сталкиваюсь, заключается в том, что read.table выводит только кадр данных. Так как мой файл шаблона содержит числа и строки в том же столбце, если я прочитаю весь файл с помощью read.table, я бы получил столбец символов (я думаю). Я обойду проблему, читая только числовые значения, которые меня интересуют:

    # read template file   
    temp <- read.table(template, stringsAsFactors = FALSE, skip = 2, nrows = 23)$V1
    lines_to_read <- temp[length(temp)]

    # modify numerical parameter values
    temp[parameters$index] <- parameters$value

Однако теперь я не знаю, как написать foo1.in. Если я использую write.table, я могу писать только матрицы или dataframes на диск, поэтому я не могу записать файл, который содержит числа и строки в том же столбце. Как я могу это решить?

EDIT Я предоставляю немного информации об этой проблеме, чтобы объяснить, почему мне нужно многократно писать этот файл. Итак, идея состоит в том, чтобы выполнить байесовский вывод для параметров калибровки компьютерного кода (исполняемого файла). Основная идея проста: у вас есть черный ящик (коммерческий) компьютерный код, который имитирует физическую проблему, например код FEM. Позвольте называть этот код Джо. Учитывая входной файл, Joe выводит предсказание для ответа физической системы. Теперь у меня также есть фактические экспериментальные измерения для реакции этой системы. Я хотел бы найти значения входов Joe, так что разница между выводами Joe и реальными измерениями сводится к минимуму (на самом деле все совсем по-другому, но это только для того, чтобы дать представление). На практике это означает, что мне нужно много раз запускать Joe с разными входными файлами и итеративно находить входные значения, которые уменьшают "несоответствие" между предсказанием Джо и экспериментальными результатами. Короче говоря:

  • Мне нужно создать много входных (текстовых) файлов
  • Я не знаю заранее содержимое входных файлов. Числовые параметры изменяются в процессе оптимизации итерационным способом.
  • Мне также нужно прочитать вывод Joe для каждого входа. На самом деле это еще одна проблема, и я, вероятно, напишу конкретный вопрос по этому вопросу.

Итак, хотя Joe является коммерческим кодом, для которого у меня есть только исполняемый файл (без источника), байесовский вывод выполняется в R, потому что R (и, для чего это важно, Python) имеют отличные инструменты для выполнения такого рода исследования.

4b9b3361

Ответ 1

Это, пожалуй, проще всего решить с использованием языка шаблонов, например Mustache, который реализован в R в whisker.

Ниже приведен пример, показывающий, как это можно сделать в вашем случае. В качестве примера я выполнил только первые три переменные и bar1.out. Реализация остальных переменных должна быть простой.

library(whisker)


# You could also read the template in using readLines
# template <- readLines("template.txt")
# but to keep example selfsufficient, I included it in the code
template <- "1 
bar{{run}}.out 
      {{P1}}
      {{P2}}
      {{T1}}
     8000.000000000000000 
      120.000000000000000 
       60.000000000000000 
        0.197500000000000 
        0.197500000000000 
        2.310000000000000 
        0.200000000000000 
        0.000000000000000 
        1.000000000000000 
        0.001187700000000 
       22.000000000000000 
        1.400000000000000 
        1.000000000000000 
        0.010000000000000 
100 
        0.058600000000000 
       -0.217000000000000 
        0.078500000000000 
       -0.110100000000000 
30 
      500.000000000000000 
T"


# Store parameters in a list
parameters <- list(
  run = 1, 
  P1 = 65,
  P2 = 4,
  T1 = 16.85)

for (i in seq_len(10)) {
  # New set of parameters
  parameters$run <- i
  parameters$P1  <- sample(1:100, 1)

  # Generate new script by rendering the template using paramers
  current_script <- whisker.render(template, parameters)
  writeLines(current_script, paste0("foo", i, ".in"))

  # Run script
  # system(...)
}

Какие усы (в этом случае возможны более сложные шаблоны, например условные элементы) заменяют все {{<variable>}} на соответствующее значение в списке parameters.

Ответ 2

Похоже, вам нужны пользовательские функции чтения/записи; не идеально, но когда у вас есть гибридная колонна, вы уже расходитесь с "аккуратными данными" (независимо от того, аккуратно или нет).

Три функции, которые упрощают то, что я считаю вам нужным:

read_myfile <- function(x) {
  # mostly during dev
  if (file.exists(x)) x <- readLines(x)
  if (length(x) == 1) x <- strsplit(rawfile, "\n")[[1]]
  # find all left-aligned NAMED rows
  hdrs <- grep("[A-Za-z]", x)
  hdrs <- c(1, hdrs) # ensure the first "1" is preserved
  dat <- mapply(function(a,b,x) if (b >= a) as.numeric(x[seq(a, b)]),
                hdrs + 1, c(hdrs[-1] - 1, length(x)), list(x),
                SIMPLIFY = FALSE)
  names(dat) <- trimws(x[hdrs])
  dat
}

mod_myfile <- function(x, i, params) {
  # sanity checks
  stopifnot(
    is.list(x),
    is.numeric(i),
    is.data.frame(params),
    all(c("index", "value") %in% colnames(params))
  )
  isbarout <- which(names(x) == "bar.out")
  stopifnot(
    length(isbarout) == 1
  )
  x$bar.out[ params$index ] <- params$value
  names(x)[isbarout] <- sprintf("bar%i.out", i)
  x
}

write_myfile <- function(x, ...) {
  newdat <- unlist(unname(
    mapply(function(hdr, dat) c(hdr, sprintf("%25.15f ", dat)),
           names(x), x, SIMPLIFY = TRUE)
  ))
  writeLines(newdat, ...)
}

Использование прямолинейно. Я начну с одной символьной строки для эмуляции шаблона ввода (функция чтения работает одинаково хорошо с символьной строкой, как и с именем файла):

rawfile <- "1 
bar.out 
       70.000000000000000 
        2.000000000000000 
       14.850000000000000 
     8000.000000000000000 
      120.000000000000000 
       60.000000000000000 
        0.197500000000000 
        0.197500000000000 
        2.310000000000000 
        0.200000000000000 
        0.000000000000000 
        1.000000000000000 
        0.001187700000000 
       22.000000000000000 
        1.400000000000000 
        1.000000000000000 
        0.010000000000000 
100 
        0.058600000000000 
       -0.217000000000000 
        0.078500000000000 
       -0.110100000000000 
30 
      500.000000000000000 
T 
"

Чтобы начать, просто прочитайте данные:

dat <- read_myfile(rawfile)
# dat <- read_myfile("file.in")
str(dat)
# List of 3
#  $ 1      : NULL
#  $ bar.out: num [1:24] 70 2 14.8 8000 120 ...
#  $ T      : NULL

Вы каким-то образом определите, как параметры должны быть изменены. Я буду использовать ваши предыдущие данные:

parameters <- data.frame(
  index = c(1:10, 13:16, 21:22),
  variable = c("P1", "P2", "T1", "s", "D", "L", "C1", "C2",
               "VA", "pw", "m", "mw", "Cp", "Z", "ff_N", "ff_M"),
  value = c(65, 4, 16.85, 7900, 110, 60, 0.1975, .1875, 2.31,
            0.2, 0.0011877, 22.0, 1.4, 1.0, 0.0785, -0.1101)
)

Первым параметром является вывод из read_myfile; второй - итератор, который вы хотите увеличить bar.out; третий - это parameters data.frame:

newdat <- mod_myfile(dat, 32, parameters)
str(newdat)
# List of 3
#  $ 1        : NULL
#  $ bar32.out: num [1:24] 65 4 16.9 7900 110 ...
#  $ T        : NULL

А теперь напишите.

write_myfile(newdat, sprintf("foo%d.in", 32))

Я не знаю, как производительность @GiovanniRighi будет сравниваться при запуске в одном сеансе R, но 1000 из этих файлов занимают менее 7 секунд на моем компьютере.

Ответ 3

Несколько трюков должны помочь. Пусть последует минимальный рабочий пример, который, я думаю, имеет все особенности вашей проблемы. Вот содержимое файла, который я изменяю, tmp.txt:

1
bar.out
21
31
T

Обычно мы работаем со списками, а не с векторами, когда R имеет гетерогенные данные. Но мне кажется, что мне легче работать с символьным символом. Прочитайте файл из текстового соединения в вектор символа:

a <- readLines("tmp.txt")

Поскольку у вас есть значения замены, замените их так же, как и строки. Поскольку похоже, что у вас есть замена строк под контролем, позвольте изменить эти числа. Мы хотим преобразовать числовой вектор, который у вас есть, в вектор символа.

value <- c(21, 31)
value <- as.character(value)
a[3:4] <- value

Теперь напишите, чтобы заменить старый файл:

writeLines(a, "tmp.txt")

Теперь комментарий Фрэнк уместен, потому что файл ввода-вывода будет серьезным узким местом здесь. Было бы намного быстрее сделать все это в ОЗУ.

 time for i in {1..1000}; do ./run.R; done

 real   0m44.988s
 user   0m33.270s
 sys    0m5.170s

Время, казалось, увеличивалось линейно, поэтому я ожидал, что миллион итераций займет около 16 часов. Большая часть времени - чтение и запись файлов. Вы можете попытаться ускорить это, но я не думаю, что вы сможете увеличить его, если вы не можете заставить ваш MCMC-двоичный код выплюнуть двоичные файлы Rdata (или файлы пера).