Стартиране на версия 14.2 на Wolfram Language & Mathematica: Big ​​Data Meets Computation & AI

Барабанният ритъм на изданията продължава…

Преди малко по-малко от шест месеца (точно преди 176 дни) пуснахме Версия 14.1. Днес с удоволствие обявявам, че пускаме Версия 14.2, която предлага най-новото от нашата изследователска и развойна дейност.

Това е вълнуващ момент за нашата технология – както по отношение на това, което сега можем да реализираме, така и по отношение на начина, по който нашата технология вече се използва в света. Характерна черта на днешното време е нарастващото използване на Wolfram Language не само от хора, но и от изкуствен интелект (AI). И е наистина приятно да видим, че всички усилия, които сме вложили през годините в последователния дизайн на езика, неговата реализация и документация, сега дават плодове – превръщайки Wolfram Language в уникално ценен инструмент за AI, допълвайки техните собствени вътрешни възможности.

Но има и друга гледна точка към изкуствения интелект. С пускането на Wolfram Notebook Assistant миналия месец, ние използваме AI технологии (и още много други), за да създадем нещо като разговорен интерфейс към Wolfram Language. Както описах при представянето на Wolfram Notebook Assistant, това е изключително полезен инструмент както за експерти, така и за начинаещи, но в крайна сметка вярвам, че най-важното му следствие ще бъде ускоряването на възможността за преминаване от всяка област Х към „изчислителна Х“ – чрез използване на цялата технологична кула, която сме изградили около 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 и да предоставим на всички възможността да използват всичко, върху което сме работили толкова усилено.

Чат на Notebook Assistant във всеки Notebook

Миналия месец пуснахме Wolfram Notebook Assistant, за да „превръща думите в изчисления“ – и да помогнем както на експерти, така и на начинаещи да използват по-широко и по-задълбочено технологиите на Wolfram Language. В Версия 14.1 основният начин за използване на Notebook Assistant беше чрез отделния прозорец с „страничен чат“. Но в Версия 14.2 „чат клетките“ се превръщат в стандартна функция на всеки notebook, достъпна за всички с абонамент за Notebook Assistant.

Просто въведете като първи символ в някоя клетка и тя ще се превърне в чат клетка:

Chat cell

Сега можете да започнете разговор с Notebook Assistant:

 

Със страничния чат разполагате с „отделен канал“ за комуникация с Notebook Assistant – който, например, няма да се запази заедно с вашия notebook. С чат клетките обаче, разговорът ви става неразделна част от самия notebook.

Всъщност, първо представихме Chat Notebooks в средата на 2023 г. – само няколко месеца след появата на ChatGPT. Chat Notebooks дефинираха интерфейса, но по онова време съдържанието на чат клетките идваше изцяло от външни LLM-и. Сега, във Версия 14.2, чат клетките вече не са ограничени само до Chat Notebooks, а са достъпни във всеки notebook. И по подразбиране те използват пълния технологичен стек на Notebook Assistant, който далеч надхвърля възможностите на обикновен LLM. Освен това, ако имате абонамент за Notebook Assistant + LLM Kit, можете безпроблемно да използвате чат клетките – без да е необходим акаунт при външен доставчик на LLM.

Функционалността на чат клетките във Версия 14.2 наследява всички функции на Chat Notebooks. Например, въвеждането на ~ в нова клетка създава чат прекъсване, което ви позволява да започнете „нов разговор“. А когато използвате чат клетка, тя има достъп до всичко в notebook-а ви, до последното чат прекъсване. (Между другото, когато използвате Notebook Assistant чрез страничния чат, той също вижда какво сте избрали във вашия „фокусиран“ notebook.)

По подразбиране чат клетките „говорят“ с Notebook Assistant. Но ако желаете, можете да ги използвате и за разговор с външни LLM-и, точно както в оригиналния Chat Notebook – а за това има удобно меню за настройка. Разбира се, ако използвате външен LLM, няма да разполагате с всички технологии, които сега са включени в Notebook Assistant, и освен ако не правите изследвания с LLM-и, в повечето случаи ще е много по-полезно и ценно да използвате чат клетките в тяхната стандартна конфигурация – за разговор с Notebook Assistant.

 

Донесете ни своите гигабайти! Представяме ви табличен

Списъци, асоциации, набори от данни (datasets) — това са много гъвкави начини за представяне на структурирани колекции от данни в 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:

 

А сега можем да начертаем резултата, показвайки позициите на дърветата от вида „вирджински бор“ (Virginia pine) в Ню Йорк:

 

Кога трябва да използвате Tabular, вместо например Dataset? Tabular е специално създаден за данни, подредени в редове и колони — и поддържа множество мощни операции, които имат смисъл именно за такива „правоъгълни“ структури от данни. Dataset е по-общ — той може да съдържа произволна йерархия от измерения на данни и затова по принцип не може да поддържа всички операции, характерни за „правоъгълни“ данни, както Tabular. Освен това, понеже е специализиран за таблични структури, Tabular може да бъде и значително по-ефективен — и действително използва най-новите методи, базирани на типове, за работа с големи обеми данни.

Ако използвате TabularStructure, ще видите част от причините, поради които Tabular е толкова ефективен. Всяка колона се третира като данни от конкретен тип (и да, типовете са съвместими с тези в компилатора на Wolfram Language). Освен това има оптимизирано управление на липсващи стойности — с няколко нови функции, добавени специално за работа с такива случаи:

 

Досега разгледахме как Tabular работи с данни, заредени „в паметта“ (in-core). Но също толкова прозрачно можете да използвате Tabular и с данни „извън паметта“ (out-of-core), например такива, които са съхранени в релационна база данни.

Ето пример как изглежда това:

 

Това е Tabular, който сочи към таблица в релационна база данни. По подразбиране той не показва изрично данните в Tabular (и всъщност дори не ги зарежда в паметта — защото те може да са огромни и дори да се променят бързо). Но въпреки това можете да задавате операции върху него точно както при всеки друг Tabular. Ето как можем да разберем кои колони са налични:

 

А това задава операция, като резултатът се връща под формата на символичен out-of-core Tabular обект:

 

Можете да „разрешите“ това и да получите явен 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, за да посочите конкретен ред, като зададете стойност от ключова колона:

 

Но нека продължим с нашите усилия за агрегиране. Да кажем, че искаме да групираме не само по вид (species), но и по остров (island). Ето как можем да направим това с помощта на AggregateRows:

 

В известен смисъл тук имаме таблица, чиито редове са определени от двойки стойности (в случая „species“ и „island“). Но често е удобно да „завъртим“ представянето така, че тези стойности да се използват съответно за редове и за колони. Това може да се направи с помощта на PivotTable:

 

Обърнете внимание на —, които показват липсващи стойности — очевидно няма пингвини от вида Gentoo на остров Dream и т.н.

Обикновено PivotTable представя същите данни като AggregateRows, но в пренареден вид. Една допълнителна възможност на PivotTable е опцията IncludeGroupAggregates, която добавя стойности All, агрегиращи по всяка група:

 

Ако изчислявате няколко функции, AggregateRows просто ще ги представи като отделни колони:

 

PivotTable също може да работи с няколко функции — като създава колони с „разширени ключове“:

 

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

 

 

Получаване на данни в табличен вид

Видяхме някои от възможностите, когато разполагаме с данни във формат Tabular. Но как всъщност се вкарват данни в Tabular? Има няколко начина. Първият е просто да конвертирате структури като списъци и асоциации. Вторият е да импортирате от файл, например CSV или XLSX (или за по-големи обеми данни — Parquet) — или от външно хранилище като S3, Dropbox и други. Третият е да се свържете с база данни. Можете също да получите данни за Tabular директно от Wolfram Knowledgebase или от Wolfram Data Repository.

Ето как можете да конвертирате списък от списъци в Tabular:

 

А ето как можете да конвертирате обратно:

 

Работи и със разредени масиви (sparse arrays), като тук мигновено създава Tabular с един милион реда:

 

което заема 80 MB памет за съхранение:

 

Ето какво се случва с един списък от асоциации:

 

Можете да получите същия Tabular, като въведете данните и имената на колоните поотделно:

 

Между другото, можете да конвертирате Tabular в Dataset:

 

И в този прост случай можете да конвертирате обратно в Tabular също така:

 

Като цяло има много опции за това как да конвертирате списъци, набори от данни (datasets) и други в обекти от тип Tabular — и ToTabular е проектиран да ви даде контрол върху този процес. Например, с ToTabular можете да създадете Tabular от колони, а не от редове:

 

А какво да кажем за външните данни? Във версия 14.2 Import вече поддържа елемент „Tabular“ за таблични формати на данни. Така, например, ако имате CSV файл:

CSV file

Import може веднага да го импортира като Tabular:

 

Това работи изключително ефективно дори при огромни CSV файлове с милиони записи. Също така успешно разпознава автоматично имената на колоните и заглавките. Подобен подход важи и за по-структурирани файлове, като тези от електронни таблици и статистически формати. Освен това поддържа и съвременни колонарни формати за съхранение като Parquet, ORC и Arrow.

Import прозрачно обработва както обикновени файлове, така и URL адреси (и URI-та), като иска удостоверяване при необходимост. Във версия 14.2 добавяме новата концепция DataConnectionObject, която предоставя символно представяне на отдалечени данни, обхващайки всички детайли за това как да се достъпят тези данни. Например, ето един DataConnectionObject за S3 bucket, чието съдържание можем веднага да импортираме:

 

(Във версия 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:

 

Когато разглеждаме различните числови колони, виждаме, че те всъщност представляват величини, които трябва да имат мерни единици. Но първо, за удобство, нека преименуваме последните две колони:

 

Сега нека преобразуваме числовите колони във величини с мерни единици и — докато сме на тази стъпка — също така преобразуваме от °C към °F:

 

Ето как сега можем да изобразим температурата като функция на времето:

 

Тук има доста колебания. А при преглед на данните виждаме, че получаваме температурни стойности от няколко различни метеорологични станции. Ето как можем да изберем данните само от една станция:

 

Какво причинява прекъсването в кривата? Ако просто превъртим до тази част на табличните данни, ще видим, че причината е липсваща информация:

 

Какво можем да направим по този въпрос? Е, има мощната функция TransformMissing, която предлага множество възможности. Тук я използваме, за да интерполира и попълни липсващите стойности за температурата:

 

И сега вече няма пропуски, но — малко загадъчно — цялата графика се простира по-далеч:

 

Причината е, че се извършва интерполация дори в случаите, когато практически нищо не е било измерено. Можем да премахнем тези редове с помощта на Discard:

 

И сега вече няма да имаме това „препокриване“ в края:

 

Понякога явно липсват данни; понякога (по-коварно) данните просто са грешни. Нека разгледаме хистограмата на стойностите на налягането в нашите данни:

 

Опа. Какви са тези малки стойности? Вероятно са грешни. (Може би са грешки при преписване?) Можем да премахнем такива „аномални“ стойности с помощта на TransformAnomalies. Тук казваме да изтрие изцяло всеки ред, в който налягането е било „аномално“:

 

Можем също да накараме TransformAnomalies да се опита да „поправи“ данните. Тук просто заменяме всяко аномално налягане с предишната стойност на налягането в табличния обект:

 

Можеш също да накараш TransformAnomalies да „отбележи“ всяка аномална стойност и да я направи „липсваща“. Но ако имаме липсващи стойности, какво се случва, когато опитаме да правим изчисления с тях? Тук влиза в игра MissingFallback. Това по същество е много проста функция — която просто връща първия си аргумент, който не е липсващ:

 

Въпреки че е проста, функцията MissingFallback е важна, защото улеснява обработката на липсващи стойности. Например, ето как се изчислява „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 връща списък с тях:

 

Това са ниско-нивoви типове, използвани в компилатора на Wolfram Language. Познаването им ни позволява веднага да разберем какви операции можем да извършим върху конкретна колона. Това е полезно както при ниско-нивoва обработка, така и при определяне какъв вид визуализация е възможна.

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

 

 

Между другото, когато един обект Tabular се показва в тетрадка (notebook), заглавията на колоните индикират типовете данни в съответните колони. Така например, в първата колона има малка кавичка която показва, че съдържа низове. Числата и датите обикновено просто „показват какво са“. Величините имат обозначени мерните единици. А общите символни изрази (като колоната „f“ тук) са отбелязани с . (Ако задържите курсора върху заглавието на колона, ще получите повече информация за типа.)

Следващото, което трябва да обсъдим, е липсващите данни. Tabular винаги третира колоните като еднородни по тип, но поддържа цялостна карта къде стойности липсват. Ако извлечете колоната, ще видите символен обект Missing:

 

Но ако оперирате директно с колоната от табличния обект, тя ще се държи така, сякаш липсващите данни наистина липсват:

 

Между другото, ако импортирате данни „от дивото“, Import ще се опита автоматично да определи правилния тип за всяка колона. Той знае как да се справя с често срещани аномалии в данните, като NaN или null в колона с числа. Но ако има други странни стойности — например notfound в средата на числова колона — можете да кажете на Import да ги превърне в обикновени липсващи данни, като зададете тези стойности в опцията MissingValuePattern.

Има още няколко тънкости, които да обсъдим във връзка със структурата на обектите Tabular. Първата е понятието за разширени ключове. Нека да разгледаме следния пример с Tabular:

 

Можем да „разгърнем това към колони“, така че стойностите x и y да станат заглавия на колони, но „под“ общото заглавие на колоната „value“:

 

Но каква е структурата на този Tabular? Можем да използваме ColumnKeys, за да разберем:

 

Сега можете да използвате тези разширени ключове като индекси за обекта Tabular:

 

В този конкретен случай, тъй като „подключовете“ „x“ и „y“ са уникални, можем просто да ги използваме, без да включваме другата част от разширения ключ:

 

Нашата последна подробност (за момента) е свързана с колоните ключове. Обикновено посочваме ред в обект Tabular, като задаваме позицията му. Но ако стойностите в определена колона са уникални, можем да ги използваме, за да посочим реда. Нека разгледаме този Tabular:

 

Колоната „fruit“ има особеността, че всяка стойност се среща само веднъж — затова можем да създадем Tabular, който използва тази колона като ключова:

 

Обърнете внимание, че номерата на редовете вече са изчезнали, а колоната ключ е маркирана със сив фон. В този Tabular можете да достъпите конкретен ред, използвайки например RowKey:

 

Еквивалентно, можете също да използвате асоциация с името на колоната:

 

Ами ако стойностите в една колона не са достатъчни, за да определят уникално ред, но няколко колони заедно са? (В реален пример – една колона съдържа имена, друга – фамилии, а трета – дати на раждане.) Тогава можете да посочите всички тези колони като ключови колони:

 

И след като сте направили това, можете да достъпите ред, като посочите стойностите във всички ключови колони:

 

 

Табличен навсякъде

Tabular предоставя нов важен начин за представяне на структурирани данни в езика Wolfram. Той е мощен сам по себе си, но това, което го прави още по-мощен, е начинът, по който се интегрира с всички останали възможности в езика Wolfram. Много функции просто работят директно с Tabular. Но във версия 14.2 стотици от тях са подобрени, за да използват специалните характеристики на Tabular.

Най-често това позволява директна работа с колони в обект Tabular. Така например, при даден обект Tabular…

 

можем веднага да създадем визуализация, базирана на две от колоните:

 

Ако една от колоните съдържа категорийни данни, ще ги разпознаем и ще ги изобразим по съответния начин:

 

Още една област, в която Tabular може да се използва директно, е машинното обучение. Например, следният код създава функция за класификация, която се опитва да определи вида на пингвин на базата на други данни за него:

 

Сега можем да използваме тази функция за класификация, за да предвидим вида на пингвин въз основа на други данни за него:

 

Можем също така да използваме целия обект Tabular и да създадем графика на пространството от признаци (feature space plot), като оцветим/етикираме точките според вида:

 

Или можем да „научим разпределението на възможните пингвини“:

 

и на случаен принцип да генерираме 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, която ви позволява да получите компонентите на масиви в символна форма.

Например, тази команда взема вектор с два компонента и го записва като изричен списък с два символни компонента:

 

Вътрешно тези компоненти се представят чрез Indexed:

 

Ето детерминантата на матрица 3×3, изразена чрез символни компоненти:

 

А ето и степенуване на матрица:

 

Дадени 3D вектори и , можем например да образуваме и векторното произведение (cross product):

 

и след това можем да го умножим скаларно (dot product) с обратна матрица:

 

 

Езикови настройки

Като ежедневен потребител на Wolfram Language съм много доволен колко плавно мога да превръщам изчислителни идеи в код. Но колкото повече улесняваме това, толкова повече откриваме нови възможности да усъвършенстваме езика. И във версия 14.2 — както и във всяка предишна версия — сме добавили редица „настройки и подобрения“ на езика.

Едно просто, но много полезно допълнение — особено ясно при работа с Tabular — е функцията Discard. Можем да я разглеждаме като допълнение към Select: тя премахва елементи според зададения от вас критерий:

 

Заедно с добавянето на Discard, сме подобрили и функцията Select. Обикновено Select връща списък с елементите, които избира. Но във версия 14.2 вече можете да зададете и други резултати. Тук искаме да получим „индекса“ (т.е. позицията) на елементите, които NumberQ избира:

 

Нещо полезно при работа с много големи обеми данни е възможността да получим битова векторна структура от SelectDiscard), която предоставя битова маска за това кои елементи са избрани и кои не:

 

Между другото, ето как можете да поискате няколко резултата едновременно от Select и Discard:

 

Когато говорехме за Tabular, вече споменахме функцията MissingFallback. Друга функция, свързана с подобряване на стабилността на кода и обработката на грешки, е новата функция Failsafe. Да предположим, че имате списък, съдържащ някои „неуспешни“ елементи. Ако приложите функцията f към този списък, тя ще се приложи еднакво и към неуспешните елементи, както и към останалите:

 

Но съвсем възможно е функцията f да не е предвидена да работи с такива „неуспешни“ входове. И точно тук идва на помощ Failsafe. Тъй като Failsafe[f][x] е дефинирана да връща f[x], ако x не е неуспех (failure), а просто да върне самия неуспех, ако е такъв. По този начин можем да прилагаме функцията f към нашия списък без притеснения, знаейки, че тя никога няма да получи неуспешен вход:

 

Говорейки за сложни случаи на грешки, още една нова функция във версия 14.2 е HoldCompleteForm. Функцията HoldForm позволява да се покаже израз без да се извършва обичайното му оценяване (evaluation). Но — подобно на Hold — тя все пак допуска някои трансформации да се случат. HoldCompleteForm — подобно на HoldComplete — предотвратява всички тези трансформации. Затова, докато HoldForm тук се обърква малко, когато последователността „се разрешава“ (resolves),

 

HoldCompleteForm напълно задържа и показва израза, без да допуска никакви трансформации или оценяване на последователността:

 

Още едно подобрение във версия 14.2 се отнася до функцията Counts. Често ми се налага да броя елементи в списък, включително и да получа 0, когато даден елемент липсва. По подразбиране Counts брои само елементите, които присъстват:

 

Но във версия 14.2 добавихме втори аргумент, който ви позволява да зададете пълен списък с всички елементи, които искате да преброите — дори и да ги няма в самия списък:

 

Като последен пример за усъвършенстване на езика във версия 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 Рационализиране и поточно предаване

За първи път представихме програмния достъп до LLM модели в Wolfram Language в средата на 2023 г., с функции като LLMFunction и LLMSynthesize. По това време тези функции изискваха достъп до външни LLM услуги. Но с излизането миналия месец на LLM Kit (заедно с Wolfram Notebook Assistant) направихме тези функции безпроблемно достъпни за всички със сабскрипция за Notebook Assistant + LLM Kit. След като имате активен абонамент, можете да използвате програмните LLM функции навсякъде във версия 14.2 без нужда от допълнителна настройка.

Добавени са и две нови функции: LLMSynthesizeSubmit и ChatSubmit. И двете са създадени с цел да ви позволят да получавате резултати от LLM модели на части (което, да, е важно — поне засега — защото LLM моделите могат да бъдат доста бавни). Подобно на CloudSubmit и URLSubmit, функциите LLMSynthesizeSubmit и ChatSubmit са асинхронни: извикват се, за да стартират процес, който ще активира съответна handler-функция, когато настъпи дадено събитие.

LLMSynthesizeSubmit и ChatSubmit поддържат разнообразни събития. Един пример е „ContentChunkReceived“ — събитие, което възниква, когато бъде получен фрагмент от съдържанието, генерирано от LLM.

Ето как може да се използва това:

 

Функцията LLMSynthesizeSubmit връща обект от тип TaskObject, но след това започва да генерира текст в отговор на подадения от вас подканващ текст (prompt), като извиква зададената от вас handler-функция всеки път, когато постъпи нов фрагмент от текст. След няколко секунди LLM моделът ще приключи процеса на генериране на текста, и ако поискате стойността на c, ще видите всички фрагменти, които е създал:

 

Нека опитаме отново, но този път ще създадем динамично визуализиране за низа s, след което ще стартираме LLMSynthesizeSubmit, за да натрупваме генерирания текст в този низ:

 

 

ChatSubmit е аналогът на ChatEvaluate, но в асинхронен вариант — и можете да го използвате, за да създадете пълноценен чат интерфейс, при който съдържанието се появява в бележника ви веднага щом LLM моделът (или инструментите, извикани от него) го генерират.

 

Рационализиране на паралелните изчисления: Пуснете всички машини!

Вече почти 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, както и множество пакети, създадени за конкретни нейни аспекти. Но с версия 14.2 най-накрая представяме вградени системни функции за работа с теория на игрите (както за матрични, така и за дървовидни игри).

Ето как се задава (нулева сума) матрична игра с 2 играча:

 

Това определя изплащанията при всяко действие на всеки играч. Можем да представим това чрез набор от данни (dataset):

 

Алтернативен вариант е да „начертаем играта“, използвайки MatrixGamePlot:

 

Добре, как можем да „разрешим“ тази игра? С други думи, какво действие трябва да предприеме всеки играч и с каква вероятност, за да максимизира средната си печалба при многократно изиграване на играта? (Предполага се, че при всяко изиграване играчите избират действията си едновременно и независимо един от друг.) „Решение“, което максимизира очакваните печалби за всички играчи, се нарича равновесие на Неш. (Като малка историческа бележка — Джон Неш дълги години е бил потребител на Mathematica и на днешния Wolfram Language, макар и доста след като е формулирал понятието за равновесие на Неш.)

В новата версия 14.2 функцията FindMatrixGameStrategies изчислява оптимални стратегии (известни още като равновесия на Неш) за матрични игри:

 

 

Този резултат означава, че за тази игра играч 1 трябва да избере действие 1 с вероятност , а действие 2 — с вероятност , а играч 2 трябва да избере съответните действия с вероятности и . Но какви са очакваните им печалби? Това се изчислява с помощта на MatrixGamePayoff:

 

Може да стане доста трудно да се проследят различните случаи в една игра, затова MatrixGame ви позволява да зададете каквито етикети пожелаете за играчите и действията:

 

Тези етикети след това се използват във визуализациите:

 

Това, което току-що показахме, всъщност е стандартен пример за игра — „дилемата на затворника“. В езика Wolfram вече разполагаме с GameTheoryData като хранилище на около 50 стандартни игри. Ето една, зададена с 4 играча:

 

И решаването на тази игра е по-малко тривиално, но ето резултата — с 27 различни решения:

 

И да, визуализациите продължават да работят, дори когато има повече играчи (тук показваме случая с 5 играча, като е показано 50-то решение на играта):

 

Струва си да споменем, че начинът, по който решаваме този тип игри, е чрез използване на най-новите ни възможности за решаване на полиномиални уравнения — и не само че можем рутинно да намираме всички възможни равновесия на Неш (а не просто една фиксирана точка), но също така можем да получаваме и точни резултати:

 

В допълнение към матричните игри, които моделират игри, при които играчите избират действията си едновременно и само веднъж, ние вече поддържаме и дървовидни игри, при които играчите се редуват, като се създава дърво от възможни изходи, завършващо с определена печалба за всеки от играчите. Ето пример за една много проста дървовидна игра:

 

Можем да получим поне едно решение на тази игра — описано чрез вложена структура, която дава оптималните вероятности за всяко действие на всеки играч при всеки ход:

 

При дървовидните игри нещата могат да станат доста по-сложни. Ето един пример — в който някои играчи понякога не знаят кои разклонения са били избрани (както е показано чрез състояния, свързани с прекъснати линии):

 

Това, което предлагаме във Версия 14.2, представлява доста пълно покритие на основните концепции от типичен уводен курс по теория на игрите. Но сега, в типичния стил на Wolfram Language, всичко е изчислимо и разширяемо — така че можете да изследвате по-реалистични игри и бързо да разглеждате множество примери, за да изградите интуиция.

Досега се фокусирахме върху „класическата теория на игрите“, особено с особеността (важна за много съвременни приложения), че всички възли на действията са резултат от различна последователност от действия. Обаче игри като морски шах (tic-tac-toe), която наскоро проучих с помощта на multiway графи, могат да бъдат опростени чрез сливане на еквивалентни възли на действия. Множество последователности от действия могат да доведат до една и съща игра на морски шах, както често се случва при итеративни игри. Тези графови структури не се вписват в класическите дървета от теорията на игрите, които представихме във Версия 14.2 — въпреки че (както показват и моите собствени усилия) те са особено подходящи за анализ с езика Wolfram.

 

 

Изчисляване на сизигиите и други постижения в астрономията

В астрономията има много „съвпадения“ — ситуации, при които нещата се подреждат по определен начин. Затъмненията са един такъв пример. Но има и много други. Във Версия 14.2 вече има обща функция — FindAstroEvent — за намиране на такива „съвпадения“, които технически се наричат сизигии („си-зъ-джий“), както и други „специални конфигурации“ на астрономически обекти.

Прост пример е септемврийското (есенно) равноденствие:

 

Грубо казано, това е моментът, когато денят и нощта са с еднаква продължителност. По-прецизно, това е моментът, в който Слънцето се намира в една от двете позиции на небето, където равнината на еклиптиката (т.е. орбиталната равнина на Земята около Слънцето) пресича небесния екватор (т.е. проекцията на земния екватор) — както можем да видим тук (еклиптиката е жълтата линия, а небесният екватор — синята):

 

Като друг пример, нека намерим следващия път през следващото столетие, когато Юпитер и Сатурн ще бъдат най-близо един до друг на небето:

 

Ще се приближат достатъчно, за да видят луните си заедно:

 

Съществува изключително голям брой астрономически конфигурации, които исторически са получили специални имена. Има равноденствия, слънцестоения, еквилукси, кулминации, съединения, опозиции, квадратури — както и периапси и апоапси (специализирани като перигей, перихелий, периреий, перижов, перикрон, периураний, перипосейдей и др.). Във Версия 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“ ще:

 

 

Настройки на потребителския интерфейс

Създаването на отличен потребителски интерфейс винаги е процес на непрекъснато усъвършенстване, а ние вече работим върху интерфейса на бележника (notebook) почти четири десетилетия. Във Версия 14.2 има няколко забележителни подобрения. Едно от тях е свързано с автоматично допълване (autocompletion) на стойности на опции.

Отдавна показваме предложения за опции, които имат дискретен набор от често използвани стойности (като AllAutomatic и др.). Във Версия 14.2 добавяме „шаблонни допълнения“, които показват структурата на настройките и позволяват чрез табулатор да се попълват конкретни стойности. През всичките тези години едно от местата, където почти винаги отивам в документацията, са настройките за FrameLabel. Но сега автоматичното допълване веднага ми показва структурата на тези настройки:

 

Interface settings autocompletion

Също така в автоматичното допълване сме добавили възможност за допълване на имена на контексти, псевдоними на контексти и символи, които съдържат контексти. И във всички случаи автоматичното допълване е „непрецизно“ (fuzzy) — то се задейства не само при въвеждане на символи в началото на името, но и на произволно място в името. Това означава, че може просто да напишете някои символи от името на даден символ и съответните контексти ще се появят като предложения за допълване.

Още една малка улеснение във Версия 14.2 е възможността да плъзгате изображения от един бележник в друг, или дори в други приложения, които поддържат плъзгане на изображения. Преди беше възможно да плъзгате изображения от други приложения в бележници, но сега вече може и обратното.

Нещо допълнително, което засега е специфично за macOS, е подобрената поддръжка за визуализация на икони (както и Quick Look). Така че, ако имате папка с множество бележници и изберете изглед „Икони“ (Icon view), ще видите малко представяне на съдържанието на всеки бележник като икона:

Notebook icon preview

Под капака във Версия 14.2 има и някои инфраструктурни подобрения, които ще позволят значителни нови функции в следващите версии. Част от тях включват обобщена поддръжка на тъмен режим (dark mode). (Да, първоначално може да изглежда, че тъмният режим е нещо просто, но когато започнете да мислите за всички графики и интерфейсни елементи, включващи цветове, става ясно, че не е така. Например, след сериозни усилия наскоро пуснахме тъмен режим и за 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 библиотеки, които ще разширят набора от операции, изпълними върху GPU масиви.

Ето един случаен вектор с десет милиона елемента, съхранен като GPU масив:

 

Графичният процесор на Mac, на който пиша това, поддържа необходимите операции за извършване на това само в своя графичен процесор, връщайки резултат GPUArray:

 

Ето времето:

 

И ето съответния обикновен (CPU) резултат:

 

В този случай резултатът с GPUArray е около 2 пъти по-бърз. Колко точно ускорение ще получите зависи от операциите, които извършвате, и от конкретния хардуер, който използвате. Досега най-големите ускорения, които съм виждал, са около 10 пъти. Но с разрастването на GPU библиотеките очаквам това да се увеличи — особено когато голяма част от изчисленията се извършват „вътре в GPU-то“ и не изискват много достъп до паметта.

Между другото, ако добавите GPUArray в кода си, обикновено това няма да промени резултатите — защото операциите по подразбиране се изпълняват на процесора (CPU), ако не са поддържани на GPU-то. (Обикновено GPUArray ускорява нещата, но ако има твърде много „пропуски“ за GPU, всички опити за прехвърляне на данни може дори да забавят процеса.) Важно е да се има предвид, че изчисленията на GPU все още не са добре стандартизирани или унифицирани. Понякога се поддържат само вектори, друг път и матрици — и може да има различни типове данни с различна числена точност, които се поддържат при различни случаи.

 

И още повече…

Освен всички неща, които обсъдихме досега, във Версия 14.2 има и много други „малки“ нови функции. Макар че може да изглеждат „малки“ в сравнение с другите теми, те могат да се окажат много важни, ако точно тази функционалност ви трябва.

Например, има функцията MidDate, която изчислява средната точка между две дати:

 

И както почти всичко, свързано с дати, MidDate крие множество тънкости. Тук например изчислява седмицата, която е на 2/3 от пътя през тази година:

 

В математиката функции като DSolve и SurfaceIntegrate вече могат да работят с променливи на символен масив:

 

SumConvergence сега позволява да се посочи диапазонът на сумиране и може да даде условия, които зависят от него:

 

Едно малко улеснение, което да, лично поисках, е че сега DigitCount ви позволява да посочите общия брой цифри, които искате да приемете за вашето число, така че да отчита и водещите нули правилно:

 

Говорейки за улеснения, за функции като MaximalBy и TakeLargest добавихме нов аргумент, който указва как да се сортират елементите, за да се определи „най-големият“. Ето стандартният числов ред:

 

и ето какво се случва, ако вместо това използваме „символичен ред“:

 

Винаги има толкова много детайли за изпипване. Както във версия 14.2, има актуализация на MoonPhase и свързани функции, както нови неща, за които да питате, така и нови методи за тяхното изчисляване:

 

 

В друга област, освен основните нови формати за импорт/експорт (особено за поддръжка на Tabular), има и обновление на импортирането на "Markdown", което дава резултати като обикновен текст, както и обновление на импортирането на "PDF", което връща смесен списък от текст и изображения.

Има още много други неща, които можете да намерите в „Обзор на новите и подобрени функции във версия 14.2“. Между другото, струва си да се отбележи, че ако разглеждате конкретна страница от документацията за дадена функция, винаги можете да разберете какво е ново във тази версия, като натиснете бутона „покажи промените“ (show changes):

 

Show changes