Broadcasting и точечные операции

Точка перед операцией превращает её в поэлементную — это и есть broadcasting, фирменный механизм Julia.

Broadcasting — применение операции или функции к каждому элементу массива (или к соответствующим элементам нескольких массивов) с помощью точечного синтаксиса.

Зачем нужен broadcasting

В научном коде постоянно нужно применить операцию ко всему массиву: прибавить число к каждому элементу, посчитать синус от всех значений, перемножить два вектора поэлементно. В Python для этого используют NumPy, и поэлементность подразумевается по умолчанию. В Julia векторные и матричные операторы по умолчанию математические, а для поэлементности ставят точку. Это делает намерение явным.

Точечные операторы

v = [1, 2, 3, 4]
println(v .+ 10)      # прибавить 10 к каждому
println(v .^ 2)       # возвести каждый в квадрат
println(v .* v)       # поэлементное произведение

Вывод:

[11, 12, 13, 14]
[1, 4, 9, 16]
[1, 4, 9, 16]

Точка для функций

Любую функцию можно применить поэлементно, поставив точку после её имени:

angles = [0.0, 1.5708, 3.14159]
println(round.(sin.(angles), digits=2))
println(uppercase.(["julia", "быстрая"]))

Вывод:

[0.0, 1.0, 0.0]
["JULIA", "БЫСТРАЯ"]

Здесь sin. берёт синус каждого угла, а внешний round. округляет каждый результат. Несколько точечных вызовов в одном выражении сливаются в один проход по массиву.

Broadcasting разных форм

Broadcasting умеет «растягивать» размерности: например, прибавить вектор-строку к вектору-столбцу и получить матрицу — как в NumPy:

col = [1, 2, 3]
row = [10 20 30]
println(col .+ row)

Вывод:

[11 21 31; 12 22 32; 13 23 33]

Как работает под капотом

Это ключевое преимущество Julia перед связкой Python+NumPy. В NumPy выражение вроде a + b*c над массивами создаёт временные промежуточные массивы для каждой операции. В Julia несколько точечных операций в одном выражении (a .+ b .* c) сливаются (fusion) компилятором в единственный цикл без промежуточных массивов. Это экономит память и время. Причём это работает для любых функций, в том числе ваших собственных — не нужно писать векторизованные версии, как в NumPy.

Частые ошибки

Самая частая ошибка — забыть точку и получить либо ошибку, либо неожиданный результат. Например, v ^ 2 для вектора попытается выполнить матричное возведение в степень и упадёт с ошибкой, а правильное поэлементное — v .^ 2. Если видите ошибку про несовпадение размерностей в, казалось бы, простой операции — проверьте, не пропущена ли точка.

Итоги

  • Точка перед оператором (.+, .*, .^) делает его поэлементным.
  • Точка после имени функции (sin., round.) применяет её к каждому элементу.
  • Несколько точечных операций сливаются в один проход без промежуточных массивов — быстрее NumPy.
  • Broadcasting работает с любыми функциями, включая ваши собственные.
Проверьте себя
1. Что делает выражение v .^ 2 для вектора v?
AВозводит весь вектор в матричную степень
BВозводит каждый элемент в квадрат поэлементно
CВызывает ошибку
DУдваивает длину вектора
2. В чём преимущество слияния (fusion) точечных операций в Julia по сравнению с NumPy?
AКод становится длиннее
BНесколько операций выполняются в одном проходе без промежуточных массивов
CРезультат всегда округляется
DТочка отключает проверку типов