```python
import numpy as np
import matplotlib.pyplot as plt
import plotutils as pu
import sympy
%matplotlib inline
```

# noise

Our goal is to produce noise in $n$ dimensions where $1 \le n$. In practical terms we'll not be able to produce noise in infinite dimensions but at least we will consider our algorithms as such we might someday. For this to work the input to our noise function will have to be a vector that describes a point in abstract vector space. We will call this point $x$ from now on. Formally, it is a vector of $n$ components $(A_1, A_2, \ldots, A_n)$ where $A_n \in \mathbb{R}$ and $-1 \le A_n \le 1$.

So we could write $f(\vec{x}) = y$. Note that $x$ here is not some point along the x-axis. It's a vector of $n$ components that specifies some point in abstract n-dimensional vector space. However, when we are dealing with one-dimensional noise this *will* be a point along the x-axis. 

Also, we will add that we really would like $y$ to be in the range of $-1 \le y \le 1$. It's not a *strict* requirement for the noise function $f$ but it would be perfect. Another thing that's important is that our noise function *cannot* be random. If we feed it some point $p$ we should expect the outcome $y$ to be the same *every* time.

What we are looking for is sometimes known as *value noise* and it's basically a seemlingly random value $y$ mapped to some point $p$ in n-dimensional space. 

# value noise in 1-d
We'll start with a function that operates on *integer* $\mathbb{Z}$ coordinates first but we'll quickly look at expanding our range to *real* $\mathbb{R}$ coordinates as well. For now, we'll start with a function $f_0(\vec{p}) = n$ where $\vec{p}$ is a scalar (a vector with one component, so basically just a number) and we'll shoot for $-1 \le n \le 1$.


```python
# Our `wave' function just an array of numbers. The length is
# important though - it will determine the period or `wavelength` of
# our noise function.
R = np.random.ranf(8) * 2 - 1

# We'll use a cheap modulo to place `p' in our vector space.
f_0 = lambda p: R[p % len(R)]

# With n steps we'll have n/len(R) periods of noise.
x = np.arange(0, 32, 1)

with plt.xkcd():
    plt.xlim(0, 32)
    plt.plot(x, f_0(x), 'c^')
    plt.plot([8, 8], [-1, 1], 'r--')
    plt.plot([16, 16], [-1, 1], 'r--')
    plt.plot([24, 24], [-1, 1], 'r--')
    arrowprops = {
        'head_length': 1.4, 
        'head_width': 0.12, 
        'length_includes_head': True, 
        'fc': 'b', 
        'ec': 'b'
    }
    plt.arrow(0, -0.6, 8, 0, **arrowprops)
    plt.annotate('(len(R), 0)', (1.15, -0.5))
```

A few things to notice here. First we setup this pretty important table `R` which contains a random selection of numbers. We use `numpy.random.ranf` which gives us a random number between $0$ and $1$. In order to get it into our desired range of $-1 \le y \le 1$ we multiply it by two and substract one. Multiplying (or scaling it) by two will give us a number in the range of $0 \le y \le 2$ so substracting one from that will get us right in the desired range $0 -1 \le y \le 2 - 1$ which ends up being $-1 \le y \le 1$. 

> #### scaling output
When using `numpy.random.ranf` we get values of $y$ where $0 \le y \le 1$ but we want values where $-1 \le y \le 1$. In order to scale it up we need something that looks like $y = 2x - 1$. We van verify it by checking it. If $x = 0$ we get $y = -1$ and if $x = 2$ we get $y = 1$. Which is exactly what we want. 

> Another way to look at this is as just a *scale* and *translate* operation where we scale something by two and then translate it by one in the oposite direction. In order to get it aligned with the origin again. This also explains why we have `R = np.random.ranf(8) * 2 - 1` in the code above.

We'll need to formalize this a bit more. For one thing, we know that `R` is just some values. In the example above we used eight values so we got a *period* (or *wavelength*) of 8. This is `len(R)`. If we increase the size of `R` we get a bigger period of noise. Of course we're just using integers as input so in our plot we'll have to do with points and not lines.

Let's summarize and consider that $R_n$ is a vector with $n$ components $(A_1, A_2, \ldots, A_n)$ where: 
* $0 \lt n$ and $n \in \mathbb{N}$
* $A_1, A_2, \ldots, A_n \in \mathbb{R}$ 
* $-1 \le A_n \le 1$. 

Now we have: $f_0(p) = R_{p \pmod q} = y$ where `q = len(R)`. 

And this (or is intended to be) equivalent to `f_0 = lambda p: R[p % len(R)]` in the code above. Basically what we have is a table $n$ of $q$ values and when given some $x$ it'll give you some value $n_{x \pmod q}$. We use the $mod$ operation to make sure we are always in a valid range of $q$ with respect to the amount of components in $R$.

# finalizing noise
One thing we've struggled a bit is with the amount of compoents in $R$. Remember, $R$ is nothing more than just a bag of random numbers. Let's now try to formalize all the definitions we have so far. We'll start with $R$. Which is actually pretty hard.


```python

```

73b246a56e74a9c8817cdc23b05d699ab5929ff1
ipynb
Jupyter Notebook
noise_old.ipynb
basp/notes
8831f5f44fc675fbf1c3359a8743d2023312d5ca
MIT
2016-12-09T13:58:13.000Z
2016-12-09T13:58:13.000Z
noise_old.ipynb
basp/notes
8831f5f44fc675fbf1c3359a8743d2023312d5ca
MIT
noise_old.ipynb
basp/notes
8831f5f44fc675fbf1c3359a8743d2023312d5ca
MIT
Qwen/Qwen-72B
1. YES
2. YES