Утворіть фрактали Ньютона


24

Ви всі знаєте метод Ньютона для наближення коренів функції, чи не так? Моя мета в цьому завданні - познайомити вас із цікавим аспектом цього алгоритму.

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

Фрактал Ньютона для f (x) = x ^ 3-1 Зображення з Вікісховища

Технічні умови

Мета цього завдання - генерувати такі фрактали. Це означає, що ви отримуєте поліном як вхідний і маєте роздрукувати відповідний фрактал у вигляді зображення у форматі, який ви обрали як вихід.

Вхідні дані

Вхід - це розділений пробілом список складних чисел. Вони записані в стилі <Real part><iImaginary part>, як цей номер: 5.32i3.05. Ви можете припустити, що вхідне число має не більше 4-х знаків після коми і менше 1000. Перший з них не повинен дорівнювати нулю. Наприклад, це може бути вхід до вашої програми:

1 -2i7.5 23.0004i-3.8 i12 0 5.1233i0.1

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

f (x) = x 5 + (-2 + 7,5 i ) x 4 + (23,0004 - 3,8 i ) x 3 + 12 i x 2 + 5,1233 + 0,1 i

Вхід може надійти до вас або з stdin, з аргументу, переданого програмі, або з підказки, відображеної у вашій програмі. Ви можете припустити, що вхід не містить жодних провідних або кінцевих символів пробілу.

Візуалізація

Фрактал потрібно винести таким чином:

  • Виберіть стільки кольорів, скільки коренів P плюс додатковий колір для розбіжності
  • Для кожного числа у видимій площині визначте, чи сходиться метод, і якщо так, до якого кореня. Розфарбуйте крапку за результатом.
  • Не друкуйте лінійки та інші химерні речі
  • Надрукуйте чорну точку в точках, що є коренями поліномів для орієнтації. Ви можете надрукувати до чотирьох пікселів навколо кожного кореня.
  • Знайдіть спосіб вибирати видиму площину таким чином, щоб усі коріння були помітні і широко поширювались по ньому, якщо це можливо. Хоча ідеальне розміщення вихідного кадру не потрібно, я залишаю за собою право відмовитись прийняти відповідь, яка обрала кадр неприйнятним чином, наприклад. завжди за одними і тими ж координатами, всі корені знаходяться в одній точці тощо.
  • Вихідне зображення має мати розмір 1024 * 1024 пікселів.
  • Час візуалізації - максимум 10 хвилин
  • Використання єдиних точних значень з плаваючою комою достатньо

Вихідні дані

Вихід повинен бути растровим графічним зображенням у вибраному вами форматі файлів, читабельним за допомогою стандартного програмного забезпечення для операційної системи марки X. Якщо ви хочете скористатися рідкісним форматом, подумайте про додавання посилання на веб-сайт, де можна завантажити його для перегляду.

Виведіть файл у stdout. Якщо у вашій мові не підтримується додавання чогось до stdout або якщо ви вважаєте цей варіант менш зручним, знайдіть інший спосіб. Будь-яким способом має бути можливість зберегти створене зображення.

Обмеження

  • Немає бібліотек обробки зображень
  • Немає фрактальних генеруючих бібліотек
  • Виграє найкоротший код

Розширення

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


6
Я не зовсім впевнений, чи підходить це як кодовий гольф. У моїх очах завдання занадто складне. Я, мабуть, виявився неправильним.
Джої

5
@Joey: Дійсно. Я хотів би, щоб це було викликом коду самостійно.
Джої Адамс

2
... або PPM з цього приводу.
Joey

1
@Joey: Моя мета полягала в тому, щоб створити досить складне завдання, оскільки багато людей не люблять дуже легких завдань.
FUZxxl

1
Це легко розбивається на окремі завдання, і якщо ваша мова споконвічно підтримує складні числа з плаваючою точкою, ви можете зберегти великий шматок. У мене є не повністю гольф-версія, що працює на 1600 символів, з них 340 - складний номерний клас. Він ще не визначає коріння та використовує кольори, але я намагаюся відстежити, що я вважаю, що це помилка в коді NR. (Знаходження кореня x ^ 3-1, починаючи з -0,5 + 0,866i, звичайно, не повинно розходитися!)
Пітер Тейлор

Відповіді:


13

Пітон, 827 777 символів

import re,random
N=1024
M=N*N
R=range
P=map(lambda x:eval(re.sub('i','+',x)+'j'if 'i'in x else x),raw_input().split())[::-1]
Q=[i*P[i]for i in R(len(P))][1:]
E=lambda p,x:sum(x**k*p[k]for k in R(len(p)))
def Z(x):
 for j in R(99):
  f=E(P,x);g=E(Q,x)
  if abs(f)<1e-9:return x,1
  if abs(x)>1e5or g==0:break
  x-=f/g
 return x,0
T=[]
a=9e9
b=-a
for i in R(999):
 x,f=Z((random.randrange(-9999,9999)+1j*random.randrange(-9999,9999))/99)
 if f:a=min(a,x.real,x.imag);b=max(b,x.real,x.imag);T+=[x]
s=b-a
a,b=a-s/2,b+s/2
s=b-a
C=[[255]*3]*M
H=lambda x,k:int(x.real*k)+87*int(x.imag*k)&255
for i in R(M):
 x,f=Z(a+i%N*s/N+(a+i/N*s/N)*1j)
 if f:C[i]=H(x,99),H(x,57),H(x,76)
for r in T:C[N*int(N*(r.imag-a)/s)+int(N*(r.real-a)/s)]=0,0,0
print'P3',N,N,255
for c in C:print'%d %d %d'%c

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

Ось результат для прикладу многочлена.

результат, наприклад, многочлен


Добре! Мені подобається це.
FUZxxl

14

Ява, 1093 1058 1099 1077 символів

public class F{double r,i,a,b;F(double R,double I){r=R;i=I;}F a(F c){return
new F(r+c.r,i+c.i);}F m(F c){return new F(r*c.r-i*c.i,r*c.i+i*c.r);}F
r(){a=r*r+i*i;return new F(-r/a,i/a);}double l(F c){a=r-c.r;b=i-c.i;return
Math.sqrt(a*a+b*b);}public static void main(String[]a){int
n=a.length,i=0,j,x,K=1024,r[]=new int[n];String o="P3\n"+K+" "+K+"\n255 ",s[];F z=new
F(0,0),P[]=new F[n],R[]=new F[n],c,d,e,p,q;for(;i<n;)P[i]=new
F((s=a[i++].split("i"))[0].isEmpty()?0:Float.parseFloat(s[0]),s.length==1?0:Float.parseFloat(s[1]));double
B=Math.pow(P[n-1].m(P[0].r()).l(z)/2,1./n),b,S;for(i=1;i<n;){b=Math.pow(P[i].m(P[i-1].r()).l(z),1./i++);B=b>B?b:B;}S=6*B/K;for(x=0;x<K*K;){e=d=c=new
F(x%K*S-3*B,x++/K*S-3*B);for(j=51;j-->1;){p=P[0];q=p.m(new
F(n-1,0));for(i=1;i<n;){if(i<n-1)q=q.m(c).a(P[i].m(new
F(n-1-i,0)));p=p.m(c).a(P[i++]);}c=c.a(d=q.r().m(p));if(d.l(z)<S/2)break;}i=j>0?0:n;for(;i<n;i++){if(R[i]==null)R[i]=c;if(R[i].l(c)<S)break;}i=java.awt.Color.HSBtoRGB(i*1f/n,j<1||e.l(c)<S&&r[i]++<1?0:1,j*.02f);for(j=0;j++<3;){o+=(i&255)+" ";i>>=8;}System.out.println(o);o="";}}}

Вхід - це аргументи командного рядка - наприклад, запустити java F 1 0 0 -1. Вихід - це stdout у форматі PPM (піксельна карта ASCII).

Шкала вибирається за допомогою фуджівари, пов'язаної з абсолютним значенням складних коренів многочлена; Потім я помножую, що пов'язане на 1,5. Я регулюю яскравість за швидкістю конвергенції, тому коріння будуть у найяскравіших ділянках. Тому логічно використовувати білий, а не чорний, щоб позначити приблизні місця коренів (що коштує мені 41 символу за те, що навіть неможливо зробити "правильно". Якщо я позначу всі точки, які сходяться в межах 0,5 пікселів від себе то деякі корені виходять без маркування; якщо я позначаю всі точки, які сходяться в межах 0,6 пікселів від себе, то деякі корені виходять з міткою більше ніж на один піксель, тому для кожного кореня я мічу першу точку, що виникає, щоб сходитися в межах 1 пікселя від себе ).

Зображення для прикладу полінома (перетвореного в png за допомогою GIMP): Коріння x ^ 5 + (- 2 + 7.5i) x ^ 4 + (23.0004-3.8i) x ^ 3 + 12i x ^ 2 + (5.1233 + 0.1i)


@FUZxxl, зображення зі старої версії. Пізніше я завантажую її зі швидкістю конвергенції. Але проблема з маркуванням коренів полягає у визначенні, який піксель слід позначити. Класична проблема полягає в тому, що з плаваючою точкою ви не можете використовувати точні тести рівності, тому вам доведеться порівнювати епсилон. Як результат, у мене немає "канонічних" значень для коренів. Я міг би позначити пікселі, які сходяться за один крок, але це не гарантує нічого позначати, і я міг би позначити блок із 4 пікселів для одного кореня.
Пітер Тейлор

@ Peter Taylor: Як бачите, Кіт Рендалл також знайшов рішення цієї проблеми. Я додав цю вимогу як додаткову складність. Один із підходів до цього - обчислити найближчий піксель для кожного кореня, а потім перевірити, чи є кожен піксель рівним йому.
FUZxxl

@FUZxxl, ти не зрозумів моєї точки зору. "Найближчий піксель" кореня недостатньо визначений. Однак я можу зламати щось, у чому може спрацювати всі тестові випадки, які ви кидаєте на це, і я створюю враження, що це зробить вас щасливими. Я збираюся пофарбувати його в білий, а не в чорний колір, тому що це більш логічно.
Пітер Тейлор

@ Петер Тейлор: Гаразд.
FUZxxl

6
Моя картинка профілю невдовзі повинна змінитися на x^6-9x^3+8, ретельно розроблену шляхом вибору коренів, а потім використовувати Wolfram Alpha для спрощення многочлена. Гаразд, я обдурив, поміняючи відтінки навколо після цього в GIMP.
Пітер Тейлор

3

Пітон, 633 байт

import numpy as np
import matplotlib.pyplot as plt
from colorsys import hls_to_rgb
def f(z):
    return (z**4 - 1)
def df(z):
    return (4*z**3) 
def cz(z):
    r = np.abs(z)
    arg = np.angle(z)   
    h = (arg + np.pi)  / (3 * np.pi)
    l = 1.0 - 1.0/(1.0 + r**0.1)
    s = 0.8 
    c = np.vectorize(hls_to_rgb) (h,l,s)
    c = np.array(c)
    c = c.swapaxes(0,2) 
    return c    
x, y = np.ogrid[-1.5:1.5:2001j, -1.5:1.5:2001j]
z = x + 1j*y    
for i in range(10):
    z -= (f(z) / df(z))
zz = z
zz[np.isnan(zz)]=0
zz=cz(zz)
plt.figure()
plt.imshow(zz, interpolation='nearest')
plt.axis('off')
plt.savefig('plots/nf.svg')
plt.close()

Після швидких підйомів та прикраси (756 байт)

import numpy as np
from numba import jit
import matplotlib.pyplot as plt
from colorsys import hls_to_rgb 

@jit(nopython=True, parallel=True, nogil=True)
def f(z):
    return (z**4 - 1)   

@jit(nopython=True, parallel=True, nogil=True)
def df(z):
    return (4*z**3) 

def cz(z):
    r = np.abs(z)
    arg = np.angle(z)   

    h = (arg + np.pi)  / (3 * np.pi)
    l = 1.0 - 1.0/(1.0 + r**0.1)
    s = 0.8 

    c = np.vectorize(hls_to_rgb) (h,l,s)
    c = np.array(c)
    c = c.swapaxes(0,2) 
    return c    

x, y = np.ogrid[-1.5:1.5:2001j, -1.5:1.5:2001j]
z = x + 1j*y    

for i in range(10):
    z -= (f(z) / df(z))

zz = z
zz[np.isnan(zz)]=0
zz=cz(zz)
plt.figure()
plt.imshow(zz, interpolation='nearest')
plt.axis('off')
plt.savefig('plots/nf.svg')
plt.close()

Наведений нижче графік призначений для Ньютона Фрактала функції log (z).

Фрактал Ньютона для журналу (z)


Ви можете використовувати короткі (1 знак) імена та видаляти пробіли, комбінуючи кілька рядків, використовуючи ;. Також видаліть усі можливі пробіли.
mbomb007

Деякі звичайні гольфи зменшують це всього до 353 байт ! Не перевіряли його (немає matplotlibтут), тому немає гарантії, що він все ще працює.
Khuldraeseth na'Barya
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.