Генерировать ньютоновские фракталы

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. Если вы хотите использовать редкий формат, рассмотрите возможность добавления ссылки на веб-сайт, где можно загрузить для него средство просмотра.

Выведите файл на стандартный вывод. Если ваш язык не поддерживает помещение чего-либо в стандартный вывод или если вы считаете этот параметр менее удобным, найдите другой способ. В любом случае должна быть возможность сохранить сгенерированное изображение.

ограничения

  • Нет библиотек обработки изображений
  • Нет фрактальных библиотек
  • Самый короткий код выигрывает

расширения

Если вам нравится эта задача, вы можете попытаться раскрасить точки в соответствии со скоростью сходимости или некоторыми другими критериями. Я хотел бы увидеть некоторые интересные результаты.

FUZxxl
источник
6
Я не совсем уверен, подходит ли это как кодовый гольф. На мой взгляд, задача слишком сложная. Хотя я могу ошибаться.
Джои
5
@Joey: действительно. Я хотел бы, чтобы это было вызовом кода самостоятельно.
Джои Адамс
2
... или PPM в этом отношении.
Джои
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 . Выходные данные выводятся на стандартный вывод в формате 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 пикселей для одного корня.
Питер Тейлор
@ Питер Тейлор: Как вы видите, Кит Рэндалл также нашел решение этой проблемы. Я добавил это требование как дополнительную сложность. Один из подходов состоит в том, чтобы рассчитать ближайший пиксель для каждого корня, а затем проверить каждый пиксель на его равенство.
FUZxxl
@FUZxxl, вы не поняли мою точку зрения. «Ближайший пиксель» корня четко не определен. Тем не менее, я могу взломать что-то, что может сработать для всех тестовых случаев, которые вы кидаете, и у меня сложилось впечатление, что это сделает вас счастливыми. Я собираюсь раскрасить его белым, а не черным, потому что это более логично.
Питер Тейлор
@ Питер Тейлор: Хорошо.
FUZxxl
6
Моя картинка профиля должна скоро измениться на x^6-9x^3+8тщательно спроектированную, выбрав корни и затем используя Wolfram Alpha, чтобы упростить полином. Хорошо, я обманул, поменяв местами оттенки в GIMP.
Питер Тейлор
3

Python, 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).

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

Сырный хлеб
источник
Вы можете использовать более короткие (1 символ) имена и удалять пробелы, комбинируя несколько строк, используя ;. Кроме того, удалите все возможные места.
mbomb007
Некоторые обычные гольфы уменьшают это до 353 байтов ! Не проверял (нет matplotlibздесь), поэтому нет гарантии, что он все еще работает.
Хулдраесет на'Барья