Contact Us!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.

| Download
Views: 15
Image: ubuntu2204
Kernel: Python 3 (system-wide)

Double click here and enter the student numbers for all group members. Don't enter any names.

Before you start work on the project, click on this link to read the MATH0011 project instructions.

  1. 23009447

Bifurcation Diagrams

This project explores the behaviour of iterative sequences, such as the one defined by x0=1/2x_0 = 1/2 and

xn+1=γxn(1tanh(xn))x_{n+1} = \gamma x_n (1 - \tanh(x_n))

for n0n \geqslant 0. You will see that as γ\gamma increases the behaviour of the sequence (xn)(x_n) changes in an interesting way. For γ\gamma less than about 5, the sequence xnx_n converges: e.g. if γ=4\gamma=4 then xn0.9729550 as n. x_n \to 0.9729550\cdots \textrm{ as } n \to \infty.

When 5.1γ85.1\leqslant \gamma \leqslant 8 the sequence doesn't converge to a limit, but instead tends to cycle between two values. For example, if γ=6\gamma=6 then

x1000=0.6913336,x1001=1.6640204,x1002=0.6913336,x1003=1.6640204x_{1000} = 0.6913336\cdots, x_{1001} = 1.6640204\cdots, x_{1002} = 0.6913336\cdots, x_{1003} = 1.6640204\cdots

As γ\gamma increases the limiting behaviour becomes a cycle of 4 values, then of 8, then 16, and so on. For gamma around 9.4 stops tending to a cycle at all and becomes completely non-periodic, but there are larger values of γ\gamma at which the sequence again tends to a cycle.

A bifurcation diagram is a diagram which summarises this behaviour. The horizontal axis shows values of γ\gamma, and the vertical axis shows values taken by the sequence after some large number of terms.

You will generate iterative sequences, plot bifurcation diagrams, and try to estimate the Feigenbaum constant, defined as the limiting ratio of differences between parameter values at which the length of the cycle changes. You will get experience in using matplotlib to produce beautiful plots and in writing code that is as efficient as possible. Approximating the Feigenbaum constant is a bit awkward, and you'll need to think carefully about the best way to do it in Python.

Use the next cell for any imports you need.

import math import matplotlib.pyplot as plt import numpy as np

Exercise 1 - defining and iterating some functions

Write a Python function h(x, g) whose output is gx(1tanh(x))gx (1- \tanh(x)).

def h(x, g): # your code goes here return g * x * (1 - math.tanh(x))

If f:RRf: \mathbb{R} \to \mathbb{R} is a function, we define fnf^{\circ n} to be the function ffff\circ f \circ \cdots \circ f obtained by composing ff with itself nn times. Thus f1(x)=f(x)f2(x)=f(f(x))f3(x)=f(f(f(x))) \begin{align*}f^{\circ 1}(x) & = f(x)\\ f^{\circ 2}(x) &= f(f(x))\\ f^{\circ 3}(x) &= f(f(f(x))) \end{align*} and so on.

Write a function iterate(f, x0, n) which takes a function ff, a number x0x_0, and a positive whole number nn and returns fn(x0)f^{\circ n}(x_0),

def iterate(f, x0, n): # your code here result = x0 for p in range(n): result = f(result) return result

Exercise 2 - finding the period of an iteration

The number y0y_0 is said to be mm-periodic for the function ff if mm is the smallest positive whole number such that y0=fm(y0)y_0 = f^{\circ m}(y_0). We'd like to check whether y0y_0 is mm-periodic for some mm, but because of rounding errors we can't expect y0y_0 to be exactly equal to fm(y0)f^{\circ m}(y_0). For this reason we'll relax our definition a bit by allowing fm(y0)f^{\circ m}(y_0) to be within ϵ\epsilon of y0y_0, where ϵ\epsilon is some small number.

Write a function periodicity(y0, f, epsilon=0.0001, max_iter=1000) which takes as input a number y0y_0, a function ff, a number ϵ\epsilon, and a positive whole number max_iter, and returns the smallest mm such that

y0fm(y0)<ϵ| y_0 - f^{\circ m}(y_0) | < \epsilon

if there is such an mm which is less than max_iter. If there is no such mm less than max_iter it should return 1-1.

I've written the first line of the function definition for you, specifying default values for epsilon and max_iter. If you can't remember how these work, review the weekly notebooks or read this link or ask a chatbot.

def periodicity(y0, f, epsilon=0.0001, max_iter=1000): # your code here m = 1 y_m = f(y0) while abs(y0 - y_m) >= epsilon and m < max_iter : m = m + 1 y_m = iterate(f, y0, m) if m == max_iter: return -1 else: return m

Here's one test case for your function. Your function h above takes two inputs, but sometimes we want to fix the value of the second input g and use the one-input function that sends a number x to h(x, g). You can create that function with def as usual, or you can write lambda x: h(x, g) which is sometimes simpler. Read more about Python lambdas at this link. If your periodicity function works correctly, then periodicity(0.3507810470182891, lambda x: h(x, 8.5)) should be 4.

periodicity(0.3507810470182891, lambda x: h(x, 8.5))
4

Exercise 3 - plotting a bifurcation diagram

In this exercise you are going to write a function to produce bifurcation diagrams for the function h(x,γ)h(x, \gamma). The function will take the following inputs:

  • g_min and g_max, the maximum and minimum values of γ\gamma on the x-axis of your plot.

  • n_values, the number of equally spaced values of γ\gamma to use.

  • preiterations, the number MM referred to in the description below.

  • n_points, the number LL referred to in the description below.

I've written the first line of the function for you and suggested some default values for these quantities.

Inside the function, generate n_values equally spaced values of γ\gamma between g_min and g_max. To make the finished diagram look good you probably need n_values to be a few thousand, but you might want to make n_values smaller to begin with to make the code run quickly.

For each of these values of γ\gamma, compute y0=fM(1/2)y_0 = f^{\circ M}(1/2) where f(x)=h(x,γ)f(x)=h(x, \gamma) and MM is the input preiterations of your function (experiment to see which values make the plot look best - roughly 500 seems to work well). This allows the iterations to settle down to a cycle, if they're going to. If y0y_0 is mm-periodic - which you can check using your periodicity function with epsilon = 0.0001 say - plot all the points with coordinates

(γ,y0),(γ,f(y0)),(γ,f(f(y0))),,(γ,f(m1)(y0))(\gamma, y_0), (\gamma, f(y_0)), (\gamma, f(f(y_0))), \ldots, (\gamma, f^{\circ (m-1)}(y_0))

If y0y_0 isn't mm-periodic for any m<1000m < 1000, plot all LL points with coordinates

(γ,y0),(γ,f(y0)),(γ,f(f(y0))),,(γ,f(L1)(y0))(\gamma, y_0), (\gamma, f(y_0)), (\gamma, f(f(y_0))), \ldots, (\gamma, f^{\circ (L-1)}(y_0))

where LL is the input n_points to your function. I suggest a value of LL of about 500 in your final diagram.

Here are some suggestions to help you create the diagram.

  • np.linspace generates equally spaced points between given limits.

  • You can plot points using plt.scatter. Given lists xs = [a0, a1, a2,...] of x-coordinates and ys = [b0, b1, b2, ...] of y-coordinates, plt.scatter(xs, ys) plots the points with coordinates (a0, b0), (a1, b1), .... You may need to use plt.scatter(xs, ys, s = e, marker='.') where e is some small number to get the plot looking good.

  • Experiment with parameter values to get a balance between making the plot look good and making the code run quickly. Start with smaller values of n_value, preiterations, n_points so that the code runs quickly, and increase them when you're confident everything works correctly.

  • Only call plt.scatter once - you should generate all of the points in your plot, then use plt.scatter to plot them. If you call plt.scatter lots of times, your code will take a long time to run.

def generate_bifurcation_diagram(g_min=2, g_max=15, n_values=3000, preiterations=500, n_points=200): # your code here

Now call your generate_bifurcation_diagram function to make a plot.

generate_bifurcation_diagram()

Make a new version of generate_bifurcation_diagram that works for the sequence defined by xn+1=γxn(1xn)x_{n+1} = \gamma x_n (1-x_n) and use it to generate another bifurcation diagram. Good values for the maximum and minimum values of γ\gamma are 2.5 and 4.

Exercise 4 - the Feigenbaum constant

In this final part you are going to try and estimate the Feigenbaum constant δ\delta. This is difficult to do well, and you will only get a very rough approximation by this method.

What you should see in your bifurcation diagram for h(x,γ)h(x, \gamma) is that for values of γ\gamma up to about 5, the iterations converge to a single value. For γ\gamma between approximately 5 and 8, the iterations eventually have period 2. For γ\gamma between roughly 8 and 9, they have period 4. This pattern continues with the periods doubling and the intervals of γ\gamma values getting geometrically smaller.

Let a1a_1 be the value of γ\gamma at which period 1 changes to period 2, a2a_2 be the value at which period 2 changes to period 4, a3a_3 be the value at which period 4 changes to period 8, and so on, so that a15,a28,a39a_1 \approx 5, a_2 \approx 8, a_3\approx 9. The Feigenbaum constant is defined to be

δ=limnan+1anan+2an+1\delta = \lim_{n \to \infty} \frac{a_{n+1}-a_n}{a_{n+2}-a_{n+1}}

Try to calculate as many of the aia_i as you can, and use them to approximate δ\delta. You can probably only get reliable values for the aia_i up to i=5i=5 or 6 because of issues with the precision of Python floats. The simplest way to find the aia_i is

  • produce a list of equally spaced γ\gamma values between 5 and 9.5

  • for each value of γ\gamma, work out the eventual periodicity of the sequence xn+1=γxn(1tanh(xn))x_{n+1} = \gamma x_n (1-\tanh(x_n)) by iterating 1000 times starting at x0=1/2x_0=1/2 with your iterate function and then using your periodicity function. Store the period for the iith value of γ\gamma in position ii of another list.

  • use a loop to find the γ\gamma values where the periodicity changes from 1 to 2, 2 to 4, 4 to 8, 8 to 16, and so on - these are your aia_i.

Now calculate as many of the values aia_i as you can for the iteration xn+1=γxn(1xn)x_{n+1} = \gamma x_n (1-x_n). You should get different aia_i to the ones above, but the limiting value δ\delta of the ratio of the differences of the aia_i is known to be the same!

Submitting your project

Check you have done the following.

  1. Included all group members' student numbers at the top of this notebook.

  2. Read through every exercise to check you have answered every part.

  3. Carefully read and followed all of the MATH0011 project instructions.

  4. Checked that all of the code in this notebook works correctly.

One group member should download the completed notebook (in CoCalc, click the File menu next to the green Save button, then click Download) and submit it on the MATH0011 Moodle page in the projects section. Please submit only one file per group.