Dr. Arne Jachens
[Fortran + Xfig] [php + svg] [Konvergenz]

Fraktal als Vektorgrafik

Manche Dinge sind einfach zu komplex, um sie von Hand zu machen, lassen sich aber auf ganz einfache Regeln zurückführen.
Ein Beispiel hierfür ist das Fraktal der Startseite www.Jachens.de.

Die Positionen jeder neuen Generation von Kreisen lässt sich per Rekursion aus ihren Müttern und Großmüttern ableiten.

Sofern man nicht direkt Postscript schreiben möchte, scheint mir das Vektorzeichenprogramm Xfig eine gute Alternative zu sein, da dessen Xfig-Format eine fast menschenlesbare ASCII-Datei ist.

Ja, es gibt Menschen, die noch FORTRAN programmieren.

 
! ifort blauesfraktal.f90 -o blauesfraktal.x && ./blauesfraktal.x
 
module global
implicit none
integer :: i,j,n,z,loop
integer :: gesamt
integer :: Mutter, Oma
real    :: x,y, x_Mutter, y_Mutter, x_Oma, y_Oma
real    :: L,Breite
integer,  parameter                 :: tiefe=6
integer,  dimension(0:tiefe)        :: Anzahl
integer, allocatable,dimension(:)   :: Muetter
real,     dimension(0:tiefe)        :: Radius
real,    allocatable,dimension(:,:) :: Position
end module global
!%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%!
program blauesfraktal
use global
implicit none
 
!Radien
x=1.0
do n=1,tiefe
   x= x + 1.0/(2.0*real(n))
end do
!Skalierung fuer Xfig
L=2**tiefe *100
Breite = 4*L
Radius(0)=L
do n=1,tiefe
   Radius(n)=0.5*Radius(n-1)
end do !n
 
!Anzahl der Kreise
Anzahl(0)=1
Anzahl(1)=4
do n=2,tiefe
   Anzahl(n) = Anzahl(n-1)*3
end do !n
gesamt=0
do n=0,tiefe
   gesamt = gesamt + Anzahl(n)
   write (*,*) n,Anzahl(n),Radius(n)
end do !n
 
allocate(Position(1:2,0:gesamt))
allocate(Muetter(0:gesamt))
 
!Positionen
n=0
z=0
Position(1,z) = 0.0
Position(2,z) = 0.0
n=1
!Norden
z=z+1
Muetter(z)=0
Position(1,z) = Position(1,Muetter(z))
Position(2,z) = Position(2,Muetter(z)) + 0.5*Radius(n-1) + Radius(n)
!Osten
z=z+1
Muetter(z)=0
Position(1,z) = Position(1,Muetter(z)) + 0.5*Radius(n-1) + Radius(n)
Position(2,z) = Position(2,Muetter(z))
!Sueden
z=z+1
Muetter(z)=0
Position(1,z) = Position(1,Muetter(z))
Position(2,z) = Position(2,Muetter(z)) - 0.5*Radius(n-1) - Radius(n)
!Westen
z=z+1
Muetter(z)=0
Position(1,z) = Position(1,Muetter(z)) - 0.5*Radius(n-1) - Radius(n)
Position(2,z) = Position(2,Muetter(z))
 
gesamt=-1
do n=2,tiefe
   !Ablaufen der vorherigen Generation
   gesamt = gesamt + Anzahl(n-2)
   do loop=1, Anzahl(n-1)
      Mutter=gesamt+loop
      Oma=Muetter(Mutter)
      x_Mutter= Position(1,Mutter)
      y_Mutter= Position(2,Mutter)
      x_Oma   = Position(1,Oma)
      y_Oma   = Position(2,Oma)
      !Abfrage der Himmelsrichtungen
      x=Position(1,Mutter) 
      y=Position(2,Mutter) + 0.5*Radius(n-1) + Radius(n)
      call test_position !Norden
      x=Position(1,Mutter) + 0.5*Radius(n-1) + Radius(n)
      y=Position(2,Mutter)
      call test_position !Osten
      x=Position(1,Mutter) 
      y=Position(2,Mutter) - 0.5*Radius(n-1) - Radius(n)
      call test_position !Sueden
      x=Position(1,Mutter) - 0.5*Radius(n-1) - Radius(n)
      y=Position(2,Mutter)
      call test_position !Westen
   end do !loop
end do !n
 
call write_fig
 
end program blauesfraktal
!%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%!
subroutine test_position
use global
implicit none
real :: a,b, c,d
 
a=x       -x_Mutter
b=y       -y_Mutter
c=x_Mutter-x_Oma
d=y_Mutter-y_Oma
!quasi nach aussen
if ( (a+c)**2+(b+d)**2 > c**2+d**2 ) then
   z=z+1
   Muetter(z)=Mutter
   Position(1,z) = x
   Position(2,z) = y
end if
end subroutine test_position
!%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%!
! http://www.xfig.org/userman/fig-format.html
subroutine write_fig
use global
implicit none
integer, parameter :: linecolor = 10
integer, parameter :: fillcolor = 10
integer            :: thickness
!            -1 = Default
!             0 = Black
!             1 = Blue
!             2 = Green
!             3 = Cyan
!             4 = Red
!             5 = Magenta
!             6 = Yellow
!             7 = White
!          8-11 = four shades of blue (dark to lighter)
!         12-14 = three shades of green (dark to lighter)
 
open (12,file="blauesFraktal.fig")
write (12,"(a)") "#FIG 3.2"
write (12,"(a)") "Landscape"
write (12,"(a)") "Center"
write (12,"(a)") "Metric"
write (12,"(a)") "A4"
write (12,"(a)") "100.00"
write (12,"(a)") "Single"
write (12,"(a)") "-2"
write (12,"(a)") "1200 2"
Radius = 0.5 * Radius
do n=0,tiefe
   do loop=1, Anzahl(n)
      z=sum(Anzahl(0:n-1)) -1
      x= 0.5*Breite + Position(1,z+loop)
      y= 0.5*Breite + Position(2,z+loop)
      x_mutter= 0.5*Breite + Position(1,Muetter(z+loop))
      y_mutter= 0.5*Breite + Position(2,Muetter(z+loop))
      thickness = int(Radius(n)/20.0)
      !Kreise
      write (12,"(a,i,a,i,a,8i)") "1 3 0 1 ",&
           linecolor," ",fillcolor," 50 0 20 0.000 1 0.0000", &
           int(x), int(y), int(Radius(n)), int(Radius(n)),    &
           int(x), int(y), int(x+Radius(n)), int(y+Radius(n))
      !Verbindungslinien
      write (12,"(3(a,i),a)") "2 1 0 ",thickness," ",linecolor," ", &
           fillcolor," 50 0 20 0.000 0 0 -1 0 0 2"
      write (12,"(4i)") int(x), int(y), int(x_mutter), int(y_mutter)
   end do !loop
end do !n
 
close (12)
end subroutine write_fig

 

Mittlerweile habe ich das Programm auch in PHP umgeschrieben:

<?php
function genJachensFractal($level){
  /*
  Author: Dr. Arne Jachens
  Source: https://arne.jachens.de
  */
  global $Position,$Mother,$NoCircles,$Radius;
  $msg = "";
  
  /* no of cicles per level */
  $NoCircles[-1] = 0;
  $NoCircles[0] = 1;
  $NoCircles[1] = 4;
  for($n=2;$n<$level;$n++){
    $NoCircles[$n] = $NoCircles[$n-1]*3;
  } #n

  /* radii and distances of the cycles */
  $Radius[0] = 50;
  for($n=1;$n<$level;$n++){
    $Radius[$n] = 0.5*$Radius[$n-1];
    $step[$n] = 2.0*$Radius[$n-1];
    $msg.= $n." ".$NoCircles[$n]." ".$Radius[$n]."</br>\n";
  } #n

  $Position['x'] = array();
  $Position['y'] = array();
  $Mother = array();
 
  /* Positionen level=0 */
  $n=0;
  $Position['x'][$n] = 0.0;
  $Position['y'][$n] = 0.0;
  $Mother[0]=0;               #creation
  
  /* Positionen level>0 */
  $offset=0;
  for($n=1;$n<$level;$n++){
    /* loop over nodes of previous level */
    $offset = $offset + $NoCircles[$n-2];
    for($loop=0;$loop<$NoCircles[$n-1];$loop++){
      $thisMother = $offset+$loop;
      $x_Mother = $Position['x'][$thisMother];
      $y_Mother = $Position['y'][$thisMother];
      /* North */
      $x = $x_Mother ;
      $y = $y_Mother + $step[$n];
      test_position($x,$y,$thisMother);
      /* East */
      $x = $x_Mother + $step[$n];
      $y = $y_Mother;
      test_position($x,$y,$thisMother);
      /* South */
      $x = $x_Mother ;
      $y = $y_Mother - $step[$n];
      test_position($x,$y,$thisMother);
      /* West */
      $x = $x_Mother - $step[$n];
      $y = $y_Mother;
      test_position($x,$y,$thisMother);
    } #loop
  } #n
  return $msg;
}

/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
function test_position($x,$y,$thisMother){
  global $Position,$Mother;
  $Grandma   = $Mother[$thisMother];
  $x_Mother  = $Position['x'][$thisMother];
  $y_Mother  = $Position['y'][$thisMother];
  $x_Grandma = $Position['x'][$Grandma];
  $y_Grandma = $Position['y'][$Grandma];
  $a = $x       -$x_Mother;
  $b = $y       -$y_Mother;
  $c = $x_Mother-$x_Grandma;
  $d = $y_Mother-$y_Grandma;
  /* not back to grandma */
  if(pow($a+$c,2)+pow($b+$d,2) > pow($c,2)+pow($d,2) ){
    array_push($Mother,$thisMother);
    array_push($Position['x'],$x);
    array_push($Position['y'],$y);
  } #fi
  return;
}

/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
function writeSVG($level,$fill,$stroke,$fracFile){
  global $Position,$Mother,$NoCircles,$Radius;

  /* scaling */
  $imgWidth = 8*$Radius[0];

  /* stroke-width reduction */
  for($n=0;$n<$level;$n++){
    $strokeWidth[$n] = 0.02*$Radius[$n];
  } #n
  
  $fracSVG = "";
  /* rectangles as  connectors */
  $offset=0;
  for($n=1;$n<$level;$n++){
    $offset = $offset + $NoCircles[$n-1];
    /* loop over nodes of this level */
    for($loop=0;$loop<$NoCircles[$n];$loop++){
      $x_tochter = 0.5*$imgWidth + $Position['x'][$offset+$loop];
      $y_tochter = 0.5*$imgWidth + $Position['y'][$offset+$loop];
      $x_Mother  = 0.5*$imgWidth + $Position['x'][$Mother[$offset+$loop]];
      $y_Mother  = 0.5*$imgWidth + $Position['y'][$Mother[$offset+$loop]];
      $x = min($x_tochter,$x_Mother);
      $y = min($y_tochter,$y_Mother);
      if(abs($x_tochter-$x_Mother)<1E-6){
	/*vertical */
	$width  = $Radius[$n];
	$x = $x-0.5*$width;
	$height = abs($y_tochter-$y_Mother);
      }else{
	/* horizontal */
	$width  = abs($x_tochter-$x_Mother);
	$height = $Radius[$n];
	$y = $y-0.5*$height;
      }
      $fracSVG.= "<rect x='".$x."' y='".$y."' width='".$width."' height='".$height."' stroke-width='".$strokeWidth[$n]."' class='fracRect' />\n";
    } #loop
  } #n
 
  
  /* circles */
  $offset=0;
  for($n=0;$n<$level;$n++){
    $offset = $offset + $NoCircles[$n-1];
    /* loop over nodes of this level */
    for($loop=0;$loop<$NoCircles[$n];$loop++){
      $xc = 0.5*$imgWidth + $Position['x'][$offset+$loop];
      $yc = 0.5*$imgWidth + $Position['y'][$offset+$loop];
      $r = $Radius[$n];
      $fracSVG.= "<circle cx='".$xc."' cy='".$yc."' r='".$r."' stroke-width='".$strokeWidth[$n]."' class='fracCirc' />\n";
    } #loop
  } #n
  
  $svgHead = "<?xml version='1.0' encoding='UTF-8' standalone='no'?>\n";
  $svgHead.= "<svg xmlns='http://www.w3.org/2000/svg' \n";
  $svgHead.= "   xmlns:dc='http://purl.org/dc/elements/1.1/' \n";
  $svgHead.= "   xmlns:cc='http://creativecommons.org/ns#' \n";
  $svgHead.= "   xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' \n";
  $svgHead.= "   viewBox='0 0 ".$imgWidth." ".$imgWidth."'>\n";
  $svgHead.= "<defs>\n";
  $svgHead.= "  <style type='text/css'><![CDATA[\n";
  $svgHead.= "    .fracCirc {\n";
  $svgHead.= "      stroke: ".$stroke.";\n";
  $svgHead.= "      fill: ".$fill.";\n";
  $svgHead.= "    }\n";
  $svgHead.= "    .fracRect {\n";
  $svgHead.= "      stroke: ".$stroke.";\n";
  $svgHead.= "      fill: ".$fill.";\n";
  $svgHead.= "    }\n";
  $svgHead.= " ]]></style>\n";
  $svgHead.= "</defs>\n";
  /*
  $svgHead.= "<metadata>\n";
  $svgHead.= "   <rdf:RDF>\n";
  $svgHead.= "      <cc:Work>\n";
  $svgHead.= "        <dc:format>image/svg+xml</dc:format>\n";
  $svgHead.= "        <dc:type\n";
  $svgHead.= "           rdf:resource='http://purl.org/dc/dcmitype/StillImage' />\n";
  $svgHead.= "        <dc:title>Jachens Logo</dc:title>\n";
  $svgHead.= "        <dc:creator>\n";
  $svgHead.= "          <cc:Agent>\n";
  $svgHead.= "            <dc:title>Dr. Arne Jachens</dc:title>\n";
  $svgHead.= "          </cc:Agent>\n";
  $svgHead.= "        </dc:creator>\n";
  $svgHead.= "        <dc:publisher>\n";
  $svgHead.= "          <cc:Agent>\n";
  $svgHead.= "            <dc:title />\n";
  $svgHead.= "          </cc:Agent>\n";
  $svgHead.= "        </dc:publisher>\n";
  $svgHead.= "        <dc:source>https://www.jachens.de</dc:source>\n";
  $svgHead.= "        <cc:license\n";
  $svgHead.= "           rdf:resource='' />\n";
  $svgHead.= "      </cc:Work>\n";
  $svgHead.= "    </rdf:RDF>\n";
  $svgHead.= "</metadata>\n";
  */
  $svgFoot = "\n</svg>";

  $fid = fopen($fracFile,"w");
  fputs($fid,$svgHead);
  fputs($fid,$fracSVG);
  fputs($fid,$svgFoot);
  fclose($fid);
  $msg = "<p>Fractal written to:</br>\n".$fracFile."</p>";
  return $msg;
}

?>



 

Konvergenz der Arme des Franktals

Um die Ausgangsfrage zu beantworten, ob die Arme des Fraktals sich irgendwann berühren:
Nehmen wir 2 markante Punkte, indem wir einmal eine Generation nach rechts und dann nach oben laufen, und einmal eine Generation nach oben und dann nach rechts laufen.
In diesem Beispiel ist der Abstand der Tochter von der Mutter gleich dem doppelten Radius der Mutter. Und der Radius der Tochter ist halb so groß wie der der Mutter.

fracDistance.png

Die Punkte liegen symmetisch und tauschen einfach ihre Koordinaten; dadurch ergibt sich bei der Abstandsberechnung die Wurzel aus 2.

Der Ausdruck in den eckigen Klammern lässt sich auf die geometrische Reihe zurückführen und mit deren Grenzwert wird der Ausdruck in den eckigen Klammern Null.

Mit anderen Worten: Für jeden endlichen Detailierungsgrad bleibt der Abstand der Fraktalarme infenitesimal klein.

Fraktal als Vektorgrafik

Eines Tages fragte ich mich, was wohl passiert, wenn man an einen Kreis kleinere Kreise in alle Himmelsrichtungen anfügt, ob sich die Äste wohl berühren würden?
Der Versuch, dies zu zeichnen brachte mich sehr schnell dazu, mir einen Algorithmus zu überlegen und eine Vektorgrafik zu errechnen.
Seitdem verwende ich dieses Fraktal als Logo.

Detailierung:
Füllung:
Linienfarbe: