Или как написать задачу для использования с “Opal”.
Или как написать собственную модель для использования с Opal.
\section{Краткий обзор архитектуры “Opal”}
Здесь будет дан обзор архитектуры системы “Opal”, необходимый для того, чтобы подключить свою задачу для использования с системой.
\section{Краткий обзор архитектуры Opal}
Система Opal представлена в двух редакциях: локальная (local) и сетевая (net). В локальной редакции приложение сервера совмещено с клиентским приложением графического интерфейса. Оба варианта могут работать на одной машине, но только сетевой вариант способен работать раздельно с графическим интерфейсом. При таком подходе появляется больше свободы в управлении для сервера и пропадает нагрузка на клиентский компьютер (если, конечно, части разнесены по разным машинам в сети).
Система “Opal” представлена в двух редакциях: локальная (local) и сетевая (net). В локальной редакции приложения сервера совмещено с клиентским приложением графического интерфейса. Оба варианта могут работать на одной машине, но только сетевой вариант способен работать раздельно с графическим интерфейсом. При таком подходе появляется больше свободы в управлении для сервера и пропадает нагрузка на клиентский компьютер (если, конечно, части разнесены по разным машинам в сети).
Локальная редакция, серверная часть совмещена с графической:
Сетевая редакция (сплошными линиями показаны фактические связи между компонентами, пунктирными — логические):
@ -14,50 +14,51 @@
\section{Подключение своего приложения}
Вне зависимости от того какой редакцией вы решили воспользоваться, все задачи находятся на той же машине, что и сервер. Сервер связывается с задачами через стандартные потоки ввода-вывода. Это сделано для того, чтобы упростить разработку пользовательских задач. Можно было бы, конечно, использовать подключения по протоколу TCP/IP, но это бы только вызвало лишние сложности.
Итак, в любом случае стандартная директория, где сервер в первую очередь ищет созданные для него задачи — это директория tasks, которая находится в корне с установленным проектом:
Вне зависимости от того какую редакцию использовать, все задачи находятся на той же машине, что и сервер. Сервер связывается с задачами через стандартные потоки ввода-вывода (stdin и stdout). Это сделано для того, чтобы упростить разработку пользовательских задач. Можно было бы, конечно, использовать подключения по протоколу TCP\slash{}IP, но это бы только вызвало лишние сложности, так как работа с этим сетевым протоколом зависит от операционной системы, и в многих языках программирования не содержится нужных библиотек в стандартной поставке.
%Opal-dir%/tasks,
Рассмотрим все этапы подготовки своего приложения и подключения его к системе Опал на основе данного в первой части условия задачи (стр. \pageref{testtask}). Исполняемый файл задачи будет называться testt.exe.
где %Opal-dir% — путь, куда установлена система “Opal”.
При старте сервер опрашивает задачи, путь к которым указан в специальном файле
\begin{verbatim}
tasks.conf
\end{verbatim}
Путь может быть как абсолютным, так и относительным. При использовании относительного пути следует помнить, что он вычисляется относительно исполняемого файла ссервера Opal.
Рассмотрим все этапы подготовки и подключения своего приложения к системе Опал на основе данного в первой части условия задачи о путешественнике Василии. Исполняемый файл задачи будет называться reltask.exe.
Таким образом, задача может располагаться где угодно в системе. Это полезно, потому что при работе задачи могут создавать или использовать дополнительные файлы, и чтобы не было путаницы каждую задачу лучше поместить в отдельную директорию.
Чтобы добавить свой проект в систему, достаточно скопировать исполняемый файл в эту директорию. (reltask.exe входит в стандартный комплект всех редакций Опал, найти его можно в директории с задачами.) После чего система опросит его, запустив с ключом -i:
Чтобы добавить свой проект в систему, достаточно добавить в конец файла tasks.conf путь к исполняемому файлу задачи.
reltask.exe -i,
а при работе будет запускать с ключом -r:
reltask.exe -r,
так что исполняемый файл должен адекватно реагировать как минимум на эти два ключа. В то же время не рекомендуется использование каких либо других ключей запуска, а все настройки передавать через сообщения по протоколу “Opal”.
Во время опроса система запускает задачу с ключом \emph{-i}:
\begin{verbatim}
reltask.exe -i
\end{verbatim}
а при работе будет запускать с ключом \emph{-r}:
\begin{verbatim}
reltask.exe -r
\end{verbatim}
так что исполняемый файл должен адекватно реагировать как минимум на эти два ключа. В то же время не рекомендуется использование каких либо других ключей запуска, а все настройки передавать через сообщения по протоколу Opal. В то же время такой подход удобен тем, что при старте без ключей (к примеру) может запускаться обычное отдельное приложение с оконным интерфейсом, а значит задача будет вести себя двояко: и как приложение для исползования с системой Opal, так и для использования как независимая программа.
\section{Ключи запуска}
Ключ -i нужен для того, чтобы сервер получил полные сведения о задаче. Эти сведения спрашиваются один раз (как опросить задачу вновь) и используются в дальнейшем при построении таблицы параметров на графическом клиенте, проверке целостности данных, в качестве рекомендаций при управлении очередью задач.
Важными секциями, которые обязательно должны быть описаны являются tasks и results. Про секции подробнее будет рассказано в следующем разделе.
Если запуск с ключом -i не принес успеха, сервер попробует запустить приложение так же с ключом --info. Если и это не принесет успеха, задача не будет добавлена в список обслуживаемых.
Ключ \emph{-i} нужен для того, чтобы сервер получил полные сведения о задаче. Эти сведения спрашиваются один раз и используются в дальнейшем при построении таблицы параметров на графическом клиенте, проверке целостности данных, в качестве рекомендаций при управлении очередью задач.
После того как параметры опрошены, задача будет готова к запуску сервером. Когда клиент сделает запрос на запуск задачи, сервер запустит еес ключом -r и через стандартный поток ввода передаст полученные от клиента параметры старта задачи (параметры задачи, модели и метода) и будет ожидать результата вычислений в виде таблицы данных.
Как и при получении информации о задаче, неудача запуска с ключом -r побудит сервер сделать попытку запуска с ключом --run. При повторной неудаче, задача будет помечена как не обслуживаемая.
\section{Протокол Opal}
\section{Протокол “Opal”}
Чтобы понять как происходит обмен командами между клиентскими задачами и серверным процессом, нужно сперва описать соответствующий этому протокол.
Чтобы понять как происходит обмен командами между клиентскими задачами и серверным процессом, нужно описать соответствующий этому протокол.
Протокол обмена данными между всеми частями системы Opal базируется на формате обмена данными JSON (JavaScript Object Notation). Формат очень простой и построен на основе словарей и списков. Эти структуры данных если в большинстве современных языков программирования, а в некоторых JSON даже поддерживается стандартной библиотекой (к примеру, Python или JavaScript). Кроме того формат удобен для чтения и изменения человеком, что явилось немаловажным фактором при его выборе. Список всех полей можно прочитать в приложении №.
Протокол обмена данными между всеми частями системы “Opal” базируется на формате обмена данными JSON (JavaScript Object Notation, json.org). Формат очень простой и построен на основе словарей и списков. Эти структуры данных если в большинстве современных языков программирования, а в некоторых JSON даже поддерживается стандартной библиотекой (к примеру, Python или JavaScript). Кроме того формат удобен для чтения и изменения человеком, что явилось немаловажным фактором при его выборе. Список всех полей можно прочитать в приложении №.
\section{Коммуникация сежду сервером и задачей}
\section{Запросы и ответы}
Запустив задачу и передав ей параметры, сервер переходит в режим прослушивания. В этом режиме сервер больше не передает никаких сообщений задаче, а только принимает сообщения от нее и обрабатывает их. Сообщения содержат в себе информацию о ходе выполнения задачи, каких-либо комментариях, предупреждениях или ошибках, которые могли возникнуть во время выполнения, а также о результатах выполнения.
Как и в любом клиент-серверном приложении клиент посылает запросы серверу, а сервер на них отвечает. Система “Opal” использует тот же принцип, но нужно учитывать, что тут иерархия немного другая. Графический клиент посылает запросы серверу, а сервер, в свою очередь, посылает запросы задачам. В любом случае каждый запрос должен иметь поле запроса, по котороу и будет определено, что же требуется выполнить. Текст запроса в общем случае выглядит так:
{ “request”: “%request-text%” },
где %request-text% — одна из препопределенных команд. Список таких команд представлен ниже:
Как и в любом клиент-серверном приложении клиент посылает запросы серверу, а сервер на них отвечает. Система Opal использует тот же принцип. Графический клиент посылает запросы серверу, а сервер, в свою очередь, посылает запросы задачам. В любом случае каждый запрос должен иметь поле запроса, по котороу и будет определено, что же требуется выполнить. Текст запроса в общем случае выглядит так:
\begin{verbatim}
{ "request": %request-text%}
\end{verbatim}
где \%request-text\% — одна из препопределенных команд. Список таких команд представлен ниже:
todo заполнить полностью
@ -71,10 +72,11 @@ get-task-list
Узнать статус выполнения задачи.
В ответ на запрос сервер высылает сообщение, в котором должно присутствовать поле answer:
{ “answer”: “%answer-text%” },
где %answer-text% — один из предопределенных ответов. Ответы характеризуют результат выполнения запроса и они должны быть сигналами для дальнейшей обработки.
\begin{verbatim}
{ "answer": %answer-text%}
\end{verbatim}
где \%answer-text\% --- один из предопределенных ответов. Ответы характеризуют результат выполнения запроса и они должны быть сигналами для дальнейшей обработки.
ok
Все прошло хорошо, можно анализировать возвращенные значения.
warning
@ -85,38 +87,40 @@ ok
Кроме поля answer в ответе могут присутствовать поля code, value и comment. Эти поля используются для получения дополнительной информации. Так поле code может содержать числовой код ошибки, которая вызвала отказ или ошибку, поле value используется для возвращения некоторого значения (прогресс выполнения, список доступных задач и др.), comment сожержит произвольную текстовую строку, которая должна просто прояснить ситуацию где необходимо.
Пример запроса и ответа. В качестве простого запроса-ответа можно привести запрос статуса выполнения задачи. Графический клиент посылает серверу запрос
{
“request”: “get-status”,
“uid”: %user-id%,
“tid”: %task-id%
\begin{verbatim}
{
"request": "get-status",
"uid": %user-id%,
"tid": %task-id%
}
где %user-id% и %task-id% обозначают уникальные идентификаторы клиента и задачи соответственно. О том, как они формируются и какую роль играют можно прочитать в последней части данного сочинения.
\end{verbatim}
где \%user-id\% и \%task-id\% обозначают уникальные идентификаторы клиента и задачи соответственно. О том, как они формируются и какую роль играют можно прочитать в последней части данного сочинения.
Сервер на основе uid и tid перенаправляет запрос нужной задаче, если, конечно, в данный момент она способна его обработать. После чего высылает клиенту результат разпроса (к примеру):
{
“answer”: “ok”,
“code”: 0,
“value”: 0.6321,
“comment”: “in progress, all right”
}
\begin{verbatim}
{
"answer": "ok",
"code": 0,
"value": 0.6321,
"comment": "in progress, all right"
}
\end{verbatim}
Если, например, клиент ошибся в tid или пытается получить доступ к чужой задаче, сервер даст ответ
{
“answer”: “error”,
“comment”: “access denied”
}
\begin{verbatim}
{
"answer": "error",
"comment": "access denied"
}
\end{verbatim}
\section{Описание данных}
Перед тем как перейти к описанию структуры, описывающей зачаду и ее модели, следует рассказать о типах данных, которые применяются в “Opal”. Далее будем говорить только о тех полях, которые встречаются в задачах, моделях и методах.
Перед тем как перейти к описанию структуры, описывающей зачаду и ее модели, следует рассказать о типах данных, которые применяются в Opal. Далее будем говорить только о тех полях, которые встречаются в задачах, моделях и методах.
Тип --- очень важная часть описания данных. Хотя без него можно было бы обойтись, предоставив пользователям полный контроль над передаваемыми данными, этого лучше избежать. Все дело в том, что тип является мощным инструментом контроля целостности передаваемых данных, не позволяя использовать, скажем, строку там, где должно быть целое число.
Подробнее о том как происходит разбор данного выражения можно прочитать в приложении №, подробные примеры есть в последнем пункте этой главы.
@ -171,20 +175,21 @@ float
Строковый тип служит для хранения символов. Отсутствует разделение на строки и символы как во многих языках программирования. Это сделано для того, чтобы упростить работу с данными. Строки можно использовать в качестве комментариев. В качестве нетривиального примера можно привести использование строк вместе с оператором выбора в качестве перечисляемого типа (аналог enum):
string choice [‘a’, ‘b’, ‘c’]
При записи строк в определении поля данных нужно быть внимательным с использованием кавычек. Кавычки внутри описания поля нужно экранировать символом обратного слеша \\. Лучше это будет видно на примере:
%“field”: “string default \”foobar\””
%"field": "string default \"foobar\""
Чтобы не запутаться надо просто помнить, что вы описываете строку в строке.
Присутствует в JSON.
\subsection{Тип момента времени \emph{time}}
Значения: от "1000-01-01 00:00:00" до "9999-12-31 23:59:59"
Является аналогом типа данных timestamp. При описании значений этого типа можно описывать как все поле целиком, так и использовать его части отдельно (время и дату). Отсутствующая часть будет заменена значением по умолчанию. Примеры:
“12:10:00”
“2012-01-01”
“2012-02-03 13:23:43”
"12:10:00"
"2012-01-01"
"2012-02-03 13:23:43"
Не поддерживается JSON, представляется как строка.
\subsection{Списочный тип}
list
\subsection{Списочный тип\emph{list}}
Списочный тип является единственным составным типом (все остальные типы — скалярные). Это значит, что при описании поля этого типа нужно указать элементы какого скалярного типа содержатся в списке. Синтаксис объявления:
list (scalar)
Сразу хочется отметить, что вложенные списки не поддерживаются.
Разбиение (диапазон) — нестандартный тип данных. Он основан на идее разбиения отрезка на одинаковые части. Это бывает очень полезно когда надо перебрать значения из некоторого диапазона с некоторым шагом.
Синтаксис записи значения типа выглядит следующим образом:
[[begin,] end [, step]]
Если указан только параметр end, то считается, что диапазон начинается с 0. Шаг по умолчанию равен 1. Если вы используете этот тип данных в описании своих полей, следует определить соответствующую его обработку. Опущенные значения будут подставлены графическим приложением (если это поддерживается).
Не поддерживается JSON, представляется как список из трех значений.
\subsection{Тип периода}
period
Еще один нестандартный тип данных. Период времени состоит из двух дат: время начала и время конца. Очень часто встает потребность описать некоторый промежуток времени на котором идет решение модели. Описание значений этого типа происходит аналогично описанию значений списка и момента времени. Пример:
[“1999-01-01”, “2000-01-01”]
Так как путешествия назад во времени еще не придуманы, то дата начала должна быть раньше даты конца во избежание казусов.
Не поддерживается JSON, представляется как список из двух значений типа time.
\subsection{Выбор из нескольких вариантовsection}
Оператор сhoice предназначен для выбора одного варианта из предложенного списка. В записи определения поля после него должен идти список возможных значений:
choice [val1, val2, … ]
Примеры:
“field”: “int choice [1, 2, 3, 4]”
“field”: “string choice [‘a’, ‘b’, ‘c’]”
"field": "int choice [1, 2, 3, 4]"
"field": "string choice [‘a’, ‘b’, ‘c’]"
Каждое значение в списке должно соответствовать указанному типу поля. Не рекомендуется использовать оператор выбора вместе со списковым типом, хотя это и не возброняется.
Если в списке присутствует только одно значение или список пуст, то фактически выбор становится фиксированным. Не лишайте пользователей выбора.
\subsection{Значение по умолчанию}
Оператор default осуществляет подстановку значения по умолчанию в графическом клиенте. Нельзя переоценить важность этого оператора, так как значение по умолчанию помогает пользователям сориентироваться для выбора нужного им значения. Параметр по умолчанию — это некий средний параметр, который подходит в большинстве случаев и (или) рекомендуется к использованию. Золотое правило работы с такими параметрами: “Если не знаешь что делает этот параметр, используй значение по умолчанию”.
Оператор default осуществляет подстановку значения по умолчанию в графическом клиенте. Нельзя переоценить важность этого оператора, так как значение по умолчанию помогает пользователям сориентироваться для выбора нужного им значения. Параметр по умолчанию — это некий средний параметр, который подходит в большинстве случаев и (или) рекомендуется к использованию. Золотое правило работы с такими параметрами: "Если не знаешь что делает этот параметр, используй значение по умолчанию".
Если вместе с оператором default используется оператор choice, то значение по умолчанию должно входить в список вариантов.
Синтаксис:
default value
Примеры:
“field”: “int default 10”
“field”: “int choice [1, 2, 3] default 2”
“field”: “string choice [‘foo’, ‘bar’, ‘foobar’]
default ‘foobar’”
"field": "int default 10"
"field": "int choice [1, 2, 3] default 2"
"field": "string choice [‘foo’, ‘bar’, ‘foobar’]
default ‘foobar’"
\subsection{Заголовок поля}
Оператор title отвечает за заголовок поля. Текст, указанный как заголовок будет использоваться в графическом интерфейсе в качестве названия поля. Если заголовок не указан, будет использоваться имя поля.
Имя поля не всегда позволяет описать поле так, как хочется. Имя хочется сделать простым, коротким и понятным программисту, в то время как заголовок должен быть полезен пользователю.
@ -246,6 +244,7 @@ default ‘foobar’”
Комментарии используются для того, чтобы пояснить для чего нужно описываемое поле. В графическом интерфейсе они проявляются как всплывающие подсказки. Чем яснее будет сформулированы комментарии, тем проще будет пользователю разобраться какие параметры и как нужно описывать для данной задачи.
\section{Задачи, модели и методы}
Покажем возможную структуру описания информационного сообщения и сообщения с параметрами, которое будет передано задаче, на примере, который был описан в первой главе про младшего сотрудника Василия.
Составление информационного сообщения — очень важный процесс при проектировании задачи. От того как описана задача напрямую зависит то, насколько удобно ей будет пользоваться и насколько гибко ей можно управлять.
Перед тем как будет подробно описана работа с моделями в программе “Opal”, необходимо установить используемую терминологию, чтобы случайно не ввести в заблуждение.
Перед тем как будет подробно описана работа с моделями в программе Opal, необходимо установить используемую терминологию, чтобы случайно не ввести в заблуждение.
Для полного описания задачи оптимизации используется несколько основных понятий.
Задача — отдельное приложение, которое может быть подключено к “Opal”. Приложение может быть как отдельным исполняемым файлом, так и некоторым скриптом на интерпретируемом языке. Для работы важно, чтобы приложение могло взаимодействовать на уровне стандартных потоков ввода-вывода для того, чтобы ему можно было передавать запросы. Термин “задача” используется в силу того, что этот объект очень схож в теми задачами (процессами, tasks), которые используются в операционных системах.
Задача — отдельное приложение, которое может быть подключено к Opal. Приложение может быть как отдельным исполняемым файлом, так и некоторым скриптом на интерпретируемом языке. Для работы важно, чтобы приложение могло взаимодействовать на уровне стандартных потоков ввода-вывода для того, чтобы ему можно было передавать запросы. Термин <<задача>> используется в силу того, что этот объект очень схож в теми задачами (процессами, tasks), которые используются в операционных системах.
Модель — набор параметров, которые описывают условия для задачи нахождения оптимального управления. Фактически, моделью является набор значений, как то период времени, на котором происходит моделирование ситуации, начальные условия, параметры для функции перехода, ограничения на управление и др.
@ -24,7 +24,7 @@
\section{Работа с моделями}
Модель — основная сущность для “Opal”. Создав единожды, модель можно отредактировать позже, присоединить или отсоединить от нее методы, создать копию (как поверхностную — только сама модель, — так и полную, включая все методы).
Модель — основная сущность для Opal. Создав единожды, модель можно отредактировать позже, присоединить или отсоединить от нее методы, создать копию (как поверхностную — только сама модель, — так и полную, включая все методы).
По сравнению с подходом, когда одно приложение решает одну задачу, концепция моделей имеет большое преимущество. Можно настроить несколько моделей на выполнение или выбрать несколько вариантов одной модели, после чего запустить их, а потом, дождавшись результата, проанализировать. Не приходится отвлекаться для того, чтобы запустить новую задачу, после того, как прошлая закончила свою работу.
Для того, чтобы лучше всего разобраться в том, что представляет из себя модель, можно рассмотреть простой пример.
@ -40,7 +40,7 @@
4. затопить печь (-6 к энергии, +5 градусов в доме).
Каждый час температура в доме падает на один градус. Если температура опустится ниже отметки в 15 градусов, то дополнительно будет тратится 2 ед. энергии, потому как Вася будет пытаться согреться. Энергии не может быть больше 100 пунктов.
По ходу дальнейшего описания работы с моделями в системе “Opal”, будем обращаться к этому примеру для наглядного представления обсуждаемой темы.
По ходу дальнейшего описания работы с моделями в системе Opal, будем обращаться к этому примеру для наглядного представления обсуждаемой темы.
О проникновении в нашу жизнь облачных технологий и возврату к терминальному режиму на современном уровне.
% Про актуальность решения задач оптимизации,
% какие классы проблем могут они решать, почему решение порой
% требует очень больших вычислительных ресурсов.
Человечество не стоит не месте. Каждый год появляются новые технологии, проводится ножество исследований, появляются новые технологии и материалы. До своего появления на свет новый продукт проходит множество стадий: идея, исследование, опытный образец, многочисленные проверки и, наконец, выпуск в свет.
С появлением компьютеров стадия исследования кардинально изменилась. Теперь не нужно создавать опытный образец, чтобы провести на нем тесты, не нужно вести огромное количество рассчетов на бумаге --- все это заменили компьютеры.
Но, тем не менее, почти все непрерывные процессы мы можем представить в качестве дискретных. Струю воды можно считать по каплям, а путь автомобиля по пройденным сантиметрам. Такой подход несколько неудобен для человека, который привык к плавности движений и форм. Однако, для вычислительных машин нет ничего лучше! Компьютеры все меряют отдельными значениями. Но если маленьких отдельных значений очень много, то они становятся похожи на непрерывный набор данных, поэтому, в сущности, какая в конечном счете разница что использовать: непрерывные функции или их дискретные аналоги?
\section{Обзор “Opal”}
\section{Обзор Opal}
Для рассмотренных ранее задач методов оптимизации в большинстве случаев используются алгоритмы, которые путем полного перебора всех возможных значений на заданной области определения находят нужное оптимальное решение.
Такие алгоритмы отличаются высоким потреблением системных ресурсов: процессорного времени и оперативной памяти компьютера. Яркий пример~--- метод Беллмана. Время расчета сего использованием может достигать от нескольких минут до нескольких дней, а объем памяти для хранения данных доходит до гигабайт.
Традиционным подходом при компьютерном решении является отдельное приложение, нацеленное на решение конкретной задачи. Приложение содержит в себе сам алгоритм, строит графики, выводит отчеты о работе. Даже с использованием уже написанных библиотек, каждое такое приложение невольно становится “изобретением велосипеда”, потому как программисту приходится заново выстраивать все компоненты (ввод-вывод параметров, проверка значений, вычислительное ядро, построение графиков и вывод отчетов) в единое целое.
Традиционным подходом при компьютерном решении является отдельное приложение, нацеленное на решение конкретной задачи. Приложение содержит в себе сам алгоритм, строит графики, выводит отчеты о работе. Даже с использованием уже написанных библиотек, каждое такое приложение невольно становится <<изобретением велосипеда>>, потому как программисту приходится заново выстраивать все компоненты (ввод-вывод параметров, проверка значений, вычислительное ядро, построение графиков и вывод отчетов) в единое целое.
Безусловно такой подход к решению нельзя назвать оптимальным. Время, затраченное на те части, которые не являются кодом самого алгоритма и которые в разных вариациях повторяются из программы в программу, довольно значительно, и иногда составляет больше времени кодирования самого алгоритма.
Назревает вопрос: если в большинстве программ разными являются только сами вычислительные алгоритмы, а все остальное присутствует в почти в неизменном виде, может эти две сущности следует разделить? Разделив, получим универсальный интерфейс, уже готовые подсистемы ввода-вывода, графиков, отчетов, таблиц. Также появится возможность запускать алгоритмы не только локально, но и где-то на другой машине, имея к ним доступ по сети или даже из web.
Выглядит многообещающе, не так ли? Запустить “прожорливый” алгоритм на удаленной машине, а потом только получить результаты работы и проанализировать их.
Выглядит многообещающе, не так ли? Запустить <<прожорливый>> алгоритм на удаленной машине, а потом только получить результаты работы и проанализировать их.
Именно этих целей и придерживается в своей философии проект “Opal”:
Именно этих целей и придерживается в своей философии проект Opal:
\begin{enumerate}
\item разделение вычислительного ядра и управляющих интерфейсов, когда “мухи отдельно, котлеты отдельно”;
\item разделение вычислительного ядра и управляющих интерфейсов, когда <<мухи отдельно, котлеты отдельно>>;
\item удобство использования, когда программисту достаточно только реализовать алгоритм, а математику с легкостью им воспользоваться;
\item универсальность представлений, когда управление задачей не зависит от задачи;
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.