Jump to my Home Page Send me a message Check out stuff on GitHub Check out my photography on Instagram Check out my profile on LinkedIn Check out my profile on reddit Check me out on Facebook

Mitch Richling: Lorenz Strange Attractor

Author: Mitch Richling
Updated: 2023-01-14

exp-ODElorenzDEQ-ART_960x540.jpg

Table of Contents

1. Strange Attractors

First, we need to set the stage with the idea of a dynamical system. Instead of a formal definition, I think a few examples will convey the idea best. Think about the movement of a pendulum, a planet and moon orbiting a star in our solar system, or a bouncing ball on your kitchen floor. An attractor for such a system is the behavior, or set of behaviors, the system will approach after a long enough time. In the case of the bouncing balls, the attractor is quite simple – it is the flat surface of the floor. Attractors don't need to be so simple. Consider for example the orbiting planet. In this case, the attractor might be a complex orbit. Now, a strange attractor is an attractor that is "complicated".

The thing that makes strange attractors so fascinating is that they show the overall patterns of a dynamical system in spite of the fact that the systems described are unbelievably complex. The way a body orbits three or more gravitating bodies is a very good example. Another good example is the strange attractor attributed to Lorenz discussed on this page.

2. The Lorenz Strange Attractor

Lorenz was studying weather prediction, and he developed a rather simple model involving a differential equation in three dimensional Euclidean space. He had great difficulty solving this equation numerically because it is very sensitive to its initial conditions. Each time he would solve the system, even with tiny rounding changes in the 6th digit, he would obtain radically different solutions. This sort of sensitivity has come to be part of the definition of chaotic systems. The differential equation he was working with was:

\[\frac{df}{dt}=[a(y-x),x(b-z)-y,xy-cz]\] \[\frac{df}{dt}=A \circ [y-x,x,-z]-[0,y+xz,-xy]\]

Note \(\circ\) is the Hadamard product (element-wise). Traditional initial conditions are the following:

\[[x, y, z] = [1/10,0,0]\]

While not part of the system proper, this is the most common set of parameters used:

\[A = [a, b, c] = [10,28,8/3]\]

3. Simulation & Visualization: Digital Computer

3.1. Solving & Plotting the Equations with Euler's Method

The picture below is a plot of this equation projected onto the XY-plane. The equation was solved using a short program implementing a very simple version of Euler's method. You can find several example implementations in various languages linked below. Once you have the data, you can generate an image with POV-Ray using any of the example POV-Ray scene files below.

For such a simple graph POV-Ray is overkill, but it demonstrates the basic idea of using a ray tracer to obtain nice plots – like the ones below.

lorenz1_pov_s5_480x270.jpg

3.2. Solving & Plotting the Equations with Adaptive Euler's Method

Euler's method is the most common numerical ODE algorithm used to solve this equation; however, a more sophisticated solution method is required to obtain high quality plots of the curve. When graphing a curve, the best plots are usually obtained when the sample points used to draw the curve are evenly spaced along the curve. Unfortunately Euler's method generates solution points that are not uniformly spaced along the curve in spite of the fact that the input variable delta is held constant. One option is to modify Euler's method to adaptively change the input variable delta so that the solution points are never too far apart. The resulting programs are a bit more complex – complex enough that I didn't implement a pure POV-Ray version. You can find (C & Perl implementations below. We will still connect the dots with cylinders, but the cylinders will be very short – so the curve will appear to be smooth. The images and movies below were all generated with POV-Ray using one of the example POV-Ray scene files below.

lorenz2_pl_s1_480x270.jpg
lorenz2_pl_s2_240x135.jpg lorenz2_pl_s3_240x135.jpg
Click on the thumbnail for a larger version!
lorenz2_pl_s1_movie_120x67.gif lorenz2_pl_s2_movie_120x67.gif lorenz2_pl_s3_movie_120x67.gif

The adaptive Euler's method above sets tight upper bounds on maximum step size so that we may obtain visually appealing graphs of the Lorenz system. This methodology is, in a way, at direct odds with most modern, advanced numerical ODE algorithms that generally use adaptive step control to make the largest steps possible while still controlling error. By observing the sizes of the steps taken by such algorithms, we can learn a bit about the systems – where they are more and less stable for example. In the image below the solution points produced by a fourth order Runge–Kutta algorithm are visible as blue stripes on the curve. The viewpoint has been changed to more clearly illustrate how tightly packed together the solution points are near the central parts of the spiral.

exp-ODElorenzDEQ-ART_960x540.jpg

3.3. Code

3.3.1. Makefile

Makefile used for most of the images on this page.

# -*- Mode:Makefile; Coding:us-ascii-unix; fill-column:132 -*-
####################################################################################################################################
# @file      makefile
# @author    Mitch Richling http://www.mitchr.me
# @Copyright Copyright 1994,1995,1997,2001,2014 by Mitch Richling.  All rights reserved.
# @Revision  $Revision: 1.7 $
# @SCMdate   $Date: 2014/10/11 14:55:27 $
# @brief     Make images and movies.@EOL
# @Keywords  
# @Std       GNUmake
#
#            
#            

#-----------------------------------------------------------------------------------------------------------------------------------

SQUAL = -Q11
#SQUAL = -Q0

SSIZE = -W3840 -H2160
#SSIZE = -W1920 -H1080
#SSIZE = -W960  -H540
#SSIZE = -W480  -H270


MQUAL = -Q11
#MQUAL = -Q5
MSIZE = -W960  -H540
#MSIZE = -W480  -H270
#MSIZE = -W240  -H135

TARGET_L1S = lorenz1_c_s1.png lorenz1_c_s2.png lorenz1_pl_s1.png lorenz1_pl_s2.png lorenz1_pov_s1.png lorenz1_pov_s2.png

TARGET_L2S = lorenz2_c_s1.png lorenz2_c_s2.png lorenz2_pl_s1.png lorenz2_pl_s2.png lorenz2_pl_s3.png lorenz2_pl_s4.png 

TARGET_L2M = lorenz2_pl_s1_movie.mp4 lorenz2_pl_s2_movie.mp4 lorenz2_pl_s3_movie.mp4

TARGET_L2MS = lorenz2_pl_s1_movie_240x135.gif lorenz2_pl_s1_movie_480x270.gif lorenz2_pl_s2_movie_240x135.gif lorenz2_pl_s2_movie_480x270.gif lorenz2_pl_s3_movie_240x135.gif lorenz2_pl_s3_movie_480x270.gif

TARGET_ALL = $(TARGET_L1S) $(TARGET_L2S) $(TARGET_L2M)

################################################################################################################################################################################################################################################################

l1s : $(TARGET_L1S)

l2s : $(TARGET_L2S)

l2m : $(TARGET_L2M)

l2ms : $(TARGET_L2MS)

all : $(TARGET_ALL)

################################################################################################################################################################################################################################################################

lorenz1_c : lorenz1.c
    gcc lorenz1.c -o lorenz1_c

lorenz1_f : lorenz1.f08
    gfortran lorenz1.f08 -o lorenz1_f

lorenz1_c.inc : lorenz1_c
    lorenz1_c > lorenz1_c.inc

lorenz1_c_s1.png : lorenz1_c.inc lorenz_s1.pov
    povray $(SSIZE) $(SQUAL) +A +AM2 -K0.0 +R4 +J3 -P +D  -HIlorenz_s1.pov -Ilorenz1_c.inc -Olorenz1_c_s1.png

lorenz1_c_s2.png : lorenz1_c.inc lorenz_s2.pov
    povray $(SSIZE) $(SQUAL) +A +AM2 -K0.0 +R4 +J3 -P +D  -HIlorenz_s2.pov -Ilorenz1_c.inc -Olorenz1_c_s2.png

lorenz1_pl.inc : lorenz1.pl
    perl lorenz1.pl > lorenz1_pl.inc

lorenz1_pl_s1.png : lorenz1_pl.inc lorenz_s1.pov
    povray $(SSIZE) $(SQUAL) +A +AM2 -K0.0 +R4 +J3 -P +D  -HIlorenz_s1.pov -Ilorenz1_pl.inc -Olorenz1_pl_s1.png

lorenz1_pl_s2.png : lorenz1_pl.inc lorenz_s2.pov
    povray $(SSIZE) $(SQUAL) +A +AM2 -K0.0 +R4 +J3 -P +D  -HIlorenz_s2.pov -Ilorenz1_pl.inc -Olorenz1_pl_s2.png

lorenz1_pov_s1.png : lorenz1.inc lorenz_s1.pov
    povray $(SSIZE) $(SQUAL) +A +AM2 -K0.0 +R4 +J3 -P +D  -HIlorenz_s1.pov -Ilorenz1.inc -Olorenz1_pov_s1.png

lorenz1_pov_s2.png : lorenz1.inc lorenz_s2.pov
    povray $(SSIZE) $(SQUAL) +A +AM2 -K0.0 +R4 +J3 -P +D  -HIlorenz_s2.pov -Ilorenz1.inc -Olorenz1_pov_s2.png

################################################################################################################################################################################################################################################################

lorenz2_c : lorenz2.c
    gcc -lm lorenz2.c -o lorenz2_c

lorenz2_c.inc : lorenz2_c
    lorenz2_c > lorenz2_c.inc

lorenz2_c_s1.png : lorenz2_c.inc lorenz_s1.pov
    povray $(SSIZE) $(SQUAL) +A +AM2 -K0.0 +R4 +J3 -P +D  -HIlorenz_s1.pov -Ilorenz2_c.inc -Olorenz2_c_s1.png

lorenz2_c_s2.png : lorenz2_c.inc lorenz_s1.pov
    povray $(SSIZE) $(SQUAL) +A +AM2 -K0.0 +R4 +J3 -P +D  -HIlorenz_s2.pov -Ilorenz2_c.inc -Olorenz2_c_s2.png

lorenz2_pl.inc : lorenz2.pl
    perl lorenz2.pl > lorenz2_pl.inc

lorenz2_pl_s1.png : lorenz2_pl.inc lorenz_s1.pov
    povray $(SSIZE) $(SQUAL) +A +AM2 -K0.0 +R4 +J3 -P +D  -HIlorenz_s1.pov -Ilorenz2_pl.inc -Olorenz2_pl_s1.png

lorenz2_pl_s2.png : lorenz2_pl.inc lorenz_s2.pov
    povray $(SSIZE) $(SQUAL) +A +AM2 -K0.0 +R4 +J3 -P +D  -HIlorenz_s2.pov -Ilorenz2_pl.inc -Olorenz2_pl_s2.png

lorenz2_pl_s3.png : lorenz2_pl.inc lorenz_s3.pov
    povray $(SSIZE) $(SQUAL) +A +AM2 -K0.0 +R4 +J3 -P +D  -HIlorenz_s3.pov -Ilorenz2_pl.inc -Olorenz2_pl_s3.png

lorenz2_pl_s4.png : lorenz2_pl.inc lorenz_s4.pov
    povray $(SSIZE) $(SQUAL) +A +AM2 -K0.0 +R4 +J3 -P +D  -HIlorenz_s4.pov -Ilorenz2_pl.inc -Olorenz2_pl_s4.png

################################################################################################################################################################################################################################################################

lorenz2_pl_s2_frame120.png : lorenz_s2.pov lorenz2_pl.inc
    povray $(MSIZE) $(MQUAL) +A +R4 +J3 -P +D -KFI1 -KFF120 -KI0 -KF1 -HIlorenz_s2.pov -Ilorenz2_pl.inc -Olorenz2_pl_s2_frame.png

lorenz2_pl_s2_movie.gif : lorenz2_pl_s2_frame120.png
    convert lorenz2_pl_s2_frame*.png lorenz2_pl_s2_movie.gif

lorenz2_pl_s2_movie.mp4 : lorenz2_pl_s2_movie.gif
    avconv -i lorenz2_pl_s2_frame%3d.png lorenz2_pl_s2_movie.mp4

lorenz2_pl_s1_frame120.png : lorenz_s1.pov lorenz2_pl.inc
    povray $(MSIZE) $(MQUAL) +A +R4 +J3 -P +D -KFI1 -KFF120 -KI0 -KF1 -HIlorenz_s1.pov -Ilorenz2_pl.inc -Olorenz2_pl_s1_frame.png

lorenz2_pl_s1_movie.gif : lorenz2_pl_s1_frame120.png
    convert lorenz2_pl_s1_frame*.png lorenz2_pl_s1_movie.gif

lorenz2_pl_s1_movie.mp4 : lorenz2_pl_s1_movie.gif
    avconv -i lorenz2_pl_s1_frame%3d.png lorenz2_pl_s1_movie.mp4

lorenz2_pl_s3_frame120.png : lorenz_s3.pov lorenz2_pl.inc
    povray $(MSIZE) $(MQUAL) +A +R4 +J3 -P +D -KFI1 -KFF120 -KI0 -KF1 -HIlorenz_s3.pov -Ilorenz2_pl.inc -Olorenz2_pl_s3_frame.png

lorenz2_pl_s3_movie.gif : lorenz2_pl_s3_frame120.png
    convert lorenz2_pl_s3_frame*.png lorenz2_pl_s3_movie.gif

lorenz2_pl_s3_movie.mp4 : lorenz2_pl_s3_movie.gif
    avconv -i lorenz2_pl_s3_frame%3d.png lorenz2_pl_s3_movie.mp4

lorenz2_pl_s1_movie_480x270.gif : lorenz2_pl_s1_movie.gif
    convert -sample 480x270 lorenz2_pl_s1_movie.gif lorenz2_pl_s1_movie_480x270.gif 

lorenz2_pl_s2_movie_480x270.gif : lorenz2_pl_s2_movie.gif
    convert -sample 480x270 lorenz2_pl_s2_movie.gif lorenz2_pl_s2_movie_480x270.gif 

lorenz2_pl_s3_movie_480x270.gif : lorenz2_pl_s3_movie.gif
    convert -sample 480x270 lorenz2_pl_s3_movie.gif lorenz2_pl_s3_movie_480x270.gif 

lorenz2_pl_s1_movie_240x135.gif : lorenz2_pl_s1_movie.gif
    convert -sample 240x135 lorenz2_pl_s1_movie.gif lorenz2_pl_s1_movie_240x135.gif 

lorenz2_pl_s2_movie_240x135.gif : lorenz2_pl_s2_movie.gif
    convert -sample 240x135 lorenz2_pl_s2_movie.gif lorenz2_pl_s2_movie_240x135.gif 

lorenz2_pl_s3_movie_240x135.gif : lorenz2_pl_s3_movie.gif
    convert -sample 240x135 lorenz2_pl_s3_movie.gif lorenz2_pl_s3_movie_240x135.gif 

################################################################################################################################################################################################################################################################

clean_intr : 
    rm -rf lorenz1_c lorenz1_c.inc lorenz1_pl.inc lorenz2_c lorenz2_c.inc lorenz2_pl.inc lorenz2_pl_s2_frame*.png lorenz2_pl_s1_frame*.png lorenz2_pl_s3_frame*.png *~ *.pov-state

clean_targets : 
    rm -rf $(TARGET_ALL)

cleanall : clean_targets clean_intr

3.3.2. Euler's Method Solvers

3.3.2.1. Perl
#!/usr/local/bin/perl
# -*- Mode:Perl; Coding:us-ascii-unix; fill-column:132 -*-

####################################################################################################################################
##
# @file      lorenz1.pl
# @author    Mitch Richling http://www.mitchr.me
# @Copyright Copyright 1994,1997,2014 by Mitch Richling.  All rights reserved.
# @Revision  $Revision: 1.1 $
# @SCMdate   $Date: 2014/10/10 19:36:56 $
# @brief     Generate a PovRay include file for the lorenz strange attractor. @EOL
# @Keywords  lorenz strange attractor
# @Std       Perl5
#
# This is a very simple program to generate a curve based on Lorenz's attractor.  It uses Euler's method to solve the equations.
# The "curve" consists of a series of spheres placed at each point found on the curve.  The spheres are connected with cylinders.
# Alternate implimentations: lorenz1.c, lorenz1.inc
#            

#-----------------------------------------------------------------------------------------------------------------------------------

$maxBalls = 10000;
$tDelta = 0.01;
$x = 0.1;
$y = 0.0;
$z = 0.0;
$a = 10.0;
$b = 28.0;
$c = 8.0 / 3.0;
printf(STDERR "Computation starting...\n");
printf("sphere {<%f,%f,%f>, %f texture { crvTex } }\n", $x, $y, $z, 0.2);

for($numBalls=0;$numBalls<$maxBalls;$numBalls++) {
  $xNew = $x + $a*($y-$x)*$tDelta;
  $yNew = $y + ($x*($b-$z)-$y)*$tDelta;
  $zNew = $z + ($x*$y-$c*$z)*$tDelta;

  if(($numBalls % int($maxBalls/20)) == 0) {
    printf(STDERR "Step: %6d  tDelta: %15.3f x: %15.2f y: %15.2f z: %15.2f\n", $numBalls, $tDelta, $x, $y, $z);
  }

  printf("sphere {<%f,%f,%f>, %f texture { crvTex } }\n", $xNew, $yNew, $zNew, 0.2);
  printf("cylinder {<%f,%f,%f>, <%f,%f,%f>, %f texture { crvTex } }\n", $x, $y, $z, $xNew, $yNew, $zNew, 0.2);

  $x=$xNew;
  $y=$yNew;
  $z=$zNew;
}
3.3.2.2. POV-Ray

This implementation is in POV-Ray itself – i.e. we are not using POV-Ray to simply plot some data, but to solve the equations!

// -*- Coding:us-ascii-unix; fill-column:132 -*- 
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// @file      lorenz1.inc
// @author    Mitch Richling http://www.mitchr.me
// @Copyright Copyright 1994,1997,2014 by Mitch Richling.  All rights reserved.
// @Revision  $Revision: 1.1 $ 
// @SCMdate   $Date: 2014/10/10 19:36:53 $
// @brief     Generate a PovRay include file for the lorenz strange attractor. @EOL
// @Keywords  lorenz strange attractor
// @Std       Povray_3.5
//
// This is a very simple program to generate a curve based on Lorenz's attractor.  It uses Euler's method to solve the equations.
// The "curve" consists of a series of spheres placed at each point found on the curve.  The spheres are connected with cylinders.
// Alternate implimentations: lorenz1.pl, lorenz1.c           

//----------------------------------------------------------------------------------------------------------------------------------

#declare tDelta = 0.01;
#declare xCur = 0.1;
#declare yCur = 0.0;
#declare zCur = 0;
#declare a = 10;
#declare b = 28;
#declare c = 8.0 / 3.0;

#declare numBalls=1;
#while (numBalls < 10000)
    #declare xNew = xCur + a*(yCur-xCur)*tDelta;
    #declare yNew = yCur + (xCur*(b-zCur)-yCur)*tDelta;
    #declare zNew = zCur + (xCur*yCur-c*zCur)*tDelta;

    sphere {<xNew,yNew,zNew>, 0.2 texture { crvTex } }
    cylinder {<xCur,yCur,zCur>, <xNew,yNew,zNew>, 0.2 texture { crvTex } }

    #declare xCur=xNew;
    #declare yCur=yNew;
    #declare zCur=zNew;

    #declare numBalls=numBalls+1;
#end
3.3.2.3. C
/* -*- Mode:C; Coding:us-ascii-unix; fill-column:132 -*- */
/**********************************************************************************************************************************/
/**
   @file      lorenz1.c
   @author    Mitch Richling http://www.mitchr.me
   @Copyright Copyright 1994,1997,2014 by Mitch Richling.  All rights reserved.
   @brief     Generate a PovRay include file for the lorenz strange attractor. @EOL
   @Std       C89

   This is a very simple program to generate a curve based on Lorenz's attractor.  It uses Euler's method to solve the equations.
   The "curve" consists of a series of spheres placed at each point found on the curve.  The spheres are connected with cylinders.
   Alternate implimentations: lorenz1.pl, lorenz1.inc

***********************************************************************************************************************************/

#include <stdio.h>

int main() {
  int maxBalls  = 10000;
  double tDelta = 0.01;
  double x      = 0.1;
  double y      = 0.0;
  double z      = 0.0;
  double a      = 10.0;
  double b      = 28.0;
  double c      = 8.0 / 3.0;
  int numBalls;
  double xNew, yNew, zNew;
  fprintf(stderr, "Computation starting...\n");
  printf("sphere {<%f,%f,%f>, %f texture { crvTex } }\n", x, y, z, 0.2);

  for(numBalls=0;numBalls<maxBalls;numBalls++) {
    xNew = x + a*(y-x)*tDelta;
    yNew = y + (x*(b-z)-y)*tDelta;
    zNew = z + (x*y-c*z)*tDelta;

    /* Display 20 "status" messages. */
    if((numBalls % (int)(maxBalls/20)) == 0)
      fprintf(stderr, "Step: %6d  tDelta: %15.3f x: %15.5f y: %15.5f z: %15.5f\n", numBalls, tDelta, x, y, z);

    printf("sphere {<%f,%f,%f>, %f texture { crvTex } }\n", xNew, yNew, zNew, 0.2);
    printf("cylinder {<%f,%f,%f>, <%f,%f,%f>, %f texture { crvTex } }\n", x, y, z, xNew, yNew, zNew, 0.2);

    x=xNew;
    y=yNew;
    z=zNew;
  }

  return 0;
}
3.3.2.4. Fortan 2008
! -*- Mode:F90; Coding:us-ascii-unix; fill-column:132 -*-
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!
! @file      lorenz1.f08
! @author    Mitch Richling <https://www.mitchr.me>
! @date      2021-05-18
! @brief     Generate a PovRay include file for the lorenz strange attractor. @EOL
! @std       F2003
!
! This is a very simple program to generate a curve based on Lorenz's attractor.  It uses Euler's method to solve the equations.
! The "curve" consists of a series of spheres placed at each point found on the curve.  The spheres are connected with cylinders.
! Alternate implimentations: lorenz1.pl, lorenz1.c, lorenz1.inc
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

PROGRAM lorenz1

  USE, INTRINSIC :: ISO_FORTRAN_ENV

  IMPLICIT NONE

  INTEGER, PARAMETER                    :: maxBalls = 10000
  REAL(REAL64), PARAMETER               :: tDelta   = 0.01_REAL64;
  REAL(REAL64), PARAMETER, DIMENSION(3) :: A        = [10.0_REAL64, 28.0_REAL64, 8.0_REAL64 / 3.0_REAL64 ]
  REAL(REAL64), DIMENSION(3)            :: x        = [ 0.1_REAL64,  0.0_REAL64,              0.0_REAL64 ], xNew
  INTEGER                               :: numBalls

  WRITE(ERROR_UNIT, fmt=*) "Computation starting..."
  WRITE(OUTPUT_UNIT, fmt='("sphere {<", f0.3, ",", f0.3, ",", f0.3, ">, 0.2 texture { crvTex } }")') x(1), x(2), x(3)
  DO numBalls=0,(maxBalls-1)

     ! This is a more natural translation of the equations into vector form.  
     ! This rearrangement of the order of the arithmetic steps leads to very different results by iteration 3000
     ! That's the essence of sensitive dependence (chaos)!!
     ! xNew = x + tDelta * (A * [x(2)-x(1), x(1), -x(3)] - [0.0_REAL64, x(2)+x(1)*x(3), -x(1)*x(2)])

     ! This is precisely the same sequence of arithmetic as used in the C, Perl, & POV-Ray code:
     xNew = x + [ A(1)*(x(2)-x(1)), x(1)*(A(2)-x(3))-x(2), x(1)*x(2)-A(3)*x(3) ] * tDelta

    ! Display 20 status messages.
    IF (MODULO(numBalls, maxBalls/20) == 0) THEN
       WRITE(ERROR_UNIT, fmt='(i6, f15.5, 3(f15.10))') numBalls, tDelta, x
    END IF

    WRITE(OUTPUT_UNIT, fmt='("sphere {<", f0.3, ",", f0.3, ",", f0.3, ">, 0.2 texture { crvTex } }")') xNew(1), xNew(2), xNew(3)
    WRITE(OUTPUT_UNIT, fmt='("cylinder {<", 2(f0.3, ","), f0.3, ">, <", 2(f0.3, ","), f0.3, ">, 0.2 texture { crvTex } }")') &
         x(1), x(2), x(3), xNew(1), xNew(2), xNew(3)

    x=xNew        
  END DO

END PROGRAM lorenz1

3.3.3. Adaptive Euler's Method Solvers

3.3.3.1. Perl
#!/usr/local/bin/perl
# -*- Mode:Perl; Coding:us-ascii-unix; fill-column:132 -*-

####################################################################################################################################
##
# @file      lorenz2.pl
# @author    Mitch Richling http://www.mitchr.me
# @Copyright Copyright 1994,1997,2001,2014 by Mitch Richling.  All rights reserved.
# @Revision  $Revision: 1.1 $
# @SCMdate   $Date: 2014/10/10 19:40:31 $
# @brief     Generate a PovRay include file for the lorenz strange attractor. @EOL
# @Keywords  lorenz strange attractor povray adaptive Euler
# @Std       Perl5
#
# This is an adaptive Euler's method to solve Lorenz's differential equations.  The value of delta f is bounded above and below so
# that the curve looks smooth when we are done.  The bounding is adjusted by adjusting t delta in each leap-frog step.  The value of
# delta t is bounded absolutely above and below to keep things from going nuts.  The method of finding an acceptable t delta is to
# use bisection between the bounds on t delta.  This is a strange adaptation of Euler's method, but it works well for this
# application. Alternate implimentation: lorenz2.c
#            

#-----------------------------------------------------------------------------------------------------------------------------------

$distToGo = 2500;                  # How long should the curve be?

$sphereRad = 0.2;                  # Size of the spheres to use

$maxMovDelta   = $sphereRad / 4;   # The maximum delta for the function.
$minMovDelta   = $sphereRad / 5;   # The minimum delta for the function.

$minTdelta        = 0.000001;      # Minimum value for tDelta
$maxTdelta        = 0.01;          # Maximum value for tDelta 
$tDeltaZeroThresh = 0.00000001;    # Episolon ball for zero.
$maxNumBisect     = 10;            # Max bisections on tDelta.

# Initial conditions, and constants.
$x = 0.1;
$y = 0.0;
$z = 0;
$a = 10;
$b = 28;
$c = 8.0 / 3;

# Solve the equations.....
$tDelta       = $maxTdelta;
while($dist < $distToGo) {
  $numBalls++;
  # Take a step up so that we minimize the number of balls.
  $tDelta = ($maxTdelta + $tDelta)/2;
  if($tDelta > $maxTdelta) { $tDelta = $maxTdelta; }
  $curMaxTdelta = $maxTdelta;
  $curMinTdelta = $minTdelta;
  # Bisect t delta until we have done it too much or
  # until we have a good value for t delta.
  $numBisect = 0;
  $doneBisecting = 0;
  while( !($doneBisecting)) {
    $numBisect++;
    $Xdelta = $a*($y-$x)*$tDelta;
    $Ydelta = ($x*($b-$z)-$y)*$tDelta;
    $Zdelta = ($x*$y-$c*$z)*$tDelta;
    $movDelta = sqrt(abs($Xdelta**2+$Ydelta**2+$Zdelta**2));
    if($numBisect > $maxNumBisect) {
      $doneBisecting = 1;
    } else {
      if($movDelta > $maxMovDelta) {
        if(abs($tDelta-$curMinTdelta)<$tDeltaZeroThresh) {
          $doneBisecting = 1;
        } else {
          $curMaxTdelta = $tDelta;
          $tDelta = ($tDelta + $curMinTdelta)/2;
          if($tDelta < $minTdelta) { $tDelta = $minTdelta; }
        }
      } elsif($movDelta < $minMovDelta) {
        if(abs($tDelta-$curMaxTdelta)<$tDeltaZeroThresh) {
          $doneBisecting = 1;
        } else {
          $curMinTdelta = $tDelta;
          $tDelta = ($curMaxTdelta + $tDelta)/2;
          if($tDelta > $maxTdelta) { $tDelta = $maxTdelta; }
        }
      } else {
        $doneBisecting = 1;
      }
    }
  }
  $dist += $movDelta;
  $xOld = $x;
  $yOld = $y;
  $zOld = $z;
  $x = $x + $Xdelta;
  $y = $y + $Ydelta;
  $z = $z + $Zdelta;
  if($movDelta>$maxMovDelta) {
    $moveCh = '*';
    $numOver++;
  } else {
    $moveCh = ' ';
  }

  #printf(STDERR "Balls: %7d tDel: %9.6f x: %9.2f y: %9.2f z: %9.2f Dist: %7.1f fDel: %9.3f #Over: %7d %1s-%1s\n", $numBalls, $tDelta, $x, $y, $z, $dist, $movDelta, $numOver, $moveCh, ($numBisect>$maxNumBisect?'*':' '));
  print "sphere {<$x,$y,$z>, $sphereRad texture { crvTex } }\n"; 
  print "cylinder {<$x,$y,$z>, <$xOld,$yOld,$zOld>, $sphereRad texture { crvTex } }\n";

}
printf(STDERR "Balls: %7d Dist: %7.1f #Over: %7d\n", $numBalls, $dist, $numOver);
3.3.3.2. C
/* -*- Mode:C; Coding:us-ascii-unix; fill-column:132 -*- */
/**********************************************************************************************************************************/
/**
   @file      lorenz2.c
   @author    Mitch Richling http://www.mitchr.me
   @Copyright Copyright 1994,1997,2001,2014 by Mitch Richling.  All rights reserved.
   @Revision  $Revision: 1.2 $ 
   @SCMdate   $Date: 2014/10/10 19:40:05 $
   @brief     Generate a PovRay include file for the lorenz strange attractor. @EOL
   @Keywords  lorenz strange attractor povray adaptive Euler
   @Std       C89

   This is an adaptive Euler's method to solve Lorenz's differential equations.  The value of delta f is bounded above and below so
   that the curve looks smooth when we are done.  The bounding is adjusted by adjusting t delta in each leap-frog step.  The value
   of delta t is bounded absolutely above and below to keep things from going nuts.  The method of finding an acceptable t delta is
   to use bisection between the bounds on t delta.  This is a strange adaptation of Euler's method, but it works well for this
   application.  Alternate implimentation lorenz2.pl.

***********************************************************************************************************************************/

#include <stdio.h>
#include <math.h>

#define DEBUG 0
#define CYL 1

int main () {  
  double distToGo = 2500.0;             /* How long should the curve be? */

  double sphereRad = 0.2;               /* Size of the spheres to use */

  double maxMovDelta = sphereRad / 4.0; /* The maximum delta for the function. */
  double minMovDelta = sphereRad / 5.0; /* The minimum delta for the function. */


  double minTdelta        = 0.000001;   /* Minimum value for tDelta */
  double maxTdelta        = 0.01;       /* Maximum value for tDelta */
  double tDeltaZeroThresh = 0.00000001; /* Episolon ball for zero. */
  double maxNumBisect     = 10;         /* Max bisections on tDelta. */

  /* Initial conditions, and constants. */
  double x = 0.1;
  double y = 0.0;
  double z = 0;
  double a = 10;
  double b = 28;
  double c = 8.0 / 3;

  double curMaxTdelta, curMinTdelta, tDelta, dist, Xdelta, Ydelta, Zdelta, movDelta, xOld, yOld, zOld;
  int    numBisect, doneBisecting, numBalls, numOver;
  char   moveCh;

 /*  Solve the equations..... */
  numBalls = 0;
  tDelta = maxTdelta;
  dist = 0.0;
  numOver = 0;
  while (dist < distToGo) {
    numBalls++;
    /*  Take a big step up to minimize the number of balls. */
    tDelta = (maxTdelta + tDelta) / 2;
    if (tDelta > maxTdelta) {
      tDelta = maxTdelta;
    }
    curMaxTdelta = maxTdelta;
    curMinTdelta = minTdelta;
    /*  Bisect t delta until we have done it too much or until we have a good value for t delta. */
    numBisect = 0;
    doneBisecting = 0;
    while (!(doneBisecting)) {
      numBisect++;
      Xdelta = a * (y - x) * tDelta;
      Ydelta = (x * (b - z) - y) * tDelta;
      Zdelta = (x * y - c * z) * tDelta;
      movDelta = sqrt (fabs (Xdelta * Xdelta + Ydelta * Ydelta + Zdelta * Zdelta));
      if (numBisect > maxNumBisect) {
        doneBisecting = 1;
      } else {
        if (movDelta > maxMovDelta) {
          if (fabs (tDelta - curMinTdelta) < tDeltaZeroThresh) {
            doneBisecting = 1;
          } else {
            curMaxTdelta = tDelta;
            tDelta = (tDelta + curMinTdelta) / 2;
            if (tDelta < minTdelta) {
              tDelta = minTdelta;
            }
          }
        } else if (movDelta < minMovDelta) {
          if (fabs (tDelta - curMaxTdelta) < tDeltaZeroThresh) {
            doneBisecting = 1;
          } else {
            curMinTdelta = tDelta;
            tDelta = (curMaxTdelta + tDelta) / 2;
            if (tDelta > maxTdelta) {
              tDelta = maxTdelta;
            }
          }
        } else {
          doneBisecting = 1;
        }
      }
    }
    dist += movDelta;
    xOld = x;
    yOld = y;
    zOld = z;
    x = x + Xdelta;
    y = y + Ydelta;
    z = z + Zdelta;
    if (movDelta > maxMovDelta) {
      moveCh = '*';
      numOver++;
    } else {
      moveCh = ' ';
    }
#if DEBUG
    fprintf (stderr, "%d Balls: %7d tDel: %9.6f x: %9.2f y: %9.2f z: %9.2f Dist: %7.1f fDel: %9.3f #Over: %7d %1c-%1c\n",
             numBisect, numBalls, tDelta, x, y, z, dist, movDelta, numOver, moveCh, (numBisect > maxNumBisect ? '*' : ' '));
#endif
    printf ("sphere {<%f,%f,%f>, %f texture { crvTex } }\n", x, y, z, sphereRad);
#if CYL
    printf("cylinder {<%f,%f,%f>, <%f,%f,%f>, %f texture { crvTex } }\n", x, y, z, xOld, yOld, zOld, sphereRad);
#endif

  }
  fprintf (stderr, "Balls: %7d Dist: %7.1f #Over: %7d\n", numBalls, dist, numOver);

  return 0;
}

3.3.4. POV-Ray scene files

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// @file      lorenz_s1.pov
// @author    Mitch Richling http://www.mitchr.me
// @Copyright Copyright 1994,1997,2014 by Mitch Richling.  All rights reserved.
// @Revision  $Revision: 1.4 $ 
// @SCMdate   $Date: 2014/10/11 03:38:26 $
// @brief     Basic setup for rendering the Lorenz strange attractor.@EOL
// @Keywords  lorenz strange attractor povray movie 
// @Std       Povray_3.7
//
//            
//            

//----------------------------------------------------------------------------------------------------------------------------------

#version 3.7;

#include "colors.inc"
#include "textures.inc"
#include "woods.inc"
#include "metals.inc"
#include "skies.inc"

global_settings { assumed_gamma 1 }

camera {
    location <cos(2*pi*clock)*-25-5, -5, sin(2*pi*clock)*45+30>
    sky      <0,1,0>
    look_at  <0,0,25>
}

light_source { < 40,-4,  40>                                     color White*0.3 }
light_source { <-40,-50,  40>                                    color White*0.6 }  // back shadow
light_source { < 40,-4, -40>                                     color White*0.3 }
light_source { <-40,-4, -40>                                     color White*0.8 } // Shadow on left

background { color Black }

#declare crvTex=texture { pigment { color Red } }
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// @file      lorenz_s2.pov
// @author    Mitch Richling http://www.mitchr.me
// @Copyright Copyright 1994,1997,2014 by Mitch Richling.  All rights reserved.
// @Revision  $Revision: 1.4 $ 
// @SCMdate   $Date: 2014/10/11 03:38:38 $
// @brief     Basic setup for rendering the Lorenz strange attractor.@EOL
// @Keywords  lorenz strange attractor povray movie 
// @Std       Povray_3.7
//
//            
//            

//----------------------------------------------------------------------------------------------------------------------------------

#version 3.7;

#include "colors.inc"
#include "textures.inc"
#include "woods.inc"
#include "metals.inc"
#include "skies.inc"

global_settings { assumed_gamma 1 }

camera {
    location <cos(2*pi*clock)*-30, -5, sin(2*pi*clock)*45+30>
    sky      <0,1,0>
    look_at  <0,0,25>
}

light_source { < 40,-4,  40>                                     color White*0.3 }
light_source { <-40,-50,  40>                                    color White*0.6 }  // back shadow
light_source { < 40,-4, -40>                                     color White*0.3 }
light_source { <-40,-4, -40>                                     color White*0.8 } // Shadow on left

background { color Black }

#declare crvTex=texture {
   pigment { color Red }
   finish  {
      ambient .15
      diffuse 0.01
      reflection 0.4
      specular 0.9
      roughness 0.04
      phong .1 
      phong_size 200
    }
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// @file      lorenz_s3.pov
// @author    Mitch Richling http://www.mitchr.me
// @Copyright Copyright 1994,1997,2014 by Mitch Richling.  All rights reserved.
// @Revision  $Revision: 1.4 $ 
// @SCMdate   $Date: 2014/10/11 03:38:43 $
// @brief     Basic setup for rendering the Lorenz strange attractor.@EOL
// @Keywords  lorenz strange attractor povray movie 
// @Std       Povray_3.7
//
//            
//            

//----------------------------------------------------------------------------------------------------------------------------------

#version 3.7;

#include "colors.inc"
#include "textures.inc"
#include "glass.inc"
#include "metals.inc"
#include "skies.inc"

global_settings { assumed_gamma 1 }

camera {
    location <cos(2*pi*clock)*-30, -5, sin(2*pi*clock)*45+30>
    sky      <0,1,0>
    look_at  <0,0,25>
}

light_source { < 40,-4,  40>                                     color White*0.3 }
light_source { <-40,-50,  40>                                    color White*0.6 }  // back shadow
light_source { < 40,-4, -40>                                     color White*0.3 }
light_source { <-40,-4, -40>                                     color White*0.8 } // Shadow on left

background { color Black }

#declare crvTex=texture { Candy_Cane }
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// @file      lorenz_s4.pov
// @author    Mitch Richling http://www.mitchr.me
// @Copyright Copyright 1994,1997,2014 by Mitch Richling.  All rights reserved.
// @Revision  $Revision: 1.4 $ 
// @SCMdate   $Date: 2014/10/11 03:38:50 $
// @brief     Basic setup for rendering the Lorenz strange attractor.@EOL
// @Keywords  lorenz strange attractor povray movie 
// @Std       Povray_3.7
//
//            
//            

//----------------------------------------------------------------------------------------------------------------------------------

#version 3.7;

#include "colors.inc"
#include "textures.inc"
#include "glass.inc"
#include "metals.inc"
#include "skies.inc"

global_settings { assumed_gamma 1 }

camera {
    location <cos(2*pi*clock)*-30, -5, sin(2*pi*clock)*45+30>
    sky      <0,1,0>
    look_at  <0,0,25>
}

light_source { < 40,-4,  40>                                     color White*0.3 }
light_source { <-40,-50,  40>                                    color White*0.6 }  // back shadow
light_source { < 40,-4, -40>                                     color White*0.3 }
light_source { <-40,-4, -40>                                     color White*0.8 } // Shadow on left

background { color Black }

#declare crvTex=texture { Candy_Cane }
#declare crvTex=texture { T_Copper_4E }
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// @file      lorenz_s4.pov
// @author    Mitch Richling http://www.mitchr.me
// @Copyright Copyright 1994,1997,2014 by Mitch Richling.  All rights reserved.
// @Revision  $Revision: 1.4 $ 
// @SCMdate   $Date: 2014/10/11 03:38:50 $
// @brief     Basic setup for rendering the Lorenz strange attractor.@EOL
// @Keywords  lorenz strange attractor povray movie 
// @Std       Povray_3.7
//
//            
//            

//----------------------------------------------------------------------------------------------------------------------------------

#version 3.7;

global_settings { assumed_gamma 1 }

#include "colors.inc"

#declare lookAt=<0,0,0>;

camera {
    location <0,0,-30>
    sky      <0,1,0>
    look_at  lookAt
}

cylinder { <-1000,0,0>, lookAt, 0.20 pigment { color Pink } }  
cylinder { <0,-1000,0>, lookAt, 0.20 pigment { color Pink } }  
cylinder { lookAt, <1000,0,0>, 0.20 pigment  { color Blue } }   
cylinder { lookAt, <0,1000,0>, 0.20 pigment  { color Yellow } } 

light_source { <0,0,-50> color White }

background { color Black }

#declare crvTex=texture { pigment { color Red } }
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// @file      lorenz_s4.pov
// @author    Mitch Richling http://www.mitchr.me
// @Copyright Copyright 1994,1997,2014 by Mitch Richling.  All rights reserved.
// @Revision  $Revision: 1.4 $ 
// @SCMdate   $Date: 2014/10/11 03:38:50 $
// @brief     Basic setup for rendering the Lorenz strange attractor.@EOL
// @Keywords  lorenz strange attractor povray movie 
// @Std       Povray_3.7
//
//            
//            

//----------------------------------------------------------------------------------------------------------------------------------

#version 3.7;

#include "colors.inc"
#include "textures.inc"
#include "glass.inc"
#include "metals.inc"
#include "skies.inc"

global_settings { assumed_gamma 1 }

camera {
    location <cos(2*pi*clock)*-30, -5, sin(2*pi*clock)*45+30>
    sky      <0,1,0>
    look_at  <0,0,25>
}

light_source { < 40,-4,  40>                                     color White*0.3 }
light_source { <-40,-50,  40>                                    color White*0.6 }  // back shadow
light_source { < 40,-4, -40>                                     color White*0.3 }
light_source { <-40,-4, -40>                                     color White*0.8 } // Shadow on left

background { color Black }

#declare crvTex=texture { Jade }

3.3.5. Resizing Images

#!/bin/bash
# -*- Mode:Shell-script; Coding:us-ascii-unix; fill-column:132 -*-
####################################################################################################################################
##
# @file      conv.sh
# @author    Mitch Richling <https://www.mitchr.me>
# @date      2021-05-21
# @brief     Convert png & gif files into various sizes for web use.@EOL
# @std       bash
#
####################################################################################################################################

for f in *.png;      do echo $f; convert -quality 100 -resize 240x135   $f `echo $f | sed 's/\.png$/_240x135.jpg/'`;   done
for f in *.png;      do echo $f; convert -quality 100 -resize 480x270   $f `echo $f | sed 's/\.png$/_480x270.jpg/'`;   done
for f in *.png;      do echo $f; convert -quality 100 -resize 960x540   $f `echo $f | sed 's/\.png$/_960x540.jpg/'`;   done
for f in *.png;      do echo $f; convert -quality 100 -resize 1920x1080 $f `echo $f | sed 's/\.png$/_1920x1080.jpg/'`; done
for f in *.png;      do echo $f; convert -quality 100                   $f `echo $f | sed 's/\.png$/_3840x2160.jpg/'`; done
for f in *movie.gif; do echo $f; cp                                     $f `echo $f | sed 's/\.gif$/_960x540.gif/'`;   done
for f in *movie.gif; do echo $f; convert              -resize 480x270   $f `echo $f | sed 's/\.gif$/_480x270.gif/'`;   done
for f in *movie.gif; do echo $f; convert              -resize 120x67    $f `echo $f | sed 's/\.gif$/_120x67.gif/'`;    done

You can find all the images here.

4. Simulation & Visualization: Analog Computer

lorenz_still_longExp.png

Given any system of differential equations it is possible to find an electronic circuit governed by the same equations. These circuits are called "analogs" of the system of equations, and that's why we use the word "analog" for most non-digital electronics today. Finding and implementing these analogs in a systematic way is the fundamental idea behind Analog computers.

4.3. Physical Implementation

lorenz_bboard.jpg

4.4. Behavior of Physical Circuit

Note the output of the circuit is passed through another analog processing unit that translates, scales, and rotates the results – that's why the image is spinning. Someday I'll do a little write-up of that circuit…

Scope
Tektronix AN/USM488 (military version of the 2235). Equipped with the standard clear plexiglass CRT filter
Capture Device
Olympus OM-D E-M1 MARK II with Olympus M.Zuiko ED 60mm f/2.8 Macro Lens
Post
ffmpeg (Encode to VP9, crop, & gamma correction)
Scope
Tektronix TDS3052B Digital Phosphor Oscilloscope
Capture Device
GRACETOP USB VGA Capture Card connected to a Raspberry PI 4 and converted with VLC
Post
ffmpeg (Encode to VP9)
Scope
Siglent SDS2504X Plus Super Phosphor Oscilloscope
Capture Device
Scope's VNC server output connected to a Raspberry PI 4 and converted with VLC
Post
ffmpeg (Encode to VP9)

5. References

Check out the chaos in electronics section of my reading list, as well as the "The Art of Electronics" by Paul Horowitz. For more about chaos in general, take a look at the fractals section of my reading list.