# https://www.reddit.com/r/options/comments/gvmluu/thought_id_share_a_project_i_just_finished_3d/
# https://pastebin.com/GbfbY6eN
import justpy as jp

'''
Referemces:
- https://www.cboe.com/micro/vix/vixwhite.pdf
- https://en.wikipedia.org/wiki/Greeks_(finance)
- https://en.wikipedia.org/wiki/Bisection_method
- https://en.wikipedia.org/wiki/Newton%27s_method
- https://quant.stackexchange.com/questions/7761/a-simple-formula-for-calculating-implied-volatility
- https://www.risklatte.xyz/Articles/QuantitativeFinance/QF135.php
- https://jakevdp.github.io/PythonDataScienceHandbook/04.12-three-dimensional-plotting.html
- https://stackoverflow.com/questions/42677160/matplotlib-3d-scatter-plot-date
'''
import datetime as dt
import math as m

import matplotlib.pyplot as plt
import yfinance as yf
from mpl_toolkits import mplot3d
from scipy.stats import norm

plt.switch_backend('TkAgg')

'''
Template for an option contract.
'''

class Option:
    '''
    Option contract 'object' according to the vanilla Black-Scholes-Merton model for the price of a European option.

    Required parameters:

    - current date
        Starting date to use for the calculation of time left until expiration,
        Must be a string with the format 'YYYY-MM-DD'.
    - current time
        Starting time to use for the calculation of time left until expiration.
        Must be a string in 24 hour time with the format 'HH:MM'.
        Time to expiration is calculated in minutes, and then converted to years.
    - opt_type
        The type of option.
        Must be a string with the value of either 'call' or 'put'.
    - exp
        Expiration date of the option.
        Must be a string with the format 'YYYY-MM-DD'
    - V
        Price of the option.
        Must be a numerical value.
    - S
        Current price of the underlying stock or ETF.
        Must be a numerical value.
    - K
        Strike price of the option.
        Must be a numerical value.
    - r
        Annualized risk-free interest rate. Conventional wisdom is to use the interest rate of
        treasury bills with time left to maturity equal to the length of time left to expiration
        of the option.
        Must be a numerical value in decimal form. Example: 1% would be entered as 0.01

    Optional parameters. Set these to 0 unless they are relevant (analyzing an entire option chain for example).

    - volume
        Number of contracts traded today.
        Must be an integer.
    - openInterest
        Number of outstanding contracts of the same type, strike, and expiration.
        Must be an integer.
    - bid
        Current bid price for the contract.
        Must be a numerical value.
    - ask
        Current asking price for the contract.
        Must be a numerical value.

    **How this works**

    This is what takes place When input values are passed and a contract is instantiated:

        1) The following values are bound to the contract, available to call at any future point:
           (For example: if you have created an option called "x", the expression "x.K" will return
           the strike price of the option.)
            - V
            - S
            - K
            - t
            - r
            - itm  (returns True if the option is in the money or False if it is out of the money)
            - date (current date that was entered into the contract)
            - time (current time that was entered into the contract)
            - exp  (expiration date of the contract)
            - volume
            - openInterest
            - bid
            - ask

        2) Implied volatility is iteratively calculated using a bisection algorithm. The Newton-Raphson
           algorithm was initially used because it is very fast, but has problems converging for deep ITM
           options, even with a good initial guess (analytical approximation).
           - can be called like "x.IV" using the option called "x" from the previous example.

        3) The following greeks are calculated and also available to call:
            - delta  (change in option price with respect to the change in underlying price)
            - gamma  (change in delta with respect to the underlying price)
            - theta  (change in option price with respect to time)
            - vega   (change in option price with respect to implied volatility)
            - rho    (change in option price with respect to the risk-free rate)
            - Lambda (capitalized because python has a built in 'lambda' function,
                      is the "leverage" that the contract provides)

            **Note: the formulas for the higher order greeks have not been rigorously verified for correctness

            - vanna  (change in delta with respect to implied volatility)
            - charm  (change in delta with respect to time)
            - vomma  (change in vega with respect to implied volatility)
            - veta   (change in vega with respect to time)
            - speed  (change in gamma with respect to the underlying price)
            - zomma  (change in gamma with respect to implied volatility)
            - color  (change in gamma with respect to time)
            - ultima (change in vomma with respect to volatility)

    The following functions can also be called on the option:
    (look at the functions themselves for further explanation)

        - theoPrice
        - theoPnL
        - impliedPrices

    This class can also be modified to take the dividend yield as an input. It is currently not used and set to 0.
    '''
    # TODO Make the impliedPrices function in terms of standard deviations
    # TODO Create function that steps the contract through various times, IVs, and Ss

    def __init__(self, current_date, current_time, opt_type, exp, V, S, K, r, volume, openInterest, bid, ask):
        '''
        Sets all the attributes of the contract.
        '''
        self.opt_type = opt_type.lower()

        if self.opt_type == 'call':

            if K < S:
                self.itm = True

            else:
                self.itm = False

        elif self.opt_type == 'put':

            if S < K:
                self.itm = True

            else:
                self.itm = False

        self.exp = exp
        self.V = round(V, 2)
        self.S = round(S, 2)
        self.K = round(K, 2)
        self.date = current_date
        self.time = current_time
        self.exp = exp
        self.t = self.__t(current_date, current_time, exp)
        self.r = r
        self.q = 0
        self.volume = volume
        self.openInterest = openInterest
        self.bid = bid
        self.ask = ask
        vol_params = self.__BSMIV(self.S, self.t)
        self.IV = vol_params[0]
        self.vega = vol_params[1]
        self.delta = self.__BSMdelta()
        self.gamma = self.__BSMgamma()
        self.theta = self.__BSMtheta()
        self.rho = self.__BSMrho()
        self.Lambda = self.__BSMlambda()
        self.vanna = self.__BSMvanna()
        self.charm = self.__BSMcharm()
        self.vomma = self.__BSMvomma()
        self.veta = self.__BSMveta()
        self.speed = self.__BSMspeed()
        self.zomma = self.__BSMzomma()
        self.color = self.__BSMcolor()
        self.ultima = self.__BSMultima()

    def __repr__(self):
        '''
        Basic contract information.
        '''
        return '${:.2f} strike {} option expiring {}.'.format(self.K,
                                                              self.opt_type,
                                                              self.exp)

    def __t(self, current_date, current_time, exp):
        '''
        Calculates the number of minutes to expiration, then converts to years.
        Minutes are chosen because the VIX does this.
        '''
        hr, minute = 17, 30
        year, month, day = [int(x) for x in exp.split('-')]
        exp_dt = dt.datetime(year, month, day, hr, minute)

        hr, minute = [int(x) for x in current_time.split(':')]
        year, month, day = [int(x) for x in current_date.split('-')]
        current_dt = dt.datetime(year, month, day, hr, minute)

        days = 24*60*60*(exp_dt - current_dt).days
        seconds = (exp_dt - current_dt).seconds

        return (days + seconds) / (365*24*60*60)

    def __d1(self, S, t, v):
        '''
        Struggling to come up with a good explanation.
        It's an input into the cumulative distribution function.
        '''
        K = self.K
        r = self.r
        q = self.q

        return ((m.log(S / K) + (r - q + 0.5*v**2)*t)) / (v*m.sqrt(t))

    def __d2(self, S, t, v):
        '''
        Struggling to come up with a good explanation.
        It's an input into the cumulative distribution function.
        '''
        d1 = self.__d1(S, t, v)

        return d1 - v*m.sqrt(t)

    def __pvK(self, t):
        '''
        Present value (pv) of the strike price (K)
        '''
        K = self.K
        r = self.r
        return K*m.exp(-r*t)

    def __pvS(self, S, t):
        '''
        Present value (pv) of the stock price (S)
        '''
        q = self.q

        return S*m.exp(-q*t)

    def __phi(self, x): return norm.pdf(x)

    def __N(self, x): return norm.cdf(x)

    def __BSMprice(self, S, t, v):
        '''
        Black-Scholes-Merton price of a European call or put.
        '''
        K = self.K
        pvK = self.__pvK(t)
        pvS = self.__pvS(S, t)

        if t != 0:

            if self.opt_type == 'call':
                Nd1 = self.__N(self.__d1(S, t, v))
                Nd2 = -self.__N(self.__d2(S, t, v))

            elif self.opt_type == 'put':
                Nd1 = -self.__N(-self.__d1(S, t, v))
                Nd2 = self.__N(-self.__d2(S, t, v))

            return round(pvS*Nd1 + pvK*Nd2, 2)

        elif t == 0:

            if self.opt_type == 'call':
                intrinsic_value = max(0, S - K)

            elif self.opt_type == 'put':
                intrinsic_value = max(0, K - S)

            return round(intrinsic_value, 2)

    # First order greek
    def __BSMvega(self, S, t, v):
        '''
        First derivative of the option price (V) with respect to implied volatility (v or IV).

        - For a 1% increase in IV, how much will the option price rise?
        '''
        pvK = self.__pvK(t)
        phid2 = self.__phi(self.__d2(S, t, v))

        return round((pvK*phid2*m.sqrt(t)) / 100, 4)

    def __BSMIV(self, S, t):
        '''
        Volatility implied by the price of the option (v/IV).

        - For the option price (V) to be fair, how volatile does the underlying (S) need to be?

        Note: this function also returns vega, because initially the Newton-Raphson method was used, and
        that requires vega to be solved at the same time. I didn't feel like rewriting things so I just
        tacked on the vega calculation after IV was calculated.
        '''
        V = self.V
        # K = self.K

        # Bisection method

        # We are trying to solve the error function which is the difference between the estimated price and the actual price
        # V_est - V
        # We need to generate two initial guesses at IV, one with a positive error and one with a negative error
        # Should be reasonable to assume that vol will be somewhere between 1% and 2000%
        v_lo = 0
        v_hi = 20
        v_mid = 0.5*(v_lo + v_hi)
        V_mid = self.__BSMprice(S, t, v_mid)
        error = V_mid - V

        # Keep iterating until the error in estimated price is less than a cent
        while v_hi - v_lo >= 0.1 / 100:

            if error > 0:
                v_hi = v_mid

            elif error < 0:
                v_lo = v_mid

            elif error == 0:
                break

            v_mid = 0.5*(v_hi + v_lo)
            V_mid = self.__BSMprice(S, t, v_mid)
            error = V_mid - V

        vega = self.__BSMvega(S, t, v_mid)

        # Newton-Raphson method
        # Kind of a mess because I was trying different things to get it to converge better for certain deep ITM options.
        # v = (m.sqrt(2*m.pi) / t)*(V / S)
        # min_err = 1000000
        # best_v = 0
        # best_vega = 0

        # i = 0
        # if self.opt_type == 'call':
        #     if V < S - K:
        #         V = S - K

        # elif self.opt_type == 'put':
        #     if V < K - S:
        #         V = K - S

        # while i < 5000:
        #     V_est = max(self.__BSMprice(S, t, v), 0.01)
        #     vega = max(self.__BSMvega(S, t, v), 0.0001)
        #     error = V_est - V

        #     if abs(error) < min_err:
        #         min_err = abs(error)
        #         best_v = v
        #         best_vega = vega

        #     if error == 0:
        #         break

        #     else:
        #         v = v - (error/(vega*100))*0.25

        #     if (i == 4999) & (error != 0):
        #         print('error in IV loop')

        #     i += 1

        # return round(best_v, 4), round(best_vega, 4)
        return round(v_mid, 4), round(vega, 4)

    # First order greek
    def __BSMdelta(self):
        '''
        First derivative of the option price (V) with respect to
        the underlying price (S).

        - For a $1 increase in S, how much will V rise?

        - Also is the risk neutral probability S is at or below K by expiration.

        Note that a risk neutral probability is not a real life probability.
        It is simply the probability that would exist if it were possible to
        create a completely risk free portfolio.
        '''
        S = self.S
        t = self.t
        v = self.IV

        if self.opt_type == 'call':
            Nd1 = self.__N(self.__d1(S, t, v))

        elif self.opt_type == 'put':
            Nd1 = -self.__N(-self.__d1(S, t, v))

        return round(Nd1, 4)

    # Second order greek
    def __BSMgamma(self):
        '''
        First dertivative of delta with respect to the price of the underlying (S).
        Second derivative of the option price (V) with respect to the stock price.

        - For a $1 increase in the stock price, how much will delta increase?
        '''
        S = self.S
        t = self.t
        v = self.IV
        pvK = self.__pvK(t)
        phid2 = self.__phi(self.__d2(S, t, v))

        return round((pvK*phid2) / (S**2*v*m.sqrt(t)), 4)

    # First order greek
    def __BSMtheta(self):
        '''
        First derivative of the option price (V) with respect to time (t).

        - How much less will the option be worth tomorrow?
        '''
        S = self.S
        t = self.t
        v = self.IV
        pvK = self.__pvK(t)
        pvS = self.__pvS(S, t)

        if self.opt_type == 'call':
            phid1 = self.__phi(self.__d1(S, t, v))
            r = -self.r
            q = self.q
            Nd1 = self.__N(self.__d1(S, t, v))
            Nd2 = self.__N(self.__d2(S, t, v))

        elif self.opt_type == 'put':
            phid1 = self.__phi(-self.__d1(S, t, v))
            r = self.r
            q = -self.q
            Nd1 = self.__N(-self.__d1(S, t, v))
            Nd2 = self.__N(-self.__d2(S, t, v))

        return round((-((pvS*phid1*v) / (2*m.sqrt(t))) + r*pvK*Nd2 + q*pvS*Nd1) / 365, 4)

    # First order greek
    def __BSMrho(self):
        '''
        First derivative of the option price (V) with respect to the risk free interest rate (r).

        - For a 1% change in interest rates, by how many dollars will the value of the option change?
        '''
        S = self.S
        t = self.t
        v = self.IV

        if self.opt_type == 'call':
            pvK = self.__pvK(t)
            Nd2 = self.__N(self.__d2(S, t, v))

        elif self.opt_type == 'put':
            pvK = -self.__pvK(t)
            Nd2 = self.__N(-self.__d2(S, t, v))

        return round((pvK*t*Nd2) / 100, 4)

    # First order greek
    def __BSMlambda(self):
        '''
        Measures the percentage change in the option price (V) per percentage change
        in the price of the underlying (S).

        - How much leverage does this option have?
        '''
        V = self.V
        S = self.S
        delta = self.delta

        return round(delta*(S / V), 4)

    # Second order greek
    def __BSMvanna(self):
        '''
        First derivative of delta with respect to implied volatility.

        - If volatility changes by 1%, how much will delta change?
        '''
        V = self.V
        S = self.S
        t = self.t
        v = self.IV
        d1 = self.__d1(S, t, v)

        return round((V / S)*(1 - (d1 / (v*m.sqrt(t)))), 4)

    # Second order greek
    def __BSMcharm(self):
        '''
        First derivative of delta with respect to time.

        - How much different will delta be tomorrow if everything else stays the same?
        - Also can think of it as 'delta decay'
        '''
        S = self.S
        t = self.t
        r = self.r
        v = self.IV
        pv = m.exp(-self.q*t)
        phid1 = self.__phi(self.__d1(S, t, v))
        d2 = self.__d2(S, t, v)
        mess = (2*(r - self.q)*t - d2*v*m.sqrt(t)) / (2*t*v*m.sqrt(t))

        if self.opt_type == 'call':
            q = self.q
            Nd1 = self.__N(self.__d1(S, t, v))

        elif self.opt_type == 'put':
            q = -self.q
            Nd1 = self.__N(-self.__d1(S, t, v))

        return round((q*pv*Nd1 - pv*phid1*mess) / 365, 4)

    # Second order greek
    def __BSMvomma(self):
        '''
        First derivative of vega with respect to implied volatility.
        Also the second derivative of the option price (V) with respect to
        implied volatility.

        - If IV changes by 1%, how will vega change?
        '''
        S = self.S
        t = self.t
        vega = self.vega
        v = self.IV
        d1 = self.__d1(S, t, v)
        d2 = self.__d2(S, t, v)

        return round((vega*d1*d2) / v, 4)

    # Second order greek
    def __BSMveta(self):
        '''
        First derivative of vega with respect to time (t).

        - How much different will vega be tomorrow if everything else stays the same?
        '''
        S = self.S
        t = self.t
        v = self.IV
        pvS = self.__pvS(S, t,)
        d1 = self.__d1(S, t, v)
        d2 = self.__d2(S, t, v)
        phid1 = self.__phi(d1)
        r = self.r
        q = self.q
        mess1 = ((r - q)*d1) / (v*m.sqrt(t))
        mess2 = (1 + d1*d2) / (2*t)

        return round((-pvS*phid1*m.sqrt(t)*(q + mess1 - mess2)) / (100*365), 4)

    # Third order greek
    def __BSMspeed(self):
        '''
        First derivative of gamma with respect to the underlying price (S).

        - If S increases by $1, how will gamma change?
        '''
        gamma = self.gamma
        S = self.S
        v = self.IV
        t = self.t
        d1 = self.__d1(S, t, v)

        return round(-(gamma / S)*((d1 / (v*m.sqrt(t))) + 1), 4)

    # Third order greek
    def __BSMzomma(self):
        '''
        First derivative of gamma with respect to implied volatility.

        - If volatility changes by 1%, how will gamma change?
        '''
        gamma = self.gamma
        S = self.S
        t = self.t
        v = self.IV
        d1 = self.__d1(S, t, v)
        d2 = self.__d2(S, t, v)

        return round(gamma*((d1*d2 - 1) / v), 4)

    # Third order greek
    def __BSMcolor(self):
        '''
        First derivative of gamma with respect to time.

        - How much different will gamma be tomorrow if everything else stays the same?
        '''
        S = self.S
        t = self.t
        r = self.r
        v = self.IV
        q = self.q
        pv = m.exp(-q*t)
        d1 = self.__d1(S, t, v)
        d2 = self.__d2(S, t, v)
        phid1 = self.__phi(d1)
        mess = ((2*(r - q)*t - d2*v*m.sqrt(t)) / (v*m.sqrt(t)))*d1

        return round((-pv*(phid1 / (2*S*t*v*m.sqrt(t)))*(2*q*t + 1 + mess)) / 365, 4)

    # Third order greek
    def __BSMultima(self):
        '''
        First derivative of vomma with respect to volatility.

        - ...why? At this point it just seems like an exercise in calculus.
        '''
        vega = self.vega
        S = self.S
        t = self.t
        v = self.IV
        d1 = self.__d1(S, t, v)
        d2 = self.__d2(S, t, v)

        return round((-vega/(v**2))*(d1*d2*(1 - d1*d2) + d1**2 + d2**2), 4)

    def theoPrice(self, date, S, v):
        '''
        Calculates the theoretical price of the option given:

        - date 'YYYY-MM-DD'
        - underlying price (S)
        - implied volatility (v)
        '''
        year, month, day = date.split('-')
        date = dt.datetime(int(year), int(month), int(day))
        year, month, day = self.exp.split('-')
        exp = dt.datetime(int(year), int(month), int(day))

        t = (exp - date).days / 365

        return self.__BSMprice(S, t, v)

    def theoPnL(self, date, S, v):
        '''
        Calculates the theoretical profit/loss given:

        - date 'YYYY-MM-DD'
        - underlying price (S)
        - implied volatility (v)
        '''
        return round(self.theoPrice(date, S, v) - self.V, 2)

    def impliedPrices(self, show):
        '''
        Returns a tuple containing two lists.

        - list[0] = dates from tomorrow until expiration
        - list[1] = price on each date

        - show = True  | plots prices over time
        - show = False | does not plot prices over time

        If implied volatility is 20% for an option expiring in 1 year, this means that
        the market is implying 1 year from now, there is roughly a 68% chance the underlying
        will be 20% higher or lower than it currently is.

        We can scale this annual number to any timeframe of interest according to v*sqrt(days/365).
        The denominator is 365 because calendar days are used for simplicity.

        If only trading days were to be taken into account, the denominator would be 252.
        '''
        S = self.S
        v = self.IV
        t = self.t

        days = [i + 1 for i in range(int(t*365))]

        if self.opt_type == 'call':
            prices = [round(S + v*m.sqrt(day / 365)*S, 2) for day in days]

        elif self.opt_type == 'put':
            prices = [round(S - v*m.sqrt(day / 365)*S, 2) for day in days]

        today = dt.datetime.now()
        dates = [(today + dt.timedelta(days=day)).date() for day in days]

        if show == True:
            plt.title('Implied moves according to a:\n{}\nLast price: ${:.2f} | IV: {:.2f}%'.format(
                self.__repr__(), self.V, 100*self.IV))

            plt.xlabel('Date')
            plt.ylabel('Spot price ($)')
            plt.plot(dates, prices)
            plt.show(block=True)

        return dates, prices

'''
Plotting
'''

def date_time_input():
    '''
    Get either current time or user specified time.
    It's a lot of code and used for both plot mode and single option mode.

    returns current_date 'YYYY-MM-DD', current_time 'HH:MM'

    Note: this is done better in single_option_input() for the expiration but I don't feel like refactoring right now
    '''
    # Print description of the date and time to be input
    print('\n"Time" refers to the time to use for the time to expiration calculation.')
    print('Example: if it is currently the weekend, and you want to see the metrics')
    print('based on EOD Friday (which is what the prices will be from), enter "1",')
    print('and enter the date of the most recent Friday, with 16:00 as the time (4pm).\n')

    # Get date
    which_datetime_string = 'Enter 0 to use current date/time, 1 to specify date/time: '
    which_datetime = input(which_datetime_string)

    datetime_options = ['0', '1']

    # If incorrect input is supplied, loop until the input is correct
    while which_datetime not in datetime_options:
        which_datetime = input(which_datetime_string)

    # If the current date/time is to be used, get the current date/time
    if which_datetime == '0':
        now = dt.datetime.now()
        current_date = str(now.date())
        current_time = '{}:{}'.format(now.time().hour, now.time().minute)

    # If the date/time is to be specified
    elif which_datetime == '1':
        # Get current date
        current_date_string = 'Enter current date [YYYY-MM-DD]: '
        current_date = input(current_date_string)

        try:
            # Check if the date is in the correct format
            year, month, day = [int(x) for x in current_date.split('-')]
            dt.datetime(year, month, day)

        except:
            # If not, loop until the format is correct
            stop_loop = 0

            while stop_loop == 0:
                current_date = input(current_date_string)

                try:
                    year, month, day = [int(x)
                                        for x in current_date.split('-')]
                    dt.datetime(year, month, day)
                    stop_loop = 1

                except:
                    stop_loop = 0
        # Get current time
        current_time_string = 'Enter current 24H time [HH:MM]: '
        current_time = input(current_time_string)

        try:
            # Check if the time is in the correct format
            hour, minute = [int(x) for x in current_time.split(':')]
            dt.datetime(year, month, day, hour, minute)

        except:
            # If not, loop until the format is correct
            stop_loop = 0

            while stop_loop == 0:
                current_time = input(current_time_string)

                try:
                    hour, minute = [int(x) for x in current_time.split(':')]
                    dt.datetime(year, month, day, hour, minute)
                    stop_loop = 1

                except:
                    stop_loop = 0

    return current_date, current_time

def multi_plot_input():
    '''
    User input for:
        - ticker
        - params
        - price_type
        - opt_type
        - current_date
        - current_time
        - r
    Returns each of the parameters in a tuple
    '''
    # Get ticker
    ticker_string = '\nEnter ticker symbol: '
    ticker = input(ticker_string).upper()

    try:
        # Try getting the first options expiration of the ticker
        # If this succeeds we know it is a valid, optionable ticker symbol
        yf.Ticker(ticker).options[0]

    except:
        # Run an input loop until a valid, optionable ticker is input
        stop_loop = 0

        while stop_loop == 0:
            print('Ticker symbol is either invalid or not optionable.')
            ticker = input(ticker_string).upper()

            try:
                yf.Ticker(ticker).options[0]
                stop_loop = 1

            except:
                stop_loop = 0

    # Print out a description of what can be plotted
    print('\nStandard Parameters     |     Nonstandard Parameters')
    print('    last or mid price   |       rho    [dV/dr]')
    print('    IV                  |       charm  [ddelta/dt]')
    print('    delta               |       veta   [dvega/dt]')
    print('    theta               |       color  [dgamma/dt]')
    print('    volume              |       speed  [dgamma/dS]')
    print('    vega                |       vanna  [ddelta/dv]')
    print('    gamma               |       vomma  [dvega/dv]')
    print('    Open Interest       |       zomma  [dgamma/dv]\n')

    # Get parameters to plot
    param_string = 'Enter 0 for standard parameters, 1 for nonstandard: '
    param_type = input(param_string)

    # If incorrect input is supplied, loop until the input is correct
    while param_type not in ['0', '1']:
        param_type = input(param_string)

    # Set parameters
    if param_type == '0':
        params = [
            'V',
            'IV',
            'delta',
            'theta',
            'volume',
            'vega',
            'gamma',
            'openInterest'
        ]

    elif param_type == '1':
        print('\n**Warning: accuracy of higher order greeks has not been verified**\n')
        params = [
            'rho',
            'charm',
            'veta',
            'color',
            'speed',
            'vanna',
            'vomma',
            'zomma'
        ]

    # Get price type
    price_type_string = 'Enter price to use for calcs [mid or last]: '
    price_type = input(price_type_string)

    # If incorrect input is supplied, loop until the input is correct
    while price_type not in ['mid', 'last']:
        price_type = input(price_type_string)

    # Get option type
    opt_type_string = 'Enter option type [calls, puts, or both]: '
    opt_type = input(opt_type_string)

    # If incorrect input is supplied, loop until the input is correct
    while opt_type not in ['calls', 'puts', 'both']:
        opt_type = input(opt_type_string)

    # Get moneyness of options to plot
    moneyess_string = 'Enter the moneyness to plot [itm, otm, or both]: '
    moneyness = input(moneyess_string).lower()

    # If incorrect input is supplied, loop until the input is correct
    while moneyness not in ['itm', 'otm', 'both']:
        moneyness = input(moneyess_string).lower()

    # Get risk free rate
    # If incorrect input is supplied, loop until the input is correct

    stop_loop = 0

    while stop_loop == 0:
        r = input('Enter the risk-free rate: ')

        try:
            r = float(r)
            stop_loop = 1

        except:
            continue

    # Get date/time
    current_date, current_time = date_time_input()

    #  Return the parameters
    return ticker, params, price_type, opt_type, moneyness, current_date, current_time, r

def single_option_input():
    # Get option type
    opt_type_string = 'Enter option type [put or call]: '
    opt_type = input(opt_type_string)

    while opt_type not in ['put', 'call']:
        opt_type = input(opt_type_string)

    # Get expiration date
    exp_string = 'Enter expiration date [YYYY-MM-DD]: '
    exp = input(exp_string)
    stop_loop = 0

    while stop_loop == 0:

        try:
            year, month, day = [int(x) for x in exp.split('-')]
            dt.datetime(year, month, day)
            stop_loop = 1

        except:
            exp = input(exp_string)

    # Get option price
    V_string = 'Enter option price: '
    V = input(V_string)
    stop_loop = 0

    while stop_loop == 0:

        try:
            V = float(V)
            stop_loop = 1

        except:
            V = input(V_string)

    # Get stock price
    S_string = 'Enter the stock price: '
    S = input(S_string)
    stop_loop = 0

    while stop_loop == 0:

        try:
            S = float(S)
            stop_loop = 1

        except:
            S = input(S_string)

    # Get strike price
    K_string = 'Enter the strike price: '
    K = input(K_string)
    stop_loop = 0

    while stop_loop == 0:

        try:
            K = float(K)
            stop_loop = 1

        except:
            K = input(K_string)

    #  Get risk-free rate
    r_string = 'Enter the risk free rate: '
    r = input(r_string)
    stop_loop = 0

    while stop_loop == 0:

        try:
            r = float(r)
            stop_loop = 1

        except:
            r = input(r_string)

    # Get date/time
    current_date, current_time = date_time_input()

    return current_date, current_time, opt_type, exp, V, S, K, r

def get_options(current_date, current_time, ticker, opt_type, price_type, r):
    '''
    See the "Option" class for an explanation of the inputs.

    Returns a dictionary of calls, puts, or both.
    Each option type is itself a dictionary of lists.

    Example:
        main_dict[calls] = calls_dict
        calls_dict['2020-06-19 call options'] = list of call option objects expiring Jun19 2020
    '''
    # Get ticker object
    ticker = yf.Ticker(ticker)

    S = ticker.info['regularMarketPrice']

    # Get exps
    exps = ticker.options

    # This dict will hold all the objects across dates
    call_objects = {}
    put_objects = {}

    # Get option chains
    for exp in exps:
        # For some reason the expiration dates that Yahoo! returns are a day early
        # This just adds a day to correct it
        year, month, day = [int(x) for x in exp.split('-')]
        corrected_exp = str(
            (dt.datetime(year, month, day) + dt.timedelta(days=1)).date())

        print('Getting data for options expiring {}...'.format(corrected_exp))

        # API call to Yahoo! for the option chain
        option_chain = ticker.option_chain(exp)

        # Split off the puts and calls dataframes
        calls = option_chain.calls.fillna(0)
        puts = option_chain.puts.fillna(0)

        # Delete the original combined chain to save memory
        del option_chain

        # Make a temporary list to hold the option objects
        single_call_chain = []
        single_put_chain = []

        # Loop through call strikes and create option objects for each contract
        for i in range(len(calls)):
            # Get parameters
            K = calls['strike'].iloc[i]
            volume = calls['volume'].iloc[i]
            openInterest = calls['openInterest'].iloc[i]
            bid = calls['bid'].iloc[i]
            ask = calls['ask'].iloc[i]
            last = calls['lastPrice'].iloc[i]

            # Get mid or last price
            if price_type == 'mid':
                V = max(round((bid + ask) / 2, 2), 0.01)

            else:
                V = max(last, 0.01)

            # Create and add the call object to the temp list
            single_call_chain.append(Option(current_date,
                                            current_time,
                                            'call',
                                            corrected_exp,
                                            V, S, K, r,
                                            volume,
                                            openInterest,
                                            bid, ask))

        # Loop through put strikes and create option objects for each contract
        for i in range(len(puts)):
            # Get parameters
            K = puts['strike'].iloc[i]
            volume = puts['volume'].iloc[i]
            openInterest = puts['openInterest'].iloc[i]
            bid = puts['bid'].iloc[i]
            ask = puts['ask'].iloc[i]
            last = puts['lastPrice'].iloc[i]

            # Get mid or last price
            if price_type == 'mid':
                V = max(round((bid + ask) / 2, 2), 0.01)

            else:
                V = max(last, 0.01)

            # Create and add the call object to the temp list
            single_put_chain.append(Option(current_date,
                                           current_time,
                                           'put',
                                           corrected_exp,
                                           V, S, K, r,
                                           volume,
                                           openInterest,
                                           bid, ask))

        # Add the call and put temp lists to the dictionary
        # Indexed by expiration date
        call_objects['{} {} options'.format(
            corrected_exp, 'call')] = single_call_chain

        put_objects['{} {} options'.format(
            corrected_exp, 'put')] = single_put_chain

        # Delete puts and calls
        del calls
        del puts

    # Return the requested option type(s)
    if opt_type == 'calls':
        return {'calls': call_objects}

    elif opt_type == 'puts':
        return {'puts': put_objects}

    else:
        return {'calls': call_objects, 'puts': put_objects}

def generate_plots(ticker, options, moneyness, params, price_type, current_date, current_time, *, scale=1.5, web=False):
    # Added scale and web keyword arguments
    # Set chart layout
    layout = [2, 4]
    rows = layout[0]
    cols = layout[1]
    fig = plt.figure(figsize=(6.4 * scale, 4.8 * scale))

    # Make subplot titles from parameters
    titles = [x.capitalize() if x != 'IV' else x for x in params]

    if 'V' in titles:
        titles[params.index('V')] = price_type.capitalize() + ' Price'
        titles[params.index('openInterest')] = 'Open Interest'

    # See which option types are to be plotted
    opt_types = list(options.keys())

    # Gets set to False after the very first iteration of parameters
    # For initially setting up plots
    first_iter = True

    # Initialize list of expirations for the y-axis labels
    exps = []
    # Counter to make sure that all expirations are added to the list once
    k = 0

    # The main title depends on which option types are plotted
    # The calls and puts portions will be appended as needed
    main_title = []

    # Loop through calls and/or puts
    for opt_type in opt_types:
        # For debugging
        # print(opt_type)

        # Set colors and create title string portions
        if opt_type == 'calls':
            itm_color = 'green'
            otm_color = 'blue'

            if moneyness == 'itm':
                main_title.append('\nITM calls={}'.format(itm_color))

            elif moneyness == 'otm':
                main_title.append('\nOTM calls={}'.format(otm_color))

            elif moneyness == 'both':
                main_title.append(
                    '\nITM calls={}, OTM calls={}'.format(itm_color, otm_color))

        else:
            itm_color = 'red'
            otm_color = 'purple'

            if moneyness == 'itm':
                main_title.append('\nITM puts={}'.format(itm_color))

            elif moneyness == 'otm':
                main_title.append('\nOTM puts={}'.format(otm_color))

            elif moneyness == 'both':
                main_title.append(
                    '\nITM puts={}, OTM puts={}'.format(itm_color, otm_color))

        # Get all the expirations of the current type
        option_chains = options[opt_type]

        # Get the names of the chains in the current type
        single_chains = list(option_chains.keys())

        # Counter for the y-axis (dates)
        # Dates are initially plotted as integers
        # After the whole plot is done, map the date labels to the integers
        j = 0

        # Loop through expirations
        for chain in single_chains:
            # For debugging
            # print(chain)

            # Only add expirations during the loop of the first option type
            if k == 0:
                exp = chain.split(' ')[0]
                exps.append(exp[0:])

            # Pull a single expiration's option chain
            single_exp_options = option_chains[chain]
            # Set/reset counter for the subplots (parameters)
            i = 1

            # Loop through parameters
            for param in params:
                # Initialize lists for itm and otm data
                x_itm = []
                y_itm = []
                z_itm = []

                x_otm = []
                y_otm = []
                z_otm = []

                # If this is the first time parameters are being looped through,
                # set up a subplot for each parameter
                if first_iter == True:
                    # For debugging
                    # print('Create axis {}:{} {} {}'.format(
                    #     i, exp, opt_type[0:-1], param))
                    exec("ax{} = fig.add_subplot({}{}{}, projection='3d')".format(i,
                                                                                rows,
                                                                                cols,
                                                                                i))
                    eval("ax{}.set_title(titles[{}])".format(i, i - 1))
                    eval("ax{}.set_xlabel('strike')".format(i))
                    eval("ax{}.view_init(45, -65)".format(i))

                else:
                    # For debugging
                    # print('Plot on axis {}:{} {} {}'.format(
                    #     i, exp, opt_type[0:-1], param))
                    pass

                # Loop through each individual option in the expiration
                for option in single_exp_options:
                    # For debugging
                    # print(option)
                    # Only need S once for the title string, but oh well
                    S = option.S
                    itm = option.itm

                    # Add itm and otm data to the appropriate lists
                    if itm == True:
                        x_itm.append(option.K)
                        y_itm.append(j)
                        eval('z_itm.append(option.{})'.format(param))

                    else:
                        x_otm.append(option.K)
                        y_otm.append(j)
                        eval('z_otm.append(option.{})'.format(param))

                # Plot itm and/or otm data for a single expiration
                if moneyness == 'itm':
                    eval("ax{}.plot(x_itm, y_itm, z_itm, '{}')".format(
                        i, itm_color))

                elif moneyness == 'otm':
                    eval("ax{}.plot(x_otm, y_otm, z_otm, '{}')".format(
                        i, otm_color))

                elif moneyness == 'both':
                    eval("ax{}.plot(x_itm, y_itm, z_itm, '{}')".format(
                        i, itm_color))
                    eval("ax{}.plot(x_otm, y_otm, z_otm, '{}')".format(
                        i, otm_color))

                # Increment the axis (parameter) counter
                i += 1

            # Set to false after first iteration
            # Only need to set up the subplots once
            first_iter = False

            # Increment the y(date)-axis counter
            j += 1

        # If a second option type is looped through, increment k
        # This prevents duplicate expirations from being added
        k += 1

    # Create ticks for the y(date)-axis
    # 13 dates seems to be the limit of readability with an 8-pt font
    # If there are more than 13 expirations, only label every other one
    if j <= 13:
        ticks = [x for x in range(j)]

    else:
        ticks = [x for x in range(1, j, 2)]
        exps = [exps[x] for x in ticks]

    # Create a list of the index of the subplots
    axs = [x for x in range(1, i)]

    # Update the y(date)-axis with date labels instead of integers
    # Do this for each subplot
    for ax in axs:
        eval("ax{}.yaxis.set_ticks(ticks)".format(ax))
        eval("ax{}.yaxis.set_ticklabels(exps, fontsize=8, verticalalignment='baseline', horizontalalignment='center', rotation=-20)".format(ax))

    # Create the main title
    main_title.insert(0, 'ATM {}: ${:.2f} [{} {}]'.format(
        ticker, S, current_date, current_time))
    fig.suptitle(''.join(main_title))

    # Make view adjustments
    fig.tight_layout(h_pad=1, w_pad=0.001)
    # fig.tight_layout(h_pad=1, w_pad=1)
    plt.subplots_adjust(wspace=0.001, hspace=0.1)
    # plt.subplots_adjust(wspace=0.2, hspace=0.2)

    # Change for web viewing

    if web:
        return jp.Matplotlib()

    # Show the plot
    plt.show(block=True)

def main():
    stop_main_loop = '0'

    # Print the read me
    print('\n---------------------------------------READ ME---------------------------------------------\n')
    print('| Data is pulled from Yahoo! finance with the yfinance python package. There is a rate    |')
    print('| limit of 2000 request per hour to the Yahoo! API, and each expiration in an option      |')
    print('| chain requires 1 API call. So if you are running this a lot, you may get odd behavior.  |')

    print('| Yahoo! also seems to "reset" around around 12-2am, so prices may all be 0 at that time. |')

    print('\n| Yahoo! does provide implied volatility numbers, but in the spirit of this exercise,     |')
    print('| IVs are manually calculated from the option price using a bisection algorithm.          |')

    print('\n| Bisection is slower than something like Newton-Raphson, but is guaranteed to converge   |')
    print('| given that the answer is between the initial guesses. The initial guesses for IV in     |')
    print('| this script are 0% and 2000%.                                                           |')

    print('\n| There are no filters with respect to open interest or volume. Whatever bid/ask/last is  |')
    print('| present will be used.                                                                   |')

    print('\n| For tickers with more than 13 expirations, only every other expiration will be labeled  |')
    print('| on the plots. Otherwise the labels get crowded and hard to read.                        |')

    print('\n------------------------------------------------------------------------------------------\n')
    input('Press any key to continue...\n')

    while stop_main_loop == '0':

        # Get mode
        mode = input(
            'Press 0 to make plots, 1 to calculate the standard greeks for a single option: ')
        while mode not in ['0', '1']:
            mode = input(
                'Press 0 to make plots, 1 to calculate the standard greeks for a single option: ')

        #  If plot mode
        if mode == '0':
            # Get inputs
            ticker, params, price_type, opt_type, moneyness, current_date, current_time, r = multi_plot_input()

            print('Connecting to Yahoo!...')

            # Get option objects
            options = get_options(current_date, current_time,
                                  ticker, opt_type, price_type, r)

            # Make plots
            generate_plots(ticker, options, moneyness, params, price_type,
                           current_date, current_time)

            # Stop or continue
            stop_main_loop = input(
                'Enter 0 to continue, anything else to quit: ')

        # If single option mode
        elif mode == '1':

            # Get inputs
            current_date, current_time, opt_type, exp, V, S, K, r = single_option_input()

            # Create option object
            try:
                option = Option(current_date, current_time,
                                opt_type, exp, V, S, K, r, 0, 0, 0, 0)

                # Names of things to print
                names = [
                    'IV',
                    'delta',
                    'gamma',
                    'vega',
                    'theta',
                    'rho'
                ]

                # Print the things
                print('\n{}'.format(option))

                for name in names:
                    thing = eval('option.{}'.format(name))
                    print('{}: {}'.format(name, thing))

                print('')

            except:
                print('\nSomething went wrong.')
                print('Check your input values\n')

    pass

if __name__ == '__main__':
    # Main loop
    # main()
    pass

def create_inputs(wp):
    input_div = jp.Div(classes='flex m-2 p-2', a=wp)
    style = 'width: 150px'
    wp.ticker = jp.QInput(label='Enter ticker', hint='', a=input_div, classes='m-2', style='width: 100px', value='AAPL', outlined=True)
    wp.param_type = jp.QSelect(label='Param type', hint='', options=['Standard', 'Nonstandard'], a=input_div, classes='m-2', style=style, value='Standard', outlined=True)
    wp.price_type = jp.QSelect(label='Price type', hint='Select price to use for calculation', options=['mid', 'last'], a=input_div, classes='m-2', style=style, value='mid', outlined=True)
    wp.opt_type = jp.QSelect(label='Option type', hint='Select option type', options=['calls', 'puts', 'both'], a=input_div, classes='m-2', style=style, value='both', outlined=True)
    wp.moneyness = jp.QSelect(label='Moneyness', hint='Select moneyness to plot', options=['itm', 'otm', 'both'], a=input_div, classes='m-2', style=style, value='both', outlined=True)
    wp.r = jp.QInput(label='Enter risk free rate', hint='',a=input_div, classes='m-2', style='width: 150px', value=0.004, type='number', outlined=True)

    now = dt.datetime.now()
    wp.current_date = str(now.date())
    minute = now.time().minute
    wp.current_time = '{}:{}'.format(now.time().hour, minute)
    if minute < 10:
        wp.current_time = '{}:0{}'.format(now.time().hour, minute)
    wp.date_time = jp.QInputDateTime(a=input_div, classes='m-2', value=f'{wp.current_date} {wp.current_time}', hint='Date and time to use', outlined=True)
    wp.plot_button = jp.QBtn(label='Generate Plots', color='primary', a=input_div, classes='m-2 h-1')
    wp.input_div = input_div
    return wp

async def plot_charts(self, msg):
    wp = msg.page
    self.loading = True
    await msg.page.update()
    options = get_options(wp.current_date, wp.current_time, wp.ticker.value, wp.opt_type.value, wp.price_type.value, float(wp.r.value))
    if wp.param_type.value == 'Standard':
        params = ['V', 'IV', 'delta', 'theta', 'volume', 'vega', 'gamma', 'openInterest']
    elif wp.param_type.value == 'Nonstandard':
        print('\n**Warning: accuracy of higher order greeks has not been verified**\n')
        params = ['rho', 'charm', 'veta', 'color', 'speed', 'vanna', 'vomma', 'zomma']
    self.loading = False
    plots = generate_plots(wp.ticker.value, options, wp.moneyness.value, params, wp.price_type.value,
                           wp.date_time.value[0:11], wp.date_time.value[12:], scale=1.5, web=True)
    plots.classes = 'border m-2 p-2'
    wp.plots_div.add(plots)

def option_3d():
    wp = jp.QuasarPage(tailwind=True)
    create_inputs(wp)
    wp.plots_div = jp.Div(classes='m-2 p-2', a=wp)
    wp.plot_button.on('click', plot_charts)
    return wp

jp.justpy(option_3d)


'''
Import libraries and functions
'''
import math
import numpy as np
import statistics
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score
import matplotlib.pyplot as plt

sqrt = math.sqrt
mean = statistics.mean

def get_mean_bias_error(x, y):
    '''
    It calculates the mean bias error function (MBE).

    Input:
        * x -> list or array of measured/actual data. For example,
        in terms of et0, it would be FAO56-PM et0.

        * y -> list or array of predicted values. For example, in
        terms of et0, it would be the predicted et0 by a neural
        network model, etc.

    Output:
        * mbe -> a float with the mean bias error value
    '''
    # check the lengths are the same
    assert len(x)==len(y)
    # convert the inputs in numpy array
    x = np.array(x)
    y = np.array(y)
    # mbe calculus
    delta = y - x
    mbe = float(sum(delta)*1.0/len(x))
    return(mbe)

def get_root_mean_square_error(x, y):
    '''
    It calculates de root mean square error (RMSE).

    Input:
        * x -> list or array of measured/actual data. For example,
        in terms of et0, it would be FAO56-PM et0.

        * y -> list or array of predicted values. For example, in
        terms of et0, it would be the predicted et0 by a neural
        network model, etc.

    Output:
        * rmse -> a float with the root mean squared error value
    '''
    # check the lengths are the same
    assert len(x)==len(y)
    # convert the inputs in numpy array
    x = np.array(x)
    y = np.array(y)
    # rmse calculus
    delta = (y - x)**2
    rmse = sqrt(sum(delta)*1.0/len(x))
    return rmse

def get_relative_error(x, y):
    '''
    It calculates de relative error RE.

    Input:
        * x -> list or array of measured/actual data. For example,
        in terms of et0, it would be FAO56-PM et0.

        * y -> list or array of predicted values. For example, in
        terms of et0, it would be the predicted et0 by a neural
        network model, etc.

    Output:
        * re -> a float with the relative error value. The value given
        is a function of per-unit, instead of per-cent.
    '''
    # check the lengths are the same
    assert len(x)==len(y)
    # convert the inputs in numpy array
    x = np.array(x)
    y = np.array(y)
    # re calculus
    mbe = get_mean_bias_error(x, y)
    x_mean = mean(x)
    re = mbe/x_mean
    return re

def get_ratio_error(x, y):
    '''
    It calculates the ratio between both average estimated and measured

    Input:
        * x -> list or array of measured/actual data. For example,
        in terms of et0, it would be FAO56-PM et0.

        * y -> list or array of predicted values. For example, in
        terms of et0, it would be the predicted et0 by a neural
        network model, etc.

    Output:
        * re -> a float with the relative error value. The value given
        is a function of per-unit, instead of per-cent.
    '''
    # check the lengths are the same
    assert len(x)==len(y)
    # convert the inputs in numpy array
    x = np.array(x)
    y = np.array(y)
    # ratio error calculus
    return mean(y)/mean(x)

def get_coefficient_of_determination(x, y):
    '''
    It calculates the coefficient of determination (R**2)

    Input:
        * x -> list or array of measured/actual data. For example,
        in terms of et0, it would be FAO56-PM et0.

        * y -> list or array of predicted values. For example, in
        terms of et0, it would be the predicted et0 by a neural
        network model, etc.
    Output:
        * R2 -> a float with the coefficient of determination in
        per unit, instead of percentage.
    '''
    # check the lengths are the same
    assert len(x)==len(y)
    # convert the inputs in numpy array
    x = np.array(x)
    y = np.array(y)

    # determines the means of the values
    x_mean = mean(x)
    y_mean = mean(y)
    delta_measured = x - x_mean
    delta_measured_2 = (x - x_mean)**2
    delta_prediction = y - y_mean
    delta_prediction_2 = (y - y_mean)**2
    R2 = (sum(delta_measured*delta_prediction)/sqrt(sum(delta_measured_2)*sum(delta_prediction_2)))**2
    return R2

def get_nash_suteliffe_efficiency(x, y):
    '''
    It calculates NSE. It is calculated by sklearn.metrics.r2_score

    Input:
        * x -> list or array of measured/actual data. For example,
        in terms of et0, it would be FAO56-PM et0.

        * y -> list or array of predicted values. For example, in
        terms of et0, it would be the predicted et0 by a neural
        network model, etc.
    Output:
        * nse -> a float with the coefficient of determination in
        per unit, instead of percentage.
    '''
    # check the lengths are the same
    assert len(x)==len(y)
    # convert the inputs in numpy array
    x = np.array(x)
    y = np.array(y)

    return r2_score(x, y)


import numpy as np

def wrap_closed_open_interval(values, lower, upper):
    return (np.asarray(values) - lower) % (upper - lower) + lower

def wrap_open_closed_interval(values, lower, upper):
    return -((lower - np.asarray(values)) % (upper - lower)) - lower

def spherical2interaural(azimuth, elevation, radius, angles_in_degrees=True):
    if angles_in_degrees:
        azimuth = np.deg2rad(azimuth)
        elevation = np.deg2rad(elevation)
    lateral = np.arcsin(np.sin(azimuth) * np.cos(elevation))
    vertical = np.arctan2(np.tan(elevation), np.cos(azimuth))
    if angles_in_degrees:
        return np.rad2deg(lateral), np.rad2deg(vertical), radius
    else:
        return lateral, vertical, radius

def spherical2cartesian(azimuth, elevation, radius, angles_in_degrees=True):
    if angles_in_degrees:
        azimuth = np.deg2rad(azimuth)
        elevation = np.deg2rad(elevation)
    hoz_proj = radius * np.cos(elevation)
    x = hoz_proj * np.cos(azimuth)
    y = hoz_proj * np.sin(azimuth)
    z = radius * np.sin(elevation)
    return x, y, z

def cartesian2spherical(x, y, z, angles_in_degrees=True):
    x = np.asarray(x)
    y = np.asarray(y)
    z = np.asarray(z)
    azimuth = np.arctan2(y, x)
    elevation = np.arctan2(z, np.sqrt(x**2 + y**2))
    radius = np.sqrt(x**2 + y**2 + z**2)
    if angles_in_degrees:
        return np.rad2deg(azimuth), np.rad2deg(elevation), radius
    else:
        return azimuth, elevation, radius

def cartesian2interaural(x, y, z, angles_in_degrees=True):
    x = np.asarray(x)
    y = np.asarray(y)
    z = np.asarray(z)
    lateral = np.arctan2(y, np.sqrt(x**2 + z**2))
    vertical = np.arctan2(z, x)
    radius = np.sqrt(x**2 + y**2 + z**2)
    if angles_in_degrees:
        return np.rad2deg(lateral), np.rad2deg(vertical), radius
    else:
        return lateral, vertical, radius

def interaural2spherical(lateral, vertical, radius, angles_in_degrees=True):
    if angles_in_degrees:
        lateral = np.deg2rad(lateral)
        vertical = np.deg2rad(vertical)
    azimuth = np.arctan2(np.tan(lateral), np.cos(vertical))
    elevation = np.arcsin(np.cos(lateral) * np.sin(vertical))
    if angles_in_degrees:
        return np.rad2deg(azimuth), np.rad2deg(elevation), radius
    else:
        return azimuth, elevation, radius

def interaural2cartesian(lateral, vertical, radius, angles_in_degrees=True):
    if angles_in_degrees:
        lateral = np.deg2rad(lateral)
        vertical = np.deg2rad(vertical)
    med_proj = radius * np.cos(lateral)
    x = med_proj * np.cos(vertical)
    y = radius * np.sin(lateral)
    z = med_proj * np.sin(vertical)
    return x, y, z


# coding=utf-8

# Author: Rodolfo J. O. Soares <rodolfoj.soares@gmail.com>

import numpy as np

def softmax(vector, theta=1.0):
    """Takes an vector w of S N-element and returns a vectors where each column
    of the vector sums to 1, with elements exponentially proportional to the
    respective elements in N.

    Parameters
    ----------
    vector : array of shape = [N,  M]

    theta : float (default = 1.0)
            used as a multiplier  prior to exponentiation

    Returns
    -------
    dist : array of shape = [N, M]
           Which the sum of each row sums to 1 and the elements are exponentially
           proportional to the respective elements in N
    """

    w = np.atleast_2d(vector)
    e = np.exp(np.array(w) / theta)
    dist = e / np.sum(e, axis=1).reshape(-1, 1)
    return dist

def softmin(vector):
    """Takes a vector w of S N-element and return a softmax-based activation function
    that is defined as f(x)=softmax(−x).

    Parameters
    ----------
    vector : array of shape = [N,  M]

    Returns
    -------
    dist : array of shape = [N, M]
           Which the sum of each row sums to 1 and the elements are exponentially
           proportional to the respective elements in N
    """

    return softmax((-1 * vector))


# Plot 3-dim dirichlet on 2d simplex as heatmap and samples

# Author: Thomas Boggs
# https://gist.github.com/tboggs/8778945
# http://blog.bogatron.net/blog/2014/02/02/visualizing-dirichlet-distributions/

import superimport

from functools import reduce

import numpy as np
import matplotlib.pyplot as plt
import pyprobml_utils as pml

import matplotlib.tri as tri

_corners = np.array([[0, 0], [1, 0], [0.5, 0.75**0.5]])
_triangle = tri.Triangulation(_corners[:, 0], _corners[:, 1])
_midpoints = [(_corners[(i + 1) % 3] + _corners[(i + 2) % 3]) / 2.0 \
              for i in range(3)]

def xy2bc(xy, tol=1.e-3):
    '''Converts 2D Cartesian coordinates to barycentric.
    Arguments:
        `xy`: A length-2 sequence containing the x and y value.
    '''
    s = [(_corners[i] - _midpoints[i]).dot(xy - _midpoints[i]) / 0.75 \
         for i in range(3)]
    return np.clip(s, tol, 1.0 - tol)

class Dirichlet(object):
    def __init__(self, alpha):
        '''Creates Dirichlet distribution with parameter `alpha`.'''
        from math import gamma
        from operator import mul
        self._alpha = np.array(alpha)
        self._coef = gamma(np.sum(self._alpha)) / \
                     reduce(mul, [gamma(a) for a in self._alpha])
    def pdf(self, x):
        '''Returns pdf value for `x`.'''
        from operator import mul
        return self._coef * reduce(mul, [xx ** (aa - 1)
                                         for (xx, aa)in zip(x, self._alpha)])
    def sample(self, N):
        '''Generates a random sample of size `N`.'''
        return np.random.dirichlet(self._alpha, N)

def draw_pdf_contours(dist, border=False, nlevels=200, subdiv=8, **kwargs):
    '''Draws pdf contours over an equilateral triangle (2-simplex).
    Arguments:
        `dist`: A distribution instance with a `pdf` method.
        `border` (bool): If True, the simplex border is drawn.
        `nlevels` (int): Number of contours to draw.
        `subdiv` (int): Number of recursive mesh subdivisions to create.
        kwargs: Keyword args passed on to `plt.triplot`.
    '''
    refiner = tri.UniformTriRefiner(_triangle)
    trimesh = refiner.refine_triangulation(subdiv=subdiv)
    pvals = [dist.pdf(xy2bc(xy)) for xy in zip(trimesh.x, trimesh.y)]
    from matplotlib import cm
    plt.tricontourf(trimesh, pvals, nlevels, cmap=cm.jet, **kwargs)
    plt.axis('equal')
    plt.xlim(0, 1)
    plt.ylim(0, 0.75**0.5)
    plt.axis('off')
    if border is True:
        #plt.hold(1)
        plt.triplot(_triangle, linewidth=1)

def plot_points(X, barycentric=True, border=True, **kwargs):
    '''Plots a set of points in the simplex.
    Arguments:
        `X` (ndarray): A 2xN array (if in Cartesian coords) or 3xN array
                       (if in barycentric coords) of points to plot.
        `barycentric` (bool): Indicates if `X` is in barycentric coords.
        `border` (bool): If True, the simplex border is drawn.
        kwargs: Keyword args passed on to `plt.plot`.
    '''
    if barycentric is True:
        X = X.dot(_corners)
    plt.plot(X[:, 0], X[:, 1], 'k.', ms=1, **kwargs)
    plt.axis('equal')
    plt.xlim(0, 1)
    plt.ylim(0, 0.75**0.5)
    plt.axis('off')
    if border is True:
        #plt.hold(1)
        plt.triplot(_triangle, linewidth=1)

f = plt.figure(figsize=(8, 6))
alphas = [[0.1] * 3,
          [1] * 3,
          [5] * 3,
          [50] * 3,
          [2, 5, 15]]
for (i, alpha) in enumerate(alphas):
    plt.figure(figsize=(8,8))
    dist = Dirichlet(alpha)
    draw_pdf_contours(dist)
    title = r'$\alpha$ = (%.3f, %.3f, %.3f)' % tuple(alpha)
    plt.title(title, fontdict={'fontsize': 12})
    a0 = int(np.round(alpha[0]*10))
    pml.savefig('dirichletHeatmap{}.jpg'.format(a0))
    plt.show()

    plt.figure(figsize=(8,8))
    plot_points(dist.sample(5000))
    pml.savefig('dirichletSample{}.jpg'.format(a0))
    plt.show()


'''
------------------------------------
Assignment 4 - EE2703 (Jan-May 2020)
Done by Akilesh Kannan (EE18B122)
Created on 09/02/20
Last Modified on 13/02/20
------------------------------------
'''

import numpy as np
from scipy.integrate import quad
from matplotlib import pyplot as plt

def coscosxFunc(x):
    return np.cos(np.cos(x))
def expFunc(x):
    return np.exp(x)

def pi_tick(value, tick_number):
    # find number of multiples of pi/2
    N = int(round(2 * value / PI))
    if N == 0:
        return "0"
    elif N == 1:
        return r"$\pi/2$"
    elif N == -1:
        return r"$-\pi/2$"
    elif N == 2:
        return r"$\pi$"
    elif N == -2:
        return r"$-\pi$"
    elif N % 2 != 0:
        return r"${0}\pi/2$".format(N)
    else:
        return r"${0}\pi$".format(N//2)

PI = np.pi
plotsDir = 'plots/'
x = np.linspace(-2*PI, 4*PI, 1201)
x = x[:-1]

plt.figure('Figure 1')
ax1 = plt.axes()
ax1.xaxis.set_major_formatter(plt.FuncFormatter(pi_tick))
plt.plot(x, coscosxFunc(x), 'k', label='True Function')
plt.plot(x, coscosxFunc(x%(2*PI)), '--', label='Fourier Series expansion')
plt.axis([-6, 10, 0.5, 1.05])
plt.legend(loc='upper right')
plt.title('$cos(cos(x))$')
plt.grid()
plt.savefig(plotsDir+'Figure 1.png')

plt.figure('Figure 2')
ax2 = plt.axes()
ax2.xaxis.set_major_formatter(plt.FuncFormatter(pi_tick))
plt.semilogy(x, expFunc(x), 'k', label='True Function')
plt.semilogy(x, expFunc(x%(2*PI)), '--', label='Fourier Series expansion')
plt.legend(loc='upper left')
plt.title('$e^x$')
plt.grid()
plt.savefig(plotsDir+'Figure 2.png')

def cosCoeff(x, k, f):
    return f(x)*np.cos(k*x)
def sinCoeff(x, k, f):
    return f(x)*np.sin(k*x)

def calc51FourierCoeffs (f):
    aCoeff = np.zeros(26)
    bCoeff = np.zeros(26)
    aCoeff[0] = quad(cosCoeff, 0, 2*PI, args=(0, f))[0]/(2*PI)
    for i in range(1, 26):
        aCoeff[i] = quad(cosCoeff, 0, 2*PI, args=(i, f))[0]/(PI)
        bCoeff[i] = quad(sinCoeff, 0, 2*PI, args=(i, f))[0]/(PI)
    coeffs = np.zeros(51)
    coeffs[0] = aCoeff[0]
    coeffs[1::2] = aCoeff[1:]
    coeffs[2::2] = bCoeff[1:]
    return coeffs

coeffCosCos = calc51FourierCoeffs(coscosxFunc)
coeffExp = calc51FourierCoeffs(expFunc)

xTicksForCoeffsSemilog = ['$a_0$']
for i in range(1, 26):
    xTicksForCoeffsSemilog.append('$a_{'+str(i)+'}$')
    xTicksForCoeffsSemilog.append('$b_{'+str(i)+'}$')

plt.figure('Figure 3')
plt.xticks(np.arange(51), xTicksForCoeffsSemilog, rotation=60)
plt.tick_params(axis='x', labelsize=7)
plt.semilogy(abs(coeffCosCos[1::2]), 'bo', label='$a_n$')
plt.semilogy(abs(coeffCosCos[2::2]), 'yo', label='$b_n$')
plt.legend()
plt.title('$cos(cos(x))$ semilog plot')
plt.grid()
plt.savefig(plotsDir+'Figure 3.png')

plt.figure('Figure 4')
plt.xticks(np.arange(51), xTicksForCoeffsSemilog, rotation=60)
plt.tick_params(axis='x', labelsize=7)
plt.semilogy(abs(coeffExp[1::2]), 'bo', label='$a_n$')
plt.semilogy(abs(coeffExp[2::2]), 'yo', label='$b_n$')
plt.legend()
plt.title('$e^x$ semilog plot')
plt.grid()
plt.savefig(plotsDir+'Figure 4.png')

plt.figure('Figure 5')
plt.loglog(abs(coeffCosCos[1::2]), 'bo', label='$a_n$')
plt.loglog(abs(coeffCosCos[2::2]), 'yo', label='$b_n$')
plt.legend()
plt.title('$cos(cos(x))$ loglog plot')
plt.grid()
plt.savefig(plotsDir+'Figure 5.png')

plt.figure('Figure 6')
plt.loglog(abs(coeffExp[1::2]), 'bo', label='$a_n$')
plt.loglog(abs(coeffExp[2::2]), 'yo', label='$b_n$')
plt.legend()
plt.title('$e^x$ loglog plot')
plt.grid()
plt.savefig(plotsDir+'Figure 6.png')

def findLSTSQCoeff(f):
    x = np.linspace(0, 2*PI, 401)
    x = x[:-1]
    b = f(x)
    M = np.zeros((400, 51))
    M[:,0] = 1
    for k in range(1,26):
        M[:,(2*k)-1]=np.cos(k*x)
        M[:,2*k]=np.sin(k*x)
    return np.linalg.lstsq(M, b, rcond=None)[0]

lstsqCosCos = findLSTSQCoeff(coscosxFunc)
lstsqExp = findLSTSQCoeff(expFunc)

plt.figure('Figure 3.1')
plt.semilogy(abs(coeffCosCos[1::2]), 'ro', label='$a_n$ by Integration')
plt.semilogy(abs(coeffCosCos[2::2]), 'bo', label='$b_n$ by Integration')
plt.semilogy(abs(lstsqCosCos[1::2]), 'go', label='$a_n$ by lstsq')
plt.semilogy(abs(lstsqCosCos[2::2]), 'yo', label='$b_n$ by lstsq')
plt.title('Comparing $cos(cos(x))$ FS coefficients - semilogy plot')
plt.legend()
plt.grid()
plt.savefig(plotsDir+'Figure 3.1.png')

plt.figure('Figure 4.1')
plt.semilogy(abs(coeffExp[1::2]), 'ro', label='$a_n$ by Integration')
plt.semilogy(abs(coeffExp[2::2]), 'bo', label='$b_n$ by Integration')
plt.semilogy(abs(lstsqExp[1::2]), 'go', label='$a_n$ by lstsq')
plt.semilogy(abs(lstsqExp[2::2]), 'yo', label='$b_n$ by lstsq')
plt.title('Comparing $e^x$ FS coefficients - semilogy plot')
plt.legend()
plt.grid()
plt.savefig(plotsDir+'Figure 4.1.png')

plt.figure('Figure 5.1')
plt.loglog(abs(coeffCosCos[1::2]), 'ro', label='$a_n$ by Integration')
plt.loglog(abs(coeffCosCos[2::2]), 'bo', label='$b_n$ by Integration')
plt.loglog(abs(lstsqCosCos[1::2]), 'go', label='$a_n$ by lstsq')
plt.loglog(abs(lstsqCosCos[2::2]), 'yo', label='$b_n$ by lstsq')
plt.title('Comparing $cos(cos(x))$ FS coefficients - loglog plot')
plt.legend()
plt.grid()
plt.savefig(plotsDir+'Figure 5.1.png')

plt.figure('Figure 6.1')
plt.loglog(abs(coeffExp[1::2]), 'ro', label='$a_n$ by Integration')
plt.loglog(abs(coeffExp[2::2]), 'bo', label='$b_n$ by Integration')
plt.loglog(abs(lstsqExp[1::2]), 'go', label='$a_n$ by lstsq')
plt.loglog(abs(lstsqExp[2::2]), 'yo', label='$b_n$ by lstsq')
plt.title('Comparing $e^x$ FS coefficients - loglog plot')
plt.legend()
plt.grid()
plt.savefig(plotsDir+'Figure 6.1.png')

absErrorsCosCos = [abs(coeffCosCos[i]-lstsqCosCos[i]) for i in range(len(coeffCosCos))]
absErrorsExp = [abs(coeffExp[i]-lstsqExp[i]) for i in range(len(coeffExp))]

print('Max. deviation for cos(cos(x)): '+str(max(absErrorsCosCos)))
print('Max. deviation for exp(x): '+str(max(absErrorsExp)))

xNew = np.linspace(-2*PI, 4*PI, 1201)
xNew = xNew[:-1]

matrixA = np.zeros((1200,51))
matrixA[:,0] = 1
for k in range(1,26):
    matrixA[:,(2*k)-1]=np.cos(k*xNew)
    matrixA[:,2*k]=np.sin(k*xNew)

plt.figure('Figure 7')
ax3 = plt.axes()
ax3.xaxis.set_major_formatter(plt.FuncFormatter(pi_tick))
plt.plot(xNew, matrixA@coeffCosCos, 'go', label='Integration')
plt.plot(xNew, matrixA@lstsqCosCos, 'r', label='lstsq')
plt.title('Reconstruction of $cos(cos(x))$')
plt.legend()
plt.grid()
plt.savefig(plotsDir+'Figure 7.png')

plt.figure('Figure 8')
ax4 = plt.axes()
ax4.xaxis.set_major_formatter(plt.FuncFormatter(pi_tick))
plt.plot(xNew, matrixA@coeffExp, 'go', label='Integration')
plt.plot(xNew, matrixA@lstsqExp, 'r', label='lstsq')
plt.title('Reconstruction of $e^x$')
plt.legend()
plt.grid()
plt.savefig(plotsDir+'Figure 8.png')


"""
Import all libraries you need.

In this example there are only libraries needed for the example code.
"""

from sklearn.datasets import load_breast_cancer
from sklearn.neighbors import KNeighborsClassifier     #KNN
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import matplotlib.pylab as plt
import numpy as np
import pandas as pd
import seaborn as sns

"""
Read the data.

In this challenge you will use the toy dataset Breast Cancer Wisconsin (Diagnostic) Database.

In the following example, how to load and read the infos of this dataset.
"""

breast_dataset=load_breast_cancer()
#let's print the dataset description
print(breast_dataset.DESCR+"\n\n")
#let's print the size of the dataset
print("Size:" +str(breast_dataset.data.shape)+"\n")
#let's print the features names
print("Features:\n"+str(breast_dataset.feature_names)+"\n")
#let's print the class names
print("Classes:\n"+str(breast_dataset.target_names))

"""
Study the features.

As an example, in the following code we use pandas (after a conversion from a  scikit-learn dataset to a pandas dataset) and seaborn libraries to:
- build a scatter matrix to study the covariance between features (in the example, all features are used, but you can plot only the features you need)
- build a heatmap of correlation between features (again of all features, but you can choose what you need) using the Pearson correlation coefficient
"""

#convert the scikit-learn dataset into pandas dataset
breast_data_pd = pd.DataFrame(breast_dataset.data,columns=breast_dataset.feature_names)
breast_data_pd['target'] = breast_dataset.target
#plot the scatter matrix
pd.plotting.scatter_matrix(breast_data_pd[breast_dataset.feature_names],figsize=(25,25))
#correlation plot
corr = breast_data_pd[breast_dataset.feature_names].corr()
f, ax = plt.subplots(figsize=(30, 30))
sns.heatmap(corr,annot=True)

"""
Apply all the feature transformations and/or feature selection and/or dimensionality reduction that you think are necessary to improve the performance.

Follow the Scikit-learn user guide, and in particular:
- for feature transformations see https://scikit-learn.org/stable/modules/preprocessing.html
- for dimensionaity reduction see this example on LDA and PCA https://scikit-learn.org/stable/auto_examples/decomposition/plot_pca_vs_lda.html
- for feature selection see https://scikit-learn.org/stable/modules/feature_selection.html

Don't modify this part where we split the data into training and test, then train a 3-NN classifier and evaluate the accuracy on the test set.
"""

#DO NOT MODIFY THE FOLLOWING SPLIT OF THE DATASET
X_train, X_test, y_train, y_test = train_test_split(breast_dataset.data, breast_dataset.target, stratify=breast_dataset.target,random_state=42)

#BE CAREFUL: To find the right transformation only use the training set (variable X_train)
#Then apply the same transformation to the data in the test set (variable X_test)

#Write here your code to transform, select or reduce features (don't forget to import libraries)

#DO NOT MODIFY THE CLASSIFIER AND THE PERFORMANCE EVALUATION
knn = KNeighborsClassifier(n_neighbors=3)
knn.fit(X_train,y_train)
scores = knn.predict(X_test)

# Show prediction accuracy
print('\nPrediction accuracy:')
print('{:.2%}\n'.format(accuracy_score(y_test, scores)))


# pylint: disable=missing-module-docstring
#
# Copyright (C) 2022 by YadavGulshan@Github, < https://github.com/YadavGulshan >.
#
# This file is part of < https://github.com/Yadavgulshan/diagonalizable > project,
# and is released under the "MIT License".
# Please see < https://github.com/YadavGulshan/diagonalizable/blob/master/LICENCE >
#
# All rights reserved.

import numpy as np
import os

matrix = np.array(
    [
        [4,2,2],
        [2,4,2],
        [2,2,4],
    ]
)

class Diagonalizable:
    def __init__(self, matrix):
        self.matrix = matrix
        self.size = len(matrix)

    def check_diagonalizable_square(self):
        # Checking if the matrix is square
        print("Making required checks!!")
        print("please wait...")
        if all(i==self.matrix.shape[0] for i in self.matrix.shape):
            os.system('clear')
            return True
        else:
            return False

    def getEigenStuff(self):
        # First check if the matrix is square
        if self.check_diagonalizable_square():
            # Getting the eigenvalues and eigenvectors
            eigenValue, eigenVector = np.linalg.eig(self.matrix)
            return eigenValue, eigenVector
        else:
            print("The matrix is not square")
            exit()

    def check_diagonalizable(self):
        # Checking if the matrix is diagonalizable
        eigenValue, eigenVector = self.getEigenStuff()
        print("Eigen Value: ", eigenValue)
        print("Eigen Vector: \n",eigenVector)
        print(set(eigenValue), '\n')

        # Check if all the eigenvalues are unique and real
        if len(eigenValue) == len(set(eigenValue)) and np.all(np.isreal(eigenValue)):
            return "Diagonalizable"
        else:
            return "Not Diagonalizable"

diagonalizable = Diagonalizable(matrix)
print(diagonalizable.check_diagonalizable())


'''
This module contains functions to generate 2D rotation matrices.
'''
import math
import numpy as np

def rotation_from_angle(angle):
    '''
    generates a 2x2 rotation matrix from a given angle following
    the definition here:
    https://en.wikipedia.org/wiki/Rotation_matrix.

    :param angle: the angle by which to rotate
    :type angle: float

    :returns: the rotation matrix
    :rtype: a 2x2 :any:`numpy.ndarray`
    '''
    c = math.cos(angle)
    s = math.sin(angle)

    return np.array([[c, -s],[s, c]])


import sys
import numpy as np

from matplotlib import pyplot as plt
from math import pow

np.set_printoptions(threshold=sys.maxsize)

RHO = 1500.0
LAMBDA = 1.0
N = 51
DELTA = 1/(N-1) # 0.02 m = 2 cm = 1 / (N-1)
K = 0.1
C = 1000.0
k = LAMBDA / (C*RHO)
DELTA_T = K * pow(DELTA,2) / k

T = np.zeros((N, N))
x = np.linspace(-0.5, 0.5, num=N)
y = np.linspace(1, 0, num=N)

def step_time_2d(T, K):
    output = T + K * ( np.roll(T,-1,1) - 2 * T + np.roll(T,1,1) + np.roll(T,-1,0) - 2 * T + np.roll(T,1,0) )
    output[0,:] = -10
    output[-1,:] = 0
    output[:,0] = output[:,1]
    output[:,-1] = output[:,-2]
    output[15,25] += 250000 / (C * RHO) * DELTA_T
    return output

time_steps = 0
while True:
    new_T = step_time_2d(T, K)
    time_steps += 1
    if abs(np.mean(new_T - T)) < pow(10, -9):
        break
    T = new_T

q = (-LAMBDA) * np.array(np.gradient(T, DELTA))

c = plt.contourf(x, y, T, levels=list(range(-10,int(np.max(T)), 8)))
plt.streamplot(x, y, q[1], -q[0], color="white", density=0.7, linewidth=1.4)
plt.axis((-0.5, 0.5, 0, 1))
ax = plt.gca()
ax.set_xlabel("y position (m)")
ax.set_ylabel("x position (m)")
cbar = plt.colorbar(c, ax=ax, ticks=list(range(-10, int(np.max(T)), 10)))
cbar.set_label("Temperatur (°C)")

print(f"Time step size: {DELTA_T}")
print(f"Tolarence cleared after {time_steps} iterations. ({time_steps * DELTA_T / 60 / 60} hours)")
print(f"Total temperature flow through the top is {np.sum(T[2,:]-T[1,:]) * LAMBDA}")
print(f"Total temperature flow through the bottom is {np.sum(T[-2,:]-T[-1,:]) * LAMBDA}")

plt.show()


import numpy as np

def softmax(y):
    y = y - np.max(y, axis=1, keepdims=True)
    return np.exp(y) / np.sum(np.exp(y), axis=1, keepdims=True)

def cross_entropy(prob, t):
    if prob.ndim == 1:
        t = t.reshape(1, t.size)
        prob = prob.reshape(1, prob.size)
    return -np.mean(np.log(prob[np.arange(prob.shape[0]), t] + 1e-7))

def softmax_cross_entropy(y, t):
    return cross_entropy(softmax(y), t)


import numpy as np
import math, csv
import matplotlib.pyplot as plt

DISTANCE = 10
ANGLE = np.pi / 4

AXIS_ROT_ANGLE = -np.pi / 2
AXIS_TRANS = np.array([10, 0])

MEAN_SENSOR = np.array([DISTANCE, ANGLE])
COVARIANCE_SENSOR = np.array([[0.75 * 0.75, 0], [0, (np.pi / 90) * (np.pi / 90)]])

if __name__ == "__main__":
    samples_dist = np.random.multivariate_normal(MEAN_SENSOR, COVARIANCE_SENSOR, 100)

    # plt.boxplot([180 * s[1] / np.pi for s in samples_dist])

    samples_x = [d * math.cos(theta) for [d, theta] in samples_dist]
    samples_y = [d * math.sin(theta) for [d, theta] in samples_dist]

    samples_coord = [np.array(list(coords)) for coords in zip(samples_x, samples_y)]

    rotation_matrix = np.array(
        [
            [math.cos(AXIS_ROT_ANGLE), math.sin(AXIS_ROT_ANGLE)],
            [-math.sin(AXIS_ROT_ANGLE), math.cos(AXIS_ROT_ANGLE)],
        ]
    )
    trans_new_coord = np.matmul(AXIS_TRANS, rotation_matrix)

    mean_sensor_x = DISTANCE * math.cos(ANGLE)
    mean_sensor_y = DISTANCE * math.sin(ANGLE)

    mean_sensor_rotated = np.matmul(
        np.array([mean_sensor_x, mean_sensor_y]), rotation_matrix
    )
    mean_sensor_transformed = np.array(
        [
            mean_sensor_rotated[0] - trans_new_coord[0],
            mean_sensor_rotated[1] - trans_new_coord[1],
        ]
    )
    mean_sensor_transformed_dist = np.array(
        [
            np.linalg.norm(mean_sensor_transformed),
            math.atan2(mean_sensor_transformed[1], mean_sensor_transformed[0]),
        ]
    )

    samples_transformed_dist = np.random.multivariate_normal(
        mean_sensor_transformed_dist, COVARIANCE_SENSOR, 100
    )
    plt.boxplot([180 * s[1] / np.pi for s in samples_transformed_dist])
    plt.show()

    # samples_rotated = [np.matmul(s, rotation_matrix) for s in samples_coord]
    # samples_transformed_dist = [np.array([s[0] - trans_new_coord[0], s[1] - trans_new_coord[1]]) for s in samples_rotated]

    # samples_transformed_x = [s[0] for s in samples_transformed_dist]
    # samples_transformed_y = [s[1] for s in samples_transformed_dist]
    #
    # samples_transformed_dist = [np.array( [np.linalg.norm(s), math.atan2(s[1], s[0])] ) for s in samples_transformed_dist]
    # plt.boxplot([180 * s[1] / np.pi for s in samples_transformed_dist])

    # fig1, ax1 = plt.subplots()
    # ax1.set_title("Points in original axis")
    # ax1.scatter(samples_x, samples_y)
    #
    # fig2, ax2 = plt.subplots()
    # ax2.set_title("Points in transformed axis")
    # ax2.scatter(samples_transformed_x, samples_transformed_y)

    fieldnames = ["distance", "angle"]

    with open("data/sensor1.csv", mode="w") as csv_file:
        writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
        writer.writeheader()

        for s in samples_dist:
            writer.writerow({"distance": round(s[0], 4), "angle": round(s[1], 4)})

    with open("data/sensor2.csv", mode="w") as csv_file:
        writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
        writer.writeheader()

        for s in samples_transformed_dist:
            writer.writerow({"distance": round(s[0], 4), "angle": round(s[1], 4)})

    test_distances = np.random.uniform(low=2.5, high=10.5, size=(10,))
    test_angles = np.random.uniform(low=np.pi / 30, high=2 * np.pi / 3, size=(10,))

    test_points = [np.array([e[0], e[1]]) for e in zip(test_distances, test_angles)]
    test_points_coords = [
        np.array([e[0] * math.cos(e[1]), e[0] * math.sin(e[1])]) for e in test_points
    ]

    test_points_rotated = [np.matmul(s, rotation_matrix) for s in test_points_coords]
    test_points_transformed = [
        np.array([s[0] - trans_new_coord[0], s[1] - trans_new_coord[1]])
        for s in test_points_rotated
    ]

    # plt.scatter([s[0] for s in test_points_coords], [s[1] for s in test_points_coords])
    # plt.scatter([s[0] for s in test_points_transformed], [s[1] for s in test_points_transformed])

    test_points_transformed_dist = [
        np.array([np.linalg.norm(s), math.atan2(s[1], s[0])])
        for s in test_points_transformed
    ]

    with open("data/test_sensor1.csv", mode="w") as csv_file:
        writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
        writer.writeheader()

        for s in test_points:
            writer.writerow({"distance": round(s[0], 4), "angle": round(s[1], 4)})

    with open("data/test_sensor2.csv", mode="w") as csv_file:
        writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
        writer.writeheader()

        for s in test_points_transformed_dist:
            writer.writerow({"distance": round(s[0], 4), "angle": round(s[1], 4)})


from Statistics.Mean import mean
from Calculator.Multiplication import multiplication
from Calculator.Subtraction import subtraction
from Calculator.Square import square
from Calculator.Division import division
from Calculator.SquareRoot import square_root
import numpy as np

def sample_correlation(data, data1):
    try:

        mean1 = mean(data)
        mean2 = mean(data1)
        List1 = []
        List2 = []

        for num in data:
            a = subtraction(int(round(mean1, 0)), num)
            List1.append(a)

        for num in data1:
            b = subtraction(mean2, num)
            List2.append(b)
        c = np.multiply(List1, List2)
        cc = 0
        for num in c:
            cc = cc + num

        d = 0
        e = 0
        # pprint(List1)
        # pprint(List2)
        for num in List1:
            d = d + square(num)
        for num in List2:
            e = e + square(num)

        f = multiplication(int(d), e)
        g = square_root(int(f))
        h = division(int(g), cc)
        # pprint(float(cc))
        # pprint(e)
        # pprint(f)
        # pprint(float(g))
        # pprint(str(round(h,9)))
        nCorrelation = round(h, 9)
        # pprint(nCorrelation)
        return nCorrelation
    except ZeroDivisionError:
        print("Error - Cannot divide by 0")
    except ValueError:
        print("Error - Invalid data inputs")

# https://www.statisticshowto.datasciencecentral.com/probability-and-statistics/correlation-coefficient-formula/

# https://www.geeksforgeeks.org/numpy-subtract-in-python/


import math as m
import numpy as np
from matplotlib import pyplot as plt

a=1; b=-62.1; c=1
x1=(-b+m.sqrt(b**2-4*a*c))/(2*a)
x2=(-b-m.sqrt(b**2-4*a*c))/(2*a)
print('x1=',x1,', x2=',x2)
print('p(x1)=',a*x1**2+b*x1+c)
print('p(x2)=',a*x2**2+b*x2+c)

x = np.linspace(-2, 2, 1000)
y = a*x**2+b*x+c

plt.plot(x, y)

def raizCuad(a,b,c):
  D=b**2-4*a*c
  if D>0:
    if b>=0:
      x1=-2*c/(b+m.sqrt(D))
      x2=(-b-m.sqrt(D))/(2*a)
    else:
      x1=(-b-m.sqrt(D))/(2*a)
      x2=-2*c/(b-m.sqrt(D))
    return [x1,x2]
  elif D==0:
    x=-b/(2*a)
    return x
  else:
    return "no existen raíces reales"

a=1; b=62.1; c=1
xnuevo=raizCuad(a,b,c)
print(xnuevo)
print('p(x1)=',a*xnuevo[0]**2+b*xnuevo[0]+c)
print('p(x2)=',a*xnuevo[1]**2+b*xnuevo[1]+c)


import numpy, scipy.special, scipy.integrate

def HIMF(logMHI):
    '''
    ALFALFA 100% HI mass function Schechter model.

    Inputs:

    logMHI = log(M_HI/M_sol)

    Outputs:

    n = Number density at log(M_HI/M_sol)
    '''

    alpha = -1.25
    phi_s = 4.5E-3
    M_s = 10.**9.94

    MHI = 10.**logMHI

    return numpy.log(10.)*phi_s*((MHI/M_s)**(alpha+1))*numpy.exp(-MHI/M_s)

#Cosmology
#WMAP9
H0 = 69.7 #km/s/Mpc
O_m = 0.2814
O_L = 1.-O_m
c = 299792.458 #km/s
O_HI = 3.9E-4
G = 6.67E-11

rho_c_0 = 3.*((H0/3.086E19)**2.)/(8.*numpy.pi*G)
rho_HI_0 = O_HI*rho_c_0*(((3.086E22)**3.)/2.E30) #M_sol/Mpc^3

def E(z):

    return numpy.sqrt((O_m*((1.+z)**3.)) + O_L)

def co_dist(z):

    d_H = c/H0

    return d_H * scipy.integrate.quad(lambda x: 1./E(x),0.,z)[0]

def phys_size(theta,z):
    '''
    Returns the physical size of the beam in Mpc at a given redshift.

    Inputs:

    theta = Angular size (radians) at z=0
    z = Redshift

    Outputs:

    l = Physical size (Mpc)
    '''

    theta_eff = theta*(1.+z)

    return co_dist(z)*theta_eff/(1.+z)

def m_av_mod(y,x):
    '''
    Integral over the 2PCF in velocity space (y-direction) and angular space (x-direction).
    Uses the fit made in Jones et al. 2016. Both x and y should be in Mpc.

    Inputs:

    y = Integration limit in velocity space (Mpc)
    x = Integration limit in the angular direcction (Mpc)

    Output:

    m = Effective volume (Mpc^3) due to clustering in 2PCF.
    '''

    A, alpha, a = 12.104434  ,   -1.13210593,   0.64058959
    b = 1./a

    r = A**(-1./alpha)

    return a*((2.*numpy.pi)/((alpha+2.)*(alpha+3.)*r**alpha)) * ( (y/b)*((x/a)**2.)*((r)**alpha)*(alpha+2.)*(alpha+3.) + 2.*(y/b)*((x/a)**(2.+alpha))*(alpha+3.)*scipy.special.hyp2f1(0.5,-1.-(alpha/2.),1.5,-(y*a/(x*b))**2.) - 2.*(y/b)**(alpha+3.) )


import numpy as np

height = [74, 74, 72, 72, 73, 69, 69, 71, 76, 71, 73, 73, 74, 74, 69]
weight = [180, 215, 210, 210, 188, 176, 209, 200, 231, 180, 188, 180, 185, 160, 180]

# bmi
# weight kg / height * height m
# calcuale bmi
np_height = np.array(height)
np_weight = np.array(weight)

bmi = np_weight / np_height **2
print("BMI of All baseball players ",bmi)

#lightweight player
lightweight = bmi >= 0.04

print('Light Weight Player ',bmi[lightweight])


import numpy as np

from math import log, exp

To = 12000
Tw = 2000
m = 5

#First part
def solveIntegralNT(a, b, p):

    sum = .0
    N = 30
    step = (b - a) / (1.0 * N)

    for i in range(1, N):
        sum += NT(a + i * step, p)

    sum += (NT(a, p) + NT(b, p)) / 2
    sum *= step

    return sum

def Fp_funct(p):
    const_part = 7243.0 * (0.5 / 300.0)
    res = const_part - 2.0 * solveIntegralNT(0.0, 1.0, p)
    return res

def calculeateP(eps):
    p1 = 3.0
    p2 = 28.0

    p = (p1 + p2) / 2
    while abs((p2 - p1) / p) >= eps:
        p = (p1 + p2) / 2

        if Fp_funct(p)*Fp_funct(p1) < 0:
            p2 = p
        else:
            p1 = p
    return p

def TZ(z):
    zm = 1
    for i in range(1, m+1):
        zm *= z
    return To + (Tw - To)*zm

def NT(z, p):
    res = 1.0
    Tz = TZ(z)
    if Tz != 0:
        res = 7243.0 * (p / Tz) * z
    return res

p = calculeateP(1e-4)
print(p)
print("\n")

Second course/4th semester/Computational Algorithms/Lab5 - idk_lab_name/1st part.py


"""
PolynomialGenerator class definition
"""
from math import comb, factorial
import numpy as np

class PolynomialGenerator():
    """
    PolynomialGenerator generates polynomial trajectories from constraints
    """

    def __init__(self):
        pass

    def generate_coefficients(self, q_init, q_final, t_f, t_0=0.0):
        """
        Calculates polynomial trajectory coefficients from constraints

        Args:
            q_init (list): list of constraints: [q_init, dq_init, ddq_init]
            q_final (list): list of constraints: [q_final, dq_final, ddq_final]
            t_f (float): Finish time
            t_0 (float, optional): Start time

        Returns:
            np.array: Resultant polynomial coefficients
        """
        A_0 = self._get_constraint_submatrix(t_0)
        A_f = self._get_constraint_submatrix(t_f)

        A = np.vstack((A_0, A_f))
        q = np.hstack((np.array(q_init), np.array(q_final)))

        return np.linalg.inv(A).dot(q)

    def _get_constraint_submatrix(self, t):
        """
        Generates time constraint submatrix for time t

        Args:
            t (float): Time constraint

        Returns:
            np.ndarray: Time constraint submatrix
        """
        A = np.array([[1, t, t**2, t**3, t**4, t**5],
                      [0, 1, 2 * t, 3 * t**2, 4 * t**3, 5 * t**4],
                      [0, 0, 2, 6 * t, 12 * t**2, 20 * t**3]])
        return A

    def polynomial_from_coefs(self, coefs, from_t, to_t, n):
        """
        Generates discrete polynomial of len(coefs) - 1 degree of size n

        Args:
            coefs (array-like): Coefficients of the polynomial to generate
            from_t (float): Start point
            to_t (float): End point
            n (int): Number of points

        Returns:
            np.ndarray: Array of n points from the polynomial
        """
        ts = np.linspace(from_t, to_t, n)
        poly = 0

        for i, coef in enumerate(coefs):
            poly += coef * ts**i

        return poly

    def dpolynomial_from_coefs(self, d, coefs, from_t, to_t, n):
        """
        Generates dth derivative of a polynomial with coefficients coefs

        Args:
            d (TYPE): Order of the derivative
            coefs (array-like): Coefficients of the polynomial to generate
            from_t (float): Start point
            to_t (float): End point
            n (int): Number of points

        Returns:
            np.array: Array of n points from the polynomial's dth derivative
        """
        ts = np.linspace(from_t, to_t, n)
        poly = 0

        if d >= len(coefs):
            return np.zeros(n)

        for i, coef in enumerate(coefs[d:], d):
            poly += coef * factorial(d) * comb(i, d) * ts**(i - d)

        return poly


import numpy as np
from scipy.stats import norm

# Underlying informations
S0 = 100.0
sigma = 0.2

# European option informations
T = 1.0
K = 100.0
r = 0.05

# Binomial tree parameters
nbr_steps = 1000
dt = T/nbr_steps
u = np.exp(sigma*np.sqrt(dt))
d = 1/u
p = (np.exp(r*dt)-d)/(u-d)

# parameters for greek calculation
d_sigma = sigma/100

# European call price and greeks according to Black-Scholes

def d1():
    return (np.log(S0/K)+(r+0.5*sigma**2)*T)/(sigma*np.sqrt(T))

def d2():
    return d1() - sigma*np.sqrt(T)

def price_BS(S0):
    return S0*norm.cdf(d1())-K*np.exp(-r*T)*norm.cdf(d2())

def delta_BS():
    return norm.cdf(d1())

def gamma_BS():
    return norm.pdf(d1())/(S0*sigma*np.sqrt(T))

def theta_BS():
    return -S0*norm.pdf(d1())*sigma/(2*np.sqrt(T))-r*K*np.exp(-r*T)*norm.cdf(d2())

def vega_BS():
    return S0*np.sqrt(T)*norm.pdf(d1())

# European call price and greeks according to the binomial tree

def binom_tree(sigma):
    u = np.exp(sigma*np.sqrt(dt))
    d = 1/u
    p = (np.exp(r*dt)-d)/(u-d)

    steps = []
    steps.append([[S0, 0.0]])

    # Generating prices
    for i in range(2, nbr_steps+2):
        step = []
        for j in reversed(range(i)):
            step.append([S0*(u**(j+1))*d**(i-j), 0.0])
        steps.append(step)

    # Reverse induction payoff
    for j in range(nbr_steps+1):
        steps[nbr_steps][j][1] = max(0.0, steps[nbr_steps][j][0]-K)

    for i in reversed(range(nbr_steps)):
        for j in range(i+1):
            steps[i][j][1] = np.exp(-r*dt)*(p*steps[i+1][j][1]
                                            + (1-p)*steps[i+1][j+1][1])

    return steps

def price_binom():
    return binom_tree(sigma)[0][0][1]

def delta_binom():
    tree = binom_tree(sigma)
    return (tree[1][0][1]-tree[1][1][1])/(S0*(u-d))

def gamma_binom():
    tree = binom_tree(sigma)
    return ((tree[2][0][1] - tree[2][1][1])/(S0*(u**2-1))
            - (tree[2][1][1] - tree[2][2][1])/(S0*(1-d**2)))/(0.5*S0*(u**2-d**2))

def theta_binom():
    tree = binom_tree(sigma)
    return (tree[2][1][1] - tree[0][0][1])/(2*dt)

def vega_binom(d_sigma):
    tree = binom_tree(sigma)
    tree_d_sigma = binom_tree(sigma+d_sigma)
    return (tree_d_sigma[0][0][1] - tree[0][0][1])/d_sigma

# Testing
delta_bs, delta_mc = delta_BS(), delta_binom()
print('Delta : \nTheorical value : {} ; Binomial tree value : {} ; Error : {} %'
    .format(delta_bs, delta_mc, 100*np.round(np.abs((delta_mc - delta_bs)/delta_bs), 5)))
gamma_bs, gamma_mc = gamma_BS(), gamma_binom()
print('Gamma : \nTheorical value : {} ; Binomial tree value : {} ; Error : {} %'
    .format(gamma_bs, gamma_mc, 100*np.round(np.abs((gamma_mc - gamma_bs)/gamma_bs), 5)))
theta_bs, theta_mc = theta_BS(), theta_binom()
print('Theta : \nTheorical value : {} ; Binomial tree value : {} ; Error : {} %'
    .format(theta_bs, theta_mc, 100*np.round(np.abs((theta_mc - theta_bs)/theta_bs), 5)))
vega_bs, vega_mc = vega_BS(), vega_binom(d_sigma)
print('Vega : \nTheorical value : {} ; Binomial tree value : {} ; Error : {} %'
    .format(vega_bs, vega_mc, 100*np.round(np.abs((vega_mc - vega_bs)/vega_bs), 5)))

input('Press enter to continue...')


import numpy as np
from scipy.integrate import simps
from scipy.optimize import root_scalar, fsolve

from ..constants import speed_of_light, omega_matter, omega_lamda, H0
cc = speed_of_light*1e-3 # Km/s

def distance_from_redshift(z):
    """
    Returns the luminosity distance [Mpc] from the redshift.

    Uses the Planck 2018 cosmological parameters from Tab. 1 in https://arxiv.org/abs/1807.06209.

    :param float z: Redshift.

    :rtype: float
    """
    integrand = lambda x: 1/np.sqrt(omega_matter*(1+x)**3+omega_lamda)
    dz = 1e-5
    X = np.arange(0,z+dz,dz)
    Y = integrand(X)
    d_L = cc/H0*(1+z)*simps(Y,X)
    return d_L

def redshift_from_distance(d_L):
    """
    Returns the redshift from the luminosity distance [Mpc].

    Uses the Planck 2018 cosmological parameters from Tab. 1 in https://arxiv.org/abs/1807.06209.

    :param float d_L: Luminosity distance [Mpc]. Must be no less than 1.

    :rtype: float
    """
    if d_L<1:
        raise ValueError('d_L must be no less than 1 Mpc!')
    f = lambda x: d_L - distance_from_redshift(x)
    z = fsolve(f,10).item()
    return z