Gerar fractais de Newton


24

Todos vocês conhecem o método Newton para aproximar as raízes de uma função, não sabem? Meu objetivo nesta tarefa é apresentar um aspecto interessante desse algoritmo.

O algoritmo de Newton converge apenas para certos, mas acima de tudo, valores complexos de entrada. Se você imagina a convergência do método para todos os valores de entrada sobre o plano complexo, geralmente obtém um belo fractal como este:

Fractal de Newton para f (x) = x ^ 3-1 Imagem de Wikimedia commons

Especificações

O objetivo desta tarefa é gerar esses fractais. Isso significa que você obtém um polinômio como entrada e precisa imprimir o fractal correspondente como uma imagem em um formato de sua escolha como saída.

Entrada

A entrada é uma lista separada por espaços em branco de números complexos. Eles são escritos para baixo no estilo <Real part><iImaginary part>, como este número: 5.32i3.05. Você pode supor que o número de entrada não tenha mais que 4 casas decimais e seja menor que 1000. O primeiro deles não deve ser zero. Por exemplo, isso pode ser uma entrada para o seu programa:

1 -2i7,5 23.0004i-3,8 i12 0 5,1233i0,1

Os números são interpretados como os coeficientes de um polinômio, começando com a maior potência. Durante o resto desta especificação, o polinómio de entrada é designado P . A entrada acima é igual a este polinômio:

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

A entrada pode vir para você a partir do stdin, de um argumento passado para o programa ou de um prompt exibido no seu programa. Você pode supor que a entrada não contém caracteres de espaço em branco à esquerda ou à direita.

Renderização

Você deve renderizar o fractal da seguinte maneira:

  • Escolha quantas cores as raízes de P mais uma cor extra para divergência
  • Para cada número no plano visível, determine se o método converge e se sim para qual raiz. Pinte o ponto de acordo com o resultado.
  • Não imprima réguas ou outras coisas sofisticadas
  • Imprima um ponto preto nos pontos, que são as raízes dos polinômios para orientação. Você pode imprimir até quatro pixels ao redor de cada raiz.
  • Encontre uma maneira de escolher o plano visível de maneira que todas as raízes sejam distinguíveis e se espalhem amplamente, se possível. Embora não seja necessário um posicionamento perfeito do quadro de saída, reservo-me o direito de recusar uma resposta que escolha o quadro de uma maneira inaceitável, por exemplo. sempre nas mesmas coordenadas, todas as raízes estão em um ponto, etc.
  • A imagem de saída deve ter um tamanho de 1024 * 1024 pixels.
  • O tempo de renderização é de 10 minutos no máximo
  • Usar valores de ponto flutuante de precisão única é suficiente

Saída

A saída deve ser uma imagem gráfica rasterizada em um formato de arquivo de sua escolha, legível por software padrão para um sistema operacional da marca X. Se você deseja usar um formato raro, considere adicionar um link a um site onde é possível baixar um visualizador para ele.

Envie o arquivo para stdout. Se o seu idioma não suportar colocar algo em stdout ou se você achar esta opção menos conveniente, encontre outra maneira. De qualquer forma, deve ser possível salvar a imagem gerada.

Restrições

  • Nenhuma biblioteca de processamento de imagem
  • Nenhuma biblioteca de geração de fractal
  • O código mais curto vence

Extensões

Se você gosta dessa tarefa, pode tentar colorir os pontos de acordo com a velocidade da convergência ou com outros critérios. Eu gostaria de ver alguns resultados interessantes.


6
Não tenho certeza se isso é adequado como um código de golfe. A meu ver, a tarefa é muito complexa. Eu posso estar errado, no entanto.
Joey

5
@ Joey: De fato. Eu gostaria que isso fosse um desafio para mim mesmo.
Joey Adams

2
... ou PPM para esse assunto.
Joey

11
@ Joey: Minha intenção era criar uma tarefa bastante difícil, pois muitas pessoas não gostam de tarefas muito fáceis.
FUZxxl

11
É facilmente dividido em tarefas separadas e, se o seu idioma suportar números complexos de ponto flutuante nativamente, você poderá salvar uma grande parte. Eu tenho uma versão não totalmente equipada com 1.600 caracteres, dos quais 340 são da classe numérica complexa. Ele ainda não identifica as raízes e usa cores, mas estou tentando rastrear o que presumo ser um bug no código NR. (Encontrar uma raiz de x ^ 3-1 a partir de -0,5 + 0,866i certamente não deve divergir!) #
Peter Taylor Peter

Respostas:


13

Python, 827 777 caracteres

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

Localiza limites de exibição (e raízes) localizando pontos de convergência para várias amostras aleatórias. Em seguida, ele desenha o gráfico calculando os pontos de convergência para cada ponto inicial e usando uma função hash para obter cores aleatórias para cada ponto de convergência. Olhe bem de perto e você poderá ver as raízes marcadas.

Aqui está o resultado do exemplo polinomial.

resultado, por exemplo, polinômio


Boa! Eu gosto disso.
FUZxxl

14

Java, 1093 1058 1099 1077 caracteres

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="";}}}

Entrada é argumentos de linha de comando - por exemplo, executar java F 1 0 0 -1 . A saída é stdout no formato PPM (pixmap ASCII).

A escala é escolhida usando o limite Fujiwara no valor absoluto das raízes complexas de um polinômio; Em seguida, multiplico o limite por 1,5. Eu ajusto o brilho pela taxa de convergência, para que as raízes estejam nas manchas mais brilhantes. Portanto, é lógico usar branco em vez de preto para marcar as localizações aproximadas das raízes (o que me custa 41 caracteres por algo que nem pode ser feito "corretamente". então algumas raízes saem sem rótulo; se eu rotular todos os pontos que convergem para 0,6 pixels de si mesmos, então algumas raízes sairão rotuladas com mais de um pixel; portanto, para cada raiz eu rotulo o primeiro ponto encontrado a convergir para dentro de 1 pixel de si mesmo )

Imagem para o exemplo polinomial (convertido em png com o GIMP): Raízes de x ^ 5 + (- 2 + 7,5i) x ^ 4 + (23.0004-3.8i) x ^ 3 + 12i x ^ 2 + (5.1233 + 0.1i)


@FUZxxl, a imagem é da versão antiga. Vou carregar um com a taxa de convergência mais tarde. Mas o problema de marcar as raízes é determinar qual pixel marcar. É o problema clássico de que, com o ponto flutuante, você não pode usar testes de igualdade exatos; portanto, é necessário comparar com um épsilon. Como resultado, não tenho valores "canônicos" para as raízes. Eu poderia marcar pixels que convergem em uma etapa, mas isso não garante marcar nada, e poderia marcar um bloco de 4 pixels para uma única raiz.
Peter Taylor

@ Peter Taylor: Como você vê, Keith Randall também encontrou uma solução para esse problema. Eu adicionei esse requisito como uma dificuldade extra. Uma maneira de fazer isso seria calcular o pixel mais próximo para cada raiz e, em seguida, verificar se cada pixel era igual a ele.
FUZxxl

@FUZxxl, você não entendeu meu argumento. "O pixel mais próximo" de uma raiz não está bem definido. No entanto, eu posso hackear algo em que possa funcionar para todos os casos de teste que você apresenta e tenho a impressão de que isso o fará feliz. Vou colorir de branco, não de preto, porque é mais lógico.
11118 Peter Taylor em São

@ Peter Taylor: Ok.
FUZxxl

6
Minha foto do perfil deve mudar em breve para x^6-9x^3+8, cuidadosamente projetada, escolhendo as raízes e, em seguida, usando o Wolfram Alpha para simplificar o polinômio. Ok, eu trapaceie trocando as tonalidades depois no GIMP.
Peter Taylor

3

Python, 633 bytes

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()

Após aceleração e embelezamento (756 bytes)

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()

O gráfico abaixo é para Newton Fractal da função log (z).

Fractal de Newton para log (z)


Você pode usar nomes mais curtos (1 caractere) e remover espaços em branco combinando várias linhas usando ;. Além disso, remova todos os espaços possíveis.
mbomb007

Alguns golfistas regulares reduzem isso para apenas 353 bytes ! Não testei (não matplotlibaqui), então não há garantia de que ainda funcione.
Khuldraeseth na'Barya 06/04/19
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.