{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Pynamical: phase diagrams of the logistic map\n", "\n", "**Citation info**: Boeing, G. 2016. \"[Visual Analysis of Nonlinear Dynamical Systems: Chaos, Fractals, Self-Similarity and the Limits of Prediction](http://geoffboeing.com/publications/nonlinear-chaos-fractals-prediction/).\" *Systems*, 4 (4), 37. doi:10.3390/systems4040037.\n", "\n", "Pynamical documentation: http://pynamical.readthedocs.org\n", "\n", "This notebook demonstrates several ways to create phase diagrams to visualize system attractors and differentiate random noise from chaos. Phase diagrams embed 1-dimensional data (like time series) from a dynamical system into 2- or 3-dimensional phase space by plotting the value at time t vs the value at time t+1 (vs the value at time t+2, if it's a 3-D plot)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import IPython.display as IPdisplay\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "import pandas as pd\n", "import pynamical\n", "from pynamical import simulate, save_fig, phase_diagram, phase_diagram_3d\n", "%matplotlib inline" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "title_font = pynamical.get_title_font()\n", "label_font = pynamical.get_label_font()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## First, let's a create a phase diagram to show the logistic map's fixed-point attractor at 0.655 when the growth rate parameter is set to 2.9" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# draw a phase diagram for 100 generations for the growth rate parameter 2.9\n", "# it shows points converging on 0.655 because the logistic map has a fixed-point attractor at 0.655 when r=2.9\n", "pops = simulate(num_gens=100, rate_min=2.9, num_rates=1, num_discard=100)\n", "phase_diagram(pops, title='Logistic Map Attractor, r=2.9', size=20)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Next, let's create phase diagrams to show the logistic map's limit cycle attractor when the growth rate parameter is set to 3.50 - 3.57" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# draw a phase diagram for 100 generations for the growth rate parameter 3.5\n", "# it shows 4 points because the logistic map has a period of 4 when r=3.5\n", "pops = simulate(num_gens=100, rate_min=3.5, num_rates=1, num_discard=100)\n", "phase_diagram(pops, title='Logistic Map Attractor, r=3.5', size=20)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# draw a phase diagram for 100 generations for the growth rate parameter 3.56\n", "# it shows 8 points because the logistic map has a period of 8 when r=3.56\n", "pops = simulate(num_gens=100, rate_min=3.56, num_rates=1, num_discard=100)\n", "phase_diagram(pops, title='Logistic Map Attractor, r=3.56', size=20)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# draw a phase diagram for 100 generations for the growth rate parameter 3.57\n", "# it shows n points because the logistic map has a period of n when r=3.57\n", "pops = simulate(num_gens=100, rate_min=3.57, num_rates=1, num_discard=100)\n", "phase_diagram(pops, title='Logistic Map Attractor, r=3.57', size=20)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Now, let's create a phase diagram to show the logistic map's strange attractor when the growth rate parameter is set to 3.9" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# draw a phase diagram for 2,000 generations for the growth rate parameter 3.9\n", "# the plot reveals the strange attractor - the logistic map is chaotic when r=3.9\n", "pops = simulate(num_gens=2000, rate_min=3.9, num_rates=1)\n", "phase_diagram(pops, xmin=0.25, xmax=0.75, ymin=0.8, ymax=1.01, size=20, title='Logistic Map Attractor, r=3.9')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Now, let's create a phase diagram to show the logistic map's strange attractors across the chaotic regime (from r=3.6 to r=4.0)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# draw a phase diagram for 2,000 generations across 50 growth rate steps from 3.6 to 4.0\n", "# each chaotic growth rate has its own parabola\n", "pops = simulate(num_gens=2000, rate_min=3.6, rate_max=4.0, num_rates=50)\n", "phase_diagram(pops, xmin=0.25, xmax=0.75, ymin=0.8, ymax=1.01, size=7, \n", " title='Logistic Map Attractor, r=3.6 to r=4.0', color='viridis')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Next, let's demonstrate how to use phase diagrams to differentiate chaos from random noise in time series data" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# sometimes it is hard to tell if a time series is chaotic or random\n", "# generate two time series of 1,000 steps, one chaotic and one random\n", "# generate 30,000 time steps for the chaotic series but only keep the final 1,000 (when system is fully evolved)\n", "total_gens = 30000\n", "gens = 1000\n", "np.random.seed(1)\n", "\n", "chaos_pops = simulate(num_gens=total_gens, rate_min=3.99, num_rates=1)\n", "chaos_pops = chaos_pops.iloc[total_gens-gens:].reset_index().drop(labels='index', axis=1)\n", "\n", "random_pops = pd.DataFrame(np.random.random(gens), columns=['value'])\n", "time_series = pd.concat([chaos_pops, random_pops], axis=1)\n", "time_series.columns = ['chaos', 'random']\n", "time_series.head()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# plot the chaotic and random time series to show how they are sometimes tough to differentiate\n", "ax = time_series.plot(kind='line', figsize=[10, 6], linewidth=3, alpha=0.6, style=['#003399','#cc0000'])\n", "ax.grid(True)\n", "ax.set_xlim(40, 90)\n", "ax.set_ylim(0, 1)\n", "ax.set_title('Time Series, Deterministic Chaos vs Random Data', fontproperties=title_font)\n", "ax.set_xlabel('Generation', fontproperties=label_font)\n", "ax.set_ylabel('Population', fontproperties=label_font)\n", "ax.legend(loc=3)\n", "\n", "save_fig('chaos-vs-random-line')\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# plot same data as 2D phase diagram instead\n", "pops = pd.concat([chaos_pops, random_pops], axis=1)\n", "pops.columns = ['chaos', 'random']\n", "phase_diagram(pops, size=20, color=['#003399','#cc0000'], ymax=1.005, legend=True, \n", " filename='logistic-attractor-chaos-random')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Now, let's create 3-D phase diagrams to show the same chaos vs random noise, in three dimensions" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# plot same data as 3D phase diagram instead\n", "phase_diagram_3d(pops, color=['#003399','#cc0000'], filename='logistic-attractor-chaos-random-3d',\n", " legend=True, legend_bbox_to_anchor=(0.94, 0.9))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Create a 3-D phase diagram to show the logistic map's strange attractors across the chaotic regime (from r=3.6 to r=4.0), twisting and curling around their state space in three dimensions" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# run logistic model for 4,000 generations across 50 growth rate steps from 3.6 to 4.0\n", "pops = simulate(num_gens=4000, rate_min=3.6, rate_max=4.0, num_rates=50)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# phase diagram: each chaotic growth rate has its own strange attractor curling through state space\n", "phase_diagram_3d(pops, title='Logistic Map Attractor, r=3.6 to r=4.0', alpha=0.1, color='viridis', color_reverse=False, \n", " azim=230, filename='3d-logistic-map-attractor-1')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# phase diagram: each chaotic growth rate has its own strange attractor curling through state space\n", "phase_diagram_3d(pops, title='Logistic Map Attractor, r=3.6 to r=4.0', alpha=0.1, color='viridis', color_reverse=False, \n", " elev=7, azim=320, filename='3d-logistic-map-attractor-2')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## In another notebook, I create animated gifs by panning and rotating around my 3-D phase diagrams to reveal more of their structure:\n", "[pynamical-demo-3d-animation.ipynb](pynamical-demo-3d-animation.ipynb)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# here's an example of the animated phase diagrams that I create in pynamical-demo-3d-animation.ipynb\n", "IPdisplay.Image(url='images/phase-animate/05-logistic-3d-phase-diagram-chaotic-regime.gif')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## For more info:\n", " - [Read the journal article](http://geoffboeing.com/publications/nonlinear-chaos-fractals-prediction/)\n", " - [Pynamical documentation](http://pynamical.readthedocs.org)\n", " - [Chaos Theory and the Logistic Map](http://geoffboeing.com/2015/03/chaos-theory-logistic-map/)\n", " - [Visualizing Chaos and Randomness with Phase Diagrams](http://geoffboeing.com/2015/04/visualizing-chaos-and-randomness/)\n", " - [Animated 3D Plots in Python](http://geoffboeing.com/2015/04/animated-3d-plots-python/)" ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python (pynamical)", "language": "python", "name": "pynamical" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.4" } }, "nbformat": 4, "nbformat_minor": 4 }