На Хабре уже проскакивали упоминания о совместимых или систем-копиях Wolfram Mathematica, но реализованных на других языках, как, скажем, Mathics. Автор статьи @ITSummaупомянул в самом начале
На Mathics такое не получится, как и многие другие примеры из этого списка тоже не сработают. Вообще, для Wolfram Language (WL) практически невозможно создать полноценный интерпретатор с открытым исходным кодом, потому что многие встроенные решатели являются проприетарной собственностью компании Вольфрама. Однако попытаться можно.
Сложно поспорить с этим утверждением, однако, возможен компромиссный вариант, позволяющий использовать все те же "решатели", но с немного иной open-source оберткой снаружи. В качестве ответа я представляю систему, которая не только воспроизводит многие ключевые функции блокнота Mathematica с нуля, но и расширяет функционал гораздо дальше, чем там, где очертил его границы Стивен Вольфрам, создав эту потрясающую систему более 30-ти лет назад.
::: Это не готовый продукт и не замена Wolfram Mathematica
Вставка для привлечения внимания
Здесь мне потребовалось завлечь пользователей этим замечательным корабликом. То, на чем он написан, - это ни что иное как Wolfram Language и то, где он исполняется здесь и сейчас, - Ваш браузер (для тех, кто кликнул на картинку).
документация (наполняется)
Но я обманул Вас, это не open-source блокнот, а нечто другое, но не менее важное, о чем Вы узнаете позже в секциях ниже. Начнем с привычных разделов...
Блокнот? Швейцарский нож
Код, иллюстрации, data-science, презентации - все сегодня возможно написать в пределах скевоморфиозного вида интерфейсов - блокнота
Ячейки разного типа это безусловно преимущество, особенно это касается типа Markdown, когда его "привезут" в Wolfram Mathematica - неизвестно.
Про презентации мы еще поговорим позже.
Бесплатный сыр
Важно отделять Wolfram Language от того, что его реализует - Wolfram Mathematica. Однако это также не совсем верно, так как Wolfram Mathematica это язык и интерфейс к нему (фронтенд), которые, вероятно, соразмерны друг с другом.
Свободная реализация языка со стандартными библиотеками уже давно доступна - это Wolfram Engine, который подобно Питону можно подключить в качестве скриптового языка к чему-угодно.
Отличия интерфейса блокнотов Mathematica от других
Некоторые из вас могут посчитать следующие пункты полезными или бесполезными конкретно для вашей работы или подхода к программированию, однако, нельзя опускать сам факт их реализации - это технически и концептуально сложная задача, которая была великолепно решена. Такое нельзя найти в Jupyter, Obsevable (d3-express), VSCode Notebook API.
Синтаксический сахар
Возведем идею формочек с цветом, которые многие видят в Visual Studio Code, редактируя какие-нибудь CSS цвета
в бесконечность и получим
Graphics3D[Sphere[]]
% /. Sphere -> Cuboid
Здесь основная идея состоит в том, что сам график с точки зрения среды - это набор выражений и символов. Когда он рисуется на экране - это все еще тот же набор символов и выражений, с которым можно взаимодействовать. А трехмерный куб - это просто одна из возможных интерпретаций.
Выходные ячейки - редактируемы
Я не знаю почему, но почти все блокнотные интерфейсы просто игнорируют эту опцию
Мы получили результат - это тоже выражение. Почему бы не использовать его в последующих вычислениях?
Возможно, языков программирования, которые могли бы воспользоваться этим на благо просто мало.
Двумерный математический ввод
Здесь я явно предвзят, так как являюсь физиком-теоретиком. Что вам нравится больше?
1/Sqrt[2]
В редакторе Хабра это сложно показать, но возможность миксовать код и математические выражения подобные тем, что в LaTeX, - это потрясающе. Представьте, если обобщить это, писать код, вставлять изображения или другие интерактивные объекты в то время, как редактор будет это видеть и обрабатывать как все тот же код. Очевидно, это работа для регулярных выражений.
Зачем изобретать велосипед с открытым исходным кодом
Очевидный вопрос, ведь рынок уже удовлетворен тем, что создает WRI. Время привести недостатки
-
Wolfram Mathematica
Проприетарный формат/среда, который/ая стоит дорогоТяжелый интерфейс (в плане отзывчивости), нестабильный UI (краш, фриз это обычное дело)
Клиент и среда связаны, сложно вести удаленную сессию с телефона/тостера
Нельзя делиться блокнотами с поддержкой интерактивности
Кривой экспорт в PDF и только статические графики/изображения
Нельзя встроить блокнот на сайт/блог
-
Wolfram Cloud
Тяжелый и тормозной фронтенд, браузер задыхается при рендере даже текстовых ячеек. Нельзя вставить более 3-5 ячеек внутрь блога/сайта как iframe.
Строгая политика к облачным файлам: либо подписка, либо удаление, если отсутствует активность
Ограниченный функционал графики и отображения выражений
Работает исключительно при наличии интернета
Превратится в тыкву при желании WRI (Wolfram Research Inc)
Неужели нельзя сделать все "хорошо". Взглянем на Jupyter Notebook, к примеру. Там нет этих недостатков, весь блокнот уместится в единый HTML файл
Эта портативность и легкость заразительна. Взглянем на Observable, где великолепно решены проблемы с интерактивностью и динамикой, чего очень не хватает в Jupyter
Интересная особенность Observable - любая переменная считается динамической. Это все равно, что если бы в Wolfram Mathematica весь блокнот был внутриDynamicModule
.
Тернистый путь разработки экосистемы
Здесь могла быть просто демонстрация готового проекта. Но вряд ли кто-то поспорит: Хабр - торт, когда можно чему-то научиться после прочтения текста или узнать что-то новое.
Чтобы решить проблему портативности и совместимости, нет никакого другого варианта, как использовать веб-браузер, который гарантирует, что все будет работать предсказуемо на любой платформе или системе.
WebGUI к консоли
У нас есть Wolfram Engine - это консольное приложение, поддерживающее stdin
/stdout
и ничего более, за исключением библиотек работы с файлами, сокетами и парочкой инструментов для OpenCL и CUDA вычислений. Пример Jupyter показал, что HTTP сервер с WebSockets протоколом для быстрого обмена TCP-подобными сообщениями с клиентским приложением работает круто. Есть ли HTTP сервер для Wolfram Language?..
Нет, но его можно всегда написать. Эта героическая задача была решена с нуля @KirillBelovTest. Можете почитать здесь. Причем не только про сервер, но и про высокоскоростной интерфейс сокетов (sockets), написанный им же с нуля на чистом Си для поддержания кроссплатформерности.
Таким образом, задача по прикручиванию веб-интерфеса складывается из достаточно типичных для современного веба блоков
В качестве шаблонизатора я написал WSP (Wolfram Script Pages) как PHP-подобный язык, только для Wolfram Language. Но сейчас он был замещен его наследником WLX (Wolfram Language XML), вдохновленным синтаксисом JSX.
Пример, как это может выглядеть
(* package manager to make sure you will get the right version *)
PacletInstall["JerryI/LPM"];
<< JerryI`LPM`
PacletRepositories[{
Github -> "https://github.com/KirillBelovTest/Objects",
Github -> "https://github.com/KirillBelovTest/Internal",
Github -> "https://github.com/JerryI/CSocketListener",
Github -> "https://github.com/KirillBelovTest/TCPServer",
Github -> "https://github.com/KirillBelovTest/HTTPHandler",
Github -> "https://github.com/KirillBelovTest/WebSocketHandler",
Github -> "https://github.com/JerryI/wl-misc",
Github -> "https://github.com/JerryI/wl-wlx"
}]
(* packages for HTTP server *)
<<KirillBelov`Objects`
<<KirillBelov`Internal`
<<KirillBelov`CSockets`
<<KirillBelov`TCPServer`
<<KirillBelov`HTTPHandler`
<<KirillBelov`HTTPHandler`Extensions`
(* WLX scripts *)
<<JerryI`WLX`
<<JerryI`WLX`Importer`
<<JerryI`WLX`WLJS`
(* setting the directory of the project *)
SetDirectory[If[StringQ[NotebookDirectory[]], NotebookDirectory[], DirectoryName[$InputFileName]]]
Print["Staring HTTP server..."];
tcp = TCPServer[];
tcp["CompleteHandler", "HTTP"] = HTTPPacketQ -> HTTPPacketLength;
tcp["MessageHandler", "HTTP"] = HTTPPacketQ -> http;
(* main app file *)
index := ImportComponent["index.wlx"];
http = HTTPHandler[];
http["MessageHandler", "Index"] = AssocMatchQ[<|"Method" -> "GET"|>] -> Function[x, index[x]]
SocketListen[CSocketOpen["127.0.0.1:8010"], tcp@# &];
StringTemplate["open http://``:``/"][httplistener[[1]]["Host"], httplistener[[1]]["Port"]] // Print;
While[True, Pause[1]];
где в директории, откуда вы запускаете скрипт, находятся два файла
index.wlx
Main = ImportComponent["main.wlx"];
<Main Request={$FirstChild}/>
А также файл с самим "приложением"
main.wlx
(* /* HTML Page */ *)
<html>
<head>
<title>WLX Template</title>
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet"/>
</head>
<body>
<div class="min-h-full">
<header class="bg-white shadow">
<div class="flex items-center mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8">
<h1 class="text-3xl font-bold tracking-tight text-gray-900">Title</h1>
</div>
</header>
<main>
<div class="mx-auto max-w-7xl py-6 sm:px-6 lg:px-8">
Local time <TextString><Now/></TextString>
</div>
</main>
</div>
</body>
</html>
Зайдя на страницу в браузере 127.0.0.1:8010, можно будет увидеть следующее
Как видно, все страницы представляют собой обычные HTML-документы с расширенным синтаксисом, таким образом, что теги, начинающиеся с заглавной буквы, считаются выражениями Wolfram Language
<TextString><Now/></TextString>
Формируя страницы из компонент, можно писать своего рода "веб-приложения". Далее я не буду вдаваться в подробности этого подхода, так как объем материала тянет на отдельную публикацию.
Интерпретатор языка Wolfram в браузере
Зачем? Он же уже есть!
Вернемся к простой задаче, как показать график из консоли, если кто-то не заплатил 300$ WRI. Откроем терминал и введем wolframscript
, затем
Plot[x, {x,0,1}]
и увидим следующее
- Graphics -
На самом деле можно вытащить больше информации, применив
ExportString[Plot[x, {x,0,1}], "ExpressionJSON"]
[
"Graphics",
[
"Line",
[
"List",
[
"List",
2.040816326530612e-8,
2.040816326530612e-8
],
[
"List",
3.0671792055962676e-4,
3.0671792055962676e-4
],
Это ни что иное, как "рецепт" приготовления этого блюда. Остается найти повара, точнее написать. Как и на чем? Кажется очевидным, исходя из факта того, что у нас будет WebGUI
//набор будущих функций
const core = {};
//интерпретатор
const interpretate = (expr, env = {}) => {
if (typeof expr === 'string') return expr; //строка
if (typeof expr === 'number') return expr; //число
//значит это выражение WL
const args = expr.slice(1);
return core[expr[0]](args, env);
}
Окей, теперь давайте объявим выражение List
. Я думаю, следующее будет ясным без дополнительных разъяснений
//async это круто!
core.List = async (args, env) => {
const list = [];
const copy = {...env};
for (const i of args) {
//запишем результат интерпретации списка или массива WL в массив list
//env передается как глубокая копия, для того, чтобы изменения ее внутри не влияли на обзекты снаружи списка
list.push(await interpretate(i, copy));
}
return list;
}
Почему так сложно? Покажу пример использования List
Graphics[{Red, Point[{-0.5,0}], {Green, Point[{0,0}]}, Point[{0.5, 0}]}]
Здесь видно, что {}
или по-другому List[]
изолирует "shared" параметры среды внутри от других листов, которые не являются вложенными. По этой причине в версии JS мы копируем переменную env
, которая будет хранить такие опции, как цвет, толщина, да и все что угодно.
Остается реализовать Line
, RGBColor
, саму функцию Graphics
и мы уже можем строить графики. Полный код приведен здесь, однако я приведу пример на псевдо-языке, как это может выглядеть
core.Line = async (args, env) => {
const data = await interpretate(args, env);
env.canvas.putLine(data, env.color);
return null;
}
core.RGBColor = async (args, env) => {
const color = await interpretate(args, env);
env.color = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
}
В целом, нужно также рассмотреть ситуации, когда вложенные выражения меняются со временем и бегать по соответствующей ветке древа для того, чтобы пересчитать положение линий и т.д., например, для таких случаев
Важно заметить, что ядро Wolfram здесь никак не вовлекается, разве что для непосредственного семплирования функции, находящейся в Plot
. Живую демонстрацию того, что может этот интерпретатор, если наполнить его всеми необходимыми примитивами, можно увидеть на странице с документацией.
Добавив еще пару функций и библиотеку THREE.js, получается делать такие картинки
VectorPlot3D[{x, y, z}, {x, -1, 1}, {y, -1, 1}, {z, -1, 1}, VectorColorFunction -> Function[{x, y, z, vx, vy, vz, n}, ColorData["ThermometerColors"][x]]][[1]];
%/. {RGBColor[r_,g_,b_] :> Sequence[RGBColor[r/50,g/50,b/50], Emissive[RGBColor[r,g,b], 5]],};
Graphics3D[{%, Roughness[0], Sphere[{0,0,0}, 0.9]}, Lighting->None, RTX->True]
Graphics3D[{
Blue, Cylinder[],
Red, Sphere[{0, 0, 2}],
Yellow, Polygon[{{-3, -3, -2}, {-3, 3, -2}, {3, 3, -2}, {3, -3, -2}}]
}]
Как связать Wolfram Kernel и Javascript машину?
Нужен наиболее эффективный способ передачи данных. Кроме того, если это будет интерфейс блокнота, нужен API.
Мне никогда не нравилась идея классических API, которые сейчас имеются для взаимодействия фронтенда с бэкендом у современных приложений. Я испытываю легкое чувство неловкости, объявляя что-то подобное
//где-то на сервере/клиенте
switch(command) {
'ping':
printf('Pong!');
break;
...
}
//где-то на клиенте/сервере
send({command: 'ping', payload: []});
Я, конечно, утрирую, но это точно плохой путь для блокнота Wolfram Language. У нас есть вебсокеты и интерпретатор, верно?
(* сервер *)
serialize = ExportString[#, "ExpressionJSON"]&;
WebSocketSend[client, Alert["Hello world!"] // serialize]
/* клиент */
const Socket = new WebSocket("ws://127.0.0.1:port");
Socket.onmessage = function (event) {
interpretate(JSON.parse(event.data));
};
//какая-то функция нужная на фроентенде
core.Alert = async (args, env) => {
const text = await interpretate(args[0], env);
alert(text);
}
Разве не прелесть? Мы можем разговаривать с UI на том же языке, на котором работает ядро. Очевидно, что если целиться на ячеечную структуру блокнота, пригодятся также и такие функции
FrontEndCreateCell[...]
FrontEndDeleteCell[...]
FrontEndEvaluate[...]
...
Для обратной связи мы можем воспользоваться тем же форматом JSON, так как ничего не стоит отправить данные от JS по каналу веб-сокетов и на стороне Wolfram Kernel сделать подобное
ImportString[input, "JSON"] // HandlerFunction
либо еще проще и быстрее, минуя JSON
input // ToExpression
Многие скажут БЕЗОПАСНОСТЬ, однако, для локального приложения это не вреднее, чем позволять жить у себя NodeJS серверу с, в принципе, неограниченными правами на чтение/запись и запуск любого системного процесса.
Его величие - редактор
Это, вероятно, чуть ли не самое сердце любого блокнотного интерфейса. Самые очевидные функции могут быть получены почти любым JS редактором кода:
подсветка синтаксиса (желательно, любого)
навигация как в привычных редакторах, так и в Vim
скорость и легкость
Однако, вспомним про синтаксический сахар и требование к "редактируемости" выходных ячеек.
Как отобразить график внутри кода?
Декорации - этот концепт был введен еще давно до появления JS и веб-редакторов кода, но в полной мере воплощен только в CodeMirror 6. Представьте себе, что мы можем написать некий виджет, который заменяет собой выражение в виде строки
//выражение, которое ищется и заменяется
const ExecutableMatcher = (ref) => { return new MatchDecorator({
regexp: /FrontEndExecutable\["([^"]+)"\]/g,
decoration: match => Decoration.replace({
widget: new ExecutableWidget(match[1], ref),
})
}) };
//сам виджет
class ExecutableWidget extends WidgetType {
constructor(name, ref) {
super();
this.ref = ref;
this.name = name;
}
eq(other) {
return this.name === other.name;
}
//та самая функция которая заменяет текст на DOM элемент
toDOM() {
let elt = document.createElement("div");
//абстрактно создаем объект и исполняем его
this.fobj = new ExecutableObject(this.name, elt);
this.fobj.execute()
this.ref.push(this.fobj);
return elt;
}
ignoreEvent() {
return true;
}
destroy() {
}
}
Это, так называемые, ReplacingDecorations. Исходный текст под ними остается нетронутым, а декорируемое выражение атомизируется, занимая место лишь одного символа для каретки. В этой связи возникает простая и элегантная идея отображения всех интерактивных объектов как выражение-"ключевая строка" FrontEndExecutable["id"]
с ссылкой на объект JSON, где будет находиться рецепт для интерпретатора, чтобы отобразить красивый график
Остается лишь создать правила, по которым выражения будут заменяться на ключевые строки и передавать параллельно сопутствующие данные в виде JSON.
Не пугайтесь абстрактного кода, позже будет ссылка на CodeSandbox, где эти игры с редактором CodeMirror можно попробовать самим.
Что насчет математических выражений?
Грубо говоря, как отобразить дробь? А дробь в дроби в дроби ... Я полагаю, что лучше один раз показать на примере
и как это можно "закодировать"
CMFraction[1, CMSqrt[6]]
Остается пробежаться регулярными выражениями и распарсить это в редакторе как
-
Editor
-
CMFraction
Editor
-
CMSqrt
Editor
-
Зачем там написано Editor - я хотел лишь подчеркнуть, что числитель и знаменатель дроби, как и ячейка под корнем, обязаны быть такими же текстовыми редакторами с подсветкой синтаксиса, как и "основной" редактор
Итого на такое выражение потребуется создать 3 инстанса CodeMirror 6. Что не так плохо. А что на счет матриц?
CMGrid[{{1,0,0}, {0,1,0}, {0,0,1}}]
Итого 10 редакторов! Хотите 26? Тогда попробуйте посмотреть результат этого выражения
Table[If[PrimeQ[i], Framed[i, Background->Yellow], i], {i, 1, 100}]
Это скриншот со страницы документации проекта, где это работает вживую
Когда число доходит до 50-100, главный редактор уже значительно тяжелее переваривает изменения в дочерних редакторах.
Я оформил это расширение как отдельный NPM пакет, так люди могут использовать его в своих проектах с Wolfram Language независимо от фронтенда. Ссылка на песочницу.
в песочнице сочетания
Ctrl+-
,Ctrl+/
на выделенном коде создадут индекс и дробь, соотвественно.
Портативность
Так как редактор и ячейки все равно уже "живут" в браузере, значит, экспорт блокнота в HTML файл не составит труда. В предыдущих секциях мы договорились использовать веб-сокеты для управления структурой блокнота, соотвественно, если просто записать последовательность команд при старте блокнота вроде
commands = {
FrontEndCreateCell[...],
FrontEndCreateCell[...],
...
};
и эмулировать это с помощью Javascript при открытии HTML-файла, то эффект будет тот же, что и в настоящем блокноте. Все необходимые библиотеки можно "утащить" туда же.
К примеру, документация к этому проекту сделана подобным образом
Сам факт того, что в браузере "крутится" обрезанная версия интерпретатора Wolfram Language, позволяет переносить часть логики напрямую в браузер. Таким образом, можно сохранить частичную интерактивность, даже без запущенного ядра Wolfram Engine.
Open-source блокнотный интерфейс Wolfram Language
В англоязычной среде и документации он встречается под названием WLJS Frontend. Почему так? Это не так важно.
документация (наполняется)
paypal
Если скомбинировать все методы и подходы, описанные в предыдущих частях, то получится следующее приложение
Особенность в том, что это всего лишь веб-сервер. А само приложение - это страница HTML с самым ванильным Javascript (за исключением библиотек, необходимых для отрисовки графики). Таким образом
пользователь может изменять стиль всего интерфейса;
ядро может произвольно менять структуру документа, а также вызывать любой Javascript код на ней (привет
eval()
);фронтенд доступен с любого устройства, способного открывать заглавную страницу Хабра;
можно экспортировать блокнот в HTML с частичным сохранением интерактивности;
оно принадлежит Вам целиком, не нуждается в интернете и работает локально.
Разумеется для удобства есть версия, где оно обернуто в ElectronJS, что позволяет привнести привычные для системных приложений доступы к проводнику и полноценному контекстному, а также оконному меню.
Ячейки
Зачем меня принуждают писать на Wolfram Language, когда я хочу сделать красивую диаграмму. Мне вообще-то нравится Mermaid
Идея обращения к анонимному файлу .mermaid
мне кажется красивой. Давайте также обратимся к Markdown
.md
# Hey, how was your day?
I think it was fine. It is <?wsp TextString[Now] ?> and I am still writting my post for Habr
Нет, мне вообще на самом деле нравится Tailwind, и я хочу оформлять свои данные с помощью него
.html
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet"/>
.html
<ul role="list" class="divide-y divide-gray-100">
<?wsp Table[ ?>
<li class="bg-white shadow my-1">
<span class="flex justify-between round gap-x-6 px-3 py-5 hover:bg-sky-100">
<div class="flex gap-x-4">
<div class="min-w-0 flex-auto">
<p class="text-sm font-semibold leading-6 text-gray-900"><?wsp RandomWord[] ?></p>
<p class="mt-1 truncate text-xs leading-5 text-gray-500"><?wsp RandomWord[] ?></p>
</div>
</div>
</span>
</li>
<?wsp , {i,10}] ?>
</ul>
Да зачем мне все эти сложности, я хотел график построить, но если бы можно было его еще покрутить...
Нет, я на самом деле хотел записать в файл
filename.txt
Hello World
JS Cells
Сильной стороной являются ячейки типа .js
, так как сам фронтенд написан в основном на JS. Как я уже описал выше, на сервере и на клиенте работают интерпретаторы Wolfram Language, соотвественно, подписываться на события друг друга или вызывать функции можно напрямую
.js
const element = document.createElement('span');
core.ShowText = (args, env) => {
element.innerText = await interpretate(args[0], env);
}
return element;
и затем из ячейки WL
ShowText["This is a text"] // FrontSubmit
Если пойти дальше, можно делать вещи чуть более сложные
С помощью расширения wljs-esm-support, можно также подключить Node и бандлер ESBuild, тогда у вас появится возможность использовать любой пакет с NPM. Как и сделал я, когда мне понадобилось подключить свой контроллер Nintendo Pro.
LLM Chatbook
Для каждой задачи подойдет свой язык - это бесспорно, но еще лучше, если эту задачу решат за тебя
.llm
Plot a butterfly curve using Wolfram Language
Это дополнение было разработано @KirillBelovTest, который также является автором сервера и сейчас также активно принимает участие в разработке.
Благодаря тому, что llm имеет доступ ко всем ячейкам, и его вывод ничем не отличается от пользовательских ячеек, складывается приятное иммерсивное ощущение, что это не чат-бот, а некий гик, который случайно забежал и набрал что-то с клавиатуры в блокноте.
Редактор
Как и было описано ранее, он поддерживает математический ввод и синтаксический мёд в полной мере благодаря CodeMirror 6
Вопрос, как сделать autocomplete для тех символов, которые объявил пользователь? Как оказалось, с 1999 года в Wolfram Kernel есть следующая функция
$NewSymbol = Print["Name: ", #1, " Context: ", #2] &
Таким образом, можно буквально отслеживать все, что было создано за текущую сессию, и отправлять эти данные в браузер.
Динамика и интерактивность
Разумеется, что нельзя соревноваться с Mathematica, не имея в арсенале инструментов для создания динамических графиков и ползунков.
В процессе создания я значительно переработал этот концепт. Меня раздражала непредсказуемость поведения динамических выражений в Mathematica, которые рано или поздно приводили к падению всего приложения.
Зачем пересчитывать все заново, когда поменялись данные, если можно делать это селективно
core.Line = () => {
//получаем все данные
//обрабатываем
//рисуем
canvas.putLine();
}
core.Line.update = () => {
//обновляем
canvas.updateLine();
}
Я к тому, что у каждой функции должен быть метод для обновления, если данные поменялись. И на каждом выражении можно принять решение о том, как и что пересчитать.
Минусом такого подхода является пожалуй то, что этот метод нужно писать вручную для каждого "важного" для пользователя выражения (в основном графические примитивы), как в Mathematica по-умолчанию интерпретатор проходится по всему древу одинаково, что при первом запуске, что при обновлении данных.
Следующее изменением - событийно-ориентированный подход. Возьмем слайдер
slider = InputRange[-1,1,0.1, "Label"->"Length"]
и привяжем к нему функцию-обработчик
EventHandler[slider, Function[l, length = l]];
EventFire[slider, 0]; (* шарахнем один раз, чтобы все инициализировалось *)
А теперь сам элемент, который будет под контролем
Graphics[{Cyan,
Rectangle[{ -1,1 }, {length // Offload, -1}]
}]
Очевидно, это сразу больше кода, однако, хирургическая точность таких методов позволяет эффективно обновлять данные
Такую отзывчивость сложно представить в Mathematica. Либо такой пример
Обладателей Nvidia RTX приглашаю взглянуть на эти две сферы
Graphics3D[{
{Emissive[Red], Sphere[{0,0,2}]},
{White, Sphere[]}
}, Lighting->None, RTX->True]
Расширяемость
Разумеется, имеется система плагинов/расширений, где возможно добавить новые типы ячеек, влиять на ход исполнения ячеек, расширять библиотеку функций и т.п. Сам проект собран из более 10 расширений, половина из которых являются системными и могут работать отдельно.
Хороший пример - анимация на сайте конференции Wolfram Saint-Petersburg 2023, где используются всего лишь два компонента:
wljs-interpreter - интерперататор WL
wljs-graphics-d3 - библиотека реализующая примитивы
Graphics
Слайды/Презентация из компонентов
Работая в академической среде, мне никогда не нравилось готовить презентации к докладам, на визуальное исполнение которых уходит большая часть времени, вместо самого содержания. Почему так? Поясню:
для обработки данных используется среда A
для визуализации среда Б
для слайдов среда С
Передача данных между ними осуществляется путем сериализации в файл, что, скажем, не очень быстро и удобно. Ах да, не забудем про
перетаскивание блоков с информацией / копирование их на другие слайды
В open-source сообществе уже есть решения на этот счет, скажем, - RevealJS с возможностью писать слайды на языке Markdown. Однако, здесь все равно не хватает компонент и, как собственно, способа передачи графических данных.
Markdown поддерживает HTML из коробки, значит, у нас уже есть доступ к стилям и оформлению, если хочется. Допустим, как сделать две колонки?
.html
<div>
<div style="width:50%; float:left" >1</div>
<div style="width:50%; float:right">2</div>
</div>
Было бы здорово сделать такой компонент. С использованием WLX это возможно
.wlx
Columns[C1_, C2_] := With[{SR = If[NumberQ[Ratio], 100.0 Ratio, 50]},
<div>
<div style="width: {SR}%; float:left;">
<C1/>
</div>
<div style="width: {100-SR}%; float:right;">
<C2/>
</div>
</div>
]
Теперь вернемся к нашим слайдам, мы ведь с этого начали
.slide
# Title
<Columns>
<Identity>
First column
</Identity>
Second one
</Columns>
Не обращайте внимание на Identity
оператор, так как он нужен чтобы подсказать WL, что вторая фраза - это уже второй аргумент к функции Columns
.
А что насчет графиков?
Plt3D = Graphics3D[Cuboid[]];
.slide
# Embed some figures
Even 3D
<div style="text-align: center; display: inline-flex;">
<Plt3D/>
</div>
Try to move it using your mouse
Можно привязаться к событиям: появление фрагмента на слайде или его смена, либо вставлять напрямую компоненты ввода (ползунки) и кнопки. Ниже представлена презентация, которую я использовал на докладах в 2023 году
Она, как и все другие примеры, доступна в самом приложении фронтенда через оконное меню File - Open Examples.
Приложения на WLX
Здесь пример, как можно использовать динамику и язык разметки WLX, чтобы набросать простенькую утилиту с графическим интерфейсом для распознавания таблиц с картинок
.wlx
LeakyModule[{img, output1, output2, Ev, pipe, EditorRaw, EditorProcessed},
(* поле drop file *)
Ev = InputFile["Drop an image here"];
(* вешаем на него обработчик *)
EventHandler[Ev, Function[file,
(* импорт по формату и само распознование текста *)
pipe = ImportByteArray[file["data"]//BaseDecode, FileExtension[file["name"]]//ToUpperCase];
pipe = Binarize[pipe];
pipe = TextRecognize[pipe];
output1 = ToString[pipe, InputForm];
output2 = ToString[(ToExpression /@ StringSplit[#, "
"]) &/@ StringSplit[pipe, "
"], InputForm];
]];
(* выходные значения *)
output1 = "- none -";
output2 = "- none -";
(* два поля вывода с подсветкой синтаксиса *)
EditorRaw = EditorView[output1 // Offload] // CreateFrontEndObject;
EditorProcessed = EditorView[output2 // Offload] // CreateFrontEndObject;
(* шаблон разметки выходной ячейки в HTML (WLX) *)
<div>
<div style="display: flex;"><Ev/></div>
<p>Raw string</p>
<div style="margin-top: 0.5em; margin-bottom: 0.5em; display: flex; border: 2px dashed skyblue;"><EditorRaw/></div>
<p>Processed string</p>
<div style="margin-top: 0.5em; margin-bottom: 0.5em; display: flex; border: 2px dashed deepskyblue;"><EditorProcessed/></div>
</div>
]
Видео в действии
Вывод ячейки в окно
Это побочная функция, созданная для возможности показывать слайды. Однако, также может быть полезной, когда блокнот становится слишком большим для навигации. Мой рабочий стол обычно выглядит как-то так
Ограничения
Обойти достижения WRI последних 20-лет двум разработчикам за год невозможно и бессмысленно (у нас нет такой цели и не будет). WLJS Frontend это альтернативный инструмент со своими преимуществами и недостатками, где для решения архитектурных проблем в одних областях были приняты компромиссные решения в других, но не замена.
@KirillBelovTestи я постарались скомпилировать бинарные файлы компонент веб-сервера под каждую платформу, однако, различия все же встречаются, что периодически пополняет банк Issues на гитхабе. Если нужна "горячая поддержка", вступайте в группу поддержки в Телеграмме.
Из других примеров, до сих пор нет функции Circle
в пакете Graphics, просто потому, что она редко используется в типичных plot-функциях Mathematica и чьи-то руки не дошли до того, чтобы написать десяток строчек кода на JS. Тем не менее, большая часть функций, которая касается построения данных по точкам, уже покрыта - смотрите здесь.
Проект развивается и дополняется почти каждый день. Это не готовый продукт, в отличие от Wolfram Mathematica.
"Этот список" из цитаты в начале статьи
Вызов брошен, а как же ответ? Вот адаптированные сниппеты из списка, который показал автор
d=theta@t-phi@t;
sol = NDSolve[{#''@t==-#4#2''[t]Cos@d-##3#2'@t^2Sin@d-Sin@#@t&@@@{{theta,phi,1,.5},{phi,theta,-1,1}},theta@0==2,phi@0==1,theta'@t==phi'@t==0/.t->0},{theta,phi},{t,0,60}];
With[{h = {Sin@#@#2,-Cos@#@#2}&},
With[{f = theta~h~u+phi~h~u /. First[sol], m1 = theta~h~u /. First[sol]},
LeakyModule[{points, time = 0., handler, task},
EventHandler[EvaluationCell[], {"destroy"->Function[Null, TaskRemove[task]]}];
handler := (points = Table[f, {u, 0., time,0.1}]; pendulum1 = Table[m1, {u, {time}}] // First; pendulum2 = points // Last;);
handler;
task = SetInterval[
time = time + 0.1;
handler;
If[time > 59., TaskRemove[task]];
, 70];
Graphics[{
Line[points // Offload], PointSize[0.05], Red,
Point[pendulum1 // Offload],
Point[pendulum2 // Offload],
Line[{pendulum1 // Offload, pendulum2 // Offload}]
}, Controls->True, Axes->True, TransitionDuration->10, TransitionType->"Linear"]
]
]
]
и демонстрация, если это запустить в блокноте
Другой "сниппет" из той же ветки
StreamPlot[{x^2,y},{x,0,3},{y,0,3}]
LeakyModule[{data, frame, i = 1},
data = CellularAutomaton[{224,{2,{{2,2,2},{2,1,2},{2,2,2}}},{1,1}}, Table[RandomInteger[{0,1}], {x,200}, {y,400}], 50];
frame = 255 data[[i]];
EventHandler[EvaluationCell[], {"destroy"->Function[Null, TaskRemove[task]]}];
task = SetInterval[
i = i + 1;
frame = 255 data[[i]];
If[i > 49,
data = CellularAutomaton[{224,{2,{{2,2,2},{2,1,2},{2,2,2}}},{1,1}}, data//Last, 50];
i = 0;
];
, 50];
Image[frame // Offload]
]
SphericalPlot3D[Re[Sin[\[Theta]]Cos[\[Theta]]Exp[2I*\[CurlyPhi]]],{\[Theta],0,\[Pi]},{\[CurlyPhi],0,2\[Pi]}]
Спасибо за внимание ????
UPD: Грамматика
Комментарии (3)
ekimenkoav
14.10.2023 18:55+3Пользуюсь Wolfram Mathematica и хотел бы задать уточняющие вопросы или дать комментарии
Проприетарный формат/среда, который стоит дорого
Согласен, что зачеркнуто! Во-первых, платно, зато WR отвечает за совместимость всех алгоритмов, функций и пр. Во-вторых, у нас на предприятии, например, Python установить нельзя, а WolframMathematica можно - такие особенности лицензирования видимо!
Тяжелый интерфейс (в плане отзывчивости), нестабильный UI (краш, фриз это обычное дело)
Я не согласен. Интерфейс запускается быстро. Не видел каких-то тормозов.
Клиент и среда связаны, нельзя подключаться с телефона / тостера
Да и ладно! С телефона неплохо работает WolframCloud, сейчас уже не работает. Но не согласен прям записывать в недостатки?
Кривой экспорт в PDF и только статические графики / изображения
Согласен. Но наверно экспорт в pdf хорош только у AdobePhotoshop?;)
Нельзя встроить блокнот на сайт/блог
Совершенно согласен, этого не хватает для публикации результатов!
Сложно (неочевидно) как добавить другие языки или типы ячеек на низком уровне (нативно)
А чем плох функционал ExternalEvaluate?
JerryI Автор
14.10.2023 18:55+3Спасибо за фидбек, @ekimenkoav!
Может мне не везет, но с 2018 года работая из под Windows, было довольно напряжно. Примеры с SE, где я тоже плакался по этому поводу 1, 2... Как теоретику, мне часто требуется иметь 5-6 динамических окон, где пересчитываются графики и т.п., соотвественно одно неверное движение (скажем *нечисло* попало в расчет), возникает краснота и все падает) Я бы понял, если бы упало ядро, но падает весь интерфейс. Даже прокуртка оказывается дерганной, если есть динамические окна (Windows 11 Mathematica 12, OSX Mathematica 12). Я вижу что фронтенд как-то тесно связан с ядром, UI блокируется, если "тяжелые" вычисление не завершились. Идея была в том, чтобы отцепить ядро и UI, чтобы не важно что и как там вычисляется, как много динамики - ячейки и блокнот были сохранены и доступны пользователю.
-
Согласен, перегнул палку. Подправил в тексте.
Я имел ввиду, скажем, при работе с Юпитером, я могу ехать на поезде и проверять, что и как там с моим расчетом в другом городе/стране с телефона или компьютера друга. ;) Мне бывает часто приходится работать из разных мест по VPN, а тащить с собой ноутбук с математикой - это не всегда выходит, особенно если вовлечены CUDA, OpenCL или другие модули. RDP или TeamViewer это сразу боль, а Wolfram Cloud тут особо что-то серьезное не покрутишь. А здесь выходит, вы можете себе локальный Wolfram Cloud устроить. Я ни в коем случае не против нативного подхода с QT и т.п., однако при всей сложности интерфейса и расширяемости, возможно песочница с JS и DOM в этом случае подходит тоже неплохо.-
Лучше Adobe Illustractor ;).
Блокноты Юпитера в формате HTML мне всегда вызывали зависть. Длинное полотно, как на первых печатных текстовых терминалах и даже иногда какие-то графики можно потыкать. В PDF оттуда уже по-проще переводить, впрочем, так как фронтенд открытый с максимально плоской и простой структурой, можно любой экспорт прикрутить.
Мы шагнем еще дальше, так как фронт все равно это HTML документ, можно зарегистрировать URLProtocol, так, что при открытии страницы блога со встроенным блокнотом можно будет запустить ядро Wolfam Kernel по кнопке у себя локально, которое присоединится к открытой странице, и "попробовать" поработать в опубликованном ноутбуке (без возможности сохранения конечно) на чужом сайте.
7. Убрал этот пункт. Тут больше технический вопрос, как отображение сделать другим (условно не только текст, как результат выполнения, а что-то более сложное)
-
KirillBelovTest
Хочу еще раз отметить одну из самых важных концепций проекта. Все, что печатается в ячейках с CodeMirror-редактора - является выражением на Wolfram Language. Это одна из самых главных особенностей Mathematica, которая там работает в точности так же. Отличие только в способе реализации. Mathematica - это приложение рабочего стола написанное на QT, а WLJS - такое же приложение, но в виде веб-страницы. В нашем приложении есть интерпретатор, который работает в браузере и передает результат выполнения кода на рендер браузеру и CodeMorror-редактору, но и в Mathematica точно так же есть отдельное Fronend Kernel, которое является интерпретатором зашитым именно в сам UI написанный на QT, а результат интерпретации передается в QT для рендера.