Чтение файла шаблона и запись его на диск после некоторых изменений
Мне нужно прочитать файл шаблона 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) имеют отличные инструменты для выполнения такого рода исследования.
Ответы
Ответ 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 (или файлы пера).