UP | HOME

The Lorenz Attractor
A MRKISS Library Example Program

Author: Mitch Richling
Updated: 2025-09-10 16:55:40
Generated: 2025-09-10 16:55:42

Copyright © 2025 Mitch Richling. All rights reserved.

Table of Contents

1. Introduction

The code for this example is found in examples/lorenz.f90. Additionally the code may be found at the end of this document in the section Full Code Listing.

One strange thing when visualizing strange attracters is that we are frequently less concerned with the absolute accuracy of the solution curves than we are with the spacing of the solution points. This example uses the Lorenz attractor to explore some of the tools MRKISS provides for these kinds of problems.

2. Curve Evolution Animation

For animations of the evolution of the curve we normally use a fixed \(\Delta{t}\) because this preserves time in the animation. That is to say we can see the point tracing the curve speed up and slow down as the derivative changes. We can produce such a solution with the fixed_t_steps solvers. Here is an example of such a curve:

lorenz_fixed.png

Notice the way some of the steps are much longer than others. This is especially apparent in the upper left:

lorenz_fixed-c.png

Many path animation tools have the ability to interpolate intermediate points on piecewise curves. Some even have the ability to use derivative information and do Hermite interpolation. Having a few longer steps when using such a tool is no issue. OTOH, when simply animating successive solution steps, large steps can make the animation jerky. When this happens, if you still want to preserve time in the animation, the only real solution is to decrease the value used for \(\Delta{t}\).

The solutions used in the images above were created by this snippet of examples/lorenz.f90:

call fixed_t_steps(status, istats, solution, eq, y_iv, param, a, b, c, t_delta_o=t_delta, t_max_o=t_max)
print '(a)', status_to_message(status)
call print_istats(status, istats)
call print_solution(status, solution, filename_o="lorenz_fixed.csv", end_o=istats(1), t_min_o=50.0_rk)

3. Sphere Sweeps

Ray tracing tools can render scenes made up of spheres orders of magnitude faster than they can deal with things like triangles. Because of this fact it is common practice to represent curves in space as thousands, or millions, of spheres tightly packed along the curve – this is called a "sphere sweep". In order for this to work out the spheres need to be close together so that the angles of intersection are very small. Still we don't want too many spheres as every sphere adds time to the overall render. What we really want is to compute the optimal distance between the sphere centers, and then use this distance for all the spheres. That means we want steps of constant \(\mathbf{\Delta{y}}\).

Solutions with constant \(\mathbf{\Delta{y}}\) steps are also used for constant velocity animations of curve evolution. These can be interesting when done with groups of stream lines.

MRKISS provides two solvers for this situation:

fixed_y_steps
This solver uses considerable compute resources to insure that every step is the same length to a user provided tolerance. It requires the user provide minimum and maximum bounds for \(\Delta{t}\) which will bracket the correct value. The resulting curves are perfect for high quality sphere sweep renders.
sloppy_fixed_y_steps
This solver only approximates optimal \(\Delta{t}\). In exchange for this inaccuracy, this routine is much faster than the previous option. The resulting solutions frequently good enough for a sphere sweep.

Here are some representative samples:

lorenz_condy.png

lorenz_sloppy_condy_short.png

These images were created by the following snippet of examples/lorenz.f90:

call sloppy_fixed_y_steps(status, istats, solution, eq, y_iv, param, a, b, c, 1.0_rk, t_delta, t_max_o=t_max)
print '(a)', status_to_message(status)
call print_istats(status, istats)
call print_solution(status, solution, filename_o="lorenz_sloppy_condy.csv", end_o=istats(1), t_min_o=50.0_rk)

! This solution will have y-delta approximately equal to 1.0 for all steps.
print '(a)', repeat('*', 120)
print '(a)', "sloppy_fixed_y_steps short"
call sloppy_fixed_y_steps(status, istats, solution, eq, y_iv, param, a, b, c, 1.0_rk, t_delta, t_max_o=t_max, &
                                adj_short_o=1)
print '(a)', status_to_message(status)
call print_istats(status, istats)
call print_solution(status, solution, filename_o="lorenz_sloppy_condy_short.csv", end_o=istats(1), t_min_o=50.0_rk)

4. Limiting Step Length

When drawing line plots of curves we normally want them to be appear smooth which means we must avoid large values for \(\mathbf{\Delta{y}}\). For curve line drawing we normally are not terribly concerned with having a few short intervals because they don't have a huge impact on rendering speed.

When rendering curves as tubes most tools render a sphere at each point and a cylinder connecting the points. As with line drawings we want the curve to appear smooth, and so we wish to avoid large values for \(\mathbf{\Delta{y}}\). When rendering tubes we are more sensitive to excessive numbers of short intervals because every point impacts rendering speed. In addition, with some tools, very short intervals can introduce rendering glitches.

Of course we can use fixed_y_steps and sloppy_fixed_y_steps(..., adj_short_o=1) as we did in the previous section; however, sloppy_fixed_y_steps without the adj_short_o=1 argument that may be more useful. Without this option only steps longer than the target are adjusted.

Here is the result:

lorenz_sloppy_condy.png

The solution used in the image above was created by this snippet of examples/lorenz.f90:

call fixed_y_steps(status, istats, solution, eq, y_iv, param, a, b, c, 1.0_rk, t_delta*7, t_max_o=t_max)
print '(a)', status_to_message(status)
call print_istats(status, istats)
call print_solution(status, solution, filename_o="lorenz_condy.csv", end_o=istats(1), t_min_o=50.0_rk)

5. Full Code Listing

5.1. Fortran Code

program lorenz
  use :: mrkiss_config,      only: rk, istats_size
  use :: mrkiss_solvers_nt,  only: fixed_t_steps, sloppy_fixed_y_steps, fixed_y_steps
  use :: mrkiss_utils,       only: print_solution, print_istats, status_to_message
  use :: mrkiss_erk_kutta_4, only: a, b, c

  implicit none

  integer,        parameter :: deq_dim       = 3
  integer,        parameter :: num_points    = 100000
  real(kind=rk),  parameter :: y_iv(deq_dim) = [1.0_rk, 0.0_rk, 0.0_rk]
  real(kind=rk),  parameter :: param(3)      = [10.0_rk, 28.0_rk, 8.0_rk/3.0_rk]
  real(kind=rk),  parameter :: t_delta       = 0.01_rk
  real(kind=rk),  parameter :: t_max         = 100.0_rk

  real(kind=rk)             :: solution(1+2*deq_dim, num_points)
  integer                   :: status, istats(istats_size)

  ! This solution will have fixed t-delta, but no control over y-delta.
  print '(a)', repeat('*', 120)
  print '(a)', "Fixed t_delta run"
  call fixed_t_steps(status, istats, solution, eq, y_iv, param, a, b, c, t_delta_o=t_delta, t_max_o=t_max)
  print '(a)', status_to_message(status)
  call print_istats(status, istats)
  call print_solution(status, solution, filename_o="lorenz_fixed.csv", end_o=istats(1), t_min_o=50.0_rk)

  ! This solution will have y-delta approximately capped to a maximum of 1.0 for all steps.
  print '(a)', repeat('*', 120)
  print '(a)', "sloppy_fixed_y_steps"
  call sloppy_fixed_y_steps(status, istats, solution, eq, y_iv, param, a, b, c, 1.0_rk, t_delta, t_max_o=t_max)
  print '(a)', status_to_message(status)
  call print_istats(status, istats)
  call print_solution(status, solution, filename_o="lorenz_sloppy_condy.csv", end_o=istats(1), t_min_o=50.0_rk)

  ! This solution will have y-delta approximately equal to 1.0 for all steps.
  print '(a)', repeat('*', 120)
  print '(a)', "sloppy_fixed_y_steps short"
  call sloppy_fixed_y_steps(status, istats, solution, eq, y_iv, param, a, b, c, 1.0_rk, t_delta, t_max_o=t_max, &
                                  adj_short_o=1)
  print '(a)', status_to_message(status)
  call print_istats(status, istats)
  call print_solution(status, solution, filename_o="lorenz_sloppy_condy_short.csv", end_o=istats(1), t_min_o=50.0_rk)

  ! This solution will have y-delta equal to 1.0 for all steps.
  print '(a)', repeat('*', 120)
  print '(a)', "fixed_y_steps"
  call fixed_y_steps(status, istats, solution, eq, y_iv, param, a, b, c, 1.0_rk, t_delta*7, t_max_o=t_max)
  print '(a)', status_to_message(status)
  call print_istats(status, istats)
  call print_solution(status, solution, filename_o="lorenz_condy.csv", end_o=istats(1), t_min_o=50.0_rk)

contains

  subroutine eq(status, dydt, y, param)
    integer,          intent(out) :: status
    real(kind=rk),    intent(out) :: dydt(:)
    real(kind=rk),    intent(in)  :: y(:)
    real(kind=rk),    intent(in)  :: param(:)
    dydt(1) = param(1)*(y(2)-y(1))       ! a(y-x)
    dydt(2) = y(1)*(param(2)-y(3))-y(2)  ! x(b-z)-y
    dydt(3) = y(1)*y(2)-param(3)*y(3)    ! xy-cy
    status = 0
  end subroutine eq

end program

5.2. GNUplot Code

The images were produced with GNUplot.

set encoding utf8
set termoption noenhanced
set datafile separator ','
# set xlabel "x"
# set ylabel "y"
# set zlabel "z"
#set grid
set margins 0, 0, 0, 0
set view 70, 40, 1.3, 1.4
set xyplane at 0
unset border
unset ytics
unset ztics
unset xtics
set terminal svg
set pointsize 0.2

set title "Lorenz (fixed)"
set output "lorenz_fixed.svg"
splot 'lorenz_fixed.csv' using 3:4:5 with linespoints pt 7 title ""

set title "Lorenz (sloppy condy)"
set terminal svg
set output "lorenz_sloppy_condy.svg"
splot 'lorenz_sloppy_condy.csv' using 3:4:5 with linespoints pt 7 title ""

set title "Lorenz (sloppy condy short)"
set terminal svg
set output "lorenz_sloppy_condy_short.svg"
splot 'lorenz_sloppy_condy_short.csv' using 3:4:5 with linespoints pt 7 title ""

set title "Lorenz (condy)"
set terminal svg
set output "lorenz_condy.svg"
splot 'lorenz_condy.csv' using 3:4:5 with linespoints pt 7 title ""