В этом разделе описывается общая архитектура RBOINC. Информация в этом разделе предназначена только для администраторов BOINC проектов и волонтёров, которые хотят установить RBOINC или участвовать в проектах, основанных на нём соотвественно. Если вы являетесь волонтёром, перейдите к подразделу Компьютеры волонтёров. Если вы не являетесь администратором своего проекта BOINC, пропустите этот раздел.
RBOINC Можно разделить на 3 части:
Схематично работу RBOINC можно изобразить так: Всё начинается на компьютерах пользователей. Пользователь открывает интерпритатор R и создаёт пачку заданий, передавая данные, код и т.д. Пакет RBOINC.cl собирает всё необходимое для выполнения заданий в архив и отсылает серверу BOINC. Сервер распоковывает архив и регистрирует задания. После этого компьютеры волонтёров запрашивают задания у сервера, скачивают их, дополнительно загружают виртуальную машину и запускают её. Виртуальная машина запускает интерпритатор R и выполняет задание. После завершения обработки файл с результатами загружается на сервер. Сервер проверяет файл валидатором и при успешной проверке помещает его в папку с результатами. Пользователь может забрать результаты расчётов в любое время.
Изначально предполагалось, что тот, кто захочет использовать пакет RBOINC должен самостоятельно собрать виртуальную машину. Теперь предполагается, что вы возьмёте виртуальную машину, настроенную на сборку виртуальных машин для RBOINC и просто доустановите необходимые пакеты и ПО. Далее описывается досборка на основе предоставляемого нами инструмента. Инструкция для сборки виртуальной машины с нуля была удалена из этого документа, и может быть найдена в старых коммитах репозитория пакета.
Предварительно собранные виртуальные машины могут быть найдены по этой ссылке: https://disk.yandex.ru/d/e_aItFuMPfKF2A. В этих машинах установлен минимальный набор ПО и пакетов, необходимый для нормальной работы пакета RBOINC. Поддерживаются только архитекуры i686 и amd64. По этой же ссылке лежит файл виртуального диска с инструментом сборки новых виртуальных машин.
Сборка виртуалки для использования с RBOINC достаточно проста:
Скачать образ диска виртуальной машины с суфиксом “bs”
Создать в virtualbox виртуальную машину, подключить к ней скачаный диск и ещё 2 пустых диска (рекомендуемый размер - 4 Гб.)
Загрузиться. Войти как root (пароля нет).
В домашней дирректории root лежат несколько скриптов:
“enter.sh” - этот скрипт нужен для изменения корневого каталога (входа) на папку будующей виртуальной машины. Используйте его для установки и обновления ПО, и других действий при подготовке виртуальных машин.
“prepare_update.sh” - этот скрипт обновляет ПО обоих виртуальных машин и инструмента сборки (за исключением ядра - его можно обновить только вручную). Дополнительно он устанавливает на виртуальные машины минимально необходимый набор пакетов языка R.
“flash.sh” - этот скрипт подготавливает образ выбранной виртуальной машины и записывает его на диск.
Сначала выполните “prepare_update.sh”. Следите за ошибками, которые возможны в процессе его выполнения.
По необходимости используйте “enter.sh” для переключения на необходимую виртуальную машину для выполнения дополнительных действий.
Когда обновление и установка ПО завершена, используйте “flash.sh” для записи виртуальной машины на диск (диск должен быть пустым).
Проверьте корректность загрузки и завершения работы полученного диска RBOINC.
Серверная часть состоит из компонентов, которые реализуют функции, или не предоставляемые BOINC, или более удобные для использования в этом пакете обёртки над интерфейсами BOINC. Серверная часть должна быть установлена на проект BOINC до создания заданий. Обратите внимание на то, что серверная часть включает в себя стороннии компоненты, которые могут лицензироваться на условиях лицензий, отличных от BSD3.
Установка компонентов заключается в копировании содержимого папки “/back-end/Server/project” в директорию вашего проекта BOINC и последующем редактировании конфигурационных файлов.
В серверную часть входит валидатор, который выполняет простейшие проверки присланных заданий. Для его сборки скопируйте папку “validator” в папку с исходными файлами BOINC, перейдите в папку “validator” и выполните “make”. Предполагается, что перед этим BOINC был сконфигурирован и собран(См. https://boinc.berkeley.edu/trac/wiki/ServerIntro).
Этот валидатор требует установки интерпритатора R на сервер. В будущем это требование может стать необязательным, а может и не стать. В качестве альтернативы можно использовать sample_trivial_validator из стандартной поставки BOINC.
В этой директории находятся файлы приложений. Обратите внимание, что ВМ не хранится в репозитории и должна быть скачана и добавлена отдельно. Это сделано из-за того, что ВМ часто меняется и занимает много места.
Официально поддериваются только ОС семейства Linux и Windows, использующие ахитектуры amd64 и i686. Теоретически возможна поддержка MacOS, с теми-же архитектурами (см. https://boinc.berkeley.edu/trac/wiki/VboxApps) но я не нашёл компьютера с ней для тестирования и отладки.
Содержит файлы, которые изначально были созданы для ssh протокола и предоставляли функции, к которым не было прямого доступа из командной оболочки. Позже они были расширены и теперь предоставляют дополнительно функции для пакета, в первую очередь это генерация уникальных имён для файлов и пачек заданий.
Предоставляет низкоуровневый(но более высокоуровневый, чем оригинальный интерфейс BOINC) интерфейс для загрузки файлов на сервер BOINC по http/https протоколам. Дополнительно сообщает пакету имя для создания пачки работ.
Шаблоны для входных и выходных файлов BOINC. Подробнее см. в https://boinc.berkeley.edu/trac/wiki/JobTemplates.
Подготовка компьютера волонтёра к работе с проектом, основанным на RBOINC достаточно проста. Необходимо установить BOINC Manager, VirtualBox и VirtualBox Extension Pack, а также включить аппаратную виртуализацию в настройках BIOS/UEFI. После установки необходимого ПО рекомендуется выполнить перезагрузку. После этого проект можно добавлять в BOINC Manager.
Также рекомендуется добавить в запуск по расписанию каждые несколько минут команду “boinccmd –project <Project URL> update”. В данный момент у проекта есть некоторые проблемы связанные с тем, что в определённый момент число заданий на сервере может стать нулевым. В этом случае BOINC Client перестаёт запрашивать новые задания и заставить его это сделать можно или ручным обновлением проекта или перезагузкой компьютера. Я не нашёл решения этой проблемы в документации BOINC, если знаете, как её решить - пошлите мне письмо на почту или добавьте feature request в репозитории RBOINC.
Если ваша операционная система Ubuntu и выполнении заданий завершается с ошибкой “Postponed: VM job unmanageable, restarting later”, то добавьте в атозагрузку запуск “vboxwebsrv” от пользователя “boinc” и перезагрузите компьютер. Тоже самое необходимо сделать, если все задания завершаются с ошибкой “NS_ERROR_SOCKET_FAIL”. Если вашей системой инициализации является systemd, то это можно сделать следующим образом (все действия необходимо выполнять от root):
[Unit]
Description=Run vboxwebsrv as user boinc
DefaultDependencies=no
After=network.target
[Service]
Type=simple
User=boinc
Group=boinc
ExecStart=/usr/bin/vboxwebsrv
TimeoutStartSec=0
RemainAfterExit=yes
[Install]
WantedBy=default.target
Это может распространяться и на другие ОС семейства Linux.
Пакет RBOINC.cl это клиентская часть RBOINC. Он должен быть установлен на компьютеры клиентов, которые хотят запускать свои задания с помощью RBOINC. Для клиентов установка выполняется максимально просто, достаточно выполнить:
install.packages(c('R.utils', 'askpass', 'foreach', 'httr', 'ssh', 'xml2',
'stats', 'utils', 'doParallel', 'parallel', 'BiocManager'))
install.packages("RBOINC.cl", repos="http://R-Forge.R-project.org")
Обратите внимание, что в данный момент пакет находится на стадии бета теста. В дальнейшем планируется отправка пакета в CRAN.
Администратор проекта RBOINC должен предоставить клиенту следующую информацию:
Для использования рекомендуется http интерфейс. Подробности о нём вы можете найти в https://boinc.berkeley.edu/trac/wiki/MultiUser. Обратите внимание, что вопреки тому, что написано на вики BOINC, высокоуровневое управление(файлы BOINC “manage_project.php” и “submit.php”) этим интерфейсом недоделано. Тем не менее, на низком уровне, этот интерфейс работает и RBOINC.cl использует его для создания заданий и разграничений прав доступа.
Если вы собираетесь использовать ssh интерфейс, то вы должны добавить соответствующего пользователя в группы администратора вашего проекта и www-data, и установить его umask в значение, позволяющее другим пользователям из этих групп читать его файлы. В будущем это требование может быть убрано.
В данный момент пакет находится в статусе беты. Установить его можно следующим образом:
install.packages(c('R.utils', 'askpass', 'foreach', 'httr', 'ssh', 'xml2',
'stats', 'utils', 'doParallel', 'parallel', 'BiocManager'))
install.packages("RBOINC.cl", repos="http://R-Forge.R-project.org")
Для работы проекта вам необходима учётная запись с правами на создание работы. Обратитесь к администратору вашего проекта RBOINC для получения данных для авторизации.
Я начинаю с функции тестирования, потому что её можно использовать без необходимости в учётной записи в проекте RBOINC. Давайте посмотрим на определение функции:
function(work_func,
test_jobs =data = NULL,
n = NULL,
init_func = NULL,
global_vars = NULL,
packages = c(),
files = c(),
callback_function = NULL,
install_func = NULL)
Её параметры:
Эта функция создаёт пачку заданий, но никуда её не отправляет. Вместо этого она выполняет все задания локально, последовательно переходя от одного задания к другому. Нет никаких гарантий, что если с помощью этой функции удалось выполнить задание, то его удастся выполнить с помощью реального сервера BOINC (и наоборот). Однако, эта функция сильно упрощает написание кода, поскольку позволяет проверить задания на типичные ошибки, например отсутствующие пакеты, необъявленные объекты, опечатки в коде и т.д.
Подключим пакет и выполним простейший код, умножающий вектор из 9 элементов на 5:
library(RBOINC.cl)
function(val)
fun =
{return(val*5)
}
1:9
data =
test_jobs(fun, data) res =
## Testing archive making... OK: /tmp/RtmpVolfKd/file7ae921bb5294.tar.xz
## Creating tmp dir for test... OK: /tmp/RtmpVolfKd/file7ae93f09e93c
## Testing archive unpacking... OK: founded files:
## /tmp/RtmpVolfKd/file7ae93f09e93c/common.tar.xz
## /tmp/RtmpVolfKd/file7ae93f09e93c/data/0.rda
## /tmp/RtmpVolfKd/file7ae93f09e93c/data/1.rda
## /tmp/RtmpVolfKd/file7ae93f09e93c/data/2.rda
## /tmp/RtmpVolfKd/file7ae93f09e93c/data/3.rda
## /tmp/RtmpVolfKd/file7ae93f09e93c/data/4.rda
## /tmp/RtmpVolfKd/file7ae93f09e93c/data/5.rda
## /tmp/RtmpVolfKd/file7ae93f09e93c/data/6.rda
## /tmp/RtmpVolfKd/file7ae93f09e93c/data/7.rda
## /tmp/RtmpVolfKd/file7ae93f09e93c/data/8.rda
## Searching jobs... OK: 9
## Running job 0.rda in /tmp/RtmpVolfKd/file7ae921152c1b/0.rda OK
## Running job 1.rda in /tmp/RtmpVolfKd/file7ae921152c1b/1.rda OK
## Running job 2.rda in /tmp/RtmpVolfKd/file7ae921152c1b/2.rda OK
## Running job 3.rda in /tmp/RtmpVolfKd/file7ae921152c1b/3.rda OK
## Running job 4.rda in /tmp/RtmpVolfKd/file7ae921152c1b/4.rda OK
## Running job 5.rda in /tmp/RtmpVolfKd/file7ae921152c1b/5.rda OK
## Running job 6.rda in /tmp/RtmpVolfKd/file7ae921152c1b/6.rda OK
## Running job 7.rda in /tmp/RtmpVolfKd/file7ae921152c1b/7.rda OK
## Running job 8.rda in /tmp/RtmpVolfKd/file7ae921152c1b/8.rda OK
for(val in res){
print(val$result)
}
## [1] 5
## [1] 10
## [1] 15
## [1] 20
## [1] 25
## [1] 30
## [1] 35
## [1] 40
## [1] 45
Функция создала 9 заданий, по числу элементов в data. Если параметр n не задан, то число заданий будет равно длине data. data должен быть вектором или нумерованным списком. n должен быть меньше или равен длине data (но больше нуля). Если длина data не делится нацело на n, то у каких-то заданий окажется меньше работы. У work_func должен быть только 1 параметр, в который будет передан элемент data.
Давайте создадим 2 задания вместо 9:
library(RBOINC.cl)
function(val)
fun =
{return(val*5)
}
1:9
data =
test_jobs(fun, data, 2) res =
## Testing archive making... OK: /tmp/RtmpyNMQCo/file7e3c1e496b0b.tar.xz
## Creating tmp dir for test... OK: /tmp/RtmpyNMQCo/file7e3c7d82fd35
## Testing archive unpacking... OK: founded files:
## /tmp/RtmpyNMQCo/file7e3c7d82fd35/common.tar.xz
## /tmp/RtmpyNMQCo/file7e3c7d82fd35/data/0.rda
## /tmp/RtmpyNMQCo/file7e3c7d82fd35/data/1.rda
## Searching jobs... OK: 2
## Running job 0.rda in /tmp/RtmpyNMQCo/file7e3c34dca0ae/0.rda OK
## Running job 1.rda in /tmp/RtmpyNMQCo/file7e3c34dca0ae/1.rda OK
for(val in res){
print(val$result)
}
## [1] 5
## [1] 10
## [1] 15
## [1] 20
## [1] 25
## [1] 30
## [1] 35
## [1] 40
## [1] 45
Функция обработки данных может быть рекурсивной. Давайте посчитаем факториал:
library(RBOINC.cl)
function(val)
fac =
{if(val == 0 || val == 1){
return(val)
else {
} return(val*fac(val- 1))
}
}
1:9
data =
test_jobs(fac, data, 2) res =
## Testing archive making... OK: /tmp/RtmpyNMQCo/file7e3c1e9c1f1.tar.xz
## Creating tmp dir for test... OK: /tmp/RtmpyNMQCo/file7e3c6ec56d11
## Testing archive unpacking... OK: founded files:
## /tmp/RtmpyNMQCo/file7e3c6ec56d11/common.tar.xz
## /tmp/RtmpyNMQCo/file7e3c6ec56d11/data/0.rda
## /tmp/RtmpyNMQCo/file7e3c6ec56d11/data/1.rda
## Searching jobs... OK: 2
## Running job 0.rda in /tmp/RtmpyNMQCo/file7e3c34ff944c/0.rda OK
## Running job 1.rda in /tmp/RtmpyNMQCo/file7e3c34ff944c/1.rda OK
for(val in res){
print(val$result)
}
## [1] 1
## [1] 2
## [1] 6
## [1] 24
## [1] 120
## [1] 720
## [1] 5040
## [1] 40320
## [1] 362880
Если вам не требуется какая-либо обработка данных, но требуется запустить сразу несколько заданий с одинаковыми параметрами, то вы можете опустить параметр data. Например, вычислим апроксимацию числа Пи методом Монте-Карло:
library(RBOINC.cl)
10000
N =
function()
pi_approx =
{ runif(N,0,1)
a = runif(N,0,1)
b = sum(a*a+b*b < 1)/N
res =return(res)
}
test_jobs(pi_approx, n = 10, global_vars = list(N = N))
res =
0
prob = 0
count =for(val in res){
prob + val$result
prob = count + 1
count = }
## Testing archive making... OK: /tmp/Rtmp52u06I/file80f654aeda91.tar.xz
## Creating tmp dir for test... OK: /tmp/Rtmp52u06I/file80f6713be70c
## Testing archive unpacking... OK: founded files:
## /tmp/Rtmp52u06I/file80f6713be70c/common.tar.xz
## /tmp/Rtmp52u06I/file80f6713be70c/data/0.rda
## /tmp/Rtmp52u06I/file80f6713be70c/data/1.rda
## /tmp/Rtmp52u06I/file80f6713be70c/data/2.rda
## /tmp/Rtmp52u06I/file80f6713be70c/data/3.rda
## /tmp/Rtmp52u06I/file80f6713be70c/data/4.rda
## /tmp/Rtmp52u06I/file80f6713be70c/data/5.rda
## /tmp/Rtmp52u06I/file80f6713be70c/data/6.rda
## /tmp/Rtmp52u06I/file80f6713be70c/data/7.rda
## /tmp/Rtmp52u06I/file80f6713be70c/data/8.rda
## /tmp/Rtmp52u06I/file80f6713be70c/data/9.rda
## Searching jobs... OK: 10
## Running job 0.rda in /tmp/Rtmp52u06I/file80f62ce13dc9/0.rda OK
## Running job 1.rda in /tmp/Rtmp52u06I/file80f62ce13dc9/1.rda OK
## Running job 2.rda in /tmp/Rtmp52u06I/file80f62ce13dc9/2.rda OK
## Running job 3.rda in /tmp/Rtmp52u06I/file80f62ce13dc9/3.rda OK
## Running job 4.rda in /tmp/Rtmp52u06I/file80f62ce13dc9/4.rda OK
## Running job 5.rda in /tmp/Rtmp52u06I/file80f62ce13dc9/5.rda OK
## Running job 6.rda in /tmp/Rtmp52u06I/file80f62ce13dc9/6.rda OK
## Running job 7.rda in /tmp/Rtmp52u06I/file80f62ce13dc9/7.rda OK
## Running job 8.rda in /tmp/Rtmp52u06I/file80f62ce13dc9/8.rda OK
## Running job 9.rda in /tmp/Rtmp52u06I/file80f62ce13dc9/9.rda OK
print(prob/count*4)
## [1] 3.1362
Обратите внимание, что в этом случае прототип функции обработки меняется: теперь это функция без параметров. Здесь мы воспользовались дополнительным праметром global_vars, для передачи числа точек для генерации. global_vars обязательно должен быть списком в котором имена элементов соответствуют именам глобальных переменным. В качестве глобальной переменной можно передавать любой объект, который можно сохранить функцией save. Например, умножим нечётные элементы на 3, а чётные разделим на 2:
library(RBOINC.cl)
function(val)
d =
{return(val/2)
}
function(val)
m =
{return(val*3)
}
function(val)
fun =
{if(mod(val,2) == 0){
d(val)
res =else {
} m(val)
res =
}return(res)
}
1:10
data =
# Здесь мы передаём функции как глобальные переменные:
test_jobs(fun, data, 3, global_vars = list(d = d, m = m), packages = "numbers") res =
## Testing archive making... OK: /tmp/RtmpqWzQsX/file82102eac31ff.tar.xz
## Creating tmp dir for test... OK: /tmp/RtmpqWzQsX/file821019b31c9d
## Testing archive unpacking... OK: founded files:
## /tmp/RtmpqWzQsX/file821019b31c9d/common.tar.xz
## /tmp/RtmpqWzQsX/file821019b31c9d/data/0.rda
## /tmp/RtmpqWzQsX/file821019b31c9d/data/1.rda
## /tmp/RtmpqWzQsX/file821019b31c9d/data/2.rda
## Searching jobs... OK: 3
## Running job 0.rda in /tmp/RtmpqWzQsX/file8210b91879a/0.rda OK
## Running job 1.rda in /tmp/RtmpqWzQsX/file8210b91879a/1.rda OK
## Running job 2.rda in /tmp/RtmpqWzQsX/file8210b91879a/2.rda OK
for(val in res){
print(val$result)
}
## [1] 3
## [1] 1
## [1] 9
## [1] 2
## [1] 15
## [1] 3
## [1] 21
## [1] 4
## [1] 27
## [1] 5
Здесь мы воспользовались дополнительным параметром packages для передачи пакета. В общем случае можно передать несколько пакетов, в виде вектора из строк, т.е:
test_jobs(fun, data, 3, global_vars = list(d = d, m = m), packages = c("package1", "package2")) res =
Пакеты устанавливаются вызовом функции BiocManager::install(). Таким образом можно использовать пакеты из BioConductor’а и неоторых других репозиториев (см. BiocManager::repositories()). Следующий пример использует пакет с BioConductor:
library(RBOINC.cl)
function()
fun =
{ matrix(c(0, 0, 1, 1,
mat =0, 0, 1, 1,
1, 1, 0, 1,
1, 1, 1, 0),
byrow=TRUE, ncol=4)
rownames(mat) <- letters[1:4]
colnames(mat) <- letters[1:4]
return(graphAM(adjMat=mat))
}
test_jobs(fun, n = 1, packages = c("graph"))
res =
::install("Rgraphviz", ask=FALSE)
BiocManagerlibrary('Rgraphviz')
plot(res[[1]]$result)
Если после этого остались пакеты, которые не удалось установить (включая зависимости), их имена передаются функции install_func
Зачастую необходимо как-то проверить данные, сохранить или выполнить постобработку. В этом случае можно передать функцию обратного вызова через callback_function. Эта функция должна принимать 1 параметр, равный элементу результата. То, что возвращает эта функция будет считаться результатом задания и помещаться в массив результата. Например, выведем все значения второго примера через print, а в качестве результата вернём значения, поделённые на 2:
library(RBOINC.cl)
function(val)
fun =
{return(val*5)
}
function(val)
callback =
{print(val)
return(val/2)
}
1:9
data =
test_jobs(fun, data, 2, callback_function = callback) res =
## Testing archive making... OK: /tmp/Rtmp4vKgVw/file842667802be2.tar.xz
## Creating tmp dir for test... OK: /tmp/Rtmp4vKgVw/file842625f8718b
## Testing archive unpacking... OK: founded files:
## /tmp/Rtmp4vKgVw/file842625f8718b/common.tar.xz
## /tmp/Rtmp4vKgVw/file842625f8718b/data/0.rda
## /tmp/Rtmp4vKgVw/file842625f8718b/data/1.rda
## Searching jobs... OK: 2
## Running job 0.rda in /tmp/Rtmp4vKgVw/file84261fb28e93/0.rda
## [1] 5
## [1] 10
## [1] 15
## [1] 20
## [1] 25
## OK
## Running job 1.rda in /tmp/Rtmp4vKgVw/file84261fb28e93/1.rda
## [1] 30
## [1] 35
## [1] 40
## [1] 45
## OK
for(val in res){
print(val$result)
}
## [1] 2.5
## [1] 5
## [1] 7.5
## [1] 10
## [1] 12.5
## [1] 15
## [1] 17.5
## [1] 20
## [1] 22.5
Через параметр files можно передать дополнительные файлы. Раскидаем по 2-м файлам следующий С++ код:
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export]]
NumericVector m(NumericVector val)
{return val*3;
}
// [[Rcpp::export]]
NumericVector d(NumericVector val)
{return d/2;
}
paste(
code_m ="#include <Rcpp.h>",
"using namespace Rcpp;",
"",
"// [[Rcpp::export]]",
"NumericVector m(NumericVector val)",
"{",
" return val*3;",
"}",
sep = '\n'
)
paste(
code_d ="#include <Rcpp.h>",
"using namespace Rcpp;",
"",
"// [[Rcpp::export]]",
"NumericVector d(NumericVector val)",
"{",
" return val/2;",
"}",
sep = '\n'
)
tempfile()
dirname =dir.create(dirname)
file(paste0(dirname, "/m.cpp"))
out =writeLines(code_m, out)
close(out)
file(paste0(dirname, "/d.cpp"))
out =writeLines(code_d, out)
close(out)
Функция init_func вызывается до начала обработки задания в каждом узле, созданным функцией makeCluster() (или в главном, если эта функция не вызывалась). Она нужна для дополнительной инициализации среды выполнения задачи. Используем сгенерированный ранее C++ файл для демонстрации работы этих параметров. Если ваша операционная система Windows, то убедитесь в том, что rtools установлены и прописаны в PATH.
library(RBOINC.cl)
function(val)
fun =
{if(mod(val,2) == 0){
val/2
res =else {
} m(val)
res =
}return(res)
}
function()
init =
{sourceCpp("m.cpp")
}
1:10
data =
test_jobs(fun, data, 1, packages = c("numbers", "Rcpp"),
res =files = paste0(dirname, "/m.cpp"), init_func = init)
## Testing archive making... OK: /tmp/RtmpVesJoT/file8589257d6b26.tar.xz
## Creating tmp dir for test... OK: /tmp/RtmpVesJoT/file85896d4563d
## Testing archive unpacking... OK: founded files:
## /tmp/RtmpVesJoT/file85896d4563d/common.tar.xz
## /tmp/RtmpVesJoT/file85896d4563d/data/0.rda
## Searching jobs... OK: 1
## Running job 0.rda in /tmp/RtmpVesJoT/file858955aa72c0/0.rda OK
for(val in res){
print(val$result)
}
## [1] 3
## [1] 1
## [1] 9
## [1] 2
## [1] 15
## [1] 3
## [1] 21
## [1] 4
## [1] 27
## [1] 5
Также можно передать несколько файлов:
library(RBOINC.cl)
function(val)
fun =
{if(mod(val,2) == 0){
d(val)
res =else {
} m(val)
res =
}return(res)
}
function()
init =
{sourceCpp("m.cpp")
sourceCpp("d.cpp")
}
1:10
data =
test_jobs(fun, data, 1, packages = c("numbers", "Rcpp"),
res =files = c(paste0(dirname, "/m.cpp"), paste0(dirname, "/d.cpp")),
init_func = init)
## Testing archive making... OK: /tmp/RtmpuwJJjv/file882b7cf2d67b.tar.xz
## Creating tmp dir for test... OK: /tmp/RtmpuwJJjv/file882b2e22b0aa
## Testing archive unpacking... OK: founded files:
## /tmp/RtmpuwJJjv/file882b2e22b0aa/common.tar.xz
## /tmp/RtmpuwJJjv/file882b2e22b0aa/data/0.rda
## Searching jobs... OK: 1
## Running job 0.rda in /tmp/RtmpuwJJjv/file882b4f28d359/0.rda OK
for(val in res){
print(val$result)
}
Кроме этого, можно передавать папки целиком:
library(RBOINC.cl)
function(val)
fun =
{if(mod(val,2) == 0){
d(val)
res =else {
} m(val)
res =
}return(res)
}
function()
init =
{sourceCpp(paste0(dname, "/m.cpp"))
sourceCpp(paste0(dname, "/d.cpp"))
}
1:3
data =
test_jobs(fun, data, packages = c("numbers", "Rcpp"),
res =global_vars = list(dname = basename(dirname)),
files = dirname, init_func = init)
## Testing archive making... OK: /tmp/Rtmpn8bszK/file8b31254dc04.tar.xz
## Creating tmp dir for test... OK: /tmp/Rtmpn8bszK/file8b317e7d6d2f
## Testing archive unpacking... OK: founded files:
## /tmp/Rtmpn8bszK/file8b317e7d6d2f/common.tar.xz
## /tmp/Rtmpn8bszK/file8b317e7d6d2f/data/0.rda
## /tmp/Rtmpn8bszK/file8b317e7d6d2f/data/1.rda
## /tmp/Rtmpn8bszK/file8b317e7d6d2f/data/2.rda
## Searching jobs... OK: 3
## Running job 0.rda in /tmp/Rtmpn8bszK/file8b31373acce1/0.rda OK
## Running job 1.rda in /tmp/Rtmpn8bszK/file8b31373acce1/1.rda OK
## Running job 2.rda in /tmp/Rtmpn8bszK/file8b31373acce1/2.rda OK
for(val in res){
print(val$result)
}
## [1] 3
## [1] 1
## [1] 9
Очень редко бывает необходимо установить пакеты из нестандартных мест или сделать какие-то дополнительные действия, если часть пакетов не установилась. Функция install_func должна иметь один параметр и она всегда вызывается после установки пакетов, перечисленных в packages. В функцию будет передан вектор, содержащий имена всех пакетов, которые не удалось установить (включая зависимости). Если внутри этой функции вам необходимо вызывать какие-либо функции из пакетов, перечисленных в packages, то обращайтесь к ним через двоеточие. Обратите внимание, что в текущей версии пакета вы не должны использовать этот параметр для установки пакетов с BioConductor. Следующий пример устанавливает пакет с github и вычисляет среднее число клиентов в системе для модели суперкомпьютера с рандомизированной политикой переключения скоростей обслуживания:
library(RBOINC.cl)
function(pkgs){
inst =for(val in pkgs){
if(val == "MJMrss"){
::install_github("ProgGrey/MJMrss")
remotes
}
}
}
function()
fun =
{ 1
lambda = 4
N = c(1,2.2)
f = matrix(c(0.1,0.9,
P_a =0.0, 1.0), nrow = 2, byrow = TRUE)
matrix(c(1.0, 0.0,
P_d =0.2, 0.8), nrow = 2, byrow = TRUE)
matrix(c(1, 2, 3, 4, # servers
classes =0.5, 0.25, 0.15, 0.1, # probability
1, 1.9, 2.5, 3), # class speed (1/mean work)
nrow = 3, byrow = TRUE)
build_model(lambda, N, classes, f, P_a, P_d)
m =return(m$mean_clients)
}
test_jobs(fun, n = 1, packages = c("remotes", "MJMrss"), install_func = inst) res =
## Testing archive making... OK: /tmp/RtmpapVANb/file8da184ae148.tar.xz
## Creating tmp dir for test... OK: /tmp/RtmpapVANb/file8da133835272
## Testing archive unpacking... OK: founded files:
## /tmp/RtmpapVANb/file8da133835272/common.tar.xz
## /tmp/RtmpapVANb/file8da133835272/data/0.rda
## Searching jobs... OK: 1
## Running job 0.rda in /tmp/RtmpapVANb/file8da16f806b73/0.rda OK
1]]$result res[[
## [1] 0.3833206
Теперь рассмотрим функции для работы с реальным сервером RBOINC. Первая функция, которая нам понадобится, это функция создания подключения create_connection с прототипом:
function(server,
create_connection =
dir,
username,password = NULL,
keyfile = NULL)
Рассмотрим её параметры:
Для получения учётных данных для создания заданий обратитесь к администратору вашего RBOINC проекта. В настоящий момент использование ssh интерфейса не рекомендуется. Если ваш сервер использует http/https интерфейс, то процедура получения прав на создание заданий обычно происходит в два этапа: сначало вы создаёте на нём обычную учётную запись пользователя BOINC, а потом администратор выдаёт ей права на создание заданий.
Примеры использования:
create_connection("ssh://server.local", "~/projects/rboinc_dev", "boincadm", "Emooka9u")
con = create_connection("http://server.local", "rboinc_dev", "submitter@example.com","ahth3Eze")
con = create_connection("https://server.local", "rboinc_dev", "submitter@example.com") con =
Эта функция должна быть вызвана до того, как вы попытаетесь как-либо взаимодействовать с сервером. Она возвращает соединение (список с параметрами, необходимыми другим функциям для работы с сервером) и может сгенерировать исключения при ошибках. Возвращаемый этой функцией объект нельзя сохранить.
Это функция обратная для create_connection. Она закрывает соединение, переданное в качестве единственного её параметра. После вызова этой функции объект, переданный ей в качестве параметра, не может быть использован никакими другими функциями. Перед завершением сеанса R всегда убеждайтесь, что вы вызвали close_connection для всех открытых ранее create_connection соединений. Обратите внимание, что единственный параметр этой функции по сути является ссылкой.
Эти функции используются для создания пачек работ на сервере и получения результатов. Сначало рассмотрим прототип функции create_jobs, используемой для создания пачек заданий:
function(connection,
create_jobs =
work_func,data = NULL,
n = NULL,
init_func = NULL,
global_vars = NULL,
packages = c(),
files = c(),
install_func = NULL)
Параметры этой функции почти такие-же, как у test_jobs:
Эта функция возвращает состояние пачки заданий, которое необходимо сохранить и использовать при вызове других функций.
Теперь посмотрим на прототип функции update_jobs_status, которая используется для получение результатов от сервера:
function(connection, jobs_status, callback_function = NULL) update_jobs_status =
Параметры этой функции:
Эта функция возвращает статус пачки работ, которое необходимо сохранить и использовать при вызове всех функций, работающих с пачками работ, включая эту.
Теперь перейдём к примерам работы этих функций. Эти примеры являются аналогами примеров, привидённых в разделе про функцию test_jobs
Подключим пакет и выполним простейший код, умножающий вектор из 2-х элементов на 5:
library(RBOINC.cl)
function(val)
fun =
{return(val*5)
}
1:2
data =
create_connection("http://server.local/", "rboinc_alpha", "submitter@example.com", "ahth3Eze")
con =
create_jobs(con, fun, data) status =
После создания заданий необходимо немного подождать. Подождём несколько минут, после чего запросим у сервера результаты:
update_jobs_status(con, status)
status =
if(status$status == "done"){
print(status$results)
close_connection(con)
}
## [[1]]
## [1] 5
##
## [[2]]
## [1] 10
Если при попытке обновление было выведено предупреждение типа:
Warning in value[[3L]](cond) :
Failed to download result: "Error in download_result(connection, jobs_status$jobs_name[k]): http://server.local//rboinc_alpha/download/rboinc/rboinc_2022_01_31_10_52_31.354400.75_2 not found.
"
просто подождите немного и повторите попытку обновления. Это сообщение появляется из-за бага BOINC: BOINC сообщает об успешном завершении работы раньше, чем копирует файл с результатами в папку результатов.
Функция создала 2 задания, по числу элементов в data. Если параметр n не задан, то число заданий будет равно длине data. data должен быть вектором или нумерованным списком. n должен быть меньше или равен длине data (но больше нуля). Если длина data не делится нацело на n, то у каких-то заданий окажется меньше работы. У work_func должен быть только 1 параметр, в который будет передан элемент data.
Давайте создадим 2 задания вместо 9:
library(RBOINC.cl)
function(val)
fun =
{return(val*5)
}
1:9
data =
create_connection("http://server.local/", "rboinc_alpha", "submitter@example.com", "ahth3Eze")
con =
create_jobs(con, fun, data, 2) status =
Обратите внимание на то, что не обязательно ждать, когда пачка заданий завершися полностью. Результаты для завершённых заданий могут загружаться по мере готовности. Подождём завершения первого задания и запросим результат:
update_jobs_status(con, status)
status =
print(status$results)
## [[1]]
## NULL
##
## [[2]]
## NULL
##
## [[3]]
## NULL
##
## [[4]]
## NULL
##
## [[5]]
## NULL
##
## [[6]]
## [1] 30
##
## [[7]]
## [1] 35
##
## [[8]]
## [1] 40
##
## [[9]]
## [1] 45
Также обратите внимание на то, что задания не обязательно выполняются по порядку. Подождём ещё немного и запросим результат с сервера:
update_jobs_status(con, status)
status =
if(status$status == "done"){
print(status$results)
close_connection(con)
}
## [[1]]
## [1] 5
##
## [[2]]
## [1] 10
##
## [[3]]
## [1] 15
##
## [[4]]
## [1] 20
##
## [[5]]
## [1] 25
##
## [[6]]
## [1] 30
##
## [[7]]
## [1] 35
##
## [[8]]
## [1] 40
##
## [[9]]
## [1] 45
Функция обработки данных может быть рекурсивной. Давайте посчитаем факториал:
library(RBOINC.cl)
function(val)
fac =
{if(val == 0 || val == 1){
return(val)
else {
} return(val*fac(val - 1))
}
}
1:9
data =
create_connection("http://server.local/", "rboinc_alpha", "submitter@example.com", "ahth3Eze")
con =
create_jobs(con, fac, data, 1) status =
update_jobs_status(con, status)
status =for(val in status$results){
print(val)
}if(status$status == "done"){
close_connection(con)
}
## [1] 1
## [1] 2
## [1] 6
## [1] 24
## [1] 120
## [1] 720
## [1] 5040
## [1] 40320
## [1] 362880
Если вам не требуется какая-либо обработка данных, но требуется запустить сразу несколько заданий с одинаковыми параметрами, то вы можете опустить параметр data. Например, вычислим апроксимацию числа Пи методом Монте-Карло:
library(RBOINC.cl)
100000
N =
function()
pi_approx =
{ runif(N,0,1)
a = runif(N,0,1)
b = sum(a*a+b*b < 1)/N
res =return(res)
}
create_connection("http://server.local/", "rboinc_alpha", "submitter@example.com", "ahth3Eze")
con =
create_jobs(con, pi_approx, n = 3, global_vars = list(N = N)) status =
update_jobs_status(con, status)
status =if(status$status == "done"){
close_connection(con)
}
0
prob = 0
count =for(val in status$results){
prob + val
prob = count + 1
count =
}print(prob/count*4)
## [1] 3.138573
Обратите внимание, что в этом случае прототип функции обработки меняется: теперь это функция без параметров. Здесь мы воспользовались дополнительным праметром global_vars, для передачи числа точек для генерации. global_vars обязательно должен быть списком в котором имена элементов соответствуют именам глобальных переменным. В качестве глобальной переменной можно передавать любой объект, который можно сохранить. Например, умножим нечётные элементы на 3, а чётные разделим на 2:
library(RBOINC.cl)
function(val)
d =
{return(val/2)
}
function(val)
m =
{return(val*3)
}
function(val)
fun =
{if(mod(val,2) == 0){
d(val)
res =else {
} m(val)
res =
}return(res)
}
1:10
data =
create_connection("http://server.local/", "rboinc_alpha", "submitter@example.com", "ahth3Eze")
con =
# Здесь мы передаём функции как глобальные переменные:
create_jobs(con, fun, data, 1, global_vars = list(d = d, m = m), packages = "numbers") status =
update_jobs_status(con, status)
status =for(val in status$results){
print(val)
}if(status$status == "done"){
close_connection(con)
}
## [1] 3
## [1] 1
## [1] 9
## [1] 2
## [1] 15
## [1] 3
## [1] 21
## [1] 4
## [1] 27
## [1] 5
Здесь мы воспользовались дополнительным параметром packages для передачи пакета. В общем случае можно передать несколько пакетов, в виде вектора из строк, т.е:
create_jobs(con, fun, data, 3, global_vars = list(d = d, m = m),
res =packages = c("package1", "package2"))
Пакеты устанавливаются вызовом функции BiocManager::install(). Таким образом можно использовать пакеты из BioConductor’а и некоторых других репозиториев (см. BiocManager::repositories()). Следующий пример использует пакет с BioConductor:
library(RBOINC.cl)
function()
fun =
{ matrix(c(0, 0, 1, 1,
mat =0, 0, 1, 1,
1, 1, 0, 1,
1, 1, 1, 0),
byrow=TRUE, ncol=4)
rownames(mat) <- letters[1:4]
colnames(mat) <- letters[1:4]
return(graphAM(adjMat=mat))
}
create_connection("http://server.local/", "rboinc_alpha", "submitter@example.com", "ahth3Eze")
con = create_jobs(con, fun, n = 1, packages = c("graph")) status =
install.packages("BiocManager")
::install(ask=FALSE)
BiocManager::install(c("graph", "Rgraphviz"), ask=FALSE)
BiocManagerlibrary('Rgraphviz')
update_jobs_status(con, status)
status =if(status$status == "done"){
close_connection(con)
}plot(status$results[[1]])
Если после этого остались пакеты, которые не удалось устновить, их имена передаются функции install_func
Зачастую необходимо как-то проверить данные, сохранить или выполнить постобработку. В этом случае можно передать функцию обратного вызова через callback_function. Эта функция должна принимать 1 параметр, равный элементу результата. То, что возвращает эта функция будет считаться результатом задания и помещаться в массив результата. Например, выведем все значения второго примера через print, а в качестве результата вернём 1:
library(RBOINC.cl)
function(val)
fun =
{return(val*5)
}
function(val)
callback =
{print(val)
return(1)
}
1:9
data =
create_connection("http://server.local/", "rboinc_alpha", "submitter@example.com", "ahth3Eze")
con =
create_jobs(con, fun, data, 2)
status =
update_jobs_status(con, status, callback)
status =if(status$status == "done"){
close_connection(con)
}
## [1] 5
## [1] 10
## [1] 15
## [1] 20
## [1] 25
## [1] 30
## [1] 35
## [1] 40
## [1] 45
for(val in status$results){
print(val)
}
## [1] 1
## [1] 1
## [1] 1
## [1] 1
## [1] 1
## [1] 1
## [1] 1
## [1] 1
## [1] 1
Через параметр files можно передать дополнительные файлы. Раскидаем по 2-м файлам следующий С++ код:
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export]]
NumericVector m(NumericVector val)
{return val*3;
}
// [[Rcpp::export]]
NumericVector d(NumericVector val)
{return d/2;
}
paste(
code_m ="#include <Rcpp.h>",
"using namespace Rcpp;",
"",
"// [[Rcpp::export]]",
"NumericVector m(NumericVector val)",
"{",
" return val*3;",
"}",
sep = '\n'
)
paste(
code_d ="#include <Rcpp.h>",
"using namespace Rcpp;",
"",
"// [[Rcpp::export]]",
"NumericVector d(NumericVector val)",
"{",
" return val/2;",
"}",
sep = '\n'
)
tempfile()
dirname =dir.create(dirname)
file(paste0(dirname, "/m.cpp"))
out =writeLines(code_m, out)
close(out)
file(paste0(dirname, "/d.cpp"))
out =writeLines(code_d, out)
close(out)
Функция init_func вызывается до начала обработки задания в каждом узле, созданным функцией makeCluster() (или в главном, если эта функция не вызывалась). Она нужна для дополнительной инициализации среды выполнения задачи. Используем сгенерированный ранее C++ файл для демонстрации работы этих параметров.
library(RBOINC.cl)
function(val)
fun =
{if(mod(val,2) == 0){
val/2
res =else {
} m(val)
res =
}return(res)
}
function()
init =
{sourceCpp("m.cpp")
}
1:10
data =
create_connection("http://server.local/", "rboinc_alpha", "submitter@example.com", "ahth3Eze")
con = create_jobs(con, fun, data, 1, packages = c("numbers", "Rcpp"),
status =files = paste0(dirname, "/m.cpp"), init_func = init)
update_jobs_status(con, status)
status =for(val in status$results){
print(val)
}if(status$status == "done"){
close_connection(con)
}
## [1] 3
## [1] 1
## [1] 9
## [1] 2
## [1] 15
## [1] 3
## [1] 21
## [1] 4
## [1] 27
## [1] 5
Также можно передать несколько файлов:
library(RBOINC.cl)
function(val)
fun =
{if(mod(val,2) == 0){
d(val)
res =else {
} m(val)
res =
}return(res)
}
function()
init =
{sourceCpp("m.cpp")
sourceCpp("d.cpp")
}
1:10
data =
create_connection("http://server.local/", "rboinc_alpha", "submitter@example.com", "ahth3Eze")
con = create_jobs(con, fun, data, 1, packages = c("numbers", "Rcpp"),
status =files = c(paste0(dirname, "/m.cpp"), paste0(dirname, "/d.cpp")),
init_func = init)
update_jobs_status(con, status)
status =for(val in status$results){
print(val)
}if(status$status == "done"){
close_connection(con)
}
Кроме этого, можно передавать папки целиком:
library(RBOINC.cl)
function(val)
fun =
{if(mod(val,2) == 0){
d(val)
res =else {
} m(val)
res =
}return(res)
}
function()
init =
{sourceCpp(paste0(dname, "/m.cpp"))
sourceCpp(paste0(dname, "/d.cpp"))
}
1:3
data =
create_connection("http://server.local/", "rboinc_alpha", "submitter@example.com", "ahth3Eze")
con = create_jobs(con, fun, data, packages = c("numbers", "Rcpp"),
status =global_vars = list(dname = basename(dirname)),
files = dirname, init_func = init)
update_jobs_status(con, status)
status =for(val in status$results){
print(val)
}if(status$status == "done"){
close_connection(con)
}
## [1] 3
## [1] 1
## [1] 9
Очень редко бывает необходимо установить пакеты из нестандартных мест или сделать какие-то дополнительные действия, если часть пакетов не установилась. Функция install_func должна иметь один параметр и она будет вызвана после установки пакетов, перечисленных в packages. В функцию будет передан вектор, содержащий имена всех пакетов, которые не удалось установить. Если внутри этой функции вам необходимо вызывать какие-либо функции из пакетов, перечисленных в packages, то обращайтесь к ним через двоеточие. Обратите внимание, что в текущей версии пакета вы не должны использовать этот параметр для установки пакетов с BioConductor. Следующий пример устанавливает пакет с github и вычисляет среднее число клиентов в системе для модели суперкомпьютера с рандомизированной политикой переключения скоростей обслуживания (требуется git, установленный в VM):
library(RBOINC.cl)
function(pkgs){
inst =for(val in pkgs){
if(val == "MJMrss"){
::install_github("ProgGrey/MJMrss")
remotes
}
}
}
function()
fun =
{ 1
lambda = 4
N = c(1,2.2)
f = matrix(c(0.1,0.9,
P_a =0.0, 1.0), nrow = 2, byrow = TRUE)
matrix(c(1.0, 0.0,
P_d =0.2, 0.8), nrow = 2, byrow = TRUE)
matrix(c(1, 2, 3, 4, # servers
classes =0.5, 0.25, 0.15, 0.1, # probability
1, 1.9, 2.5, 3), # class speed (1/mean work)
nrow = 3, byrow = TRUE)
build_model(lambda, N, classes, f, P_a, P_d)
m =return(m$mean_clients)
}
create_connection("http://server.local/", "rboinc_alpha", "submitter@example.com", "ahth3Eze")
con = create_jobs(con, fun, n = 1, packages = c("remotes", "MJMrss"),
status =install_func = inst)
$results[[1]] status
## [1] 0.3833206
Обратите внимание на то, что вам не обязательно поддерживать соединение активным всё время вычислений. Вместо этого вы можете подключиться к серверу, создать задания, отключиться, а спустя некоторое время подключиться, чтобы забрать результат. Более того, сессию R также не обязательно поддерживать активной. Т.е. вы можете сделать что-то вроде этого:
... create_connection(...)
con = create_jobs(...)
status =close_connection(con)
save(status, file = "jobs.rda")
quit()
А спустя неделю:
create_connection(...)
con =load("jobs.rda")
update_jobs_status(con, status)
status =close_connection(con)
...
Это функция для отмены пачки заданий. В качестве первого параметра ей должно быть передано открытое заранее функцией create_connection соединение. В качестве второго - параметра передаётся статус пачки работы, который возвращается функциями create_jobs или update_jobs_status. Немотря на то, что второй параметр ссылка, правильный вызов этой функции такой:
cancel_jobs(con, status) status =
Не передавайте объект status или его копию другим функциям после вызова этой функции.