Mathematical Artwork in Python Collin J. Delker Mathematical Artwork in Python

© 2020 Collin J. Delker, All Rights Reserved. Except as permitted under the United States Copyright Act of 1976, no part of this publication may be reproduced or distributed in any form or by any means, or stored in a database or retrieval system, without the prior written permission of the publisher, with the exception that the program listings may be entered, stored, and executed in a computer system, but they may not be reproduced for publication.

Editing and Code Testing: David G. Delker

https://codeismycanvas.art Albuquerque, New Mexico, USA

Cover Image: Polynomial with traps of a thin strip along the real axis and a small circle. CONTENTS:

1 Introduction 1 1.1 History ...... 2 1.2 Use of Python ...... 3 1.3 Saving your Images ...... 5 1.4 Organization ...... 6

2 Math and Python Background 7 2.1 Numerical Computations ...... 7 2.2 Trigonometric functions ...... 9 2.3 Polar and Rectangular Coordinates ...... 10 2.4 Coordinate Formulas ...... 10 2.5 Complex Numbers ...... 11 2.6 Histograms ...... 12

3 Computer Graphics 15 3.1 Raster Graphics ...... 15 3.2 Vector Graphics ...... 17 3.3 Representing Color ...... 19

4 Parametric Methods 23 4.1 ...... 24 4.2 Harmonographs ...... 27 4.3 Spirograph ...... 31 4.4 Non-physical Curves ...... 33 4.5 Fancier coloring ...... 35 4.6 Using Many Shapes ...... 37 4.7 Parametric Drawings ...... 40 4.8 Examples Gallery ...... 47 4.9 Experiments ...... 51

5 53 5.1 Random number generation ...... 54 5.2 Making Randomized Curves ...... 56 5.3 with Randomness ...... 62 5.4 Textured Lines ...... 67 5.5 Textured Shapes and Backgrounds ...... 71 5.6 Gallery ...... 75 5.7 Experiments ...... 79

6 Orbits and Escape 81 6.1 Iterative systems ...... 81

i 6.2 Escape Time ...... 86 6.3 Variations ...... 92 6.4 Refining ...... 95 6.5 Exploring ...... 100 6.6 Gallery ...... 100 6.7 Experiments ...... 102

7 Orbit Traps 103 7.1 Trap shapes ...... 109 7.2 Trap coloring methods ...... 113 7.3 Multiple Traps ...... 118 7.4 Recurrence functions ...... 119 7.5 Tips ...... 120 7.6 Examples ...... 121 7.7 Experiments ...... 123

8 Orbit Density Fractals 125 8.1 ...... 125 8.2 Coloring techniques ...... 131 8.3 Orbits that Escape ...... 137 8.4 Examples Gallery ...... 142 8.5 Experiments ...... 143

9 The 145 9.1 Optimizations ...... 150 9.2 Iteration Depth ...... 151 9.3 Noise ...... 153 9.4 Coloring ...... 155 9.5 Zooming ...... 157 9.6 Minimum Iterations ...... 157 9.7 Gallery ...... 163 9.8 Experiments ...... 166

10 Color Theory 167 10.1 Hue, Saturation, Lightness ...... 167 10.2 Color Wheels ...... 168 10.3 Palettes and Schemes ...... 171 10.4 Gradients ...... 174 10.5 Experiments ...... 177

11 Composition 179 11.1 Simplicity ...... 179 11.2 Rule of Thirds ...... 180 11.3 Leading Lines and Curves ...... 181 11.4 Balance ...... 182 11.5 Framing ...... 183 11.6 Avoid Mergers ...... 184

12 Polishing the Image 185 12.1 Antialiasing ...... 185 12.2 Color Limitations ...... 187 12.3 File Formats ...... 188 12.4 Preparing for print ...... 189 12.5 Workflow ...... 193

ii 13 APPENDIX: Code 195 13.1 Python Setup ...... 195 13.2 Color Conversions ...... 196 13.3 Interactive Jupyter Fractals ...... 199

14 APPENDIX: About this Book 201 14.1 Writing a book in Jupyter ...... 201 14.2 About the Author ...... 203

Bibliography 205

iii iv CHAPTER ONE

INTRODUCTION

When I was in college, I decided I should learn some Python programming. Most engineering students at the time used MATLAB for programming and data analysis work, but because of licensing issues, MATLAB was only available in the university’s compter labs, and I was too cheap to buy the student version. One cold weekend, I really wasn’t keen on trekking off through a foot of snow to the computer lab just to finish some data analysis I was working on, but I kept hearing good things about the Python language. Rather than venturing out in the cold, I stayed in my cozy bedroom office and decided to see what Python could do. Since most of my research work involved visualizing measurement data with different kinds of plots, I decided the best way to learn how to use Python for data and plotting was to figure out how to use it to make fractals. After installing Python and a few libraries on my laptop, it just took an hour or two to learn enough code to make my first Mandelbrot , one of the original fractal images first discovered, show up on the screen. Amazed at how easyitwas to do things in Python, I quickly picked it up and used it for all my research data processing needs. Like almost every student, I found plenty of excuses to procrastinate on school work and do other things that weren’t about getting closer to graduating. Before long I was experimenting with other fractal variations and eventually set up my first website, on the university server, with some of the images I created. While a lot of computer programmers experiment with making fractals and like to use fractal computations to run benchmark tests of their code and hardware, the artistic side of fractal design is often neglected. Search the internet for fractals and, while there are many beautiful ones out there, you’ll find even more that look like plots from a journal paper - maybe mathe- matically correct but having no regard for composition, color, or aesthetics. This book aims to bridge the gaps between the math, the programming, and the design of algorithmically- based . The artist needs aspects of all three to make the best images. We won’t go deeply into any one of these three aspects, but hopefully I will provide enough background on each of them to make some interesting images that can be considered pieces of art. The other goal of this book is to show how this style of art can be made from its roots as mathematical equations. There are many canned software programs, both stand-alone ap- plications and programming libraries, that make it easy to create fractals, but they become magic black-box machines where the user punches in a few random numbers and out comes an image. When I have used these, I was left unsatisfied with having no understanding of what really was going on behind the scenes. What’s the fun in making art based on math when we don’t actually understand the math? This book avoids any pre-compiled libraries and gets down to the basics of the equations themselves. By implementing the code from the ground up, we will know exactly what is going on and how to adjust it to do what we want. Prerequisites for this book include a basic understanding of math, including algebra and a lit- tle trigonometry. Some of the art is based on simple physics, such as the relationship between

1 Mathematical Artwork in Python distance, , and acceleration, but both the math and physics should be understandable at a high-school level. Familiarity with the Python programming language is also assumed, although enough background and examples are given that this book could also be a good com- panion for advancing the reader’s Python skills, just as the first fractals I created were away to learn the Python programming language. Hopefully these prerequisites are not barriers for anyone to enjoy this book and find it useful. For some good references on general Python programming, see [1], [2], [3].

1.1 History

Mathematics has been a part of art almost since art began. Look at architecture and sculpture in ancient Rome and Greece, for example, where artists were believed to use the √ 1 + 5 2 to define proportions in buildings, , and sculpture. Leonardo DaVinci was knownfor being meticulous in his paintings, keeping them true to the science and geometry of anatomy and . Some have argued that the first art actually defined by an algorithm werethe geometric patterns that decorate furniture, pottery, and architecture from various cultures over a thousand years ago. In the mid-1800s, mechanical devices attached an ink pen to a or and let the machine autonomously pull the pen to make interesting shapes over a piece of paper. This was perhaps the first example of the artist setting upa machine and putting it in motion while the physics did the rest of the work very similar to how we program computers today. The first computer-generated artwork dates to the 1960s and used a similar technique ofa pen drawing on paper — or a computer plotter — but rather than being controlled by grav- ity, , and friction, the pen was controlled by the computer and servo motors. Most computer-generated art as we know it started in the mid 1980s as monitor and printer tech- nology became more common and accessible. The first fractal images date to the 1970s. The exact definition of fractal isunclearand up for debate, but they typically are defined by algorithms with infinite and self- , meaning if the fractal is magnified, smaller versions of the fractal shape will appear. Yet the fractal can be zoomed to infinity without losing any detail. The famous fractal, which many would consider “the” definition of a fractal, was named for mathematician in 1978. The first published Mandelbrot set image was drawn in plain text output to a printer, and looked something like this [4].

2 Chapter 1. Introduction Mathematical Artwork in Python

** **** ********* **************** ****************** ********************* * * ********************** **************************** ******************************* ********************************** ******************************* **************************** * * ********************** ********************* ****************** **************** ********* **** **

Of course, computing power has progressed significantly, and now fractals with all kinds of fancy coloring schemes and alternative equations can be made.

1.2 Use of Python

All the concepts in this book are developed using Python code, specifically Python 3.8. In most cases, the code relies only on a few common third-party libraries intended for general-purpose scientific computing. The Numpy library is used to efficiently process large arrays ofnumbers. Matplotlib is used for plotting mathematically-based plots and graphs, and the Pillow library (formerly Python Imaging Library, or PIL) converts arrays of numbers into images and back. These tools, and the code in this book, should be generic enough that they can be easily translated into other languages. Only a very few cases will use other third-party libraries or functions without fully explaining, or providing pure-Python alternatives, to what they are doing. While there are other Python libraries for creating graphics, the aim of this book is to fully understand the algorithms behind the art. Too much reliance on tools that do everything for you removes, or at least hides, the ability to have complete understanding and control over the art, so these tools are avoided. This book is not intended to give a complete overview of the Python language or the Numpy and Pillow libraries, but it should provide enough background and sample code to get started. Most of the code samples in this book were developed in a Jupyter Notebook environment [5], which allows execution of a few lines of code at a time in a box called a cell to view the output. In fact, the book itself was written using the Sphinx [6] and Nbsphinx [7] tools. Sphinx is a Python program usually used for writing code documentation, and Nbsphinx converts Jupyter notebooks into the document itself. This sentence was written in a Jupyter raw cell. The code you see in this book, and the resulting images it creates, were actually executed when the book was compiled. As an example, a Jupyter code cell and its output looks like this: import numpy as np import matplotlib.pyplot as plt x,y = np.mgrid[:100,:100] (continues on next page)

1.2. Use of Python 3 Mathematical Artwork in Python

(continued from previous page) z = np.cos(x*0.2) + np.sin(y*0.3) plt.imshow(z, cmap='ocean');

And just like in Jupyter, the next cell keeps the same variables in memory, so we can reuse variables without redefining them like this, where z was defined previously: plt.imshow(z, cmap='Oranges');

In Jupyter, the value, or “representation” of the last line of a cell is what will be displayed. So a cell like this displays the variable a: a = 5 + 3 a 8 while simply assigning a does not display anything. a = 5 + 3

Occasionally you’ll see semicolons at the end of a line, such as some in the imshow lines in the above examples. Most people will think semicolons are for C code, but in Python they

4 Chapter 1. Introduction Mathematical Artwork in Python are actually valid to designate the end of a line, and in Jupyter, they suppress display of the output. Without the semicolon in cells that generate an image, you’ll get both the image and a text line describing what was returned from the last line of the cell, like this: plt.imshow(z, cmap='Blues')

Nobody cares about the memory address of the image, so the semicolon is there to remove that extra line. You are encouraged to copy the code segments into your own notebook to run and experiment with, but be aware that copying code directly from a PDF or EPUB may not always work correctly. Just check things like tab indentation and line breaks to make sure everything was correctly formatted. The main driver of Sphinx development has been writing code documentation for websites in html format, so working with it for a more traditional book took a bit of extra work and configuration. Section 14 has a more in-depth description of the book-writing process using Sphinx and Nbconvert.

1.3 Saving your Images

Of course, you will eventually want to save your images so you can print them, set them as your desktop background, or share them on social media. To save a plot that was drawn using Matplotlib, use the plt.savefig function after it is drawn. The file extension determines the image format to save, for example, here a Portable Network Graphics (PNG) file is created. The savefig function takes several optional parameters, including a dpi, or dots-per-inch value, which affects the image quality for PNG and JPG formats. plt.imshow(z, cmap='Blues') plt.savefig('mypicture.png')

1.3. Saving your Images 5 Mathematical Artwork in Python

For images created using the Pillow library, the Image object has a save method. Again, the image format is determined based on the file extension passed in. Here the same z array is converted to a grayscale Pillow image object and then saved to a PNG file. from PIL import Image img = Image.fromarray((z*255).astype(np.uint8), 'L') img.save('mypicture2.png')

See Section 12.3 for some suggestions on different image file formats.

1.4 Organization

This book is organized into several sections. First, a quick overview of some basic math concepts and how they are implemented in Python using the Numpy library are presented, followed by a description of how computers represent graphics in raster and vector forms. Then two chapters are presented on vector graphics, including images made by parametric equations and with random variations added in. Four chapters are devoted to fractal images with details on how they are calculated, different ways of determining coloring schemes, and other variations in the formula and display of the results. Another chapter is devoted to color theory, with mathematically-based techniques for selecting pleasing complementary color palettes. A chapter on composition rules, traditionally applied to photography, but in the context of mathematical art is presented. Finally, I’ve included a chapter detailing a few tips for cleaning up an image and preparing it for printing or display.

6 Chapter 1. Introduction CHAPTER TWO

MATH AND PYTHON BACKGROUND

This chapter aims to provide a short background in the math used in later chapters and how it is implemented in Python and the Numpy library. If you are already a Python expert and math whiz, it is fair to skip over this chapter.

2.1 Numerical Computations

In Python, any intensive mathematical computation is done with the Numpy (NUMerical PYthon) library [8]. This library implements key algorithms in C code for much faster per- formance than pure Python lists, and provides very efficient operation on entire arrays of numbers without requiring Python for loops. Numpy is typically imported like this, giving it the convenient abbreviation np. import numpy as np

The fundamental Numpy data type is an n-dimensional array. Usually, we’ll use 1- or 2- di- mensional arrays, although 3-dimensional arrays are used when dealing with color images having three color channels. We can make an array of all zeros or all ones, of any size, using these functions: np.zeros(5) array([0., 0., 0., 0., 0.]) np.ones(8) array([1., 1., 1., 1., 1., 1., 1., 1.])

Or, an array can be made with specific values defined. np.array([3, 5, 2, 10]) array([ 3, 5, 2, 10])

All the values in an array can be operated on element-wise using a single operator. The following line multiplies every element in the array by 3. np.array([1, 2, 3]) * 3 array([3, 6, 9])

7 Mathematical Artwork in Python

It’s often useful to make an array of equally-spaced values, for example when calculating or plotting a function over a range. Numpy’s linspace function does this nicely, while allowing you to specify start and endpoints, along with the number of points in between. Here, ten points between -1 and 1, including the endpoints, are put in an array.

x = np.linspace(-1, 1, num=10) x array([-1. , -0.77777778, -0.55555556, -0.33333333, -0.11111111, 0.11111111, 0.33333333, 0.55555556, 0.77777778, 1. ])

Taking these as the x values of a function, now the y values for all x values can be calculated all at once. Recall that ** is a power operator in Python, so here the square of x is calculated and assigned to y.

y = x**2 y array([1. , 0.60493827, 0.30864198, 0.11111111, 0.01234568, 0.01234568, 0.11111111, 0.30864198, 0.60493827, 1. ])

Now, to visualize our x and y points in a 2-dimensional plane, another Python library is used. Matplotlib [9] is the most popular library for drawing mathematical plots and is imported using its pyplot submodule as shown below. Simply call the plot function with x and y arrays as arguments to make the plot and display it in Jupyter. Calling plt.show() would be necessary if running the code from a .py file rather than a Jupyter notebook. Using the x and y variables we just defined above,

import matplotlib.pyplot as plt

plt.plot(x, y);

Note that we are plotting a discrete representation of the function y = x2. Each x-y pair is connected with a short line segment approximating the function, which is most apparent near the bottom of the parabola in this example Increasing the number of segments smooths out the curve.

x = np.linspace(-1, 1, num=500) y = x**2 plt.plot(x, y);

8 Chapter 2. Math and Python Background Mathematical Artwork in Python

2.2 Trigonometric functions

Numpy has functions for most mathematical functions found on a scientific calculator. Par- ticularly useful for our later art creation are trigonometric functions such as sine and cosine. These are useful functions because they are periodic, meaning they repeat themselves after a certain period. Given the sine function y = A sin(2πkx), the amplitude, or maximum value, of the result is A, and the period is k. x = np.linspace(0, np.*2, num=200) y = 2 * np.sin(3*x) plt.plot(x, y);

Another term can be added inside the sine function, called the phase, which shifts the entire curve left or right. This will be useful when making parametric function plots in Section 4.

2.2. Trigonometric functions 9 Mathematical Artwork in Python

2.3 Polar and Rectangular Coordinates

Trigonometric functions are also useful due to their relation to right triangles, which comes up surprisingly often in computer graphics. Usually, we work in x-y values where x is the distance in the horizontal direction and y the distance in the vertical direction. This is called a rectangular, or Cartesian, coordinate system. Sometimes it is useful to work in a polar coordinate system, where instead of x-y values, points are defined using the distance from the center and a rotation angle - usually referred to as the radius and theta angle. Consider the triangle pictured below. It has x-y coordinates at (0, 0), (2, 0), and (3, 2). However, knowing these points, we can find the distance r and angle θ, which define the (3, 2) point equivalently.

√ The r value is given by r = x2 + y2, which of course you should recognize as the famous Pythagorean Theorem. The angle, theta, is θ = arctan(y/x). The sine and cosine functions come in to play when converting the other direction, taking r and θ values and finding their x and y components. x = r cos(θ) y = r sin(θ) Also note that Numpy’s trigonometric functions work on angles in radians. Most of us are more used to thinking in degrees, but there are two functions, np.deg2rad and np.rad2deg that do the conversions for us.

2.4 Coordinate Formulas

Another useful formula is one to find the distance between two x-y points. The distance d between points (x1, y1) and (x2, y2) is √ 2 2 d = (x2 − x1) + (y2 − y1) The angle between three x-y points is a useful formula that is not commonly remembered from high school geometry. It can be found using the Law of Cosines, and is given by d2 + d2 − d2 θ = arccos( 12 13 23 ) 2d12d13 where d12 is the distance between point 1 and point 2.

10 Chapter 2. Math and Python Background Mathematical Artwork in Python

2.5 Complex Numbers

Fractals are usually calculated using complex numbers. Don’t worry, they’re not that compli- cated. While essential and very powerful for many engineering applications, only the basics of using them in calculations will be covered here. A complex number is essentially a way to define and manipulate two numbers in one. Ithas what is called a real part and an imaginary part. The complex number may be written as

z = a + bi

where a is the real part and b the imaginary part, multiplied by the imaginary number i. There’s nothing “imaginary” about i, except that early mathematicians studying the concept found them controversial and√ had to “imagine” that the square root of negative one actually existed, as by definition i = −1. In the words of one of my engineering professors, “stick your finger in a light socket – that’s i”, referring of course to how electrical currents canbe elegantly described by complex number equations. Remember our x-y coordinates in the plane? A complex number can be plotted just the same by assuming the real part is the horizontal component and the imaginary part is the vertical component. In Python, complex numbers have their own built-in data type, created by adding a j after the numeric value. Python takes the electrical engineering notation for complex numbers that uses a j rather than i, since i is usually used to signify electrical current. (Note that i is used for current because that electrical property was originally known as “intensity”.) Here, a complex number with real part 2 and imaginary part 3 is defined in Python.

a = 2 + 3j

Adding and subtracting complex numbers is straightforward. Just add or subtract each of the components. Python handles this for you.

a = 2 + 3j b = 2 + 1j a + b (4+4j)

Multiplication and division are a little more complicated, but operate using the well-known “FOIL” method (First, Outer, Inner, Last) introduced in algebra classes, with the added sim- plification that j2 = −1. Python takes care of this for you, too.

a * b (1+8j)

Going step-by-step, the multiplication can be worked out by FOIL then combining all the real and imaginary terms:

(2 + 3j)(2 + 1j) 4 + 2j + 6j + 3j2 4 + 8j − 3 1 + 8j

Numpy can handle arrays of complex numbers too, but if creating arrays from scratch, it needs to be told to use a complex data type using the dtype parameter.

2.5. Complex Numbers 11 Mathematical Artwork in Python

np.zeros(4, dtype=complex) array([0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j])

Alternatively, a complex-type array can be made by adding a two arrays together: x = np.ones(4) y = np.ones(4) * 2 x + 1j*y array([1.+2.j, 1.+2.j, 1.+2.j, 1.+2.j])

More mathematical concepts and their implementations in Numpy will be introduced later, but this gives us a good basis for understanding the topics to come.

2.6 Histograms

A final mathematical tool we will make use ofare histograms. These plots help visualize a large set of numbers to see how close or spread out they are. The set of numbers is divided into “bins” of equal width, then plotted where x-axis represents the value of the bin, and the y-axis represents how many numbers fall in each bin. Here we can see it with a random set of ten numbers, plotting the histogram with plt.hist: x = np.random.random_sample(size=10) print(x) plt.hist(x) plt.xlabel('Value') plt.ylabel('Count'); [0.39632194 0.43187019 0.42782441 0.99312799 0.70494578 0.35095939 0.63234389 0.36309547 0.77061946 0.90115051]

From the histogram, one can see there are three data points between 0 and 0.4, two data points between 0.4 and 0.5, and so on. With small data sets like this one, the histogram is not as useful, but it becomes very helpful when the number of points gets large. In this example with 500 values, the histogram tells us that the values are more likely to be near 0 than near 2 or -2.

12 Chapter 2. Math and Python Background Mathematical Artwork in Python

x = np.random.normal(size=500) plt.hist(x) plt.xlabel('Value') plt.ylabel('Count');

In later chapters, the histogram will be extended into two dimensions so that it can be used to help create two-dimensional images. For example, the following image is a histogram in two dimensions, where each pixel represents a range of x and y values, and the brightness of each pixel represents how many points are in each bin.

Again, there are more data points near the center of the image than near the edges. Take a horizontal slice through the center of the image, for example, and a one-dimensional his- togram could be made based on those brightnesses along that slice. In later chapters, we will use 2D histograms to visualize interesting complicated mathematcial functions, where the histogram becomes the art.

2.6. Histograms 13 CHAPTER FOUR

PARAMETRIC METHODS

In this chapter, images are created using vector graphics defined by plotting lines, curves, or other shapes. The images are defined based on mathematical functions that compute xand y coordinates. Because these models are usually just x-y pairs connected by line segments, a math plotting library such as Matplotlib can be used. In Python, start by importing Numpy to help with the math and Matplotlib to do the plotting. We’ll also set up a default figure size so that it doesn’t have to be specified every time. import numpy as np import matplotlib.pyplot as plt plt.rcParams['figure.figsize'] = (5, 5)

Simple plots can be made by defining x and y values, as demonstrated by plotting the function y = x2. The linspace function creates a range of x values, from -3 to +3 with 500 points. x = np.linspace(-3, 3, num=500) y = x**2 plt.plot(x, y);

Notice that the x scale and the y scale are not the same. In artistic applications, this is not likely desired because shapes like circles will be stretched into ovals. Also, we don’t typically care about the axes values, except possibly when developing and debugging an image. Adding the following plt.axis calls after plotting remedies these issues.

23 Mathematical Artwork in Python

x = np.linspace(-3, 3, num=500) y = x**2 plt.plot(x, y) plt.axis('equal') plt.axis('off');

Now, we have the plotting tools to start making interesting figures. But first, it will take a basic understanding of a few more mathematical concepts, which involve plotting some systems that aren’t very artistic to begin with. The first systems are based on physical objects such as pendulums, which help in understanding the math behind them. As more complexity is added to the systems, the math-to-art line can be crossed.

4.1 Pendulums

Start by thinking about a simple pendulum, the kind found in mechanical clocks. These pen- dulums oscillate with a fixed period that depends on the pendulum’s length and the forceof . Pulling out some physics calculations, the position of the pendulum along its swing path is

y = A sin(kt) where A is the largest distance from the center of the swing and k is a constant that depends on the pendulum length and force of gravity; this constant determines how quickly a given pendulum swings. One could plot a pendulum swing as a function of time, and end up with this:

plt.figure(figsize=(6,4)) t = np.linspace(0, 2*np.pi, 500) y = np.sin(2*t) plt.plot(t, y);

24 Chapter 4. Parametric Methods Mathematical Artwork in Python

Now let the pendulum swing in two dimensions. Both x and y vary with time independently. The position is defined by two equations

x = Ax sin(kxt + ϕx)

y = Ay sin(kyt + ϕy)

The ϕ (Greek letter phi) terms are the phase of the oscillation and were added to account for the two dimensions not necessarily having the same starting position within their swing. If we could attach a pen to the pendulum and ensure that it always contacts a fixed piece of paper, we could draw the 2-dimensional path the pendulum traces out. Note that cos(t) = sin(t + π/2), so the code uses cosine instead of adding in a phase shift to the sine function to get the same thing.

t = np.linspace(0, 2*np.pi, 500) x = np.sin(t) y = np.cos(t) plt.plot(x, y) plt.axis('equal') plt.axis('off');

4.1. Pendulums 25 Mathematical Artwork in Python

This system is called a parametric equation, because the value of x and y depends on the parameter t, where t is often thought of as time. This is unlike the typical function plotting introduced in beginning algebra courses where y usually depends on x through some function y = f(x), which results in one x value producing only one y value. With parametric equations, x and y are independent of each other, but depend on the common factor t, which allows a single x value to have multiple y values, such as in the plot of the circle. Notice that t does not actually show up in the plot, although if we could make it into an animation, we could see how the pen traverses the circle and then repeats itself over time. Although some minimalist artists may disagree, a circle may not be considered art by itself. Somewhat more interesting figures can be created by varying the k and ϕ parameters, result- ing in what are called Lissajous Curves, named for Jules Antoine Lissajous, who first created these curves in the mid 1800s, not with a pendulum, but by reflecting light off a pair of vi- brating tuning forks as illustrated in Fig. 4.1.

Fig. 4.1: Creating Lissajous curves using a pair of tuning forks from John Tyndall’s 1875 book “Sound” [10]. Public Domain.

Here are a few examples of Python-generated Lissajous curves. Experiment with your own coefficients to see what sort of variations are possible. t = np.linspace(0, 2*np.pi, 500) x = np.sin(t) y = np.cos(t) plt.subplot(221, aspect='equal') plt.plot(np.sin(t), np.cos(t)) plt.subplot(222, aspect='equal') plt.plot(np.sin(2*t), np.cos(t)) plt.subplot(223, aspect='equal') plt.plot(np.sin(4*t), np.cos(3*t + np.pi/3)) plt.subplot(224, aspect='equal') plt.plot(np.sin(4*t), np.cos(5*t)) plt.tight_layout()

26 Chapter 4. Parametric Methods Mathematical Artwork in Python

4.2 Harmonographs

Things get more interesting by adding complexity to the functions. As a next step, what if two pendulums could both swing in the x and y directions, instead of just one pendulum? A mechanical device called a harmonograph, illustrated in Fig. 4.2, can generate such curves physically with pen and paper, using two pendulums each having 2-dimensional swing. These devices became popular in the 1800’s as party curiosities [11] but served no purpose other than making drawings. Mathematically, the equations are similar to the pendulum system but add a second sine term to x and y:

4.2. Harmonographs 27 Mathematical Artwork in Python

Fig. 4.2: Single-pendulum harmonograph from Archibald Williams “Things to Make” [12]. Public Domain.

x = d[Ax1 sin(kx1t + ϕx1) + Ax2 sin(kx2t + ϕx2)]

y = d[Ay1 sin(ky1t + ϕy1) + Ay2 sin(ky2t + ϕy2)]

To model real harmonographs, a damping factor d was also added to account for friction slowing down each pendulum’s swing. The damping could be computed with an exponential function, but another easy way to calculate d is using t d = 1 − k i max(t) where k is the constant decay rate of the pendulum, resulting in a decay to 1 − k at the final time t. Without decay (d = 1 or k = 0), the harmonograph pattern repeats itself after a certain period as shown in this example, noting the closed loop. t = np.linspace(0, 2*np.pi, 500) d = 1 x = d*(np.sin(3*t) + .2 * np.sin(6*t)) y = d*(np.sin(2*t) + .3 * np.sin(8*t)) plt.plot(x, y) plt.axis('equal') plt.axis('off');

28 Chapter 4. Parametric Methods Mathematical Artwork in Python

But with decay, the same swing pattern erodes toward the center of the figure. Now we’re starting to get something interesting. t = np.linspace(0, 20*np.pi, 5000) k = .4 d = 1 - k*t/t.max() x = d*(np.sin(3*t) + .2 * np.sin(6*t)) y = d*(np.sin(2*t) + .3 * np.sin(8*t)) plt.plot(x, y) plt.axis('equal') plt.axis('off');

In some harmonographs, the paper can also oscillate on a pendulum below the pen. This adds yet another sine term to x and y. Since we’re not bound to physical systems when writing code, we can even consider higher numbers of pendulums, each adding another sine term. Here is a function for computing a harmonograph curve with any number of pendulums, where the length of each input argument equals the number of pendulums.

4.2. Harmonographs 29 CHAPTER FOURTEEN

APPENDIX: ABOUT THIS BOOK

14.1 Writing a book in Jupyter

This book was written entirely in Jupyter notebooks and plain-text files using ReStructured Text (RST) syntax. While it took a bit of experimentation and tinkering to get the formatting correct, the process was much smoother than attempting something like this, with all its embedded code blocks and images, in a traditional word processor. This appendix, written partly for my own reference if I ever take on another writing project, details some of the methods and hacks I used to make the book. The Sphinx tool, typically used to generate documentation for Python packages, was used to convert the Jupyter notebooks and RST files into the book output, both in Portable Doc- ument Format (PDF) and Electronic Publication (EPUB) formats. Another Python package, Nbsphinx, was installed to handle conversion of the Jupyter notebooks to allow Sphinx to read them. Jupyter notebooks have the built-in option for writing documentation cells in Markdown syn- tax. While this is useful in single notebooks, it is limited when adding citations or cross- references to sections written in other notebook files. For this reason, the text was written in Jupyter raw cells with RST syntax. For these cells, the cell type was changed from Code to Raw, then the “Raw NBConvert Format” option changed to “ReStructured Text” on every cell with text in it. Code cells were added and run as normal in Jupyter. Some code cells were hidden for cases where the code is not relevant to the chapter, such as repeated imports or functions already defined in previous chapters. For hidden cells, the metadata was addedto the cell (in Jupyter, from the View menu, select Cell Toolbar > Edit MetaData, then click the Metadata button on the cell to hide and add the following dictionary item.

{"nbsphinx": "hidden"}

There is no easy way, that I could find, to hide the code cell but still show its output using Nbsphinx. For these cases, the code cell was hidden with the above metadata while its output was saved to an image file. The next cell then loaded that image file withthe .. image:: directive in RST. A few Sphinx extensions were installed and enabled to help with formatting: • sphinx.ext.imgmath: for converting equations into PNG format for EPUB compatibility • sphinxcontrib.bibtex: for adding citations and a bibliography • sphinxcontrib.cairosvgconverter: for converting SVG output from Jupyter cells into PDF for Latex compatibility • sphinx.ext.mathjax: to use Mathjax for rendering equations in HTML output. HTML was used for proofing and debugging the book, but not in the final PDF or EPUB product.

201 Mathematical Artwork in Python

The other formatting issue I didn’t like was how, like in typical Jupyter notebooks, each cell is numbered with a prompt In[1] or Out[1]. While useful in a code development notebook, it just adds clutter and confusion to a book. While the Sphinx developers offer a way using CSS to hide these prompts, it only works in HTML or HTML-based output formats, which does not include Latex. Setting these lines

nbsphinx_input_prompt = '' nbsphinx_output_prompt = ''

in the conf.py file remove the prompts from all output formats. For EPUB format, a separate cover page and header file was required, while the Latex version stored its cover page in the Latex preamble variable defined in the Sphinx conf.py file. The PDF is generated via LaTeX, which usually does a nice job placing images and text and paginating the document. In some cases, however, page breaks were put in places that don’t work, for example in Chapter 1 the original document had a page break right in the middle of the text-based Mandelbrot output. To remedy these, Latex raw directives were used:

.. raw:: latex

\newpage

Since the raw:: latex directive is only processed for PDF outputs, this won’t affect the EPUB version or HTML documents that do not have the same pagination.

14.1.1 Environment

My Python environment was installed using the Anaconda Python distribution, with a spe- cific conda environment set up to specify all the package versions for the book. The specific environment, including all version numbers used to compile the book, is defined by the envi- ronment.yml file:

name: book channels: - conda-forge dependencies: - numpy==1.19.1 - matplotlib==3.3.0 - scipy==1.5.2 - sympy==1.6.1 - pillow==7.2.0 - jupyterlab==2.2.4 - numba==0.50.1 - sphinx==3.3.1 - nbsphinx==0.7.1 - sphinxcontrib-bibtex==1.0.0 - cairosvg==2.4.2 - pip==20.2.1: - sphinxcontrib-svg2pdfconverter[CairoSVG]==1.1.0

Right about the time I was putting the final touches on the book and writing this descrip- tion, the Jupyter Book project (https://jupyterbook.org/) announced a huge new revision that probably would streamline the process and make book-writing even easier. Maybe the next edition will use that.

202 Chapter 14. APPENDIX: About this Book Mathematical Artwork in Python

14.2 About the Author

Collin Delker is an engineer by day, but an artist at heart. With a PhD in engineering, he always had an interest in creative endeavors that blend science, math, , and music. Just as there is beauty in a renaissance or symphony performance, there can be as much beauty in an electrical schematic, a well-made software algorithm, or a mathematical equa- tion. His mathematical artwork combines aspects of all these fields to generate abstract images. Appreciating and creating art makes one a better engineer, being an engineer opens new possibilities for creating art.

14.2. About the Author 203