#!/usr/bin/python3

import spintrum
import math
import matplotlib.pyplot as plt
import numpy as np
import scipy.optimize
import multiprocessing
import mpmath as mp


def filter_spectrum(spec, freq_lim):
    x_axis = np.array([])
    y_axis = np.array([])
    for i in range(len(freq_lim)):
        for j in range(len(spec["x"])):
            if freq_lim[i][0] <= spec["x"][j] <= freq_lim[i][1]:
                x_axis = np.append(x_axis,spec["x"][j])
                y_axis = np.append(y_axis,spec["y"][j])
    return {"x": x_axis, "y": y_axis}


#defining initial parameters

with open("../data/methylformateSignal.txt") as f:
    data = f.readlines()
    data = np.array(list(map(np.double,data)))
    data = data - np.mean(data)

gammas = [4257.7e4,4257.7e4,4257.7e4,4257.7e4,1070.8e4]
multips = [2,2,2,2,2]

gammah = 2*math.pi*4257.7e4
BThermal = 1.8e0
T2 = 1
points = len(data)
sampleRate = 2000
spectrumRange = [[70,119],[121,179],[181,239],[241,299]]
jCouplings = \
[
    [0,0,0,0,0],
    [0,0,0,0,0],
    [0,0,0,0,0],
    [0,0,0,0,0],
    [0,0,0,0,0],
]

experimental_spectrum = filter_spectrum(spintrum.FFTSpectralDensity(data, 2000), spectrumRange)


gen = spintrum.SpinSimulator(gyromagneticRatios=gammas,
                             jCouplings=jCouplings,
                             spinMultiplicities=multips,
                             doPrint=False)

spinOp = spintrum.SpinOperations()
spinOp.add_operation(spintrum.SpinOperations.OPERATION__THERMAL_POPULATE,
                     {'Bx': 0, 'By': 0, 'Bz': BThermal, 'T': 293.778})
spinOp.add_operation(spintrum.SpinOperations.OPERATION__TIP_SPINS,
                     {'direction': 'y', 'BVsTArea': 4*math.pi/gammah})
spinOp.add_operation(spintrum.SpinOperations.OPERATION__SET_HAMILTONIAN,
                     {'Bx': 0, 'By': 0, 'Bz': 0})
spinOp.add_operation(spintrum.SpinOperations.OPERATION__INIT_TIME_INDEPENDENT_EVOLUTION,
                     {'samplingRate': sampleRate, 'measurementDirection': 'z'})
spinOp.add_operation(spintrum.SpinOperations.OPERATION__EVOLVE_TIME_INDEPENDENT,
                     {'points': points, 'threads': multiprocessing.cpu_count()})

gen.update_parameters(spinOperations=spinOp, jCouplings=jCouplings, gyromagneticRatios=gammas)


def generate_spectrum(params):

    amplitude = params[3]
    T2 = params[4]
    jCouplings = \
        [
            [0, params[0], params[0], params[0],params[1]],
            [0, 0, 0, 0, params[2]],
            [0, 0, 0, 0, params[2]],
            [0, 0, 0, 0, params[2]],
            [0, 0, 0, 0, 0],

        ]


    spinOp = spintrum.SpinOperations()
    spinOp.add_operation(spintrum.SpinOperations.OPERATION__THERMAL_POPULATE,
                         {'Bx': 0, 'By': 0, 'Bz': BThermal, 'T': 293.778})
    spinOp.add_operation(spintrum.SpinOperations.OPERATION__TIP_SPINS,
                         {'direction': 'y', 'BVsTArea': 4 * math.pi / gammah})
    spinOp.add_operation(spintrum.SpinOperations.OPERATION__SET_HAMILTONIAN,
                         {'Bx': np.float(params[5]), 'By': 0, 'Bz': np.float(params[6])})
    spinOp.add_operation(spintrum.SpinOperations.OPERATION__INIT_TIME_INDEPENDENT_EVOLUTION,
                         {'samplingRate': sampleRate, 'measurementDirection': 'z'})
    spinOp.add_operation(spintrum.SpinOperations.OPERATION__EVOLVE_TIME_INDEPENDENT,
                         {'points': points, 'threads': multiprocessing.cpu_count()})

    gen.update_parameters(jCouplings=jCouplings, spinOperations=spinOp)

    signal = amplitude*gen.simulate()

    signal = signal - np.mean(signal)
    signal = [signal[i]*math.exp(-i/sampleRate/T2) for i in range(len(signal))]

    fft = filter_spectrum(spintrum.FFTSpectralDensity(signal, 2000), spectrumRange)

#    plt.plot(fft['x'],fft['y'],realSpectrum['x'],realSpectrum['y'])
#    plt.show()

    return fft



def objective_func(params):
    print(params)
    spect = generate_spectrum(params)
    funcValue = np.sqrt(np.sum((spect['y'] - experimental_spectrum['y']) ** 2))
    print('Objective function value: ',funcValue)
    return funcValue



pars = np.array([-1.06785235e+00,2.26791767e+02,4.26684847e+00,4.53620118e-05,2.73284204e+00,3.11541220e-09,2.80718054e-09])
#pars = np.array([-1.06785235e+00,2.26791767e+02,4.26684847e+00,4.53620118e-05,2.73284204e+00])

# get_objective_func(pars)

optimum_params = scipy.optimize.fmin(objective_func, pars, ftol = 5.0e-13, maxfun = 1)
optjCouplings = mp.matrix([[0, optimum_params[0], optimum_params[0], optimum_params[0], optimum_params[1]], [0, 0, 0, 0, optimum_params[2]], [0, 0, 0, 0, optimum_params[2]], [0, 0, 0, 0, optimum_params[2]], [0, 0, 0, 0, 0]])

CorrelationMatrix = spintrum.get_correlation_matrix(optimum_params, generate_spectrum, objective_func)
print('Correlation matrix: ','\n',CorrelationMatrix)

print('Parameters at minimum: ','\n', optimum_params)
errorbars = spintrum.get_errorbars(CorrelationMatrix)
print('Standard deviations of parameters: ','\n',errorbars)

#writout to file
np.set_printoptions(suppress=True,precision=20)
fr = open("../data/methylformateFitResult.txt","w")
fr.write('Minimum at J-couplings:\n')
fr.write(str(optjCouplings)+'\n')
fr.write('Minimum at paramters:\n')
fr.write(str(optimum_params) + '\n')
fr.write('Parameters standard deviations:\n')
fr.write(str(errorbars)+'\n')
fr.write('Parameters correlation matrix:\n')
fr.write(str(CorrelationMatrix)+'\n')
fr.write('Minimum objective function value: ' + str(objective_func(optimum_params)))
fr.close()

fft_final = generate_spectrum(optimum_params)

plt.plot(fft_final['x'], fft_final['y'], experimental_spectrum['x'], experimental_spectrum['y'])

plt.show()
