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

import numpy as np
import pandas as pd
import multiprocessing as mp

from math import sqrt
from functools import partial
#from dask import delayed, compute
from nbeats.contrib.utils.pytorch.losses import divide_no_nan

AVAILABLE_METRICS = ['mse', 'rmse', 'mape', 'smape', 'mase', 'rmsse',
                     'mini_owa', 'pinball_loss']

######################################################################
# METRICS
######################################################################

def mse(y, y_hat):
    """Calculates Mean Squared Error.

    MSE measures the prediction accuracy of a
    forecasting method by calculating the squared deviation
    of the prediction and the true value at a given time and
    averages these devations over the length of the series.

    Parameters
    ----------
    y: numpy array
        actual test values
    y_hat: numpy array
        predicted values

    Return
    ------
    scalar: MSE
    """
    mse = np.mean(np.square(y - y_hat))

    return mse

def rmse(y, y_hat):
    """Calculates Root Mean Squared Error.

    RMSE measures the prediction accuracy of a
    forecasting method by calculating the squared deviation
    of the prediction and the true value at a given time and
    averages these devations over the length of the series.
    Finally the RMSE will be in the same scale
    as the original time series so its comparison with other
    series is possible only if they share a common scale.

    Parameters
    ----------
    y: numpy array
      actual test values
    y_hat: numpy array
      predicted values

    Return
    ------
    scalar: RMSE
    """
    rmse = sqrt(np.mean(np.square(y - y_hat)))

    return rmse

def mape(y, y_hat):
    """Calculates Mean Absolute Percentage Error.

    MAPE measures the relative prediction accuracy of a
    forecasting method by calculating the percentual deviation
    of the prediction and the true value at a given time and
    averages these devations over the length of the series.

    Parameters
    ----------
    y: numpy array
      actual test values
    y_hat: numpy array
      predicted values

    Return
    ------
    scalar: MAPE
    """
    mape = np.mean(np.abs(y - y_hat) / np.abs(y))
    mape = 100 * mape

    return mape

def smape(y, y_hat):
    """Calculates Symmetric Mean Absolute Percentage Error.

    SMAPE measures the relative prediction accuracy of a
    forecasting method by calculating the relative deviation
    of the prediction and the true value scaled by the sum of the
    absolute values for the prediction and true value at a
    given time, then averages these devations over the length
    of the series. This allows the SMAPE to have bounds between
    0% and 200% which is desireble compared to normal MAPE that
    may be undetermined.

    Parameters
    ----------
    y: numpy array
      actual test values
    y_hat: numpy array
      predicted values

    Return
    ------
    scalar: SMAPE
    """
    delta_y = np.abs(y - y_hat)
    scale = np.abs(y) + np.abs(y_hat)
    smape = divide_no_nan(delta_y, scale)
    smape = 200 * np.mean(smape)
    assert smape <= 200, 'SMAPE should be lower than 200'

    return smape

def mase(y, y_hat, y_train, seasonality=1):
    """Calculates the M4 Mean Absolute Scaled Error.

    MASE measures the relative prediction accuracy of a
    forecasting method by comparinng the mean absolute errors
    of the prediction and the true value against the mean
    absolute errors of the seasonal naive model.

    Parameters
    ----------
    y: numpy array
      actual test values
    y_hat: numpy array
      predicted values
    y_train: numpy array
      actual train values for Naive1 predictions
    seasonality: int
      main frequency of the time series
      Hourly 24,  Daily 7, Weekly 52,
      Monthly 12, Quarterly 4, Yearly 1

    Return
    ------
    scalar: MASE
    """
    scale = np.mean(abs(y_train[seasonality:] - y_train[:-seasonality]))
    mase = np.mean(abs(y - y_hat)) / scale
    mase = 100 * mase

    return mase

def rmsse(y, y_hat, y_train, seasonality=1):
    """Calculates the M5 Root Mean Squared Scaled Error.

    Parameters
    ----------
    y: numpy array
      actual test values
    y_hat: numpy array of len h (forecasting horizon)
      predicted values
    seasonality: int
      main frequency of the time series
      Hourly 24,  Daily 7, Weekly 52,
      Monthly 12, Quarterly 4, Yearly 1

    Return
    ------
    scalar: RMSSE
    """
    scale = np.mean(np.square(y_train[seasonality:] - y_train[:-seasonality]))
    rmsse = sqrt(mse(y, y_hat) / scale)
    rmsse = 100 * rmsse

    return rmsse

def mini_owa(y, y_hat, y_train, seasonality, y_bench):
    """Calculates the Overall Weighted Average for a single series.

    MASE, sMAPE for Naive2 and current model
    then calculatess Overall Weighted Average.

    Parameters
    ----------
    y: numpy array
        actual test values
    y_hat: numpy array of len h (forecasting horizon)
        predicted values
    seasonality: int
        main frequency of the time series
        Hourly 24,  Daily 7, Weekly 52,
        Monthly 12, Quarterly 4, Yearly 1
    y_train: numpy array
        insample values of the series for scale
    y_bench: numpy array of len h (forecasting horizon)
        predicted values of the benchmark model

    Return
    ------
    return: mini_OWA
    """
    mase_y = mase(y, y_hat, y_train, seasonality)
    mase_bench = mase(y, y_bench, y_train, seasonality)

    smape_y = smape(y, y_hat)
    smape_bench = smape(y, y_bench)

    mini_owa = ((mase_y/mase_bench) + (smape_y/smape_bench))/2

    return mini_owa

def pinball_loss(y, y_hat, tau=0.5):
    """Calculates the Pinball Loss.

    The Pinball loss measures the deviation of a quantile forecast.
    By weighting the absolute deviation in a non symmetric way, the
    loss pays more attention to under or over estimation.
    A common value for tau is 0.5 for the deviation from the median.

    Parameters
    ----------
    y: numpy array
      actual test values
    y_hat: numpy array of len h (forecasting horizon)
      predicted values
    tau: float
      Fixes the quantile against which the predictions are compared.

    Return
    ------
    return: pinball_loss
    """
    delta_y = y - y_hat
    pinball = np.maximum(tau * delta_y, (tau-1) * delta_y)
    pinball = pinball.mean()

    return pinball

def panel_mape(y_hat):
    y_hat = y_hat.copy()
    y_hat['mape'] = np.abs(y_hat['y_hat']-y_hat['y'])/np.abs(y_hat['y'])
    y_hat_grouped = y_hat.groupby('unique_id').mean().reset_index()
    mape = np.mean(y_hat_grouped['mape'])
    return mape

def panel_smape(y_hat):
    y_hat = y_hat.copy()
    y_hat['smape'] = np.abs(y_hat['y_hat']-y_hat['y'])/(np.abs(y_hat['y'])+np.abs(y_hat['y_hat']))
    y_hat_grouped = y_hat.groupby('unique_id').mean().reset_index()
    smape = 2 * np.mean(y_hat_grouped['smape'])
    return smape


from quantfin.simulation import random_covariance
from quantfin.statistics import marchenko_pastur, targeted_shirinkage
from quantfin.portfolio import HRP
from matplotlib import rcParams
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

n_assets = 25
n_time = 5000
n_factors = 3
random_seed = 666

file_path = r'/Users/gustavoamarante/Dropbox/CQF/Final Project'  # Mac
# file_path = r'/Users/gusamarante/Dropbox/CQF/Final Project'  # Macbook

# ===== generate data =====
mu = np.zeros(n_assets)
cov = random_covariance(size=n_assets, n_factors=n_factors, random_seed=random_seed)
data = pd.DataFrame(data=np.random.multivariate_normal(mu, cov, n_time),
                    columns=[f'Asset {i + 1}' for i in range(n_assets)])

# ===== plot correlation matrix =====
hrp = HRP(data)
hrp.plot_corr_matrix(cmap='mako', figsize=(10, 10), show_chart=True,
                     save_path=file_path + r'/figures/Simulated Correlation Matrix.pdf')

# ===== compute different correlations matrices =====
corr = data.corr().values  # empirical correlation
corr_denoised, _, _ = marchenko_pastur(corr_matrix=corr, T=data.shape[0], N=data.shape[1])  # denoised correlation
corr_ts, _, _ = targeted_shirinkage(corr_matrix=corr, T=data.shape[0], N=data.shape[1], ts_alpha=1)  # target-shrinkage

# ===== sorted eigenvalues of each method =====
eig_empirical = np.sort(np.linalg.eig(corr)[0])[::-1]
eig_denoised = np.sort(np.linalg.eig(corr_denoised)[0])[::-1]
eig_ts = np.sort(np.linalg.eig(corr_ts)[0])[::-1]
df_eig = pd.DataFrame(data={'Empirical Eigenvalues': eig_empirical,
                            'Denoised Eigenvalues': eig_denoised,
                            'Targeted Shirinkage': eig_ts},
                      index=[i + 1 for i in range(n_assets)])

# ===== chart =====
# time series Chart
MyFont = {'fontname': 'Century Gothic'}
rcParams['font.family'] = 'sans-serif'
rcParams['font.sans-serif'] = ['Century Gothic']

plt.figure(figsize=(10, 4))
plt.plot(df_eig, linewidth=2)
plt.tick_params(axis='y', which='both', right=False, left=False, labelleft=True)
plt.tick_params(axis='x', which='both', top=False, bottom=False, labelbottom=True)
plt.legend(df_eig.columns, loc='upper right', ncol=1, frameon=True)
ax = plt.gca()
ax.set(yscale='log', xlabel='Sorted Eigenvalues', ylabel='Log-Scale')
ax.yaxis.grid(color='grey', linestyle='-', linewidth=0.5, alpha=0.5)
ax.xaxis.grid(color='grey', linestyle='-', linewidth=0.5, alpha=0.5)
plt.tight_layout()
plt.savefig(file_path + f'/figures/Simulated Sorted Eigenvalues.pdf', pad_inches=0)
plt.show()

# ===== get characteristics =====
df_char = pd.DataFrame()
corr_mat_dict = {'Empirical': corr,
                 'Denoised': corr_denoised,
                 'Targeted Shirinkage': corr_ts}
for mat in corr_mat_dict.keys():
    eigvals = np.linalg.eig(corr_mat_dict[mat])[0]
    df_char.loc['Maximum Eigenvalue', mat] = np.max(eigvals)
    df_char.loc['Minimum Eigenvalue', mat] = np.min(eigvals)
    df_char.loc['Determinant', mat] = np.linalg.det(corr_mat_dict[mat])
    df_char.loc['Max Norm', mat] = np.max(np.abs(corr_mat_dict[mat] - corr))
    df_char.loc['Frobenius Norm', mat] = np.linalg.norm(corr_mat_dict[mat] - corr, 'fro')

df_char.to_clipboard()


import scipy.sparse as sp
import numpy as np
import tensorflow as tf

def eigen_pe_sp(edges, num_nodes, pos_enc_dim):
    if isinstance(edges, tf.Tensor):
        edges = edges.numpy()
    if isinstance(num_nodes, tf.Tensor):
        num_nodes = num_nodes.numpy()
    if isinstance(pos_enc_dim, tf.Tensor):
        pos_enc_dim = pos_enc_dim.numpy()

    rows_mat = edges[:,0]
    cols_mat = edges[:,1]

    data_mat = np.ones(rows_mat.shape, dtype='float32')

    A = sp.csr_matrix((data_mat, (rows_mat,cols_mat)),
                      shape=(num_nodes,num_nodes),
                      dtype='float32')
    N = sp.diags(A.sum(axis=1).A.squeeze().clip(1) ** -0.5, dtype=float)
    L = sp.eye(num_nodes) - N * A * N

    EigVal, EigVec = sp.linalg.eigs(L, k=pos_enc_dim+1, which='SR', tol=1e-2)
    EigVec = EigVec[:, EigVal.argsort()]

    pos_enc = np.real(EigVec[:,1:pos_enc_dim+1]).astype('float32')

    return pos_enc

def eigen_pe_np(edges, num_nodes, pos_enc_dim):
    if isinstance(edges, tf.Tensor):
        edges = edges.numpy()
    if isinstance(num_nodes, tf.Tensor):
        num_nodes = num_nodes.numpy()
    if isinstance(pos_enc_dim, tf.Tensor):
        pos_enc_dim = pos_enc_dim.numpy()

    rows_mat = edges[:,0]
    cols_mat = edges[:,1]

    data_mat = np.ones(rows_mat.shape, dtype='float32')

    A = sp.csr_matrix((data_mat, (rows_mat,cols_mat)),
                      shape=(num_nodes,num_nodes),
                      dtype='float32')
    N = sp.diags(A.sum(axis=1).A.squeeze().clip(1) ** -0.5, dtype=float)
    L = sp.eye(num_nodes) - N * A * N

    EigVal, EigVec = np.linalg.eig(L.toarray())
    EigVec = np.real(EigVec[:, EigVal.argsort()])
    pos_enc = EigVec[:,1:pos_enc_dim+1].astype('float32')

    return pos_enc

def eigen_pe(edges, num_nodes, pos_enc_dim, sparse=True):
    if sparse:
        out = tf.py_function(eigen_pe_sp, [edges, num_nodes, pos_enc_dim], tf.float32)
    else:
        out = tf.py_function(eigen_pe_np, [edges, num_nodes, pos_enc_dim], tf.float32)

    if not isinstance(num_nodes, tf.Tensor):
        out.set_shape([num_nodes, None])
    if not isinstance(pos_enc_dim, tf.Tensor):
        out.set_shape([None, pos_enc_dim])
    return out

class EigenFeatures:
    def __init__(self                                     ,
                 num_features        = 8                  ,
                 sparse              = True               ,
                 edges_input_key     = 'edges'            ,
                 num_nodes_input_key = 'num_nodes'        ,
                 vector_key          = 'eigen_vectors'    ):

        self.num_features        = num_features
        self.sparse              = sparse
        self.edges_input_key     = edges_input_key
        self.num_nodes_input_key = num_nodes_input_key
        self.vector_key          = vector_key

    def __call__(self, inputs):
        edges = inputs[self.edges_input_key]
        num_nodes = inputs[self.num_nodes_input_key]

        U = eigen_pe(edges, num_nodes, self.num_features, self.sparse)

        return_dict = {
            **inputs           ,
            self.vector_key : U,
        }

        return return_dict


# Generates samples from the heart-shaped density and writes to heart.npy
import numpy as np
import torch
import random
import math
from tqdm import tqdm

def potential(z):
    z = z.view(-1, 2)
    x, y = z[:, 0], z[:, 1]
    u = 0.8 * x ** 2 + (y - ((x**2)**(1/3)))**2
    u = u / 2**2
    return u

def log_Q(potential, z_prime, z, step):
    z.requires_grad_()
    grad = torch.autograd.grad(potential(z).mean(), z)[0]
    return -(torch.norm(z_prime - z + step * grad, p=2, dim=1) ** 2) / (4 * step)

def metropolis_adjusted_langevin_algorithm(potential, n_samples=100000, step=0.1):
    burn_in = 10000
    Z0 = torch.randn(1, 2)
    Zi = Z0
    samples = []
    pbar = tqdm(range(n_samples + burn_in))
    for i in pbar:
        Zi.requires_grad_()
        u = potential(Zi).mean()
        grad = torch.autograd.grad(u, Zi)[0]
        prop_Zi = Zi.detach() - step * grad + np.sqrt(2 * step) * torch.randn(1, 2)
        log_ratio = -potential(prop_Zi).mean() + potential(Zi).mean() +\
                    log_Q(potential, Zi, prop_Zi, step) - log_Q(potential, prop_Zi, Zi, step)
        if torch.rand(1) < torch.exp(log_ratio):
            Zi = prop_Zi
        samples.append(Zi.detach().numpy())
    return np.concatenate(samples, 0)[burn_in:]

samples = metropolis_adjusted_langevin_algorithm(potential)
np.save('heart-mala.npy', samples)


import numpy as np
import matplotlib.pyplot as plt

# deriv computes 1D derivative, dF/dr, using central difference method
def deriv(F,r):
	# get the length of array F (assume same length for r)
    L = F.size

	# create empty array to store results
    result=  np.empty(L)

	# use central diff method for all interior points (we will build in tutorial)
    for i in range(L-2):
        result[i+1] = (F[i+2] - F[i]) / (r[i+2] - r[i])

    result[0] = (F[1] - F[0]) / (r[1] - r[0])
    result[L-1] = (F[L-1] - F[L-2]) / (r[L-1] - r[L-2])

    return result

# read in the files "vFieldX.csv" and "vFieldY.csv"
vFieldX= np.loadtxt( 'vFieldX.csv', delimiter = ',' )
vFieldY = np.loadtxt( 'vFieldY.csv', delimiter = ',' )

# Create a 2D grid of x, y points using numpy's meshgrid function (see Exercise 1)
nx, ny = 100,100
x = np.linspace(-5,5,nx)
y = np.linspace(-5,5,ny)
X, Y = np.meshgrid(x,y)

# Divergence
divX = np.empty(X.shape)

for j in range(ny):
    divX[j,:] = deriv(vFieldX[j,:],x)

divY = np.empty(Y.shape)

for i in range(nx):
    divY[:,i] = deriv(vFieldY[:,i], y)

totalDiv = divX + divY

# Curl
curlX = np.empty(X.shape)

for j in range(ny):
    curlX[j,:] = deriv(vFieldY[j,:], x)

curlY = np.empty(Y.shape)

for i in range(nx):
    curlY[:,i] = deriv(vFieldX[:,i], y)

totalCurl = curlX - curlY

# Plotting the Divergence and Curl using subplots
lines = 10**np.linspace(10, 12, 11)
lines = sorted(list(-lines)+list(lines))
fig, (ax1, ax2) = plt.subplots( nrows = 1, ncols = 2, sharex = False, sharey = False )

ax1.contourf(X, Y, totalDiv) #levels = Lines, colors = 'k', linewidths = 1)
CS = ax2.contour(x, y, totalCurl, ) #levels = Lines, colors = 'k', linewidths = 1)

ax1.set_title('Divergence of a Vector Field', fontweight = 'bold' )
ax2.set_title('Curl of a Vector Field', fontweight = 'bold' )

ax1.set(xlabel = "X", ylabel = "Y")
ax2.set(xlabel = "X", ylabel = "Y")

ax2.clabel(CS, inline = 1, fontsize = 8)

fig.set_size_inches(9, 5)

plt.savefig('divCurlPlot.png', dpi=300)


import numpy as np
import matplotlib.pyplot as plt

def softmax(inputs):
    """
    Calculate the softmax for the give inputs (array)
    :param inputs:
    :return:
    """
    return np.exp(inputs) / float(sum(np.exp(inputs)))

def line_graph(x, y, x_title, y_title):
    """
    Draw line graph with x and y values
    :param x:
    :param y:
    :param x_title:
    :param y_title:
    :return:
    """
    plt.plot(x, y)
    plt.xlabel(x_title)
    plt.ylabel(y_title)
    plt.show()

softmax_inputs = [2, 3, 5, 6]

print("Softmax Function Output :: {}".format(softmax(softmax_inputs)))

graph_x = range(0, 21)
graph_y = softmax(graph_x)

print("Graph X readings: {}".format(graph_x))
print("Graph Y readings: {}".format(graph_y))

line_graph(graph_x, graph_y, "Inputs", "Softmax Scores")


import math
from functools import partial
import numpy as np
import matplotlib.pyplot as plt
from scipy.misc import derivative

def _function(fun: str, x: int or float):
    """
    Transform the string to executable form.

    :param fun: a one parameter function, it must be a string.
    :param x: the parameter of the function..
    :return: the executable form of the string function.
    """
    return eval(fun)

def BM(
        fun: str,
        a: int or float,
        b: int or float,
        eps1: float,
        eps2: float,
        max_time: int
) -> tuple:
    """
    BM algorithm.

    :param fun: the function which will be solved by BM, it must be a string.
    :param a: the left boundary you assign.
    :param b: the right boundary you assign.
    :param eps1: the minimum boundary of the solution.
    :param eps2: the minimum boundary of the function's value of solution.
    :param max_time: the max iteration time.
    :return: a tuple with two elements, the first element is a list which
        includes the single solution of objective equation and the second
        element is the iteration time.
    """
    f = partial(_function, fun)
    # The partial function, it can fix the parameters for a objective function.

    time = 1
    while time < max_time:
        x = (a + b) / 2
        xe = (b - a) / 2
        print(f"第{time}次迭代:{x}")

        if abs(xe) < eps2 and abs(f(x)) < eps1:
            return x, time
        else:
            if f(a) * f(x) < 0:
                b = x
            else:
                a = x
        time += 1

def BMZ(
        fun: str,
        a: int or float,
        eps1: float,
        eps2: float,
        k: int,
        dx: int or float,
        max_time: int
) -> tuple:
    """
    BMZ algorithm.

    :param fun: the function which will be solved by BM, it must be a string.
    :param a: the left boundary you assign.
    :param eps1: the minimum boundary of the solution.
    :param eps2: the minimum boundary of the function's value of solution.
    :param k: the number of all solutions.
    :param dx: the step length.
    :param max_time: the max iteration time.
    :return: a tuple with two elements, the first element is a list which
        includes the single solution of objective equation and the second
        element is the iteration time.
    """
    f = partial(_function, fun)
    # The partial function, it can fix the parameters for a objective function.

    all_solution = []

    time = 1
    while time < max_time:
        x = a + time * dx

        if abs(f(x) - 0) < eps1:
            all_solution.append(x)

        elif f(x) * f(x + dx) > 0:
            # The judgement condition in book is "f(x) * f(x + 1) > 0", but
            # while I debugged here, I discovered it will ignore one solution,
            # so I had to modify it.

            time += 1
            pass
        else:
            try:
                all_solution.append(
                    BM(fun, x, a + (time + 1) * dx, eps1, eps2, max_time)[0]
                )
                time += 1
            except Exception:
                time += 1
                pass

        if len(all_solution) == k:
            break

    return all_solution, time

def false_position(
        fun: str,
        a: int or float,
        b: int or float,
        eps1: float,
        eps2: float,
        max_time: int
) -> tuple:
    """
    Using false position algorithm to solve the function.

    :param fun: the function which will be solved by false position
        algorithm, it must be a string.
    :param a: the left boundary.
    :param b: the right boundary.
    :param eps1: the minimum boundary of the solution.
    :param eps2: the minimum boundary of the function's value of solution.
    :param max_time: the max iteration time.
    :return: the single solution of the function.
    """

    def _getX1(fun, x_left, x_right):
        x = x_left - (
                (x_right - x_left) / (fun(x_right) - fun(x_left))
            ) * fun(x_left)
        return x

    f = partial(_function, fun)
    # The partial function, it can fix the parameters for a objective function.

    getX1 = partial(_getX1, f)
    x1 = getX1(a, b)
    x = a
    xe = b

    time = 1
    while time < max_time:
        if x * x1 < 0:
            xe = x1
            x1 = getX1(x, xe)
        else:
            x = x1
            x1 = getX1(x, xe)

        if abs((xe - x) / 2) < eps1 or abs(f(x)) < eps2:
            break
        time += 1

    return x, time

def simple_iteration(
        fun: str,
        initial_value: int or float,
        eps1: int or float,
        eps2: int or float,
        max_time: int
) -> tuple:
    """
    Using simple iteration algorithm to calculate the solution of nonlinear
    equation which you assign. There is a little inconvenience, the parameter
    "fun" must be the iteration form but not the objective function. Be example,
    if you want to get the solution of "x ** 2", you must make the "fun" is
    iteration form like "x ** 2 + x" or other iteration forms.

    :param fun: The iteration form of your objective function, it must be str
        type.
    :param initial_value: the initial value.
    :param eps1: the minimum boundary of the solution.
    :param eps2: the minimum boundary of the function's value of solution.
    :param max_time: the max iteration time.
    :return: a tuple with two elements, the first element is a list which include
        all solutions of the objective equation and the second element is the
        iteration time.
    """

    f = partial(_function, fun)
    x = initial_value
    solution_sequence = [x]

    time = 1
    while time < max_time:
        x = f(x)
        solution_sequence.append(x)

        if abs(solution_sequence[-1] - solution_sequence[-2]) < eps1 \
                or abs(f(solution_sequence[-1])) < eps2:
            break

        time += 1
    return solution_sequence, time

def steffensen(
        fun: str,
        initial_value: float or int,
        eps1: float or int,
        eps2: float or int,
        max_time: int
) -> tuple:
    """
    This function implements Steffensen algorithm which can solve a nonlinear
    equation, compare to simple iteration algorithm, it has more fast convergence
    rate. And, same as simple iteration, you must transform the objective equation
    to iteration form.

    :param fun: the iteration form of the objective equation.
    :param initial_value: the initial value.
    :param eps1: the minimum boundary of the solution.
    :param eps2: the minimum boundary of the function's value of solution.
    :param max_time: the max iteration time.
    :return: a tuple with two elements, the first element is the solution and the
        second element is the iteration time.
    """
    f = partial(_function, fun)
    x = [initial_value]
    print(f"第1次：{x[-1]}， f(x) = {f(x[-1]) - x[-1]}")

    time = 1
    while time < max_time:
        x1 = f(x[-1])
        x2 = f(x1)
        x.append(x[-1] - ((x1 - x[-1]) ** 2) / (x2 - 2 * x1 + x[-1]))

        if abs(x[-1] - x[-2]) < eps1 or abs(f(x[-1])) < eps2:
            break

        print(f"第{time}次：{x[-1]}， f(x) = {f(x[-1]) - x[-1]}")

        time += 1
    print(f"最后一次：{x[-1]}，f(x) = {f(x[-1]) - x[-1]}")

    return x, time

def newton_iteration(
        fun: str,
        initial_value: int or float,
        eps1: int or float,
        eps2: int or float,
        max_time: int
) -> tuple:
    """
    This function is a implementation of Newton iteration algorithm. The
    parameter "fun" is the iteration form of the objective equation.

    :param fun: the iteration form of the objective equation.
    :param initial_value: the initial value.
    :param eps1: the minimum boundary of the solution.
    :param eps2: the minimum boundary of the function's value of solution.
    :param max_time: the max iteration time.
    :return: a tuple with two elements, the first element is the solution and the
        second element is the iteration time.
    """
    f = partial(_function, fun)
    x = initial_value
    print(f"第1次迭代结果：{x}，f(x) = {f(x)}")

    time = 1
    while time < max_time:
        xOld = x

        fd = (f(x + 0.0000001) - f(x)) / 0.0000001
        # Using the definition to calculate the value of derivatives of the
        # objective equation.

        x -= f(x) / fd

        if abs(x - xOld) < eps1 and abs(f(x)) < eps2:
            break
        print(f"第{time}次迭代结果：{x}，f(x) = {f(x)}")

        time += 1

    return x, time

def multiple_solutionNewton(
        fun: str,
        initial_value: int or float,
        eps1: int or float,
        eps2: int or float,
        max_time: int
) -> tuple:
    """
    The algorithm of this function implementation is multiple root acceleration.

    :param fun: the iteration form of the objective equation.
    :param initial_value: the initial value.
    :param eps1: the minimum boundary of the solution.
    :param eps2: the minimum boundary of the function's value of solution.
    :param max_time: the max iteration time.
    :return: a tuple with two elements, the first element is the solution and the
        second element is the iteration time.
    """
    f = partial(_function, fun)
    x = initial_value

    time = 1
    while time < max_time:
        xOld = x

        fd = derivative(f, x, dx=0.0001, n=1)
        fdd = derivative(f, x, dx=0.00001, n=2)
        # In order to calculate the value of two derivative of the objective
        # equation, I have to use the function "derivative" in "scipy". The
        # variable "fd" is the value of one derivative of the objective and
        # the "fdd" is the value of two derivative.

        x -= (f(x) * fd) / (fd ** 2 - f(x) * fdd)

        if abs(x - xOld) < eps1 and abs(f(x) < eps2):
            break

        time += 1

    return x, time

def secant(
        fun: str,
        initial_value: int or float,
        eps1: int or float,
        eps2: int or float,
        max_time: int
) -> tuple:
    """
    The algorithm this function implementation is the improvement of Newton
    iteration algorithm, this algorithm use difference quotient to replace
    differential quotient.

    :param fun: the iteration form of the objective equation.
    :param initial_value: the initial value.
    :param eps1: the minimum boundary of the solution.
    :param eps2: the minimum boundary of the function's value of solution.
    :param max_time: the max iteration time.
    :return: a tuple with two elements, the first element is the solution and the
        second element is the iteration time.
    """
    f = partial(_function, fun)
    x = [initial_value]
    x.append(x[0] - f(x[0]) / (((f(x[0]) + 0.0000001) - f(x[0])) / 0.0000001))

    time = 1
    while time < max_time:
        xNew = x[-1] - (f(x[-1]) / (f(x[-1]) - f(x[-2]))) * (x[-1] - x[-2])
        x.append(xNew)

        if abs(x[-1] - x[-2]) < eps1 and abs(f(x[-1])) < eps2:
            break

        time += 1

    return x[-1], time

def newton_descent(
        fun: str,
        initial_value: int or float,
        eps1: int or float,
        eps2: int or float,
        eps3: int or float,
        eps4: int or float,
        max_time: int
) -> tuple:
    """
    This function implements the improvement of Newton iteration, it has more fast
    convergence speed.

    :param fun: the iteration form of the objective equation.
    :param initial_value: the initial value.
    :param eps1: the minimum boundary of the solution.
    :param eps2: the minimum boundary of the function's value of solution.
    :param eps3: the minimum value of t.
    :param eps4: the correction of initial value.
    :param max_time: the max iteration time.
    :return: a tuple with two elements, the first element is the solution and the
        second element is the iteration time.
    """
    f = partial(_function, fun)
    x = [initial_value]
    t = 1

    time = 1
    while time < max_time:
        fd = (f(x[-1] + 0.0000001) - f(x[-1])) / 0.0000001
        x_ = x[-1] - t * (f(x[-1]) / fd)

        if abs(f(x_) - f(x[-1])) < eps1 or abs(f(x_)) < eps2:
            return x_, time

        if abs(f(x_)) < abs(f(x[-1])):
            x.append(x_)
            print(f"第{time}次迭代结果：{x[-1]}")
            time += 1
        else:
            if t > eps3:
                t /= 2
                time += 1
                continue
            else:
                x.append(x_ + eps4)
                print(f"第{time}次迭代结果：{x[-1]}")
                t /= 2
                time += 1

    return x[-1], time

if __name__ == '__main__':
    testBIn = "1 - x - math.sin(x)"
    print("Test of BM algorithm:")
    bm_result = BM(testBIn, 0, 1, 0.001, 0.005, 10000)
    print(f"最终结果：{bm_result[0]}")
    print(f"迭代次数：{bm_result[1]}")

    print("Test of false position algorithm:")
    false_position_re = false_position(testBIn, 0, 1, 0.01, 0.005, 10000)
    print(f"最终结果：{false_position_re[0]}")
    print(f"迭代次数：{false_position_re[1]}")

    testBIn1 = "6 * x ** 4 - 40 * x ** 2 + 9"
    print("Test of BMZ algorithm:")
    bmz_result = BMZ(testBIn1, -5, 0.001, 0.1, 4, 0.1, 100)
    print(f"所有根：{bmz_result[0]}")
    print(f"迭代次数：{bmz_result[1]}")

    testIter = "math.e ** (-x)"
    print("Test of simple iteration algorithm:")
    simpleIteration_result = simple_iteration(testIter, 0.5, 0.0001, 0.0001, 10000)
    print(f"最终结果：{simpleIteration_result[0]}")
    print(f"迭代次数：{simpleIteration_result[1]}")
    print("Test of steffensen algorithm:")
    steffensen_result = steffensen(testIter, 0.5, 0.0001, 0.0001, 10000)
    print(f"最终结果：{steffensen_result[0]}")
    print(f"迭代次数：{steffensen_result[1]}")
    plt.plot(simpleIteration_result[0], label="simple iteration")
    plt.plot(steffensen_result[0], label="steffensen")
    plt.show()

    # testNewton = "x - (1 / 2) * (x + 3 / x)"  # 3 ^ (1 / 2) iteration form.
    testNewton = "(x ** 2) ** (1 / 3)"
    # test = "(x - 1.5) ** 2"
    print("Test of Newton iteration algorithm:")
    newtonSolution = newton_iteration(testNewton, 1.5, 0.00001, 0.00001, 10000)
    print(f"最终结果：{newtonSolution[0]}")
    print(f"迭代次数：{newtonSolution[1]}")
    print("Test of multiple solution algorithm:")
    multi = multiple_solutionNewton(testNewton, 1, 0.00001, 0.00001, 10000)
    print(f"最终结果：{multi[0]}")
    print(f"迭代次数：{multi[1]}")

    print("Test of secant algorithm:")
    secantSolution = secant(testNewton, 1, 0.00001, 0.00001, 10000)
    print(f"最终结果：{secantSolution[0]}")
    print(f"迭代次数：{secantSolution[1]}")
    print("Test of Newton descent algorithm:")
    des = newton_descent(testNewton, 1.5, 0.00001, 0.00001, 0.001, 0.001, 10000)
    print(f"最终结果：{des[0]}")
    print(f"迭代次数：{des[1]}")
    #
    # t = "(x ** 2) ** (1 / 3)"
    # x = np.linspace(-10, 10, 10000)
    # plt.plot(x, list(map(partial(_function, t), x)))
    # plt.show()

    # def e(x):
    #     return math.e ** -x
    # def f(x):
    #     ff = x - (e(x) - x) ** 2 / (e(e(x)) - 2 * e(x) + x)
    #     return ff
    # print(derivative(f, 0.5671190400572149, dx=0.0001, n=1))
    # print(derivative(f, 0.5671190400572149, dx=0.0001, n=2))
    # def ff(x):
    #     return abs(derivative(e, x, dx=0.0001, n=1))
    #
    # xx = np.linspace(0, 1, 1000)
    #
    # yy = max(list(map(ff, xx)))
    # print(yy)

    # x = np.linspace(0, 1, 10000)
    # plt.plot(x, list(map(f, x)))
    # plt.show()
    # print(e(0), e(1))
    # print(f(1))


"""
Filename: gth_solve.py

Author: Daisuke Oyama

Routine to compute the stationary distribution of an irreducible Markov
chain by the Grassmann-Taksar-Heyman (GTH) algorithm.

"""
import numpy as np

try:
    xrange
except:  # python3
    xrange = range

def gth_solve(A, overwrite=False):
    r"""
    This routine computes the stationary distribution of an irreducible
    Markov transition matrix (stochastic matrix) or transition rate
    matrix (generator matrix) `A`.

    More generally, given a Metzler matrix (square matrix whose
    off-diagonal entries are all nonnegative) `A`, this routine solves
    for a nonzero solution `x` to `x (A - D) = 0`, where `D` is the
    diagonal matrix for which the rows of `A - D` sum to zero (i.e.,
    :math:`D_{ii} = \sum_j A_{ij}` for all :math:`i`). One (and only
    one, up to normalization) nonzero solution exists corresponding to
    each reccurent class of `A`, and in particular, if `A` is
    irreducible, there is a unique solution; when there are more than
    one solution, the routine returns the solution that contains in its
    support the first index `i` such that no path connects `i` to any
    index larger than `i`. The solution is normalized so that its 1-norm
    equals one. This routine implements the Grassmann-Taksar-Heyman
    (GTH) algorithm [1]_, a numerically stable variant of Gaussian
    elimination, where only the off-diagonal entries of `A` are used as
    the input data. For a nice exposition of the algorithm, see Stewart
    [2]_, Chapter 10.

    Parameters
    ----------
    A : array_like(float, ndim=2)
        Stochastic matrix or generator matrix. Must be of shape n x n.
    overwrite : bool, optional(default=False)
        Whether to overwrite `A`; may improve performance.

    Returns
    -------
    x : numpy.ndarray(float, ndim=1)
        Stationary distribution of `A`.

    References
    ----------
    .. [1] W. K. Grassmann, M. I. Taksar and D. P. Heyman, "Regenerative
       Analysis and Steady State Distributions for Markov Chains,"
       Operations Research (1985), 1107-1116.

    .. [2] W. J. Stewart, Probability, Markov Chains, Queues, and
       Simulation, Princeton University Press, 2009.

    """
    A1 = np.array(A, dtype=float, copy=not overwrite)

    if len(A1.shape) != 2 or A1.shape[0] != A1.shape[1]:
        raise ValueError('matrix must be square')

    n = A1.shape[0]

    x = np.zeros(n)

    # === Reduction === #
    for i in xrange(n-1):
        scale = np.sum(A1[i, i+1:n])
        if scale <= 0:
            # There is one (and only one) recurrent class contained in
            # {0, ..., i};
            # compute the solution associated with that recurrent class.
            n = i+1
            break
        A1[i+1:n, i] /= scale

        A1[i+1:n, i+1:n] += np.dot(A1[i+1:n, i:i+1], A1[i:i+1, i+1:n])

    # === Backward substitution === #
    x[n-1] = 1
    for i in xrange(n-2, -1, -1):
        x[i] = np.dot(x[i+1:n], A1[i+1:n, i])

    # === Normalization === #
    x /= np.sum(x)

    return x


# ============================================================================ #
#                             LINEAR REGRESSION                                #
# ============================================================================ #
# Renders stacked line charts showing convex and non-convex obj function
#%%
import pandas as pd
import plotly.offline as po
import plotly.graph_objs as go
import numpy as np
from sklearn import datasets
from sklearn.linear_model import SGDRegressor
from ml_studio.supervised_learning.regression import LinearRegression
from ml_studio.supervised_learning.training.metrics import R2
from ml_studio.utils.data_manager import StandardScaler, data_split
from ml_studio.visual.animations import SingleModelFit2D, SingleModelSearch3D
directory = "./content/figures/"
# ---------------------------------------------------------------------------- #
#                                   DATA                                       #
# ---------------------------------------------------------------------------- #
#%%
# Data
X, y, coef = datasets.make_regression(n_samples=1000, n_features=1, bias=10,
                                noise=40, coef=True, random_state=50)
X_train, X_test, y_train, y_test = data_split(X,y, test_size=0.3, seed=50)
# ---------------------------------------------------------------------------- #
#                                SCATTER PLOT                                  #
# ---------------------------------------------------------------------------- #
#%%
# Linear Regression Scatterplot
data = go.Scatter(
    x=X_train.flatten(),
    y=y_train,
    mode='markers',
    marker=dict(color='steelblue')
)
layout = go.Layout(title='Simulated Data',
                   height=400,
                   width=800,
                   showlegend=False,
                   xaxis_title="X",
                   yaxis_title="Y",
                   margin=dict(l=10,r=10,t=40,b=10),
                   template='plotly_white'
                   )
fig = go.Figure(data=data, layout=layout)
fig.show()
po.plot(fig, filename = "./content/figures/simulated_training_data.html", auto_open=False)
# ---------------------------------------------------------------------------- #
#                             LINEAR REGRESSION                                #
# ---------------------------------------------------------------------------- #
#%%
# Linear Regression
scaler = StandardScaler()
scaler.fit(X_train)
X_train = scaler.transform(X_train)
lr = LinearRegression(epochs=1000, learning_rate=0.01, val_size=0.2, patience=40,
                      early_stop=True, metric='r2', verbose=True, checkpoint=100)
lr.fit(X_train,y_train)
print(lr.intercept_)
print(lr.coef_.shape)
# ---------------------------------------------------------------------------- #
#                                ANIMATIONS                                    #
# ---------------------------------------------------------------------------- #
#%%
# Animations
plot = SingleModelSearch3D()
plot.search(lr, directory=directory, filename="linear_regression_search_test.gif")
plot = SingleModelFit2D()
plot.fit(lr, directory=directory, filename="linear_regression_fit_test.gif")
#%%
# ---------------------------------------------------------------------------- #
#                                  TEST                                        #
# ---------------------------------------------------------------------------- #
scaler.fit(X_test)
X_test = scaler.transform(X_test)

# %%
# ---------------------------------------------------------------------------- #
#                   PREDICTED VS ACTUAL ON TEST DATA                           #
# ---------------------------------------------------------------------------- #
def f_actual(x):
    y = (10 + np.dot(coef, x)).flatten()
    return y
def f_pred(x):
    y = (lr.intercept_ + X.dot(lr.coef_)).flatten()
    return y
x = np.linspace(start=np.min(X_test), stop=np.max(X_test), num=100)
y_actual = f_actual(x)
y_pred = f_pred(x)
# ---------------------------------------------------------------------------- #
#%%
# Print actual vs predicted fit
data = [go.Scatter(x=X_test.flatten(),y=y_test,
                mode='markers',
                name='Test Data',
                marker=dict(color='steelblue')),
        go.Scatter(x=x, y=y_actual,
                mode='lines',
                name='Actual Fit'),
        go.Scatter(x=x, y=y_pred,
                mode='lines',
                name='Predicted Fit')]

layout = go.Layout(title='Actual vs Predicted Simulated Test Data',
                   height=400,
                   width=800,
                   showlegend=True,
                   xaxis_title="X",
                   yaxis_title="Y",
                   margin=dict(l=10,r=10,t=40,b=10),
                   template='plotly_white'
                   )
fig = go.Figure(data=data, layout=layout)
fig.show()
po.plot(fig, filename = "./content/figures/linear_regression_test_actual_v_predicted_.html", auto_open=False)

# ---------------------------------------------------------------------------- #
#                   PREDICTED VS ACTUAL R2 ON TEST DATA                        #
# ---------------------------------------------------------------------------- #
# %%
r2_pred = lr.score(X_test, y_test)
y_test_pred = f_actual(X_test)
r2_actual = R2()(y_test, y_test_pred)
# Print actual vs predicted R2
x = ['Actual Model', 'Learned Model']
y = [r2_actual, r2_pred]
data = [go.Bar(x=x,y=y,
                text=np.round(y,4),
                textposition='auto',
                showlegend=False,
                marker_color='rgb(55, 83, 109)')]

layout = go.Layout(title='R2 for Actual vs Predicted Simulated Test Data',
                   height=400,
                   width=800,
                   showlegend=True,
                   xaxis_title="X",
                   yaxis_title="Y",
                   margin=dict(l=10,r=10,t=40,b=10),
                   template='plotly_white'
                   )
fig = go.Figure(data=data, layout=layout)
fig.show()
po.plot(fig, filename = "./content/figures/linear_regression_test_actual_v_predicted_r2.html", auto_open=False)

# %%


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

"""
Example of running Nestle to fit the parameters of a straight line.
"""

from __future__ import print_function, division

import os
import sys
from scipy.special import ndtri
import numpy as np

# import Nestle
import nestle

# import model and data
from createdata import *

LN2PI = np.log(2.*np.pi)
LNSIGMA = np.log(sigma)

def prior_transform(theta):
    """
    A function defining the tranform between the parameterisation in the unit hypercube
    to the true parameters.

    Args:
        theta (tuple): a tuple containing the parameters.

    Returns:
        tuple: a new tuple or array with the transformed parameters.
    """

    mprime, cprime = theta # unpack the parameters (in their unit hypercube form)

    cmin = -10.  # lower bound on uniform prior on c
    cmax = 10.   # upper bound on uniform prior on c

    mmu = 0.     # mean of Gaussian prior on m
    msigma = 10. # standard deviation of Gaussian prior on m

    m = mmu + msigma*ndtri(mprime) # convert back to m
    c = cprime*(cmax-cmin) + cmin  # convert back to c

    return (m, c)

def loglikelihood_nestle(theta):
    """
    The log-likelihood function.
    """

    m, c = theta # unpack the parameters

    # normalisation
    norm = -0.5*M*LN2PI - M*LNSIGMA

    # chi-squared (data, sigma and x are global variables defined early on in this notebook)
    chisq = np.sum(((data-straight_line(x, m, c))/sigma)**2)

    return norm - 0.5*chisq

nlive = 1024     # number of live points
method = 'multi' # use MutliNest algorithm
ndims = 2        # two parameters
tol= 0.5         # the stopping criterion (this is the nestle default, so doesn't need to be set)

res = nestle.sample(loglikelihood_nestle, prior_transform, ndims, method=method, npoints=nlive, dlogz=tol)

logZnestle = res.logz                         # value of logZ
infogainnestle = res.h                        # value of the information gain in nats
logZerrnestle = np.sqrt(infogainnestle/nlive) # estimate of the statistcal uncertainty on logZ

# output marginal likelihood
print('Marginalised evidence is {} ± {}'.format(logZnestle, logZerrnestle))

# re-scale weights to have a maximum of one
nweights = res.weights/np.max(res.weights)

# get the probability of keeping a sample from the weights
keepidx = np.where(np.random.rand(len(nweights)) < nweights)[0]

# get the posterior samples
postsamples = res.samples[keepidx,:]

print('Number of posterior samples is {}'.format(postsamples.shape[0]))

# plot posterior samples (if corner.py is installed)
try:
    import matplotlib as mpl
    mpl.use("Agg") # force Matplotlib backend to Agg
    import corner # import corner.py
except ImportError:
    sys.exit(1)

fig = corner.corner(postsamples, labels=[r"$m$", r"$c$"], truths=[m, c])
fig.savefig('Nestle.png')


import os
import numpy as np
import math
from scipy import ndimage
import nibabel as nib
from PIL import Image
from scipy import signal

'''
Function to generate derivatives of Gaussian kernels, in either 1D, 2D, or 3D.
Source code in MATLAB obtained from Qiyuan Tian, Stanford University, September 2015
Edited to work in Python by Tony.
'''

def doggen(sigma):
    halfsize = np.ceil(3 * np.max(sigma))
    x = range(np.single(-halfsize), np.single(halfsize + 1));  # Python colon is not inclusive at end, while MATLAB is.
    dim = len(sigma);

    if dim == 1:
        X = np.array(x);  # Remember that, by default, numpy arrays are elementwise multiplicative
        k = -X * np.exp(-X**2/(2 * sigma**2));

    elif dim == 2:
        [X, Y] = np.meshgrid(x, x);
        k = -X * np.exp(-X**2/(2*sigma[0]^2) * np.exp(-Y**2))

    elif dim == 3:
        [X, Y, Z] = np.meshgrid(x, x, x);
        X = X.transpose(0, 2, 1);  # Obtained through vigorous testing (see below...)
        Y = Y.transpose(2, 0, 1);
        Z = Z.transpose(2, 1, 0);

        X = X.astype(float);
        Y = Y.astype(float);
        Z = Z.astype(float);
        k = -X * np.exp(np.divide(-np.power(X, 2), 2 * np.power(sigma[0], 2))) * np.exp(np.divide(-np.power(Y,2), 2 * np.power(sigma[1],2))) * np.exp(np.divide(-np.power(Z,2), 2 * np.power(sigma[2],2)))

    else:
        print 'Only supports up to 3 dimensions'

    return np.divide(k, np.sum(np.abs(k[:])));

'''
Function to generate Gaussian kernels, in 1D, 2D and 3D.
Source code in MATLAB obtained from Qiyuan Tian, Stanford University, September 2015
Edited to work in Python by Tony.
'''

def gaussgen(sigma):
    halfsize = np.ceil(3 * max(sigma));
    x = range(np.single(-halfsize), np.single(halfsize + 1));

    dim = len(sigma);

    if dim == 1:
        x = x.astype(float);
        k = np.exp(-x**2 / (2 * sigma^2));

    elif dim == 2:
        [X, Y] = np.meshgrid(x, x);
        X = X.astype(float);
        Y = Y.astype(float);

        k = np.exp(-X**2 / (2 * sigma[0]**2)) * np.exp(-Y**2 / (2 * sigma[1]**2));

    elif dim == 3:
        [X, Y, Z] = np.meshgrid(x, x, x);
        X = X.transpose(0, 2, 1);  # Obtained through vigorous testing (see below...)
        Y = Y.transpose(2, 0, 1);
        Z = Z.transpose(2, 1, 0);

        X = X.astype(float);  # WHY PYTHON?
        Y = Y.astype(float);
        Z = Z.astype(float);
        k = np.exp(-X**2 / (2 * sigma[0]**2)) * np.exp(-Y**2 / (2 * sigma[1]**2)) * np.exp(-Z**2 / (2 * sigma[2]**2));

    else:
        print 'Only supports up to dimension 3'

    return np.divide(k, np.sum(np.abs(k)));

'''
Function takes a single image (TIFF, or other also works), and returns
the single image as a numpy array.  Called by tiff_stack_to_array.
'''
def tiff_to_array(input_path):
    im = Image.open(input_path)
    # im.show()

    imarray = np.array(im)
    # print(imarray)

    return imarray

'''
Function takes input_path, which should should lead to a directory.
Loads all TIFFs in input_path, then generates numpy arrays from the
TIFF stack by calling tiff_to_array helper function.
'''
def tiff_stack_to_array(input_path):
    im_list = [];
    for filename in os.listdir(input_path):
        if filename.endswith(".tiff"):
            # print(os.path.join(directory, filename))
            im_arr = tiff_to_array(filename)
            im_list.append(im_arr)

    s = np.stack(im_list, axis=2)
    print s.shape
    return s

'''
Function takes a numpy array (from TIFF_stack_to_array) and saves output
FSL structure tensor as filename string. Allows inputting alternate dogsigmaArr,
gausigmaArr, angleArr, although defaults to currently to parameters from MATLAB script.
Also returns tensorfsl (the tensor fsl structure) image numpy array.

## Parameters (the script loops through all parameters and saves each result automatically)
# dogsigmaArr = [1]; Sigma values for derivative of gaussian filter, recommended value: 0.6 - 1.3 (based on actual data)
# gausigmaArr = [2.3]; Sigma values for gaussian filter, recommended value: 1.3 - 2.3 (based on actual data)
# angleArr = [25]; Angle thresholds for fiber tracking, recommended value: 20 - 30.

Follows code from MATLAB CAPTURE scripts, edited to work in Python by Tony.
'''
def generate_FSL_structure_tensor(img_data, filename, dogsigmaArr=[1], gausigmaArr=[2.3], angleArr=[25]):
    for jj in range(len(dogsigmaArr)):
        dogsigma = dogsigmaArr[jj];
        print "Start DoG Sigma on " + str(dogsigma);

        # Generate dog kernels
        dogkercc = doggen([dogsigma, dogsigma, dogsigma]);
        dogkercc = np.transpose(dogkercc, (0, 2, 1));  # annoying

        #print dogkercc.shape;
        #print dogkercc[:, :, 0];

        dogkerrr = np.transpose(dogkercc, (1, 0, 2));

        #print dogkerrr[:, :, 0];
        dogkerzz = np.transpose(dogkercc, (0, 2, 1));

        #print dogkerzz[:, :, 0];

        # Compute gradients
        grr = signal.convolve(img_data, dogkerrr, 'same');

        #print grr[:, :, 0];

        gcc = signal.convolve(img_data, dogkercc, 'same');

        #print gcc[:, :, 0];

        gzz = signal.convolve(img_data, dogkerzz, 'same');

        #print gzz[:, :, 0];

        # Compute gradient products
        gprrrr = np.multiply(grr, grr);

        #print gprrrr[:, :, 0];

        gprrcc = np.multiply(grr, gcc);

        #print gprrcc[:, :, 0];

        gprrzz = np.multiply(grr, gzz);

        #print gprrzz[:, :, 0]

        gpcccc = np.multiply(gcc, gcc);
        gpcczz = np.multiply(gcc, gzz);
        gpzzzz = np.multiply(gzz, gzz);

        # Compute gradient amplitudes
        # print ga.dtype;
        ga = np.sqrt(gprrrr + gpcccc + gpzzzz);

        #print ga[:, :, 0];

        print "GA SHAPE:"
        print ga.shape;

        # Convert numpy ndarray object to Nifti data type
        gradient_amplitudes_data = nib.Nifti1Image(ga, affine=np.eye(4));

        # Save gradient amplitudes image
        nib.save(gradient_amplitudes_data, 'gradient_amplitudes.nii');

        # Compute gradient vectors
        gv = np.concatenate((grr[..., np.newaxis], gcc[..., np.newaxis], gzz[..., np.newaxis]), axis = 3);

        #print gv[:, :, 0, 0];

        gv = np.divide(gv, np.tile(ga[..., None], [1, 1, 1, 3]));
        #print gv[:, :, 0, 1];

        print "GV SHAPE:"
        print gv.shape;

        # Convert numpy ndarray object to Nifti data type
        gradient_vectors_data = nib.Nifti1Image(gv, affine=np.eye(4));

        # Save gradient vectors
        nib.save(gradient_vectors_data, 'gradient_vectors.nii');

        # Compute structure tensor
        for kk in range(len(gausigmaArr)):
            gausigma = gausigmaArr[kk];
            print "Start Gauss Sigma with gausigma = " + str(gausigma);

            print "Generating Gaussian kernel..."
            gaussker = np.single(gaussgen([gausigma, gausigma, gausigma]));

            #print gaussker[:, :, 0];

            print "Blurring gradient products..."
            gprrrrgauss = signal.convolve(gprrrr, gaussker, "same");

            #print gprrrrgauss[:, :, 0];

            gprrccgauss = signal.convolve(gprrcc, gaussker, "same");

            #print gprrccgauss[:, :, 0];

            gprrzzgauss = signal.convolve(gprrzz, gaussker, "same");
            gpccccgauss = signal.convolve(gpcccc, gaussker, "same");
            gpcczzgauss = signal.convolve(gpcczz, gaussker, "same");
            gpzzzzgauss = signal.convolve(gpzzzz, gaussker, "same");

            print "Saving a copy for this Gaussian sigma..."
            tensorfsl = np.concatenate((gprrrrgauss[..., np.newaxis], gprrccgauss[..., np.newaxis], gprrzzgauss[..., np.newaxis], gpccccgauss[..., np.newaxis], gpcczzgauss[..., np.newaxis], gpzzzzgauss[..., np.newaxis]), axis = 3);

             # Convert numpy ndarray object to Nifti data type
            tensor_fsl_data = nib.Nifti1Image(tensorfsl, affine=np.eye(4));

            nib.save(tensor_fsl_data, str(filename) + "dogsigma_" + str(jj) + "gausigma_" + str(kk) + 'tensorfsl.nii');

    print 'Completed computing on ' + str(filename) + '!'
    return tensorfsl


import numpy as np
from scipy import stats

#################
# BSM模型相关
def get_option_d(s, k, t, r, sigma, q):
    d1 = (np.log(s/k) + (r - q + 0.5*sigma**2)*t)/(sigma*np.sqrt(t))
    d2 = (np.log(s/k) + (r - q - 0.5*sigma**2)*t)/(sigma*np.sqrt(t))
    return d1, d2

def get_option_greeks(cp, s, k, t, r, sigma, q):
    """
    计算期权希腊值
    :param cp:
    :param s:
    :param k:
    :param t:
    :param r:
    :param sigma:
    :param q:
    :return:
    """
    d1, d2 = get_option_d(s, k, t, r, sigma, q)
    delta = cp * stats.norm.cdf(cp * d1)
    gamma = stats.norm.pdf(d1) / (s * sigma * np.sqrt(t))
    vega = (s * stats.norm.pdf(d1) * np.sqrt(t))
    theta = (-1 * (s * stats.norm.pdf(d1) * sigma) / (2 * np.sqrt(t)) - cp * r * k * np.exp(-r * t) * stats.norm.cdf(cp * d2))
    return delta, gamma, vega, theta

def bsm_value(cp, s, k, t, r, sigma, q):
    d1, d2 = get_option_d(s, k, t, r, sigma, q)
    if cp > 0:
        value = (
            s*np.exp(-q*t)*stats.norm.cdf(d1) -
            k*np.exp(-r*t)*stats.norm.cdf(d2)
        )
    else:
        value = (
            k * np.exp(-r * t) * stats.norm.cdf(-d2) -
            s*np.exp(-q*t) * stats.norm.cdf(-d1)
        )
    return value
##############

# 二分法迭代计算隐波
def calculate_single_option_iv_by_bsm(
    cp, s, k, c, t, r, q,
    initial_iv=0.5, # 迭代起始值，如果上一个分钟有计算过隐波，这里把上一分钟的结果输入进来，有助于加快收敛
):

    c_est = 0  # 期权价格估计值
    top = 1  # 波动率上限
    floor = 0  # 波动率下限
    sigma = initial_iv  # 波动率初始值
    count = 0  # 计数器
    best_result = 0
    error = abs(c - c_est)
    last_error = error
    while error > 0.0001:
        c_est = bsm_value(cp, s, k, t, r, sigma, q)
        error = abs(c - c_est)
        if error < last_error:
            best_result = sigma

        # 根据价格判断波动率是被低估还是高估，并对波动率做修正
        count += 1
        if count > 100:  # 时间价值为0的期权是算不出隐含波动率的，因此迭代到一定次数就不再迭代了
            sigma = 0
            break

        if c - c_est > 0:  # f(x)>0
            floor = sigma
            sigma = (sigma + top)/2
        else:
            top = sigma
            sigma = (sigma + floor)/2
    return best_result

# 计算隐含分红率
# 我们目前不计算这个
def calculate_dividend_rate(
    underlying_price,    # 当前标的价格
    call_price,
    put_price,
    rest_days,           # 剩余时间
    exercise_price,        # 行权价
    free_rate,
    ):
    c = call_price
    c_p = put_price
    r = free_rate
    t = rest_days / 360
    k = exercise_price
    s = underlying_price
    q = -np.log((c+k*np.exp(-r*t)-c_p)/(s))/t
    return q

# 计算隐波和Greeks
def calculate_single_option_greeks(
    underlying_price,    # 当前标的价格
    option_price,        # 期权价格
    call_put,            # 期权方向, CALL=1 PUT=-1
    rest_days,           # 剩余时间，按自然日计算，也可以用小数来表示不完整的日子
    exercise_price,      # 行权价
    free_rate = 0.03,    # 无风险利率，如果没有数据，指定为3%
    dividend_rate = 0,   # 分红率，目前指定为0
    initial_iv = 0.5,    # 初始迭代的隐波
):
    cp = call_put
    s = underlying_price
    r = free_rate
    k = exercise_price
    t = rest_days / 360
    c = option_price
    q = dividend_rate
    sigma = calculate_single_option_iv_by_bsm(cp, s, k, c, t, r, q, initial_iv)
    delta, gamma, vega, theta = get_option_greeks(cp, s, k, t, r, sigma, q)
    # sigma就是iv
    return sigma, delta, gamma, vega, theta


#! /usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import division
import math
import numpy as np

def homogeneity(m):
    # m sense distribution matrix.
    # rows are clusters, columns are gold stardard.
    nrow, ncol = m.shape
    N = m.sum()

    # conditional part
    conditional_entropy = .0
    for i in xrange(nrow):
        for j in xrange(ncol):
            val = m[i,j]
            if val != 0:
                conditional_entropy -= (val / N) * math.log(val / m[i].sum())

    # total part
    total_entropy = .0
    for i in xrange(ncol):
        val = m.T[i].sum()
        if val != 0:
            total_entropy -= (val / N) * math.log(val / N)

    return 1 - (conditional_entropy / total_entropy)

def completeness(m):
    return homogeneity(m.T)

def vmeasure(m):
    h = homogeneity(m)
    c = completeness(m)
    return (2 * h * c) / (h + c)

def example():
    #m = np.matrix([[10, 10, 15],[20, 50, 0], [1, 10, 60], [5, 0, 0]])
    m = np.matrix([[20, 5, 3],[5, 50, 0], [1, 7, 60], [5, 0, 0]])
    print "Contingency Matrix:\n {}".format(m)
    print "-" * 50
    print "Homogeneity={:.3f}, Completeness={:.3f}, and V-Measure={:.3f}".format(homogeneity(m), completeness(m), vmeasure(m))

if __name__ == '__main__':
    example()


'''
620031587
Net-Centric Computing Assignment
Part A - RSA Encryption

Adapted by Donn Morrison for Python for Programmers IFUD1056-IINI4014
March 2018
donn.morrison@ntnu.no
'''

import random
import numpy as np
from random import randint
import sys

'''
Euclid's algorithm for determining the greatest common divisor
Use iteration to make it faster for larger integers
'''
def gcd(a, b):
    while b != 0:
        a, b = b, a % b
    return a

'''
Euclid's extended algorithm for finding the multiplicative inverse of two numbers
'''
def multiplicative_inverse(a, b):
    """Returns a tuple (r, i, j) such that r = gcd(a, b) = ia + jb
    """
    # r = gcd(a,b) i = multiplicitive inverse of a mod b
    #      or      j = multiplicitive inverse of b mod a
    # Neg return values for i or j are made positive mod b or a respectively
    # Iterateive Version is faster and uses much less stack space
    x = 0
    y = 1
    lx = 1
    ly = 0
    oa = a  # Remember original a/b to remove
    ob = b  # negative values from return results
    while b != 0:
        q = a // b
        (a, b) = (b, a % b)
        (x, lx) = ((lx - (q * x)), x)
        (y, ly) = ((ly - (q * y)), y)
    if lx < 0:
        lx += ob  # If neg wrap modulo orignal b
    if ly < 0:
        ly += oa  # If neg wrap modulo orignal a
    # return a , lx, ly  # Return only positive values
    return lx

def generate_keypair():
    # Generate primes
    primes = PrimeGen(100)
    p = q = 2
    # Select two primes
    while p == q:
        q = primes[randint(0, len(primes)-1)]
        p = primes[randint(0, len(primes)-1)]
    #n = pq
    n = p * q

    #Phi is the totient of n
    phi = (p-1) * (q-1)

    #Choose an integer e such that e and phi(n) are coprime
    e = random.randrange(1, phi)

    #Use Euclid's Algorithm to verify that e and phi(n) are comprime
    g = gcd(e, phi)
    while g != 1:
        e = random.randrange(1, phi)
        g = gcd(e, phi)

    #Use Extended Euclid's Algorithm to generate the private key
    d = multiplicative_inverse(e, phi)

    #Return public and private keypair
    #Public key is (e, n) and private key is (d, n)
    return ((e, n), (d, n))

def encrypt(pk, plaintext):
    #Unpack the key into it's components
    key, n = pk
    #Convert each letter in the plaintext to numbers based on the character using a^b mod m
    cipher = [pow(ord(char), key) % n for char in plaintext]
    #Return the array of bytes
    return cipher

def decrypt(pk, ciphertext):
    #Unpack the key into its components
    key, n = pk
    #Generate the plaintext based on the ciphertext and key using a^b mod m
    plain = [chr(pow(char, key) % n) for char in ciphertext]
    #Return the array of bytes as a string
    return ''.join(plain)

def PrimeGen(n=10000):
    primes = []
    chk = 2
    while len(primes) < n:
        ptest = [chk for i in range(2,chk) if chk%i == 0]
        primes += [] if ptest else [chk]
        chk += 1
    return primes

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print("usage: python rsa.py \"<message>\"")
        sys.exit(1)

    message = sys.argv[1]

    public, private = generate_keypair()

    print("Public key: ", public)
    print("Private key: ", private)

    encrypted_msg = encrypt(public, message)
    print("Your encrypted message is: ")
    print(''.join(map(lambda x: str(x)+ " ", encrypted_msg)))
    print("Your message is:")
    print(decrypt(private, encrypted_msg))


# All basic usage of numpy
import numpy as np
# prints the version of numpy
print(np.__version__)

# Whether element is a numpy array
el = np.array(list())
# Below command resolves to true
if isinstance(el, np.ndarray):
    print("Element is a numpy array.")

array1 = np.array([1, 2, 3, 4])
# It performs element wise operation on whole collection
sum_of_array = array1 + array1
# Returns [2, 4, 6, 8]
print(sum_of_array)

# We can access individual numpy array elements, use square brackets
# Prints 2. i.e. value of array with index 1
print(array1[1])
# Returns array of boolean values which are greater than 2
array2 = array1 > 2

# Prints [False, False, True, True]
print(array2)
# We can print selective items of array by passing array of boolean values
array3 = array1[array2]
print(array3)

two_d_list = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
two_d_array = np.array(two_d_list)
# Shape is 3 rows, 4 columns
print(two_d_array.shape)
# To get all the 3rd columns of
print(two_d_array[:,2])
# To get values [6, 7]
print(two_d_array[1,1:3])

# Reshape
org_array = [1, 2, 3, 4, 5, 6, 7, 8]
conv_np_array = np.array(org_array)
# Returns (8,) as shape
print('Shape ', conv_np_array.shape)
# Converting into shape of (2,4)
conv_2_4_array = np.reshape(conv_np_array, [2, 4])
# Returns (2, 4) as shape
print('Shape ', conv_2_4_array.shape)
# Prints [[1 2 3 4], [5 6 7 8]]
print('Element of conv_2_4_array', conv_2_4_array)
# Converting into shape of (2, 2, 2)
conv_2_2_2_array = np.reshape(conv_np_array, [2, 2, 2])
# Returns (2, 2, 2) as shape
print('Shape ', conv_2_2_2_array.shape)
# Prints
"""
[[[1 2]
  [3 4]]

 [[5 6]
  [7 8]]]
"""
print('Element of conv_2_2_2_array', conv_2_2_2_array)

# The shape will convert [row1, row2, ....rown-1, col]
# If we want to have single row with elements just 1 or -1 to the top of the shape list.

# Converting into shape of (1, 2, 2, 2)
conv_1_2_2_2_array = np.reshape(conv_np_array, [-1, 2, 2, 2])
# Returns (1, 2, 2, 2) as shape, even if we gave first dimension of shape as -1
print('Shape ', conv_1_2_2_2_array.shape)
"""
[[[[1 2]
   [3 4]]

  [[5 6]
   [7 8]]]]
"""
print('Element of conv_1_2_2_2_array', conv_1_2_2_2_array)

# Perform an operation on each element of array
"""
import math
import numpy as np

def calcSigmoid(z):
    return 1 / ( 1 + math.exp(-z) )

def sigm(X):
    sm = np.vectorize(calcSigmoid)
    return sm(X)

sigm([1,0])
array([0.73105858, 0.5       ])
"""

# Adding columns

first = np.array([[1,3], [2,4]])
'''
array([[1, 2],
       [3, 4]])
'''

sec = np.array([[5],[6]])
'''
array([[5],
       [6]])
'''

# on column concat
third = np.concatenate((first, sec), axis=1)
'''
array([[1, 2, 5],
       [3, 4, 6]])
'''

# Get the scalar value of (1,1) matrix
in_vec = np.array([[5]])
'''
array([[5]])
'''
in_vec.shape # (1, 1)
in_scalar = np.asscalar(in_vec) # 5

# identity matrix
eye = np.eye(3)
'''
array([[1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]])
'''

# Reading csv file
'''
from numpy import genfromtxt
my_data = genfromtxt('my_file.csv', delimiter=',')
'''

'''
X = np.arange(1,7)
#array([1, 2, 3, 4, 5, 6])
X = X.reshape(3, 2)

array([[1, 2],
       [3, 4],
       [5, 6]])

If we see the elements arranged in filling first rows by rows
If we want to fill by columns first, i.e

array([[1, 4],
       [2, 5],
       [3, 6]])

We can do in two ways

1. First take the order in reverse then transpose

X = np.arange(1,7).reshape(2,3) # Observe 2,3 rather thn 3,2
X = X.T

2. Chaining with swapaxes

X = np.arange(1,7).reshape(2,3).swapaxes(0,1)
'''

'''
Indexing from particular index to end
mm = np.arange(1,5).reshape(2,2)
mm
array([[1, 2],
       [3, 4]])
nn = mm.flatten().reshape((4,1))
array([[1],
       [2],
       [3],
       [4]])
# From index 2 to end
nn[2:,]
array([[3],
       [4]])


import torch
import numpy as np

def interval_score_loss(predictions, real, alpha=0.28):
    """
    Taken from: https://stats.stackexchange.com/questions/194660/forecast-accuracy-metric-that-involves-prediction-intervals
    Need to predict lower and upper bounds of interval, use target to assess error.

    :param lower: Lower bound predictions
    :param upper: Upper bound predictions
    :param real: Target
    :param alpha: Alpha in metric in
    :return: Average of interval score loss
    """
    lower = predictions[:, 0]
    upper = predictions[:, 1]

    real_lower = 2 * torch.abs(real - lower) / alpha
    upper_real = 2 * torch.abs(upper - real) / alpha
    upper_lower = torch.abs(upper - lower)

    real_lower[real > lower] = 0
    upper_real[real < upper] = 0

    return torch.sum(real_lower + upper_real + upper_lower) / len(real)

"""Implementation of the quantile loss function"""

from typing import Sequence
import numpy as np

def quantile_loss(
    predictions: Sequence[float], targets: Sequence[float], quantile: float
) -> float:
    """Quantile loss function.

    Args:
        predictions (sequence of floats):
            Model predictions, of shape [n_samples,].
        targets (sequence of floats):
            Target values, of shape [n_samples,].
        quantile (float):
            The quantile we are seeking. Must be between 0 and 1.

    Returns:
        float: The quantile loss.
    """
    # Convert inputs to NumPy arrays
    target_arr = np.asarray(targets)
    prediction_arr = np.asarray(predictions)

    # Compute the residuals
    res = target_arr - prediction_arr

    # Compute the mean quantile loss
    loss = np.mean(
        np.maximum(res, np.zeros_like(res)) * quantile
        + np.maximum(-res, np.zeros_like(res)) * (1 - quantile)
    )

    # Ensure that loss is of type float and return it
    return float(loss)

def smooth_quantile_loss(
    predictions: Sequence[float],
    targets: Sequence[float],
    quantile: float,
    alpha: float = 0.4,
) -> float:
    """The smooth quantile loss function from [1].

    Args:
        predictions (sequence of floats):
            Model predictions, of shape [n_samples,].
        targets (sequence of floats):
            Target values, of shape [n_samples,].
        quantile (float):
            The quantile we are seeking. Must be between 0 and 1.
        alpha (float, optional):
            Smoothing parameter. Defaults to 0.4.

    Returns:
        float: The smooth quantile loss.

    Sources:
        [1]: Songfeng Zheng (2011). Gradient Descent Algorithms for
             Quantile Regression With Smooth Approximation. International
             Journal of Machine Learning and Cybernetics.
    """
    # Convert inputs to NumPy arrays
    target_arr = np.asarray(targets)
    prediction_arr = np.asarray(predictions)

    # Compute the residuals
    residuals = target_arr - prediction_arr

    # Compute the smoothened mean quantile loss
    loss = quantile * residuals + alpha * np.log(1 + np.exp(-residuals / alpha))

    # Ensure that loss is of type float and return it
    return float(loss.mean())


# This script defines and tests a function for generating a sequence of normally
# distributed random numbers using the Box-Muller method.
#
# Ref. 1: Numerical Recipes in C 2nd ed.
# Ref. 2: Statistics for Engineers and Scientists 2nd ed. Navidi
#

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

def gaussian(mu=0, sigma=1):
    """Generate a random number from a Gaussian distribution.

    Adapted from ref 1.
    """

    if "iset" not in gaussian.__dict__:
        gaussian.iset = 0
    if "gset" not in gaussian.__dict__:
        gaussian.gset = 0

    if gaussian.iset == 0:
        v1, v2, rsq = 0.0, 0.0, 0.0
        while rsq >= 1.0 or rsq == 0.0:
            v1 = 2.0*random.random() - 1
            v2 = 2.0*random.random() - 1
            rsq = v1*v1 + v2*v2
        fac = np.sqrt(-2.0*np.log(rsq)/rsq)
        gaussian.iset = 1
        gaussian.gset = v1*fac
        xi = v2*fac
    else:
        gaussian.iset = 0
        xi = gaussian.gset

    return mu + xi*sigma  # See ref 2 pg. 242

def gaussian_distribution(mu=0, sigma=1, n=100):
    """Create a list of random numbers from a Gaussian distribution."""
    return [gaussian(mu, sigma) for _ in range(n)]

if __name__ == "__main__":

    # Test the distribution
    mean = 0
    stddev = 1
    nsamps = 100000
    nbins = int((2*nsamps)**(1./3))

    # Plot theoretical distribution
    pdf_range = np.arange(-6, 6, 0.001)
    pdf = norm.pdf(pdf_range, mean, stddev)

    # Create the histogram of generated values
    x = gaussian_distribution(mu=mean, sigma=stddev, n=nsamps)
    plt.hist(x, bins=nbins, normed=True)
    plt.plot(pdf_range, pdf, linewidth=2, color='r')
    plt.grid(True)
    plt.ylabel("PDF")
    plt.xlabel("Random Values")
    plt.show()


import matplotlib.pyplot as plt
import numpy as np

def rect(x):
    r"""
    Rectangle function:
        rect(x) = {1, if |x|<= 0.5; 0, otherwise}
    """
    return np.where(np.abs(x) >= 0.5, 0, 1)

def chirp(t, T, Kr):
    """
    Create a chirp signal :
        S_{tx}(t) = rect(t/T) * exp(1j*pi*Kr*t^2)
    """
    return rect(t / T) * np.exp(1j * np.pi * Kr * t ** 2)

Ns = 1000
t = np.linspace(-1, 1, Ns)
yrect = rect(t)

plt.figure()
plt.plot(t, yrect, '-r', linewidth=2)
plt.axis([-1, 1, -0.1, 1.1])
plt.grid()
plt.xlabel('Time/s')
plt.ylabel('Amplitude')
plt.title('rect')
plt.show()

Kr1 = 5
Kr2 = 20
T1 = 2.0
T2 = 4.0
t = np.linspace(-3, 3, Ns)
ychirp1 = chirp(t, T1, Kr=Kr1)
ychirp2 = chirp(t, T2, Kr=Kr1)

ychirp3 = chirp(t, T1, Kr=Kr2)
ychirp4 = chirp(t, T2, Kr=Kr2)

plt.figure()
plt.subplot(221)
plt.grid()
plt.plot(t, ychirp1, '-r')
plt.xlabel('Time/s')
plt.ylabel('Amplitude')
plt.title("T=" + str(T1) + "s, K=" + str(Kr1) + "Hz/s")
plt.subplot(222)
plt.grid()
plt.plot(t, ychirp2, '-b')
plt.xlabel('Time/s')
plt.ylabel('Amplitude')
plt.title("T=" + str(T2) + "s, K=" + str(Kr1) + "Hz/s")
plt.subplot(223)
plt.grid()
plt.plot(t, ychirp3, '-r')
plt.xlabel('Time/s')
plt.ylabel('Amplitude')
plt.title("T=" + str(T1) + "s, K=" + str(Kr2) + "Hz/s")
plt.subplot(224)
plt.grid()
plt.plot(t, ychirp4, '-b')
plt.xlabel('Time/s')
plt.ylabel('Amplitude')
plt.title("T=" + str(T2) + "s, K=" + str(Kr2) + "Hz/s")
plt.tight_layout()
plt.show()

# ==================compute spectral

ychirp1_fft = np.fft.fft(ychirp1)
ychirp2_fft = np.fft.fft(ychirp2)
ychirp3_fft = np.fft.fft(ychirp3)
ychirp4_fft = np.fft.fft(ychirp4)

ychirp1_fft = np.fft.fftshift(ychirp1_fft)
ychirp2_fft = np.fft.fftshift(ychirp2_fft)
ychirp3_fft = np.fft.fftshift(ychirp3_fft)
ychirp4_fft = np.fft.fftshift(ychirp4_fft)

f = np.fft.fftfreq(t.shape[-1])
# print(f)
f = np.fft.fftshift(f)
# print(f)

plt.figure()
plt.subplot(221)
plt.grid()
plt.plot(f, np.abs(ychirp1_fft), '-r')
plt.xlabel('Frequency/Hz')
plt.ylabel('Amplitude')
plt.title("T=" + str(T1) + "s, K=" + str(Kr1) + "Hz/s")
plt.subplot(222)
plt.grid()
plt.plot(f, np.abs(ychirp2_fft), '-b')
plt.xlabel('Frequency/Hz')
plt.ylabel('Amplitude')
plt.title("T=" + str(T2) + "s, K=" + str(Kr1) + "Hz/s")
plt.subplot(223)
plt.grid()
plt.plot(f, np.abs(ychirp3_fft), '-r')
plt.xlabel('Frequency/Hz')
plt.ylabel('Amplitude')
plt.title("T=" + str(T1) + "s, K=" + str(Kr2) + "Hz/s")
plt.subplot(224)
plt.grid()
plt.plot(f, np.abs(ychirp4_fft), '-b')
plt.xlabel('Frequency/Hz')
plt.ylabel('Amplitude')
plt.title("T=" + str(T2) + "s, K=" + str(Kr2) + "Hz/s")
plt.tight_layout()
plt.show()


# Polynomial Regression

# Importing the libraries
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

# Importing the dataset
dataset = pd.read_csv('Position_Salaries.csv')
X = dataset.iloc[:, 1:2].values
y = dataset.iloc[:, 2].values

# Splitting the dataset into the Training set and Test set
"""from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 0)"""

# Feature Scaling
"""from sklearn.preprocessing import StandardScaler
sc_X = StandardScaler()
X_train = sc_X.fit_transform(X_train)
X_test = sc_X.transform(X_test)"""

# Fitting Linear Regression to the dataset
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(X, y)

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

# Visualising the Linear Regression results
plt.scatter(X, y, color = 'red')
plt.plot(X, lin_reg.predict(X), color = 'blue')
plt.title('Truth or Bluff (Linear Regression)')
plt.xlabel('Position level')
plt.ylabel('Salary')
plt.show()

# Visualising the Polynomial Regression results
plt.scatter(X, y, color = 'red')
plt.plot(X, lin_reg_2.predict(poly_reg.fit_transform(X)), color = 'blue')
plt.title('Truth or Bluff (Polynomial Regression)')
plt.xlabel('Position level')
plt.ylabel('Salary')
plt.show()

# Visualising the Polynomial Regression results (for higher resolution and smoother curve)
"""
plt.scatter(X, y, color = 'red')
plt.plot(X_grid, lin_reg_2.predict(poly_reg.fit_transform(X_grid)), color = 'blue')
plt.title('Truth or Bluff (Polynomial Regression)')
plt.xlabel('Position level')
plt.ylabel('Salary')
plt.show()

# Predicting a new result with Linear Regression
lin_reg.predict([[6.5]])

# Predicting a new result with Polynomial Regression
lin_reg_2.predict(poly_reg.fit_transform([[6.5]]))
"""


import numpy as np
from sympy import Matrix

class SisLin:

    def __init__(self, A, B = [0, 0, 0]):
        self.A = np.array(A, dtype = float)
        self.B = np.array(B, dtype = float)
        self.L = None
        self.U = None

    def verifyLU(self):
        m, n = self.A.shape
        for i in range(1, m+1):
            print(f'Minimo quadrado da matriz A_{i}{i}: ', np.round(np.linalg.det(self.A[:i,:i]), 4))

    def LU(self):
        U = np.copy(self.A)
        L = np.zeros(np.shape(self.A))
        pivot = []
        m,n = U.shape
        for i in range(n-1):
            for j in range(i+1, n):
                if U[i,i] == 0:
                    print('Existe elemento nulo na diagonal principal!')
                    break
                ml = U[j,i]/U[i,i]
                U[j,:] = U[j,:] - ml*U[i,:]
                pivot.append(ml)

        pivot = np.array(pivot)

        for i in range(n-1):
            for j in range(i+1, n):
                L[j,i] = pivot[0]
                pivot = np.delete(pivot, 0)

        for i in range(n):
            for j in range(n):
                if i == j:
                    L[i,j] = 1
        Ldf = Matrix(L)
        Udf = Matrix(U)
        self.L = L
        self.U = U

        print('*' * 14,'Matriz L', '*' * 14 ,'\n')

        display(Ldf)

        print('*' * 14,'Matriz U', '*' * 14 ,'\n')

        display(Udf)

    def goit(self):
        L = np.copy(self.L)
        B = np.copy(self.B).T
        m, n = self.L.shape
        k = 1
        for i in range(m - 1):
            for j in range(k, n):
                c = L[i][i] / L[j][i]
                L[j] = (L[i] - c * L[j])
                B[j] = (B[i] - c * B[j])
            k += 1

        Y = self.solution(L, B).T
        print('Vetor Y:', Y[0])
        X = self.solution(self.U, Y)
        print('Vetor X:', X.T[0])

        #return X

    def solution(self, l , y):
        L = np.copy(l)
        Y = np.copy(y).T
        m, n = L.shape
        X = np.zeros((1, len(Y))).T
        k = n - 2
        X[len(Y) - 1] = Y[-1] / L[-1][-1]

        for i in range(m-2, -1, -1):
            X[i] = ((Y[i:] - L[i][i+1:].dot(X[i+1:])) / L[i][i])[0]

        return X

    def Jacobi_Richardson(self, e = 0.1, c = 4, X0 = [0, 0, 0], lim = 50, erro = 0):

        error = lambda x1, x2: np.linalg.norm(x1 - x2)
        if erro == 1:
            error = lambda x1, x2: np.max(np.abs(x1- x2))/np.max(np.abs(x1))

        a = np.copy(self.A)
        b = np.copy(self.B).T
        X = np.zeros(b.shape)
        X_aux = np.array(X0, dtype = float)
        m, n = a.shape
        N = 0
        k = 1

        for j in range(m):
            X[j] = b[j] / a[j,j]
            X[j] -= np.sum(np.delete(X_aux, j) * (np.array([np.delete(a[j], j)]) / (a[j,j])))
        print(f'x_{k}: ', np.round(X, c))

        while error(X, X_aux) > e and N < lim:
            X_aux = np.copy(X)
            for j in range(m):
                X[j] = b[j] / a[j,j]
                X[j] -= np.sum(np.delete(X_aux, j) * (np.array([np.delete(a[j], j)]) / (a[j,j])))
            k += 1
            N += 1
            print(f'x_{k}: ', np.round(X, c))

    def verify_JR(self):
        m, n = self.A.shape
        B = []
        for i in range(m):
            B_aux = np.sum(np.absolute(np.delete(self.A[i], i) / self.A[i,i]))
            B.append(B_aux)
            if B_aux >= 1:
                print('Não há garantia de convergencia pelo método de Jacobi-'
                      'Richardson.')
                return print(f'B_{i+1} = {B[i]:.4f}')

        print('Há garantia de convergencia pelo método de Jacobi-Richardson')
        return print('B = ', B)

    def verify_GS(self):
        m, n = self.A.shape
        B = np.ones((1, m))
        for i in range(m):
            B[0][i] = np.sum(np.absolute(np.delete(self.A[i], i) / self.A[i,i] * np.delete(B, i)))
            if B[0][i] >= 1:
                print('Não há garantia de convergencia pelo método de Gauss-'
                      'Seidel.')
                return print(f'B_{i+1} = {B[0][i]:.4f}')
        print('Há garantia de convergencia pelo método de Gauss-Seidel')
        return print(f'B = ', B[0])

    def Gauss_Seidel(self, e = 0.1, c = 4, X0 = [0, 0, 0], lim = 50, erro=0):

        error = lambda x1, x2: np.linalg.norm(x1 - x2)
        if erro == 1:
            error = lambda x1, x2: np.max(np.abs(x1- x2))/np.max(np.abs(x1))

        a = np.copy(self.A)
        b = np.copy(self.B).T
        X = np.array(X0, dtype = float)
        X_ant = np.copy(X)
        m, n = a.shape
        k = 1
        N = 0

        for j in range(m):
            X[j] = b[j] / a[j,j]
            X[j] -= np.sum(np.delete(X, j) * (np.array([np.delete(a[j], j)]) / (a[j,j])))
        print(f'x_{k}: ', np.round(X, c))

        while error(X, X_ant) > e and N < lim:
            X_ant = np.copy(X)
            for j in range(m):
                X[j] = b[j] / a[j,j]
                X[j] -= np.sum(np.delete(X, j) * (np.array([np.delete(a[j], j)]) / (a[j,j])))
            k += 1
            N += 1
            print(f'x_{k}: ', np.round(X, c))


##############################################################################
### Python 2nd Order ODE solver, using the Runge-Kutta 4th order method
### Solves ordinary differential equations of the form
###
###			(d^2)x/(dt^2) = f(t, x, v)
###
### As usual, the 2nd order ODE is split into two coupled 1st order ODEs,
### which are solved with the Runge-Kutta function:
###
###                                     | x'(t) = v(t)
###              x''(t) = f(t, x, v) -> |
###                                     | v'(t) = f(t, x, v)
###
### User must input the following parameters:
### 1. Via command line arguments: time step, total time, output interval.
### 2. The right-hand side of the differential equation
###    inside the rhs function, in the rungekutta2.py file.
### 3. Initial conditions t_0, x_0, v_0 (example: x(t_0) = x_0, v(t_0) = v_0),
###    at the beginning of the code, right below the imports.
###
### The force can be specified inside the "force" function, in rungekutta2.py.
###
### Example: python ODE2.py 0.1 100 10
### This will solve the ODE with a timestep of dt = 0.1, for 100 time units,
### writing output every 10 time steps.
###
### Output: File "RK4_2_output.txt", also plots results.
###
### Author: Nikos Tryfonidis, November 2015
### The MIT License (MIT)
### Copyright (c) 2015 Nikos Tryfonidis
### See LICENSE.txt
##############################################################################

# Import necessary packages
import numpy as np
import sys

# Import functions
from rungekutta2 import RK4_2nd
from plot1D import plot1D

##### Set Initial Conditions Here: #####
t_0 = 0.0
x_0 = 0.0
v_0 = 2.0
########################################

#################################################################################
### Command line arguments:
#################################################################################

# Check number of command line arguments
if len(sys.argv) != 4:
	print "Usage: python ODE2.py <timestep> <total time> <output interval>"
	print "Please run again following the command line input format above."
	print "Exiting..."
	sys.exit(1)

# Get command line arguments
h = float(sys.argv[1])
totalTime = float(sys.argv[2])
interval = int(sys.argv[3])

#################################################################################

# Calculate number of time steps
totalSteps = totalTime/h
# Find size of output
size = int(totalSteps/interval) + 1

# Print a summary of parameters
print "Running Runge-Kutta with the following parameters:"
print "Time step: %f\tTotal time: %f\t" %(h, totalTime)
print "Output interval: %d" %interval

# Allocate variable arrays and apply given initial conditions
t = np.linspace(t_0, t_0 + totalTime, size)

x = np.zeros(size)
x[0] = x_0

v = np.zeros(size)
v[0] = v_0

# Call Runge Kutta
tOld = t[0]
xOld = x[0]
vOld = v[0]
for i in xrange(1, size):
    for j in xrange(0, interval):
        tOld += h    # Unnecessary, but to keep generality
        xOld, vOld = RK4_2nd(tOld, xOld, vOld, h)
    x[i] = xOld
    v[i] = vOld

# Save t, x and v into output file (each array is a column)
np.savetxt('RK4_2_output.txt', np.c_[t, x, v], fmt='%.10f')

# Plot t, x (plot1d function in plot1D.py)
plot1D(size, "RK4_2_output.txt")


"""Utils related to GSP."""

import numpy as np

def eigendecompose(S):
    """Eigendecompose the input matrix."""
    eigvals, V = np.linalg.eig(S)
    return eigvals, V

def gft(S, x, decompose_shift=True):
    """Perform the inverse GFT.

    Parameters
    ----------
    S : np.ndarray, shape=(N, N)
        Graph shift operator or GFT matrix.
    x : np.ndarray, shape=(N,) or (N, 1)
        Graph signal spectrum.
    decompose_shift : bool, default=True

    Example
    -------
    >>> from gspx.utils.gsp import gft, igft
    >>> from gspx.utils.graph import make_sensor
    >>> A, coords = make_sensor(N=10, seed=2)
    >>> s = np.arange(10)
    >>> s_ = gft(A, igft(A, s))
    >>> np.round(np.sum(s_ - s), decimals=10)
    0.0

    """
    if decompose_shift:
        _, U = eigendecompose(S)
        Uinv = np.linalg.inv(U)
    else:
        Uinv = S.copy()

    not_column = False
    if len(x.shape) == 1:
        x_column = x[:, np.newaxis]
        not_column = True
    else:
        x_column = x.copy()

    out = Uinv @ x_column

    if not_column:
        return out.ravel()
    else:
        return out

def igft(S, x, decompose_shift=True):
    """Perform the inverse GFT.

    Parameters
    ----------
    S : np.ndarray, shape=(N, N)
        Graph shift operator or IGFT matrix.
    x : np.ndarray, shape=(N,) or (N, 1)
        Graph signal spectrum.
    decompose_shift : bool, default=True

    Example
    -------
    >>> from gspx.utils.gsp import gft, igft
    >>> from gspx.utils.graph import make_sensor
    >>> A, coords = make_sensor(N=10, seed=2)
    >>> s = np.arange(10)
    >>> s_ = gft(A, igft(A, s))
    >>> np.round(np.sum(s_ - s), decimals=10)
    0.0

    """
    if decompose_shift:
        _, U = eigendecompose(S)
    else:
        U = S.copy()
    return gft(U, x, decompose_shift=False)


from PIL import Image
import numba
import numpy as np

MAX_ITER = 1000
MAX_RADIUS = 2
SIZE_Image = 500

START_POINT = (0, 0)
ZOOM = 1
RANGE = 1 / ZOOM

C = complex(-0.761576,-0.0847596)
# C = complex(-1, 0)
START_X = START_POINT[0] - RANGE
END_X = START_POINT[0] + RANGE
STEP_X = (END_X - START_X) / SIZE_Image

START_Y = START_POINT[1] + RANGE
END_Y = START_POINT[1] - RANGE
STEP_Y = (END_Y - START_Y) / SIZE_Image

BLACK = (0,0,0)
WHITE = (255,255,255)

@numba.jit(nopython=True)
def calc(z, c):
    return z ** 2 + c

@numba.jit(nopython=True)
def explode(z):
    for i in range(MAX_ITER):
        z = calc(z, C)
        if abs(z) > MAX_RADIUS:
            return i
    return i

@numba.jit(nopython=True)
def loop(arr):
    j = START_Y
    for y in range(SIZE_Image):
        i = START_X
        for x in range(SIZE_Image):
            z = complex(i, j)
            e = explode(z)
            if e < MAX_ITER:
                color = round(e*255)/MAX_ITER
                arr[y, x]  = (color, color, color)
            else:
                arr[y, x] = (0, 0, 0)
            i += STEP_X
        j += STEP_Y

def run():
    arr = np.empty([SIZE_Image,SIZE_Image, 3])
    loop(arr)
    img = Image.fromarray(np.uint8(arr))
    img.save("result_julia.jpg", "JPEG")

if __name__ == "__main__":
    run()


#! /usr/bin/env python
# -*- coding: utf-8 -*-
# vim:fenc=utf-8
#
# Copyright © 2019 Jinsong Liu <jinsongliu@utexas.edu>
#
# Distributed under terms of the GNU-License license.

"""

"""
import numpy as np
from . import spectrums
import inspect

class PowerSpectrum(object):
    """
    Reference: https://www.imft.fr/IMG/pdf/psdtheory.pdf  "The Power Spectral Density and the Autocorrelation"
    Definitions:
    - ACF: for a real, stationary signal x(t), its ACF R is defined as: R(tau) = E[x(t)x(t+tau)]
    - PSD: Fourier transform of R(tau) is called the Power spectral denstity (PSD), Sx/pxx
    => Sx(w_rad) = \int R(tau) exp(-2*pi*j*w_rad*tau) d tau

    Properties:
    1. since Sx is an average of the magnitude squared of the Fourier transform
        Sx(w_rad) = lim T->inf 1/T E[|X_T(w_rad)|^2],
        where X_T(w_rad) is the Fourier transform of x(t), X_T(w_rad) = int x(t) exp(-2*pi*j*w_rad*t) dt

    2. Sx(-w_rad) = Sx(w_rad)
    3. Dual relationship: R(tau) = int Sx(w_rad) exp(2*pi*j*w_rad*tau) d tau
    4. At tau=0, R(0) = E[x(t)^2] = int Sx(w_rad) dw, "variance" = Area under curve
    5. Parseval's Identity:
        Assume x(t) ergodic in the autocorrelation, i.e. R(tau) = E[x(t) x(t+tau)] = \lim_{T->inf} 1/T \int_{-T/2} ^{T/2} x(t) x(t+tau) dt
        for any signal x(t):
        lim_{T->inf} 1/T \int _{-T/2} ^{T/2} x(t)^2 dt = \int_{-inf} ^{inf} Sx(w_rad) dw

    """

    def __init__(self, name=None, *args):
        self.name   = name
        self.sides  = 'single'
        self.args   = args
        if self.name is None:
            self.ndim = None
        elif isinstance(self.name, str):
            self.name = self.name.lower()
            try:
                self.density_func = getattr(spectrums, self.name.lower())
                sig = inspect.signature(self.density_func)
                self.ndim = len(sig.parameters) - 1
            except AttributeError as e:
                self.ndim = None
                raise ValueError(e)
        else:
            raise ValueError("'module 'uqra.solver.spectrums' has no attribute '{}'".format(self.name))

    def set_density(self, w_rad, pxx, sides='single'):
        assert w_rad.shape == pxx.shape, 'w_rad and pxx should have same shape. w_rad.shape={}, pxx.shape={}'.format(w_rad.shape, pxx.shape)
        self.w_rad  = w_rad
        self.pxx    = pxx
        self.sides  = sides
        self.dw     = w_rad[1] -w_rad[0]

    def cal_density(self, w_rad):
        w_rad, pxx = self.density_func(w_rad, *self.args)
        assert w_rad.shape == pxx.shape, 'w_rad and pxx should have same shape. w_rad.shape={}, pxx.shape={}'.format(w_rad.shape, pxx.shape)
        self.w_rad= w_rad
        self.pxx  = pxx
        self.dw   = w_rad[1] - w_rad[0]
        return pxx

    def get_acf(self):
        """
        Return Autocorrelation function corresponding to the specified power spectrum assuming stationary process by Inverse Fourier Transform (R=IFFT(PSD))
        PowerSpectrum.sides = 'double'
        """
        # ---------------------------------------------------------
        #                   |   Range       |   delta step
        # Time domain       | [-tmax, tmax] | dt = given
        # Frequency domain  | [-fmax, fmax] | dw = 1/(2(tmax))
        #                   | fmax = 1/(2*dt)
        # ---------------------------------------------------------
        # for single side psd, transform to double side and return corresponding w_rad, pxx
        # Transformation shouldn't change obj.w_rad, obj.pxx
        if not self._is_symmetric(self.w_rad, self.pxx):
            psd_w, psd_pxx = self.psd_single2double()
        else:
            psd_w   = self.w_rad
            psd_pxx = self.pxx

        nfrequencies    = psd_w.size
        w_min, w_max, dw= self.w_rad[0], self.w_rad[-1], self.w_rad[1]-self.w_rad[0]
        dt              = 1.0*2*np.pi/(2*w_max)
        acf_ifft        = np.fft.ifft(psd_pxx) / dt
        acf             = np.sqrt(acf_ifft.real**2 + acf_ifft.imag**2)
        t               = np.arange(-nfrequencies//2, nfrequencies//2) * dt
        acf             = np.roll(acf,nfrequencies//2)
        assert t.size == acf.size
        return t, acf

    def from_acf(self, t, acf_t):
        """
        Given auto correlation function acf_tau in [0,t] with dtau, return corresponding power spectral density function
        Process is assumed to be stationary such that acf is just a function of tau and acf is symmetric. e.g. acf(-tau) = acf(tau)
        Arguments:
            tau: time interval, if only [0,tmax] is specified, [-tmax,0) will be pre-appended automatically
                Values of acf at both positive and negative lags (tau) is necessary. When applying fft(data), algorithm assumes data repeat after time interval. If only positive part provided, symmetric information of acf will not be passed ([-tmax,0] will be exactly same as [0, tmax] instead of symmetric acf(tau) = acf(-tau))
            acf: autocorrelation function or values at specified tau
        Returns:
            Power spectral density function
            psd_w, psd_pxx
        """

        t, acf_t= self._correct_acf_format(t, acf_t)
        dt      = t[1]-t[0]
        psd_pxx = np.fft.fft(acf_t).real * dt
        psd_w   = 2*np.pi*np.fft.fftfreq(t.size,d=dt)
        # Since we know acf function is even, fft of acf_tau should only contain real parts
        # psd_pxx = np.sqrt(acf_fft.real**2 + acf_fft.imag**2) * dtau

        # reorder frequency from negative to positive ascending order
        psd_pxx = np.array([x for _,x in sorted(zip(psd_w,psd_pxx))])
        psd_w   = np.array([x for _,x in sorted(zip(psd_w,psd_w))])
        self.w_rad  = psd_w
        self.pxx= psd_pxx
        self.sides='double'
        return psd_w, psd_pxx

    def gen_process(self,t=None, phase=None, random_seed=None):
        """
        Generate Gaussian time series for given spectrum with IFFT method
        Note: For one side psd, one need to create IFFT coefficients for negative frequencies to use IFFT method.
            Single side psd need to be divide by 2 to create double side psd, S1(self.w_rad) = S1(-self.w_rad) = S2(self.w_rad)/2
            Phase: theta(self.w_rad) = -theta(-self.w_rad)

        Arguments: optional
            t: time index, start 0 to tmax
            phase: phase uniformly between[-pi, pi]
            random_seed: seed to generate phase if phase is not given
        Return:
            psd_w: frequencies of spectral density
            eta_fft_coeffs: surface wave power spectral denstiy

        """
        # ---------------------------------------------------------
        #                   |   Range       |   delta step
        # Time domain       | [0, tmax]     | dt = given
        # Frequency domain  | [-fmax, fmax] | dw = 1/tmax
        #                   | fmax = 1/(2*dt)
        # ---------------------------------------------------------
        if self.sides.lower() == 'single':
            psd_w, pxx = self.w_rad , self.pxx
            # psd_w, pxx= self.psd_single2double()
        else:
            raise NotImplementedError
            assert self._is_symmetric(self.w_rad, self.pxx)
            psd_w, pxx = self.w_rad, self.pxx

        psd_w_min, psd_w_max, psd_dw = np.amin(abs(psd_w)), np.amax(abs(psd_w)) , psd_w[1]-psd_w[0]
        if t is None:
            ### if t is not given, returned time series will be generated based on specified frequency values in psd_w
            tmin, tmax, dt = 0, 2*np.pi/psd_dw, np.pi/psd_w_max
            fft_freq_min, fft_freq_max, fft_freq_dw = 0, psd_w_max, psd_dw
            fft_freq_pos = np.arange(int(round(fft_freq_max/fft_freq_dw) +1)) * fft_freq_dw
            time = np.arange(int(round(tmax/dt))+1) *dt
        else:
            ### when a time index is given, sampling frequency need to be small enough to cover the period (tmax)
            ### and frequency window need to be large enough to have time resolution dt
            tmax, tmin, dt = t[-1], t[0], t[1] - t[0]
            fft_freq_min, fft_freq_max, fft_freq_dw = 0, max(np.pi/dt, psd_w_max), min(psd_dw, 2*np.pi/tmax)
            fft_freq_pos = np.arange(int(round(fft_freq_max/fft_freq_dw) +1)) * fft_freq_dw
            tmin, tmax, dt = 0, 2*np.pi/fft_freq_dw, np.pi/fft_freq_max
            time = np.arange(int(round(tmax/dt))+1) *dt

        psd_w_idx = [int(round(psd_w_min/fft_freq_dw)),int(round(psd_w_max/fft_freq_dw))]
        if phase is None:
            np.random.seed(random_seed)
            theta = np.random.uniform(-np.pi, np.pi, psd_w.size)
        else:
            theta = phase[:psd_w.size]

        print('time:', time.shape)
        print('theta:', theta.shape)
        psd_A = np.sqrt(2.0*pxx*fft_freq_dw)/2.0 * np.exp(-1j*theta) *  (pxx.size*2-1) #(fft_freq_pos*2-1) # amplitude
        # fft_A = np.zeros(fft_freq_pos.size, dtype=complex)
        # fft_A = np.zeros(pxx.size, dtype=complex)
        # fft_A[psd_w_idx[0]:psd_w_idx[1]] = psd_A
        fft_A = psd_A
        fft_A = np.append(fft_A, np.flip(np.conj(fft_A[1:])))
        # eta = np.roll(eta,ntime_steps).real # roll back to [-T, T], IFFT result should be real, imaginary part is very small ~10^-16
        eta = np.fft.ifft(fft_A).real
        if eta.size != time.size:
            raise ValueError(' len(eta)= {:d}, but len(time) = {:d}'.format(eta.size, time.size))

        if t is not None:
            eta = np.interp(t, time, eta)
            time = t
        return time, eta , psd_w, theta

    def _is_symmetric(self, x, y):
        """
        Check if the following two symmetric properties are met:
        1. x[i] = -x[i]
        2. y[x] = -y[x]
        """
        x = np.array(x)
        y = np.array(y)
        x = x[x.argsort()]
        y = y[x.argsort()]
        assert x.size == y.size

        if x[0] * x[-1] >= 0: # return false if xmin, xmax same sign
            return False
        n     = x.size+1 if (x.size) %2 else x.size
        bool1 = np.array_equal(x[1:n/2], -np.flip(x[n/2:-1]))
        bool2 = np.array_equal(y[1:n/2], -np.flip(y[n/2:-1]))
        return bool1 and bool2

    def _correct_acf_format(self, t, acf_t):
        """
        Modify (t, acf_t) to standard format corresponding to frequenceis
        t = [0,1,...,n, -n,...,-1] * dt
        2n+1 time steps
        """
        t     = np.array(t)
        t     = t[t.argsort()]
        acf_t = acf_t[t.argsort()]

        ntimesteps = t.size
        ## if not symmetric, following steps are taken:
        # 1. time step dt is taken as the mean value of all time steps
        # 2. corresponding acf values are interpolated
        if not self._is_symmetric(t, acf_t):
            dt = np.mean(t[1:-1] - t[:-2], dtype=np.float64)
            ntimesteps = np.ceil(max(abs(t))/dt)
            t1 = np.arange(-ntimesteps, ntimesteps) * dt
            acf_t_interp = np.interp(t1, t, acf_t)
            return np.roll(t1, ntimesteps+1), np.roll(acf_t_interp, ntimesteps+1)
        else:
            return t, acf_t

    def psd_single2double(self):
        """
        Convert single side psd specified by (w_rad, pxx) to double side
        Arguments:
            w_rad   : single side frequency vector, could start from arbitrary value
            pxx : single side power spectral density (PSD) estimate, pxx,
        Returns:
            ww_rad  : double side frequency vector
            pxx2: double side power spectral density (PSD) estimate, pxx2
        """
        assert self.w_rad is not None and self.pxx is not None
        w_rad, pxx = self.w_rad, self.pxx
        ## sorting w_rad in ascending order
        w_rad= w_rad[w_rad.argsort()]
        pxx = pxx [w_rad.argsort()]
        assert w_rad[0] >= 0 and w_rad[-1] >0 # make sure frequency vector in positive range
        dw      = np.mean(w_rad[1:]-w_rad[:-1], dtype=np.float64) # dw is the average frequency difference
        N       = len(np.arange(w_rad[-1], 0, -dw)) # does not include 0
        pxx2    = np.zeros(2*N+1)
        ww_rad  = np.zeros(2*N+1)
        # print(ww_rad.shape, pxx2.shape)
        #
        # pxx2: [0,|1,2,...,N | N+1,...,2N+1]
        #       [0,| positive |   negative  ]

        ## padding psd from 0 to w_rad[0] with 0
        # first element of pxx2[0] is power at frequency 0. If pxx(0) is not given, 0 is padded
        pxx2[0] = pxx[0] if w_rad[0]==0 else 0
        # Positive frequencies part
        m = pxx.size
        pxx2[N+1-m:N+1] = pxx/2
        # Negative frequencies part
        pxx2[N+1:]  = np.flip(pxx2[1:N+1])
        ww_rad[1:N+1]   = np.flip(np.arange(w_rad[-1], 0, -dw))
        ww_rad[N+1:]    = -np.arange(w_rad[-1], 0, -dw)

        ## Reorder psd to [-inf, negative, 0, positive, +inf]
        pxx2= np.roll(pxx2, N)
        ww_rad  = np.roll(ww_rad, N)

        return ww_rad, pxx2

    def _gen_process_sum(self):
        if self.sides.lower() == 'double':
            raise NotImplementedError('Stay tuned')
        else:
            psd_w, psd_pxx = self.w_rad, self.pxx
            w_max, dw = psd_w[-1]  , psd_w[1]-psd_w[0]
            tmax, dt = 0.5*2*np.pi/dw , 0.5/w_max
            N = int(tmax/dt)
            t = np.arange(-N,N+1) * dt
            # tmax = t[-1]
            theta = np.random.uniform(-np.pi, np.pi, len(psd_w))
            # Direct sum with single side psd
            ampf = np.sqrt(2*psd_pxx*(dw)) # amplitude

            # Reshape to matrix operation format
            ampf  = np.reshape(ampf,  (N+1,1))
            psd_w = np.reshape(psd_w, (N+1,1))
            theta = np.reshape(theta, (N+1,1))
            t     = np.reshape(t,     (1, 2*N+1))
            eta = np.sum(ampf * np.cos(2*np.pi*psd_w*t + theta),axis=0)

        return t, eta