import numpy as np
import pdb

from scipy.integrate import cumtrapz

__author__ = 'Penelope Maher'

'''
   See text book: P141-142 of global physical climatology by Hartmann

   [v] = (g/(2 pi a cos (lat))) (partial Phi/ partial p)
   [omega] = (-g/(2 pi a^2 cos (lat))) (partial Phi/ partial lat)

   integrate [v] wrt pressure

   Psi = ( (2 pi a cos(lat)) /g) integrate [v] dp

   and [omega] wrt lat

   Psi = ( (-2 pi a^2 cos(lat)) /g) integrate [omega] dlat

   Psi units -> Pa m s = kg/(ms^2) * ms = kg /s
                and taking into account the scale factor kg/s 1x10^10
'''

def mask_array(data_shape, fill_value):
    #create a new array without missing values but is masked
    data = np.zeros(data_shape)
    mask = np.zeros(data_shape) #all false equivalent
    data = np.ma.masked_array(data, mask,  fill_value=fill_value)
    return data

def set_fill_value(data, fill_value):
    #take a masked array, update the fill value and return a masked array

    mask  = data.mask.copy()

    #replace the data.masked value with the specificied fill_value
    data = data.filled(fill_value=fill_value) #returns an unmasked arrary
    data_out = np.ma.masked_array(data, mask, fill_value=fill_value)

    return data_out

class MeridionalStreamFunction():

    '''Calculates the mass stream function.
           Data is a dictionary with masked zonal-mean arrays of omega and vcomp.
           Pressure can be in hPa or Pa.

           Return: masked array with units x10^10 kg/s (i.e. it is scaled)

           Why compute the Riemann sums when you can use cumtrapz directly?
               Cumtrapz works great for unmasked arrays. I used it to validate
               the code. But for masked arrays, it ignores the mask which can
               make for problematic stream functions.

           Note: in other peoples code (e.g. TrapD by Ori Admas and in Martin Juckers
               (https://github.com/mjucker/aostools/blob/master/climate.py),
               psi is only computed using vcomp and not both the vcomp and omega
               components in this code.
    '''
    #for more information on riemann sums, see for example
    #https://www.math.ubc.ca/~pwalls/math-python/integration/riemann-sums/

    def __init__(self, pres_var_name, testing=False, name_flag=None):

        #constants
        self.grav=9.81 #m/s
        self.rad_earth=6.371e6 #m

        #scale factor for outputting psi
        self.psi_scale_factor = 1e10

        #the data's pressure name
        self.pres_var_name = pres_var_name

        #testing flag is for debugging
        self.testing = testing
        #syntax end of filename for plots during debugging
        self.name_flag = name_flag

        #data to be assigned during the script
        self.pres     = None
        self.lat      = None
        self.lat_rad  = None
        self.omega_zm = None
        self.vcomp_zm = None

        #assigned during method
        self.input_dtype = None
        self.fill_value  = None

        #was the data reordered?
        self.flip_lat = None
        self.flip_pres = None

    def data_properties(self, data):

        #data properties
        self.input_dtype =  data['omega'].dtype
        self.fill_value = data['omega'].fill_value

        #check data
        assert isinstance(data['omega'],np.ma.core.MaskedArray)  == True , 'Data is not masked'
        assert len(data['omega'].shape)  == 3, 'Data shape is not 3D, investigate'

    def data_ordering(self, data):

        #if SH pole is not the first element, then reverse direction
        if np.where(data['lat'] == data['lat'].min())[0] !=0:
            self.lat      = data['lat'][::-1].copy()
            self.omega_zm = data['omega'][:,:,::-1].copy()
            self.vcomp_zm = data['vcomp'][:,:,::-1].copy()
            self.flip_lat = True
        else:
            self.lat = data['lat'].copy()
            self.omega_zm = data['omega'].copy()
            self.vcomp_zm = data['vcomp'].copy()
            self.flip_lat = False

        #test if data is in Pa or hPa - multiply data by 100 if in hPa
        if data[self.pres_var_name].max() > 1100:
            pres_factor = 1.
        else:
            pres_factor = 100.

        # the pressure integral is from surface up.
        if np.where(data[self.pres_var_name] == data[self.pres_var_name].min())[0] ==0:
            self.pres = data[self.pres_var_name][::-1]*pres_factor
            self.omega_zm = self.omega_zm[:,::-1,:].copy()
            self.vcomp_zm = self.vcomp_zm[:,::-1,:].copy()
            self.flip_pres = True
        else:
            self.pres  = data[self.pres_var_name]*pres_factor
            self.flip_pres = False

    def revert_data_ordering(self, data, psi):

        #if lat or pressure was reversed, then put back to original form
        if self.flip_lat:
            psi = psi[:,:,::-1].copy()

        if self.flip_pres:
            psi = psi[:,::-1,:].copy()

        return psi

    def set_fill_value_zero(self):
        # note this is only valid for surface missing values and not
        # missing values in obs data for eg.

        #note that this method assigns a np array that is NOT masked
        self.omega_zm = set_fill_value(self.omega_zm, 0.0)
        self.vcomp_zm = set_fill_value(self.vcomp_zm, 0.0)

    def cumtrap_method_lat(self):

        #compute using scipy generated integration as a sanity check
        trap_lat  = cumtrapz( np.cos(self.lat_rad) * self.omega_zm.data,
                              x=self.lat_rad, axis=-1, initial=0)

        return trap_lat

    def cumtrap_method_pres(self):

        #compute using scipy generated integration
        trap_pres  = cumtrapz(self.vcomp_zm.data, x=self.pres, axis=1, initial=0)

        return trap_pres

    def riemann_sum_lat(self):
        #compute the left and right riemann sums of psi_lat

        #new masked data arrays
        left_riemann_sum_lat   = mask_array(self.omega_zm.shape, self.fill_value)
        right_riemann_sum_lat  = mask_array(self.omega_zm.shape, self.fill_value)

        #start the loop at 1 (not 0) and integrate cos(lat) x omega from 90S to 90N
        for count in range(1, len(self.lat_rad)):
            #when integrating, use omega as unmasked data, else strips will be removed
            #from the resulting integral. Instead the missing values have been set to
            #zero and when it is integrated it will not contriute to the integral.

            #get the integrtaions step - same value used for left and right sums
            dlat = self.lat_rad[count] - self.lat_rad[count-1]  #dlat > 0

            #compute the left riemann sum
            left_riemann_sum_lat[:,:,count]  = (left_riemann_sum_lat[:,:,count-1] +
                np.cos(self.lat_rad[count]) * self.omega_zm[:,:,count-1].data * dlat)

            #keep track of the data mask
            left_riemann_sum_lat[:,:,count].mask = self.omega_zm[:,:,count-1].mask

            #compute the right riemann sum
            right_riemann_sum_lat[:,:,count] = (right_riemann_sum_lat[:,:,count-1] +
                np.cos(self.lat_rad[count]) * self.omega_zm[:,:,count].data * dlat)

            #keep track of the data mask
            right_riemann_sum_lat[:,:,count].mask = self.omega_zm[:,:,count].mask

        return left_riemann_sum_lat, right_riemann_sum_lat

    def riemann_sum_pres(self):
        #compute the left and right riemann sums of psi_pres

        left_riemann_sum_pres  = mask_array(self.vcomp_zm.shape, self.fill_value)
        right_riemann_sum_pres = mask_array(self.vcomp_zm.shape, self.fill_value)

        for count in range(1,len(self.pres)):

            dp = (self.pres[count] - self.pres[count-1])  #dp<0

            #compute the left riemann sum
            left_riemann_sum_pres[:,count,:] = (left_riemann_sum_pres[:,count-1,:]+
                                               self.vcomp_zm[:,count-1,:].data * dp )

            #keep track of the data mask
            left_riemann_sum_pres[:,:,count].mask = self.vcomp_zm[:,:,count-1].mask

            #compute the left riemann sum
            right_riemann_sum_pres[:,count,:] = (right_riemann_sum_pres[:,count-1,:]+
                                                self.vcomp_zm[:,count,:].data * dp )

            #keep track of the data mask
            right_riemann_sum_pres[:,:,count].mask = self.vcomp_zm[:,:,count].mask

        return left_riemann_sum_pres, right_riemann_sum_pres

    def avg_masked_array(self, array1, array2):
        #inputs are two masked arrays of the same shape

        assert array1.shape == array2.shape, 'Input data is different shapes.'

        #create a new masked array with dim [2, time, pre, lat]
        new_array = np.ma.array((array1, array2))
        #take the mean - if one array is masked, the value is the other array
        array_avg  = np.ma.mean(new_array, axis=0)

        return array_avg

    def apply_psi_scaling(self, riemann_lat_avg, riemann_pres_avg):

        psi_lat  = (-2.*np.pi*self.rad_earth*self.rad_earth)/self.grav * riemann_lat_avg

        pres_const = ((2.*np.pi*self.rad_earth*np.cos(self.lat_rad))/self.grav)
        psi_pres = pres_const[np.newaxis,np.newaxis,:] * riemann_pres_avg

        return psi_lat/self.psi_scale_factor, psi_pres/self.psi_scale_factor

    def cal_stream_fn_riemann(self, data, plot_result=True):

        '''This part of the code does all the heavy lifting'''

        #check data and get dtype and fill value
        self.data_properties(data)

        #ensure data is from SH to NH, surface to TOA and pres in Pa
        self.data_ordering(data)

        #degree to radians
        self.lat_rad=np.radians(self.lat, dtype=self.input_dtype)

        #Set the fill value to zero, else the surface masking will block out
        #strips of the integration
        self.set_fill_value_zero()

        #Do the integration using the inbuild function cumtrap
        # this is for a visual check for debugging the riemann sums
        # the cumtrap function is a black box that does not handle masked data
        # where the left and right sums I know exactly how it is computed
        trap_lat = self.cumtrap_method_lat()
        trap_pres = self.cumtrap_method_pres()

        #Do the integration with the left and right riemann sums
        left_riemann_sum_lat, right_riemann_sum_lat = self.riemann_sum_lat()
        left_riemann_sum_pres, right_riemann_sum_pres = self.riemann_sum_pres()

        #get the average of the left and right sums
        riemann_lat_avg = self.avg_masked_array(left_riemann_sum_lat, right_riemann_sum_lat)
        riemann_pres_avg = self.avg_masked_array(left_riemann_sum_pres, right_riemann_sum_pres)

        #Apply the scaling for the psi intergrals
        psi_lat, psi_pres = self.apply_psi_scaling(riemann_lat_avg, riemann_pres_avg)

        #take the avergae of the two psi functions
        psi = self.avg_masked_array(psi_lat, psi_pres)

        TS = TestingStreamfunction(self.omega_zm, self.vcomp_zm,
                                   self.pres, self.lat, self.lat_rad, self.name_flag)

        if self.testing:
            #make extra plots if testing code
            TS.testing_riemann_sum('lat', right_riemann_sum_lat, left_riemann_sum_lat,
                                   trap_lat, riemann_lat_avg)

            TS.testing_riemann_sum('pres', right_riemann_sum_pres, left_riemann_sum_pres,
                                   trap_pres, riemann_pres_avg)

        if plot_result:
            #always plot the psi values as a visual check the code is working
            TS.testing_psi(psi_lat, psi_pres, psi)

        #reverse the pressure back ot original coordinates if needed
        psi = self.revert_data_ordering(data, psi)

        return psi

class TestingStreamfunction():

    def __init__(self, omega_zm, vcomp_zm, pres, lat, lat_rad, name_flag):

        self.omega_zm = omega_zm
        self.vcomp_zm = vcomp_zm
        self.pres     = pres
        self.lat      = lat
        self.lat_rad  = lat_rad

        self.name_flag = name_flag

    def testing_riemann_sum(self, test_type, right_riemann_sum, left_riemann_sum,
                            trap_val, riemann_avg, time_mean=True):

        print('Testing riemann sums for ', test_type)

        data_range = np.arange(-1,1.1,0.25)

        if test_type == 'lat':
            var_flag = 'omega'
            data_factor = 10
            plot_scale_factor = 1e2
            data = self.omega_zm
        else:
            var_flag = 'vcomp'
            data_factor = 1
            plot_scale_factor = 2e-5
            data = self.vcomp_zm

        self.contour_plot(data*data_factor, data_range, var_flag, time_mean)

        self.contour_plot(right_riemann_sum*plot_scale_factor, data_range, 'right_'+ test_type, time_mean)

        self.contour_plot(left_riemann_sum*plot_scale_factor, data_range, 'left_'+ test_type, time_mean)

        self.contour_plot(riemann_avg*plot_scale_factor, data_range, 'avg_'+ test_type, time_mean)

        self.contour_plot(trap_val*plot_scale_factor, data_range, 'cumtrapz_'+ test_type, time_mean)

    def testing_psi(self, psi_lat, psi_pres, psi, time_mean=True):

        scale_factor = 1
        bounds = np.arange(-20.,20.1,5)
        self.contour_plot(psi_lat*scale_factor,  bounds, 'psi_lat',time_mean)
        self.contour_plot(psi_pres*scale_factor, bounds, 'psi_pres',time_mean)
        self.contour_plot(psi*scale_factor, bounds, 'psi',time_mean)

    def contour_plot(self, data, bounds, name, time_mean):

        #incoming pressure is in Pa

        import matplotlib.pyplot as plt
        import matplotlib as mpl

        cmap = plt.get_cmap('RdBu_r')

        norm = mpl.colors.BoundaryNorm(bounds, cmap.N)

        if time_mean:
            time_loop_len = 1
            start_loop = 1
        else:
            #sorry this is hard coded - I will change it in the future.
            time_loop_len = 120+12#len(data.shape[0])
            start_loop = 120

        for count in range(start_loop, time_loop_len):
            print('Time is: ', count)
            if time_mean:
                data_plot = np.ma.mean(data, axis=0)
                filename='testing_psi_{0}.eps'.format(name, self.name_flag)
            else:
                data_plot = data[count,...].copy()

            fig = plt.figure(figsize=(8, 8))
            ax = fig.add_axes([0.15, 0.15, 0.8, 0.8])

            filled_map = ax.pcolormesh(self.lat, self.pres/100., data_plot,
                                       cmap=cmap,norm=norm, shading='nearest')
            ax.set_ylim(1000,100)
            ax_cb=fig.add_axes([0.15, 0.05, 0.8, 0.03]) #[xloc, yloc, width, height]
            cbar = mpl.colorbar.ColorbarBase(ax_cb, cmap=cmap,norm=norm, ticks=bounds,
                                             orientation='horizontal',format='%.1f',
                                             extend='both')
            if time_mean:
                plt.savefig(filename)
            plt.show()


import math
import numpy as np
import tensorflow as tf

####################################
# Problem Class A. Single equations.
####################################
def A1():
    diffeq = lambda t, y: -y
    init = lambda: (tf.convert_to_tensor(0., dtype=tf.float64), tf.convert_to_tensor(1., dtype=tf.float64))
    solution = lambda t: tf.exp(-t)
    return diffeq, init, solution

def A2():
    diffeq = lambda t, y: -y**3 / 2
    init = lambda: (tf.convert_to_tensor(0., dtype=tf.float64), tf.convert_to_tensor(1., dtype=tf.float64))
    solution = lambda t: 1 / tf.sqrt(t + 1)
    return diffeq, init, solution

def A3():
    diffeq = lambda t, y: y * tf.cos(t)
    init = lambda: (tf.convert_to_tensor(0., dtype=tf.float64), tf.convert_to_tensor(1., dtype=tf.float64))
    solution = lambda t: tf.exp(tf.sin(t))
    return diffeq, init, solution

def A4():
    diffeq = lambda t, y: y / 4 * (1 - y / 20)
    init = lambda: (tf.convert_to_tensor(0., dtype=tf.float64), tf.convert_to_tensor(1., dtype=tf.float64))
    solution = lambda t: 20 / (1 + 19 * tf.exp(-t / 4))
    return diffeq, init, solution

def A5():
    diffeq = lambda t, y: (y - t) / (y + t)
    init = lambda: (tf.convert_to_tensor(0., dtype=tf.float64), tf.convert_to_tensor(4., dtype=tf.float64))
    return diffeq, init, None

#################################
# Problem Class B. Small systems.
#################################
def B1():

    def diffeq(t, y):
        dy0 = 2 * (y[0] - y[0] * y[1])
        dy1 = -(y[1] - y[0] * y[1])
        return tf.stack([dy0, dy1])

    def init():
        return tf.convert_to_tensor(0., dtype=tf.float64), tf.convert_to_tensor([1., 3.], dtype=tf.float64)

    return diffeq, init, None

def B2():

    A = tf.convert_to_tensor([[-1., 1., 0.], [1., -2., 1.], [0., 1., -1.]], dtype=tf.float64)

    def diffeq(t, y):
        dy = _matrix_vector(A, y)
        return dy

    def init():
        return tf.convert_to_tensor(0., dtype=tf.float64), tf.convert_to_tensor([2., 0., 1.], dtype=tf.float64)

    return diffeq, init, None

def B3():

    def diffeq(t, y):
        dy0 = -y[0]
        dy1 = y[0] - y[1] * y[1]
        dy2 = y[1] * y[1]
        return tf.stack([dy0, dy1, dy2])

    def init():
        return tf.convert_to_tensor(0., dtype=tf.float64), tf.convert_to_tensor([1., 0., 0.], dtype=tf.float64)

    return diffeq, init, None

def B4():

    def diffeq(t, y):
        a = tf.sqrt(y[0] * y[0] + y[1] * y[1])
        dy0 = -y[1] - y[0] * y[2] / a
        dy1 = y[0] - y[1] * y[2] / a
        dy2 = y[0] / a
        return tf.stack([dy0, dy1, dy2])

    def init():
        return tf.convert_to_tensor(0., dtype=tf.float64), tf.convert_to_tensor([3., 0., 0.], dtype=tf.float64)

    return diffeq, init, None

def B5():

    def diffeq(t, y):
        dy0 = y[1] * y[2]
        dy1 = -y[0] * y[2]
        dy2 = -0.51 * y[0] * y[1]
        return tf.stack([dy0, dy1, dy2])

    def init():
        return tf.convert_to_tensor(0., dtype=tf.float64), tf.convert_to_tensor([0., 1., 1.], dtype=tf.float64)

    return diffeq, init, None

####################################
# Problem Class C. Moderate systems.
####################################
def C1():

    A = np.zeros([10, 10])
    A = A.flatten()
    A[:-1:11] = -1.
    A[10::11] = 1.
    A = A.reshape([10, 10])

    A = tf.convert_to_tensor(A, dtype=tf.float64)

    def diffeq(t, y):
        return _matrix_vector(A, y)

    def init():
        y0 = np.zeros(10)
        y0[0] = 1
        return tf.convert_to_tensor(0., dtype=tf.float64), tf.convert_to_tensor(y0, dtype=tf.float64)

    return diffeq, init, None

def C2():

    A = np.zeros([10, 10])
    A = A.flatten()
    A[:-1:11] = np.linspace(-1., -9., 9)
    A[10::11] = np.linspace(1., 9., 9)
    A = A.reshape([10, 10])

    A = tf.convert_to_tensor(A, dtype=tf.float64)

    def diffeq(t, y):
        return _matrix_vector(A, y)

    def init():
        y0 = np.zeros(10)
        y0[0] = 1
        return tf.convert_to_tensor(0., dtype=tf.float64), tf.convert_to_tensor(y0, dtype=tf.float64)

    return diffeq, init, None

def C3():
    n = 10
    A = np.zeros([n, n])
    A = A.flatten()
    A[::n + 1] = -2.
    A[n::n + 1] = 1.
    A[1::n + 1] = 1.
    A = A.reshape([n, n])

    A = tf.convert_to_tensor(A, dtype=tf.float64)

    def diffeq(t, y):
        return _matrix_vector(A, y)

    def init():
        y0 = np.zeros(n)
        y0[0] = 1
        return tf.convert_to_tensor(0., dtype=tf.float64), tf.convert_to_tensor(y0, dtype=tf.float64)

    return diffeq, init, None

def C4():
    n = 51
    A = np.zeros([n, n])
    A = A.flatten()
    A[::n + 1] = -2.
    A[n::n + 1] = 1.
    A[1::n + 1] = 1.
    A = A.reshape([n, n])

    A = tf.convert_to_tensor(A, dtype=tf.float64)

    def diffeq(t, y):
        return _matrix_vector(A, y)

    def init():
        y0 = np.zeros(n)
        y0[0] = 1
        return tf.convert_to_tensor(0., dtype=tf.float64), tf.convert_to_tensor(y0, dtype=tf.float64)

    return diffeq, init, None

def C5():

    k2 = tf.convert_to_tensor(2.95912208286, dtype=tf.float64)
    m0 = tf.convert_to_tensor(1.00000597682, dtype=tf.float64)
    m = tf.convert_to_tensor([
        0.000954786104043,
        0.000285583733151,
        0.0000437273164546,
        0.0000517759138449,
        0.00000277777777778,
    ], dtype=tf.float64)
    m = tf.reshape(m, [1, 5])

    def diffeq(t, y):
        # y is 2 x 3 x 5
        # y[0] contains y, y[0] contains y'
        # second axis indexes space (x,y,z).
        # third axis indexes 5 bodies.
        dy = y[1, :, :]
        y = y[0]
        r = tf.reshape(tf.sqrt(tf.reduce_sum(y ** 2, axis=0)), (1, 5))
        d = tf.sqrt(tf.reduce_sum((y[:, :, None] - y[:, None, :]) ** 2, 0))
        F = tf.reshape(m, (1, 1, 5)) * ((y[:, None, :] - y[:, :, None]) / tf.reshape(d * d * d, (1, 5, 5)) + tf.reshape(y, (3, 1, 5)) /
                               tf.reshape(r * r * r, (1, 1, 5)))
        # F.view(3, 5 * 5)[:, ::6] = 0
        F = tf.reshape(F, (3, 5 * 5))
        F = F.numpy()
        F[:, ::6] = 0
        F = F.reshape((3, 5, 5))
        F = tf.convert_to_tensor(F, dtype=tf.float64)

        ddy = k2 * (-(m0 + m) * y / (r * r * r)) + tf.reduce_sum(F, 2)

        return tf.stack([dy, ddy], 0)

    def init():
        y0 = tf.convert_to_tensor([
            3.42947415189, 3.35386959711, 1.35494901715, 6.64145542550, 5.97156957878, 2.18231499728, 11.2630437207,
            14.6952576794, 6.27960525067, -30.1552268759, 165699966404, 1.43785752721, -21.1238353380, 28.4465098142,
            15.388265967
        ], dtype=tf.float64)
        y0 = tf.transpose(tf.reshape(y0, shape=[5, 3]), perm=[1, 0])

        dy0 = tf.convert_to_tensor([
            -.557160570446, .505696783289, .230578543901, -.415570776342, .365682722812, .169143213293, -.325325669158,
            .189706021964, .0877265322780, -.0240476254170, -.287659532608, -.117219543175, -.176860753121,
            -.216393453025, -.0148647893090
        ], dtype=tf.float64)
        dy0 = tf.transpose(tf.reshape(dy0, [5, 3]), perm=[1, 0])

        return tf.convert_to_tensor(0., dtype=tf.float64), tf.stack([y0, dy0], 0)

    return diffeq, init, None

###################################
# Problem Class D. Orbit equations.
###################################
def _DTemplate(eps):

    def diffeq(t, y):
        r = (y[0]**2 + y[1]**2)**(3 / 2)
        dy0 = y[2]
        dy1 = y[3]
        dy2 = -y[0] / r
        dy3 = -y[1] / r
        return tf.stack([dy0, dy1, dy2, dy3])

    def init():
        return tf.convert_to_tensor(0., dtype=tf.float64), tf.convert_to_tensor([1 - eps, 0, 0, math.sqrt((1 + eps) / (1 - eps))], dtype=tf.float64)

    return diffeq, init, None

D1 = lambda: _DTemplate(0.1)
D2 = lambda: _DTemplate(0.3)
D3 = lambda: _DTemplate(0.5)
D4 = lambda: _DTemplate(0.7)
D5 = lambda: _DTemplate(0.9)

##########################################
# Problem Class E. Higher order equations.
##########################################
def E1():

    def diffeq(t, y):
        dy0 = y[1]
        dy1 = -(y[1] / (t + 1) + (1 - 0.25 / (t + 1)**2) * y[0])
        return tf.stack([dy0, dy1])

    def init():
        return tf.convert_to_tensor(0., dtype=tf.float64), tf.convert_to_tensor([.671396707141803, .0954005144474744], dtype=tf.float64)

    return diffeq, init, None

def E2():

    def diffeq(t, y):
        dy0 = y[1]
        dy1 = (1 - y[0]**2) * y[1] - y[0]
        return tf.stack([dy0, dy1])

    def init():
        return tf.convert_to_tensor(0., dtype=tf.float64), tf.convert_to_tensor([2., 0.], dtype=tf.float64)

    return diffeq, init, None

def E3():

    def diffeq(t, y):
        dy0 = y[1]
        dy1 = y[0]**3 / 6 - y[0] + 2 * tf.sin(2.78535 * t)
        return tf.stack([dy0, dy1])

    def init():
        return tf.convert_to_tensor(0., dtype=tf.float64), tf.convert_to_tensor([0., 0.], dtype=tf.float64)

    return diffeq, init, None

def E4():

    def diffeq(t, y):
        dy0 = y[1]
        dy1 = .32 - .4 * y[1]**2
        return tf.stack([dy0, dy1])

    def init():
        return tf.convert_to_tensor(0., dtype=tf.float64), tf.convert_to_tensor([30., 0.], dtype=tf.float64)

    return diffeq, init, None

def E5():

    def diffeq(t, y):
        dy0 = y[1]
        dy1 = tf.sqrt(1 + y[1]**2) / (25 - t)
        return tf.stack([dy0, dy1])

    def init():
        return tf.convert_to_tensor(0., dtype=tf.float64), tf.convert_to_tensor([0., 0.], dtype=tf.float64)

    return diffeq, init, None

###################
# Helper functions.
###################
def _to_tensor(x):
    if not tf.is_numeric_tensor(x):
        x = tf.convert_to_tensor(x, dtype=tf.float64)
    return x

def _matrix_vector(A, y):
    A = tf.cast(A, tf.float64)
    y = tf.cast(y, tf.float64)

    y = tf.expand_dims(y, -1)
    z = tf.matmul(A, y)

    z = tf.squeeze(z)
    return z


import numpy as np
from sympy import *
import matplotlib.pyplot as plt
import time
import math

def actual_result(func, val):
    dfunc = diff(func,'x')
    x = val
    return eval(str(dfunc))

def two_point(func, x, h):
    funcx = eval(str(func))
    x = x + h
    funcxh = eval(str(func))
    return (funcxh - funcx) / h

def plot(Result2Point, Error2Point):
    plt.plot(Result2Point, 'k--', label="2 Point")
    plt.plot(Error2Point, 'k', label="2 Point Error")
    plt.legend = plt.legend(loc='upper center', shadow=True)
    plt.title('Two Point')
    plt.show()

def twoPoint(func, x, h):
    Result2Point = []
    Error2Point = []
    start = time.time()
    actualResult = actual_result(func, x)
    for item in h:
        Result2Point.append(two_point(func, x, item))

    Error2Point = np.asarray(Result2Point) - actualResult

    end = time.time()
    timeElapsed = end - start
    print("Elapsed time: %f" % timeElapsed)
    print(Result2Point)
    plot(Result2Point, Error2Point)

if __name__ == '__main__':

    twoPoint(sin('x'), 1.0, [0.1, 0.01, 0.001])


'''
 Demo code to illustrate how to solve an MDP with LP in Python.

 Resolution of Howard's taxicab MDP by linear programming.

 Code developed by Philippe Preux, Universite de Lille, France & Inria.
 Originally put online on September 23rd, 2020, on https://ph-preux.github.io/software/
  This version is of September 27th, 2020.

 This code has been developed and is provided to the community only to
 serve as a demonstrator. It might not work on your computer, it mght not
 meet your expectations. In any case, this code has not been made in order
 to cause any harm neither to anyone, nor to any computer, nor to anything.
 That being said, you use this code under your own responsability, and risks.

 This code is freely available under MIT licence.
'''

import numpy as np
import pulp

# definition of the MDP
N = 3 # nb of states
A = 3 # nb of actions
# the transition tensor: P [current state, action, next state] = probability of transiting to next state when emitting action in current state
P = np.array ([[[1/2, 1/4, 1/4],
                [1/16, 3/4, 3/16],
                [1/4, 1/8, 5/8]],
               [[1/2, 0, 1/2],
                [0,0,0],
                [1/16, 7/8, 1/16]],
               [[1/4, 1/4, 1/2],
                [1/8, 3/4, 1/8],
                [3/4, 1/16, 3/16]]])
# the return function: expected return when transiting to next state when emitting action in current state
R = np.array ([10, 4, 8, 8, 2, 4, 4, 6, 4, 14, 0, 18, 0, 0, 0, 8, 16, 8, 10, 2, 8, 6, 4, 2, 4, 0, 8])
R = R.reshape ([N, A, N])
gamma = .9
# in states 0 and 2, all 3 actions are possible. In state 1, only actions 0 and 2 are possibles:
possible_actions = np.array ([[0, 1, 2], [0, 2], [0, 1, 2]])

# define the LP
v = pulp.LpVariable.dicts ("s", (range (N))) # the variables
prob = pulp.LpProblem ("taxicab", pulp.LpMinimize) # minimize the objective function
prob += sum ([v [i] for i in range (N)]) # defines the objective function
# now, we define the constrain: there is one for each (state, action) pair.
for i in range (N):
    for a in possible_actions [i]:
        prob += v [i] - gamma * sum (P [i, a, j] * v [j] for j in range(N)) >= sum (P [i, a, j] * R [i, a, j] for j in range(N))

# Solve the LP
prob.solve ()
# after resolution, the status of the solution is available and can be printed:
# print("Status:", pulp.LpStatus[prob.status])
# in this case, it should print "Optimal"

# extract the value function
V = np.zeros (N) # value function
for i in range (N):
    V [i] = v [i]. varValue

# extract the optimal policy
pi_star = np.zeros ((N), dtype=np.int64)
vduales = np.zeros ((N, 3))
s = 0
a = 0
for name, c in list(prob.constraints.items()):
    vduales [s, a] = c.pi
    if a < A - 1:
        a = a + 1
    else:
        a = 0
        if s < N - 1:
            s = s + 1
        else:
            s = 0
for s in range(N):
    pi_star [s] = np.argmax (vduales [s, :])


import numpy as np
import matplotlib.pyplot as plt
import scipy.optimize as opt
from math import exp
import scipy.stats as st

np.random.seed(100872)

# forward function and Jacobian
f_fnc = lambda x, theta: theta[0]*(1 - np.exp(-theta[1]*x))

Jf_fnc = lambda x, theta: np.stack((1 - np.exp(-theta[1]*x), theta[0] * x * np.exp(-theta[1]*x)), axis=1)

# observation positions
xObs = np.array([0.3, 0.5, 1.0, 1.8, 3.3, 5.8])

# ground truth parameter
thetatruth = np.array([1.0, 0.3])

# forward function for fixed observation positions
f_fwd = lambda theta: f_fnc(xObs, theta)
Jf_fwd = lambda theta: Jf_fnc(xObs, theta)

# observational noise
sigma = 0.3

# generate data
y = f_fwd(thetatruth) + np.random.normal(0, sigma, xObs.shape)

# prior standard deviation (both parameters)
gamma = 0.8

############## Stuff for didactical purposes, not necessary for sampling:
# Plot of posterior negative logdensity
xs = np.linspace(0, 6, 300)

misfit = lambda theta: 1/(2*sigma**2)*np.dot((y-f_fwd(theta)).T, y-f_fwd(theta))

# find MAP optimizer (by grid iteration)
posteriorlogdensity = lambda theta: misfit(theta) + 1/(2*gamma**2)*theta[0]**2 + 1/(2*gamma**2)*theta[1]**2

N_contours =150
theta1s = np.linspace(-3, 3, N_contours)
#theta1s = np.linspace(0.6, 1.8, N_contours)
theta2s = np.linspace(-1, 3, N_contours)
#theta2s = np.linspace(0.04, 0.2, N_contours)

T1, T2 = np.meshgrid(theta1s, theta2s)
postvals = np.zeros((N_contours, N_contours))
misvals = np.zeros((N_contours, N_contours))
for n, t1 in enumerate(theta1s):
	for m, t2 in enumerate(theta2s):
	    postvals[m, n] = posteriorlogdensity(np.array([t1, t2]))
	    misvals[m, n] = misfit(np.array([t1, t2]))

indmin = np.unravel_index(np.argmin(postvals, axis=None), misvals.shape)
thetaMAP_grid = np.array([theta1s[indmin[1]], theta2s[indmin[0]]])
######################################################################

# starting point for optimization
theta0 = np.random.normal(0, gamma, thetatruth.shape)

# RTO sampling
N_samples = 1000;
mean_theta= None
init_method = "random"
res = rto_scalable(f_fwd, Jf_fwd, y, sigma, gamma, theta0, N_samples=N_samples, init_method="random")

 # you can also try init_method = "fixed" or "previous", but will work worse

# extract data
samples_plain = res["samples_plain"]
#samples_corrected = res["samples_corrected"]
thetaMAP = res["thetaMAP"]
#logweights = res["logweights"]
#num_bad_opts = res["num_bad_opts"]
#num_bad_QR = res["num_bad_QR"]

# plot results
plt.figure(1); plt.ion()
plt.title("parameter space")
plt.contourf(theta1s, theta2s, np.log(postvals), 20, cmap=plt.get_cmap("viridis"))
plt.plot(samples_plain[:, 0], samples_plain[:, 1], '.', label="samples (uncorrected)")
#plt.plot(samples_corrected[:, 0], samples_corrected[:, 1], '.', label="samples")
plt.plot(thetatruth[0], thetatruth[1], 'go', markersize=10, label="th_true")
plt.plot(thetaMAP_grid[0], thetaMAP_grid[1], 'yo', markersize=10, label="th_MAP (grid search)")

plt.plot(thetaMAP[0], thetaMAP[1], 'mo', markersize=10, label="th_MAP (optimization)")
plt.legend(numpoints=1)

plt.figure(2); plt.clf()
inds = np.random.choice(range(samples_plain.shape[0]), 100, replace=False)
plt.plot(xs, f_fnc(xs, np.reshape(samples_plain[0], thetatruth.shape)), '0.9', linewidth=1, label="samples (uncorrected)")
for ind in inds:
	plt.plot(xs, f_fnc(xs, np.reshape(samples_plain[ind], thetatruth.shape)), '0.9', linewidth=1)

plt.plot(xObs, y, 'rx', label="observation")
plt.plot(xs, f_fnc(xs, thetatruth), 'g', linewidth=3, label="th_true")
plt.plot(xs, f_fnc(xs, thetaMAP_grid), 'y', linewidth=3, label="th_MAP (grid search)")
plt.plot(xs, f_fnc(xs, thetaMAP), 'm', linewidth=3, label="th_MAP (optimization)")
plt.legend(numpoints=1)
plt.show()


import numpy as np
import pandas as pd

def rmsle(actuals: pd.DataFrame, predictions: pd.DataFrame) -> float:
    """
    Computes the root mean square log error between the actuals and predictions.
    Raises and error if there are multiple predictions for a single work item, or if there are missing predictions
    :param actuals: A DataFrame with the columns 'work_item' and 'duration_in_days'
    :param predictions: A DataFrame with the columns 'work_item' and 'predictions'
    :return: RMSLE between actuals and predictions
    """
    assert len(actuals) == len(predictions)
    assert set(actuals.work_item.values) == set(predictions.work_item.values)
    actuals_values = actuals.duration_in_days.values
    predictions_values = predictions.predictions.values
    rmsle = np.sqrt(sum(((np.log(actuals_values + 1) - np.log(predictions_values + 1)) ** 2)) / len(actuals_values))
    return rmsle

def my_rmsle(actuals: pd.DataFrame, predictions: pd.DataFrame, index_col: str, time_col: str) -> float:
    """
    Computes the root mean square log error between the actuals and predictions
    Raises and error if there are multiple predictions for a single work item, or if there are missing predictions
    :param actuals: Dataframe with training data
    :param predictions: Dataframe with predicted data
    :param index_col: work_items or other index to match on
    :param time_col: time you predicted
    :return: RMSLE between actuals and predictions
    """
    assert len(actuals) == len(predictions)
    assert set(actuals[index_col].values) == set(predictions[index_col].values)
    actuals_values = actuals[time_col].values
    predictions_values = predictions.predictions.values
    rmsle = np.sqrt(sum(((np.log(actuals_values + 1) - np.log(predictions_values + 1)) ** 2)) / len(actuals_values))
    return rmsle


import numpy as np

def rodrigues(miller_dict, theta, rounding=True):
    '''rodrigues matrix

    Arguments:
        miller_dict {list or np.array} -- axis of rotation
        theta {float} -- angle [deg]
        rounding {Bool} -- do or not rounding

    Returns:
        np.array3x3 -- rodrigues mat
    '''
    norm = np.linalg.norm(miller_dict)
    miller_dict = miller_dict / norm if norm > 1e-5 else miller_dict
    cos = np.cos(np.deg2rad(theta))
    sin = np.sin(np.deg2rad(theta))
    u = miller_dict[0]
    v = miller_dict[1]
    w = miller_dict[2]
    g = np.array([[cos + u**2. * (1 - cos), u * v * (1 - cos) - w * sin, u * w * (1 - cos) + v * sin],
                  [u * v * (1 - cos) + w * sin, cos + v**2. *
                   (1 - cos), v * w * (1 - cos) - u * sin],
                  [u * w * (1 - cos) - v * sin, v * w * (1 - cos) + u * sin, cos + w**2. * (1 - cos)]])
    return np.round(g) if rounding else g

def rotate_mat(bunge_euler):
    '''R matrix

    Arguments:
        phi {list} -- bunge's euler [phi1, phi, phi2]

    Returns:
        numpy array3x3
    '''
    phi = np.deg2rad(bunge_euler)
    Z1 = np.array([[np.cos(phi[0]), np.sin(phi[0]), 0],
                   [-np.sin(phi[0]), np.cos(phi[0]), 0],
                   [0, 0, 1]])
    X = np.array([[1, 0, 0],
                  [0, np.cos(phi[1]), np.sin(phi[1])],
                  [0, -np.sin(phi[1]), np.cos(phi[1])]])
    Z2 = np.array([[np.cos(phi[2]), np.sin(phi[2]), 0],
                   [-np.sin(phi[2]), np.cos(phi[2]), 0],
                   [0, 0, 1]])
    R = np.dot(Z2, np.dot(X, Z1))
    return R

def euler2pole(phi, direct):
    ''' The southern hemisphere is also inverted and projected '''
    R = rotate_mat(phi)
    direct = direct / np.linalg.norm(direct)
    o = np.dot(R.T, direct.T)
    return o

def stereo(phi_array, direct=np.array([1, 1, 1])):
    '''Convert Bunge euler angle to projected pole figure
        with the specified orientation.

    Arguments:
        phi_array {array_like} -- Bunge euler angle

    Keyword Arguments:
        direct {array_like} -- Direction of plane to project (default: {np.array([1, 1, 1])})

    Returns:
        numpy array (2L, :) -- Coordinates on 2D plane
    '''
    projection_point = []
    for phi in phi_array:
        pole = euler2pole(phi, direct)
        projection_point.append([2 * pole[0] / (1 + np.abs(pole[2])),
                                 2 * pole[1] / (1 + np.abs(pole[2]))])
    return np.array(projection_point)


import numpy as np
from typing import Callable
from crawler.crawl.crawl import Crawl
from crawler.crawl.page_rank.page_rank import PageRank

# See https://www.cs.ubc.ca/~nando/540b-2011/lectures/book540.pdf
# for more information about this implementation
class PageRankComputer(object):
    def __init__(self, crawl: Crawl, epsilon: float = 0.001,
                 stop_condition: Callable[[int, np.array, np.array], bool] =
                 lambda iterations, pi_t, pi_t_1: iterations == 1000):
        self.__crawl = crawl
        self.__size = len(crawl)
        self.__urls = crawl.urls
        self.__url_indexes = crawl.url_indexes

        self.__epsilon = epsilon
        self.__stop_condition = stop_condition

        self.transition_matrix = None
        self.page_rank_matrix = None

    def __compute_transition_matrix(self) -> np.array:
        # Adjacency matrix, i.e T[i][j] is the number of links from URL_i to URL_j
        T = self.__crawl.adjacency_matrix
        # Matrix of uniform probability
        # E[i][j] = 1/size
        E = np.ones(T.shape) / self.__size
        # L[i][j] = T[i][j] + e*(1/size), this avoids falling in link cycles
        L = T + self.__epsilon * E
        # Normalization
        G = np.zeros(L.shape)
        for i in range(self.__size):
            G[i, :] = L[i, :] / np.sum(L[i, :])
        # G is a stochastic
        # matrix, matrix where G[i][j] stores the probability
        # of the web surfer of going from link i to link j
        return G

    def __compute_page_rank(self) -> np.array:
        self.transition_matrix = self.__compute_transition_matrix()
        pi = np.random.rand(self.__size)
        pi /= np.sum(pi)
        pi_t = pi
        iterations = 0
        while True:
            pi_t_1 = np.dot(pi_t, self.transition_matrix)
            self.__stop_condition(iterations, pi_t_1, pi_t)
            if self.__stop_condition(iterations, pi_t, pi_t_1):
                pi_t = pi_t_1
                break
            pi_t = pi_t_1
            iterations += 1
        return pi_t

    def compute(self) -> np.array:
        self.page_rank_matrix = self.__compute_page_rank()
        return PageRank(self.page_rank_matrix, self.__url_indexes)


"""fully connected layer"""

import numpy as np

class FCLayer:
    def __init__(self, num_input, num_output, init_std=0.01, trainable=True):
        """Applies a linear transformation to the incoming data: :math:`y = Ax + b`
        Args:
             num_input: size of each input sample
             num_output: size of each output sample
             init_std: std of weight initializer
             trainable: whether if this layer is trainable.
        """
        self.num_input  = num_input
        self.num_output = num_output
        self.trainable  = trainable

        self.W = np.random.normal(0, init_std, (num_output, num_input))
        self.b = np.random.normal(0, init_std, (num_output, 1))

        self.diff_W = np.zeros((num_output, num_input))
        self.diff_b = np.zeros((num_output, 1))

    def forward(self, Input):
        # TODO: Put your code here
        # Please delete `pass` and return the output
        output     = np.dot(Input, self.W.T) + self.b.T
        self.cache = Input
        return output

    def backward(self, delta):  # delta has been calculated in the last layer
        # TODO: Put your code here
        # Please delete `pass`, calculate delta, self.grad_W, self.grad_b and return delta
        delta_l     = np.dot(delta, self.W)
        self.grad_W = np.dot(delta.T, self.cache)
        self.grad_b = np.sum(delta, axis = 0, keepdims=True).T
        return delta_l


from collections import deque
from math import log, ceil
from numpy import array, int64

# def split_arr(ar, br, lo, hi, N):
# 	if lo<hi:
# 		mid = (lo+hi)/2
# 		# O(lgN)
# 		a = split_arr(ar, br, lo, mid, N)
# 		b = split_arr(ar, br, mid+1, hi, N)
# 		return a if a>b else (b if b > -1 else 0)
# 	else:
# 		maximus = float('-inf')
# 		b_num = br[lo]
# 		# O(N)
# 		for index in xrange(N):
# 			if lo >= index and b_num >= ar[index]:
# 				res = lo - index
# 				if res > maximus:
# 					maximus = res
# 			elif b_num < ar[index] and lo <= index:
# 				return maximus
# 		return maximus

# bad solution
def split_arr2(ar, b_index, b_num, lo, hi, N):
	s = deque(array([], dtype=int64), int(ceil(log(N, 2)))**2)
	s.appendleft([lo, hi])
	res = 0
	maximus = float('-inf')
	while s:
		lo, hi = s.pop()
		if lo<hi:
			mid = (lo+hi)/2
			s.append([lo, mid])
			s.append([mid+1, hi])
		else:
			if b_index >= lo and b_num >= ar[lo]:
				res = b_index - lo
				if res > maximus:
					maximus = res
			elif b_num < ar[lo] and b_index <= lo:
				return maximus
	return maximus

# the good mysterious solution
def split_arr1(ar, br, lo, hi, N):
	s = deque(array([], dtype=int64), int(ceil(log(N, 2)))**2)
	s.appendleft([lo, hi])
	maximus = float('-inf')
	res = 0
	# O(lgN)
	while s:
		lo, hi = s.pop()
		if lo<hi:
			mid = (lo+hi)/2
			s.append([lo, mid])
			s.append([mid+1, hi])
		else:
			b_num = br[lo]
			# O(lgN)
			if b_num >= ar[lo]:
				t = 0
				while t <= lo:
					if b_num >= ar[t]:
						res = lo - t
						if res > maximus:
							maximus = res
					t += 1
			# res = split_arr2(ar, lo, b_num, 0, N-1, N)
			# if res > maximus: maximus = res
	return maximus if maximus > -1 else 0

def split_arr(ar, br, N):
	maximus = float('-inf')
	res = 0
	# O(NlgN)
	for i in xrange(N):
		# print "for i=%s"%i
		a_num = ar[i]
		lo, hi = 0, N-1
		while lo <= hi:
			mid = (lo+hi)/2
			b_num = br[mid]
			if b_num >= a_num:
				res = mid - i
				if res > maximus:
					# assert mid >= i # solution would break for the clause j >= i
					# print "bi,mid=%s ai=%s"%(mid, i)
					maximus = res
				lo = mid + 1
			else:
				hi = mid - 1
	# print maximus if maximus > -1 else 0
	return maximus if maximus > -1 else 0

if __name__ == "__main__":
	ar = [7, 7, 3, 3, 3, 2, 2, 2, 1]
	br = [8, 8, 7, 7, 5, 5, 4, 3, 2]
	N = 9
	print split_arr(ar, br, N)
	assert split_arr(ar, br, N) == 5

	ar = [6, 5, 4, 4, 4, 4]
	br = [2, 2, 2, 2, 2, 2]
	N = 6
	assert split_arr(ar, br, N) == 0

	ar = [10, 9, 9, 2, 1]
	br = [10, 9, 7, 5, 1]
	N = 5
	assert split_arr(ar, br, N) == 0

	import os
	from cStringIO import StringIO

	with open(os.path.join(os.getcwd(), 'max_index_tests', "test1.txt"), 'r') as f:
		s = StringIO(f.read())

	with open( os.path.join(os.getcwd(), 'max_index_tests', r"test1ans.txt"), 'r' ) as f:
		ans = StringIO(f.read())

	T = int(s.readline().strip("\n"))

	for _ in xrange(T):
		N = int(s.readline().strip("\n"))
		ar = map(int, s.readline().strip("\n").split())
		br = map(int, s.readline().strip("\n").split())
		assert split_arr(ar, br, N) == int(ans.readline().strip("\n"))


import numpy as np

a = np.zeros((4, 3), dtype=np.int)
print(a)
# [[0 0 0]
#  [0 0 0]
#  [0 0 0]
#  [0 0 0]]

print(a.shape)
# (4, 3)

b = np.arange(6).reshape(2, 3)
print(b)
# [[0 1 2]
#  [3 4 5]]

print(b.shape)
# (2, 3)

# print(a + b)
# ValueError: operands could not be broadcast together with shapes (4,3) (2,3)

a = np.zeros((2, 3, 4), dtype=np.int)
print(a)
# [[[0 0 0 0]
#   [0 0 0 0]
#   [0 0 0 0]]
#
#  [[0 0 0 0]
#   [0 0 0 0]
#   [0 0 0 0]]]

print(a.shape)
# (2, 3, 4)

b = np.arange(3)
print(b)
# [0 1 2]

print(b.shape)
# (3,)

# print(a + b)
# ValueError: operands could not be broadcast together with shapes (2,3,4) (3,)

b_3_1 = b.reshape(3, 1)
print(b_3_1)
# [[0]
#  [1]
#  [2]]

print(b_3_1.shape)
# (3, 1)

print(a + b_3_1)
# [[[0 0 0 0]
#   [1 1 1 1]
#   [2 2 2 2]]
#
#  [[0 0 0 0]
#   [1 1 1 1]
#   [2 2 2 2]]]


import math
import numpy as np
import random

# get a random point within a circile
def get_random_point(circle_x,circle_y,circle_r):
    # random angle
    alpha = 2 * math.pi * random.random()
    # random radius
    r = circle_r * math.sqrt(random.random())
    # calculating coordinates
    x = r * math.cos(alpha) + circle_x
    y = r * math.sin(alpha) + circle_y
    return x,y

# generate list of random points
def generate_position(num_points,circle_x,circle_y,circle_r):
    points_x=[]
    points_y=[]
    for i in range(num_points):
        x,y=get_random_point(circle_x,circle_y,circle_r)
        points_x.append(x)
        points_y.append(y)
    return np.c_[points_x,points_y]

# return the 9 nozzles position, use margin to control the distance between nozzles and edge of the circle
def get_nozzles_position(circle_x,circle_y, radius,margin=0.2):
    # random angle
    cluster_density=2 * math.pi/36
    cluster_alpha=[]
    for num in range(3):
        cluster_alpha.append(2 * math.pi*num/3-cluster_density)
        cluster_alpha.append(2 * math.pi*num/3)
        cluster_alpha.append(2 * math.pi*num/3+cluster_density)
    positions=[]
    for alpha in cluster_alpha:
        # random radius
        r = radius + margin
        # calculating coordinates
        x = r * math.sin(alpha) + circle_x
        y = r * math.cos(alpha) + circle_y
        rotation= math.degrees(alpha)
        positions.append((x,y,-rotation))
    return positions

# Get the nozzles_status
def get_nozzles_status(active_nozzles):
    active_num=int(9*active_nozzles/100)
    active_order=[0,3,6,1,4,7,2,5,8]
    active_nozzles_array=[False for i in range(9)]
    for i in range(active_num):
        active_nozzles_array[active_order[i]]=True
    return active_nozzles_array

def update_nozzles_status(nozzles,status):
    for index, status in enumerate(status):
        if status:
            nozzles[index].set_text("+")
        else:
            nozzles[index].set_text("-")


from __future__ import division
import numpy
import matplotlib.pyplot as pyplot

USER = "Maciej Grabias"
USER_ID = 'njgh39'

def f(x):
    return (x**2.0)*numpy.sin(x) # function returning product of x squared times sin(x)

def g(x):
    return 2.0*x*numpy.sin(x) - (x**2.0 - 2.0)*numpy.cos(x) # function returning indefinite integral of
    # function above

def integrate_numeric(x0, x1, n_panels): # function returning the area under the curve of f(x)
    # using Simpson's rule
    panel_width = (x1 - x0) / n_panels
    area = 0.0

    for ix in range(n_panels):
        a = x0 + ix * panel_width
        panel_area = (panel_width/6) * (f(a) + 4 * f(a + panel_width/2) + f(a + panel_width))
        area = area + panel_area
    return area

def integrate_analytic(x0, x1):
    return g(x1) - g(x0)

PANEL_COUNTS = [4, 8, 16, 32, 64, 128, 256, 512, 1024]

X0 = 0
X1 = 2

y_data = []
ref = integrate_analytic(X0, X1)

for n in PANEL_COUNTS:
    s = integrate_numeric(X0, X1, n)
    error = abs((s-ref)/ref)
    y_data.append(error)

pyplot.figure(figsize = (6,6))
pyplot.loglog()
pyplot.scatter(PANEL_COUNTS, y_data)
pyplot.ylabel('Percentage Error')
pyplot.xlabel('Number of Panels')
pyplot.title("Percentage Error in the Numerical Method VS Number of Panels Used \n (both scales logarithmic)")
pyplot.show()

ANSWER1 = "The increasing number of panels increases the accuracy of the numerical method because \
narrower panels enable more accurate iteration and summation"
ANSWER2 = "Increasing panel count will still increase the accuracy of the method, but not as much \
as it does while using Simpson's method.'"


import matplotlib.pyplot as plt
import numpy as np
import scipy.stats as sc

TEST_DATA = np.array([[1,2,3,2,1,2,3,4,2,3,2,1,2,3,4,4,3,2,3,2,3,2,1],
                      [5,6,5,4,5,6,7,7,6,7,7,2,8,7,6,5,5,6,7,7,7,6,5],
                      [9,8,7,8,8,7,4,6,6,5,4,3,2,2,2,3,3,4,5,5,5,6,1],
                      [3,2,3,2,2,2,2,3,3,3,3,4,4,4,4,5,6,6,7,8,9,8,5],
                      ])

# find mean for each of our observations
y = np.mean(TEST_DATA, axis=1, dtype=np.float64)
# and the 95% confidence interval
ci95 = np.abs(y - 1.96 * sc.sem(TEST_DATA, axis=1))

# each set is one try
tries = np.arange(0, len(y), 1.0)

# tweak grid and setup labels, limits
plt.grid(True, alpha=0.5)
plt.gca().set_xlabel('Observation #')
plt.gca().set_ylabel('Mean (+- 95% CI)')
plt.title("Observations with corresponding 95% CI as error bar.")

plt.bar(tries, y, align='center', alpha=0.2)
plt.errorbar(tries, y, yerr=ci95, fmt=None)

plt.show()


import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm

def contour(plt, score, x_axis, y_axis, colour, lessthan=True):
    #plt.axvline(score, color=colour)
    x_temp = [x for x in x_axis if x <= score]
    if lessthan:
        y_temp = y_axis[:len(x_temp)]
    else:
        y_temp = y_axis[len(x_temp): ]
        x_temp = [x for x in x_axis if x > score]
    plt.fill_between(x_temp, 0, y_temp, facecolor=colour)

if __name__ == '__main__':
    mean = 80; sd = 15
    x_axis = np.arange(10, 140, 0.001)
    y_axis = norm.pdf(x_axis,mean,sd)

    plt.plot(x_axis, y_axis)
    plt.xlabel('Scores')
    plt.ylabel('PDF')
    plt.title("Gaussian Distribution with Mean: {} and STD: {}".format(mean, sd))

    colour='#4dac26'
    score = 60
    contour(plt, score, x_axis, y_axis, colour, lessthan=True)

    colour='#f1b6da'
    score = 90
    contour(plt, score, x_axis, y_axis, colour, lessthan=False)

    plt.show()


import math
import cmath

import numpy as np

from src.dirac_notation.bra import Bra
from src.dirac_notation.ket import Ket
from src.dirac_notation.matrix import Matrix
from src.dirac_notation import functions as dirac

# Kets and Bras

# Computational vectors |0>, |1>, ..., |m>
comp_ket_x = lambda x, d : Ket([ 1 if i == x else 0 for i in range(0, d) ]) if d > x else None
comp_bra_x = lambda x, d : Bra([ 1 if i == x else 0 for i in range(0, d) ]) if d > x else None

# Fourier vectors |e0>, |e1>, ..., |en>
fourier_ket_x = lambda x, d : (1 / math.sqrt(d)) * np.sum([ cmath.rect(1, (2 * math.pi * i * x) / d) * comp_ket_x(i, d) for i in range(0, d) ])
fourier_bra_x = lambda x, d : dirac.adjoint(fourier_ket_x(x, d))

# Angle vectors |0, theta> and |1, theta>
angle_ket_0 = lambda theta : (math.cos(theta / 2) * comp_ket_x(0, 2)) + (math.sin(theta / 2) * comp_ket_x(1, 2))
angle_ket_1 = lambda theta : (math.sin(theta / 2) * comp_ket_x(0, 2)) - (math.cos(theta / 2) * comp_ket_x(1, 2))

# Computational vectors in 2D, |0> and |1>
ket_0 = comp_ket_x(0, 2)
ket_1 = comp_ket_x(1, 2)
bra_0 = dirac.adjoint(ket_0)
bra_1 = dirac.adjoint(ket_0)

# Fourier vectors in 2D, |+> and |->
ket_plus = fourier_ket_x(0, 2)
ket_minus = fourier_ket_x(1, 2)
bra_plus = dirac.adjoint(ket_plus)
bra_minus = dirac.adjoint(ket_minus)

# Computational vectors in C^4, |psi_00>, |psi_01>, |psi_10> and |psi_11>
ket_psi_00 = dirac.tensor(ket_0, ket_0)
ket_psi_01 = dirac.tensor(ket_0, ket_1)
ket_psi_10 = dirac.tensor(ket_1, ket_0)
ket_psi_11 = dirac.tensor(ket_1, ket_1)
bra_psi_00 = dirac.adjoint(ket_psi_00)
bra_psi_01 = dirac.adjoint(ket_psi_01)
bra_psi_10 = dirac.adjoint(ket_psi_10)
bra_psi_11 = dirac.adjoint(ket_psi_11)

# Bell vectors, |omega+>, |omega->, |psi+> and |psi->
ket_phi_plus = (1 / math.sqrt(2)) * ((dirac.tensor(ket_0, ket_0)) + (dirac.tensor(ket_1, ket_1)))
ket_phi_minus = (1 / math.sqrt(2)) * ((dirac.tensor(ket_0, ket_0)) - (dirac.tensor(ket_1, ket_1)))
ket_psi_plus = (1 / math.sqrt(2)) * ((dirac.tensor(ket_0, ket_1)) + (dirac.tensor(ket_1, ket_0)))
ket_psi_minus = (1 / math.sqrt(2)) * ((dirac.tensor(ket_0, ket_1)) - (dirac.tensor(ket_1, ket_0)))
bra_phi_plus = dirac.adjoint(ket_phi_plus)
bra_phi_minus = dirac.adjoint(ket_phi_minus)
bra_psi_plus = dirac.adjoint(ket_psi_plus)
bra_psi_minus = dirac.adjoint(ket_psi_minus)

# Unitary matrices

# Basic matrices
zero_matrix = lambda d1, d2 : Matrix(np.zeros((d1, d2)))
identity_matrix = lambda d : Matrix(np.identity(d))

# Pauli matrices
pauli_x_matrix = Matrix(np.array([[0, 1], [1, 0]]))
pauli_y_matrix = Matrix(np.array([[0, -1.0j], [1.0j, 0]]))
pauli_z_matrix = Matrix(np.array([[1, 0], [0, -1]]))
n_sigma_matrix = lambda nx, ny, nz : (nx * pauli_x_matrix) + (ny * pauli_y_matrix) + (nz * pauli_z_matrix)

# Fourier transform matrix
fourier_transform_matrix = lambda d : sum([ fourier_ket_x(i, d) * comp_bra_x(i, d) for i in range(0, d) ], zero_matrix(d, d))
hadamard_matrix = fourier_transform_matrix(2) # 2D is known as Hadamard gate (H)

# CNOT matrix
cnot_matrix = dirac.tensor((ket_0 * bra_0), identity_matrix(2)) + dirac.tensor((ket_1 * bra_1), pauli_x_matrix)

# Basis

comp_basis_vectors = lambda d : [comp_ket_x(i, d) for i in range(0, d)]
fourier_basis_vectors = lambda d : [fourier_bra_x(i, d) for i in range(0, d)]

comp_2d_basis_vectors = [ket_0, ket_1]
psi_basis_vectors = [ket_psi_00, ket_psi_01, ket_psi_10, ket_psi_11]
bell_basis_vectors = [ket_phi_plus, ket_phi_minus, ket_psi_plus, ket_psi_minus]


import numpy as np
from sklearn.linear_model import LinearRegression

np.random.seed(111)

'''
The data is generated adding noise to the values from  y = 0.8x + 2 equation
Therefore the expectation of the auto encoder is to get the values w and b closer to 0.8 and 2 respectively
'''

# generate random x values
X_train = np.random.random((1, 50))[0]

# get the reference y value
y_reference = 0.8 * X_train + 2

# add noise to the reference y value
y_train = y_reference + np.sqrt(0.01) * np.random.random((1, 50))[0]

# reshape (model expect a 2D array and X_train is a 1D. So reshape (50,) in to (50, 1))
X_train = X_train.reshape(-1, 1)

# build model
model = LinearRegression()

# train model
model.fit(X_train, y_train)

# get weights and bias of the learned model
w = model.coef_[0]
b = model.intercept_

print('\nSklearn Linear Regression completed')
print('w Expected : 0.8' + '  Learned : ' + str(w))
print('b Expected : 2.0' + '  Learned : ' + str(b))


from scipy.special import comb

def alternate_splice(n,m):
    alternate_splice_count=0
    for k in range(m, n+1):
        alternate_splice_count = (alternate_splice_count + comb(n, k, exact=True))%1000000

    return alternate_splice_count

def main():
    with open('datasets/rosalind_aspc.txt') as input_file:
        n, m = input_file.read().strip().split()

    alternate_splice_count=alternate_splice(int(n),int(m))

    print(str(alternate_splice_count))

    with open('solutions/rosalind_aspc.txt', 'w') as output_file:
        output_file.write(str(alternate_splice_count))

if(__name__=='__main__'):
    main()


from time import time
import numpy as np
from icecream import ic

"""
Write a function that takes a non-empty array of distinct integers and an integer
representing a target sum. If any two numbers in the input array sum up to the target sum,
the function should return them an an array, in any order.
If no two numbers sum up th the target sum, the function should return an empty array.
Note that the target sum has to be obtained by summing two different integers in the array,
you can't add a single integer to itself in order to obtain the target sum.
You can assume that there will be at most one pair of numbers summing up to the target sum.

"""

array_00 = [10, 15, 3, 7]
target_sum_00 = 17

array_01 = [3, 5, -4, 8, 11, 1, -1, 6, 7, 2, -14, 4]
target_sum_01 = 10

array_02 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 15]
target_sum_02 = 18

np.random.seed(1)
arr = list(set(np.random.randint(low=-100, high=100, size=100)))

array_03 = arr
target_sum_03 = 18

def time_decorator(func):
    # This function shows the execution time of
    # the function object passed
    def wrap_func(*args, **kwargs):
        t1 = time()
        result = func(*args, **kwargs)
        t2 = time()
        print(f'Function {func.__name__!r} executed in {(t2 - t1):.4f}s')
        return result

    return wrap_func

@time_decorator
def long_time(n):
    for i in range(n):
        for j in range(100000):
            i * j

@time_decorator
def two_number_sum_00(array, target_sum):
    seen = set()
    for num in array:
        if target_sum - num in seen:
            # ic(seen, num)
            return [target_sum - num, num]
        seen.add(num)
    return []

# O(n) time | O(n) space
@time_decorator
def two_number_sum_01(array, target_sum):
    nums = {}
    for num in array:
        potential_match = target_sum - num
        if potential_match in nums:
            ic(potential_match, num)
            return [potential_match, num]
        else:
            nums[num] = True
    return []

# O(n^2) time | O(1) space - mine
@time_decorator
def two_number_sum_02(array, target_sum):
    for idx, x in enumerate(array):
        for y in array[idx + 1:]:
            if x + y == target_sum:
                # ic(x, y)
                return [x, y]
    return []

# O(n^2) time | O(1) space
@time_decorator
def two_number_sum_03(array, target_sum):
    for i in range(len(array) - 1):
        first_num = array[i]
        for j in range(i + 1, len(array)):
            second_num = array[j]
            if first_num + second_num == target_sum:
                # ic(first_num, second_num)
                return [first_num, second_num]
    return []

# O(nlog(n)) | O(1) space
@time_decorator
def two_number_sum_04(array, target_sum):
    array.sort()
    left = 0
    right = len(array) - 1
    while left < right:
        current_sum = array[left] + array[right]
        if current_sum == target_sum:
            # # ic(array[left], array[right])
            return [array[left], array[right]]
        elif current_sum < target_sum:
            left += 1
        elif current_sum > target_sum:
            right -= 1
    return []

if __name__ == "__main__":
    # two_number_sum_00(array_00, target_sum_00)
    array = array_01
    target_sum = target_sum_01
    ic(array, target_sum)
    # ic(two_number_sum_00(array, target_sum))
    # x = two_number_sum_01(array, target_sum)
    # ic(x)
    ic(two_number_sum_02(array, target_sum))
    # ic(two_number_sum_03(array, target_sum))
    # ic(two_number_sum_04(array, target_sum))


# In this tutorial we will see how to fit curves to give set of data points
# In the first part, we will see how to make a linear fit

import numpy as np
import matplotlib.pyplot as plt

# lets create an artificial data set of x and y points to which we will fit a straight line
# We will make x=y in which case the slope =1 and intercept will be 0

x = np.arange(0,10,1)
# you can also add some variations to y to simulate an experimental datapoint.
# one way is to add gaussian noise by sampling a number from a gaussian distribution and adding it to y.
# like this:
noise = np.random.normal(0,1,10)# check tutorials 04 to learn about random number generatio
y =  x+noise # in case noise is zero, y = x will be a straight line with slope exactly equal to 1 and intercept exaclty 0

plt.scatter(x,y)

fit_res = np.polyfit(x, y, 1) # 1 is the degree of fit, 1 is for linear fit, 2 for quadratic plot and so on

# z contains the results of the fit
# This is how you get the slope and intercept

print("The fit results are:{0}".format(fit_res)) # the first element in the array is the slope and the second element is the intercept
print("The first value is the slope and the second is the intercept")
#Now lets plot the line as estimated by the linear fit:
y_fit = fit_res[0]*x + fit_res[1] # y=mx + c
# enable the line below to see the fitted line
plt.plot(x,y_fit)

plt.show()

# assignments
# # in many cases, one might require the error on the slope and intercept
# in which case one can do the following:

# fit_res, error= np.polyfit(x, y, 1,full=False, cov=True) # 1 is the degree of fit, 1 is for linear fit, 2 for quadratic plot and so on
# print("The error matrix is {0}".format(error))
# print("The diagonal elements are the errors") # The first element in the diagonal is that of slope and the second element in the diagonal is that of the intercept
# Try the above code

# In case you want to fit a second order polynomial  change np.polyfit(x,y,1) to np.polyfit(x,y,2)

# Change the mean and sigma in the gaussian distribution and see how the error matrix changes.


"""
Example of solving Mini-Max Problem
max { (x0-15)^2+(x1-80)^2, (x1-15)^2 + (x2-8)^2, (x2-8)^2 + (x0-80)^2 } -> min
Currently nsmm is single OO solver available for MMP
It defines function F(x) = max_i {f[i](x)}
and solves NSP F(x) -> min using solver ralg.
It's very far from specialized solvers (like MATLAB fminimax),
but it's better than having nothing at all,
and allows using of nonsmooth and noisy funcs.
This solver is intended to be enhanced in future.
"""
from numpy import *
from openopt import *

f1 = lambda x: (x[0]-15)**2 + (x[1]-80)**2
f2 = lambda x: (x[1]-15)**2 + (x[2]-8)**2
f3 = lambda x: (x[2]-8)**2 + (x[0]-80)**2
f = [f1, f2, f3]

# you can define matrices as numpy array, matrix, Python lists or tuples

#box-bound constraints lb <= x <= ub
lb = [0]*3# i.e. [0,0,0]
ub = [15,  inf,  80]

# linear ineq constraints A*x <= b
A = mat('4 5 6; 80 8 15')
b = [100,  350]

# non-linear eq constraints Aeq*x = beq
Aeq = mat('15 8 80')
beq = 90

# non-lin ineq constraints c(x) <= 0
c1 = lambda x: x[0] + (x[1]/8) ** 2 - 15
c2 = lambda x: x[0] + (x[2]/80) ** 2 - 15
c = [c1, c2]
#or: c = lambda x: (x[0] + (x[1]/8) ** 2 - 15, x[0] + (x[2]/80) ** 2 - 15)

# non-lin eq constraints h(x) = 0
h = lambda x: x[0]+x[2]**2 - x[1]

x0 = [0, 1, 2]
p = MMP(f,  x0,  lb = lb,  ub = ub,   A=A,  b=b,   Aeq = Aeq,  beq = beq,  c=c,  h=h, xtol = 1e-6, ftol=1e-6)
#p = MMP(f,  x0, ftol=1e-8)
# optional, matplotlib is required:
#p.plot=1
r = p.solve('nsmm', iprint=1, NLPsolver = 'ralg', maxIter=1e3, minIter=1e2)
print 'MMP result:',  r.ff

#
### let's check result via comparison with NSP solution
F= lambda x: max([f1(x),  f2(x),  f3(x)])
p = NSP(F,  x0, lb = lb, ub = ub,  c=c,  h=h,  A=A,  b=b,  Aeq = Aeq,  beq = beq, xtol = 1e-6, ftol=1e-6)
#p = NSP(F,  x0)
r_nsp = p.solve('ralg')
#print 'NSP result:',  r_nsp.ff,  'difference:', r_nsp.ff - r.ff
#"""
#starting solver nsmm (license: BSD)  with problem  unnamed
#  iter       ObjFun        log10(maxResidual)
#     0       6.4660e+03   +1.89
#   10       6.4860e+03   -0.68
#   20       6.4158e+03   -1.23
#   30       6.4119e+03   -3.08
#   40       6.3783e+03   -2.95
#   50       6.3950e+03   -4.05
#   60       6.3951e+03   -6.02
#   70       6.3938e+03   -6.02
#   78       6.3936e+03   -6.00
#nsmm has finished solving the problem unnamed
#istop:  3 (|| X[k] - X[k-1] || < xtol)
#Solver:   Time Elapsed = 0.41   CPU Time Elapsed = 0.38
#objFunValue: 6393.6196095379446 (feasible, max constraint =  9.95421e-07)
#MMP result: 6393.6196095379446
#starting solver ralg (license: BSD)  with problem  unnamed
#itn 0 : Fk= 6466.0 MaxResidual= 78.0
#itn 10  Fk: 6485.9728487666425 MaxResidual: 2.07e-01 ls: 2
#itn 20  Fk: 6415.8358391383163 MaxResidual: 5.92e-02 ls: 1
#itn 30  Fk: 6411.9310394431113 MaxResidual: 8.22e-04 ls: 3
#itn 40  Fk: 6378.3471060481961 MaxResidual: 1.12e-03 ls: 2
#itn 50  Fk: 6394.9848936519056 MaxResidual: 8.94e-05 ls: 0
#itn 60  Fk: 6395.054402295913 MaxResidual: 9.57e-07 ls: 1
#itn 70  Fk: 6393.8314202292149 MaxResidual: 9.63e-07 ls: 1
#itn 78  Fk: 6393.6196095379446 MaxResidual: 9.95e-07 ls: 1
#ralg has finished solving the problem unnamed
#istop:  3 (|| X[k] - X[k-1] || < xtol)
#Solver:   Time Elapsed = 0.44   CPU Time Elapsed = 0.32
#objFunValue: 6393.6196095379446 (feasible, max constraint =  9.95421e-07)
#NSP result: 6393.6196095379446 difference: 0.0
#"""


import numpy as np
from numpy import random
from numpy.linalg import norm
import numpy.linalg as LA
import argparse
import logging
import os
from matplotlib import pyplot as plt
import cvxpy as cp
from gl_cvx_gurobi import gl_cvx_gurobi
from gl_cvx_mosek import gl_cvx_mosek
from gl_gurobi import gl_gurobi
from gl_mosek import gl_mosek
from gl_SGD_primal import gl_SGD_primal
from gl_GD_primal import gl_GD_primal
from gl_ProxGD_primal import gl_ProxGD_primal
from gl_FGD_primal import gl_FGD_primal
from gl_FProxGD_primal import gl_FProxGD_primal
from pytablewriter import MarkdownTableWriter
from gl_ADMM_primal import gl_Admm_primal
from gl_ADMM_dual import gl_Admm_dual
from gl_ALM_dual import gl_Alm_dual
from pytablewriter import typehint

# min 0.5 * ||A * x - b||_2^2 + mu * ||x||_{1,2}
# Credit to
#   http://niaohe.ise.illinois.edu/IE598_2016/lasso_demo/index.html

def obj_func(x: np.ndarray):
    temp = np.matmul(A, x) - b
    fro_term = 0.5 * np.sum(np.multiply(temp, temp))
    regular_term = np.sum(np.apply_along_axis(func1d=lambda row: LA.norm(row), axis=1, arr=x))
    return fro_term + mu * regular_term

def gen_data(seed=97006855):
    n, m, l = 512, 256, 2
    mu = 1e-2
    generator = random.Generator(random.MT19937(seed=seed))
    A = generator.standard_normal(size=(m, n))
    k = round(n * 0.1)
    p = generator.permutation(n)[:k]
    u = np.zeros(shape=(n, l))
    u[p, :] = generator.standard_normal(size=(k, l))  # ground truth
    b = np.matmul(A, u)
    x0 = generator.standard_normal(size=(n, l))
    errfun = lambda x1, x2: norm(x1 - x2, 'fro') / (1 + norm(x1, 'fro'))
    errfun_exact = lambda x: norm(x - u, 'fro') / (1 + norm(x, 'fro'))
    sparsity = lambda x: np.sum(np.abs(x) > 1e-6 * np.max(np.abs(x))) / (n * l)
    return n, m, l, mu, A, b, u, x0, errfun, errfun_exact, sparsity

def setup_logger(logger_name, log_file, level=logging.INFO):
    log_setup = logging.getLogger(logger_name)
    formatter = logging.Formatter('%(asctime)s: %(levelname)-5s %(message)s')
    fileHandler = logging.FileHandler(log_file, mode='a')
    fileHandler.setFormatter(formatter)
    streamHandler = logging.StreamHandler()
    streamHandler.setFormatter(formatter)
    log_setup.setLevel(level)
    log_setup.addHandler(fileHandler)
    log_setup.addHandler(streamHandler)

def plot_result(mode: str, file_name: str, ground_truth, cvx_mosek_rv, cvx_gurobi_rv, x):
    plt.clf()
    fig = plt.figure(1)

    plt.subplot(2, 1, 1)
    plt.plot(np.arange(0, n, 1), ground_truth[:, 0], 'r*', label='ground truth')
    # plt.plot(np.arange(0, n, 1), cvx_mosek_rv[ :, 0 ], 'g^', label='CVX-Mosek')
    # plt.plot(np.arange(0, n, 1), cvx_gurobi_rv[ :, 0 ], 'bv', label='CVX-Gurobi')
    plt.plot(np.arange(0, n, 1), x[:, 0], 'mo', label=mode)
    plt.xlim(0, n)
    plt.title('{}: Results on the 1st dimension'.format(mode))

    plt.subplot(2, 1, 2)
    plt.plot(np.arange(0, n, 1), ground_truth[:, 1], 'r*', label='ground truth')
    # plt.plot(np.arange(0, n, 1), cvx_mosek_rv[ :, 1 ], 'g^', label='CVX-Mosek')
    # plt.plot(np.arange(0, n, 1), cvx_gurobi_rv[ :, 1 ], 'bv', label='CVX-Gurobi')
    plt.plot(np.arange(0, n, 1), x[:, 1], 'mo', label=mode)
    plt.xlim(0, n)
    plt.title('{}: Results on the 2nd dimension'.format(mode))

    plt.tight_layout()
    plt.show()
    plt.savefig(file_name)

cvx_mosek_rv, cvx_gurobi_rv = None, None

def write_to_table(log_dicts: dict):
    table_name = "Statistics"
    headers = None
    value_matrix = []
    for mode, log_dict in log_dicts.items():
        if headers is None:
            headers = ['solver'] + list(log_dict.keys())
        value_matrix.append([mode] + list(log_dict.values()))
    assert headers is not None
    type_hints = [typehint.String] * len(headers)
    writer = MarkdownTableWriter(
        table_name=table_name,
        headers=headers,
        value_matrix=value_matrix,
        type_hints=type_hints
    )
    writer.write_table()

def solve_routine(mode: str, func, x0, A, b, mu, opts, u, errfun, errfun_exact, sparsity):
    x, num_iters, out = func(x0, A, b, mu, opts)
    solve_time = out["tt"]
    fval = out["fval"]
    log_dict = {
        "cpu": "%5.2f" % solve_time,
        "iter": "%5d" % (-1 if num_iters is None else num_iters),
        "optval": "%6.5E" % fval,
        "sparsity": "%6.4f" % sparsity(x),
        "err-to-exact": "%3.2E" % errfun_exact(x),
        "err-to-cvx-mosek": "%3.2E" % errfun(cvx_mosek_rv, x),
        "err-to-cvx-gurobi": "%3.2E" % errfun(cvx_gurobi_rv, x)
    }
    log_str = ("[%-10s]: " % mode) + ', '.join(map(lambda x: x[0] + ": " + x[1], log_dict.items()))
    logger = logging.getLogger('opt')
    logger.info(log_str)
    plot_result(mode, os.path.join(destination_directory, "%s.svg" % mode), u, cvx_mosek_rv, cvx_gurobi_rv, x)
    return x, num_iters, out, log_dict

if __name__ == '__main__':
    parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter,
                                     description='A demo that solves the optimization problem '
                                                 '$\min_x{0.5 * ||A * x - b||_2^2 + mu * ||x||_{1,2}}$')
    parser.add_argument('--log', type=str, default='opt.log', help='Path to the logging file.')
    parser.add_argument('--dest_dir', type=str, default='figures', help='Destination directory.')
    args = parser.parse_args()

    log_file_path = args.log
    destination_directory = args.dest_dir

    setup_logger("opt", log_file_path, level=logging.INFO)
    logger = logging.getLogger('opt')
    logger.info("========================== New Log ========================================")

    if not os.path.isdir(destination_directory):
        os.makedirs(destination_directory)
        logger.info("Create directory: %s" % destination_directory)

    installed_solvers = cp.installed_solvers()
    logger.info("Installed solvers for cvxpy: " + str(installed_solvers))

    n, m, l, mu, A, b, u, x0, errfun, errfun_exact, sparsity = gen_data()

    fig = plt.figure(1)
    plt.subplot(2, 1, 1)
    plt.plot(np.arange(0, n, 1), u[:, 0], '*')
    plt.plot(np.arange(0, n, 1), u[:, 1], 'o')
    plt.xlim(0, n)
    plt.title(r'(1) exact solution $u$')
    # plt.show( )
    plt.savefig(os.path.join(destination_directory, 'ground_truth.svg'))

    cvx_mosek_rv, _, _ = gl_cvx_mosek(x0, A, b, mu, {})
    cvx_gurobi_rv, _, _ = gl_cvx_gurobi(x0, A, b, mu, {})

    solvers = {
        'CVX-Mosek': gl_cvx_mosek,
        'CVX-Gurobi': gl_cvx_gurobi,
        # 'Mosek': gl_mosek,
        # 'Gurobi': gl_gurobi,
        'SGD Primal': gl_SGD_primal,
        "GD Primal": gl_GD_primal,
        "FGD Primal": gl_FGD_primal,
        "ProxGD Primal": gl_ProxGD_primal,
        "FProxGD Primal": gl_FProxGD_primal,
        "ALM Dual": gl_Alm_dual,
        "ADMM Dual": gl_Admm_dual,
        "ADMM Primal": gl_Admm_primal,
    }

    solvers_opts = {
        'CVX-Mosek': {},
        'CVX-Gurobi': {},
        'Mosek': {},
        'Gurobi': {},
        'SGD Primal': {},
        "GD Primal": {},
        "ProxGD Primal": {},
        "FGD Primal": {},
        "FProxGD Primal": {},
        "ADMM Dual": {},
        "ADMM Primal": {},
        "ALM Dual": {},
    }

    f_hists = {}
    log_dicts = {}
    for mode, solver in solvers.items():
        _, _, out, log_dict = solve_routine(mode, solver, x0, A, b, mu, solvers_opts[mode],
                                            u, errfun, errfun_exact, sparsity)
        if 'f_hist' in out:
            f_hists[mode] = out["f_hist"]
        log_dicts[mode] = log_dict

    write_to_table(log_dicts)

    plot_solver_color = {
        'SGD Primal': 'g',
        "GD Primal": 'b',
        "ProxGD Primal": 'r',
        "FGD Primal": 'y',
        "FProxGD Primal": 'k',
        "ADMM Dual": 'm',
        "ADMM Primal": 'c',
        "ALM Dual": 'peachpuff'
    }

    plt.clf()
    file_name = os.path.join(destination_directory, 'relative_objective.svg')
    fig, ax = plt.subplots(figsize=(9, 6))
    f_star = obj_func(u)
    for mode, f_hist in f_hists.items():
        f_hist = (f_hist - f_star) / f_star
        plt.semilogy(np.arange(0, len(f_hist)), np.array(f_hist), color=plot_solver_color[mode], linewidth=2, label=mode)
    plt.legend(prop={'size': 12})
    plt.ylabel('$(f(x^k)-f^*)/f^*$')
    plt.xlabel('Iteration')
    plt.savefig(file_name)

    plt.clf()
    file_name = os.path.join(destination_directory, 'objective_function.svg')
    fig, ax = plt.subplots(figsize=(9, 6))
    f_star = obj_func(u)
    for mode, f_hist in f_hists.items():
        plt.semilogy(np.arange(0, len(f_hist)), np.array(f_hist), plot_solver_color[mode], linewidth=2, label=mode)
    plt.legend(prop={'size': 12})
    plt.ylabel('$f(x^k)$')
    plt.xlabel('Iteration')
    plt.savefig(file_name)


# -*- coding: utf-8 -*-
"""
Created on Sat Nov  9 19:50:37 2019
@author: wahid
"""

#numpy a math tools
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

#Importing Dataset
dataset = pd.read_csv('Position_Salaries.csv')
#get the independent data
X = dataset.iloc[:,1:-1].values
#get the dependent data
y = dataset.iloc[:,2].values

#Fitting linear regression to dataset
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(X, y)

#Fitting Polynomial regression to dataset
from sklearn.preprocessing import PolynomialFeatures
poly_reg = PolynomialFeatures(degree = 5)
X_poly = poly_reg.fit_transform(X)
lin_reg_2 = LinearRegression()
lin_reg_2.fit(X_poly, y)

#Visualizing the Linear regression results
plt.scatter(X, y, color = 'red')
plt.plot(X, lin_reg.predict(X), color = 'pink')
plt.title('Linear Regression')
plt.xlabel('Position')
plt.ylabel('Salary')
plt.show()

X_grid = np.arange(min(X), max(X), 0.1)
X_grid = X_grid.reshape(len(X_grid),1)

#Visualize the Polynomial regression results
plt.scatter(X, y, color = 'red')
""" we will recalculate the X_poly incase the X has been modified before this line"""
plt.plot(X_grid, lin_reg_2.predict(poly_reg.fit_transform(X_grid)), color = 'blue')
plt.title('Ploynomial Regression')
plt.xlabel('Position')
plt.ylabel('Salary')
plt.show()

Part 2 - Regression/Section 6 - Polynomial Regression/polynomial_regression.py


from numpy import *

def compute_error_for_line_given_points(b, m, points):

    # initialize it at 0
    totalerror = 0
    # for every point
    for i in range(0, len(points)):
        # get the x value
        x = points[i, 0]

        # get the y value
        y = points[i, 1]

        # get the difference, square it and add it to the total
        totalerror += (y - (m*x + b)) ** 2

    # get the average of the total error
    return totalerror / float(len(points))

def gradient_descent_runner(points, initial_b, initial_m, learning_rate, num_iterations):
    # starting b and m
    b = initial_b
    m = initial_m

    # gradient descent
    for i in range(num_iterations):
        [b, m] = step_gradient(b, m, array(points), learning_rate)
    return [b, m]

def step_gradient(b_current, m_current, points, learningrate):
    # Starting points for our gradients
    b_gradient = 0
    m_gradient = 0

    n = float(len(points))
    for i in range(0, len(points)):
        x = points[i, 0]
        y = points[i, 1]
        # direction with respect to b and m
        # computing partial derivatives with our error function
        b_gradient += -(2/n) * (y - (m_current*x + b_current))
        m_gradient += -(2/n) * x * (y - (m_current * x) + b_current)

    # update our b and m values using our partial derivatives
    new_b = b_current - (learningrate * b_gradient)
    new_m = m_current - (learningrate * m_gradient)
    return [new_b, new_m]

def run():
    # Step 1: Collect our data
    points = genfromtxt('../data/data.csv', delimiter=',')

    # Step 2: Define our hyperparameters
    # how fast our model should converge
    learning_rate = 0.0001
    # y = mx + b - Slope formula
    initial_b = 0
    initial_m = 0
    num_iterations = 1000

    # Step 3: Train our model
    print("Starting gradient descent at b = {0}, m = {1}, error = {2}".format(initial_b, initial_m,
                                                                              compute_error_for_line_given_points(
                                                                                initial_b, initial_m, points)))
    [b, m] = gradient_descent_runner(points, initial_b, initial_m, learning_rate, num_iterations)
    print("final gradient descent parameters: b = {1}, m = {2}, error = {3}".format(num_iterations, b, m,
                                                                                compute_error_for_line_given_points(
                                                                                b, m, points)))

if __name__ == '__main__':
    run()


import numpy

class MyModel:

    def __init__(self):
        '''
        Creates a new MyModel
        '''
        # Straight lines described by two parameters:
        # The slop is the angle of the line
        self.slope = 0
        # The intercept moves the line up or down
        self.intercept = 0
        # The history of cost per iteration
        self.cost_history = []

    def predict(self, x):
        '''
        Estimates the target variable from the value of x
        '''
        return x * self.slope + self.intercept

    def get_summary(self):
        '''
        Returns a string that summarises the model
        '''
        return f"y = {self.slope} * x + {self.intercept}"

def cost_function(actual, predicted):
    # use the mean squared differences
    return numpy.average((actual - predicted)**2)

def calculate_gradient(x, actual, predicted):
    """
    This calculates gradient for a linear regession
    using the Mean Squared Error cost function
    """
    # The partial derivatives of MSE are as follows
    # You don't need to be able to do this just yet but
    # it is important to note these give you the two gradients
    # that we need to train our model
    error = predicted - actual
    grad_intercept = numpy.mean(error) * 2
    grad_slope = (x * error).mean() * 2

    return grad_intercept, grad_slope

def gradient_descent(x, y, learning_rate, number_of_iterations):
    """
    Performs gradient descent for a two-parameter function.

    learning_rate: Larger numbers follow the gradient more aggressively
    number_of_iterations: The maximum number of iterations to perform
    """

    model = MyModel()
    # set the initial parameter guess to 0
    model.intercept = 0
    model.slope = 0
    model.cost_history = []

    last_cost = float('inf')

    for i in range(number_of_iterations):
        # Calculate the predicted values
        predicted = model.predict(x)

        # == OPTIMISER ===
        # Calculate the gradient
        grad_intercept, grad_slope = calculate_gradient(x, y, predicted)
        # Upx the estimation of the line
        model.slope -= learning_rate * grad_slope
        model.intercept -= learning_rate * grad_intercept

        estimate = model.predict(x)
        cost = cost_function(y, estimate)
        # Upx the history of costs
        model.cost_history.append(cost_function(y, estimate))

        # Print the current estimation and cost every 100 iterations
        if( i % 100 == 0):

            print("Iteration", i, " Current estimate:", model.get_summary(), f"Cost: {cost}")

            if (cost + 0.001) >= last_cost:
                print("Model training complete after",i, "iterations")
                break
            last_cost = cost

    if i == (number_of_iterations - 1):
        print("Maximum number of iterations reached. Stopping training")

    # # Print the final model
    # print(f"Final estimate:", model.get_summary())
    return model


def play_mh(my_sel=1):

    import numpy as np

    doors = {1, 2, 3}

    prize = np.random.choice(list(doors))

    if prize != my_sel:
        (reveal,) = doors - {prize} - {my_sel}
    else:
        reveal = np.random.choice(list(doors - {prize}))

    (rem,) = doors - {reveal} - {my_sel}

    print(f'Monty reveals a goat behind Door #' + str(reveal))
    print(f'Do you wish to swap to Door #' + str(rem) + '?')

    choice = input('y or n: ')

    if choice == 'y':
        my_sel = rem

    if my_sel == prize:
        win = 'YOU WIN THE CAR!'
        result = 1

    else:
        win = 'you win a goat :('
        result = 0

    print(f'You open Door #' + str(my_sel) + ' and . . . ' + win)

    return result

def stats_mh(swap=True):

    import numpy as np

    doors = {1, 2, 3}

    prize = np.random.choice(list(doors))
    my_sel = np.random.choice(list(doors))

    if prize != my_sel:
        (reveal,) = doors - {prize} - {my_sel}
    else:
        reveal = np.random.choice(list(doors - {prize}))

    (rem,) = doors - {reveal} - {my_sel}

    if swap == True:
        my_sel = rem

    if my_sel == prize:
        result = 1

    else:
        result = 0

    return result


""" Statistical functions and utilities. """
import numpy as np
from scipy.stats import describe
import pandas as pd
import math
from collections import namedtuple
from math import sqrt

def rss(actual, forecast):
    ''' Computes the residuals squares sum (RSS).

    .. math:: RSS = \sum_{i=1}^n (y_i - f(x_i))^2

    :param actual: The target values.
    :param forecast: Predicted values.

    :returns: The RSS.
    '''
    return ((actual - forecast) ** 2).sum()

def mse(actual, forecast):
    ''' Computes the mean squared error.

    .. math:: \operatorname{MSE}=\\frac{1}{n}\\sum_{i=1}^n(\\hat{Y_i} - Y_i)^2

    :param actual: The target values.
    :param forecast: Predicted values.

    :returns: The MSE.

    '''
    return ((actual - forecast) ** 2).mean()

def mpe(actual, forecast):
    ''' Computes the mean percentage error. \
        Non-finite values due to zero division are ignored.

    .. math:: \\text{MPE} = \\frac{100\\%}{n}\\sum_{t=1}^n \\frac{a_t-f_t}{a_t}

    :param actual: The target values.
    :param forecast: Predicted values.

    :returns: The MPE.
    '''
    ratio = (actual - forecast)/actual
    ratio = ratio[np.isfinite(ratio)]

    return 100 * ratio.mean()

def mape(actual, forecast):
    ''' Computes the mean absolute percentage error. \
        Non-finite values due to zero division are ignored.

    .. math::  \mbox{MAPE} = \\frac{1}{n}\\sum_{t=1}^n  \
        \\left|\\frac{A_t-F_t}{A_t}\\right|

    :param actual: The target values.
    :param forecast: Predicted values.

    :returns: The MAPE.
    '''
    ratio = (actual - forecast)/actual
    ratio = ratio[np.isfinite(ratio)]

    return np.abs(ratio).mean()

def wssse(point, center):
    ''' Computes the **Within Set Sum of Squared Error(WSSSE)**.

    :param point: A point for which to calculate the error.
    :param center: The center of a cluster.

    :returns: The WSSSE.
    '''
    return sqrt(sum([x**2 for x in (point - center)]))

def trimean(arr):
    """ Calculates the trimean.

    .. math:: TM=\\frac{Q_1 + 2Q_2 + Q_3}{4}

    :param arr: An array containing numbers.

    :returns: The trimean for the array.

    >>> import numpy as np
    >>> from dautils import stats
    >>> stats.trimean(np.arange(9))
    4.0

    """
    q1 = np.percentile(arr, 25)
    q2 = np.percentile(arr, 50)
    q3 = np.percentile(arr, 75)

    return (q1 + 2 * q2 + q3)/4

def outliers(arr, method='IQR', factor=1.5, percentiles=(5, 95)):
    """ Gets the limits given an array for values to be considered
    outliers.

    :param arr: An array containing numbers.
    :param method: IQR (default) or percentiles.
    :param factor: Factor for the IQR method.
    :param percentiles: A tuple of percentiles.

    :returns: A namedtuple with upper and lower limits.

    >>> import numpy as np
    >>> from dautils import stats
    >>> stats.outliers(a)
    Outlier(min=-48.5, max=149.5)
    """
    Outlier = namedtuple('Outlier', ['min', 'max'])
    outlier = None

    if method == 'IQR':
        q1 = np.percentile(arr, 25)
        q3 = np.percentile(arr, 75)
        iqr = q3 - q1
        outlier = Outlier(q1 - factor * iqr, q3 + factor * iqr)
    elif method == 'percentiles':
        minmax = [np.percentile(arr, p) for p in percentiles]
        outlier = Outlier(minmax[0], minmax[1])
    else:
        raise AssertionError('Unknown method expected IQR or percentiles')

    return outlier

def clip_outliers(arr):
    """ Clips values using limits for values
    to be considered outliers.

    :param arr: An array containing numbers.

    :returns: A clipped values array.

    >>> import numpy as np
    >>> from dautils import stats
    >>> arr = list(range(5))
    >>> outliers = [-100, 100]
    >>> arr.extend(outliers)
    >>> arr
    [0, 1, 2, 3, 4, -100, 100]
    >>> stats.clip_outliers(arr)
    array([ 0.,  1.,  2.,  3.,  4., -4.,  8.])
    """
    amin, amax = outliers(arr)

    return np.clip(arr, amin, amax)

def zscores(x):
    return (x - x.mean())/x.std()

def sqrt_bins(arr):
    """ Uses a rule of thumb to calculate
    the appropriate number of bins for an array.

    :param arr: An array containing numbers.

    :returns: An integer to serve as the number of bins.
    """
    return int(math.sqrt(len(arr)))

def ci(arr, alpha=0.95):
    """ Computes the confidence interval.

    :param arr: An array containing numbers.
    :param alpha: A value in the range 0 - 1\
    that serves as percentiles.

    :returns: The confidence interval.
    """
    interval = 1 - alpha

    minmax = [interval/2, alpha + interval/2]

    return np.percentile(arr, 100 * np.array(minmax))

def jackknife(arr, func, alpha=0.95):
    """ Jackknifes an array with a supplied function.

    :param arr: An array containing numbers.
    :param func: The function to apply.
    :param alpha: A number in the range 0 - 1 used\
    to calculate the confidence interval.

    :returns: Three numbers - the function value for arr,\
    the lower limit of the confidence interval and the\
    upper limit of the confidence interval.
    """
    n = len(arr)
    idx = np.arange(n)

    low, high = ci([func(arr[idx != i]) for i in range(n)], alpha)

    return func(arr), low, high

class Distribution():
    """ Wraps `scipy.stats` distribution classes.
    Most of the methods are only appropriate for continuous distributions.

    :ivar nbins: The number of bins for a histogram plot.
    :ivar train: The train data.
    :ivar test: The test data.
    """
    def __init__(self, data, dist, nbins=20, cutoff=0.75, range=None):
        self.nbins = nbins
        self.train, self.test = self.split(data, cutoff)
        self.hist_values, edges = np.histogram(self.test, bins=self.nbins,
                                               range=range, density=True)
        self.x = 0.5 * (edges[1:] + edges[:-1])
        self.dist = dist
        self.residuals = None
        self.pdf_values = None

    def describe_residuals(self, *args, **kwds):
        """ Describes the residuals of a fit to a distribution.
        Only appropriate for continuous distributions.

        :returns: Statistics for the residuals as a dict.
        """
        _, _, mean, var, skew, kurtosis = describe(self.error(*args, **kwds))
        result = {}
        result['Mean'] = mean
        result['Var'] = var
        result['Skew'] = skew
        result['Kurtosis'] = kurtosis
        result = pd.DataFrame([result])
        result.index = ['Residuals Statistics']

        return result

    def error(self, *args, **kwds):
        """ Computes the residuals of a fit to a distribution.
        Only appropriate for continuous distributions.

        :returns: The residuals of the fit.
        """
        if self.residuals is None:
            self.residuals = self.hist_values - self.pdf(*args, **kwds)

        return self.residuals

    def fit(self, *args):
        """ Fits data to a distribution.
        Only appropriate for continuous distributions.

        :returns: The result of the fit.
        """
        return self.dist.fit(self.train, *args)

    def mean(self):
        return self.train.mean()

    def mean_ad(self, *args, **kwds):
        """ Computes the mean absolute deviation.

        :returns: The mean absolute deviation.
        """
        abserror = np.abs(self.error(*args, **kwds))

        return abserror.mean()

    def pdf(self, *args, **kwds):
        """ Computes the probability distribution function.
        Only appropriate for continuous distributions.

        :returns: The probability distribution function.
        """
        if self.pdf_values is None:
            self.pdf_values = self.dist.pdf(self.x, *args, **kwds)

        return self.pdf_values

    def plot(self, ax):
        """ Plots a histogram of the data and a fit.
        Only appropriate for continuous distributions.
        """
        ax.hist(self.train, bins=self.nbins, normed=True, label='Data')
        ax.plot(self.x, self.pdf_values, label='PDF')
        ax.set_ylabel('Probability')
        ax.legend(loc='best')

    def rmse(self, *args, **kwds):
        """ Computes the root mean square error of the distribution fit.
        Only appropriate for continuous distributions.

        :returns: The RMSE of the fit.
        """
        se = self.error(*args, **kwds) ** 2

        return np.sqrt(se.mean())

    def rvs(self, *args, **kwds):
        """ Generates random values.

        :returns: The generated values.
        """
        return self.dist.rvs(*args, **kwds)

    def split(self, data, cutoff):
        """ Splits data into test and train data.

        :param data: An array containing numbers.
        :param cutoff: A value in the range 0 - 1 used\
        to split the data.

        :returns: Two arrays - the train data and the test data.
        """
        n = len(data)
        n = int(cutoff * n)

        return data[:n], data[n:]

    def var(self):
        return self.train.var()

# TODO Decide on interpolation
class Box():
    ''' Implements the concept of a box we know from box plots. '''
    def __init__(self, arr):
        self.arr = arr
        self.q1 = np.percentile(arr, 25, interpolation='nearest')
        self.q3 = np.percentile(arr, 75, interpolation='nearest')

    def calc_iqr(self):
        """ Calculates the Inter Quartile Range.

        :returns: The interquartile range of the data.

        >>> import numpy as np
        >>> from dautils import stats
        >>> arr = np.array([7, 15, 36, 39, 40, 41])
        >>> box = stats.Box(arr)
        >>> box.calc_iqr()
        25
        """
        return self.q3 - self.q1

    def iqr_from_box(self):
        """ Calculates the number (0 or more) of
        inter quartile ranges (IQR) from the box formed
        by the first and third quartile.

        :returns: A float representing the number of IQRs
        """
        iqr = self.calc_iqr()
        from_q1 = np.abs(self.arr - self.q1)/iqr
        from_q3 = np.abs(self.arr - self.q3)/iqr

        return np.minimum(from_q1, from_q3)


from numpy import zeros

# Define rk4_set function
def rk4_set(f,t0,tf,y0,v0,n):
    h = (tf - t0)/n
    t = zeros(n+1)
    y = zeros(n+1)
    v = zeros(n+1)
    t[0] = t0
    y[0] = y0
    v[0] = v0
    for i in range(n):
        M0 = h * f(t[i],y[i],v[i])
        M1 = h * f(t[i] + h/2.0, y[i] + h/2.0 * v[i], v[i] +  M0/2.0)
        M2 = h * f(t[i] + h/2.0, y[i] + h/2.0 * v[i] + h/4.0 * M0, v[i] + M1/2.0)
        t[i+1] = t[i] + h
        M3 = h * f(t[i+1], y[i] + h * v[i] + h/2.0 * M1, v[i] + M2)
        y[i+1] = y[i] + h * v[i] + h/6.0 * (M0 + M1 + M2)
        v[i+1] = v[i] + (M0 + 2.0 * M1 + 2.0 * M2 + M3)/6.0
    return t,y,v

# Define fuctions
def f(t,y,v):
    return (t**2 - 1.0) * y

# Set initial conditions
t0 = 0
tf = 1
y0 = 1.0
v0 = 0.0
n = 10

# Execute RK4_SET
t, y, v = rk4_set(f,t0,tf,y0,v0,n)

# Print results
print('%5s %8s %8s' % ('t','y','v'))
for i in range(n+1):
    print("%9.4f %9.4f %9.4f" % (t[i],y[i],v[i]))


import numpy

def get_seasonal_forcing(
        period: float, shift: float, prop_amplitude: float, average_value: float
):
    """
    Factory function to get a trigonometric/sinusoidal function (using cosine) to represent seasonal forcing of
    transmission in a model.
    Note that the time unit is not specified (as elsewhere in the repository), so the period is not assumed.

    :param period: float
        Time to complete an entire cycle of forcing
    :param shift: float
        Time at which the peak value will be reached
    :param prop_amplitude: float
        Amplitude of the forcing function relative to the average value
        Note that the amplitude is HALF of the total variation in the function
    :param average_value: float
        Average value of the function, mid-way between peak and trough values
    :return:
        The seasonal forcing function
    """

    amplitude = prop_amplitude * average_value

    def seasonal_forcing(time):
        return \
            numpy.cos(
                (time - shift) * 2. * numpy.pi / period
            ) * \
            amplitude + \
            average_value

    return seasonal_forcing

if __name__ == "__main__":
    period, shift, amplitude, average = 365., 173., 0.5, 0.04
    x_values = numpy.linspace(0., period, 20)
    forcing_function = get_seasonal_forcing(period, shift, amplitude, average)
    for i in x_values:
        print(f"date: {i}, \t value: {forcing_function(i)}")


from sympy import *

'''
aqui puedes crear funciones que recojan resultados de la web y con ello, puedes empezar a
dividir el contenido de la web dependiendo del calculo que quieras mostrar. Un ejemplo es lo que esta abajo
Al usar latex, puedes mostrar un resultado como una imagen. El unicode, lo muestra como si fuera
en assci

Tambien, en vez de una pagina web, puedes empezar a utilizar visual studio code para realizar tus calculos y
hacer graficos de todo tipo y crear estadisticas y todo lo que quieras.

es un lenguaje muy potente para el calculo matematico y personalmente lo uso bastante a la hora de crear estadisticas
y mostrarlas en la web con pandas.
'''

x,y,z = symbols('x y z')
# init_session(use_latex=True) se imprime una especie de foto
init_printing(use_unicode=True)

def muestra():

    # print_mathml(Integral(sqrt(1/x), x))

    # ecuacion = Eq(1 + x,x**2)
    # res=solve(ecuacion, x)

    return print_mathml(Integral(sqrt(1/x), x))


import numpy as np
from collections import Counter
import itertools
from visual import show_tfidf

docs = [
    "it is a good day, I like to stay here",
    "I am happy to be here",
    "I am bob",
    "it is sunny today",
    "I have a party today",
    "it is a dog and that is a cat",
    "there are dog and cat on the tree",
    "I study hard this morning",
    "today is a good day",
    "tomorrow will be a good day",
    "I like coffee, I like book and I like apple",
    "I do not like it",
    "I am kitty, I like bob",
    "I do not care who like bob, but I like kitty",
    "It is coffee time, bring your cup",
]

docs_words = [d.replace(",", "").split(" ") for d in docs]
vocab = set(itertools.chain(*docs_words))
v2i = {v: i for i, v in enumerate(vocab)}
i2v = {i: v for v, i in v2i.items()}

def safe_log(x):
    mask = x != 0
    x[mask] = np.log(x[mask])
    return x

tf_methods = {
        "log": lambda x: np.log(1+x),
        "augmented": lambda x: 0.5 + 0.5 * x / np.max(x, axis=1, keepdims=True),
        "boolean": lambda x: np.minimum(x, 1),
        "log_avg": lambda x: (1 + safe_log(x)) / (1 + safe_log(np.mean(x, axis=1, keepdims=True))),
    }
idf_methods = {
        "log": lambda x: 1 + np.log(len(docs) / (x+1)),
        "prob": lambda x: np.maximum(0, np.log((len(docs) - x) / (x+1))),
        "len_norm": lambda x: x / (np.sum(np.square(x))+1),
    }

def get_tf(method="log"):
    # term frequency: how frequent a word appears in a doc
    _tf = np.zeros((len(vocab), len(docs)), dtype=np.float64)    # [n_vocab, n_doc]
    for i, d in enumerate(docs_words):
        counter = Counter(d)
        for v in counter.keys():
            _tf[v2i[v], i] = counter[v] / counter.most_common(1)[0][1]

    weighted_tf = tf_methods.get(method, None)
    if weighted_tf is None:
        raise ValueError
    return weighted_tf(_tf)

def get_idf(method="log"):
    # inverse document frequency: low idf for a word appears in more docs, mean less important
    df = np.zeros((len(i2v), 1))
    for i in range(len(i2v)):
        d_count = 0
        for d in docs_words:
            d_count += 1 if i2v[i] in d else 0
        df[i, 0] = d_count

    idf_fn = idf_methods.get(method, None)
    if idf_fn is None:
        raise ValueError
    return idf_fn(df)

def cosine_similarity(q, _tf_idf):
    unit_q = q / np.sqrt(np.sum(np.square(q), axis=0, keepdims=True))
    unit_ds = _tf_idf / np.sqrt(np.sum(np.square(_tf_idf), axis=0, keepdims=True))
    similarity = unit_ds.T.dot(unit_q).ravel()
    return similarity

def docs_score(q, len_norm=False):
    q_words = q.replace(",", "").split(" ")

    # add unknown words
    unknown_v = 0
    for v in set(q_words):
        if v not in v2i:
            v2i[v] = len(v2i)
            i2v[len(v2i)-1] = v
            unknown_v += 1
    if unknown_v > 0:
        _idf = np.concatenate((idf, np.zeros((unknown_v, 1), dtype=np.float)), axis=0)
        _tf_idf = np.concatenate((tf_idf, np.zeros((unknown_v, tf_idf.shape[1]), dtype=np.float)), axis=0)
    else:
        _idf, _tf_idf = idf, tf_idf
    counter = Counter(q_words)
    q_tf = np.zeros((len(_idf), 1), dtype=np.float)     # [n_vocab, 1]
    for v in counter.keys():
        q_tf[v2i[v], 0] = counter[v]

    q_vec = q_tf * _idf            # [n_vocab, 1]

    q_scores = cosine_similarity(q_vec, _tf_idf)
    if len_norm:
        len_docs = [len(d) for d in docs_words]
        q_scores = q_scores / np.array(len_docs)
    return q_scores

def get_keywords(n=2):
    for c in range(3):
        col = tf_idf[:, c]
        idx = np.argsort(col)[-n:]
        print("doc{}, top{} keywords {}".format(c, n, [i2v[i] for i in idx]))

tf = get_tf()           # [n_vocab, n_doc]
idf = get_idf()         # [n_vocab, 1]
tf_idf = tf * idf       # [n_vocab, n_doc]
print("tf shape(vecb in each docs): ", tf.shape)
print("\ntf samples:\n", tf[:2])
print("\nidf shape(vecb in all docs): ", idf.shape)
print("\nidf samples:\n", idf[:2])
print("\ntf_idf shape: ", tf_idf.shape)
print("\ntf_idf sample:\n", tf_idf[:2])

# test
get_keywords()
q = "I get a coffee cup"
scores = docs_score(q)
d_ids = scores.argsort()[-3:][::-1]
print("\ntop 3 docs for '{}':\n{}".format(q, [docs[i] for i in d_ids]))

show_tfidf(tf_idf.T, [i2v[i] for i in range(len(i2v))], "tfidf_matrix")


import numpy as np

x = np.array([-1, 1, 2.569858113, 6])  # x coordinates in space
y = np.array([1, 3, 1, -2])  # f(x)

print("Column  0 ")
print(y)

n = len(y)

table = np.zeros([n, n])  # Create a square matrix to hold table
table[::, 0] = y  # first column is y

results = {"table": [], "coefficient": []}

def newtonInterpolation(x, y):
    """ Creates Newton table and extracts coefficients """
    for j in range(1, n):
        results["table"].append([])
        for i in range(n - j):
            # create table by updating other columns
            table[i][j] = (table[i + 1][j - 1] - table[i][j - 1]) / (x[i + j] - x[i])
            results["table"][len(results["table"]) - 1].append(table[i][j])

    coeff = table[0]  # return first row
    for c in coeff:
        results["coefficient"].append(c)

    index = 1
    for i in results["table"]:
        print("Column ", index)
        print(i)
        index += 1

    return table[0]

coeff_vector = newtonInterpolation(x, y)

print("The newton polynom is: ")
for i in range(n):
    print(coeff_vector[i], end=" ")
    for j in range(i):
        print("( x -", x[j], ")", end=" ")
    if (i != n - 1):
        print("+", end=" ")
print()