"""Calculates implied volatility/the greeks for a call or a put, according to the Black-Scholes-Merton model.

Contents
--------
JIT compiled functions (thanks numba) that compute BSM formulas, as well as an OptionContract class that acts as a
container for the results.

Sources
-------
Implementation:
    https://en.wikipedia.org/wiki/Black–Scholes_model
    https://en.wikipedia.org/wiki/Greeks_(finance)
    https://en.wikipedia.org/wiki/Bisection_method
    https://www.amazon.com/Options-as-Strategic-Investment-Fifth/dp/0735204659
    https://www.amazon.com/Options-Futures-Other-Derivatives-10th-dp-013447208X/dp/013447208X/ref=dp_ob_title_bk
Intuition:
    https://quant.stackexchange.com/questions/12901/does-implied-volatility-always-exist
    https://www.youtube.com/watch?v=spkim5Ns304
    https://www.youtube.com/watch?v=lD8nzTETqcs
    https://www.reddit.com/r/options/comments/hgg1c1/clear_explanation_of_d1_in_black_scholes/?sort=confidence
    https://financetrainingcourse.com/education/wp-content/uploads/2011/03/Understanding.pdf

Note
-----
Models are just models. The BSM model famously fails at describing reality, but it can still be useful for thinking
about the market in a different way.

In the end supply and demand are the only things that determine the market price of anything...just because a model says
something should happen doesn't mean it will.
"""

import datetime as dt
import json
from json import dumps
from numba import jit, double
from numpy import sqrt, log, power, exp, pi, inf  # , sin
# from scipy.stats import norm
# from scipy.integrate import quad

'''
General functions
'''

def year_delta(start: str, end: str) -> float:
    """
    Arguments
    ---------
    start : str
        Starting datetime (YYYY-MM-DD HH:MM:SS)
    end   : str
        Ending datetime (YYYY-MM-DD HH:MM:SS)

    Returns
    -------
    float : the time between start/end in years, at a resolution of seconds
    """
    tdelta = dt.datetime.strptime(
        end, '%Y-%m-%d %H:%M:%S') - dt.datetime.strptime(start, '%Y-%m-%d %H:%M:%S')
    return (tdelta.days / 365) + (tdelta.seconds / (365*24*60*60))

@jit(nopython=True)
def phi(x: float) -> float:
    """Standard normal (0, 1) probability density function.

    Arguments
    ---------
    x : float
        z-score to plug into the PDF

    Returns
    -------
    float
        Probability density at x.
    """
    return exp(-0.5*power(x, 2)) / sqrt(2*pi)

@jit(nopython=True)
def N(x: float) -> double:
    """Standard normal (0, 1) cumulative distribution function.

    Arguments
    ---------
    x : float
        z-score to plug into the CDF

    Returns
    -------
    double
        Probability of some observation being x or less.

    Note
    ----
    Tried many different ways to cut down on calculation time. scipy.stats.norm.cdf is super accurate, but super slow
    because there is a lot of overhead every time it's called. Unfortunately this can't be JITted away, because at its
    core, scipy is calling a FORTRAN routine to make calculation.

    Algebraic approximations are lightning fast, but not accurate enough for extremely ITM or OTM options, causing the
    bisection algorithm to not converge on a price and hit the max_iter limit.

    Fortunately, the approximation from McMillan is very accurate. I want to find out where he got it.
    """
    x = double(x)
    # http://jjmie.hu.edu.jo/vol-13-4/16-19-01.pdf
    # absx = abs(x)
    # return {
    #     True: (1 / (1 + exp(-1.702*x))) - 0.0095*sin(2.5*x)*exp(-0.01*power(x, 2)),
    #     False: (1 / (1 + exp(-1.702*x))) + power(10, -3)*(
    #         0.2656*power(absx, 5) -
    #         5.1970*power(absx, 4) +
    #         39.810*power(absx, 3) -
    #         147.60*power(absx, 2) +
    #         258.64*absx - 163.3
    #     )
    # }[absx <= 2]
    # https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.norm.html
    # return norm.cdf(x)
    # https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.quad.html
    # return quad(PDF, -inf, x)[0]
    # https://www.researchgate.net/publication/337326463_An_accurate_approximation_for_the_standard_normal_distribution_function
    # y = power(
    #     0.8601423*power(x, 2 / 3) +
    #     0.0007497*power(x, 5 /3) -
    #     0.0056102*power(x, 8 / 3) +
    #     0.0008232*power(x, 11 / 3),
    #     3
    # )
    # return 0.5*(1 + sqrt(1 - exp(-y)))
    # https://www.amazon.com/Options-as-Strategic-Investment-Fifth/dp/0735204659
    absx = abs(x)
    y = 1 / (1 + 0.2316419*absx)
    z = 0.3989423*exp(-0.5*power(x, 2))
    xx = 1 - z*(
        1.330274*power(y, 5) -
        1.821256*power(y, 4) +
        1.781478*power(y, 3) -
        0.356538*power(y, 2) +
        0.3193815*y
    )
    return xx if x > 0 else 1 - xx

'''
Pricing functions
'''

@jit(nopython=True)
def d1_func(S: float, K: float, t: float, sigma: float, r: float, q: float) -> float:
    """Z-score of current log return.

    Arguments
    ---------
    S     : float
        Spot price of the underlying
    K     : float
        Strike price
    t     : float
        Time until expiration, in units of years
    sigma : float
        Annualized volatility of log returns
    r     : float
        Annualized risk free rate of return
    q     : float
        Annualized dividend yield of the underlying

    Returns
    -------
    d1 : float

    Note
    ----
    Remember a Z-score is a deviation from the mean, normalized by the standard deviation.

        (x - mu) / sigma

        In the Black-Scholes world:
            x is the current log return we could get by exercising right now:
                ln(S / K)
            mu is the return we can expect on average:
                ((r - q) + 0.5*sigma^2)*t
                 ^ risk free rate
                ((r - q) + 0.5*sigma^2)*t
                     ^ less the amount the stock drops from the dividend
                ((r - q) + 0.5*sigma^2)*t
                               ^ plus some amount due to the normal fluctuation of returns
                ((r - q) + 0.5*sigma^2)*t
                                        ^ scaled by the time left until expiration
            sigma is the annualized volatility scaled by the time left until expiration:
                sigma*sqrt(t)
                      ^ we do a sqrt because sigma is a stdev which is the sqrt of the variance

    Think of d1 as some quantity of standard deviations of log returns. When we plug this into N(), we are calculating
    the probability of this number of standard deviations occurring.
    """
    return (log(S / K) + (r - q + 0.5*power(sigma, 2))*t) / (sigma*sqrt(t))

@jit(nopython=True)
def d2_func(S: float, K: float, t: float, sigma: float, r: float, q: float) -> float:
    """Z-score of current log return.

    Arguments
    ---------
    S     : float
        Spot price of the underlying
    K     : float
        Strike price
    t     : float
        Time until expiration, in units of years
    sigma : float
        Annualized volatility of log returns
    r     : float
        Annualized risk free rate of return
    q     : float
        Annualized dividend yield of the underlying

    Returns
    -------
    d2 : float

    Note
    ----
    Remember a Z-score is a deviation from the mean, normalized by the standard deviation.

        (x - mu) / sigma

        In the Black-Scholes world:
            x is the current log return we could get by exercising right now:
                ln(S / K)
            mu is the return we can expect on average:
                ((r - q) - 0.5*sigma^2)*t
                 ^ risk free rate
                ((r - q) - 0.5*sigma^2)*t
                     ^ less the amount the stock drops from the dividend
                ((r - q) - 0.5*sigma^2)*t
                               ^ plus some amount due to the normal fluctuation of returns
                ((r - q) - 0.5*sigma^2)*t
                                        ^ scaled by the time left until expiration
            sigma is the annualized volatility scaled by the time left until expiration:
                sigma*sqrt(t)
                      ^ we do a sqrt because sigma is a stdev which is the sqrt of the variance

    Think of d2 as some quantity of standard deviations of log returns. When we plug this into N(), we are calculating
    the probability of this number of standard deviations occurring.
    """
    return (log(S / K) + (r - q - 0.5*power(sigma, 2))*t) / (sigma*sqrt(t))

@jit(nopython=True)
def BSMprice(opt_type: int, S: float, K: float, t: float, sigma: float, r: float, q: float) -> float:
    """Black-Scholes-Merton price of an option.

    Arguments
    ---------
    opt_type : int
        1 for a call, 0 for a put
    S        : float
        Spot price of the underlying
    K        : float
        Strike price
    t        : float
        Time until expiration, in units of years
    sigma    : float
        Annualized volatility of log returns
    r        : float
        Annualized risk free rate of return
    q        : float
        Annualized dividend yield of the underlying

    Returns
    -------
    float
        The price of the option with the given parameters, as defined by the Black-Scholes-Merton model.

    Note
    -----
    For a call option, here is the rough meaning for each term:
    S*exp(-q*t)*N(d1) - K*exp(-r*t)*N(d2)
    S*exp(-q*t)                             => present value of the stock price, accounting for the dividend
               *                               aka stock price discounted by the amount it will drop due to the div
                N(d1)                       => probability that takes into account 'underwater outcomes' will be 0
                       -                       that is, the payoff is zero if the stock is not above the strike
                        K*exp(-r*t)         => present value of the strike price, accounting for the risk free rate
                                   *           aka strike discounted by the amount that cash to buy will grow at the rfr
                                    N(d2)   => probability of the option being exercised
                                               that is, the probability of the stock price being above the strike
    """
    d1 = d1_func(S, K, t, sigma, r, q)
    d2 = d2_func(S, K, t, sigma, r, q)
    if opt_type == 1:
        return S*exp(-q*t)*N(d1) - K*exp(-r*t)*N(d2)
    elif opt_type == 0:
        return K*exp(-r*t)*N(-d2) - S*exp(-q*t)*N(-d1)

'''
Greeks functions
'''

@jit(nopython=True)
def BSMIV(opt_type: int, V: float, S: float, K: float, t: float, r: float, q: float) -> float:
    """Calculation of Black-Scholes-Merton implied volatility using the bisection method.

    Arguments
    ---------
    opt_type : int
        1 for a call, 0 for a put
    V        : float
        Spot price of the option
    S        : float
        Spot price of the underlying
    K        : float
        Strike price
    t        : float
        Time until expiration, in units of years
    r        : float
        Annualized risk free rate of return
    q        : float
        Annualized dividend yield of the underlying

    Returns
    -------
    float
        The volatility implied by the price of the option with the given parameters, as defined by the
        Black-Scholes-Merton model.

    Note
    ----
    Implied volatility has nothing to do with the historical volatility of the underlying asset. It is the volatility
    that is expected from now until the option's expiration. More literally (and correctly), it is the volatility that
    makes the current option price 'fair' according to BSM.
    """
    intval = 0.0
    if opt_type == 1:               # Initial check that the OptionContract class already does, but since we are now
        # directly using this function outside the class, we need to perform these checks:
        intval = max([0.0, S - K])
    elif opt_type == 0:             # - the option price cannot be less than intrinsic value, else an arb exists
        # - the option price also cannot be greater than or equal to the stock price,
        intval = max([0.0, K - S])
    if V < intval:  # because that makes no sense
        return 0.0
    if V >= S:
        return inf

    # Flag for failing to find an implied volatility
    failed = False
    # Don't search for a starting lo guess more than 20 times
    max_iter_lo = 20
    # Don't search for a starting hi guess more than 20 times
    max_iter_hi = 20
    # Don't do the bisection loop more than 20 times
    max_iter = 20
    # Initial guess at an IV which ideally gives a price below V
    sigma_lo = 0.1
    # Initial guess at an IV which ideally gives a price above V
    sigma_hi = 0.5
    sigma_mid = 0.5*(sigma_lo + sigma_hi)   # Midpoint of the initial guesses

    counter_lo = 0
    # Calculate the price given by the low IV guess
    V_lo = BSMprice(opt_type, S, K, t, sigma_lo, r, q)
    # If the low price is at or above V, decrease the initial
    while (V_lo >= V) and (not failed):
        # low guess at IV until V_lo is less than V
        sigma_lo /= 1.1
        V_lo = BSMprice(opt_type, S, K, t, sigma_lo, r, q)
        counter_lo += 1
        if counter_lo == max_iter_lo:
            print('COULD NOT FIND INITIAL LO IV')
            failed = True

    counter_hi = 0
    # Calculate the price given by the high IV guess
    V_hi = BSMprice(opt_type, S, K, t, sigma_hi, r, q)
    # If the low price is at or below V, increase the initial
    while (V_hi <= V) and (not failed):
        # high guess at IV until V_hi is greater than V
        sigma_hi *= 1.1
        V_hi = BSMprice(opt_type, S, K, t, sigma_hi, r, q)
        counter_hi += 1
        if counter_hi == max_iter_hi:
            print('COULD NOT FIND INITIAL HI IV')
            failed = True

    # Calculate the price given by the midpoint IV guess
    V_mid = BSMprice(opt_type, S, K, t, sigma_mid, r, q)

    counter = 0
    # Iterate as long as sigma_mid price differs by $0.01
    while (round(V_mid, 2) != round(V, 2)) and (not failed):
        if V_mid < V:                       # If the mid price < V, make the mid IV the new low guess
            sigma_lo = sigma_mid
        if V_mid > V:                       # If the mid price > V, make the mid IV the new high guess
            sigma_hi = sigma_mid
        # Recalculate the middle IV guess
        sigma_mid = 0.5 * (sigma_lo + sigma_hi)
        # Recalculate the price given by the middle IV guess
        V_mid = BSMprice(opt_type, S, K, t, sigma_mid, r, q)
        counter += 1
        if counter >= max_iter:             # Return the middle IV guess if we reach max_iter, and print a message
            print('IV MAX ITER')
            failed = True

    if failed:
        return -1.0

    return sigma_mid

@jit(nopython=True)
def BSMdelta(opt_type: int, t: float, q: float, d1: float):
    """First derivative of V with respect to S (dV/dS).

    Arguments
    ---------
    opt_type : int
        1 for a call, 0 for a put
    t        : float
        Time until expiration, in units of years
    q        : float
        Annualized dividend yield of the underlying
    d1       : float
        z-score of the log return given by exercising the option right now

    Returns
    -------
    float
        delta

    Note
    ----
    There are two main interpretations of delta:
        1) As mentioned above, delta is the first derivative of the option price with respect to the spot price of the
           underlying. This can roughly be translated into the number of dollars the option price will increase for a $1
           increase in the underlying asset.

        2) It also can be interpreted as a rough probability that the option will expire $0.01 in the money. Note that
           this is NOT a probability of the underlying being above the strike at any point before expiration. THAT
           probability is roughly 2 times delta.

    The first interpretation is the most mathematically correct, and is the basis of hedging a portfolio against
    price changes of the underlying asset. For example:

        The delta of the underlying is always 1. Let's say you own 50 shares of some stock. If you buy a 0.5 delta put,
        you are (in theory) momentarily insulated from the stock dropping (and rising).

            1) If the stock price decreases by $1, you lose $50 from that, but gain $50 from the put increasing in value
                (0.5 delta x 100 share contract size = $50)

            2) Conversely, a $1 increase in the stock price will gain you $50, but you lose you $50 from the put
    """
    if opt_type == 1:
        return exp(-q*t)*N(d1)
    elif opt_type == 0:
        return -exp(-q*t)*N(-d1)

@jit(nopython=True)
def BSMvega(S: float, K: float, t: float, r: float, q: float, d1: float, d2: float):
    """First derivative of option price with respect to sigma (dV/dsigma).

    Arguments
    ---------
    S        : float
        Spot price of the underlying
    K        : float
        Strike price
    t        : float
        Time until expiration, in units of years
    r        : float
        Annualized risk free rate of return
    q        : float
        Annualized dividend yield of the underlying
    d1       : float
        z-score of the log return given by exercising the option right now
    d2       : float
        z-score of the log return given by exercising the option right now

    Returns
    -------
    float
        vega

    Note
    ----
    Vega is the sensitivity of the option price with respect to implied volatility. This can seem like circular logic,
    because as shown in the BSMIV function, you have to know the option price to know volatility, and volatility has
    nothing to do directly with the historical volatility of the underlying.

    Also note that both calls and puts will increase in price if volatility increases. This means that if expected
    volatility rises enough, the direction of the underlying asset price might not matter. For example:

        - You have one call option that has a delta of 0.5 and a vega of 0.6. The underlying drops by $1, but implied
          volatility rises by 1%. The option price loses $0.50 of value from the underlying price decrease, but gains
          $0.60 from the increasing expected volatility. The net price change of your call is +$0.10, even though the
          stock price decreased.

    The example above would have the same outcome if the call was instead a put, and the underlying price increased. It
    should also be noted that this means an option price can change even if the underlying price does not move (this
    should hopefully be obvious just from the simple fact that there are multiple parameters which determine the price
    of an option).
    """
    # Use both definitions
    vega1 = S*exp(-q*t)*phi(d1)*sqrt(t)
    vega2 = K*exp(-r*t)*phi(d2)*sqrt(t)
    return 0.5*(vega1 + vega2) / 100

@jit(nopython=True)
def BSMtheta(opt: int, S: float, K: float, t: float, sigma: float, r: float, q: float, d1: float, d2: float) -> float:
    """First derivative of option price with respect to time (dV/dt).

    Arguments
    ---------
    opt   : int
        1 for call, 0 for put
    S     : float
        Spot price of the underlying
    K     : float
        Strike price
    t     : float
        Time until expiration, in units of years
    sigma : float
        Annualized volatility of log returns
    r     : float
        Annualized risk free rate of return
    q     : float
        Annualized dividend yield of the underlying
    d1    : float
        z-score of the log return given by exercising the option right now
    d2    : float
        z-score of the log return given by exercising the option right now

    Returns
    -------
    float
        theta

    Note
    ----
    Theta is expressed in terms of value per year. To get value per calendar day, divide by 365, or by 252 for value per
    trading day.

    Theta is simply the amount that an option will (lose) due to the passage of time. It makes sense if you think about
    less time being less chance for an option to move (further) into the money.
    """
    if opt == 1:
        return (-exp(-q*t)*((S*phi(d1)*sigma) / (2*sqrt(t))) - r*K*exp(-r*t)*N(d2) + q*S*exp(-q*t)*N(d1)) / 365
    elif opt == 0:
        return (-exp(-q*t)*((S*phi(d1)*sigma) / (2*sqrt(t))) + r*K*exp(-r*t)*N(-d2) - q*S*exp(-q*t)*N(-d1)) / 365

@jit(nopython=True)
def BSMrho(opt_type: int, K: float, r: float, t: float, d2: float) -> float:
    """First derivative of option price with respect to the risk free rate (dV/dr).

    Arguments
    ---------
    opt_type : int
        1 for call, 0 for put
    K        : float
        Strike price
    t        : float
        Time until expiration, in units of years
    r        : float
        Annualized risk free rate of return
    d2       : float
        z-score of the log return given by exercising the option right now

    Returns
    -------
    float
        rho

    Note
    ----
    Rho gives the amount the option's value will change by given a 1% increase in the risk free interest rate.
    """
    if opt_type == 1:
        return K*t*exp(-r*t)*N(d2) / 100
    elif opt_type == 0:
        return -K*t*exp(-r*t)*N(-d2) / 100

@jit(nopython=True)
def BSMepsilon(opt_type: int, S: float, t: float, q: float, d1: float) -> float:
    """First derivative of option price with respect to the dividend yield (dV/dq).

    Arguments
    ---------
    opt_type : int
        1 for call, 0 for put
    S        : float
        Spot price of the underlying
    t        : float
        Time until expiration, in units of years
    q        : float
        Annualized dividend yield of the underlying
    d1       : float
        z-score of the log return given by exercising the option right now

    Returns
    -------
    float
        epsilon

    Note
    ----
    As defined here, this is the amount that the option's value will change given a 1% increase in the dividend yield.
    """
    if opt_type == 1:
        return -S*t*exp(-q*t)*N(d1) / 100
    elif opt_type == 0:
        return S*t*exp(-q*t)*N(-d1) / 100

@jit(nopython=True)
def BSMlambda(delta: float, S: float, V: float) -> float:
    """Not really a greek, but rather an expression of leverage.

    Arguments
    ---------
    delta : float
        BSM delta of the option
    V     : float
        Spot price of the option
    S     : float
        Spot price of the underlying

    Returns
    -------
    float
        lambda

    Note
    ----
    Percentage change in the option price per percentage change in the underlying asset's price.
    """
    return delta*(S / V)

@jit(nopython=True)
def BSMgamma(S: float, K: float, t: float, sigma: float, r: float, q: float, d1: float, d2: float) -> float:
    """First derivative of delta with respect to the price underlying (ddelta/dS). Also d2V/dS2.

    Arguments
    ---------
    S     : float
        Spot price of the underlying
    K     : float
        Strike price
    t     : float
        Time until expiration, in units of years
    sigma : float
        Annualized volatility of log returns
    r     : float
        Annualized risk free rate of return
    q     : float
        Annualized dividend yield of the underlying
    d1    : float
        z-score of the log return given by exercising the option right now
    d2    : float
        z-score of the log return given by exercising the option right now

    Returns
    -------
    float
        gamma

    Note
    ----
    Best thought of as: for every dollar the stock moves, delta changes by the amount given by gamma. Gamma is the
    highest at the money, and decreases for strikes farther in or out of the money. This effect becomes more extreme
    as time to expiration decreases.
    """
    # Use both definitions
    gamma1 = (exp(-q*t)*phi(d1)) / (S*sigma*sqrt(t))
    gamma2 = (K*exp(-r*t)*phi(d2)) / (power(S, 2)*sigma*sqrt(t))
    return 0.5*(gamma1 + gamma2)

@jit(nopython=True)
def BSMvanna(S: float, t: float, sigma: float, q: float, d1: float, d2: float, vega: float) -> float:
    """First derivative of delta with respect to volatility (ddelta/dsigma). Also dvega/dS.

    Arguments
    ---------
    S     : float
        Spot price of the underlying
    t     : float
        Time until expiration, in units of years
    sigma : float
        Annualized volatility of log returns
    q     : float
        Annualized dividend yield of the underlying
    d1    : float
        z-score of the log return given by exercising the option right now
    d2    : float
        z-score of the log return given by exercising the option right now
    vega  : float
        First derivative of option price with respect to volatility

    Returns
    -------
    float
        vanna

    Note
    ----
    Can be useful to know for a delta or vega hedged portfolio. Helps the trader determine the usefulness of a delta or
    vega hedge.
    """
    # Use both definitions
    vanna1 = -exp(-q*t)*phi(d1)*(d2 / sigma)
    vanna2 = (vega / S)*(1 - (d1 / (sigma*sqrt(t))))
    return 0.5*(vanna1 + vanna2)

@jit(nopython=True)
def BSMcharm(opt_type: int, t: float, sigma: float, r: float, q: float, d1: float, d2: float) -> float:
    """First derivative of delta with respect to time (ddelta/dt). Also dtheta/dS.

    Arguments
    ---------
    opt_type : int
        1 for call, 0 for put
    t        : float
        Time until expiration, in units of years
    sigma    : float
        Annualized volatility of log returns
    r        : float
        Annualized risk free rate of return
    q        : float
        Annualized dividend yield of the underlying
    d1       : float
        z-score of the log return given by exercising the option right now
    d2       : float
        z-score of the log return given by exercising the option right now

    Returns
    -------
    float
        charm

    Note
    ----
    Expressed in delta per year. For this reason we divide by 365 to get delta per day.

    Mostly accurate when there is a long time until expiration. When time to expiration gets small, charm can change
    often enough that full-day estimates may not be very accurate. Can be useful when delta-hedging a position over a
    weekend.
    """
    if opt_type == 1:
        return -q*exp(-q*t)*N(d1) - exp(-q*t)*phi(d1)*((2*t*(r - q) - d2*sigma*sqrt(t)) / (2*t*sigma*sqrt(t))) / 365
    if opt_type == 0:
        return -q*exp(-q*t)*N(-d1) - exp(-q*t)*phi(d1)*((2*t*(r - q) - d2*sigma*sqrt(t)) / (2*t*sigma*sqrt(t))) / 365

@jit(nopython=True)
def BSMvomma(S: float, t: float, sigma: float, q: float, d1: float, d2: float, vega: float) -> float:
    """First derivative of vega with respect to volatility (dvega/dsigma). Also d2V/dsigma2.

    Arguments
    ---------
    S     : float
        Spot price of the underlying
    t     : float
        Time until expiration, in units of years
    sigma : float
        Annualized volatility of log returns
    q     : float
        Annualized dividend yield of the underlying
    d1    : float
        z-score of the log return given by exercising the option right now
    d2    : float
        z-score of the log return given by exercising the option right now
    vega  : float
        First derivative of option price with respect to volatility

    Returns
    -------
    float
        vomma

    Note
    ----
    Measures second order sensitivity of the option price with respect to volatility, or the rate of change of vega as
    volatility changes.

    See https://en.wikipedia.org/wiki/Greeks_(finance)#Vomma for more info.
    """
    # Use both definitions
    vomma1 = S*exp(-q*t)*phi(d1)*sqrt(t)*((d1*d2) / sigma)
    vomma2 = vega*((d1*d2) / sigma)
    return 0.5*(vomma1 + vomma2)

@jit(nopython=True)
def BSMveta(S: float, t: float, sigma: float, r: float, q: float, d1: float, d2: float) -> float:
    """First derivative of vega with respect to time (dvega/dt).

    Arguments
    ---------
    S     : float
        Spot price of the underlying
    t     : float
        Time until expiration, in units of years
    sigma : float
        Annualized volatility of log returns
    r     : float
        Annualized risk free rate of return
    q     : float
        Annualized dividend yield of the underlying
    d1    : float
        z-score of the log return given by exercising the option right now
    d2    : float
        z-score of the log return given by exercising the option right now

    Returns
    -------
    float
        veta

    Note
    ----
    To get the percentage change in vega per day, divide by 100 times the number of days per calendar year.
    """
    return (-S*exp(-q*t)*phi(d1)*sqrt(t)*(q + ((d1*(r - q)) / (sigma*sqrt(t))) - ((1 + d1*d2) / (2*t)))) / (100*365)

@jit(nopython=True)
def BSMphi(S: float, K: float, t: float, sigma: float, r: float, q: float) -> float:
    """Second derivative of option price with respect to strike (d2V/dK2).

    Arguments
    ---------
    S     : float
        Spot price of the underlying
    K     : float
        Strike price
    t     : float
        Time until expiration, in units of years
    sigma : float
        Annualized volatility of log returns
    r     : float
        Annualized risk free rate of return
    q     : float
        Annualized dividend yield of the underlying

    Returns
    -------
    float
        phi

    Note
    ----
    From Wikipedia: This partial derivative has a fundamental role in the Breeden-Litzenberger formula, which uses
    quoted call option prices to estimate the risk-neutral probabilities implied by such prices.
    """
    return exp(-r*t)*(1 / K)*(1 / sqrt(2*pi*t*power(sigma, 2)))*exp(
        -(1 / (2*t*power(sigma, 2)))*power(
            log(K / S) - t*((r - q) - 0.5*power(sigma, 2)), 2
        )
    )

@jit(nopython=True)
def BSMspeed(S: float, t: float, sigma: float, q: float, d1: float, gamma: float) -> float:
    """First derivative of gamma with respect to option price (dgamma/dS). Also d3V/dS3.

    Arguments
    ---------
    S     : float
        Spot price of the underlying
    t     : float
        Time until expiration, in units of years
    sigma : float
        Annualized volatility of log returns
    q     : float
        Annualized dividend yield of the underlying
    d1    : float
        z-score of the log return given by exercising the option right now
    gamma : float
        Gamma of an option

    Returns
    -------
    float
        gamma

    Note
    ----
    Sometimes referred to as the gamma of gamma. Like other higher order greeks, can be useful when monitoring a delta-
    hedged portfolio.
    """
    # Use both definitions
    speed1 = -exp(-q*t) * (phi(d1) / (power(S, 2) * sigma *
                                      sqrt(t))) * ((d1 / (sigma * sqrt(t))) + 1)
    speed2 = -(gamma / S)*((d1 / (sigma*sqrt(t))) + 1)
    return 0.5*(speed1 + speed2)

@jit(nopython=True)
def BSMzomma(S: float, t: float, sigma: float, q: float, d1: float, d2: float, gamma: float) -> float:
    """First derivative of gamma with respect to volatility (dgamma/dsigma). Also dvanna/dS.

    Arguments
    ---------
    S     : float
        Spot price of the underlying
    t     : float
        Time until expiration, in units of years
    sigma : float
        Annualized volatility of log returns
    q     : float
        Annualized dividend yield of the underlying
    d1    : float
        z-score of the log return given by exercising the option right now
    d2    : float
        z-score of the log return given by exercising the option right now
    gamma : float
        Gamma of an option

    Returns
    -------
    float
        zomma

    Note
    ----
    Can be useful when monitoring a gamma-hedged portfolio.
    """
    # Use both definitions
    zomma1 = exp(-q*t)*((phi(d1) * (d1 * d2 - 1)) /
                        (S * power(sigma, 2) * sqrt(t)))
    zomma2 = gamma*((d1*d2 - 1) / sigma)
    return 0.5*(zomma1 + zomma2)

@jit(nopython=True)
def BSMcolor(S: float, t: float, sigma: float, r: float, q: float, d1: float, d2: float) -> float:
    """First derivative of gamma with respect to volatility (dgamma/dsigma). Also dvanna/dS.

    Arguments
    ---------
    S     : float
        Spot price of the underlying
    t     : float
        Time until expiration, in units of years
    sigma : float
        Annualized volatility of log returns
    r     : float
        Annualized risk free rate of return
    q     : float
        Annualized dividend yield of the underlying
    d1    : float
        z-score of the log return given by exercising the option right now
    d2    : float
        z-score of the log return given by exercising the option right now

    Returns
    -------
    float
        color

    Note
    ----
    Expressed in gamma per year. Divide by 365 to get gamma per day. Can be useful when monitoring a gamma-hedged
    portfolio.
    """
    return (-exp(-q*t) * (phi(d1) / (2*S*t*sigma*sqrt(t))))*(
        2*q*t + 1 + d1*((2*t*(r - q) - d2*sigma*sqrt(t)) / (sigma*sqrt(t)))
    ) / 365

@jit(nopython=True)
def BSMultima(sigma: float, d1: float, d2: float, vega: float) -> float:
    """First derivative of vomma with respect to volatility. (dvomma/dsigma). Also d3V/dS3.

    Arguments
    ---------
    sigma : float
        Annualized volatility of log returns
    d1    : float
        z-score of the log return given by exercising the option right now
    d2    : float
        z-score of the log return given by exercising the option right now
    vega  : float
        First derivative of option price with respect to volatility

    Returns
    -------
    float
        ultima

    Note
    ----
    Third-order sensitivity of the option price with respect to volatility.
    """
    return (-vega / power(sigma, 2))*(d1*d2*(1 - d1*d2) + power(d1, 2) + power(d2, 2))

@jit(nopython=True)
def BSMdualdelta(opt_type: float, t: float, r: float, d2: float) -> float:
    """First derivative of option price with respect to strike (dV/dK).

    Arguments
    ---------
    opt_type : int
        1 for call, 0 for put
    t        : float
        Time until expiration, in units of years
    r        : float
        Annualized risk free rate of return
    d2       : float
        z-score of the log return given by exercising the option right now

    Note
    ----
    The actual probability of an option finishing in the money is its dual delta.
    """
    if opt_type == 1:
        return exp(-r*t) * N(d2)
    elif opt_type == 0:
        return -exp(-r*t) * N(-d2)

@jit(nopython=True)
def BSMdualgamma(K: float, t: float, sigma: float, r: float, d2: float) -> float:
    """First derivative of delta with respect to strike (ddelta/dK).

    Arguments
    ---------
    K     : float
        Strike price
    t        : float
        Time until expiration, in units of years
    sigma : float
        Annualized volatility of log returns
    r        : float
        Annualized risk free rate of return
    d2       : float
        z-score of the log return given by exercising the option right now

    Note
    ----
    The actual gamma?
    """
    return exp(-r*t)*(phi(d2) / (K * sigma * sqrt(t)))

'''
OptionContract class
'''

class OptionContract:
    """Black-Scholes-Merton option contract.

    Required Arguments
    ------------------
    opt_type : str
        'call' or 'put'
    exp_date : str
        Expiration date of the option. 'YYYY-MM-DD'
    V       : float
        Spot price of the option
    S       : float
        Spot price of the underlying
    K       : float
        Strike price

    Optional Arguments
    ------------------
    r                : float
        Annualized risk free rate of return. Default is 0.
    q                : float
        Annualized dividend yield of the underlying. Default is 0.
    exp_time         : str
        Time of day (24h time) that the option expires. Default is 16:00:00.
    current_datetime : str
        Datetime  to use as the current date/time (like 'YYYY-MM-DD HH:MM:SS'). Default is the current date and time.
    IV               : float
        If implied volatility has already been obtained, use it to calculate the greeks. Default is None.
    price_type       : str
        See which price made the IV calc fail, if it did.

    Attributes
    ----------
    current_datetime : str
    expiration         : str
        Datetime like 'YYYY-MM-DD HH:MM:SS'
    opt_type           : float
    V                  : float
    S                  : float
    K                  : float
    r                  : float
    q                  : float
    t                  : float
        Time until expiration in years. Second resolution.
    IV                 : float
    delta              : float
    vega:              : float
    theta              : float
    rho                : float
    epsilon            : float
    Lambda             : float
    gamma              : float
    vanna              : float
    charm              : float
    vomma              : float
    veta               : float
    phi                : float
    speed              : float
    zomma              : float
    color              : float
    ultima             : float
    dualdelta          : float
    dualgamma          : float

    Note
    ----
    Initial versions of this class contained all the calculation functions, but it's just too slow for a whole chain.
    By extracting them out into separate functions, we can use numba to JIT compile them and make them a lot faster.
    """

    def __init__(
            self, opt_type,
            exp_date: str,
            V: float,
            S: float,
            K: float,
            r: float = 0,
            q: float = 0,
            exp_time: str = '16:00:00',
            current_datetime: str = dt.datetime.strftime(dt.datetime.now(), '%Y-%m-%d %H:%M:%S'),
            IV: float = None,
            price_type: str = None
    ):
        #
        # Assign inputs
        #
        self.current_datetime = current_datetime
        self.__exp_date = exp_date
        self.__exp_time = exp_time
        self.expiration = f'{exp_date} {exp_time}'
        self.opt_type = opt_type
        self.itm = {'call': S > K, 'put': K >= S}[self.opt_type]
        self.V = max([float(V), 0.01])
        self.S = float(S)
        self.K = float(K)
        self.r = float(r)
        self.q = float(q)
        self.t = float(year_delta(self.current_datetime, self.expiration))
        #
        # Initialize the greeks
        #
        self.IV = 0.0
        self.delta = 0.0
        self.vega = 0.0
        self.theta = 0.0
        self.rho = 0.0
        self.epsilon = 0.0
        self.Lambda = 0.0
        self.gamma = 0.0
        self.vanna = 0.0
        self.charm = 0.0
        self.vomma = 0.0
        self.veta = 0.0
        self.phi = 0.0
        self.speed = 0.0
        self.zomma = 0.0
        self.color = 0.0
        self.ultima = 0.0
        self.dualdelta = 0.0
        self.dualgamma = 0.0
        #
        # IV calc
        #
        opt_type_01 = {'call': 1, 'put': 0}[self.opt_type]
        # If there is no supplied IV, calculate it (if not conditions 1&2)
        if IV is None:
            if self.V <= {'call': max([0.0, S - K]), 'put': max([0.0, K - S])}[self.opt_type]:
                # Condition 1) IV = 0 if V less than intrinsic value
                self.IV = 0
            elif self.V > self.S:
                # Condition 2) IV = inf if V greater than the stock price
                self.IV = inf
            else:
                self.IV = BSMIV(opt_type_01, self.V, self.S,
                                self.K, self.t, self.r, self.q)
                if self.IV == -1.0:
                    self.IV = 0
                    price_type_str = f' ({price_type})' if price_type is not None else ''
                    print(
                        json.dumps(
                            {
                                'exp': self.expiration,
                                'opt_type': self.opt_type,
                                'itm': self.itm,
                                'V': '{:.2f}'.format(self.V) + price_type_str,
                                'S': '{:.2f}'.format(self.S),
                                'K': '{:.2f}'.format(self.K),
                                'r': self.r,
                                'q': self.q,
                                't': self.t
                            }, indent=4
                        )
                    )
        else:
            self.IV = IV                            # If there is a supplied IV, use it
        if self.IV != 0 and self.IV != inf:         # If we have a valid IV, calculate the greeks
            d1 = d1_func(self.S, self.K, self.t, self.IV, self.r, self.q)
            d2 = d2_func(self.S, self.K, self.t, self.IV, self.r, self.q)
            #
            # First order greeks
            #
            self.delta = BSMdelta(opt_type_01, self.t, self.q, d1)
            self.vega = BSMvega(self.S, self.K, self.t, self.r, self.q, d1, d2)
            self.theta = BSMtheta(
                opt_type_01, self.S, self.K, self.t, self.IV, self.r, self.q, d1, d2)
            self.rho = BSMrho(opt_type_01, self.K, self.r, self.t, d2)
            self.epsilon = BSMepsilon(opt_type_01, self.S, self.t, self.q, d1)
            self.Lambda = BSMlambda(self.delta, self.S, self.V)
            #
            # Second order greeks
            #
            self.gamma = BSMgamma(self.S, self.K, self.t,
                                  self.IV, self.r, self.q, d1, d2)
            self.vanna = BSMvanna(self.S, self.t, self.IV,
                                  self.q, d1, d2, self.vega)
            self.charm = BSMcharm(opt_type_01, self.t,
                                  self.IV, self.r, self.q, d1, d2)
            self.vomma = BSMvomma(self.S, self.t, self.IV,
                                  self.q, d1, d2, self.vega)
            self.veta = BSMveta(self.S, self.t, self.IV,
                                self.r, self.q, d1, d2)
            self.phi = BSMphi(self.S, self.K, self.t, self.IV, self.r, self.q)
            #
            # Third order greeks
            #
            self.speed = BSMspeed(self.S, self.t, self.IV,
                                  self.q, d1, self.gamma)
            self.zomma = BSMzomma(self.S, self.t, self.IV,
                                  self.q, d1, d2, self.gamma)
            self.color = BSMcolor(self.S, self.t, self.IV,
                                  self.r, self.q, d1, d2)
            self.color = BSMcolor(self.S, self.t, self.IV,
                                  self.r, self.q, d1, d2)
            self.ultima = BSMultima(self.IV, d1, d2, self.vega)
            #
            # Misc greeks
            #
            self.dualdelta = BSMdualdelta(opt_type_01, self.t, self.r, d2)
            self.dualgamma = BSMdualgamma(self.K, self.t, self.IV, self.r, d2)

    def __repr__(self):
        params = [
            f'"{self.opt_type}"',
            f'"{self.__exp_date}"',
            f'{self.V}', f'{self.S}', f'{self.K}',
            f'r={self.r}', f'q={self.q}', f'exp_time={self.__exp_time}',
            f'current_datetime={self.current_datetime}'
        ]
        return f'{self.__class__.__name__}({", ".join(params)})'

    def __str__(self):
        return dumps(self.__dict__, indent=4)


import numpy as np

def logit(x):
    """
    Logit function

    Args:
    x (TYPE): x

    Returns:
    TYPE: transformed x
    """
    return np.log(x / (1.0 - x))

def invLogit(x):
    """
    Inverse logit function

    Args:
    x (TYPE): x

    Returns:
    TYPE: transformed x
    """
    return 1.0 / (1.0 + np.exp(-x))

def lrBoundedBackward(x, lower, upper):
    """
    Transform from physical parameters bounded in the interval [lower,upper] to transformed (unconstrained) ones

    Args:
        x (TYPE): vector of parameters
        lower (float): vector with lower limits
        upper (float): vector with upper limits

    Returns:
    TYPE: transformed vector of parameters
    """
    return logit( (x-lower) / (upper - lower) )

def lrBoundedForward(x, lower, upper):
    """
    Transform from transformed (unconstrained) parameters to physical ones bounded in the interval [lower,upper]

    Args:
        x (float): vector of transformed parameters
        lower (float): vector with lower limits
        upper (float): vector with upper limits

    Returns:
    Float: transformed variables and log Jacobian
    """
    temp = invLogit(x)
    return lower + (upper - lower) * temp, np.log(upper - lower) + np.log(temp) + np.log(1.0 - temp)

def lBoundedBackward(x, lower):
    """
    Transform from physical parameters with lower limit to transformed (unconstrained) ones

    Args:
        x (float): vector of parameters
        lower (float): vector with lower limits

    Returns:
    Float: transformed vector of parameters
    """
    return np.log(x-lower)

def lBoundedForward(x, lower):
    """
    Transform from transformed (unconstrained) parameters to physical ones with upper limit

    Args:
        x (float): vector of transformed parameters
        lower (float): vector with lower limits

    Returns:
    Float: transformed variables and log Jacobian
    """
    return np.exp(x) + lower, x

def rBoundedBackward(x, upper):
    """
    Transform from physical parameters with upper limit to transformed (unconstrained) ones

    Args:
        x (float): vector of parameters
        upper (float): vector with upper limits

    Returns:
    Float: transformed vector of parameters
    """
    return np.log(upper-x)

def rBoundedForward(x, upper):
    """
    Transform from transformed (unconstrained) parameters to physical ones with upper limit

    Args:
        x (float): vector of transformed parameters
        upper (float): vector with upper limits

    Returns:
    Float: transformed variables and log Jacobian
    """
    return upper - np.exp(x), x

def simplexBackward(x):
    """
    Transform from a simplex of dimension K to transformed (unconstrained) parameters

    Args:
        x (float): vector of parameters of size K

    Returns:
    Float: transformed vector of parameters
    """
    K = x.shape[0]
    k = np.arange(K-1) + 1
    zk = np.zeros(K-1)
    for i in range(K-1):
        zk[i] = x[i] / (1.0 - np.sum(x[0:i]))
    return logit(zk) - np.log(1.0 / (K-k))

def simplexForward(x):
    """
    Transform from transformed (unconstrained) parameters to physical ones within a simplex. For a simplex in K dimensions,
    we only provide the K-1 dimensional array x

    Args:
        x (float): vector of transformed parameters in K-1 dimensions

    Returns:
    Float: transformed variables of size K and log Jacobian
    """
    K = x.shape[0] + 1
    k = np.arange(K - 1)+1
    zk = invLogit(x + np.log(1.0 / (K-k)))
    xk = np.zeros(K)
    for i in range(K-1):
        xk[i] = (1.0 - np.sum(xk[0:i])) * zk[i]
    xk[K-1] = 1.0 - np.sum(xk[0:K-1])
    return xk, np.log(np.sum(xk[0:K-1] * (1.0-zk[0:K-1])))


import numpy as np

# "02406795E" -> 24.11325
# "6044718N" -> 60.7453
def coord(coord_string):
    degrees = coord_string[:-6]
    minutes = coord_string[-6:-1]
    return float(degrees) + float(minutes)/1000/60

# distance between two coordinates, equirectangular approximation (for <1 km)
def dist(point1, point2):

    lat1, lon1 = point1
    lat2, lon2 = point2
    R = 6371e3
    x = (lon2-lon1) * np.cos((lat1+lat2)/2)
    y = lat2-lat1

    x = np.deg2rad(x)
    y = np.deg2rad(y)

    return R * np.hypot(x,y)

def read_cup(filename):
    turnpoint_headers = 1

    with open(filename, 'r') as f:
        skipped = 0
        names = []
        latitudes = []
        longitudes = []

        for line in f:
            if skipped < turnpoint_headers:
                skipped += 1
                continue

            columns = line.strip().split(',')

            try:
                name = columns[0]
                latitude_string = columns[3].replace('.', '') # "60.44718N" -> "6044718N" (to same format as in .igc file)
                longitude_string = columns[4].replace('.', '')
            except:
                continue # broken line

            names.append(name.replace('"', '')) # strip unnecessary quotes from .cup CSV
            latitudes.append(coord(latitude_string))
            longitudes.append(coord(longitude_string))

        return names, latitudes, longitudes

def read_igc(filename=None, content=None):

    igc_latitudes = []
    igc_longitudes = []

    if filename:
        with open(filename, 'r') as f:
            igc_content = f.readlines()

    if content:
        igc_content = content.split('\n')

    for line in igc_content:
        if len(line) and line[0] == 'B':
            latitude_string = line[7:15]
            longitude_string = line[15:24]

            igc_latitudes.append(coord(latitude_string))
            igc_longitudes.append(coord(longitude_string))

    track = np.column_stack((igc_latitudes, igc_longitudes))
    return track

def turnpoints_in_track(track, turnpoints):
    visited = []

    for tp_name, tp_lat, tp_lon in zip(*turnpoints):

        tp_coords = (tp_lat, tp_lon)

        limit = 360.0 / 40000 * 3
        lat_within = track[ np.abs(track[:,0] - tp_lat) < limit ]
        both_within = lat_within[ np.abs(lat_within[:,1] - tp_lon) < limit]

        if len(both_within):
            for fix_coords in both_within:
                distance = dist(fix_coords, tp_coords)
                if distance < 1000:
                    visited.append(tp_name)
                    break
    return visited


"""
vrp.py:  model for the vehicle routing problem using callback for adding cuts.

approach:
    - start with assignment model
    - add cuts until all components of the graph are connected

Copyright (c) by Joao Pedro PEDROSO and Mikio KUBO, 2012
"""
import math
import random
import networkx
from pyscipopt import Model, Conshdlr, quicksum, multidict, SCIP_RESULT, SCIP_PRESOLTIMING, SCIP_PROPTIMING

class VRPconshdlr(Conshdlr):

    def addCuts(self, checkonly):
        """add cuts if necessary and return whether model is feasible"""
        cutsadded = False
        edges = []
        x = self.model.data
        for (i, j) in x:
            if self.model.getVal(x[i, j]) > .5:
                if i != V[0] and j != V[0]:
                    edges.append((i, j))
        G = networkx.Graph()
        G.add_edges_from(edges)
        Components = list(networkx.connected_components(G))
        for S in Components:
            S_card = len(S)
            q_sum = sum(q[i] for i in S)
            NS = int(math.ceil(float(q_sum) / Q))
            S_edges = [(i, j) for i in S for j in S if i < j and (i, j) in edges]
            if S_card >= 3 and (len(S_edges) >= S_card or NS > 1):
                cutsadded = True
                if checkonly:
                    break
                else:
                    self.model.addCons(quicksum(x[i, j] for i in S for j in S if j > i) <= S_card - NS)
                    print("adding cut for", S_edges)

        return cutsadded

    def conscheck(self, constraints, solution, checkintegrality, checklprows, printreason):
        if self.addCuts(checkonly = True):
            return {"result": SCIP_RESULT.INFEASIBLE}
        else:
            return {"result": SCIP_RESULT.FEASIBLE}

    def consenfolp(self, constraints, nusefulconss, solinfeasible):
        if self.addCuts(checkonly = False):
            return {"result": SCIP_RESULT.CONSADDED}
        else:
            return {"result": SCIP_RESULT.FEASIBLE}

    def conslock(self, constraint, nlockspos, nlocksneg):
        pass

def vrp(V, c, m, q, Q):
    """solve_vrp -- solve the vehicle routing problem.
       - start with assignment model (depot has a special status)
       - add cuts until all components of the graph are connected
    Parameters:
        - V: set/list of nodes in the graph
        - c[i,j]: cost for traversing edge (i,j)
        - m: number of vehicles available
        - q[i]: demand for customer i
        - Q: vehicle capacity
    Returns the optimum objective value and the list of edges used.
    """

    model = Model("vrp")
    vrp_conshdlr = VRPconshdlr()

    x = {}
    for i in V:
        for j in V:
            if j > i and i == V[0]:       # depot
                x[i,j] = model.addVar(ub=2, vtype="I", name="x(%s,%s)"%(i,j))
            elif j > i:
                x[i,j] = model.addVar(ub=1, vtype="I", name="x(%s,%s)"%(i,j))

    model.addCons(quicksum(x[V[0],j] for j in V[1:]) == 2*m, "DegreeDepot")
    for i in V[1:]:
        model.addCons(quicksum(x[j,i] for j in V if j < i) +
                        quicksum(x[i,j] for j in V if j > i) == 2, "Degree(%s)"%i)

    model.setObjective(quicksum(c[i,j]*x[i,j] for i in V for j in V if j>i), "minimize")
    model.data = x

    return model, vrp_conshdlr

def distance(x1,y1,x2,y2):
    """distance: euclidean distance between (x1,y1) and (x2,y2)"""
    return math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)

def make_data(n):
    """make_data: compute matrix distance based on euclidean distance"""
    V = range(1,n+1)
    x = dict([(i, random.random()) for i in V])
    y = dict([(i, random.random()) for i in V])
    c, q = {}, {}
    Q = 100
    for i in V:
        q[i] = random.randint(10, 20)
        for j in V:
            if j > i:
                c[i, j] = distance(x[i], y[i], x[j], y[j])
    return V, c, q, Q

if __name__ == "__main__":
    n = 19
    m = 3
    seed = 1
    random.seed(seed)
    V,c,q,Q = make_data(n)
    model, conshdlr = vrp(V, c, m, q, Q)

    model.setBoolParam("misc/allowstrongdualreds", 0)
    model.includeConshdlr(conshdlr, "VRP", "VRP constraint handler",
                          sepapriority = 0, enfopriority = 1, chckpriority = 1, sepafreq = -1, propfreq = -1,
                          eagerfreq = -1, maxprerounds = 0, delaysepa = False, delayprop = False, needscons = False,
                          presoltiming = SCIP_PRESOLTIMING.FAST, proptiming = SCIP_PROPTIMING.BEFORELP)
    model.optimize()
    x = model.data

    edges = []
    for (i, j) in x:
        if model.getVal(x[i, j]) > .5:
            if i != V[0] and j != V[0]:
                edges.append((i, j))

    print("Optimal solution:", model.getObjVal())
    print("Edges in the solution:")
    print(sorted(edges))


#!/usr/bin/env python
# -*- coding: utf-8 -*-

# 3rd party imports
import numpy as np

__author__ = "Louis Richard"
__email__ = "louisr@irfu.se"
__copyright__ = "Copyright 2020-2021"
__license__ = "MIT"
__version__ = "2.3.7"
__status__ = "Prototype"

def cart2sph(x, y, z):
    r"""Cartesian to spherical coordinate transform.

    .. math::

        \alpha = \arctan \left( \frac{y}{x} \right) \\
        \beta = \arccos \left( \frac{z}{r} \right) \\
        r = \sqrt{x^2 + y^2 + z^2}

    with :math:`\alpha \in [0, 2\pi), \beta \in [0, \pi], r \geq 0`

    Parameters
    ----------
    x : float or array_like
        x-component of Cartesian coordinates
    y : float or array_like
        y-component of Cartesian coordinates
    z : float or array_like
        z-component of Cartesian coordinates

    Returns
    -------
    alpha : float or array_like
        Azimuth angle in radians
    beta : float or array_like
        Elevation angle in radians (with 0 denoting North pole)
    r : float or array_like
        Radius
    """

    # Radius
    r = np.sqrt(x**2 + y**2 + z**2)

    # Azimuthal angle
    alpha = np.arctan2(y, x)

    # Elevation angle
    beta = np.arccos(z / r)

    return alpha, beta, r


#!/usr/bin/python

from sympy import *
import matplotlib.pyplot as plt

y = symbols('y', cls=Function)
x = symbols('x')

res = dsolve(y(x).diff(x, 2) + y(x).diff() + y(x), ics = {y(0):1, y(x).diff(x).subs(x, 0):0})
#plt.plot(res.rhs, xlim=(-5, 10), ylim=(-7, 3))
plot(res.rhs, xlim=(-5, 10), ylim=(-7, 3))

#deq = Eq( 10*y(t).diff(t,2) + 10*y(t).diff(t) + 90*y(t), 0 )
#sol = dsolve( deq, ics= { y(0):0.16, y(t).diff(t).subs(t,0):0 } )
#plot( sol.rhs, xlim=(0,8), ylim=(-0.5,0.5) )

res = dsolve(10 * y(x).diff(x, 2) + 10 * y(x).diff() + 90 * y(x), ics = {y(0):0.16, y(x).diff(x).subs(x, 0):0})
#plt.plot(res.rhs, xlim=(0, 8), ylim=(-0.5, 0.5))
plot(res.rhs, xlim=(0, 8), ylim=(-0.5, 0.5))

print(res.rhs)


import numpy as np
import sys
dp = np.zeros((1000, 1000))
def sum(a, i, j):
    ans = 0
    for m in range(i, j + 1):
        ans = (ans + a[m]) % 100

    return ans
def solve(a, i, j):
    if (i == j):
        return 0
    if (dp[i][j] != -1):
        return dp[i][j]
    dp[i][j] = sys.maxsize
    for k in range(i, j):
        dp[i][j] = min(dp[i][j], (solve(a, i, k) + solve(a, k + 1, j)
                                  + (sum(a, i, k) * sum(a, k + 1, j))))
    return dp[i][j]
def initialize(n):
    for i in range(n + 1):
        for j in range(n + 1):
            dp[i][j] = -1
if __name__ == "__main__":
    a = [40, 60, 20]
    n = len(a)
    initialize(n)
    print(int(solve(a, 0, n - 1)))


import matplotlib.pyplot as plt
import numpy as np
import scipy.optimize as opt

from plotData import plot_data
from costFunction import cost_Function_Reg
import plotDecisionBoundary as pdb
import predict as predict
import mapFeature as mf

# ============ 1.读取数据
# The first two columns contain the exam scores and the third column contains the label.
data = np.loadtxt('ex2data2.txt', delimiter=',')
X = data[:, 0:2]
y = data[:, 2]

plot_data(X, y)

plt.xlabel('Microchip Test 1')
plt.ylabel('Microchip Test 2')
plt.legend(['y = 1', 'y = 0'])
# plt.show()

# 2.投影到高维，计算带正则项的代价和梯度

X = mf.map_feature(X[:, 0], X[:, 1],6)
print(X.shape)
# Initialize fitting parameters
initial_theta = np.zeros(X.shape[1])

# Set regularization parameter lambda to 1
lmd = 1

# Compute and display initial cost and gradient for regularized logistic regression
cost, grad = cost_Function_Reg (X, y,initial_theta, lmd)

np.set_printoptions(formatter={'float': '{: 0.4f}\n'.format})
print('Cost at initial theta (zeros): {: 0.4f}'.format(cost))
print('Expected cost (approx): 0.693')
print('Gradient at initial theta (zeros) - first five values only: \n{}'.format(grad[0:5]))
print('Expected gradients (approx) - first five values only: \n 0.0085\n 0.0188\n 0.0001\n 0.0503\n 0.0115')

# Compute and display cost and gradient with non-zero theta
test_theta = np.ones(X.shape[1])

cost, grad = cost_Function_Reg( X, y,test_theta, lmd)

print('Cost at test theta: {}'.format(cost))
print('Expected cost (approx): 2.13')
print('Gradient at test theta - first five values only: \n{}'.format(grad[0:5]))
print('Expected gradients (approx) - first five values only: \n 0.3460\n 0.0851\n 0.1185\n 0.1506\n 0.0159')

# ===================== Part 2: Regularization and Accuracies =====================
# Optional Exercise:
# In this part, you will get to try different values of lambda and
# see how regularization affects the decision boundary
#
# Try the following values of lambda (0, 1, 10, 100).
#
# How does the decision boundary change when you vary lambda? How does
# the training set accuracy vary?
#

# Initializa fitting parameters
initial_theta = np.zeros(X.shape[1])

# Set regularization parameter lambda to 1 (you should vary this)
lmd = 0.1

# Optimize
def cost_func(t):
    return cost_Function_Reg(X, y,t, lmd)[0]

def grad_func(t):
    return cost_Function_Reg(X, y,t, lmd)[1]

theta, cost, *unused = opt.fmin_bfgs(f=cost_func, fprime=grad_func, x0=initial_theta, maxiter=400, full_output=True, disp=False)

# Plot boundary
print('Plotting decision boundary ...')
pdb.plot_Decision_Boundary( X, y,theta)
plt.title('lambda = {}'.format(lmd))

plt.xlabel('Microchip Test 1')
plt.ylabel('Microchip Test 2')
plt.show()
# Compute accuracy on our training set
p = predict.predict( X,theta)

print('Train Accuracy: {:0.4f}'.format(np.mean(y == p) * 100))
print('Expected accuracy (with lambda = 1): 83.1 (approx)')


#!/usr/bin/env python

"""
Diffie-Hellman Key Demo in Python
author: Jerzy Wawro, Galicea
License:  GNU General Public License <http://www.gnu.org/licenses/>.

pip install primesieve
pip install numpy
"""

import numpy as np
import primesieve
from random import randint

def generate_primes(n1,n2):
	return primesieve.primes(n1, n2)

def int_from_bytes(n):
	try:  # python 3
		int.from_bytes(n, byteorder="big")
	except:
		import struct
		format = 'Q' * (len(n) / 8)
		return struct.unpack(format, n)[0]

class DiffieHellman(object):
	"""
	Demo  implementation of the Diffie-Hellman protocol.
	"""

	def __init__(self, generator=2, group=None, keyBytes=72):
		"""
		Generate group and keys.
		"""
		if group:
			self.group=group
		else:
			self.group = self.getPrime()
		self.generator = generator
		self.privateKey = self.genPrivateKey(keyBytes)
		self.publicKey = self.genPublicKey()

	def genPrivateKey(self, size):
		"""
		Private Key = random with the specified number of bits (size*8)
		http://stefanocappellini.com/generate-pseudorandom-bytes-with-python/
		"""
		return np.random.bytes(size)

	def genPublicKey(self):
		"""
		key = generator ** privateKey % group.
		"""
		return pow(self.generator, int_from_bytes(self.privateKey), self.group)

	def sharedSecret(self, otherKey):
		"""
		sharedSecret = otherKey ** privateKey % group
		"""
		return pow(otherKey, int_from_bytes(self.privateKey), self.group)

	def getPrime(self, min=1000):
		"""
		Eratosthenes sieve http://code.activestate.com/recipes/117119/
		https://github.com/hickford/primesieve-python
		"""
		prime_list = generate_primes(min, min+100)
		while (not prime_list):
		  prime_list = generate_primes(min, min+100)
		n=randint(0, len(prime_list)-1)
		return prime_list[n]

if __name__=="__main__":
	first = DiffieHellman()
	# generator and group as public
	print "generator=%s, group=%s" % (first.generator, first.group)
	two = DiffieHellman(generator=first.generator, group=first.group)

	print 'shared secret [1] = %s' % first.sharedSecret(two.publicKey)
	print 'shared secret [2] = %s' % two.sharedSecret(first.publicKey)


#numpy imports
import numpy

from numpy import zeros, diff, sqrt, eye
from numpy import pi, cos as _cos, sin as _sin,\
                  arccos as _acos, arcsin as _asin,\
                  arctan2 as _atan2, arctan as _atan

from numpy import sign
from numpy import array as mat, dot, cross, inf
from numpy import linalg
from numpy.linalg import norm

from quaternion import *

#random import
from random import random as rand
#----------------------------------------------------------------------------------------------------------#
def deg_to_rad(x):
    return x * pi / 180.0
#----------------------------------------#
def rad_to_deg(x):
    return x / pi * 180.0
#----------------------------------------#
def atan(x, unit='rad'):
    if unit == 'rad':
        return _atan(x)
    elif unit == 'deg':
        return rad_to_deg(_atan(x))
#----------------------------------------#
def atan2(y, x, unit='rad'):
    if unit == 'rad':
        return _atan2(y,x)
    elif unit == 'deg':
        return rad_to_deg(_atan2(y,x))
#----------------------------------------#
def cos(x, unit='rad'):
    if unit == 'rad':
        return _cos(x)
    elif unit == 'deg':
        return _cos( deg_to_rad(x))
#----------------------------------------#
def sin(x, unit='rad'):
    if unit == 'rad':
        return _sin(x)
    elif unit == 'deg':
        return _sin( deg_to_rad(x))
#----------------------------------------#
def acos(x, unit='rad'):
    if unit == 'rad':
        return _acos(x)
    elif unit == 'deg':
        return rad_to_deg( _acos(x) )
#----------------------------------------#
def asin(x, unit='rad'):
    if unit == 'rad':
        return _asin(x)
    elif unit == 'deg':
        return rad_to_deg( _asin(x) )
#----------------------------------------------------------------------------------------------------------#
def matmul(*matrix_factors):
    '''
        Takes a list of matrices as arguments and perform
        a series of matrix multiplications from left to right
        in the order given.

        The parameters may contain vectors and scalars as well as long as
        the matrix-vector dimensions are the same.

        Note:
        The sequence can not start with a scalar, it must start
        with either a vector or a matrix of type numpy.ndarray.
    '''
    return reduce(dot, matrix_factors, 1)
#----------------------------------------------------------------------------------------------------------#
def matmul_series(*matrix_factors):
    '''
        Takes a list of matrices as arguments and perform
        a series of matrix multiplications from left to right
        in the order given, but contrary to matmul
        it returns each partial result in a list.

        The parameters may contain vectors and scalars as well as long as
        the matrix-vector dimensions are the same.

        Note:
        The sequence can not start with a scalar, it must start
        with either a vector or a matrix of type numpy.ndarray.
    '''
    res = []
    res.append(matrix_factors[0])
    for i in xrange(1,len(matrix_factors)):
        res.append(reduce(dot, matrix_factors[:i+1], 1))
    return res
#----------------------------------------------------------------------------------------------------------#
def homogenous_matrix(*args):
    """
    Creates a homogenous matrix ( 4x4 matrix of type [[R,t],[0,1]] ),
    allows input on the forms:

    -1:   No input
    0:    R                                       (1x)
    1:    (rot, tilt, skew, x,y,z)                (3x, 3x)
    2:    (rot, tilt, skew, t)                    (3x, 1x)
    3:    (angles, x,y,z)                         (1x, 3x)
    3.1:  (R, x,y,z)                              (1x, 3x)
    4:    (R, t), where R is list or numpy.array  (1x, 1x)
    """
    l_args = len(args)
    valid_types  = [tuple, list, numpy.ndarray]

    ## TODO: separate into subcase-functions (!!!)
    if  l_args == 0: #case -1: no input
        R = eye(4)
        t = [0,0,0]
    elif  l_args == 1: #case 0
        if type(args[0]) in [list, tuple]:
            R = mat(args[0])
        else:
            R = args[0]
        m,n = R.shape
        if m == n:
            R = expand_matrix(R)
            return R
        else:
            return R
    elif  l_args == 2: #case 4
        R,t = args
    elif l_args == 4:
        if type(args[0]) in valid_types:
            if type(args[0][0] ) in valid_types: #case 3.1
                R,t = args[0], args[1:]
            else:
                (rot, tilt, skew), t = args[0], args[1:] #case 3
        else: #case 2
            (rot, tilt, skew), t = args[:3], args[3:][0]
    elif l_args == 6: #case 1
        (rot, tilt, skew), t = args[:3], args[3:]

    if not type(t) in valid_types:
        raise ArithmeticError('translation part can not be a scalar.')
    elif len(t) < 3:
        raise ArithmeticError('translation part must be of dimension 3.')

    if not 'R' in locals():
        R = rotation_matrix_rot_tilt_skew(rot, tilt, skew)

    T = zeros((4,4))
    m,n = R.shape

    T[:m, :n] = R
    T[:3, 3] = t[:3]
    T[3, :] = [0, 0, 0, 1]
    return T
#----------------------------------------------------------------------------------------------------------#
def homogenous_matrices(frames):
    """
    Creates a matrix of homogenous matrices( 4x4 matrix of type [[R,t],[0,1]] ),
    each frame in the input 'frames' can have one of following descriptions:
    0:    R                                       (1x)
    1:    (rot, tilt, skew, x,y,z)                (3x, 3x)
    2:    (rot, tilt, skew, t)                    (3x, 1x)
    3:    (angles, x,y,z)                         (1x, 3x)
    3.1:  (R, x,y,z)                              (1x, 3x)
    4:    (R, t), where R is list or numpy.array  (1x, 1x)
    """
    return mat( map(lambda x: homogenous_matrix(*x), frames) )
#----------------------------------------------------------------------------------------------------------#
def homogenous_translation_x( tx ):
    return mat([[1,        0,      0,     tx],
                [0,        1,      0,      0],
                [0,        0,      1,      0],
                [0,        0,      0,      1]])
#----------------------------------------------------------------------------------------------------------#
def homogenous_translation_z( tz ):
    return mat([[1,     0,     0,      0],
                [0,     1,     0,      0],
                [0,     0,     1,      tz],
                [0,     0,     0,      1]])
#----------------------------------------------------------------------------------------------------------#
def homogenous_rotation_x( ang ):
    #convert to radians
    c = cos(ang * pi / 180)
    s = sin(ang * pi / 180)
    return mat([[1,     0,      0,      0],
                [0,     c,     -s,     0],
                [0,     s,     c,      0],
                [0,     0,     0,      1]])
#----------------------------------------------------------------------------------------------------------#
def homogenous_rotation_z( ang ):
    #convert to radians
    c = cos(ang * pi / 180)
    s = sin(ang * pi / 180)
    return mat([[c,     -s,    0,     0],
                [s,     c,     0,     0],
                [0,     0,     1,     0],
                [0,     0,     0,     1]])
#----------------------------------------#
def expand_matrix(matrix, k=1,l=1):
    if k < 0 or l < 0:
        raise ArithmeticError('Only positive values (or zero) allowed for changing the dimension of the matrix.')
    m,n = matrix.shape
    res = numpy.eye(m+k,n+l)
    res[0:m, 0:n] = matrix
    return res
#----------------------------------------#
def rotation_matrix_z(angle):
    '''
        creates a rotation Z-mapping from subspace to worldspace
        using euler angles in degrees
    '''
    angle = deg_to_rad(angle)
    return mat([[ cos(angle), -sin(angle), 0 ],
                [ sin(angle),  cos(angle), 0 ],
                [  0   ,    0  , 1 ]])
#----------------------------------------#
def rotation_matrix_x(angle):
    '''
        creates a rotation X-mapping from subspace to worldspace
        using euler angles in degrees
    '''
    angle = deg_to_rad(angle)
    return mat([[ 1,     0     ,           0 ],
                  [ 0, cos(angle), -sin(angle) ],
                  [ 0, sin(angle),  cos(angle)]])
#----------------------------------------#
def rotation_matrix_y(angle):
    '''
        creates a rotation Y-mapping from subspace to worldspace
        using euler angles in degrees
    '''
    angle = deg_to_rad(angle)
    return mat([[ cos(angle),     0     ,   sin(angle) ],
                  [ 0,              1     ,   0 ],
                  [ -sin(angle),    0     ,   cos(angle)]])
#----------------------------------------#
def rotation_matrix_rot_tilt_skew(rot, tilt, skew):
    '''
        creates a rotation ZXZ-mapping from subspace to worldspace
        using euler angles in degrees (extrinsic mapping)

        (!) Note - rot is rotated counter-clockwise compared to ordinary euler z-rotation.
    '''
    return matmul(rotation_matrix_z(-rot), rotation_matrix_x(tilt), rotation_matrix_z(skew))
#----------------------------------------#
def rotation_matrix_x_y_z(rx,ry,rz):
    '''
        creates a rotation XYZ-mapping from subspace to worldspace
        using euler angles in degrees (extrinsic mapping)
    '''
    return matmul(rotation_matrix_x(rx), rotation_matrix_y(ry), rotation_matrix_z(rz))
#----------------------------------------#
def rotation_matrix_skew_tilt_rot(rot, tilt, skew):
    '''
        creates a rotation ZXZ-mapping from subspace to worldspace
        using euler angles in degrees (intrinsic mapping)

        (!) Note - rot is rotated counter-clockwise compared to ordinary euler z-rotation.
    '''
    return matmul(rotation_matrix_z(skew), rotation_matrix_x(tilt), rotation_matrix_z(-rot))
#----------------------------------------#
def rotation_matrices(rts):
    '''
        creates a rotation ZXZ-mapping from subspace to worldspace
        using euler angles in degrees (intrinsic mapping)

        (!) Note - rot is rotated counter-clockwise compared to ordinary euler z-rotation.
    '''
    M = lambda x: homogenous_matrix(rotation_matrix_rot_tilt_skew(*x))
    return nmap(M, rts)
#----------------------------------------#
def coordinate_system_from_two_directions(dirz, diry):
    '''
        Generates an orthogonal coordinate system from two vectors in world space,
        the vectors themselves need not be orthogonal.

        Alternative interpretation:
        creates a (general) rotation mapping from subspace to worldspace
        using vector directions in world-space
    '''
    dirz = dirz / norm(dirz)
    diry = diry / norm(diry)
    diry = gram_schmith_step(diry, dirz)
    dirx = cross(diry, dirz)
    return mat([dirx, diry, dirz]).T
#----------------------------------------#
def get_normalized_vector(*components):
    return mat(components) / norm(components)
#----------------------------------------#
def orthogonal_projection_vectors(a, b):
    '''
        Orthogonal projection of vector a onto vector b
    '''
    c = b / norm(b)
    return a.dot(c) * c
#----------------------------------------#
def gram_schmith_step(a, b):
    '''
        Calculates with the help of vector A the orthogonal component relative to the
        parallell direction vector B
    '''
    return a - orthogonal_projection_vectors(a, b)
#----------------------------------------#
# MISC
#----------------------------------------#
def rand_range(low, high):
        if low > high:
                low, high = high, low
        return low + rand()*(high-low)
#----------------------------------------#
def nmap(func, *seq):
    return mat( map(func, *seq) )

def nzip(*seq):
    return mat( zip(*seq) )

def lop(func, a,b):
    return nmap( lambda x: reduce(func, x), zip(a,b))
#----------------------------------------#
def rot_tilt_skew(R):
    zs_vector = R[2,:] #matmul(R.T, numpy.array([0, 0,  1]) )
    zp_vector = -R[:,2] #matmul(R , numpy.array([0, 0, -1]) )

    #angle between normal and zp_vector direction
    #rot  = atan2( zp_vector[0], zp_vector[1] ) * 180 / pi
    rot  = atan2( zp_vector[0], zp_vector[1]) * 180 / pi
    tilt = -atan( norm(zp_vector[0:2]) / zp_vector[2] ) * 180 / pi
    skew = -atan2( zs_vector[0], zs_vector[1] ) * 180 / pi

    if tilt < 0.01:
        x_vector = R[:,0]
        z_vector = R[:,2]
        rot = atan2(x_vector[0], x_vector[1]) * 180 / pi
    return (rot, tilt, skew)


"""
Module for doing the training
"""

from __future__ import division
import numpy as np
import math
import plot as plot
from numpy import matrix
from numpy import linalg

def trainUnweighted (train_X, train_Y):
    """
    Trains the unweighted linear regression model
    Theta = (X.T * X)-1 * X.T * Y
    """

    X_matrix = np.matrix(train_X).T
    Y_matrix = np.matrix(train_Y).T

    X_ones = np.ones((X_matrix.shape[0], 1))
    X_matrix = np.hstack((X_ones, X_matrix))

    return np.linalg.inv(X_matrix.T * X_matrix) * X_matrix.T * Y_matrix

def displayWeighted (train_X, train_Y, tau=0.8):
    """
    For each point trains the weighted model
    and plots a point
    """
    train_X.sort()
    h_theta = []
    for x_i in train_X:
        prediction = float(trainWeighted(train_X, train_Y, x_i, tau))
        h_theta.append(prediction)

    plot.linePlot(train_X, train_Y, h_theta, fileName=("Q2/plots/weighted-" + str(tau).replace('.','_')), title=("Weighted - Tau=" + str(tau)))

def trainWeighted (train_X, train_Y, x, tau):
    """
    Trains the weighted linear regression model, for a single query point
    Theta = (X.T * W * X)-1 * X.T * W * Y
    """

    X_matrix = np.matrix(train_X).T
    Y_matrix = np.matrix(train_Y).T
    x_matrix = np.matrix([1,x]).T

    X_ones = np.ones((X_matrix.shape[0], 1))
    X_matrix = np.hstack((X_ones, X_matrix))

    W_matrix = generateW (train_X, x, tau)

    # print (X_matrix.shape, W_matrix.shape, Y_matrix.shape)
    Theta = np.linalg.inv(X_matrix.T * W_matrix * X_matrix) * X_matrix.T * W_matrix * Y_matrix
    prediction = Theta.T * x_matrix
    return prediction

def generateW (train_X, x, tau):
    """
    Generate the diagonal weight matrix W for the training set train_X and query x, with parameter tau
    """

    weights = []
    for x_i in train_X:
        expo =( (x - x_i)**2) / (-2.0 * tau**2)
        weights.append(math.exp(expo))
    return np.diag(weights)


def haversine_np(longitude1, latitude1, longitude2, latitude2):
    """
    Return the great circle distance between two points on the earth (specified in decimal degrees). All args must be of equal length. Reference from https://stackoverflow.com/a/4913653, https://stackoverflow.com/a/29546836

    Parameters
    ----------
    longitude1: float
        Base longitude
    latitude1: float
        Base latitude
    longitude2: float
        Longitude to compare
    latitude2: float
        Latitude to compare

    Returns
    -------
    km : float
    """
    import numpy as np

    lon1, lat1, lon2, lat2 = map(
        np.radians, [longitude1, latitude1, longitude2, latitude2]
    )

    dlon = lon2 - lon1
    dlat = lat2 - lat1

    a = np.sin(dlat / 2.0) ** 2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon / 2.0) ** 2

    c = 2 * np.arcsin(np.sqrt(a))
    km = 6371 * c
    return km

if __name__ == "__main__":
    import doctest

    doctest.testmod()


import numpy as np
import matplotlib.pyplot as plt

def sigmoid(x, w, b):
    return 1/(1 + np.exp(-x*w+b))

x = np.arange(-5.0, 5.0, 0.1)
y1 = sigmoid(x, 0.5, 0)
y2 = sigmoid(x, 1, 0)
y3 = sigmoid(x, 2, 0)

plt.plot(x, y1, "r", linestyle='--')
plt.plot(x, y2, 'g')
plt.plot(x, y3, 'b', linestyle='--')
plt.plot([0, 0], [1.0, 0.0], ":")
plt.title("sigmoid function")
plt.show()


import numpy as np

# probabilities for 3 samples
softmax_outputs = np.array([[0.7, 0.1, 0.2],
                            [0.1, 0.5, 0.4],
                            [0.02, 0.9, 0.08]])

# indices of the softmax output distribution
#class_targets = np.array([0, 1, 1])
class_targets = np.array([[1, 0, 0], [0, 1, 0], [0, 1, 0]])

# probabilities for target values -
# only if categorical labels
if len(class_targets.shape) == 1:
    correct_confidences = softmax_outputs[
        range(len(softmax_outputs)),
        class_targets]

# mask values - only for one-hot encoded labels
elif len(class_targets.shape) == 2:
    correct_confidences = np.sum(
        softmax_outputs * class_targets, axis=1)

print(correct_confidences)

# losses
neg_log = -np.log(correct_confidences)

average_loss = np.mean(neg_log)
print(average_loss)


# -*- coding: utf-8 -*-
import sympy as sp

x = sp.symbols('x')

print("sin'(x) = {0}".format(sp.diff(sp.sin(x), x)))
print("sin'(x^2) = {0}".format(sp.diff(sp.sin(x ** 2), x)))
print("log'(x) = {0}".format(sp.diff(sp.log(x), x)))

sigmod = (1 / (1 + sp.E ** -x))

sigmod_diff = sp.diff(sigmod, x)

print("sigmod'(x) = {0}".format(sigmod_diff))

assert (sigmod * (1 - sigmod)).simplify() == sigmod_diff.simplify()

theta0, theta1, theta2 = sp.symbols('theta0 theta1 theta2')
x1, x2, y = sp.symbols('x1 x2 y')

sse = (theta0 + theta1 * x1 + theta2 * x2 - y) ** 2 / 2

print('partial(θ0) = {0}'.format(sp.diff(sse, theta0)))
print('partial(θ1) = {0}'.format(sp.diff(sse, theta1)))
print('partial(θ2) = {0}'.format(sp.diff(sse, theta2)))

p, n, k = sp.symbols('p n k')
likelihood = p ** k * (1 - p) ** (n - k)
print(sp.diff(likelihood, p).simplify())


import numpy as np

def feasible_starting_point(y, C):
    """
    Calculate feasible starting point for
    Barrier method specific for SVM dual problem.

    Parameters:
    -----------
    y : `numpy.ndarray`
        (nData,) The labels.
    C : `float`
        Regularization parameter. The strength of the regularization is
        inversely proportional to C. Must be strictly positive.
        The penalty is a squared l2 penalty.

    Returns:
    --------
    x0 : numpy.ndarray`
        (nData,1) The starting point for the feasible Newton method.
    """
    nData = len(y)
    nPos = np.sum(y==1)
    pos_neg_frac = nPos / (nData-nPos)
    x0 = np.zeros((nData,1))
    for i in range(nData):
        if y[i]==1:
            x0[i] = C*(1-nPos/nData)
        else:
            x0[i] = C*pos_neg_frac*(1-nPos/nData)
    return x0


import numpy as np
# 行向量
v = np.array([[0, 1, 2]])
print(f"行向量v1=\n{v}",v.shape)
# 列向量
v = np.array([[0], [1], [2]])
print(f"列向量v2=\n{v}",v.shape)

v=np.array([1,2,3])
print(f"真实向量v={v}",v.shape)
print(f"真实向量v={v.T}",v.shape)

# 创建矩阵
matrix = np.mat([[1, 2], [3, 4]])

print(f"矩阵matrix=\n{matrix}")
print(f"矩阵matrix的转置=\n{matrix.T}")

f = np.vectorize(lambda i: i*i)

print(f"矩阵函数:\n{f(matrix)}")
# 矩阵的秩

print(f"矩阵的秩\n{np.linalg.matrix_rank(matrix)}")

# 行列式
print(f"矩阵的行列式\n{np.linalg.det(matrix)}")

# 迹
print(f"矩阵的迹\n{matrix.trace()}")

# 特征向量和特征值
eigenvalues, eigenvectors = np.linalg.eig(matrix)
print(f"特征值和特征向量\n {eigenvalues}\n{eigenvectors}")

# 矩阵的逆
m = np.mat([[1, 2], [3, 4]])
result = np.linalg.inv(m)
print(f"矩阵的逆矩阵\n {result}")

# 矩阵的左乘
m1 = np.mat([[1, 2], [3, 4]])
m2 = np.mat([[0, 1], [1, 2]])
print(f"np.dot(m1, m2)=\n{np.dot(m1, m2)}")
print(f"矩阵的左乘\n {m1 @ m2}\n{m2 @ m1}")
print(f"矩阵的点乘\n {m1 * m2}\n {m2*m1}")

# 向量的点积
v1 = np.array([1, 0])
v2 = np.array([0, 1])
d1 = np.dot(v1, v2)
d2 = np.vdot(v1, v2)
d3 = np.inner(v1, v2)
d4 = np.outer(v1, v2)
d5 = np.cross(v1, v2)
print(f"d1= {d1}")
print(f"d2= {d2}")
print(f"d3={d3}")
print(f"d4=\n{d4}")
print(f"d5\5=\n{d5}")
print("---------------------")
# TT^-1
m = np.mat([[1, 2], [2, 3]])
m_inv = np.linalg.inv(m)
print(m @ m_inv)
print(m_inv)

# 矩阵x向量
print("----------")
m = np.array([[1, 2, 3], [3, 4, 5]])
print(m, m.shape, type(m))
# v = np.array([[1], [2], [3]])
v = np.array([1, 2, 3]).T
print(v, v.shape, type(v))
result = np.dot(m, v)
print(result, result.shape, type(result))

# 矩阵x矩阵
m1 = np.mat([[1, 2], [3, 4]])
m2 = np.mat([[2, 2], [1, 1]])
result = np.dot(m1, m2)
# result = m1 @ m2 等价
# result = m1 * m2 等价
print(result)
# 矩阵乘向量
v=np.array([1,2,3]).T
print(v,v.shape)
m=np.mat([[1,2,3],[1,0,1]])

result=np.dot(m,v)
print(f"矩阵x 向量={result} {type(result)} {result.shape}")
print("-------------")
# 向量的内积
v1 = np.array([0,2,1])
v2 = np.array([2,0,1])
inner_product = v1 @ v2
inner_product = np.inner(v1, v2)
print(inner_product)

# 向量的外积/叉积
cross_product=np.cross(v1,v2)
print(cross_product)

# 方程求解
m=np.array([[1,0],[0,1]])
y=np.array([[2,0],[0,2]])
result=np.linalg.solve(m,y)
print(result)


# Linear Regression Gradient Descent with Single Feature

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

house_sales = pd.read_csv('house_sales.csv')

sqft_living = house_sales[['sqft_living']].values.flatten()[:, np.newaxis]
sqft_lot = house_sales[['sqft_lot']].values.flatten()[:, np.newaxis]
bedrooms = house_sales[['bedrooms']].values.flatten()[:, np.newaxis]
bathrooms = house_sales[['bathrooms']].values.flatten()[:, np.newaxis]

data_m = np.hstack((sqft_living, sqft_lot, bedrooms, bathrooms))
stdm = np.std(data_m, axis=0)
mum = np.mean(data_m, axis=0)
data_m = (data_m - mum) / stdm
ones = np.ones((len(data_m), 1))
data_m = np.hstack((ones, data_m))
print('data_m', data_m)

# setup features
data_x = house_sales[['sqft_living']].values.flatten()[:, np.newaxis]
stdx = np.std(data_x, axis=0)
mux = np.std(data_x, axis=0)
data_x = (data_x - mux) / stdx
data_x = np.hstack((np.ones_like(data_x), data_x))

print('data_x', data_x)

# setup targets
data_y = house_sales[['price']].values.flatten()[:, np.newaxis]

# separate into training and testin datasets
order = np.random.permutation(len(data_x))
portion = 10000
test_x = data_x[order[:portion]]
test_y = data_y[order[:portion]]
train_x = data_x[order[portion:]]
train_y = data_y[order[portion:]]

def get_gradient(w, x, y):
    # matrix multiply x by weights
    y_estimate = x.dot(w).flatten()

    # calculate error between actual values and estimates
    error = (y.flatten() - y_estimate)

    mse = (1 / len(x)) * np.sum(np.power(error, 2))

    # calculate the gradient
    # error.dot(x) multiplies the error for each observation
    # by the value of the feature use to predict
    gradient = -(1 / len(x)) * error.dot(x)

    return gradient, mse

# variables for use in gradient descent algorithm
w = [0, 0]
alpha = 0.5
tolerance = 1e-5

# Perform Gradient Descent
iterations = 1
while True:
    gradient, error = get_gradient(w, train_x, train_y)
    new_w = w - alpha * gradient

    # Stopping Condition
    if np.sum(abs(new_w - w)) < tolerance:
        print('converged')
        break

    # Print error every 50 iterations
    if iterations % 100 == 0:
        print("Iteration:", iterations)
        print("Error:", np.sum(error))

    iterations += 1
    w = new_w

print('w =', w)
print('Test Cost = ', get_gradient(w, test_x, test_y)[1])
print('xdotw', data_x.dot(w))
print('x', data_x[:, 1])

plt.plot(data_x[:,1], data_x.dot(w), c='g', label='Model')
plt.scatter(train_x[:,1], train_y, c='b', label='Train Set')
plt.scatter(test_x[:,1], test_y, c='r', label='Test Set')
plt.grid()
plt.legend(loc='best')
plt.xlabel('X')
plt.ylabel('Y')
plt.show()


import numpy

# Earth equatorial radius
Re = 6378137.0

# flattening parameter
f  = 1.0 / 298.2572235630

# polar radius
Rp = Re * (1.0 - f)

# eccentricity
e  = (Re**2 - Rp**2)/(Re**2)

def geod2cart(rlat, rlon, height):
    """
    Geodetic to Cartesian coordinate conversion

    Call
        cart = geod2cart(rlat, rlon, height)

    Input
        rlat -- NumPy float array of Geodetic latitudes
        rlon -- NumPy float array of Geodetic longitudes
        height -- NumPy float array of heights (m)

    Output
        cart -- tuple, x, y, and z coordinates in meters
    """

    flatfn = (2.0 - f) * f
    funsq = (1.0 - f)**2
    # rlat = numpy.deg2rad(lat)
    # rlon = numpy.deg2rad(lon)

    gd = Re/numpy.sqrt(1.0 - flatfn*numpy.sin(rlat)**2)

    cart =  numpy.array([numpy.cos(rlat)*numpy.cos(rlon)*(gd + height),
            numpy.cos(rlat)*numpy.sin(rlon)*(gd + height),
            numpy.sin(rlat)*(gd*funsq + height)])
    return cart

def Reff(lat):
    """
    Calculate effective radius given a latitude
    """

    a_earth = 6378137.0 #     ! semi - major    axis(m)
    flatt = 0.003352811 #     ! flattening
    gm_ratio = 0.003449787 #  ! gravitational    ratio

    sinlat2 = numpy.sin(numpy.deg2rad(lat))**2
    return a_earth / (1.0 + flatt + gm_ratio - 2.0 * flatt * sinlat2)

def curvature(lat, lon, theta):
    '''
    :param lat:    Surface point latitude
    :param lon:    Surface point longitude
    :param theta: Azimuth direction
    :return:
    r_coc centre of curvature
    roc Radius of curvature
    '''
    rlat = numpy.deg2rad(lat)
    rlon = numpy.deg2rad(lon)
    x = geod2cart(rlat, rlon, 0.0)
    atoc2 = (Re / Rp) ** 2
    xy2 = x[0] ** 2 + x[1] ** 2
    z2 = (atoc2 * x[2]) ** 2
    cphi2 = xy2 / (xy2 + z2)
    sphi2 = z2 / (xy2 + z2)
    r_EW = 1.0 / numpy.sqrt( cphi2 + (sphi2/atoc2) )  # normalised wrt Re
    r_NS = r_EW**3 / atoc2                         # normalised wrt Re
    roc = Re / ( (numpy.cos(theta)**2/r_NS) + (numpy.sin(theta)**2/r_EW) )

    n = numpy.array([numpy.cos(rlat) * numpy.cos(rlon),
         numpy.cos(rlat) * numpy.sin(rlon),
         numpy.sin(rlat)])

    r_coc = x - roc * n
    return roc, r_coc


import numpy as np
import numba as nb

@nb.jit(nopython=True)
def laplacian_LDO(N):
    """Generate the laplacian linear differential operator for a grid with N points:

        -2      1       0       0
        1       -2      1       0
        0       1       -2      1  .  .  .
                .
                .
                .

    """

    A = np.zeros((N-2, N-2))
    for i in range(1, N-3):
        A[i, i-1] = 1
        A[i, i] = -2
        A[i, i+1] = 1

    A[0, 0] = -2
    A[0, 1] = 1
    A[N-3, N-3] = -2
    A[N-3, N-4] = 1

    return A

@nb.jit(nopython=True)
def prolongation(N):
    """Generates the interpolation operator for going from a grid with (N-1)/2 points to a grid with N points:

        0.5     0       0
        1       0       0
        0.5     0.5     0   .   .   .
        0       1       0
        0       0.5     0.5
                .
                .
                .

    """

    I = np.zeros((N, int((N-1)/2)))
    I[0, 0] = 0.5
    I[-1, -1] = 0.5
    for i in range(1, N-1):
        if i % 2 == 0:
            I[i, int(i/2)] = 0.5
            I[i, int(i/2-1)] = 0.5
        else:
            I[i, int((i-1)/2)] = 1

    return I

@nb.jit(nopython=True)
def full_weighting(N):
    """Returns the restriction operator for going from a grid with 2N points to a grid with N points. This is just the
    transpose of the prolongation operator (up to a factor).
    """
    return np.transpose(prolongation(N))*0.5

def get_good_grid_sizes(N):
    """Return an array of length N containing good grid sizes to use for multigrid. If you use a grid which cannot be
    coarsened by the VMG or FMG solvers, you won't see any speedup over Jacobi iteration alone. The grid sizes given
    by this function can be coarsened multiple times, making them ideal for use with multigrid.
    """

    a = np.zeros(N+1, dtype=int)
    a[0] = 7
    for i in range(1, N+1):
        a[i] = int(2*a[i-1] + 1)
    return a+2


from __future__ import division

import math

import numpy as np

def polar2cartesian(rho, phi):
    return [rho * math.cos(phi),
            rho * math.sin(phi)]

def cartesian2polar(x, y):
    return [math.sqrt(x ** 2 + y ** 2),
            math.atan2(y, x)]

def spherical2cartesian(rho, phi, theta):
    return [rho * math.sin(theta) * math.cos(phi),
            rho * math.sin(theta) * math.sin(phi),
            rho * math.cos(theta)]

def cartesian2spherical(x, y, z):
    rho = math.sqrt(x ** 2 + y ** 2 + z ** 2)
    return [rho,
            math.atan2(y, x),
            math.acos(z / rho) if rho != 0 else 0]

def cartesian2auto(vector):
    if len(vector) == 2:
        return cartesian2polar(vector[0], vector[1])
    return cartesian2spherical(vector[0], vector[1], vector[2])

def rad2deg(rad):
    return rad / math.pi * 180

def deg2rad(deg):
    return deg / 180 * math.pi

def get_distance(x0, y0, x1, y1):
    return math.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2)

def vector_magnitude(vector):
    return np.linalg.norm(vector)

def c2d():
    return np.array([0, 0])

def rotate(vector, matrix):
    return (vector * matrix).getA()[0]


import sympy as sp

def hat(x, i, n):
    hi = sp.Piecewise(
        (0, x < (i - 1) / (n + 1)),
        (0, x > (i + 1) / (n + 1)),
        ((n + 1) * x - i + 1, x < i / (n + 1)),
        (-(n + 1) * x + i + 1, x > i / (n + 1))
    )
    return hi

def hats_sp(x, n):
    hats = []
    for i in range(1, n + 1):
        hats.append(hat(x, n, i))

    return hats

if __name__ == "__main__":
    x, i, j, n, p = sp.symbols('x, i, j, n, p')
    rsv = i - (n+1)*x + 1
    lsv = (n+1)*x - i + 2

    bi = (i / (n + 1), (i + 1) / (n + 1))
    p22 = sp.integrate(rsv ** 2 * lsv ** 2, (x, *bi))
    p31 = sp.integrate(rsv ** 3 * lsv ** 1, (x, *bi))
    p40 = 2 * sp.integrate(rsv ** 4, (x, *bi))
    p22 = sp.simplify(p22)
    p31 = sp.simplify(p31)
    p40 = sp.simplify(p40)

    print("22:", p22)
    print("31:", p31)
    print("40:", p40)

    rsvp = rsv.diff(x)
    lsvp = lsv.diff(x)

    p22p = sp.integrate(rsvp ** 2 * lsvp ** 2, (x, *bi))
    p31p = sp.integrate(rsvp ** 3 * lsvp ** 1, (x, *bi))
    p13p = sp.integrate(rsvp ** 1 * lsvp ** 3, (x, *bi))
    p40p = 2 * sp.integrate(rsvp ** 4, (x, *bi))
    p11p = sp.integrate(rsvp ** 1 * lsvp ** 1, (x, *bi))
    p20p = 2 * sp.integrate(rsvp ** 2, (x, *bi))
    p22p = sp.simplify(p22p)
    p31p = sp.simplify(p31p)
    p13p = sp.simplify(p13p)
    p40p = sp.simplify(p40p)
    p11p = sp.simplify(p11p)
    p20p = sp.simplify(p20p)

    print("22p:", p22p)
    print("31p:", p31p)
    print("13p:", p31p)
    print("40p:", p40p)
    print("11p:", p11p)
    print("20p:", p20p)