<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Numba Demo 1
Sum of first X integers
Given this simple function
Step1:  Then we define $sum_j(x)$ that is identical but just with decorator @jit in the definition.
Step2:  Lets benchmark them!
Lets define a benchmark to study perfromances of our implementations of $sum(x)$
Step3:  Benchmark results
Lets measure them
Step4:  Numba caching
A second run to study numba caching mechanism
Step5:  Numba JIT functionality works in the following way
Step6:  About Cython
Step7:  The numba jitted function is comparable with the cythonized one, lets check what was the C code cython used, just to give us an idea of the efficiency of the code generated.
<ASSISTANT_TASK:>
Python Code:
def sum_p(X):
    y = 0
    for x_i in range(int(X)):
        y += x_i
    return y
Explanation: Numba Demo 1
Sum of first X integers
Given this simple function:
$$sum(x) = \sum\limits_{x=0}^X x$$
Lets define $sum_p(x)$ in pure Python
End of explanation
from numba import jit
@jit
def sum_j(X):
    y = 0
    for x_i in range(int(X)):
        y += x_i
    return y
Explanation: Then we define $sum_j(x)$ that is identical but just with decorator @jit in the definition.
End of explanation
import os
import time
import pandas as pd
import matplotlib
%matplotlib inline
# Different platforms require different functions to properly measure current timestamp:
if os.name == 'nt':
    now = time.clock
else:
    now = time.time
def run_benchmarks(functions, call_parameters, num_times,
                   logy=False, logx=False):
    
    # Executes one function several times and measure performances:
    def _apply_function(function, num_times):
        for j in range(num_times):
            t_0 = now()
            y = function(*call_parameters)
            duration = (now() - t_0)
            yield float(duration)
    
    def _name(function):
        return '${' + function.__name__ + '(x)}$'
    
    # Execute all functions the requested number of times and collects durations:
    def _apply_functions(functions, num_times):
        for function in functions:
            yield pd.Series(_apply_function(function, num_times),
                            name=_name(function))
            
            
    # Collects and plots the results:
    df = pd.concat(_apply_functions(functions, num_times),
                   axis=1)
    ax = df.plot(figsize=(10,5),
                 logy=logy,
                 logx=logx,
                 title='$T[f(x)]$ in seconds',
                 style='o-')
Explanation: Lets benchmark them!
Lets define a benchmark to study perfromances of our implementations of $sum(x)$:
End of explanation
run_benchmarks(functions=[sum_p, sum_j],
               call_parameters=(10000000,),
               num_times=5,
               logy=True) # Logarithmic scale
Explanation: Benchmark results
Lets measure them:
End of explanation
run_benchmarks(functions=[sum_j],
               call_parameters=(1000000000000000.,),
               num_times=5,
               logy=True) # Logarithmic scale
Explanation: Numba caching
A second run to study numba caching mechanism
End of explanation
from numba import jit
@jit
def sum_j(x):
    y = 0.
    x_i = 0.
    while x_i < x:
        y += x_i
        x_i += 1.
    return y
%load_ext Cython
%%cython
def sum_c(double x):
    cdef double y = 0.
    cdef double x_i = 0.
    while x_i < x:
        y += x_i
        x_i += 1.
    return y
Explanation: Numba JIT functionality works in the following way:
- At each call of a function $f(x)$, numba looks at the type $T$ of $x$.
- If it is the first time that type have been used generates a native implementation $f_T(x)$.
- If it is not  the first time that type have been used generates fetches the native implementation from a cache.
- Numba executes $f_T(x)$ that is orders of magnitudes faster than a pure Python implementation.
And what about adding Cython to the game?
Lets define the same functon, but tuned to operate with floats:
$$sum(x) = \sum\limits_{x=0}^X x$$
We redefine it using numba and cython, this time using float numbers.
End of explanation
run_benchmarks(functions=[sum_j, sum_c],
               call_parameters=(1000000000.,),
               num_times=10)
Explanation: About Cython:
- generates C code from a Python code.
- allows to define low level C-types.
- in this example we use C-type double.
- C code is generated, compiled and executed.
Benchmarks JIT vs Cython
End of explanation
%%cython --annotate
def sum_c(double x):
    cdef double y = 0.
    cdef double x_i = 0.
    while x_i < x:
        y += x_i
        x_i += 1.
    return y
Explanation: The numba jitted function is comparable with the cythonized one, lets check what was the C code cython used, just to give us an idea of the efficiency of the code generated.
End of explanation
<END_TASK>