Компьютерная математика Weekly – Telegram
Компьютерная математика Weekly
1.06K subscribers
48 photos
2 videos
70 links
Download Telegram
(в порядке лытдыбра)

пытался разобраться с применениями тождества тройного произведения Якоби — вот можно посмотреть, как оно выглядит:

from sage.all import *
N = 10
q, Z = var('q'), var('Z')
J = prod((1-q**(2*m))*(1+q**(2*m-1)*Z**2)*(1+q**(2*m-1)*Z**(-2))
for m in range(1, N+1))
print(J.series(q,2*N+1))


его можно применять к товарищам типа θ(q):=Σq^{n^2} — а степени такой суммы считают представления чисел в виде суммы квадратов и т.д.

но это пока с трудом идет



что давно знаю — что из тройного произведения следует пентагональная теорема Эйлера, позволяющая быстро вычислять количества разбиений (способов представить число N в виде суммы слагаемых, порядок которых не важен)

// ранее про разбиения: https://news.1rj.ru/str/compmathweekly/40

про этот сюжет можно прочитать в брошюре «Диаграммы Юнга, плоские разбиения и знакочередующиеся матрицы» Е.Смирнова, https://mccme.ru/free-books/dubna/smirnov-asm.pdf

для разминки написал соответствующее вычисление:

penta = concat [[(p,s), (p+m,s)] | m <- [1..],
let (p,s) = (m*(3*m-1) `div` 2, (-1)^(m+1))]

partn = 1 : [sum [s*(partn !! (n-p)) | (p,s) <- pents] | n <- [1..],
let pents = takeWhile ((<= n) . fst) penta]

(и действительно, partn !! 500, скажем, мгновенно отвечает 2300165032574323995027)
(продолжая предыдущее)

положим φ(q)=(1-q)(1-q²)(1-q³)…

если раскрыть все скобки, то чудесным образом почти всё сокращается (про это и говорит упоминаемая выше пентагональная теорема Эйлера) — это можно видеть в первой строчке таблицы

и что получается при раскрытии скобок для φ³, тоже хорошо видно (и это, как и пентагональную теорему Эйлера, несложно вывести из тождества тройного произведения Якоби)

дальше жизнь усложняется, но кое-что еще заметить можно

скажем, для 8-й степени
коэффициенты при q^{4k+3} сразу обращаются в ноль, а коэффициенты при q^{4k+1} делятся на 8, причем если разделить, то получится последовательность -1, 8, -20, 0, 70, -64, -56, 0, 125, которую мы уже видели… (понятно где, да?)

серьезные люди уже давно бы изучили модулярные формы (и если хотите написать комментарий с этой т.з., то не стесняйтесь, пожалуйста), но я пока продолжу играть в ненаучную нумерологию…
Как численно найти корни многочлена?

Если корни сильно разной величины, x₁≫x₂≫x₃≫…, то k-й элементарный симметрический многчлен неотличим от произведения k самых больших иксов, так что любой корень — это примерно отношение соседних коэффициентов многочлена.

Если числа изначально разные по модулю, то чтобы их «раздвинуть» достаточно возвести все их в большую степень. Если P(x) многочлен, то P(√x) конечно не многочлен… зато P(√x)P(-√x) — вполне себе многочлен, а его корни суть квадраты корней исходного. После нескольких таких итераций корни становятся достаточно различными, чтобы работала идея из предыдущего абзаца.

Такой способ предлагал Лобачевский (впрочем до него видимо Данделен), а мне про это рассказал коллега Бахарев.

На коленке реализовал это так:

def absroots(poly,N=5):
poly, d = np.array(poly,dtype=np.float64), len(poly)-1
signs = np.ones(d+1)
signs[1::2] *= -1
for _ in range(N):
poly *= 1/poly[-1]
poly = np.convolve(poly,poly*signs)[::2]
return np.array([(-poly[k]/poly[k+1])**(1/(2**N))
for k in range(d)])

— и действительно работает… но близко к корням (модулям корней) так не подойдешь (точность растет медлено, а потом вообще происходит катастрофа). Вроде есть какие-то модификации, которые улучшают ситуацию.

Впрочем, уже грубая оценка для всех корней видимо полезна — метод Ньютона, наоборот, сходится очень быстро, но если начинать итерации рядом с корнем (и действительно, пара итераций Лобачевского + пара итераций Ньютона для каких-то многочленов у меня работает прекрасно).

// Ранее здесь про метод Ньютона: https://news.1rj.ru/str/compmathweekly/27 (и далее по ссылкам)
🔥93🤯1
Компьютерная математика Weekly
для недавно присоединившихся — некоторые старые посты: * про быстрое вычисление числа пи * про подсчет разбиений на доминошки и логарифмический масштаб * про то как нарисовать снежинку и дерево
несколько достаточно доступных сюжетов для недавно присоединившихся:

* экспериментальное вычисление пи в экселе
https://news.1rj.ru/str/compmathweekly/76

* что будет, если возвести многочлен в большую степень?
https://news.1rj.ru/str/compmathweekly/81

* знаете ли вы, как выглядит график синуса?
https://news.1rj.ru/str/compmathweekly/89

( предыдущий выпуск: https://news.1rj.ru/str/compmathweekly/64 )
14🔥4
можно ли сравнить две функции за конечное время?

если вы написали две функции, которые определены на бесконечном множестве, то кажется очевидным, что нельзя написать программу, которая только применяя эти функции (не имея доступа к их коду) проверит за конечное время, равны ли они (в математическом смысле: совпадают ли они во всех точках) — и всё же иногда это возможно!



функции мы будем рассматривать на множестве Кантора, т.е. на бесконеных последовательностях 0 и 1

т.к. мы пишем программу — и функции на последовательностях, и сами последовательности у нас будут, конечно, только вычислимые; кроме того, они будут всюду определенные (можно написать type Cantor = Natural -> Bit, но надо иметь в виду, что если такая функция для числа 17 не определена, то она не задает элемент множества Кантора)

мы хотели бы сравнить две функции из Cantor, т.е., если угодно, написать функцию equal :: Eq y => (Cantor -> y) -> (Cantor -> y) -> Bool (здесь написано, что если для типа y определено сравнение, то equal сравнивает две функции Cantor→y)

если функции НЕ равны, то это уж точно можно понять за конечное время, т.к. любые вычислимые функции на Cantor зависят только от какого-то числа первых битов — это проявление компактности канторовского множества, из-за которой любая непрерывная функция на нем равномерно непрерывна (ср. с натуральными числами, где легко придумать вычислимую функцию которая никаким фиксированным количеством первых битов не определяется!)

но, на первый взгляд, если функции равны, то невозможно понять, до какого момента надо их сравнивать

и всё же можно за конечное время установить не только неравенство, но и равенство:

data Bit = Zero | One
deriving (Eq)
type Natural = Integer
type Cantor = Natural -> Bit
(#) :: Bit -> Cantor -> Cantor
x # a = \i -> if i == 0 then x else a(i-1)
forsome :: (Cantor -> Bool) -> Bool
find :: (Cantor -> Bool) -> Cantor
forsome p = p(find p)
find p = first # find(p . (first #))
where first = if forsome(p . (Zero #)) then Zero else One
equal :: Eq y => (Cantor -> y) -> (Cantor -> y) -> Bool
equal f g = not(forsome(\a -> f a /= g a))

здесь функция forsome проверяет, существует ли точка множества Кантора, в которой верно p (что само по себе не менее удивительно, чем проверка равенства функций… и собственно через такой «бесконечный поиск за конечное время» равенство мгновенно переписывается), а find находит точку, в которой верно p, если таковая есть

при этом определение find через forsome крайне прямолинейное: «если существует пример с первым битом 0, то первый бит равен 0, иначе первый бит равен 1 (а дальше применяем find для определения оставшихся битов)»… и кажется, что пока ничего особо не сделано — но вот такое короткое (и вроде бы тоже довольно тавтологичное) определение forsome чудесным образом позволяет “вытянуть себя за волосы из болота”

мб позже будет комментарий, как это работает — но сразу оставлю ссылку на источник — Martin Escardo по диссертации Ulrich Berger'а; и спасибо коллеге Раскину за объяснения

хочу еще отметить, что это история не про хаскелл, можно напрячься и на питоне (например) переписать — но тут приятно, что синтаксис хорошо соответствует (некоторой) математике
10🔥4🤯3
Компьютерная математика Weekly pinned «несколько достаточно доступных сюжетов для недавно присоединившихся: * экспериментальное вычисление пи в экселе https://news.1rj.ru/str/compmathweekly/76 * что будет, если возвести многочлен в большую степень? https://news.1rj.ru/str/compmathweekly/81 * знаете ли вы, как выглядит…»
как работает код из предыдущего поста, реализующий «поиск за конечное время по множеству Кантора»?

посмотрим еще раз на определение
forsome p = p(find p)
(напомню, что p это функция из бесконечных последовательностей нулей и единиц в True/False, а функция forsome должна проверять, выполняется ли p хоть в какой-то точке)

в одной ситуации всё понятно сразу: если p определена как константа (вообще не читает свой вход, а возвращает True или False), то ленивый компилятор даже вникать в определение find не будет, а вернет эту константу — и в этом случае forsome работает правильно

пусть теперь p для возвращения значения достаточно прочитать первые N битов последовательности — будем доказывать индукцией по N, что правильно работают и forsome, и
find p = frst # find(p.(frst#))
where frst = if forsome(p.(0#)) then 0 else 1

(напомню, что find должен возвращать пример точки, в которой p верно, если такая точка есть; # обозначает конкатенацию)

функция p.(0#) в точке x₁ x₂… возвращает p(0 x₁ x₂…) — поэтому ей достаточно прочитать N-1 бит икса — поэтому если p выполняется для какой-либо последовательности, начинающейся с нуля, то frst=0 и find p возвращает правильный первый бит по предположению индукции для forsome… а остальные биты возвращает правильные по предположению индукции для find (оставшиеся подробности индукции опущу)

наконец, для любой вычислимой всюду определенной функции p такое N, что p для ответа достаточно первых N битов последовательности, непременно существует — и это топологическое утверждение: проявление компактности множества Кантора (и вот для функций на натуральных числах aka конечных последовательностях 0 и 1 такое свойство очевидно неверно!)

***

после таких фокусов возникают в голове волшебные картины… вот например: если мы умеем проверять равенство функций в смысле совпадения во всех точках — то можно, казалось бы, для одной задачи написать медленный, но точно правильный алгоритм, а дальше любой другой алгоритм решения той же задачи автоматически верифицировать

и в принципе, действительно, можно, но есть две проблемы

первая — что «всюду определенные функции на множестве Кантора» это довольно узкий и бесполезный класс функций… ну вот по тому, что функция p всегда читает не более N(p) битов уже понятно, что ничего особо интересного так реализовать нельзя, правда же?

вторая — что никакой магии на самом деле нет: вот как реализовать forsome без возвышенных слов и хаскелловских трюков? ну вот p ест поток из единиц и нулей… ждем пока p попросит первый бит, после чего одной копии p даем 0, а другой копии даем 1… если p после этого вернула значение, то понятно, что делать… если не вернула, а просит еще бит, то одной копии дали 00, одной 01… и так далее — компактность (сорри, совсем без слов не обошлось ) гарантирует, что рано или поздно это закончится

видно только, что закончится не очень быстро: мы просто перебираем 2^N двоичных последовательностей… в исходном варианте еще и довольно неэффективно всё организовано — но даже если доработать (в блог-посте по прошлой ссылке обсуждаются варианты), то по сути наша магическая проверка корректности алгоритма сводится к тому, что «всё определено на конечном множестве, ну вот на каждой точке этого конечного множества и сравниваем значение с табличным»


и всё-таки если не в самом алгоритме, то в том, как он описан, я какую-то магию вижу… и еще нравится ощущение, что ghci компилирует практически непосредственно математические определения (более опытные люди пишут, правда, что это очень misleading ощущение )
5
пока не спалось — нарисовал фрактал Ньютона прямо в excel

(для многочлена — в данном случае, z³-1 — нарисовано, к какому из его корней сходится метод Ньютона, если начинать с данной точки комплексной плоскости)



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

1) есть поддержка комплексных чисел… правда немножко специфическая: COMPLEX(1,2) выдает "1+2i" и с т.з. экселя это просто строка, операции типа + или * с ней не работают… зато с такими строками работают отдельные функции типа IMSUM, IMPOWER и т.п. — то есть z-(z³-1)/(3z²) превращается в (несколько мучительное) IMSUB(z, IMDIV(IMSUB(IMPOWER(z, 3), 1), IMPRODUCT(3, IMPOWER(z, 2))))

2) есть LAMBDA и даже с поддержкой рекурсии! т.е. можно создать свою функцию NewtonIter и написать в ее определение буквально LAMBDA(z, n, IF(n <= 0, z, NewtonIter(…, n-1))), где многоточие заменяет формулу из предыдущего пункта

(в реальности в определение желательно добавить еще обработку обращения в ноль или почти в ноль знаменателя, но эту техническую деталь опущу)
1🔥19😁21🤯1
производные без анализа

школьники говорят, что на сборах по математике для 11 класса рассказывали про производные многочленов без пределов через «предпроизводную» — тоже дело, но мне больше нравится кое-что другое

что такое производная многочлена P? очень просто: если ε²=0, то P(x+ε)=P(x)+εP'(x)

ср. с обычным «(f(x+ε)-f(x))/ε→f'(x) при ε→0» — только теперь никаких пределов не осталось, только алгебра в кольце чисел вида a+bε с соотношением ε²=0

эту алгебру «дуальных чисел» легко реализовать — вот всё, по сути, что нужно для такого автоматического дифференцирования:

class Dual:
def __init__(self, a, b=0):
self.a, self.b = a, b
def __add__(self, other):
return Dual(self.a + other.a, self.b + other.b)
def __mul__(self, other):
return Dual(self.a * other.a, self.a * other.b + self.b * other.a)

def diff(f, x):
return f(Dual(x, 1)).b


ну технически, если сейчас написать

def poly(x):
return x**2-3*x-1

print(diff(poly,1))

то вместо производной многочлена в единице мы увидим, что возведение дуальных чисел в степень не определено, умножение дуальных чисел на обычные неопределено, вычитание не определено… более технически полноценное определение класса положу в комментарии

***

в связи с дуальными числами можно говорить еще про разную геометрию — так, если умножение на комплексные числа отвечает за повороты, то дуальные числа реализуют любимые мной перекашивания: exp(εt)(x+yε) = x+(y+xt)ε… а также, говорят, реализуют параболические повороты, но эта часть что-то плохо пока в голову уложилась
👍53
пока каменный цветок не выходит (положу в комментарии, как это выглядит) — поделюсь ссылкой на компьютерную игру: https://adam.math.hhu.de/#/g/AlexKontorovich/RealAnalysisGame

(первое впечатление: это невыносимо, непонятно как кто-то может в этом формализовывать какие-то содержательные доказательства… но мб у вас получится лучше)
🔥3🤨1
This media is not supported in your browser
VIEW IN TELEGRAM
многим рассказывал¹, как нарисовать «ленивый додекаэдр»: взять куб и поделить каждую грань пополам регулярным образом — как раз получится 6×2=12 граней, 8+12=20 вершин (вершины куба и середины его ребер)… вся комбинаторика получается правильная

если хочется еще и правильной геометрии — нужно просто немного всё продеформировать, это и показано в видео

¹ и даже писал в «Квантике» — см. №9 за 2025 год

***

как такое нарисовать и не перетрудиться? начнем с вершин куба — это просто все точки с координатами ±1

from itertools import product
vertices = list(product([-1,1], repeat=3))

чтобы получить додекаэдр, надо добавить еще 12 вершин… не хочется их все писать руками, но тут есть большая группа симметрий G: можно переставлять координаты по циклу и расставлять знаки — и так все новые вершины можно получить из одной, (φ,0,1/φ)… и что еще приятнее, все грани можно получить из одной

v0 = (phi,0,psi)
vertices += [g(v0) for g in G()]
f0 = [(phi,0,-psi),(1,1,-1),(psi,phi,0),(1,1,1),(phi,0,psi)]
faces = [tuple(vertices.index(g(v)) for v in f0) for g in G()]
poly = Polyhedron(vertices, faces)

(результат действия элемента g на набор точек f0 — это просто tuple(g(v) for v in f0) — но в faces надо положить не координаты этих точек, а номера соответствующих вершин)

если в качестве phi и psi взять не (±1+√5)/2, а 1, то додекаэдр превратится в куб с дополнительными вершинами в серединах ребер — и такая деформация анимируется в manim примерно в одну строчку

приведу еще код для генерации группы G

def G():
for signs in product([-1,1], repeat=3):
for r in range(3):
yield lambda x: tuple(x[(i+r)%3]*signs[i] for i in range(3))

(а всё собранное целиком положу в комментарии — с использованием симметрии ~20 строк получилось… ну если с паузами и вращением камеры, то чуть больше)

***

видно, кстати, что в группе G всего 24 элемента, из которых 12 сохраняют ориентацию… а всего в группе I симметрий додекаэдра 12×5=60 вращений — получаем действие I⁺ на 60/12=5 элементах I⁺/G⁺, который дает изоморфизм I⁺≃A₅

конечно, то же можно сказать и более геометрически: G это как раз подгруппа симметрий додекаэдра, сохраняющих вписанный в него куб, а 5-элементное множество I⁺/G⁺ отождествляется с 5 вписанными в додекаэдр кубами («кубы Кеплера») — вот эти кубы I⁺ и переставляет
1🔥179👏2👍1