```python
import numpy as np
import matplotlib.pyplot as plt
plt.style.use(["dark_background", "ggplot"])

SMALL_SIZE = 16
MEDIUM_SIZE = 18
BIGGER_SIZE = 40

plt.rc('axes', titlesize=MEDIUM_SIZE)     # fontsize of the axes title
plt.rc('axes', labelsize=MEDIUM_SIZE)    # fontsize of the x and y labels
plt.rc('xtick', labelsize=SMALL_SIZE)    # fontsize of the tick labels
plt.rc('ytick', labelsize=SMALL_SIZE)    # fontsize of the tick labels
plt.rc('legend', fontsize=SMALL_SIZE)    # legend fontsize

from functools import partial
```


```python
import utils.plots as plot
from utils.generateOrbits import last_value_orbit, generate_orbit, generate_orbit_iter
```


```python
import sys 
  
# the setrecursionlimit function is 
# used to modify the default recursion 
# limit set by python. Using this,  
# we can increase the recursion limit 
# to satisfy our needs 
  
sys.setrecursionlimit(10**6) 
```

# Tests 


```python
#with plt.style.context('dark_background'):
#    plt.plot(np.sin(np.linspace(0, 2 * np.pi)), 'r-o')
#plt.show()
```

# Caos introduction 

## Functions 

- Functions are deterministics if their output is completely defined by their input. 
- If a function is not deterministic, it is stochastic 

obs: (The root of the word stochastic is stochos which means "guess" or "target" in ancient Greek).

### Functions as Actions



Let $f$ be the action triple. That is, the function takes a number $x$ as input and returns $3x$. For instance, if the input is 4 the output is 12. There are some ways to write this, such as:

- $$f(4) = 12$$ 

-  $$4 \xrightarrow[]{f}12$$

### Functions as Maps



A function can also be understood as a mapping between sets (My favorite). Let's call this function $g$. Mathematically we would write:

$$
f: Input \rightarrow Output
$$

which means **f is a function that maps Input to Output**.

### Function as Graphs

Let's represent the previously function ($g$) as a graph.


```python
plt.figure(figsize=(20,6))
input_x = [-2, -1, 0, 1, 2, 3]
input_y = [-1,  0, 1, 2, 3, 4]
plt.scatter(input_x, input_y, s = 70)
plt.title(r"$g$")
plt.xlabel("Input")
plt.ylabel("Output")
plt.show()
```

Let $f$ be a new function which will map the missing pairs values on the graph.


```python
plt.figure(figsize=(20,6))
input_x = [-2, -1, 0, 1, 2, 3]
input_y = [-1,  0, 1, 2, 3, 4]
plt.plot(input_x, input_y, 'r-o')
plt.title(r"$f$")
plt.xlabel("Input")
plt.ylabel("Output")
plt.show()
```

## Iterating Functions

Iterated functions are ```dynamical systems``` (a generic name for a variable or set of variables that change over time.)



A easier way to observe the iterating process is with a series, which is normally called ```itinerary``` or ```orbit``` (which I prefer and use throughout the text). For instance, this is a orbit with 4 steps.

- $$2 \xrightarrow[]{f}4 \xrightarrow[]{f}8 \xrightarrow[]{f}16 \xrightarrow[]{f}32$$

Similary we can write the same orbit as a function composition, given that $f(g(x)) \text{ is equals to } f\circ g (x)$

- $$  f \circ f\circ f \circ f (2)$$
- $$  f( f( f( f(2)))) $$

However, the best way to indicate a series is with the follwing notation:

- $$ f^{(4)}(x)$$




```python
def mult2(x):
    return x*2

print(mult2(2))
print(mult2(4))
print(mult2(8))
# We can compute this orbit
print(mult2(mult2(mult2(mult2(2)))))
```

    4
    8
    16
    32



```python
def square(x):
    return x**2

print(square(2))
print(square(4))
print(square(square(square(2))))
```

    4
    16
    256



```python
# signature: func, initial_value, steps
print(last_value_orbit(mult2, 2, 4))
print(last_value_orbit(square, 2, 3))
```

    32
    256


## Exploring functions

### Basic Plot 


```python
y = generate_orbit(mult2, 2, 4, [])
fig = plt.figure(figsize = (20,4))
_=plot.plot_orbit(y, "Orbit mult2", "Steps", "Values")
```


```python
y = generate_orbit(square, 2, 3, [])
fig = plt.figure(figsize = (20,4))
_=plot.plot_orbit(y, "Orbit square", "Steps", "Values")
```

### Exploring big steps


```python
y = generate_orbit(square, 2, 8, [])
fig = plt.figure(figsize = (20,4))
_=plot.plot_orbit(y, "Orbit square", "Steps", "Values")
```


```python
y = generate_orbit(square, 0.5, 20, [])
fig = plt.figure(figsize = (20,4))
_=plot.plot_orbit(y, "Orbit square", "Steps", "Values")
```


```python
y0 = generate_orbit(square, 0.5, 20, [])
y1 = generate_orbit(square, 0.33, 20, [])
y2 = generate_orbit(square, 0.1, 20, [])
y3 = generate_orbit(square, 0.9, 20, [])
fig = plt.figure(figsize = (20,4))

_ = plot.plot_orbit([y0,y1,y2,y3], "Orbit square", "Steps", "Values")
```

## Fixed points

It is the initial value where the following equation holds:

$$f(x) = x$$

The fixed point of the ```square_function``` ($x^2$) is 0 and 1


```python
fig, (ax1,ax2) = plt.subplots(ncols=2,figsize = (20,6))

y0 = generate_orbit(square, 0.5, 20, [])
y1 = generate_orbit(square, 0.33, 20, [])
y2 = generate_orbit(square, 0.1, 20, [])
y3 = generate_orbit(square, 0.9, 20, [])

plot.plot_orbit([y0,y1,y2,y3], "Orbit square", "Steps", "Values", ax1)
plot.plot_func(square, (-2,2), "square function", "x", "y", ax2)

plt.show()
```

## Butterfly effect


```python
def logistc_equation(r, x):
    return r*x*(1-x)
```


```python
logistc_equation_4 = partial(logistc_equation, 3)

y0 = generate_orbit(logistc_equation_4, 0.4, 200, [])

fig = plt.figure(figsize = (20,6))
_=plot.plot_orbit([y0], "Orbit logistc_equation_4", "Steps", "Values")

plt.show()

```


```python
logistc_equation_4 = partial(logistc_equation, 4)

y0 = generate_orbit(logistc_equation_4, 0.8, 100, [])
y1 = generate_orbit(logistc_equation_4, 0.80001, 100, [])

fig = plt.figure(figsize = (20,6))
_=plot.plot_orbit([y0,y1], "Orbit logistc_equation_4", "Steps", "Values")

plt.show()
```


```python
fig, (ax1,ax2) = plt.subplots(2,1,figsize = (20,12))

y0 = generate_orbit(logistc_equation_4, 0.8, 100, [])
y1 = generate_orbit(logistc_equation_4, 0.80001, 100, [])
y2 = generate_orbit(logistc_equation_4, 0.80000000001, 100, [])

_=plot.plot_orbit([y0,y1], "Orbit", "Steps", "Values", ax1)
#_=plot.plot_orbit([y0,y2], "Orbit", "Steps", "Values", ax2)
_=plot.plot_orbit([list(np.array(y2) - np.array(y0))], "Orbit", "Steps", "Y1 - Y0", ax2)

plt.show()
```

## Diagram 


```python
def logistc_equation(r, x):
    return r*x*(1-x)
```


```python
fig = plt.figure(figsize = (20,6))

labels = ("Bifurcation Diagram - logistic", r"$r$ values", "Final States")
r_periodic_change, logistic_lines = plot.plot_bifurcation_diagram(logistc_equation, 0.4, 5000, 0.01, 3, (0,4), labels)

plt.show()
```


```python
fig = plt.figure(figsize = (20,6))

labels = ("Bifurcation Diagram", r"$r$ values", "Final States")
r_periodic_change, _ = plot.plot_bifurcation_diagram(logistc_equation, 0.4, 10000, 0.01, 4, (3,4), labels)

plt.show()
```


```python
delta1 = r_periodic_change[1] - r_periodic_change[0]
delta2 = r_periodic_change[2] - r_periodic_change[1]
```


```python
delta1/delta2
```




    4.5




```python
r_periodic_change
```




    [3.0,
     3.4499999999999904,
     3.5499999999999883,
     3.569999999999988,
     3.5799999999999876,
     3.609999999999987]



### Different functions 


```python
def cubic(r, x):
    return r*(x**2)*(1-x)

def sinu(r, x):
    return r*np.sin(np.pi*x/2)
```


```python
fig = plt.figure(figsize = (20,6))

labels = ("Bifurcation Diagram - cubic", r"$r$ values", "Final States")
_, _ = plot.plot_bifurcation_diagram(cubic, 0.4, 5000, 0.01, 3, (4.5, 6.5), labels)

plt.show()
```


```python
fig = plt.figure(figsize = (20,6))

labels = ("Bifurcation Diagram - sin", r"$r$ values", "Final States")
_, _ = plot.plot_bifurcation_diagram(sinu, 0.4, 5000, 0.01, 3, (0,2), labels)

plt.show()
```


```python
d1 = r_periodic_change[1] - r_periodic_change[0]
d2 = r_periodic_change[2] - r_periodic_change[1]
d3 = r_periodic_change[3] - r_periodic_change[2]
```


```python
r_periodic_change
```




    [3.0,
     3.4499999999999904,
     3.5499999999999883,
     3.569999999999988,
     3.5799999999999876,
     3.609999999999987]




```python
d1/d2
```




    4.5




```python
d2/d3
```




    5.0



$$
\begin{align}
\Delta_1 &= r2 - r1 \approx 3.45 - 3       = 0.45  \\
\Delta_2 &= r3 - r2 \approx 3.544 - 3.45  = 0.094 \\  
\Delta_3 &= r3 - r2 \approx 3.5644 - 3.544 = 0.0204 \\
\end{align}
$$

$$
\begin{align}
\delta_n &= \frac{\Delta_n}{\Delta_{n+1}}\\\\
\delta_1 &= \frac{\Delta_1}{\Delta_2} = \frac{0.45}{0.094} \approx 4.787\\\\
\delta_2 &= \frac{\Delta_2}{\Delta_3} = \frac{0.094}{0.0204} \approx 4.608\\\\
\end{align}
$$

$$
\delta_n \rightarrow 4.66920160 ... \text{as n gets large}
$$

This number is now known as **Feigenbaum's constant** , after the mathematical physicist Mitchell Feigenbaum, who discovered the number in
the late 1970s.

## References 

**Book:**

- Chaos and fractals An elementary introduction - by David P. Feldman 

b8b787cb9b4a7b59b05de5acba2832508ab551c8
ipynb
Jupyter Notebook
caos_introduction.ipynb
rocabrera/caos_fractals
ca6d7658a3a3748dce5f1fb1debdc0cd1d59a52b
MIT
caos_introduction.ipynb
rocabrera/caos_fractals
ca6d7658a3a3748dce5f1fb1debdc0cd1d59a52b
MIT
caos_introduction.ipynb
rocabrera/caos_fractals
ca6d7658a3a3748dce5f1fb1debdc0cd1d59a52b
MIT
Qwen/Qwen-72B
1. YES
2. YES