AGM na sterydach

Osiem lat temu pisałem tu o średniej arytmetyczno-geometrycznej, po naszemu AGM (arithmetic-geometric mean). Tu można poczytać: link 1, link 2.

Okazuje się, że przy odrobinie pomyślunku da się rozciągnąć liczenie takiej średniej na trzy liczby.

Przypomnę najpierw, że AGM dwóch liczb x i y liczymy tak, że najpierw liczymy ich średnią arytmetyczną (czyli suma na pół), potem – geometryczną (czyli iloczyn pod pierwiastek), a to co wyjdzie – czyli dwie nowe liczby – poddajemy tej samej operacji, i tak dalej. Po nieskończenie wielu krokach dostaniemy w wyniku AGM. Przy czym rzecz jasna zamiast nieskończenie wielu kroków wystarczy zrobić tyle kroków, żebyśmy byli zadowoleni z aktualnego przybliżenia. Swoją drogą AGM jest dość szybko zbieżne: po 5 krokach dostajemy wynik z błędem rzędu 10-12, wystarcza do większości obliczeń.

No dobra, ale to dla dwóch liczb. A co z trzema?

Można zrobić tak: trzy liczby x, y, z dzielimy na trzy pary: (x, y), (x, z), (y, z). Następnie z każdej pary liczymy średnią arytmetyczną (dostajemy w wyniku trzy liczby). Te znów dzielimy na trzy pary, z których z kolei liczymy średnie geometryczne. I tak w kółko, aż się zbiegnie do jednej liczby (z zadaną dokładnością).

Jak by to zapisać w Pythonie?

Dość prosto:

sa = lambda x, y: (x + y) * 0.5 # średnia arytmetyczna
sg = lambda x, y: (x * y) ** 0.5 # średnia geometryczna

x, y, z = 10, 100, 1000

for i in range(30):
    x, y, z = sa(x, y), sa(x, z), sa(y, z)
    x, y, z = sg(x, y), sg(x, z), sg(y, z)

print(x, y, z)

Zbieżność tego procesu jest dużo wolniejsza; dokładność 10-12 dostajemy po około 25-30 krokach. Ale się zbiega.

Jeżeli ktoś by się temu przyjrzał bliżej, okazałoby się, że AGM liczona dla dwóch zmiennych zbiega się wykładniczo, a dla trzech – liniowo. Ale to już może na kiedy indziej.

A jak by to wyglądało dla większej liczby zmiennych?

Hmmm.

Dla czterech zmiennych mamy sześć możliwych par, a więc w pierwszym kroku dostaniemy sześć liczb (a nie cztery). Z tych sześciu liczb możemy zrobić 15 par i tak dalej. Ucieka nam to wykładniczo. Owszem, gdzieś tam się w końcu zbiegnie, ale dla dłuższych list startowych zabraknie nam pamięci zanim to policzymy.

Ale da się inaczej. Zamiast dzielić n zmiennych na pary, można:

(1) Podzielić je na n różnych grup o liczebności (n-1) każda (takich, że w każdej grupie brakuje innej liczby z oryginalnych n). Na przykład dla czterech zmiennych v, x, y, z dzielimy na grupy (v, x, y),(v, x, z),(v, y, z),(x, y, z).

(2) Dla każdej grupy policzyć średnią arytmetyczną.

(3) Wynik (2) to znów n liczb, powtarzamy manewr z punktu pierwszego…

(4) … tylko teraz liczymy dla każdej grupy średnią geometryczną.

(5) powtarzamy kroki (1) – (4) aż nam się znudzi.

Efekt?

Sprawdźmy dla czterech zmiennych:

sa = lambda x, y, z: (x + y + z) * (1/3)
sg = lambda x, y, z: (x * y * z) ** (1/3)

v, x, y, z = 10, 100, 1000, 100000

for i in range(27):
    v, x, y, z = sa(x, y, z), sa(v, y, z), sa(v, x, z), sa(v, x, y)
    v, x, y, z = sg(x, y, z), sg(v, y, z), sg(v, x, z), sg(v, x, y)

print(v, x, y, z)

Wynik: 13411.839901919342

Nie wiem jak szybko to się zbiega, ale dokładność 10-12 dostajemy tu po 18 krokach, więc chyba nieco szybciej niż przy trzech zmiennych.

Dla ogólnego przypadku kod wyglądałby mniej więcej tak:

def sa(x): return sum(x)/len(x)


def sg(x):
    r = 1
    for n in x:
        r = r*n
    return r ** (1/len(x))


x = [10, 200, 3000]

while max(x) != min(x):
    nx1 = []
    for j in range(len(x)):
        tx = x.copy()
        tx.pop(j)
        nx1.append(sa(tx))
    nx2 = []
    for j in range(len(x)):
        tx = nx1.copy()
        tx.pop(j)
        nx2.append(sg(tx))
    x = nx2.copy()

print(x[0])

Wynik: 740.9217572745254

Masz jakieś ciekawsze pomysły na liczenie średnich?

Dzisiejszy wpis sponsorował John.

Zapisz się
Powiadom o
guest
3 komentarzy
Inline Feedbacks
Zobacz wszystkie komentarze
3
0
Zapraszam do skomentowania wpisu.x
()
x