Запуск версії 14.2 Wolfram Language & Mathematica: Big ​​Data Meets Computation & AI

Барабанний дріб релізів триває…

Трохи менше ніж шість місяців тому (точно 176 днів тому) ми випустили Версію 14.1. Сьогодні я з радістю оголошую про випуск Версії 14.2, яка містить найновіші здобутки нашого відділу досліджень і розробок.

Це захоплюючий час для нашої технології як з точки зору того, що ми тепер можемо впроваджувати, так і з огляду на те, як наша технологія зараз використовується у світі в цілому. Особливої уваги заслуговує те, що мову Wolfram Language дедалі частіше використовують не лише люди, а й штучний інтелект. І дуже приємно бачити, що всі наші зусилля з послідовного проєктування, реалізації та документування мови протягом років тепер дають свої плоди, роблячи Wolfram Language унікально цінним інструментом для штучного інтелекту — доповнюючи їхні власні вроджені можливості.

Проте існує й інший аспект штучного інтелекту. Завдяки запуску Wolfram Notebook Assistant минулого місяця ми використовуємо технології ШІ (а також багато іншого), щоб надати, фактично, розмовний інтерфейс до Wolfram Language. Як я описував під час випуску Wolfram Notebook Assistant, це надзвичайно корисно як для експертів, так і для початківців, але в підсумку я вважаю, що найважливішим наслідком буде прискорення переходу від будь-якої галузі X до «обчислювальної галузі X» — із використанням усієї технологічної інфраструктури, яку ми створили навколо Wolfram Language.

Отже, що нового у версії 14.2? Під капотом відбулися зміни, що зробили Wolfram Notebook Assistant ефективнішим і зручнішим. Але також є багато помітних розширень і покращень у видимих користувачу частинах Wolfram Language. Загалом додано 80 абсолютно нових функцій, а також 177 функцій, які зазнали суттєвих оновлень.

Продовжуються довготривалі дослідницькі розробки, наприклад додатковий функціонал для роботи з відео та нові можливості у роботі з символічними масивами. Окрім того, з’явилися абсолютно нові вбудовані функції, такі як теорія ігор. Але найбільше нововведення у версії 14.2 — це робота з табличними даними, особливо з великими табличними даними. Це зовсім нова підсистема Wolfram Language з потужними наслідками по всій системі. Ми працювали над нею кілька років і раді вперше випустити її у версії 14.2.

Говорячи про розробку нового функціоналу: понад сім років тому ми започаткували концепцію відкритого проєктування програмного забезпечення, проводячи трансляції наших зустрічей з проєктування. Наприклад, від моменту випуску версії 14.1 ми провели 43 таких трансляції загальною тривалістю 46 годин (окрім цього я провів 73 години інших трансляцій). Частину функціоналу, що зараз є у версії 14.2, ми почали розробляти кілька років тому. Але ми ведемо трансляції вже настільки довго, що практично все, що є у версії 14.2, ми розробляли в прямому ефірі і відкрито для публіки. Розробка програмного забезпечення — це важка праця (про що можна переконатися, переглянувши трансляції). Але завжди надихає бачити плоди цієї праці у системі, яку ми поступово створюємо так довго. Тож сьогодні мені приємно представити версію 14.2 і дозволити всім користуватися тим, над чим ми так наполегливо працювали.

 

Помічник блокнота Чат у будь-якому блокноті

Минулого місяця ми випустили Wolfram Notebook Assistant, щоб «перетворювати слова на обчислення» — і допомагати як експертам, так і новачкам ширше й глибше використовувати технології Wolfram Language. У версії 14.1 основним способом використання Notebook Assistant було окреме вікно «бічного чату». Але у версії 14.2 «чат-комірки» стали стандартною функцією будь-якого нотатника, доступною всім, хто має підписку на Notebook Assistant.

Просто введіть символ на початку будь-якої комірки, і вона перетвориться на чат-комірку:

Chat cell

Тепер ви можете почати спілкування з Notebook Assistant:

 

За допомогою бічного чату ви маєте «окремий канал» для спілкування з Notebook Assistant, який, наприклад, не зберігається разом із вашим нотатником. А от чат-комірки стають невід’ємною частиною самого нотатника.

Вперше ми представили Chat Notebooks у середині 2023 року — лише через кілька місяців після появи ChatGPT. Chat Notebooks визначали інтерфейс, але на той час вміст чат-комірок формувався виключно зовнішніми LLM-моделями. Тепер, у версії 14.2, чат-комірки доступні не лише у спеціальних Chat Notebooks, а в будь-якому нотатнику. І за замовчуванням вони використовують повну технологічну інфраструктуру Notebook Assistant, яка значно перевершує можливості лише «сирої» LLM-моделі. Крім того, якщо у вас є підписка на Notebook Assistant + LLM Kit, ви можете безперешкодно використовувати чат-комірки — обліковий запис у зовнішніх постачальників LLM більше не потрібен.

Функціональність чат-комірок у версії 14.2 успадкувала всі можливості Chat Notebooks. Наприклад, введення символу ~ у новій комірці створює перерву чату, яка дозволяє почати «нову розмову». І коли ви використовуєте чат-комірку, вона має доступ до всього вмісту нотатника аж до останньої перерви чату. (До речі, при використанні Notebook Assistant через бічний чат він також бачить виділений вами фрагмент у “фокусному” нотатнику.)

За замовчуванням чат-комірки «спілкуються» з Notebook Assistant. Але за бажанням ви можете використовувати їх для взаємодії із зовнішніми LLM, як це було в оригінальних Chat Notebooks — для цього є зручне меню. Звісно, при використанні зовнішньої LLM ви не отримаєте всі ті можливості, які тепер надає Notebook Assistant. Якщо ви не займаєтесь дослідженнями LLM, то зазвичай буде набагато корисніше й ефективніше використовувати чат-комірки в їхньому стандартному режимі — для спілкування з Notebook Assistant.

 

Принесіть нам свої гігабайти! Представляємо Табуляр

Списки, асоціації, набори даних — це дуже гнучкі способи представлення структурованих колекцій даних у Wolfram Language. Але тепер, у версії 14.2, з’явився ще один: Tabular.
Tabular забезпечує надзвичайно зручний і ефективний спосіб роботи з таблицями даних, які представлені у вигляді рядків і стовпців. І коли ми говоримо «ефективний», ми маємо на увазі, що він без проблем може обробляти гігабайти даних — як у оперативній пам’яті, так і поза нею.

Давайте розглянемо приклад. Почнемо з імпорту табличних даних:

 

Це дані про дерева в Нью-Йорку — 683 788 дерев, кожне з яких має 45 властивостей (іноді з пропусками).
Tabular запроваджує низку нових концепцій. Одна з них — це обробка табличних стовпців подібно до змінних. У цьому прикладі ми використовуємо цю можливість, щоб побудувати гістограму значень стовпця “tree_dbh” у нашому об’єкті Tabular:

 

Можна уявити собі Tabular як оптимізовану форму списку асоціацій, де кожен рядок — це асоціація з ключами, що відповідають назвам стовпців.
Функції на кшталт Select просто працюють із Tabular:

 

Length повертає кількість рядків:

 

CountsBy розглядає Tabular як список асоціацій, витягуючи значення, пов’язане з ключем “spc_latin” (“латинська назва виду”) у кожній асоціації, та підраховує, скільки разів зустрічається кожне з цих значень
(тут "spc_latin" — скорочення для виразу #"spc_latin"&):

 

Щоб отримати назви стовпців, можна скористатися новою функцією ColumnKeys:

 

Розглядаючи Tabular як список асоціацій, ми можемо витягувати його частини — спочатку задаючи вибірку рядків, а потім — вибірку стовпців:

 

З появою Tabular ми змогли впровадити багато нових операцій.
Приклад — AggregrateRows, яка створює новий Tabular на основі заданого, агрегуючи групи рядків, у цьому випадку — ті, що мають однакове значення у стовпці “spc_latin”, а потім застосовує до них функцію, тут — обчислює середнє значення у стовпці “tree_dbh”:

 

Операція на кшталт ReverseSortBy «просто працює» з цією таблицею, тут — сортування за спаданням за значенням у стовпці “meandbh”:

 

Тут ми створюємо звичайну матрицю з невеликого зрізу даних із нашого Tabular:

 

А тепер ми можемо побудувати графік результату, показуючи розташування сосен Вірджинії у Нью-Йорку:

 

Коли варто використовувати Tabular, а не, скажімо, Dataset?
Tabular спеціально створений для даних, організованих у рядки та стовпці — і підтримує багато потужних операцій, які мають сенс саме для такої «прямокутної» структури даних.
Dataset є більш загальним: він може містити довільну ієрархію вимірів даних, тому загалом не підтримує всі «прямокутні» операції, характерні для Tabular. Крім того, завдяки спеціалізації на «прямокутних» даних, Tabular є значно ефективнішим і використовує найсучасніші типово-специфічні методи обробки великих обсягів даних.

Якщо скористатися функцією TabularStructure, можна побачити, що робить Tabular таким ефективним. Кожен стовпець розглядається як дані певного типу (і так, ці типи узгоджуються з тими, що використовуються в компіляторі Wolfram Language). Крім того, передбачено оптимізовану обробку пропущених даних (для чого додано кілька нових спеціалізованих функцій):

 

 

Те, що ми бачили досі, — це робота Tabular з даними «в пам’яті» (in-core). Але ви також можете досить прозоро використовувати Tabular з даними, що знаходяться поза пам’яттю (out-of-core), наприклад, даними, збереженими у реляційній базі даних.

Ось приклад того, як це виглядає:

 

Це Tabular, який посилається на таблицю у реляційній базі даних. За замовчуванням він не відображає дані безпосередньо у Tabular (і взагалі не завантажує їх у пам’ять — бо дані можуть бути дуже великими і швидко змінюватися). Проте ви все одно можете задавати операції так само, як і з будь-яким іншим Tabular. Ось команда, яка визначає, які стовпці є в таблиці:

 

А це задає операцію, повертаючи результат у вигляді символічного Tabular-об’єкта, який працює з даними поза пам’яттю (out-of-core):

 

Ви можете «розв’язати» це й отримати явний Tabular в оперативній пам’яті, використовуючи ToMemory:

 

 

Маніпулювання даними в таблиці

Припустімо, у вас є Tabular — наприклад, такий, що базується на даних про пінгвінів:

 

Існує багато операцій, які дозволяють структуровано змінювати дані у цьому Tabular, повертаючи як результат новий Tabular.
Наприклад, ви можете просто взяти останні 2 рядки з Tabular:

 

Або ви можете вибрати випадковим чином 3 рядки:

 

Інші операції залежать від фактичного вмісту Tabular. І оскільки кожен рядок можна розглядати як асоціацію, ви можете створювати функції, які ефективно звертаються до елементів за назвами стовпців:

 

Зверніть увагу, що ми завжди можемо використовувати #[name] для звернення до елементів у стовпці.
Якщо name — це алфавітно-цифровий рядок, то можна також скористатися скороченням #name.
А для інших рядків — використовувати формат #”name”.
Деякі функції дозволяють просто вказати “name”, що є скороченням для функції #[“name”]:

 

Досі ми говорили лише про впорядкування або вибірку рядків у Tabular. А як щодо стовпців?
Ось приклад, як можна створити Tabular, що містить лише два стовпці з нашого початкового Tabular:

 

А що, якщо нам потрібні не лише наявні стовпці, а й нові — як функції від існуючих?
Функція ConstructColumns дозволяє створювати нові стовпці, вказуючи їхні назви та функції, які обчислюють значення для кожного рядка:

 

(Зверніть увагу на хитрий прийом — використання повної форми Function дозволяє уникнути потреби у дужках, як у прикладі "species"(StringTake[#species,1]&).)

Функція ConstructColumns дає змогу створити новий Tabular на основі наявного, додаючи нові стовпці.
Функція TransformColumns дозволяє змінювати стовпці у вже існуючому Tabular, наприклад, замінюючи назви видів на їхні перші літери:

 

TransformColumns також дозволяє додавати нові стовпці, вказуючи їхній вміст так само, як у ConstructColumns.
Але куди саме TransformColumns розміщує нові стовпці?
За замовчуванням — в кінці, після всіх наявних стовпців.
Але якщо ви явно вкажете існуючий стовпець, він буде використаний як орієнтир для розміщення нового стовпця.
(Запис “name” → Nothing видаляє стовпець.)

 

Усе, що ми розглядали досі, працює окремо з кожним рядком Tabular.
Але що робити, якщо ми хочемо «захопити» цілий стовпець для обчислень — скажімо, обчислити середнє значення по стовпцю, а потім відняти його від кожного значення?
Функція ColumnwiseValue дозволяє це зробити, передаючи у функцію (тут — Mean) список усіх значень заданого стовпця (або кількох стовпців):

 

ColumnwiseValue фактично дозволяє обчислювати скалярне значення, застосовуючи функцію до цілого стовпця.
Існує також ColumnwiseThread, яка дозволяє обчислити список значень, що, по суті, буде «вплетений» у стовпець.
У цьому прикладі ми створюємо стовпець зі списку накопичених значень:

 

До речі, як ми обговоримо нижче, якщо у вас уже є зовнішньо згенерований список значень (потрібної довжини), який ви хочете використати як стовпець, ви можете зробити це безпосередньо за допомогою InsertColumns.

Є ще одна концепція, яка на практиці дуже корисна при роботі з табличними даними — це групування.
У наших даних про пінгвінів кожен рядок відповідає окремому пінгвіну певного виду.
Але що, якщо ми хочемо згрупувати всіх пінгвінів одного виду — наприклад, щоб обчислити їхню середню масу тіла?

Це можна зробити за допомогою AggregateRows.
AggregateRows працює подібно до ConstructColumns у тому сенсі, що ви вказуєте стовпці та їхній вміст.
Але, на відміну від ConstructColumns, вона створює нові агреговані рядки:

 

Що це за перший стовпець у таблиці? Сірий фон його значень означає, що це так званий «ключовий стовпець» — стовпець, значення якого (можливо, разом з іншими ключовими стовпцями) можна використовувати для звернення до рядків.
Пізніше ми побачимо, як можна скористатися функцією RowKey, щоб звертатися до рядка, вказавши значення з ключового стовпця:

 

Але продовжимо нашу роботу з агрегацією.
Припустімо, ми хочемо групувати не лише за видом, а й за островом.
Ось як це можна зробити за допомогою AggregateRows:

 

У певному сенсі ми маємо таблицю, рядки якої задаються парами значень (у цьому випадку — “species” і “island”).
Але часто зручніше «переорієнтувати» такі таблиці так, щоб одне з цих значень використовувалося для рядків, а інше — для стовпців.
І це можна зробити за допомогою функції PivotTable:

 

Зверніть увагу на —, які позначають відсутні значення; очевидно, наприклад, що на острові Dream немає пінгвінів виду Gentoo тощо.

PivotTable зазвичай містить ті самі дані, що й AggregateRows, але у зміненому вигляді.
Одна з додаткових можливостей PivotTable — це параметр IncludeGroupAggregates, який додає записи All, що відображають агреговані значення по кожній групі:

 

Якщо ви обчислюєте кілька функцій одночасно, AggregateRows просто поверне їх як окремі стовпці:

 

PivotTable також може працювати з кількома функціями — створюючи стовпці з «розширеними ключами»:

 

А тепер ви можете використовувати RowKey та ExtendedKey, щоб звертатися до елементів отриманого Tabular:

 

 

Отримання даних у табличному вигляді

Ми вже розглянули деякі можливості роботи з даними у форматі Tabular. Але як отримати дані у форматі Tabular? Є кілька способів. Перший — це просто конвертація з інших структур, наприклад, зі списків або асоціацій. Другий спосіб — імпорт з файлу, наприклад CSV або XLSX (або, якщо йдеться про великі обсяги даних — з формату Parquet), а також із зовнішніх сховищ даних, таких як S3, Dropbox тощо. Третій спосіб — підключення до бази даних. Крім того, дані для Tabular можна отримати безпосередньо з Wolfram Knowledgebase або з Wolfram Data Repository.

Ось як можна перетворити список списків у Tabular:

 

А ось як можна перетворити його назад:

 

Це працює і з розрідженими масивами — ось приклад миттєвого створення Tabular з мільйоном рядків:

 

який займає 80 МБ пам’яті для збереження:

 

Ось що відбувається зі списком асоціацій:

 

Той самий Tabular можна отримати, ввівши окремо дані та назви стовпців:

 

До речі, Tabular можна легко перетворити на Dataset.

 

А у цьому простому випадку можна також перетворити його назад у Tabular:

 

Взагалі ж існує безліч варіантів конвертації списків, наборів даних тощо у об’єкти Tabular — і функція ToTabular налаштована так, щоб ви могли керувати цим процесом. Наприклад, за допомогою ToTabular можна створити Tabular із стовпців, а не з рядків:

 

А як щодо зовнішніх даних? У версії 14.2 Import тепер підтримує елемент “Tabular” для табличних форматів даних.
Наприклад, якщо у вас є CSV-файл, ви можете імпортувати його як Tabular:

CSV file

Import може безпосередньо імпортувати дані у вигляді Tabular:

 

Це працює дуже ефективно навіть із великими CSV-файлами, що містять мільйони записів. Також добре автоматично розпізнає назви стовпців і заголовки. Аналогічним чином працює імпорт більш структурованих файлів, наприклад із електронних таблиць та статистичних форматів даних. Крім того, підтримуються сучасні формати стовпцевого зберігання даних, такі як Parquet, ORC та Arrow.

Import прозоро обробляє як звичайні файли, так і URL-адреси (та URI), запитуючи автентифікацію за потреби. У версії 14.2 ми вводимо нове поняття — DataConnectionObject, яке надає символічне представлення віддалених даних, по суті інкапсулюючи всі деталі щодо їх отримання. Наприклад, ось DataConnectionObject для S3-бакету, вміст якого ми можемо миттєво імпортувати:

 

(У версії 14.2 ми підтримуємо Amazon S3, Azure Blob Storage, Dropbox, IPFS — і ще багато інших у планах. Також планується підтримка підключень до сховищ даних, API тощо.)

А що робити з даними, які надто великі або надто швидко змінюються, щоб їх імпортувати явно? Важливою особливістю Tabular (згадано вище) є прозора робота з зовнішніми даними, наприклад, у реляційних базах даних.

Ось посилання на велику зовнішню базу даних:

RelationalDatabase

Це визначає Tabular, який посилається на таблицю у зовнішній базі даних:

tab = Tabular

Ми можемо запросити розміри Tabular — і бачимо, що він містить 158 мільйонів рядків:

Dimensions

Таблиця, яку ми розглядаємо, містить усі лінійні дані з OpenStreetMap. Ось перші 3 рядки та 10 стовпців:

ToMemory

Більшість операцій над Tabular тепер фактично виконуються у зовнішній базі даних. Тут ми запитуємо вибрати рядки, у яких поле “name” містить “Wolfram”:

Select

Фактичні обчислення виконуються лише тоді, коли ми використовуємо ToMemory, і в цьому випадку (через велику кількість даних у базі) це займає трохи часу. Але незабаром ми отримуємо результат у вигляді Tabular:

ToMemory

І ми дізнаємося, що у базі даних є 58 об’єктів із назвою, що містить «Wolfram»:

Length

Ще одним джерелом даних для Tabular є вбудована Wolfram Knowledgebase. У версії 14.2 функція EntityValue підтримує прямий вивід у форматі Tabular:

 

Wolfram Knowledgebase надає багато хороших прикладів даних для Tabular. Те саме стосується і Wolfram Data Repository — де зазвичай достатньо застосувати Tabular, щоб отримати дані у форматі Tabular:

 

 

Очищення даних для табличних даних

У багатьох сенсах це — справжній виклик у сфері аналізу даних. Так, дані перебувають у цифровій формі. Але вони «брудні» — не структуровані, не готові до обчислень. Wolfram Language вже давно є унікально потужним інструментом для гнучкого очищення даних (і, наприклад, для просування через десять рівнів перетворення даних у обчислювану форму, які я описав кілька років тому).

Але тепер, у версії 14.2, з появою Tabular, у нас з’явилася ціла нова колекція зручних можливостей для очищення даних. Почнімо з імпорту «диких» даних (і, до речі, цей приклад ще досить чистий порівняно з іншими):

 

(До речі, якщо б у файлі траплялися справді дивні речі, ми могли б скористатися параметром MissingValuePattern, щоб задати шаблон, який одразу замінював би ці значення на Missing[…].)

Гаразд, але почнімо з огляду того, що ми отримали з файлу, скориставшись функцією TabularStructure:

 

Ми бачимо, що Import успішно визначив базовий тип даних у більшості стовпців — хоча, наприклад, він не може точно сказати, чи є числа просто числами, чи вони представляють величини з одиницями виміру тощо. Також було виявлено, що в деяких стовпцях присутні пропущені значення.

Як перший крок очищення даних, приберімо стовпець “id”, який, схоже, не має значення:

 

Далі ми бачимо, що елементи у першому стовпці розпізнаються як рядки — але насправді це дати, які слід об’єднати з часом із другого стовпця. Ми можемо зробити це за допомогою TransformColumns, видаливши тепер «зайвий» стовпець, замінивши його на Nothing:

 

Якщо подивитися на числові стовпці, стає зрозуміло, що насправді це величини, які повинні мати одиниці виміру. Але спершу, для зручності, перейменуймо два останні стовпці:

 

Тепер давай перетворимо числові стовпці на стовпці з величинами та одиницями виміру, і заодно конвертуємо значення з градусів Цельсія у Фаренгейти:

 

Ось як тепер можна побудувати графік температури як функції від часу:

 

На графіку видно багато коливань. І якщо поглянути на самі дані, то стане зрозуміло, що ми отримуємо значення температури з кількох різних метеостанцій. Ось як можна вибрати дані лише з однієї станції:

 

Звідки взявся розрив на графіку? Якщо просто прокрутити таблицю до цієї ділянки, ми побачимо, що причина — у відсутніх даних:

 

Що ж ми можемо з цим зробити? Є потужна функція TransformMissing, яка надає багато варіантів обробки.
У цьому прикладі ми просимо її інтерполювати та заповнити відсутні значення температури:

 

Тепер на графіку більше немає прогалин, але, трохи загадково, сам графік розширився далі:

 

Причина в тому, що інтерполяція виконується навіть у випадках, коли фактично нічого не було виміряно. Ми можемо видалити такі рядки за допомогою функції Discard:

 

І тепер у нас не буде цього «виступу» наприкінці графіка:

 

Іноді в даних явно вказано, що значення відсутнє; іноді ж (що підступніше) дані просто є некоректними.
Давайте подивимось на гістограму значень тиску в нашому наборі даних:

 

Ой. Що це за малі значення? Напевно, вони помилкові (можливо, це помилки під час введення).
Ми можемо видалити такі «аномальні» значення за допомогою функції TransformAnomalies.
У цьому прикладі ми говоримо їй повністю вилучити будь-який рядок, де тиск вважається «аномальним»:

 

Ми також можемо змусити TransformAnomalies намагатися «виправити» дані. У цьому прикладі ми замінюємо будь-які аномальні значення тиску на попереднє значення тиску в Tabular:

 

Також ви можете вказати TransformAnomalies, щоб вона «позначала» будь-яке аномальне значення як «відсутнє» (missing). Але що тоді відбувається, якщо ми намагаємося виконувати обчислення з відсутніми значеннями? Саме для цього існує функція MissingFallback. Це, по суті, дуже проста функція, яка повертає перший аргумент, що не є відсутнім (non-missing):

 

Хоча ця функція досить проста, вона дуже важлива для зручного опрацювання відсутніх значень.
Наприклад, цей приклад обчислює «northspeed», при цьому у разі відсутності потрібних даних підставляє значення 0:

 

 

Структура табличного

Ми вже казали, що Tabular — це «наче» список асоціацій. І справді, якщо застосувати до нього функцію Normal, саме це ви й отримаєте:

 

Але внутрішньо Tabular зберігається значно компактніше та ефективніше. І корисно знати щось про це, щоб ви могли працювати з об’єктами Tabular напряму, без потреби «розбирати» їх на списки чи асоціації. Ось наш базовий приклад Tabular:

 

Що відбувається, якщо ми витягаємо рядок? Ми отримаємо об’єкт типу TabularRow:

 

Якщо застосувати Normal, ми отримаємо асоціацію:

 

Ось що відбувається, якщо натомість витягнути стовпець:

 

Тепер Normal повертає список:

 

Ми можемо створити TabularColumn зі списку:

 

Тепер ми можемо використати InsertColumns, щоб вставити такий символічний стовпець в існуючий Tabular (вказування “b” означає, що InsertColumns вставить новий стовпець після стовпця “b”):

 

Але що насправді міститься всередині об’єкта Tabular? Давайте розглянемо це на прикладі:

 

TabularStructure надає нам огляд внутрішньої структури цього об’єкта:

 

Перше, що варто зауважити — усе подано у вигляді стовпців, що відображає фундаментальну орієнтацію Tabular на колонки. Частково саме це робить Tabular таким ефективним: усередині стовпця всі значення одного типу. Крім того, для таких типів, як величини з одиницями виміру чи дати, дані зберігаються у вигляді списку чисел, а метадані про їх інтерпретацію зберігаються окремо у єдиному екземплярі.

І так, усе це суттєво впливає на продуктивність. Ось, наприклад, розмір у байтах нашого Tabular із даними про дерева Нью-Йорка, про який ми говорили вище:

 

Але якщо перетворити його на список асоціацій за допомогою Normal, розмір результату буде приблизно в 14 разів більшим:

 

Гаразд, але що це за «типи стовпців» у структурі Tabular?
Функція ColumnTypes повертає їх список:

 

Це — низькорівневі типи, подібні до тих, що використовуються у компіляторі Wolfram Language. І одна з переваг знання цих типів полягає в тому, що вони одразу дають розуміння, які операції можна виконувати з певним стовпцем. Це корисно як для низькорівневої обробки, так і, наприклад, для визначення можливих способів візуалізації даних.

Коли Import імпортує дані з CSV-файлу чи подібного джерела, він намагається самостійно визначити тип кожного стовпця. Але іноді (як уже згадувалося вище) вам може знадобитися «перетворити» стовпець у інший тип, явно вказавши «тип призначення» у вигляді опису типу Wolfram Language.
Наприклад, ось так ми можемо перетворити стовпець “b” на 32-бітне дійсне число, а стовпець “c” — на величину у метрах:

 

До речі, коли Tabular відображається в нотбуку, заголовки стовпців показують типи даних у відповідних стовпцях. У цьому прикладі, наприклад, у першому стовпці є лапка , яка вказує, що він містить рядки. Числа та дати просто «виглядають як є». Величини мають позначення одиниць виміру. Загальні символьні вирази (наприклад, як у стовпці “f”) позначаються як . (Якщо навести курсор на заголовок стовпця, можна побачити більше інформації про тип даних.)

Наступна важлива тема — це відсутні дані.
Tabular завжди вважає, що стовпці мають однорідний тип, але зберігає загальну карту того, де значення відсутні.
Якщо витягнути стовпець, то в таких місцях ви побачите символьний вираз Missing:

 

Але якщо ви працюєте безпосередньо зі стовпцем Tabular, він просто поводитиметься так, ніби відсутні дані дійсно відсутні — тобто пропускатиме їх у розрахунках:

 

До речі, якщо ви імпортуєте дані «з дикого середовища», Import намагатиметься автоматично визначити правильний тип для кожного стовпця. Він вміє розпізнавати поширені аномалії у вхідних даних, як-от NaN або null у числових стовпцях. Але якщо трапляються інші дивні речі — наприклад, notfound посеред числового стовпця — ви можете вказати Import перетворювати такі значення на стандартні Missing[…], задавши їх через опцію MissingValuePattern.

Є ще кілька тонкощів, пов’язаних зі структурою об’єктів Tabular. Перша з них — це поняття розширених ключів (extended keys). Припустімо, ми маємо такий Tabular:

 

Ми можемо «розгорнути цю таблицю у стовпці», щоб значення x та y стали заголовками стовпців — але в межах загального заголовка стовпця “value”:

 

Але якою є структура цього Tabular? Ми можемо скористатися функцією ColumnKeys, щоб дізнатися:

 

Тепер ці розширені ключі можна використовувати як індекси для доступу до значень у Tabular:

 

У цьому конкретному випадку, оскільки «підключі» “x” та “y” є унікальними, ми можемо використовувати лише їх — без потреби вказувати іншу частину розширеного ключа:

 

Наша остання (наразі) тонкість стосується пов’язаного поняття — ключових стовпців (key columns).
Зазвичай ми визначаємо рядок у об’єкті Tabular, просто вказуючи його позицію. Але якщо значення певного стовпця є унікальними, ми можемо використовувати їх для посилання на конкретний рядок. Розгляньмо ось такий приклад Tabular:

 

Стовпець fruit має особливість — кожне значення у ньому зустрічається лише один раз. Тож ми можемо створити Tabular, який використовує цей стовпець як ключовий:

 

Зверніть увагу, що номери рядків тепер зникли, а ключовий стовпець позначений сірим фоном. У цьому Tabular ви можете посилатися на конкретний рядок, наприклад, за допомогою функції RowKey:

 

Аналогічно, ви також можете використовувати асоціацію з ім’ям стовпця:

 

Що робити, якщо значень у одному стовпці недостатньо, щоб унікально ідентифікувати рядок, але разом кілька стовпців дають унікальність? (У реальному житті, наприклад, один стовпець містить імена, інший — прізвища, а третій — дати народження.) Тоді ви можете позначити всі ці стовпці як ключові:

 

Після цього ви зможете посилатися на рядок, вказуючи значення у всіх ключових стовпцях:

 

 

Таблиця Скрізь

Tabular — це важливий новий спосіб представлення структурованих даних у Wolfram Language. Він сам по собі є потужним інструментом, але його сила значно зростає завдяки інтеграції з усіма іншими можливостями Wolfram Language. Багато функцій одразу працюють із Tabular. А у версії 14.2 сотні функцій були вдосконалені, щоб використовувати особливі можливості Tabular.

Найчастіше це дає змогу працювати безпосередньо зі стовпцями в Tabular. Наприклад, маючи Tabular:

 

Ми можемо одразу створити візуалізацію на основі двох стовпців:

 

Якщо один зі стовпців містить категоріальні дані, ми це розпізнаємо і побудуємо графік відповідно:

 

Ще одна сфера, де Tabular одразу можна використовувати — це машинне навчання. Наприклад, ось так створюється функція-класифікатор, яка намагається визначити вид пінгвіна за іншими даними про нього:

 

Тепер ми можемо використовувати цю функцію-класифікатор, щоб передбачити вид пінгвіна за іншими даними про нього:

 

Ми також можемо взяти весь Tabular і побудувати графік простору ознак, позначивши точки за видами:

 

Або ми могли б «вивчити розподіл можливих пінгвінів»

 

і випадковим чином згенерувати 3 «вигаданих пінгвінів» з цього розподілу:

 

 

Алгебра із символічними масивами

Однією з ключових інновацій версії 14.1 стало запровадження символічних масивів — а також можливість створювати вирази, що включають векторні, матричні та масивні змінні, та брати їх похідні. У версії 14.2 ми робимо крок уперед у роботі з символічними масивами — вперше систематично автоматизуємо те, що раніше виконувалось вручну: алгебру з символічними масивами та спрощення виразів, що їх містять.

Почнемо з функції ArrayExpand. Наша давня функція Expand розрахована лише на розкриття звичайного множення, ефективно для скалярів — тому у цьому випадку вона нічого не робить:

 

Але у версії 14.2 ми також маємо ArrayExpand, яка виконає розкриття:

 

ArrayExpand працює з багатьма узагальненнями множення, які не є комутативними:

 

У такому прикладі нам насправді не потрібно нічого знати про a та b. Але іноді розкриття неможливе без, наприклад, знання їхніх розмірностей. Один зі способів вказати ці розмірності — задати умову в ArrayExpand:

 

Альтернативою є використання явної символічної змінної масиву:

 

Окрім розкриття узагальнених добутків за допомогою ArrayExpand, версія 14.2 також підтримує загальне спрощення виразів із символічними масивами:

 

Функція ArraySimplify спеціально виконує спрощення символічних масивів, залишаючи інші частини виразів без змін. Версія 14.2 підтримує багато видів спрощень масивів:

 

 

Ми могли б виконувати ці спрощення, не знаючи нічого про розмірності a та b. Але іноді без цієї інформації ми не можемо піти так далеко. Наприклад, якщо розмірності невідомі, отримуємо:

 

Але маючи інформацію про розмірності, ми можемо явно спростити це до одиничної матриці розміром n×n:

 

ArraySimplify також може враховувати симетрії масивів. Наприклад, давайте створимо символічну симетричну матрицю:

 

І тепер ArraySimplify може одразу спростити це:

 

Можливість виконувати алгебраїчні операції над цілими масивами у символічній формі — це дуже потужно. Але іноді також важливо працювати з окремими компонентами масивів. У версії 14.2 ми додали ComponentExpand, що дозволяє отримувати компоненти масивів у символічній формі.

Наприклад, ця функція бере вектор з 2 компонент і записує його як явний список із двома символічними компонентами:

 

Під капотом ці компоненти представлені за допомогою Indexed:

 

Ось визначник матриці розміром 3×3, записаний через символічні компоненти:

 

А ось піднесення матриці до степеня:

 

Маючи тривимірні вектори та , ми також можемо, наприклад, утворити векторний добуток (cross product):

 

а потім можемо виконати скалярний добуток (dot product) цього результату з оберненою матрицею:

 

 

Налаштування мови

Як щоденний користувач Wolfram Language, я дуже задоволений тим, наскільки легко мені вдається перекладати обчислювальні ідеї у код. Але чим більше ми полегшуємо цей процес, тим більше помічаємо нові можливості для вдосконалення мови. І у версії 14.2 — як і в усіх попередніх — ми додали низку «налаштувань мови».

Одна проста — але дуже корисна, особливо у поєднанні з Tabular — функція це Discard. Її можна розглядати як доповнення до Select: вона відкидає елементи за вказаною вами умовою:

 

Разом із додаванням Discard, ми також покращили Select. Зазвичай Select просто повертає список елементів, які задовольняють умову. Але у версії 14.2 ви можете вказати інший результат. У цьому прикладі ми просимо повернути “індекси” (тобто позиції) елементів, які вибирає NumberQ:

 

Те, що може бути корисним при роботі з дуже великими обсягами даних, — це можливість отримати бітовий вектор (bit vector) з функцій Select (та Discard), який надає бітову маску, що показує, які елементи вибрано, а які — ні:

 

До речі, ось як можна отримати кілька результатів за допомогою Select та Discard:

 

Коли ми говорили про Tabular, ми вже згадували MissingFallback. Ще одна функція, пов’язана з підвищенням надійності коду та обробкою помилок — це нова функція Failsafe. Припустімо, у вас є список, який містить деякі «невдалі» елементи. Якщо ви застосуєте функцію f до цього списку, вона виконуватиметься і для елементів з помилками, так само як і для всіх інших:

 

Але цілком можливо, що f не була розрахована на обробку таких «невдалих» вхідних даних. І саме тут на допомогу приходить Failsafe. Адже Failsafe[f][x] визначено так, що вона повертає f[x], якщо x не є невдалим значенням, і просто повертає саму помилку, якщо це так. Отже, тепер ми можемо безпечно застосовувати f до всього списку, знаючи, що вона ніколи не отримає на вхід елемент з помилкою:

 

Говорячи про складні випадки обробки помилок, ще одна нова функція у версії 14.2 — це HoldCompleteForm.

HoldForm дозволяє відображати вираз без стандартного його обчислення. Але — як і Hold — вона все ж допускає певні перетворення.

HoldCompleteForm, як і HoldComplete, повністю блокує всі такі перетворення. Тому, хоча HoldForm у цьому випадку трохи “плутається”, коли послідовність «розгортається», — HoldCompleteForm чітко зберігає початкову форму виразу без жодних змін:

 

HoldCompleteForm повністю утримує та відображає послідовність без жодних змін:

 

Ще одне вдосконалення, додане у версії 14.2, стосується функції Counts. Мені часто потрібно підраховувати елементи у списку, включно з тими, яких немає — щоб отримати 0, якщо певний елемент відсутній. За замовчуванням Counts підраховує лише ті елементи, які насправді присутні:

 

Але у версії 14.2 ми додали другий аргумент до функції Counts, який дозволяє вказати повний список елементів, які ви хочете підрахувати — навіть якщо деякі з них відсутні у списку:

 

Як останній приклад удосконалення мови у версії 14.2, згадаю AssociationComap. У версії 14.0 ми представили Comap як «ко-» (на кшталт «ко-функтор» тощо) аналог функції Map:

 

У версії 14.2 ми представляємо AssociationComap — «ко-» версію функції AssociationMap:

 

Уявіть це як зручний спосіб створювати марковані (позначені) таблиці, наприклад так:

 

 

Освітлення наших кольорів; Розваги до 2025 року

У 2014 році — для версії 10.0 — ми провели масштабне оновлення стандартних кольорів для всіх наших функцій графіки та візуалізації, запропонувавши, на нашу думку, добре рішення. (І, як ми щойно помітили, досить дивно, що в наступні роки багато бібліотек для графіки та візуалізації наче копіювали саме наш підхід!)

Минуло вже десять років, змінилися візуальні очікування та технології відображення, і ми вирішили, що настав час освіжити наші кольори для 2025 року.

Ось як виглядав типовий графік у версіях 10.0 — 14.1:

 

А ось той самий графік у версії 14.2:

 

За задумом, він досі цілком впізнаваний, але тепер має трохи більше «вогника».

Якщо на графіку більше кривих — більше кольорів. Ось стара версія:

 

А ось нова версія:

 

Гістограми теж стали яскравішими. Ось стара версія:

 

А ось нова:

 

Ось порівняння старих («2014») і нових («2025») кольорів:

 

Це ненав’язливо, але відчутно впливає на вигляд. Мушу зізнатися, що останніми роками мені часто доводилося підправляти кольори майже в кожному зображенні, яке я публікував у Wolfram Language. Але з новими кольорами це бажання зникло — і тепер я знову можу просто використовувати стандартні кольори без додаткових налаштувань!

 

LLM Оптимізація та потокове передавання

У середині 2023 року ми вперше представили програмний доступ до великих мовних моделей (LLM) у Wolfram Language за допомогою функцій, таких як LLMFunction та LLMSynthesize. Тоді ці функції вимагали доступу до зовнішніх сервісів LLM.

Але з випуском минулого місяця LLM Kit (разом із Wolfram Notebook Assistant) ми зробили ці функції доступними для всіх, хто має підписку на Notebook Assistant + LLM Kit. Після оформлення підписки ви можете використовувати програмні функції LLM у будь-якому місці і скрізь у версії 14.2 без додаткових налаштувань.

Також з’явилися дві нові функції: LLMSynthesizeSubmit та ChatSubmit. Вони призначені для отримання поступових результатів від LLM (і так, це важливо, принаймні наразі, бо LLM можуть працювати досить повільно). Подібно до CloudSubmit та URLSubmit, функції LLMSynthesizeSubmit і ChatSubmit є асинхронними: ви викликаєте їх, щоб запустити процес, який викликатиме відповідну обробну функцію щоразу, коли відбувається певна подія.

Обидві функції підтримують різноманітні події. Наприклад, подія “ContentChunkReceived” виникає щоразу, коли надходить частина контенту від LLM.

Ось як це можна використати:

 

Функція LLMSynthesizeSubmit повертає об’єкт типу TaskObject, а потім починає синтезувати текст у відповідь на заданий запит, викликаючи вашу функцію-обробник щоразу, коли надходить чергова частина тексту.

Після кількох хвилин LLM завершить процес синтезу тексту, і якщо ви запросите значення змінної c, побачите всі частини тексту, які він згенерував:

 

Давайте спробуємо ще раз, але тепер налаштуємо динамічне відображення для рядка s, а потім запустимо LLMSynthesizeSubmit, щоб накопичувати згенерований текст у цей рядок:

 

 

ChatSubmit is the analog of ChatEvaluate, but asynchronous—and you can use it to create a full chat experience, in which content is streaming into your notebook as soon as the LLM (or tools called by the LLM) generate it.

 

Оптимізація паралельних обчислень: запустіть усі машини!

Протягом майже 20 років у Wolfram Language існує зручна можливість виконувати паралельні обчислення за допомогою функцій, таких як ParallelMapParallelTable та Parallelize. Паралельні обчислення можуть виконуватися на кількох ядрах одного комп’ютера або на багатьох машинах у мережі. (Наприклад, у моїй поточній конфігурації зараз 7 машин із загальною кількістю 204 ядра.)

За останні кілька років, частково у відповідь на збільшення кількості ядер у сучасних комп’ютерах, ми поступово оптимізували процес запуску паралельних обчислень. І у версії 14.2 ми, так, паралелізували сам процес підготовки паралельних обчислень. Це означає, що, наприклад, мої 7 машин одночасно запускають свої паралельні ядра — і весь процес тепер завершується за лічені секунди, замість того, щоб тривати хвилинами, як раніше:

 

Ще одна нова можливість для паралельних обчислень у версії 14.2 — це автоматична паралелізація по кількох змінних у ParallelTable. Раніше ParallelTable уже мав різні алгоритми для оптимізації розподілу обчислень між ядрами. Тепер цю функціональність розширено, щоб працювати з кількома змінними одночасно:

 

Як людина, яка регулярно виконує масштабні обчислення у Wolfram Language, я важко можу переоцінити, наскільки безперебійно для мене працюють можливості паралельних обчислень. Зазвичай я спочатку розробляю обчислення за допомогою MapTable тощо, а коли готовий виконати повну версію — замінюю на ParallelMapParallelTable і так далі. І дивовижно, наскільки суттєво це прискорює роботу — часто в 200 разів (за умови, що обчислення не має великого накладного трафіку для комунікації).

До речі, про накладні витрати на комунікацію: у версії 14.2 з’явилися дві нові функції — ParallelSelect та ParallelCases, які дозволяють вибирати та знаходити випадки у списках паралельно, зменшуючи накладні витрати, оскільки назад до головного ядра передаються лише фінальні результати. Хоча ця функціональність фактично вже була доступна через конструкції типу Parallelize[… Select[…] …], у версії 14.2 її значно спростили та оптимізували.

 

Дотримуйтесь цього ____! Відстеження у відео

Припустимо, у нас є відео, наприклад, із людьми, які проходять через вокзал. Деякий час ми вже могли взяти один кадр такого відео і виявити на ньому людей. Але у версії 14.2 з’явилася нова можливість — відстеження об’єктів, які рухаються між кадрами відео.

Почнемо з самого відео:

 

 

Ми могли б взяти окремий кадр і знайти обмежувальні рамки (bounding boxes) для зображення. Але починаючи з версії 14.2 ми можемо застосувати ImageBoundingBoxes одразу до всього відео:

 

Потім ми можемо використати ці дані про обмежувальні рамки, щоб виділити людей у відео — за допомогою нової функції HighlightVideo:

 

 

 

Але це просто окремо показує, де люди знаходяться в кожному кадрі; воно не пов’язує їх між кадрами. У версії 14.2 ми додали VideoObjectTracking, щоб відстежувати об’єкти між кадрами:

 

 

 

Тепер, якщо використати HighlightVideo, різні об’єкти будуть позначені різними кольорами:

 

 

 

 

Це виділяє всі унікальні об’єкти, ідентифіковані протягом відео, та рахує їх кількість:

 

«Де ж собака?» — можете запитати ви. Вона справді з’являється недовго:

 

А якщо подивитися на перший кадр, де собака мала б з’явитися, здається, що те, що, ймовірно, є людиною в нижньому правому куті, помилково прийняли за собаку:

 

Так, саме це воно й визначило як собаку:

 

 

Теорія ігор

«А як щодо теорії ігор?» — давно запитують люди. І так, у Wolfram Language вже давно реалізовано багато функцій для теорії ігор, а також створено чимало пакетів для окремих її аспектів. Але у версії 14.2 ми нарешті вводимо вбудовані системні функції для роботи з теорією ігор (як з матричними, так і з деревовидними іграми).

Ось як можна задати (нуль-сумну) двогравцеву матричну гру:

 

Це визначає виграші для кожного гравця при кожній дії опонента. Ми можемо подати це у вигляді датасету:

 

Альтернативний варіант — «побудувати графік гри» за допомогою MatrixGamePlot:

 

Отже, як нам «розв’язати» цю гру? Іншими словами, яку дію має обрати кожен гравець і з якою ймовірністю, щоб максимізувати свій середній виграш за багаторазового повторення гри? (Припускається, що в кожному раунді гравці одночасно та незалежно обирають дії.)

«Розв’язанням» називають набір стратегій, які максимізують очікуваний виграш для обох гравців — і саме це називається рівновагою Неша.
(До речі, John Nash, автор цієї концепції, багато років користувався Mathematica — нині Wolfram Language — хоча це було вже після того, як він сформулював свою теорію.)

Тож тепер, у версії 14.2, функція FindMatrixGameStrategies дозволяє знаходити оптимальні стратегії (тобто рівновагу Неша) для матричних ігор:

 

Цей результат означає, що в цій грі гравець 1 повинен обирати дію 1 з імовірністю , а дію 2 — з імовірністю , тоді як гравець 2 має виконувати свої дії з імовірностями і відповідно. Але якими будуть їхні очікувані виграші? Це обчислює функція MatrixGamePayoff:

 

Відстежувати різні варіанти у грі може бути досить складно, тому MatrixGame дозволяє задавати будь-які мітки (назви) для гравців і дій:

 

Ці мітки потім використовуються у візуалізаціях:

 

Те, що ми щойно показали, насправді є класичним прикладом — «дилема в’язня». У Wolfram Language тепер доступна функція GameTheoryData, яка містить репозиторій приблизно з 50 стандартних ігор. Ось приклад однієї з них, заданої для 4 гравців:

 

І розв’язати цю гру вже не так просто, але ось результат — 27 різних розв’язків:

 

І так, візуалізації продовжують працювати навіть із більшою кількістю гравців (тут ми показуємо випадок з 5 гравцями та позначаємо 50-й розв’язок гри):

 

Варто згадати, що розв’язання таких ігор відбувається завдяки новітнім можливостям розв’язування поліноміальних рівнянь у Wolfram Language. І що важливо — ми можемо знаходити всі можливі рівноваги Неша, а не лише одну фіксовану точку, як це роблять деякі інші системи. Більше того, ми можемо отримувати точні (аналітичні) результати, а не лише числові наближення.

 

Окрім матричних ігор, які моделюють ситуації, де гравці одночасно вибирають свої дії лише один раз, ми також підтримуємо деревовидні ігри, у яких гравці ходять по черзі, утворюючи дерево можливих результатів, що завершується заданими виграшами для кожного з гравців. Ось приклад дуже простої деревовидної гри:

 

Ми можемо знайти принаймні одне розв’язання цієї гри — у вигляді вкладеної структури, яка задає оптимальні ймовірності для кожної дії кожного гравця на кожному кроці:

 

Ситуації з деревовидними іграми можуть бути значно складнішими. Ось приклад, у якому деякі гравці часом не знають, які гілки були обрані (що позначено станами, з’єднаними пунктирними лініями):

 

Те, що ми отримали у версії 14.2, охоплює практично всі базові концепції типової вступної курсу з теорії ігор. Але тепер, у звичному стилі Wolfram Language, усе це є обчислюваним і розширюваним — тож ви можете досліджувати більш реалістичні ігри та швидко виконувати багато прикладів для формування інтуїції.

До цього часу ми зосереджувалися на «класичній теорії ігор», зокрема на особливості (важливій для багатьох сучасних застосувань), що всі вузли дій відповідають різним послідовностям ходів. Однак ігри, як-от хрестики-нулики (які я нещодавно вивчав за допомогою мультишляхових графів), можна спростити, об’єднавши еквівалентні вузли дій. Адже різні послідовності ходів можуть вести до однакової гри в хрестики-нулики, що часто трапляється в іграх з ітераціями. Такі графові структури не вписуються у типові деревовидні моделі класичної теорії ігор, представлені у версії 14.2 — проте, як показують мої власні дослідження, вони чудово піддаються аналізу за допомогою Wolfram Language.

 

Обчислення сизигій та інші досягнення в астрономії

У астрономії існує багато «співпадінь» — ситуацій, коли об’єкти розташовуються особливим чином. Наприклад, затемнення — це один із таких випадків. Але їх набагато більше. У версії 14.2 з’явилася загальна функція FindAstroEvent, яка шукає ці «співпадіння», технічно відомі як сизигії («sizz-ee-gees»), а також інші «спеціальні конфігурації» астрономічних об’єктів.

Простий приклад — осіннє рівнодення у вересні:

 

Приблизно це той момент, коли день і ніч мають однакову тривалість. Точніше — це той час, коли Сонце знаходиться в одній із двох точок на небі, де площина екліптики (тобто орбітальна площина Землі навколо Сонця) перетинає небесний екватор (проекція земного екватора) — як показано на цьому малюнку (жовта лінія — екліптика; синя — небесний екватор):

 

Як ще один приклад, давайте знайдемо наступний раз упродовж наступного століття, коли Юпітер і Сатурн будуть найближче один до одного на небі:

 

Вони підійдуть настільки близько, що можна буде одночасно побачити їхні супутники:

 

Існує неймовірна кількість астрономічних конфігурацій, яким історично надавалися спеціальні назви: рівнодення, сонцестояння, еквілюкси, кульмінації, з’єднання, протистояння, квадратури, а також періапси та апоапси (зокрема такі, як перигей, перигелій, периареон, периджове, перікроне, периуран, періпосейдей тощо). У версії 14.2 ми підтримуємо всі ці поняття.

Наприклад, ця команда дає наступний момент, коли Тритон буде найближчим до Нептуна:

 

Відомий приклад стосується перигелію (найближчого підходу до Сонця) Меркурія. Давайте обчислимо положення Меркурія (з точки зору Сонця) в усі його перигелії протягом перших кількох десятиліть XIX століття:

 

Ми бачимо, що відбувається систематичне «просування» (поряд з деякими коливаннями):

 

Отже, тепер давайте кількісно обчислимо це просування. Почнемо з визначення моментів перших перигеліїв у 1800 та 1900 роках:

 

Тепер обчислимо кутову відстань між положеннями Меркурія в ці моменти:

 

Потім поділимо це значення на різницю в часі:

 

і перетворимо одиниці виміру:

 

Відомо, що 43 кутові секунди на століття цієї прецесії зумовлені відхиленнями від закону обернених квадратів у гравітації, які передбачені загальною теорією відносності — і, звісно, це враховано в нашій астрономічній обчислювальній системі. (Інша частина просування пояснюється традиційними гравітаційними впливами Венери, Юпітера, Землі та інших планет.)

 

PDE тепер також для магнітних систем

Більше ніж півтора десятиліття тому ми взяли на себе зобов’язання зробити Wolfram Language повноцінним середовищем для моделювання диференціальних рівнянь у частинних похідних (PDE). Звичайно, допомагало те, що ми могли спиратися на всі інші можливості Wolfram Language — і те, що ми створили, стало набагато ціннішим завдяки синергії з іншими частинами системи. Протягом років, докладаючи багато зусиль, ми поступово розвивали символічні можливості моделювання PDE у всіх основних сферах. І наразі можна сміливо сказати, що ми здатні працювати з PDE-моделюванням у промисловому масштабі для багатьох реальних задач. Втім, завжди є нові випадки, для яких варто розробляти додаткові інструменти. У версії 14.2 ми додаємо вбудовані примітиви для моделювання статичних та квазистатичних магнітних полів.

Наприклад, ось як тепер можна змоделювати магніт у формі піскового годинника. Тут визначаються граничні умови, а потім розв’язуються рівняння для магнітного скалярного потенціалу:

 

Потім ми можемо взяти цей результат і, наприклад, одразу побудувати лінії магнітного поля, які він задає:

 

У версії 14.2 також додано примітиви для роботи зі слабо змінними електричними струмами та магнітними полями, які вони створюють. Вся ця функціональність миттєво інтегрується з іншими нашими доменами моделювання — такими як теплоперенос, динаміка рідин, акустика тощо.

Про моделювання диференціальних рівнянь у частинних похідних (PDE) та його застосування можна розповідати дуже багато. У версії 14.2 ми додали понад 200 сторінок підручникової документації, включно з прикладами дослідницького рівня.

 

Нові функції в графіці, геометрії та графіках

Графіка завжди була сильною стороною Wolfram Language, а за останнє десятиліття ми також значно розвинули можливості обчислювальної геометрії. Версія 14.2 додає ще більше «вишеньок на торті», особливо у зв’язку графіки з геометрією та геометрії з іншими частинами системи.

Наприклад, у версії 14.2 з’явилися можливості працювати з геометрією для тих об’єктів, які раніше були лише графічними примітивами. Ось геометрична область, утворена заповненням кривої Безьє:

 

І тепер ми можемо виконувати всі звичні операції обчислювальної геометрії з цією областю:

 

Тепер також працює ось таке:

 

Ще одна новинка у версії 14.2 — це MoleculeMesh, яка дозволяє створювати обчислювану геометрію на основі молекулярних структур. Ось графічне відображення молекули:

 

А ось тепер — геометрична сітка, що відповідає цій молекулі:

 

Потім ми можемо виконувати обчислювальні геометричні операції над цією сіткою:

 

 

Ще одна новинка у версії 14.2 — додатковий метод для візуалізації графів, який враховує симетрії. Якщо створити шарований граф із симетричної сітки, він одразу не відображатиметься симетрично:

 

Але з новим макетом графа “SymmetricLayeredEmbedding” він відображатиметься саме симетрично:

 

 

Налаштування інтерфейсу користувача

Створення чудового інтерфейсу користувача — це завжди процес постійного вдосконалення, і ми працюємо над інтерфейсом ноутбука вже майже чотири десятиліття. У версії 14.2 з’явилося кілька помітних покращень. Одне з них стосується автозаповнення значень опцій.

Ми давно показували автозаповнення для опцій із дискретним набором стандартних значень (наприклад, AllAutomatic тощо). У версії 14.2 ми додаємо так звані «шаблонні автозаповнення» — вони показують структуру налаштувань і дають змогу за допомогою клавіші Tab переходити між параметрами, щоб швидко заповнити потрібні значення. Наприклад, я дуже часто звертаюся до документації щодо налаштувань FrameLabel, але тепер автозаповнення одразу показує структуру цих налаштувань:

Interface settings autocompletion

Ще одне покращення автозаповнення у версії 14.2 — тепер воно підтримує автозаповнення імен контекстів, їхніх псевдонімів, а також символів із включеними контекстами. І в усіх випадках автозаповнення є «фаззіз» — тобто спрацьовує не тільки за початковими літерами, а й за символами з будь-якої частини імені. Це означає, що ви можете вводити будь-які символи з назви, і відповідні контексти чи символи з’являться у списку автозаповнення.

Ще одна маленька зручність у версії 14.2 — можливість перетягувати зображення з одного ноутбука в інший, або навіть у будь-яку іншу програму, яка приймає перетягування зображень. Раніше можна було перетягувати зображення з інших додатків у ноутбуки, а тепер це можна робити й у зворотньому напрямку.

Ще одна новинка, поки що специфічна для macOS, — покращена підтримка попереднього перегляду іконок (а також Quick Look). Тож якщо у вас є папка з ноутбуками, і ви оберете режим перегляду Icon, то побачите невеликі іконки, що відображають вміст кожного ноутбука:

Notebook icon preview

Під капотом у версії 14.2 також відбулися важливі інфраструктурні оновлення, які відкриють шлях до значних нових можливостей у майбутніх версіях. Деякі з них пов’язані з розширеною підтримкою темного режиму. (Так, на перший погляд може здатися, що темний режим — це просто, але коли врахувати всі графічні та інтерфейсні елементи з кольорами, стає очевидно, що це далеко не так. Наприклад, після великих зусиль ми нещодавно випустили темний режим для Wolfram|Alpha.)

У версії 14.2 з’явився новий символ LightDarkSwitched, який є частиною механізму задання стилів, що автоматично перемикаються між світлим і темним режимами. Також існує опція стилю LightDark, яка дозволяє переключати режими для ноутбуків і вже підтримується принаймні експериментально.

Пов’язана з темним/світлим режимом ідея тематичних кольорів — символічних кольорів, які можна змінювати разом. Для цього також є експериментальний символ ThemeColor. Але повне впровадження цього механізму очікується у наступній версії.

 

Початок переходу на нативний графічний процесор

Багато важливих функцій у Wolfram Language автоматично використовують GPU, коли вони доступні. Ще 15 років тому ми представили примітиви для низькорівневого програмування GPU. Але у версії 14.2 ми починаємо процес полегшення доступу до можливостей GPU для оптимізації загального використання Wolfram Language. Ключовим нововведенням є об’єкт GPUArray — масив даних, який (за можливості) зберігається так, щоб бути безпосередньо доступним для вашого GPU. (На деяких системах він зберігатиметься у виділеній «GPU-пам’яті», на інших, наприклад на сучасних Mac, — у спільній пам’яті, доступній безпосередньо GPU.)

У версії 14.2 підтримується початковий набір операцій, які можна виконувати безпосередньо на GPU-масивах. Набір операцій трохи відрізняється залежно від типу GPU. З часом ми плануємо використовувати або створювати багато додаткових бібліотек GPU, які розширять перелік підтримуваних операцій. Ось приклад випадкового вектору з десятьма мільйонами елементів, збереженого як GPUArray:

 

GPU на Mac, на якому я зараз пишу, підтримує необхідні операції для виконання цих обчислень повністю на GPU, повертаючи результат у вигляді GPUArray:

 

Ось час виконання:

 

А ось відповідний результат, отриманий на звичайному (CPU) процесорі:

 

У цьому випадку результат із GPUArray приблизно вдвічі швидший. Насправді приріст продуктивності залежить від операцій, які ви виконуєте, та від вашого апаратного забезпечення. Найбільші прирости, які я бачив досі, — приблизно 10 разів. Але з часом, коли ми додаватимемо більше бібліотек для GPU, очікую, що цей коефіцієнт збільшиться, особливо якщо обчислення переважно відбуваються безпосередньо на GPU і при цьому не надто інтенсивні операції з пам’яттю.

До речі, якщо ви додаєте GPUArray у свій код, це зазвичай не впливає на результати — бо операції за замовчуванням виконуються на CPU, якщо вони не підтримуються на GPU. Зазвичай GPUArray пришвидшує виконання, але якщо занадто багато операцій не підтримуються на GPU (так звані «GPU misses»), спроби передати дані туди-сюди можуть навіть уповільнити роботу.

Варто пам’ятати, що GPU-обчислення досі не стандартизовані й неоднорідні: іноді підтримуються лише вектори, іноді — матриці; бувають різні типи даних і рівні числової точності, які по-різному підтримуються на різних системах.

 

І навіть більше…

Окрім усіх згаданих великих новинок, у версії 14.2 також додано багато «дрібних» функцій, які, хоч і можуть здаватися невеликими, стануть дуже корисними, якщо саме ця можливість вам потрібна.

Наприклад, функція MidDate — вона обчислює середину (середню точку) між двома датами:

 

Як і майже всі функції, пов’язані з датами, MidDate має безліч нюансів. Ось приклад, де вона обчислює дату, що знаходиться на відстані двох третин від початку року (тиждень 2/3 шляху через цей рік):

 

У математиці такі функції, як DSolve і SurfaceIntegrate тепер можуть працювати зі змінними символьного масиву:

 

Функція SumConvergence тепер дозволяє вказувати діапазон підсумовування й може повертати умови, що залежать від цього діапазону:

 

Маленьке, але зручне доповнення (так, це була моя особиста пропозиція): тепер функція DigitCount дозволяє вказувати, скільки загалом цифр слід вважати у числі — щоб правильно враховувати початкові нулі:

 

Говорячи про зручності — для таких функцій, як MaximalBy та TakeLargest, ми додали новий аргумент, який визначає, як саме впорядковувати елементи, щоб визначити «найбільші». Ось, наприклад, стандартне числове впорядкування:

 

А ось що відбувається, якщо замість цього ми використаємо «символічне впорядкування»:

 

Завжди є безліч деталей, які потребують доопрацювання. Наприклад, у версії 14.2 оновлено функцію MoonPhase та пов’язані з нею — додано нові параметри для запитів, а також нові методи для їх обчислення:

 

 

У ще одній області — окрім основних нових форматів імпорту/експорту (зокрема для підтримки Tabular) — оновлено імпорт “Markdown“, який тепер повертає результат у вигляді звичайного тексту, а також оновлено імпорт “PDF“, який тепер повертає змішаний список із тексту та зображень.

Є й багато інших нововведень, про які можна дізнатися зі сторінки «Summary of New and Improved Features in 14.2». До речі, варто пам’ятати: якщо ви відкрили сторінку документації певної функції, ви завжди можете дізнатися, що саме в ній нового у цій версії — просто натисніть “show changes” (показати зміни).

Show changes