from __future__ import division, print_function

import sys

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

# Use the classic matplotlib plotting style
matplotlib.style.use('classic')

import scipy.stats as stat

from LoadData import loadDataCAMS

# Check if cython is installed
try:

    import cython

    # Cython init
    import pyximport
    pyximport.install(setup_args={'include_dirs':[np.get_include()]})
    from SporadicsCompareOrbitsDSH import findMinimumDSH, findNearestNeighborDistDSH

except:
    print("'Cython' module not installed - you will not se able to calculate the nearest neighbour distances \
        in D_SH!")

# loadData indices - indices of parameters passed from the loadData function
MJD_INDX =      1
SOL_INDX =      2
AMAG_INDX =     3
RA_INDX =       4
DEC_INDX =      5
VI_INDX =       6
VG_INDX =       7

A_INDX =        8
Q_INDX =        9
ECC_INDX =      10
INCL_INDX =     11
NODE_INDX =     12
PERI_INDX =     13

Q_STD_INDX =    20
ECC_STD_INDX =  21
INCL_STD_INDX = 22
NODE_STD_INDX = 23
PERI_STD_INDX = 24
VG_STD_INDX =   25

HBEG_INDX =     14
HEND_INDX =     15
QC_INDX =       16
DV12_INDX =     17
LAMBDA_G_INDX = 18
BETA_G_INDX =   19

# Font sizes for histogram
HIST_TICK_FONTSIZE = 8
HIST_TITLE_FONTSIZE = 9
HIST_LABEL_FONTSIZE = 14

def drawFromHistogram(data_array, n_bins, n_samples):
    """ Draw random samples from a histogram using the Cumulative density method.

    Arguments:
        data_array: [1D ndarray] numpy array containing the data
        n_bins: [int] number of bins of the histogram
        n_samples: [int] number of samples to draw from the histogram

    Return:
        random_from_cdf: [ndarray] data drawn from the histogram

    """

    # Initialize the histogram
    hist, bins = np.histogram(data_array, bins=n_bins)

    # Calculate the midpoints of histograms
    bin_midpoints = bins[:-1] + np.diff(bins)/2

    # Generate the cumulative sum of the histogram
    cdf = np.cumsum(hist)

    # Normalize the cumulative sum
    cdf = cdf / float(cdf[-1])

    # Take uniform samples from the histogram
    values = np.random.rand(n_samples)

    value_bins = np.searchsorted(cdf, values)
    random_from_cdf = bin_midpoints[value_bins]

    bin_width = (max(data_array) - min(data_array))/n_bins

    # Uniformly sample from within each bin (if were were not doing this, then we would have only one value
    # per bin, i.e. only n_bins unique values)
    random_from_cdf += np.random.random(n_samples)*bin_width - bin_width/2.0

    return random_from_cdf

def plotCompareHistograms(original_data, synthetic_data, n_bins, title, subplot=False, save_legend=False,
    data_label=''):
    """ Plot 2 histograms, one overlaid on top of the other, for easy comparison of the two.

    Arguments:
        original_data: [ndarray] first data array (observed data)
        synthetic_data: [ndarray] second data array (synthetic data)
        n_bins: [ndarray] number of bins of histograms
        title: [str] title of the histogram

    Keyword arguments:
        subplot: [bool] False by default, set to True if doing multiple subplots on a single graph
        save_legend: [bool] plot the histogram legend
        data_label: [str] add this prefix in front of each name of the file that is saved to disk

    Return:
        None
    """

    # Init the colormap
    cmap = matplotlib.cm.get_cmap('inferno')

    plt.hist(original_data, n_bins, alpha=0.5, color=cmap(0.1), label='Observed', hatch='//', zorder=3)
    plt.hist(synthetic_data, n_bins, alpha=0.5, color=cmap(0.9), label='Synthetic', hatch='\\\\', zorder=3)

    plt.xlabel(title)
    plt.ylabel("Counts")

    # Set label size
    plt.gca().xaxis.label.set_size(HIST_LABEL_FONTSIZE)

    ## Uncomment if you want to plot the grid in the background
    #plt.grid(zorder=0)

    # Show legend if not plotting a subplot, or if the legend is requested
    if (not subplot) or (save_legend):
        plt.legend(loc='upper left')

    # Set the X axis limits to the limits of the original data
    plt.xlim((min(original_data), max(original_data)))

    if not subplot:

        plt.tight_layout()

        # Save the plot to disk
        savePlot(plt, title, data_label)

        plt.show()
        plt.clf()

def plot2DHistogram(x_array, y_array, plot_title, x_axis_label, y_axis_tabel, subplot=False, x_width=None,
    y_width=None, vmin=None, vmax=None):
    """ Plot the 2D histogram (density plot) of two given data arrays.

    Arguments:
        x_array: [1D ndarray] array of X axis values
        y_array: [1D ndarray] array of Y axis values
        plot_title: [str] plot title
        x_axis_label: [str] label of the X axis
        y_axis_label: [str] label of the Y axis

    Keyword arguments:
        subplot: [bool] False by default, set to True if doing multiple subplots on a single graph
        x_width: [float] None by default, if provided it is used for calculating the number of bins by
            diving the range of the data into equally sized chunks: x_nbins = (x_max - x_min)/x_width
        y_width: [float] None by default, if provided it is used for calculating the number of bins by
            diving the range of the data into equally sized chunks: y_nbins = (y_max - y_min)/y_width
        vmin: [float] None by default, if given it is the lower limit of the color representing the histogram
            counts
        vmax: [float] None by default, if given it is the upper limit of the color representing the histogram
            count

    Return:
        (vmin, vmax): [tuple] minimum and maximum histogram bin counts

    """

    # Construct the x*y matrix
    eq_matrix = np.column_stack((x_array, y_array))
    x, y = eq_matrix.T

    # Determine the data limits
    xmin, xmax = min(x_array), max(x_array)
    ymin, ymax = min(y_array), max(y_array)

    # Calculate the bin number for X data if the widths are provided
    if x_width is not None:
        x_nbins = (xmax - xmin)/x_width
    else:
        # If the widths were not defined, take the default value
        x_nbins = 100

    # Calculate the bin number for Y data if the widths are provided
    if y_width is not None:
        y_nbins = (ymax - ymin)/y_width

    else:
        # If the widths were not defined, take the default value
        y_nbins = 100

    # Make a histogram
    hist, x_bins, y_bins = np.histogram2d(x, y, bins=(x_nbins, y_nbins))
    plt.hist2d(x, y, bins=(x_nbins, y_nbins), cmap='inferno_r', vmin=vmin, vmax=vmax)

    # Set plot limits
    plt.xlim((xmin, xmax))
    plt.ylim((ymin, ymax))

    # Set the title and labels
    plt.title(plot_title, fontsize=HIST_TITLE_FONTSIZE)
    plt.xlabel(x_axis_label, fontsize=HIST_LABEL_FONTSIZE)
    plt.ylabel(y_axis_tabel, fontsize=HIST_LABEL_FONTSIZE)

    # Change font size of ticks
    plt.tick_params(axis='both', which='major', labelsize=HIST_TICK_FONTSIZE)

    if not subplot:
        plt.show()
        plt.clf()

    return np.min(hist), np.max(hist)

def plot2DResidualHistogram(x1_array, y1_array, x2_array, y2_array, plot_title, x_axis_label, y_axis_tabel,
    subplot=False, x_width=None, y_width=None, vmin=None, vmax=None):
    """ Plot the absolute difference of two 2D histograms (density plots), of two sets of data. The color
        range of bin counts is determined by the range of the first data set.

    Arguments:
        x1_array: [1D ndarray] first array of X axis values
        y1_array: [1D ndarray] first array of Y axis values
        x2_array: [1D ndarray] second array of X axis values
        y2_array: [1D ndarray] second array of Y axis values
        plot_title: [str] plot title
        x_axis_label: [str] label of the X axis
        y_axis_label: [str] label of the Y axis

    Keyword arguments:
        subplot: [bool] False by default, set to True if doing multiple subplots on a single graph
        x_width: [float] None by default, if provided it is used for calculating the number of bins by
            diving the range of the data into equally sized chunks: x_nbins = (x_max - x_min)/x_width
        y_width: [float] None by default, if provided it is used for calculating the number of bins by
            diving the range of the data into equally sized chunks: y_nbins = (y_max - y_min)/y_width
        vmin: [float] None by default, if given it is the lower limit of the color representing the histogram
            counts
        vmax: [float] None by default, if given it is the upper limit of the color representing the histogram
            count

    Return:
        None

    """

    # Construct the x1*y1 matrix
    eq_matrix1 = np.column_stack((x1_array, y1_array))
    x1, y1 = eq_matrix1.T

    # Construct the x2*y2 matrix
    eq_matrix2 = np.column_stack((x2_array, y2_array))
    x2, y2 = eq_matrix2.T

    # Determine the data limits by using the first data set
    xmin, xmax = min(x1_array), max(x1_array)
    ymin, ymax = min(y1_array), max(y1_array)

    # Calculate the bin number if the widths are provided
    if x_width is not None:
        x_nbins = (xmax - xmin)/x_width
    else:
        # If the widths were not defined, take the default value
        x_nbins = 100

    # Calculate the bin number if the widths are provided
    if y_width is not None:
        y_nbins = (ymax - ymin)/y_width

    else:
        # If the widths were not defined, take the default value
        y_nbins = 100

    # Make a first histogram
    hist1, x1_bins, y1_bins = np.histogram2d(x1, y1, bins=(x_nbins, y_nbins))

    # Make a second histogram
    hist2, x2_bins, y2_bins = np.histogram2d(x2, y2, bins=(x_nbins, y_nbins), range=[[np.min(x1), np.max(x1)],
        [np.min(y1), np.max(y1)]])

    # Calculate the absolute difference of the two histogram
    residuals = np.abs(hist1 - hist2)

    # # Get the color range
    ## Uncomment if you want to force the color range to the original histogram range
    # vmin = np.min(hist1)
    # vmax = np.max(hist1)

    ## Uncomment if you want to force the color range to the residuals histogram range
    # vmin = np.min(residuals)
    # vmax = np.max(residuals)

    # Plot the difference histogram
    plt.gca().pcolorfast(x1_bins, y1_bins, residuals.T, cmap='inferno_r', vmin=vmin, vmax=vmax)

    # Set the plot limits
    plt.xlim((xmin, xmax))
    plt.ylim((ymin, ymax))

    # Set the title and axis labels
    plt.title(plot_title, fontsize=HIST_TITLE_FONTSIZE)
    plt.xlabel(x_axis_label, fontsize=HIST_LABEL_FONTSIZE)
    plt.ylabel(y_axis_tabel, fontsize=HIST_LABEL_FONTSIZE)

    # Change font size of ticks
    plt.tick_params(axis='both', which='major', labelsize=HIST_TICK_FONTSIZE)

    if not subplot:
        plt.show()
        plt.clf()

def plotAllResults(loaded_data, inv_a_results, q_results, e_results, peri_results, node_results, incl_results,
        q_std, e_std, peri_std, node_std, incl_std, data_label=''):
    """ Plot all results of synthetic orbit generation.

    Arguments:
        loaded_data: [2D ndarray] numpy array containing the input data, column indices are defined at the
            beginning of this file in uppercase letters
        inv_a_results: [ndarray] array containing the inverse semi-major axis data
        q_results: [ndarray] array containing perihelion distance data
        e_results: [ndarray] array containing eccentricity data
        peri_results: [ndarray] array containing argument of perihelion data
        node_results: [ndarray] array containing node data
        incl_results: [ndarray] array containing inclination data
        q_std: [ndarray] mean perihelion distance standard deviation
        e_std: [ndarray] mean eccentricity standard deviation
        peri_std: [ndarray] mean argument of perihelion standard deviation
        node_std: [ndarray] mean node standard deviation
        incl_std: [ndarray] mean inclination standard deviation

    Keyword arguments:
        data_label: [str] add this prefix in front of each name of the file that is saved to disk

    Return:
        None
    """

    ### Plot individual 1D histograms

    n_bins = 80

    # Compare q histograms
    plotCompareHistograms(loaded_data[:, Q_INDX], q_results, n_bins, '$q$', data_label=data_label)

    plt.subplot(411)
    # Compare e histogram
    plotCompareHistograms(loaded_data[:, ECC_INDX], e_results, n_bins, '$e$', subplot=True, save_legend=True)

    plt.subplot(412)
    # Compare peri histogram
    plotCompareHistograms(loaded_data[:, PERI_INDX], peri_results, n_bins, '$\omega$', subplot=True)

    plt.subplot(413)
    # Compare node histogram
    plotCompareHistograms(loaded_data[:, NODE_INDX], node_results, n_bins, '$\Omega$', subplot=True)

    plt.subplot(414)
    # Compare inclination histogram
    plotCompareHistograms(loaded_data[:, INCL_INDX], incl_results, n_bins, '$i$', subplot=True)

    plt.tight_layout()

    # Adjust the spacing of subplots
    plt.subplots_adjust(hspace=0.58, bottom=0.08)

    # Adjust figure size
    plt.gcf().set_size_inches(8, 8, forward=True)

    # Save the plot to disk
    savePlot(plt, 'all', data_label)

    plt.show()
    plt.clf()

    ###

    ### Peri - Incl plots

    # Plot the observed data peri-incl 2D histogram
    ax1 = plt.subplot(131)
    vmin, vmax = plot2DHistogram(loaded_data[:, PERI_INDX], loaded_data[:, INCL_INDX], 'Observed data',
        '$\omega$', '$i$', subplot=True, x_width=peri_std, y_width=incl_std)

    # Set limits of the X axis
    ax1.set_xlim([0, 360])

    # Plot the synthetic data peri-incl 2D histogram
    ax2 = plt.subplot(132, sharey=ax1)
    plot2DHistogram(peri_results, incl_results, 'Synthetic data', '$\omega$', '$i$', subplot=True,
        x_width=peri_std, y_width=incl_std, vmin=vmin, vmax=vmax)

    # Set limits of the X axis
    ax2.set_xlim([0, 360])

    # Update the Y limits to conform to the input data
    plt.ylim((min(loaded_data[:, INCL_INDX]), max(loaded_data[:, INCL_INDX])))

    # Make Y axis tick labels invisible
    plt.setp(ax2.get_yticklabels(), visible=False)
    ax2.set_ylabel('')

    # Plot the absolute residuals 2D histogram
    ax3 = plt.subplot(133, sharey=ax1)
    plot2DResidualHistogram(loaded_data[:, PERI_INDX], loaded_data[:, INCL_INDX], peri_results,
        incl_results, '|Observed-synthetic| residuals', '$\omega$', '$i$', subplot=True, x_width=peri_std,
        y_width=incl_std, vmin=vmin, vmax=vmax)

    # Set limits of the X axis
    ax3.set_xlim([0, 360])

    # Update the Y limits to conform to the input data
    plt.ylim((min(loaded_data[:, INCL_INDX]), max(loaded_data[:, INCL_INDX])))

    # Make Y axis tick labels invisible
    plt.setp(ax3.get_yticklabels(), visible=False)
    ax3.set_ylabel('')

    # Adjust spacing
    plt.tight_layout()
    plt.subplots_adjust(right=0.88, wspace=0.05)

    # Get plot positions
    [[x00,y00],[x01,y01]] = ax1.get_position().get_points()
    [[x10,y10],[x11,y11]] = ax3.get_position().get_points()
    pad = 0.01; width = 0.02

    # Add the colorbar axis
    fig = plt.gcf()
    cbar_ax = fig.add_axes([x11+pad, y10, width, y01-y10])
    axcb = plt.colorbar(cax=cbar_ax, label='Counts')
    axcb.ax.tick_params(labelsize=HIST_TICK_FONTSIZE)

    # Save the plot to disk
    savePlot(plt, 'peri_vs_i', data_label)

    plt.show()
    plt.clf()

    ###

    ### e - 1/a plots

    # Plot the observed e vs 1/a 2D histogram
    ax1 = plt.subplot(131)
    vmin, vmax = plot2DHistogram(loaded_data[:, ECC_INDX], 1.0/loaded_data[:, A_INDX], 'Observed data',
        '$e$', '$1/a$', subplot=True, x_width=e_std)

    # Set limits of the X axis
    ax1.set_xlim([0, 1])

    # Plot the synthetic e vs 1/a 2D histogram
    ax2 = plt.subplot(132, sharey=ax1)
    plot2DHistogram(e_results, inv_a_results, 'Synthetic data', '$e$', '$1/a$', subplot=True,
        x_width=e_std, vmin=vmin, vmax=vmax)

    # Set limits of the X axis
    ax2.set_xlim([0, 1])

    # Update the Y limits to conform to the input data
    plt.ylim((min(1.0/loaded_data[:, A_INDX]), max(1.0/loaded_data[:, A_INDX])))

    # Make Y axis tick labels invisible
    plt.setp(ax2.get_yticklabels(), visible=False)
    ax2.set_ylabel('')

    # Hide '0.0' in xticks
    labels = ax2.get_xticks().tolist()
    labels[0] = ''
    ax2.set_xticklabels(labels)

    # Plot the absolute residuals 2D histogram
    ax3 = plt.subplot(133, sharey=ax1)
    plot2DResidualHistogram(loaded_data[:, ECC_INDX], 1.0/loaded_data[:, A_INDX], e_results,
        inv_a_results, '|Observed-synthetic| residuals', '$e$', '$1/a$', subplot=True, x_width=e_std,
        vmin=vmin, vmax=vmax)

    # Set limits of the X axis
    ax3.set_xlim([0, 1])

    # Update the Y limits to conform to the input data
    plt.ylim((min(1.0/loaded_data[:, A_INDX]), max(1.0/loaded_data[:, A_INDX])))

    # Make Y axis tick labels invisible
    plt.setp(ax3.get_yticklabels(), visible=False)
    ax3.set_ylabel('')

    # Hide '0.0' in xticks
    labels = ax3.get_xticks().tolist()
    labels[0] = ''
    ax3.set_xticklabels(labels)

    # Adjust spacing
    plt.tight_layout()
    plt.subplots_adjust(right=0.88, wspace=0.05)

    # Get plot positions
    [[x00,y00],[x01,y01]] = ax1.get_position().get_points()
    [[x10,y10],[x11,y11]] = ax3.get_position().get_points()
    pad = 0.01; width = 0.02

    # Add the colorbar axis
    fig = plt.gcf()
    cbar_ax = fig.add_axes([x11+pad, y10, width, y01-y10])
    axcb = plt.colorbar(cax=cbar_ax, label='Counts')
    axcb.ax.tick_params(labelsize=HIST_TICK_FONTSIZE)

    # Save the plot to disk
    savePlot(plt, 'e_vs_inv_a', data_label)

    plt.show()
    plt.clf()

    ###

    ### Peri-q plots

    # Plot the observed peri vs q plot
    ax1 = plt.subplot(131)
    vmin, vmax = plot2DHistogram(loaded_data[:, PERI_INDX], loaded_data[:, Q_INDX], 'Observed data',
        '$\omega$', '$q$', subplot=True, x_width=peri_std, y_width=q_std)

    # Set limits of the X axis
    ax1.set_xlim([0, 360])

    # Plot the synthetic data peri-incl 2D histogram
    ax2 = plt.subplot(132, sharey=ax1)
    plot2DHistogram(peri_results, q_results, 'Synthetic data', '$\omega$', '$q$', subplot=True,
        x_width=peri_std, y_width=q_std, vmin=vmin, vmax=vmax)

    # Set limits of the X axis
    ax2.set_xlim([0, 360])

    # Update the Y limits to conform to the input data
    plt.ylim((min(loaded_data[:, Q_INDX]), max(loaded_data[:, Q_INDX])))

    # Make Y axis tick labels invisible
    plt.setp(ax2.get_yticklabels(), visible=False)
    ax2.set_ylabel('')

    # Plot the absolute residuals 2D histogram
    ax3 = plt.subplot(133, sharey=ax1)
    plot2DResidualHistogram(loaded_data[:, PERI_INDX], loaded_data[:, Q_INDX], peri_results, q_results,
        '|Observed-synthetic| residuals', '$\omega$', '$q$', subplot=True, x_width=peri_std,
        y_width=q_std, vmin=vmin, vmax=vmax)

    # Set limits of the X axis
    ax3.set_xlim([0, 360])

    # Update the Y limits to conform to the input data
    plt.ylim((min(loaded_data[:, Q_INDX]), max(loaded_data[:, Q_INDX])))

    # Make Y axis tick labels invisible
    plt.setp(ax3.get_yticklabels(), visible=False)
    ax3.set_ylabel('')

    # Adjust spacing
    plt.tight_layout()
    plt.subplots_adjust(right=0.88, wspace=0.05)

    # Get plot positions
    [[x00,y00],[x01,y01]] = ax1.get_position().get_points()
    [[x10,y10],[x11,y11]] = ax3.get_position().get_points()
    pad = 0.01; width = 0.02

    # Add the colorbar axis
    fig = plt.gcf()
    cbar_ax = fig.add_axes([x11+pad, y10, width, y01-y10])
    axcb = plt.colorbar(cax=cbar_ax, label='Counts')
    axcb.ax.tick_params(labelsize=HIST_TICK_FONTSIZE)

    # Save the plot to disk
    savePlot(plt, 'peri_vs_q', data_label)

    plt.show()
    plt.clf()

    ###

def generateJopekMethodE(loaded_data, n_samples, q_std, node_std, e_std, peri_std, incl_std, show_plots=True,
    get_more_data=False):
    """ Generate synthetic orbits using the Jopek & Bronikowska (2016) method E approach.

    Arguments:
        loaded_data: [2D ndarray] numpy array containing the input data, column indices are defined at the
            beginning of this file in uppercase letters
        n_samples: [int] number of orbits to draw
        q_std: [float] standard deviation of the perihelion distance, used for plotting 2D histograms
        node_std: [float] standard deviation of the node, used for plotting 2D histograms
        e_std: [float] standard deviation of the eccentricity, used for plotting 2D histograms
        peri_std: [float] standard deviation of the argument of perihelion, used for plotting 2D histograms
        incl_std: [float] standard deviation of the inclination, used for plotting 2D histograms

    Keyword arguments:
        show_plots: [bool] True by default, if False the plots are not generated, only data vectors are
            returned
        get_more_data: [bool] False by default, used internally as a flag to indicate that the function is in
            recursion, NOTE: do not set this value manually!

    Return:
        (inv_a_generated, q_generated, e_generated, peri_generated, node_generated, incl_generated): [tuple of
        ndarrays] orbital elements of generated orbits

    """

    # Calculate the fraction of orbits with geocentric ecliptic latitudes beta > 0
    positive_beta_fraction = len(loaded_data[loaded_data[:, BETA_G_INDX] > 0])/len(loaded_data)

    print('Beta > 0 fraction:', positive_beta_fraction)

    ### Independently generate each orbital element from their histograms

    # Specify number of bins for each parameter
    e_bins = 80
    peri_bins = 80
    node_bins = 80
    incl_bins = 80

    # Generate e from histogram
    e_generated = drawFromHistogram(loaded_data[:, ECC_INDX], e_bins, n_samples)

    # Generate peri from histogram
    peri_generated = drawFromHistogram(loaded_data[:, PERI_INDX], peri_bins, n_samples)

    # Generate node from histogram
    node_generated = drawFromHistogram(loaded_data[:, NODE_INDX], node_bins, n_samples)

    # Generate inclination from histogram
    incl_generated = drawFromHistogram(loaded_data[:, INCL_INDX], incl_bins, n_samples)

    ###

    ### Calculate q from the results

    # 1 AU (set for constant, an improvement would be for it to vary with solar longitude)
    au_dist = 1.0

    # Find the sign of the e*cosw expression in respect to the beta fraction
    beta_signs = [-1 if entry < positive_beta_fraction else 1 for entry in np.random.random(n_samples)]
    beta_signs = np.array(beta_signs)

    # Calculate q
    q_generated = au_dist*(1.0 + beta_signs*e_generated*np.cos(np.radians(peri_generated)))/(1.0 + \
        e_generated)

    ###

    # Calculate 1/a
    inv_a_generated = (1 - e_generated)/q_generated

    # Filter out all entries where the generated 1/a is larger than the maximum input 1/a
    good_indices = np.where(inv_a_generated <= max(1.0/loaded_data[:, A_INDX]))[0]

    inv_a_generated = inv_a_generated[good_indices]
    q_generated = q_generated[good_indices]
    e_generated = e_generated[good_indices]
    peri_generated = peri_generated[good_indices]
    node_generated = node_generated[good_indices]
    incl_generated = incl_generated[good_indices]

    # Generate some more data to replace the rejected orbits:
    if (len(good_indices) < n_samples) and not get_more_data:

        # Calculate the number of missing orbits
        missing_orbits_n = n_samples - len(good_indices)

        # Generate new synthetic orbits
        inv_a_more, q_more, e_more, peri_more, node_more, incl_more = generateJopekMethodE(loaded_data,
            n_samples, q_std, node_std, e_std, peri_std, incl_std, show_plots=False, get_more_data=True)

        # Fill the rejected orbits with new ones, randomly generated
        random_choice = np.random.randint(0, len(q_more)-1, missing_orbits_n)
        inv_a_generated = np.r_[inv_a_generated, inv_a_more[random_choice]]
        q_generated = np.r_[q_generated, q_more[random_choice]]
        e_generated = np.r_[e_generated, e_more[random_choice]]
        peri_generated = np.r_[peri_generated, peri_more[random_choice]]
        node_generated = np.r_[node_generated, node_more[random_choice]]
        incl_generated = np.r_[incl_generated, incl_more[random_choice]]

    if show_plots:

        # Plot all results
        plotAllResults(loaded_data, inv_a_generated, q_generated, e_generated, peri_generated, node_generated,
            incl_generated, q_std, e_std, peri_std, node_std, incl_std, data_label='jopek')

    return inv_a_generated, q_generated, e_generated, peri_generated, node_generated, incl_generated

def generateKDEOrbits(loaded_data, n_samples, bandwidth, q_std, node_std, e_std, peri_std, incl_std,
    covariance=None, show_plots=True, get_more_data=False):
    """ Implementation of the KDE-based orbit generation.

    Arguments:
        loaded_data: [2D ndarray] numpy array containing the input data, column indices are defined at the
            beginning of this file in uppercase letters
        n_samples: [int] number of orbits to draw
        bandwidth: [float] a number by which the covariance matrix will be multipled with
        q_std: [float] standard deviation of the perihelion distance, used for plotting 2D histograms
        node_std: [float] standard deviation of the node, used for plotting 2D histograms
        e_std: [float] standard deviation of the eccentricity, used for plotting 2D histograms
        peri_std: [float] standard deviation of the argument of perihelion, used for plotting 2D histograms
        incl_std: [float] standard deviation of the inclination, used for plotting 2D histograms

    Keyword arguments:
        covariance: [2D ndarray] None by default, custom covarience matrix
        show_plots: [bool] True by default, if False the plots are not generated, only data vectors are
            returned
        get_more_data: [bool] False by default, used internally as a flag to indicate that the function is in
            recursion, NOTE: do not set this value manually!

    Return:
        (inv_a_results, q_results, e_results, peri_results, node_results, incl_results): [tuple of ndarrays]
            orbital elements of generated orbits

    """

    # Extract individual parameters from the loaded data and rescale them
    inv_a_array = 1.0/loaded_data[:, A_INDX]
    q_array = loaded_data[:, Q_INDX]
    node_array = np.radians(loaded_data[:, NODE_INDX])
    e_array = loaded_data[:, ECC_INDX]
    peri_array = np.radians(loaded_data[:, PERI_INDX])
    incl_array = np.radians(loaded_data[:, INCL_INDX])

    # Create a matrix of the input parameters
    kde_matrix = np.column_stack((q_array, node_array, e_array, peri_array, incl_array))

    # Create the Gaussian kernel density estimate
    kde_kernel = stat.gaussian_kde(kde_matrix.T)

    # Make a covariance matrix if none was given
    if covariance is None:

        # Set the covariance matrix
        kde_kernel.covariance = bandwidth*np.array(
            [[q_std,          0,             0,          0,                 0],
            [   0,   np.radians(node_std),   0,          0,                 0],
            [   0,            0,           e_std,        0,                 0],
            [   0,            0,             0,   np.radians(peri_std),     0],
            [   0,            0,             0,          0,      np.radians(incl_std)]])**2

    else:

        # Use the covariance matrix if it was given
        kde_kernel.covariance = bandwidth*covariance

    # Set the inverse of the covariance
    kde_kernel.inv_cov = np.linalg.inv(kde_kernel.covariance)

    # Draw sample orbits from the KDE
    kde_results = kde_kernel.resample(n_samples)

    # Extract individual parameter arrays
    q_results, node_results, e_results, peri_results, incl_results = np.hsplit(kde_results.T, 5)

    # Convert the input to degees
    node_array = np.degrees(node_array)
    peri_array = np.degrees(peri_array)
    incl_array = np.degrees(incl_array)

    # Convert results to degrees (and wrap to 360)
    node_results = np.degrees(node_results) % 360
    peri_results = np.degrees(peri_results) % 360

    # Convert the inclination to degrees, and mirror the edges
    incl_results = np.degrees(incl_results)
    incl_results[incl_results < 0] *= -1
    incl_results[incl_results > 180] = 360 - incl_results[incl_results > 180]

    # Calculate 1/a from the results
    inv_a_results = (1.0 - e_results)/q_results

    ### Cut the resulting range to the input range
    input_names = ['1/a', 'q', 'node', 'e', 'peri', 'incl']
    input_list = [inv_a_array, q_array, node_array, e_array, peri_array, incl_array]
    results_list = [inv_a_results, q_results, node_results, e_results, peri_results, incl_results]

    for i, result in enumerate(results_list):

        # Find the input min and max
        min_input, max_input = min(input_list[i]), max(input_list[i])

        # # Print the range of the input data
        # print('Parameter:', input_names[i], 'Min:', min_input, 'Max:', max_input)

        # Find the indices of the results that are inside the input range
        inside_range_indices = np.where((result >= min_input) & (result <= max_input))

        # Filter every dimension
        for j in range(len(results_list)):
            results_list[j] = results_list[j][inside_range_indices]

    # Unpack the filtered results
    inv_a_results, q_results, node_results, e_results, peri_results, incl_results = results_list

    ###

    if not get_more_data:

	    # Generate some more data to replace the rejected orbits:
	    while len(q_results) < n_samples:

	        # print('Rejected orbits percent:', (1.0 - len(q_results)/n_samples)*100.0)

	        # Calculate the number of missing orbits
	        missing_orbits_n = n_samples - len(q_results)

	        # print('Missing orbits:', missing_orbits_n)

	        # Generate new synthetic orbits
	        inv_a_more, q_more, e_more, peri_more, node_more, incl_more = generateKDEOrbits(loaded_data,
	            n_samples, bandwidth, q_std, node_std, e_std, peri_std, incl_std, show_plots=False,
	            get_more_data=True)

	        # Fill the rejected orbits with new ones, which are randomly chosen
	        random_choice = np.random.randint(0, len(q_more) - 1, missing_orbits_n)
	        inv_a_results = np.r_[inv_a_results, inv_a_more[random_choice]]
	        q_results = np.r_[q_results, q_more[random_choice]]
	        e_results = np.r_[e_results, e_more[random_choice]]
	        peri_results = np.r_[peri_results, peri_more[random_choice]]
	        node_results = np.r_[node_results, node_more[random_choice]]
	        incl_results = np.r_[incl_results, incl_more[random_choice]]

	        ### Remove orbit duplicates ###

	        synthetic_data_array = np.column_stack([inv_a_results, q_results, e_results, incl_results, \
	        	node_results, peri_results])

	        synthetic_data_array = np.unique(synthetic_data_array, axis=0)

	        inv_a_results, q_results, e_results, incl_results, node_results, \
	        	peri_results = synthetic_data_array.T

	        ### ###

    if show_plots:

        ### Discern what file name this data should have

        # If the bandwidth is a scalar, use that scalar as a label
        if np.isscalar(bandwidth):
            bandwidth_label = str(bandwidth).replace('.', '_')

        # If bandwidth is a matrix, mark it as nonscalar and use the sum of the diagonal as the label
        else:
            bandwidth_label = 'nonscalar_' + str(np.sum(bandwidth.diagonal())).replace('.', '_')

        ###

        # Plot all results
        plotAllResults(loaded_data, inv_a_results, q_results, e_results, peri_results, node_results,
            incl_results, q_std, e_std, peri_std, node_std, incl_std,
            data_label='kde_bandwidth_' + str(bandwidth_label))

    return inv_a_results, q_results, e_results, peri_results, node_results, incl_results

def hist2dDiff(original_data, synthetic_data, x_std, y_std, x_index, y_index):
    """ Calculate the difference between histograms of the original and simulated data.

    The bin widths are chosen to be similar to the standard deviation of the data.

    Arguments:
        original_data: [2D ndarray] 2D numpy array containing the observed data
        synthetic_data: [2D ndarray] 2D numpy array containing synthetic data
        x_std: [float] standard deviation of the axis X data
        y_std: [float] standard deviation of the axis Y data
        x_index: [int] column index of the X axis orbital element in the data
        y_index: [int] column index of the Y axis orbital element in the data

    Return:
        [float] Chi Squared distance between the observed and synthetic data histograms

    """

    ### Calculate the number of bins

    # Get the data range
    x_min, x_max = min(original_data[:, x_index]), max(original_data[:, x_index])
    y_min, y_max = min(original_data[:, y_index]), max(original_data[:, y_index])

    x_range = x_max - x_min
    y_range = y_max - y_min

    # Calculate the number of bins used
    x_nbins = int(x_range/x_std)
    y_nbins = int(y_range/y_std)

    ###

    # Create a histogram of the original data
    orig_H, _, _ = np.histogram2d(original_data[:, x_index], original_data[:, y_index], bins=[x_nbins, \
        y_nbins])

    # Create a histogram of the synthetic data
    synt_H, _, _  = np.histogram2d(synthetic_data[:, x_index], synthetic_data[:, y_index], bins=[x_nbins, \
        y_nbins])

    # How many percent of total points to take to estimate the average difference
    top_n_percent = 0.01

    top_n = int(x_nbins*y_nbins*(1.0 - top_n_percent))

    # Calculate the mean of top 1% absolute differences
    return np.mean(np.sort(np.abs(orig_H - synt_H).ravel())[top_n:])

def hist2dChiSquared(original_data, synthetic_data, x_std, y_std, x_index, y_index):
    """ Calculate the Chi Squared between histograms of the original and simulated data.

    Reference: Pele, O. and Werman, M., 2010, September. The quadratic-chi histogram distance family.
        In European conference on computer vision (pp. 749-762). Springer Berlin Heidelberg.

    The bin widths are chosen to be similar to the standard deviation of the data.

    Arguments:
        original_data: [2D ndarray] 2D numpy array containing the observed data
        synthetic_data: [2D ndarray] 2D numpy array containing synthetic data
        x_std: [float] standard deviation of the axis X data
        y_std: [float] standard deviation of the axis Y data
        x_index: [int] column index of the X axis orbital element in the data
        y_index: [int] column index of the Y axis orbital element in the data

    Return:
        [float] Chi Squared distance between the observed and synthetic data histograms

    """

    ### Calculate the number of bins

    # Get the data range
    x_min, x_max = min(original_data[:, x_index]), max(original_data[:, x_index])
    y_min, y_max = min(original_data[:, y_index]), max(original_data[:, y_index])

    x_range = x_max - x_min
    y_range = y_max - y_min

    # Calculate the number of bins used
    x_nbins = int(x_range/x_std)
    y_nbins = int(y_range/y_std)

    ###

    # Create a histogram of the original data
    orig_H, _, _ = np.histogram2d(original_data[:, x_index], original_data[:, y_index], bins=[x_nbins, \
        y_nbins], range=[[x_min, x_max], [y_min, y_max]])

    # Create a histogram of the synthetic data
    synt_H, _, _  = np.histogram2d(synthetic_data[:, x_index], synthetic_data[:, y_index], bins=[x_nbins, \
        y_nbins], range=[[x_min, x_max], [y_min, y_max]])

    chi_sq_sum = 0

    # Calculate the sum of Chi Squared
    for synth_row, orig_row in zip(synt_H, orig_H):
        for synth_element, orig_element in zip(synth_row, orig_row):

            if (synth_element == 0) and (orig_element == 0):
                continue

            chi_sq_sum += ((synth_element - orig_element)**2)/(synth_element + orig_element)

    chi_sq_sum = (1/2.0)*chi_sq_sum

    return chi_sq_sum

def getHistDiff(loaded_data, n_samples, bandwidth, q_std, node_std, e_std, peri_std, incl_std,
        generate_type='kde'):
    """ Generate orbits by either the KDE method using the given bandwidth, or Jopek's method E, and calculate
        the Chi Squared histogram distance.

    Arguments:
        loaded_data: [2D ndarray] numpy array containing the input data, column indices are defined at the
            beginning of this file in uppercase letters
        n_samples: [int] number of orbits to draw
        bandwidth: [float] a number by which the covariance matrix will be multipled with
        q_std: [float] standard deviation of the perihelion distance, used for plotting 2D histograms
        node_std: [float] standard deviation of the node, used for plotting 2D histograms
        e_std: [float] standard deviation of the eccentricity, used for plotting 2D histograms
        peri_std: [float] standard deviation of the argument of perihelion, used for plotting 2D histograms
        incl_std: [float] standard deviation of the inclination, used for plotting 2D histograms

    Keyword arguments:
        generate_type: [str] If "kde", KDE orbits will ge generated, if "jopek", jopek method E orbits will be
            generated. "kde" by default.

    Return:
        [float] Chi Squared histogram distance between the observed and synthetic data histograms

    """

    # Extract individual parameters from the loaded data
    q_array = loaded_data[:, Q_INDX]
    node_array = loaded_data[:, NODE_INDX]
    e_array = loaded_data[:, ECC_INDX]
    peri_array = loaded_data[:, PERI_INDX]
    incl_array = loaded_data[:, INCL_INDX]

    # Make the 2D observed data array
    original_data_array = np.column_stack([q_array, e_array, peri_array, node_array, incl_array])

    if generate_type == 'kde':

        # Draw KDE orbits
        inv_a_generated, q_generated, e_generated, peri_generated, node_generated, \
            incl_generated = generateKDEOrbits(loaded_data, n_samples, bandwidth, q_std, node_std, e_std,
                peri_std, incl_std, show_plots=False)

    elif generate_type == 'jopek':

        # Generate synthetic sporadic orbits using Jopek & Bronikowska (2016) method E
        inv_a_generated, q_generated, e_generated, peri_generated, node_generated, \
            incl_generated = generateJopekMethodE(loaded_data, len(loaded_data), q_std, node_std, e_std,
                peri_std, incl_std, show_plots=False)

    # Make the 2D synthetic data array
    synthetic_data_array = np.column_stack([q_generated, e_generated, peri_generated, node_generated,
        incl_generated])

    # Set the histogram comparison axes as peri and q
    peri_index = 2 # peri index
    q_index = 0 # q index

    # Calculate the histogram difference
    #return hist2dDiff(original_data_array, synthetic_data_array, peri_std, q_std, peri_index, q_index)
    return hist2dChiSquared(original_data_array, synthetic_data_array, peri_std, q_std, peri_index, q_index)

def exploreBandwidth(loaded_data, n_samples, q_std, node_std, e_std, peri_std, incl_std):
    """ Run the KDE for a range of bandwidths and calculate Chi Squared histogram distance for each bandwidth.

    Arguments:
        loaded_data: [2D ndarray] numpy array containing the input data, column indices are defined at the
            beginning of this file in uppercase letters
        n_samples: [int] number of orbits to draw
        q_std: [float] standard deviation of the perihelion distance, used for plotting 2D histograms
        node_std: [float] standard deviation of the node, used for plotting 2D histograms
        e_std: [float] standard deviation of the eccentricity, used for plotting 2D histograms
        peri_std: [float] standard deviation of the argument of perihelion, used for plotting 2D histograms
        incl_std: [float] standard deviation of the inclination, used for plotting 2D histograms

    Return:
        None
    """

    # Create a log distribution of bandwidth points from 10.0 to 0.01
    bandwidth_range = np.logspace(1, -2, 20)

    # Extract individual parameters from the loaded data and rescale them
    q_array = loaded_data[:, Q_INDX]
    node_array = loaded_data[:, NODE_INDX]
    e_array = loaded_data[:, ECC_INDX]
    peri_array = loaded_data[:, PERI_INDX]
    incl_array = loaded_data[:, INCL_INDX]

    # Make the 2D data arrays
    original_data_array = np.column_stack([q_array, e_array, peri_array, node_array, incl_array])

    histogram_differences = []

    # Go through the given bandwidths
    for bandwidth in bandwidth_range:

        # Repeat 10 times and take the average
        temp_hist_diff = []

        for i in range(10):

            # Sample orbits from KDE
            inv_a_generated, q_generated, e_generated, peri_generated, node_generated, \
                incl_generated = generateKDEOrbits(loaded_data, n_samples, bandwidth, q_std, node_std, e_std,
                    peri_std, incl_std, show_plots=False)

            synthetic_data_array = np.column_stack([q_generated, e_generated, peri_generated, node_generated,
                incl_generated])

            # Set the histogram comparison axes as peri and q
            peri_index = 2
            q_index = 0 # q index

            # # Calculate the top 1% histogram difference
            # temp_hist_diff.append(hist2dDiff(original_data_array, synthetic_data_array, peri_std, q_std,
            #     peri_index, q_index))

            # Calculate the Chi Squared of the histograms
            temp_hist_diff.append(hist2dChiSquared(original_data_array, synthetic_data_array, peri_std, q_std,
                peri_index, q_index))

        histogram_differences.append(np.mean(temp_hist_diff))

    print('Bandwidths:', bandwidth_range)
    print('Histogram diffs:', histogram_differences)

    # Save the numpy arrays to disk
    np.save('bandwidth_range', np.array(bandwidth_range))
    np.save('histogram_differences', np.array(histogram_differences))

    plt.plot(bandwidth_range, histogram_differences)

    # Flip the X axis
    plt.gca().invert_xaxis()

    # Set X axis as log
    plt.gca().set_xscale('log')

    # Set axes labels
    plt.xlabel('Bandwidth')
    #plt.ylabel('Mean of top 1% absolute histogram differences')
    plt.ylabel('$\chi^2$ histogram distance')

    plt.grid()

    plt.show()

def calculateNN(loaded_data, n_samples, q_std, node_std, e_std, peri_std, incl_std):
    """ Calculate the average nearest neightbour distance of the synthetic dataset for a range of bandwidths.

    Arguments:
        loaded_data: [2D ndarray] numpy array containing the input data, column indices are defined at the
            beginning of this file in uppercase letters
        n_samples: [int] number of orbits to draw
        q_std: [float] standard deviation of the perihelion distance, used for plotting 2D histograms
        node_std: [float] standard deviation of the node, used for plotting 2D histograms
        e_std: [float] standard deviation of the eccentricity, used for plotting 2D histograms
        peri_std: [float] standard deviation of the argument of perihelion, used for plotting 2D histograms
        incl_std: [float] standard deviation of the inclination, used for plotting 2D histograms

    Return:
        None
    """

    # Check the nn for a range of bandwidths (from 10.0 to 0.01)
    bandwidth_range = np.logspace(1, -2, 20)

    nn_results = []
    for bandwidth in bandwidth_range:

        # Draw orbits from the KDE
        inv_a_generated, q_generated, e_generated, peri_generated, node_generated, \
            incl_generated = generateKDEOrbits(loaded_data, n_samples, bandwidth, q_std, node_std, e_std,
                peri_std, incl_std, show_plots=False)

        # Make the 2D data arrays
        synthetic_data_array = np.column_stack([q_generated, e_generated, incl_generated, node_generated,
            peri_generated])

        # Find the average NN distance
        synthetic_nn = findNearestNeighborDistDSH(synthetic_data_array)
        print('Synthetic NN', synthetic_nn)

        nn_results.append(synthetic_nn)

    print('---------------')
    print('Bandwidths:', bandwidth_range)
    print('NN results:', nn_results)

    # Save the numpy arrays to disk
    np.save('bandwidth_range', np.array(bandwidth_range))
    np.save('nn_results', np.array(nn_results))

    # Flip the X axis
    plt.gca().invert_xaxis()

    # Set X axis as log
    plt.gca().set_xscale('log')

    plt.plot(bandwidth_range, nn_results)
    plt.xlabel('Bandwidth')
    plt.ylabel('Average nearest neighbor, $D_{SH}$')

    plt.show()
    plt.clf()
    plt.close()

def estimateBandwidthMatrixWithNN(loaded_data, n_samples, q_std, node_std, e_std, peri_std, incl_std):
    """ Test a range of q and peri bandwidths and find the mean NN distance and histogram difference for each.

    Arguments:
        loaded_data: [2D ndarray] numpy array containing the input data, column indices are defined at the
            beginning of this file in uppercase letters
        n_samples: [int] number of orbits to draw
        q_std: [float] standard deviation of the perihelion distance, used for plotting 2D histograms
        node_std: [float] standard deviation of the node, used for plotting 2D histograms
        e_std: [float] standard deviation of the eccentricity, used for plotting 2D histograms
        peri_std: [float] standard deviation of the argument of perihelion, used for plotting 2D histograms
        incl_std: [float] standard deviation of the inclination, used for plotting 2D histograms

    Return:
        None
        """

    # Extract individual parameters from the loaded data and rescale them
    q_array = loaded_data[:, Q_INDX]
    node_array = loaded_data[:, NODE_INDX]
    e_array = loaded_data[:, ECC_INDX]
    peri_array = loaded_data[:, PERI_INDX]
    incl_array = loaded_data[:, INCL_INDX]

    # Make the 2D data arrays
    original_data_array = np.column_stack([q_array, e_array, peri_array, node_array, incl_array])

    # Check the NN for a range of q bandwidths
    q_bandwidth_range = np.logspace(1, -3, 40)

    # Check the NN for a range of peri bandwidths
    peri_bandwidth_range = np.logspace(1, -1, 40)

    # Form the results matrix
    nn_results_matrix = np.zeros((len(q_bandwidth_range), len(peri_bandwidth_range)))

    # Histogram differences matrix
    hist_diff_matrix = np.zeros((len(q_bandwidth_range), len(peri_bandwidth_range)))

    # Bandwidth for other parameters
    bandwidth = 10.0

    for i, q_bandwidth in enumerate(q_bandwidth_range):

        for j, peri_bandwidth in enumerate(peri_bandwidth_range):

            # Init the bandwidth matrix
            bandwidth_matrix = np.array(
                [[q_bandwidth,    0,       0,       0,        0], # q
                [   0,   bandwidth,   0,       0,        0], # node
                [   0,      0,     bandwidth,     0,        0], # e
                [   0,      0,       0,   peri_bandwidth,     0], # peri
                [   0,      0,       0,       0,    bandwidth]]) # i

            # Run the new method
            inv_a_generated, q_generated, e_generated, peri_generated, node_generated, \
                incl_generated = generateKDEOrbits(loaded_data, n_samples, bandwidth_matrix, q_std, node_std,
                    e_std, peri_std, incl_std, show_plots=False)

            # Create a synthetic data srray
            synthetic_data_array = np.column_stack([q_generated, e_generated, peri_generated, node_generated,
                incl_generated])

            # Check the nearest neighbor distance
            synthetic_nn = findNearestNeighborDistDSH(synthetic_data_array)

            # Save the results to a final matrix
            nn_results_matrix[i,j] = synthetic_nn

            print('---------')
            print('q bandwidth:', q_bandwidth, 'peri bandwidth:', peri_bandwidth)
            print('Mean NN:', synthetic_nn)

            # Set the histogram comparison axes as peri and q
            peri_index = 2
            q_index = 0 # q index

            # # Calculate the histogram difference
            # hist_diff = hist2dDiff(original_data_array, synthetic_data_array, peri_std, q_std,
            #     peri_index, q_index)

            # Calculate the Chi Squared histogram distance
            hist_diff = hist2dChiSquared(original_data_array, synthetic_data_array, peri_std, q_std,
                peri_index, q_index)

            # Save the result to the histogram matrix
            hist_diff_matrix[i,j] = hist_diff

            print('Hist diff:', hist_diff)

    print('RESULTS:')
    print('Y axis: q bandwidth range:', q_bandwidth_range)
    print('X axis: Peri bandwidth range:', peri_bandwidth_range)
    print('NN matrix:')
    print(nn_results_matrix)
    print('Histogram difference:')
    print(hist_diff_matrix)

    # Save the results to files
    np.save('q_bandwidths', q_bandwidth_range)
    np.save('peri_bandwidths', peri_bandwidth_range)
    np.save('nn_results_matrix', nn_results_matrix)
    np.save('hist_diff_matrix', hist_diff_matrix)

def savePlot(plt, file_name, data_label=''):
    """ Saves the plot to disk.

    Arguments:
        plt: [plot handle]
        file_name: [str] name of the file where the plot will be saved

    Keyword arguments:
        data_label: [str] add this prefix in front of each name of the file that is saved to disk

    Return:
        None

    """

    if data_label != '':
        data_label += '_'

    # Save the plot to disk
    plt.savefig(data_label + file_name.replace('$', '')+'.png', dpi=300)

# Main part of the program
if __name__ == "__main__":

    # Name of the input data file
    input_file_name = "CAMS-v2-2013.csv"

    ### Set data parameters

    # Range of solar longitudes to use
    sol_min = 0
    sol_max = 360

    # Convergence angle threshold
    Q_thresh = 15.0 # deg

    # Geocentric velocity threshold
    Vg_error_thresh = 0.1

    # Set the random number generator seed so the results are reproducible
    np.random.seed(1)

    ###

    # Load CAMS data (sporadics only, thus shower_num=0)
    print('Loading data...')
    loaded_data = loadDataCAMS(input_file_name, sol_min, sol_max, Q_thresh, Vg_error_thresh,
        shower_num=0).astype(np.float64)

    # Reject all hyperbolic and parabolic orbits
    loaded_data = loaded_data[loaded_data[:, ECC_INDX] < 1.0]
    print('Input data size:', len(loaded_data))

    # Find unique orbits
    meteor_ids = loaded_data[:, 8:13]
    b = np.ascontiguousarray(meteor_ids).view(np.dtype((np.void, meteor_ids.dtype.itemsize*meteor_ids.shape[1])))
    _, idx = np.unique(b, return_index=True)

    unique_data = meteor_ids[idx]

    print('Unique orbits:', len(unique_data))

    # Take only unique orbits
    loaded_data = loaded_data[idx]

    # Define the number of orbits to draw from the KDE (the same as the number of observed input orbits)
    n_samples = len(loaded_data)

    # Calculate means of standard deviations
    q_std = np.mean(loaded_data[:, Q_STD_INDX])
    e_std = np.mean(loaded_data[:, ECC_STD_INDX])
    incl_std = np.mean(loaded_data[:, INCL_STD_INDX])
    peri_std = np.mean(loaded_data[:, PERI_STD_INDX])

    node_std = np.mean(loaded_data[:, NODE_STD_INDX])

    # # Uncomment this part if you want to see how node stddev changes with inclination
    # # Inclination vs. node stddev plot
    # plt.scatter(loaded_data[:, INCL_INDX], loaded_data[:, NODE_STD_INDX])
    # plt.xlabel('Inclination')
    # plt.ylabel('Node SD')
    # plt.xlim([-1, 181])
    # plt.ylim([0, np.max(loaded_data[:, NODE_STD_INDX])])
    # plt.show()
    # plt.clf()

    # # Filter nodes std's by inclination (low inclinations have huge deviations in node, so take only those
    # # in the 20 deg to 160 deg range for standard deviation calculation)
    # node_std_filtered = loaded_data[:, NODE_STD_INDX][np.where((loaded_data[:, INCL_INDX] > 20) & \
    #    (loaded_data[:, INCL_INDX] < 160))]
    # node_std = np.mean(node_std_filtered)

    print('Standard deviations, mean:')
    print('q', q_std)
    print('e', e_std)
    print('incl', incl_std)
    print('node', node_std)
    print('peri', peri_std)
    print('-----')

    print('Jopek method results')

    # Generate synthetic sporadic orbits using Jopek & Bronikowska (2016) method E
    inv_a_jopek, q_jopek, e_jopek, peri_jopek, node_jopek, \
        incl_jopek = generateJopekMethodE(loaded_data, len(loaded_data), q_std, node_std, e_std, peri_std,
            incl_std)

    # Calculate the histogram difference
    jopek_hist_diff = getHistDiff(loaded_data, n_samples, 0, q_std, node_std, e_std, peri_std, incl_std,
        generate_type='jopek')

    print('Jopek Chi Squared histogram distance:', jopek_hist_diff)

    # # Uncomment if you wish to calculate the nearest neighbour distance of orbits generated using Method E
    # jopek_data_array = np.column_stack([q_jopek, e_jopek, incl_jopek, node_jopek, peri_jopek])
    # jopek_nn = findNearestNeighborDistDSH(jopek_data_array)
    # print('Jopek NN:', jopek_nn)

    # Extract individual parameters from the loaded data
    q_array = loaded_data[:, Q_INDX]
    node_array = loaded_data[:, NODE_INDX]
    e_array = loaded_data[:, ECC_INDX]
    peri_array = loaded_data[:, PERI_INDX]
    incl_array = loaded_data[:, INCL_INDX]

    ### UNCOMMENT IF YOU WISH TO GENERATE THE PLOT WHICH SHOWS THE INFLUENCE OF DIFFERENT BANDWIDTHS ON
    ### HISTOGRAM DIFFERENCES OR NEAREST NEIGHBOUR VALUE - ONLY FOR SCALAR MATRIX BANDWIDTH

    # # Run bandwidth estimation
    # exploreBandwidth(loaded_data, n_samples, q_std, node_std, e_std, peri_std, incl_std)

    # # Calculate Nearest Neighbor distances for a variety of bandwidths
    # calculateNN(loaded_data, n_samples, q_std, node_std, e_std, peri_std, incl_std)

    # sys.exit()

    ###

    ### UNCOMMENT IF YOU WANT TO GENERATE THE 2D HISTOGRAM PLOT OF q AND peri BANDWIDTHS INFLUENCE ON
    ### HISTOGRAM DIFFERENCES OR NEAREST NEIGHBOUR VALUE

    ### WARNING: This calculation can take >24 hours, depending on the system
    # # Check a range of q and peri bandwidths to see how the mean NN and histogram difference behaves
    # estimateBandwidthMatrixWithNN(loaded_data, n_samples, q_std, node_std, e_std, peri_std, incl_std)

    # sys.exit()

    ###

    # Define the nonscalar bandwidth matrix
    bandwidth = 10

    bandwidth_matrix = np.array(
        [[0.01,    0,       0,       0,        0], # q
        [   0,   bandwidth,   0,       0,        0], # node
        [   0,      0,     bandwidth,     0,        0], # e
        [   0,      0,       0,   6,     0], # peri
        [   0,      0,       0,       0,    bandwidth]]) # i

    # Make a list of bandwidths to go through
    bandwidth_list = [10, 0.1, bandwidth_matrix]

    for bandwidth in bandwidth_list:

        print('KDE, bandwidth:', bandwidth)

        # Draw KDE orbits
        inv_a_generated, q_generated, e_generated, peri_generated, node_generated, \
            incl_generated = generateKDEOrbits(loaded_data, n_samples, bandwidth, q_std, node_std, e_std,
                peri_std, incl_std)

        original_data_array = np.column_stack([q_array, e_array, incl_array, node_array, peri_array])
        synthetic_data_array = np.column_stack([q_generated, e_generated, incl_generated, node_generated,
            peri_generated])

        # print('Total orbits:', len(synthetic_data_array))
        # print('Unique orbits:', len(np.unique(synthetic_data_array, axis=0)))

        # # UNCOMMENT if you want to find nearest neighbors distance (WARNING: it takes a while)
        # original_nn = findNearestNeighborDistDSH(original_data_array)
        # print('Observed NN:', original_nn)

        # # Check the nearest neighbor distance

        # synthetic_nn = findNearestNeighborDistDSH(synthetic_data_array)
        # print('Synthetic NN', synthetic_nn)

        # # Find the average minimum DSH between the original and synthetic data
        # avg_min_dsh = findMinimumDSH(original_data_array, synthetic_data_array)

        # print('Average minimum DSH::', avg_min_dsh)

        # Calculate the histogram difference
        hist_diff = getHistDiff(loaded_data, n_samples, bandwidth, q_std, node_std, e_std, peri_std, incl_std,
            generate_type='kde')
        print('Chi Squared histogram distance:', hist_diff)

        # # Print out the results
        # print('   1/a,    Q,    Node,   e,    peri,   incl')
        # for line in np.hstack([inv_a_results, q_results, node_results, e_results, peri_results, incl_results]):
        #     inv_a, q, node, e, peri, incl = line

        #     pattern = "%.3f"
        #     print([float(pattern % i) for i in (inv_a, q, node, e, peri, incl)])


#!/usr/bin/env python
# -*- coding: utf-8 -*-
import argparse
from PIL import Image, ImageChops, Image, ImageDraw, ImageFont, ImageStat
import logging
import os
import argparse
import matplotlib.pyplot as plt

def get_parser():
    parser = argparse.ArgumentParser(
        description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)

    parser.add_argument("-v", "--verbose",
                            action="store_true", help="be verbose")
    parser.add_argument("-s", "--show",
                            action="store_true", help="be verbose")
    parser.add_argument("samples",
                        help="be verbose", type=int)
    parser.add_argument("conditions",
                        help="be verbose", type=int)

    parser.add_argument("file", help="", default="") # nargs='+')
    return parser

def get_rms(im):
    stat = ImageStat.Stat(im)
    #r,g,b = stat.mean
    ## print('bg sum', stat.sum[0])
    ## print('bg mean', stat.mean[0])
    ## print('bg rms', stat.rms[0])
    return stat.rms[0]

if __name__ == '__main__':
    parser = get_parser()
    args = parser.parse_args()

    img = Image.open(args.file)
    # design 8x6
    y = 0
    x = 0
    sample = args.samples
    delx = img.width / args.samples
    dely = img.height / args.conditions

    dat = []
    for iy in range(0, args.conditions):
        vector = []
        for ix in range(0, args.samples):
            tmp = img.copy()
            if args.verbose: print((x, y, delx, dely))
            dot = tmp.crop((x, y, x+delx, y+dely))
            if args.show:
                dot.show()
            if ix == 0: # reset this is wt
                wt = get_rms(dot)
                vector.append(wt - wt) # ugly hack to see anything
            else:
                sample_rms = get_rms(dot)
                vector.append(wt - sample_rms)
            x += delx
        dat.append(vector)
        y += dely
        x = 0

    if args.verbose: print(dat)

    import numpy as np
    m = np.array(dat)
    if args.verbose: print(m)

    column_sums = m.sum(axis=0)
    print(column_sums)
    import pandas as pd
    df = pd.DataFrame({'score' : column_sums})#, index = ['wt','del',34,39,40,410,41,42])
    df.plot.bar()

    for i in df['score']:
        print(round(i, 2))

    print(df['score'] / float(sample))
    df['score'] = df['score'] / sample / df['score'].std() * -1  # -1 gives - scores for worsen growth
    #print(df['score'])

    for i in df['score']:
        print(round(i, 2))

    print()
    for i in df['score']:
        print(round(i, 2), end='\t')
    print()

    if 0:
        from sklearn import preprocessing
        x = df.values #returns a numpy array
        min_max_scaler = preprocessing.MinMaxScaler()
        x_scaled = min_max_scaler.fit_transform(x)
        df = pd.DataFrame(x_scaled) #, index =
        #df.set_index(['wt','del',34,39,40,410,41,42])
        #print(df)
    df.plot.bar()
    f = os.path.splitext(args.file)[0]
    outfn = f + '_plot.png'
    print('Output created ' + outfn)
    plt.savefig(outfn)


from PIL import Image
import os
import cv2
import hydra
from src.pseudo_touch.utils.utils import search_folder

module_dir = os.path.dirname(__file__)  # get current directory
files = os.listdir(module_dir)

if __name__ == "__main__":
    # script meta data, configuration and constants
    start_path = "/"
    hydra.initialize("../cfg", version_base=None)
    cfg = hydra.compose("collection.yaml")
    cfg.repository_directory = search_folder(start_path, cfg.repository_directory)
    cfg.experiment_directory = f"{cfg.repository_directory}/{cfg.experiment_directory}"
    objects_array = cfg.object_name.split(",")
    i = 0
    for object_name in objects_array:
        path = (
            cfg.experiment_directory
            + "/"
            + object_name
            + "/"
            + object_name
            + "_images"
            + "/depth"
        )
        files = os.listdir(path)
        for file in files:
            if file.endswith(".tif"):
                image = cv2.imread(path + "/" + file, cv2.IMREAD_UNCHANGED)
                if image.shape[0] < 48 or image.shape[1] < 48:
                    while image.shape[0] < 48 or image.shape[1] < 48:
                        image = cv2.resize(
                            image, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC
                        )
                    cv2.imwrite(path + "/" + file, image)
                    i += 1
        print(f"Upscaled {i} images")


seq_one = set(int(x) for x in input().split())
seq_two = set(int(x) for x in input().split())

n = int(input())

for _ in range(n):
    cmd = input().split()
    if cmd[0] == 'Add':
        if cmd[1] == 'First':
            [seq_one.add(int(num)) for num in cmd[2:]]
        elif cmd[1] == 'Second':
            [seq_two.add(int(num)) for num in cmd[2:]]

    elif cmd[0] == 'Remove':
        set_to_remove = set()
        set_to_remove.update(int(n) for n in cmd[2:])

        if cmd[1] == 'First':
            seq_one = seq_one.difference(set_to_remove)
        elif cmd[1] == 'Second':
            seq_two = seq_two.difference(set_to_remove)

    elif cmd[0] == 'Check':
        is_subset = seq_one.issubset(seq_two) or seq_two.issubset(seq_one)
        print(is_subset)

print(*sorted(seq_one), sep=', ')
print(*sorted(seq_two), sep=', ')


#!/usr/bin/env python
# -*- coding: utf-8 -*-
# --------------------------------------------
# 简易日志服务器，用于接收 ChatGPT 前端日志
# --------------------------------------------

import base64
import argparse
from color_log.clog import log
from flask import Flask, request, jsonify
from flask_cors import CORS

APP = Flask(__name__)
CORS(APP)   # 允许跨域

def args() :
    parser = argparse.ArgumentParser(
        prog='部署用于接受 ChatGPT WEB 页面日志的服务器', # 会被 usage 覆盖
        usage='python ./py/log_server.py -s {host} -p {PORT}',
        description='部署用于接受 ChatGPT WEB 页面日志的服务器',
        epilog='python ./py/log_server.py -h'
    )
    parser.add_argument('-s', '--host', dest='host', type=str, default='0.0.0.0', help='日志服务监听 IP')
    parser.add_argument('-p', '--port', dest='port', type=int, default=5000, help='日志服务监听端口')
    return parser.parse_args()

def main(args) :
    APP.run(host=args.host, port=args.port)

@APP.route('/heartbeat', methods=['POST'])
def tolog():
    data = request.json
    base64_msg = data.get('message', '')
    msg = unbase64(base64_msg)
    log.info(msg)
    return jsonify({"status": "success"}), 200

def unbase64(text) :
    decoded_bytes = base64.b64decode(text)
    return decoded_bytes.decode('utf-8')

if __name__ == "__main__":
    main(args())


from shuup.core.models import Supplier
from shuup.testing.models import SupplierPrice

class CheapestSupplierPriceSupplierStrategy(object):
    def get_supplier(self, **kwargs):
        # Here we did some trick and passed different
        # kwargs than is passed from shop product get
        # supplier. Shouldn't be issue as long as this
        # strategy is able to adjust to both set of
        # kwargs.
        product_id = kwargs.get("product_id")
        if not product_id:
            shop_product = kwargs.get("shop_product")
            if not shop_product:
                return
            product_id = shop_product.product.pk

        shop = kwargs.get("shop")
        if not shop:
            shop_product = kwargs.get("shop_product")
            if not shop_product:
                return
            shop = shop_product.shop

        # Supplier with best price and fallback to
        # first shop product supplier. Likely there
        # needs to be cache around this in real solution,
        # but this is just a testing strategy to help
        # test supplier prices and multiple suppliers
        # with front.
        enabled_suppliers = Supplier.objects.enabled(shop=shop)

        supplier_price = (
            SupplierPrice.objects.filter(shop=shop, product_id=product_id, supplier__in=enabled_suppliers)
            .select_related("supplier")
            .order_by("amount_value")
            .first()
        )

        if supplier_price:
            return supplier_price.supplier

        return enabled_suppliers.filter(shop_products__product__id=product_id).first()


"""
Testing utilities.
"""

__author__ = "Lenz Furrer"

import io
from pathlib import Path

DATA = Path(__file__).parent / 'data'

def get_cases(fmts):
    return [(fmt, path) for fmt in fmts for path in Path(DATA, fmt).glob('*')]

def path_id(value):
    if isinstance(value, Path):
        return value.stem

def xopen(source, fmt):
    """Text or binary IO, depending on the format."""
    binary = fmt.endswith(('xml', '.zip', '.tgz'))
    if isinstance(source, Path):
        if binary:
            f = open(source, mode='rb')
        else:
            f = open(source, encoding='utf8')
    else:
        if binary:
            f = io.BytesIO(source)
        else:
            f = io.StringIO(source)
    return f


#import altair as alt
import datetime
import folium
import geopandas as gpd
import geopy
import json
import base64
import matplotlib.pyplot as plt
import networkx as nx
import numpy as np
import osmnx as ox
import pandas as pd
import plotly_express as px
import plotly.io as pio
import plotly.offline as pyo
import requests
import seaborn as sns
import streamlit as st
#import psycopg2

from folium.features import DivIcon
from googletrans import Translator
from PIL import Image
from shapely.geometry import Point, LineString
from spacy import displacy
from spacy_streamlit import visualize_ner
from streamlit_folium import folium_static
from folium.plugins import MarkerCluster

from branca.element import Figure
from streamlit_folium import folium_static
from geopy.geocoders import Nominatim

ox.config(use_cache=True, log_console=True)
pyo.init_notebook_mode(connected=True)

st.set_page_config(
    page_title="Automatic River Encroachment Detection - Bangladesh",
    layout="wide",
    initial_sidebar_state="expanded",
)

st.set_option('deprecation.showPyplotGlobalUse', False)

st.markdown(
    """

    """,
    unsafe_allow_html=True
)

st.sidebar.markdown('
Automatic River Encroachment Detection - Bangladesh
', unsafe_allow_html=True)

add_selectbox = st.sidebar.radio(
    "",
    ("Home", "About", "Features", "River Encroachment Detection", "Visualizations", "Conclusion", "Team")
)

if add_selectbox == 'Home':

    LOGO_IMAGE = "omdena-bangladesh-logo.png"

    st.markdown(
          """

        """,
        unsafe_allow_html=True
    )

    st.markdown(
          f"""

          """,
          unsafe_allow_html=True
    )

    st.subheader('PROBLEM STATEMENT')

    st.markdown('River encroachment is a threatening problem that has been going on for a few years now. Bangladesh Govt. has been relentlessly working to free rivers \
    from encroachment after ensuring their navigability by dredging them round the year. BIWTA has removed a total of 18,025 illegal structures from riverbanks between \
    2010 and 2019 and reclaimed 738 acres of riverbanks from encroachments in Dhaka and Narayanganj river port areas. The National River Conservation Commission says 50 \
    percent of the rivers have been freed from the clutch of grabbers, but the rate is only 7 percent in Barishal, the lawyer said. Bangladesh has around 800 rivers. The \
    State Minister recently said over 65,000 grabbers have encroached on the rivers.', unsafe_allow_html=True)

elif add_selectbox == 'About':

    st.subheader('ABOUT THE PROJECT')

    st.markdown('
Project Goals
', unsafe_allow_html=True)
    st.markdown('• Collect data from opensource satellite images of specific rivers of Bangladesh, such as Buriganga, Meghna, etc, and extract necessary information with remote sensing analysis.', unsafe_allow_html=True)
    st.markdown('• Process the data following a systematic methodology and do exploratory data analysis of illegally occupied river banks.', unsafe_allow_html=True)
    st.markdown('• Develop an automatic encroachment detection for certain rivers and deliver geolocations of them.', unsafe_allow_html=True)
    st.markdown('• Outline an AI-driven solution to improve the cautioning system for Bangladesh.', unsafe_allow_html=True)

    st.markdown('
Locations Choosen
', unsafe_allow_html=True)
    st.markdown('We had choosen "Keraniganj", "Kamrangirchar" and "Bosilsaa" as our regions of interest, which is onto the river banks of "Buriganga" river. Its located in the city premises of Dhaka',
                unsafe_allow_html=True)

    st.markdown('
Developments Made
', unsafe_allow_html=True)
    st.markdown('• ',
                unsafe_allow_html=True)
    st.markdown('• '
                , unsafe_allow_html=True)

elif add_selectbox == 'Features':

    st.subheader('PROJECT ENDORSEMENTS')

    st.markdown('• ', unsafe_allow_html=True)
    st.markdown('• ', unsafe_allow_html=True)
    st.markdown('• ', unsafe_allow_html=True)

elif add_selectbox == 'River Encroachment Detection':

    st.subheader('FIND THE ENCROACHMENT SPOTS')

elif add_selectbox == 'Visualizations':

    st.subheader('PROJECT VISUALIZATIONS')
    st.markdown('
Topic1
', unsafe_allow_html=True)
    st.image("Topic1.png", width=500)
    st.markdown('
Topic2
', unsafe_allow_html=True)
    st.image("Topic2.png", width=500)
    st.markdown('
Topic3
', unsafe_allow_html=True)
    st.image("Topic3.jpg", width=500)
    st.markdown('
Topic4
', unsafe_allow_html=True)
    st.image("Topic4.jpg", width=500)

elif add_selectbox == 'Conclusion':

    st.subheader('TECH STACK')

    st.markdown('• Data Collection - ', unsafe_allow_html=True)
    st.markdown('• Data Preprocessing - ', unsafe_allow_html=True)
    st.markdown('• Exploratory Data Analysis & Visualizations - ', unsafe_allow_html=True)
    st.markdown('• Machine Learning - Python, Jupyter Notebook ()', unsafe_allow_html=True)
    st.markdown('• Dashboard - Streamlit', unsafe_allow_html=True)

    st.subheader('PROJECT SUMMARY')

    st.markdown('• ', unsafe_allow_html=True)
    st.markdown('• ', unsafe_allow_html=True)
    st.markdown('• ', unsafe_allow_html=True)
    st.markdown('• ', unsafe_allow_html=True)
    st.markdown('• ', unsafe_allow_html=True)

    st.subheader('CONCLUSION')

    st.markdown('', unsafe_allow_html=True)

elif add_selectbox == 'Team':

    st.subheader('COLLABORATORS')

    st.markdown('Ricard Borras',
                unsafe_allow_html=True)
    st.markdown('Prathima Kadari',
                unsafe_allow_html=True)
    st.markdown('Vaishnavi Kanagasabapathi',
                unsafe_allow_html=True)

    st.subheader('PROJECT MANAGER')

    st.markdown('Shawon Kazi', unsafe_allow_html=True)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sat Jun 13 16:42:17 2020

@author: jakeparker
"""
from subprocess import call

#custom styles can be inserted here
styles = {
          "light" : '''
          body { margin: 10px 10px; background-color: #F5F5F5; font-size: 100%; }
          figure { break-inside: avoid; margin: 4px 4px; max-width: 100%; display: inline-block; }
          img { max-width: 100%; }
          figcaption { padding: 2px; text-align: center; font-family: Arial, sans-serif; background-color: #FFFFFF; }
          p { font-size: 1em; font-family: DejaVu Sans Mono, Courier, monospace; background-color: #FFFFFF; padding: 5px; }
          h1, h2, h3, h4, h5, h6 { font-family: Arial, sans-serif; background-color: #FFFFFF; padding: 5px; }
          table, th, td { border: 1px solid black; border-collapse: collapse; }
          table { background-color: #FFFFFF; }
          th, td { padding: 8px; }
          th { font-family: Arial, sans-serif; }
          td { text-align: center; font-family: DejaVu Sans Mono, Courier, monospace; }''', \
          \
          "dark" : '''
          body { margin: 10px 10px; background-color: #181818; font-size: 100%; }
          figure { break-inside: avoid; margin: 5px 5px; max-width: 100%; display: inline-block; }
          img { max-width: 100%; }
          figcaption { padding: 2px; text-align: center; font-family: Arial, sans-serif; color: #F8F8F8; background-color: #303030; }
          p { font-size: 1em; font-family: DejaVu Sans Mono, Courier, monospace; color: #F8F8F8; background-color: #303030; padding: 5px; }
          h1, h2, h3, h4, h5, h6 { font-family: Arial, sans-serif; background-color: #303030; color: #F8F8F8; padding: 5px; }
          table, th, td { border: 2px solid #181818; border-collapse: collapse; color: #F8F8F8; }
          table { background-color: #303030; }
          th, td { padding: 8px; font-color: #F8F8F8; }
          th { font-family: Arial, sans-serif; }
          td { text-align: center; font-family: DejaVu Sans Mono, Courier, monospace; }''', \
          \
          "printer-friendly": '''
          body { margin: 10px 10px; background-color: #FFFFFF; font-size: 100%; }
          figure { break-inside: avoid; margin: 4px 4px; max-width: 100%; display: inline-block; border: 1px solid black; }
          img { max-width: 100%; }
          figcaption { padding: 2px; text-align: center; font-family: Arial, sans-serif; border: }
          p { font-size: 1em; font-family: DejaVu Sans Mono, Courier, monospace; padding: 5px; border: 1px solid black; }
          h1, h2, h3, h4, h5, h6 { font-family: Arial, sans-serif; padding: 5px; border: 1px solid }
          table, th, td { border: 1px solid black; border-collapse: collapse; }
          th, td { padding: 8px; }
          th { font-family: Arial, sans-serif; }
          td { text-align: center; font-family: DejaVu Sans Mono, Courier, monospace; }''', \
          \
          "new": '''
          body { margin: 0px 0px; border-style: solid; border-color: #303030; border-width: 20px 50px; padding: 5px; background-color: #FFFFFF; font-size: 100%; }
          figure { break-inside: avoid; margin: 4px 4px; max-width: 100%; display: inline-block; border: 1px solid black; }
          img { max-width: 100%; }
          figcaption { padding: 2px; text-align: center; font-family: Arial, sans-serif; border: }
          p { font-size: 1em; font-family: DejaVu Sans Mono, Courier, monospace; padding: 5px; border: 1px solid black; }
          h1, h2, h3, h4, h5, h6 { font-family: Arial, sans-serif; padding: 5px; border: 1px solid }
          table, th, td { border: 1px solid black; border-collapse: collapse; }
          th, td { padding: 8px; }
          th { font-family: Arial, sans-serif; }
          td { text-align: center; font-family: DejaVu Sans Mono, Courier, monospace; }'''
          }

class html:

    def __init__(self, filename, lang="en", charset="UTF-8", style='light'):
        self.filename = filename
        self.lang = lang
        self.charset = charset
        self.body_text = '''
    '''
        self.style = css_to_dict(styles[style])

    def write_header(self, header, size='h2', header_style_pairs=[]):
        self.body_text += '''
        ''' + create_tag(size, header_style_pairs) + header + '''</''' + size + '''>'''

    def write_figure(self, image_file, caption='', width='', height='', inline=False, newline=False, figure_style_pairs=[], image_style_pairs=[], caption_style_pairs=[]):
        if newline or not inline:
            self.body_text += '''

'''
        self.body_text += '''
        ''' + create_tag('figure',figure_style_pairs)
        self.body_text += '''
            '''
        if caption:
            self.body_text += '''
            ''' + create_tag('figcaption',caption_style_pairs) + caption + '''
'''
        self.body_text += '''

'''
        if not inline:
            self.body_text += '''

'''

    def write_text(self, text, text_style_pairs=[]):
        self.body_text += '''
        ''' + create_tag('p',text_style_pairs) + '''
            ''' + text + '''

'''

    def write_table(self, data,col_headers=[], row_headers=[], float_format='{: .5g}', table_style_pairs=[], header_style_pairs=[], data_style_pairs=[]):
        self.body_text += '''
        ''' + create_tag('table',table_style_pairs)
        try:
            col_headers = list(data.columns)
            data = data.values
        except:
            pass
        if col_headers:
            self.body_text += '''

'''
            if row_headers:
                self.body_text += '''
                ''' + create_tag('th',header_style_pairs) + '''	'''
            for ch in col_headers:
                self.body_text += '''
                ''' + create_tag('th',header_style_pairs) + str(ch) + '''	'''
            self.body_text += '''

'''
        for ii in range(len(data)):
            self.body_text += '''

'''
            if row_headers:
                self.body_text += '''
                ''' + create_tag('th',header_style_pairs) + str(row_headers[ii]) + '''	'''
            for d in data[ii]:
                if isinstance(d,(float,complex)):
                    self.body_text += '''
                ''' + create_tag('td',data_style_pairs) + float_format.format(d) + '''	'''
                else:
                    self.body_text += '''
                ''' + create_tag('td',data_style_pairs) + str(d) + '''	'''
            self.body_text += '''

'''
        self.body_text += '''

'''

    def set_style(self, tag, style_pairs):
        if isinstance(style_pairs,tuple):
            self.style[tag][style_pairs[0]] = style_pairs[1]
        elif isinstance(style_pairs,list):
            for prop, val in style_pairs:
                self.style[tag][prop] = val

    def save(self):
        self.head_text = '''
<!DOCTYPE html>

        <meta charset="''' + self.charset + '''">
        <title>"''' + self.filename + '''"</title>

    '''
        self.body_text += '''

    '''
        outfile = open(self.filename,'w')
        outfile.write(self.head_text)
        outfile.write(self.body_text)
        outfile.close

    def save_as_pdf(self, page_size='A4', page_orientation='Portrait', page_margin='5mm', arg_pairs=[]):
        self.set_style('body',[('margin','0px'),('border-width','0px'),('padding','0px')])
        self.save()
        call_list = ['wkhtmltopdf','--enable-local-file-access','-s',page_size,'-O',page_orientation, \
                    '-B',page_margin,'-T',page_margin,'-L',page_margin,'-R',page_margin]
        if arg_pairs:
            if isinstance(arg_pairs,tuple):
                call_list.extend(arg_pairs)
            elif isinstance(arg_pairs,list):
                for pair in arg_pairs:
                    call.list.extend(pair)
        call_list.extend([self.filename,self.filename + '.pdf'])
        try:
            call(call_list)
        except FileNotFoundError:
            print('Save as PDF failed: wkhtmltopdf call failed')
            print('Install from wkhtmltopdf.org or check installation')

def style_pairs_to_str(style_pairs):
    style_str = ''
    if isinstance(style_pairs,tuple):
        style_str += style_pairs[0] + ': ' + style_pairs[1] + '; '
    elif isinstance(style_pairs,list):
        for prop, val in style_pairs:
            style_str += prop + ': ' + val + '; '
    return style_str

def css_to_dict(css_file_str):
    style_dict = dict()
    blocks = css_file_str.split('}')
    for b in blocks:
        if '{' not in b: continue
        tag = b.split('{')[0].strip()
        style_dict[tag] = dict()
        props = b.split('{')[1].split(';')
        for p in props:
            if ':' not in p: continue
            prop = p.split(':')[0].strip()
            style_dict[tag][prop] = p.split(':')[1].strip()
    return style_dict

def create_tag(tag, style_pairs):
    if not style_pairs:
        return '<' + tag + '>'
    else:
        return '<' + tag + ' style="' + style_pairs_to_str(style_pairs) + '">'


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
@author: Erick Suarez
@contact: esuarez@cs.ucsb.edu
@file: HexPlayer.py
@version: 0.1
@description:
'''

from __future__ import print_function

import sys
import time
import getopt
import random
import copy

# ======================================================================================
# Constants
# ======================================================================================
ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
LETTER2INT = {ALPHABET[i]: i for i in range(26)}
VALUE_EMPTY = 0
VALUE_RED = 1  # from letter side to letter side
VALUE_BLUE = -1  # from integer side to integer side

class HexAgent:
    # ======================================================================================
    # Constructor
    # ======================================================================================
    def __init__(self, boardSize, color):
        self.hexBoard = [[VALUE_EMPTY for j in range(boardSize)] for i in range(boardSize)]
        self.boardSize = boardSize
        self.color = color
        self.playersMoves = []
        self.firstMove = True
        self.secondMove = False

    # ======================================================================================
    # Public Methods
    # ======================================================================================
    def inp_to_pos(self, inp):
        try:
            pi = inp[0]
            if not (pi in ALPHABET):
                return None
            pi = LETTER2INT[pi]
            pj = int(inp[1:])
            move = (pi,pj)
            if self.check_pos(move):
                return move
            else:
                # out of range
                raise Exception
        except Exception:
            # fail to translate, invalid input
            print("# Error: invalid position.")
            sys.exit(2)

    def pos_to_inp(self, move):
        try:
            pi = move[0]
            pj = move[1]
            if self.check_pos(move):
                inp = "{}{}".format(ALPHABET[pi],pj)
                return inp
            else:
                # out of range
                raise Exception
        except Exception:
            # fail to translate, invalid input
            print("# Error: invalid position.")
            sys.exit(2)

    def update_board(self, board, move, value):
        # update board status
        # return True: successful
        # return False: failed
        try:
            pi = move[0]
            pj = move[1]
            if self.check_pos(move):
                if board[pi][pj]==VALUE_EMPTY:
                    board[pi][pj] = value
                    if(value == self.color):
                        self.playersMoves.append(move)
                    return True
                else:
                    raise Exception
            else:
                # out of range
                raise Exception
        except Exception:
            print("# Error: invalid position.")
            sys.exit(2)

    def print_board(self):
        print("     ",end="")
        for j in range(self.boardSize):
            print(" {:<2} ".format(j),end="")
        print()
        print("    +",end="")
        for j in range(self.boardSize):
            print("---+",end="")
        print()
        for i in range(self.boardSize):
            print(" {:3}|".format(ALPHABET[i]),end="")
            for j in range(self.boardSize):
                if self.hexBoard[i][j]==VALUE_RED:
                    print(" R |",end="")
                elif self.hexBoard[i][j]==VALUE_BLUE:
                    print(" B |",end="")
                else:
                    print("   |",end="")
            print()
            print("    +",end="")
            for j in range(self.boardSize):
                print("---+",end="")
            print()

    def minimax(self):
        moves = []
        if(self.firstMove):
            self.firstMove = False
            if(self.hexBoard[self.boardSize//2][self.boardSize//2] == VALUE_EMPTY):
                return (self.boardSize//2, self.boardSize//2)
            else:
                return(self.boardSize//2, self.boardSize//2 + 1)
            if(self.color == VALUE_RED):
                return (self.boardSize//2, 0)
            else:
                if(self.hexBoard[0][self.boardSize//2] == VALUE_EMPTY):
                    return(0, self.boardSize//2)
                else:
                    return(0, 1+self.boardSize//2)
        else:
            moves = self.getAdjacentMoves()
        bestMove = moves[0]
        bestScore = float('inf')
        alpha = float('-inf')
        beta = float('inf')
        depth = 2

        for move in moves:
            self.nextState(move, self.color)
            score = self.maxValue(alpha, beta, depth)
            if score < bestScore:
                bestMove = move
                bestScore = score
            self.revertState(move)

        return bestMove

    # ======================================================================================
    # Private Methods
    # ======================================================================================
    def check_pos(self, move):
        # check validity of pos
        try:
            pi = move[0]
            pj = move[1]
            if pi<0 or pi>=self.boardSize or pj<0 or pj>=self.boardSize:
                return False
            else:
                return True
        except Exception:
            # could be type error or something
            return False

    def maxValue(self, alpha, beta, depth):
        if(depth == 0 or self.gameOver(self.hexBoard)):
            return self.heuristicValue(self.hexBoard)

        moves = self.getAdjacentMoves()
        bestScore = float('-inf')

        for move in moves:
            if(self.color == VALUE_RED):
                self.nextState(move, VALUE_BLUE)
            else:
                self.nextState(move, VALUE_RED)
            score = self.minValue(alpha, beta, depth-1)
            if score > bestScore:
                bestScore = score
            if score >= beta:
                self.revertState(move)
                return bestScore
            if score > alpha:
                alpha = score
            self.revertState(move)

        return bestScore

    def minValue(self, alpha, beta, depth):
        if(depth == 0 or self.gameOver(self.hexBoard)):
            return self.heuristicValue(self.hexBoard)

        moves = self.getAdjacentMoves()
        bestScore = float('inf')

        for move in moves:
            self.nextState(move, self.color)
            score = self.maxValue(alpha, beta, depth-1)

            if score < bestScore:
                bestScore = score
            if score <= alpha:
                self.revertState(move)
                return bestScore
            if score < beta:
                beta = score
            self.revertState(move)

        return bestScore

    def heuristicValue(self, currentState):
        visitedPositions = {}
        value = 1

        lowerLimit = 0
        upperLimit = 0

        for move in self.playersMoves:

            if move not in visitedPositions:
                lowerLimit = 0
                upperLimit = 0
                numberOfConnectedNodes = 0
                direction = 0

                if(self.color == VALUE_BLUE):
                    lowerLimit = move[0]
                    upperLimit = move[0]
                    numberOfConnectedNodes = 1
                    direction = move[0]
                else:
                    lowerLimit = move[1]
                    upperLimit = move[1]
                    numberOfConnectedNodes = 1
                    direction = move[1]

                distance = [lowerLimit, upperLimit, numberOfConnectedNodes]

                numberOfConnectedNodes += self.numberOfConnections(move, distance, visitedPositions)

                lowerLimit = distance[0]
                upperLimit = distance[1]
                numberOfConnectedNodes = distance[2]

                value += (3*numberOfConnectedNodes) * (self.boardSize/(((self.boardSize - direction)))) * (1/(lowerLimit+1))
        #self.print_board()
        #print(str(value))
        value *=  ((upperLimit+1))

        return value

    def numberOfConnections(self, move, distance, visitedPositions):
        i = move[0]
        j = move[1]
        connections = 1
        if(move in visitedPositions):
            return connections

        visitedPositions[move] = True

        #node = (i-1, j)
        if((i-1) >= 0 and self.hexBoard[i-1][j] == self.color):
            if(self.color == VALUE_RED and i-1 < distance[0]):
                distance[0] = i-1
            connections += 1 + self.numberOfConnections((i-1,j), distance, visitedPositions)
        #node = (i+1, j)
        if((i+1) < self.boardSize and self.hexBoard[i+1][j] == self.color):
            if(self.color == VALUE_RED and (i+1) > distance[1]):
                distance[1] = (i+1)
            connections += 1 + self.numberOfConnections((i+1,j), distance, visitedPositions)
        #node = (i, j-1)
        if((j-1) >= 0 and self.hexBoard[i][j-1] == self.color):
            if(self.color == VALUE_BLUE and j-1 < distance[0]):
                distance[0] = j-1
            connections += 1 + self.numberOfConnections((i,j-1), distance, visitedPositions)
        #node = (i, j+1)
        if((j+1) < self.boardSize and self.hexBoard[i][j+1] == self.color):
            if(self.color == VALUE_BLUE and (j+1) > distance[1]):
                distance[1] = (j+1)
            connections += 1 + self.numberOfConnections((i,j+1), distance, visitedPositions)
        #node = (i+1, j-1)
        if(j-1 >= 0 and i+1 < self.boardSize and self.hexBoard[i+1][j-1] == self.color):
            if(self.color == VALUE_RED and (i+1) > distance[1]):
                distance[1] = (i+1)
            if(self.color == VALUE_BLUE and (j-1) < distance[0]):
                distance[0] = j-1
            connections += 1 + self.numberOfConnections((i+1,j-1), distance, visitedPositions)
        #node = (i-1, j+1)
        if((j+1) < self.boardSize and (i-1) >= 0 and self.hexBoard[i-1][j+1] == self.color):
            if(self.color == VALUE_RED and (i-1) < distance[0]):
                distance[0] = i-1
            if(self.color == VALUE_BLUE and (j+1) > distance[0]):
                distance[1] = (j+1)
            connections += 1 + self.numberOfConnections((i-1,j+1), distance, visitedPositions)

        return connections

    def getAvailableMoves(self, currentState):
        availableMoves = []

        for i in range(self.boardSize):
            for j in range(self.boardSize):
                if currentState[i][j]==VALUE_EMPTY:
                    availableMoves.append((i,j))

        return availableMoves

    def getAdjacentMoves(self):
        adjacentMoves = []
        radius = 2
        playersMoves = self.getPlayersMoves(self.hexBoard)
        for playerMove in playersMoves:
            for i in range(self.boardSize):
                for j in range(self.boardSize):
                    if self.hexBoard[i][j] == VALUE_EMPTY and self.inIRangeOf(i, playerMove, radius) and self.inJRangeOf(j, playerMove, radius):
                        #if(self.deadOrVulnerableMove((i,j))):
                        adjacentMoves.append((i,j))

        return adjacentMoves
    def deadOrVulnerableMove(self, move):
        i = move[0]
        j = move[1]

        count = 0
        if(self.check_pos((i-1, j))):
            if(self.hexBoard[i-1][j] == VALUE_EMPTY):
                count += 1

        if(self.check_pos((i+1, j))):
            if(self.hexBoard[i+1][j] == VALUE_EMPTY):
                count += 1

        if(self.check_pos((i, j -1))):
            if(self.hexBoard[i][j-1] == VALUE_EMPTY):
                count += 1

        if(self.check_pos((i, j+1))):
            if(self.hexBoard[i][j+1] == VALUE_EMPTY):
                count += 1

        if(self.check_pos((i+1, j-1))):
            if(self.hexBoard[i+1][j-1]):
                count += 1

        if(self.check_pos((i-1, j+1))):
            if(self.hexBoard[i-1][j-1]):
                count += 1

        return count != 6
    def inIRangeOf(self, i, playerMove, radius):
        lowerLimit = playerMove[0] - radius
        upperLimit = playerMove[0] + radius

        return(lowerLimit <= i and i <= upperLimit)
    def inJRangeOf(self, j, playerMove, radius):
        lowerLimit = playerMove[1] - radius
        upperLimit = playerMove[1] + radius

        return(lowerLimit <= j and j <= upperLimit)

    def getPlayersMoves(self, currentState):
        playerMoves = []

        for i in range(self.boardSize):
            for j in range(self.boardSize):
                if currentState[i][j]==self.color:
                    playerMoves.append((i,j))

        return playerMoves

    def nextState(self, move, value):
        self.update_board(self.hexBoard, move, value)

    def revertState(self, move):
        pi = move[0]
        pj = move[1]

        if self.check_pos(move):
            if(self.hexBoard[pi][pj] == self.color):
                del self.playersMoves[-1]
            self.hexBoard[pi][pj] = VALUE_EMPTY

    def gameOver(self, currentState):
        return (len(self.getAvailableMoves(currentState)) == 0)

def main(argv):
    try:
        opts, args = getopt.getopt(argv, "dp:s:", ["debug","player=","size="])
    except getopt.GetoptError:
        print('Error: RandomHex.py [-d] [-p <ai_color>] [-s <board_size>]')
        print('.  or: RandomHex.py [--debug] [--player=<ai_color>] [--size=<board_size>]')
        sys.exit(2)

    # default arguments
    arg_player = "RED"
    arg_size = 7
    arg_debug = False
    for opt, arg in opts:
        if opt in ("-d","--debug"):
            arg_debug = True
        elif opt in ("-p","--player"):
            arg_player = arg.upper()
            if not arg_player in ["RED","BLUE"]:
                print('Error: Invalid player, should be either "RED" or "BLUE".')
                sys.exit(2)
        elif opt in ("-s","--size"):
            try:
                arg_size = int(arg)
                if arg_size<=0 or arg_size>26:
                    raise Exception()
            except Exception:
                print('Error: Invalid size, should be integer in [1,26].')
                sys.exit(2)

    # initialize the game
    color = 0
    if(arg_player == "RED"):
        color = VALUE_RED
    else:
        color = VALUE_BLUE
    hexAgent = HexAgent(arg_size, color)

    while(True):
        if hexAgent.color==VALUE_RED:
            # RED playes first
            c_pos = hexAgent.minimax()
            c_inp = hexAgent.pos_to_inp(c_pos)
            # introduce random time pause
            # time.sleep(random.randint(0,4))
            print(c_inp)
        else:
            # wait for opponent
            c_inp = input()
            c_pos = hexAgent.inp_to_pos(c_inp)
        # RED MOVES
        hexAgent.update_board(hexAgent.hexBoard, c_pos, VALUE_RED)
        if arg_debug:
            hexAgent.print_board()

        if hexAgent.color==VALUE_BLUE:
            # BLUE playes
            c_pos = hexAgent.minimax()
            c_inp = hexAgent.pos_to_inp(c_pos)
            # introduce random time pause
            # time.sleep(random.randint(0,4))
            print(c_inp)
        else:
            # wait for opponent
            c_inp = input()
            c_pos = hexAgent.inp_to_pos(c_inp)
        # BLUE MOVES
        hexAgent.update_board(hexAgent.hexBoard, c_pos, VALUE_BLUE)
        if arg_debug:
            hexAgent.print_board()

if __name__=="__main__":
    main(sys.argv[1:])


'''
6 2 1 2 3 2 5 1 4 5 7 2 2
'''
while True:
    try:
        lst=list(map(int,input().split(' ')))
        ls = len(lst)
        n=lst[0]  #链表结点个数
        headv=lst[1]  #头结点值
        dvalue=lst[ls-1]  #待删除的结点
        link={headv:None}  #链表
        head=link #头结点
        for i in range(2,ls-2,2):
            cur=head #遍历的游标
            curv=list(cur.keys())[0]
            while True: #查找结点，将新节点插入查找到的结点后面
                if curv==lst[i+1]:
                    tmp = cur[curv]
                    cur[curv]={lst[i]:tmp}
                    break
                cur=cur[curv]    #游标移动到下一结点
                if cur is None :
                    break
                curv=list(cur.keys())[0]
        cur=head
        curv=headv
        #删除结点
        while True:
            if curv != dvalue:
                print(curv,end=' ')
            cur=cur[curv]
            if cur is None:
                break
            curv=list(cur.keys())[0]
    except:
        break
'''
5 2 3 2 4 3 5 2 1 4 3
'''

从单向链表中删除指定值的节点(字典实现).py

从单向链表中删除指定值的节点(字典实现).py


#!/usr/bin/python
# -*- coding: iso-8859-15 -*-
'''
Created on 14/09/2013

@author: Eldhelion
'''
import pygame
import sys
import Scenarios

from Screen import *
from Button import *
from ButtonInter import *
from ButtonIntra import *
import globals

class Menu(Screen):
    def __init__(self, controller, screen):
        self.buttons = []
        self.screen = screen
        self.control=controller
#        self.buttons.append(ButtonInter(248, 400, 290, 73, 3, "Comecar PVC", 0))
#        self.buttons.append(ButtonInter(544, 400, 290, 73, 2, "Comecar PVP", 1))
#        self.buttons.append(ButtonInter(368, 515, 290, 73, 1, "Configuracoes", 2))
        self.buttons.append(ButtonInter(694, 427, 290, 73, 3, " ", 0))
        self.buttons.append(ButtonInter(694, 517, 290, 73, 2, " ", 1))
        self.buttons.append(ButtonInter(694, 607, 290, 73, 1, " ", 2))
        self.music = pygame.mixer.music.load('data\\audio\\Cjack2.mid')
        self.ok = self.buttons[0]
        self.config = self.buttons[1]
        self.Bleft = self.Bright = self.Bmiddle = False
        self.background = Scenarios.Menu()

        self.music = pygame.mixer.music.play(10, 0.0)
        if not(pygame.mixer.music.get_busy()):
            pygame.mixer.music.set_volume(1.0)

    def execute(self):

        self.background.draw(self.screen)

        for evento in pygame.event.get():
            for i in self.buttons:
                resp = i.click(evento)
                if(isinstance(i, ButtonInter) and resp!=None):
                    self.control.changeScreen(resp)
                elif(isinstance(i, ButtonIntra) and resp!=None):
                    pass
                if evento.type == pygame.QUIT:
                    sys.exit(0)
        for i in self.buttons:
            i.desenha(self.screen)
        if (self.ok.getState()):
            self.control.changeScreen(2)
        if (self.config.getState()):
            sys.exit(0)


import turtle
turtle.speed(907689760865)

def star(turtle,size):
    for i in range(5):
        turtle.forward(size)
        turtle.right(144)

size = 1
for i in range(50):
    star(turtle,size)
    size = size + 4
    turtle.forward(1)
    turtle.right(8)

turtle.penup()
turtle.goto(-350, 200)
turtle.pendown()
turtle.color("red")

def square(sidelength):
    for i in range(4):
        turtle.forward(sidelength)
        turtle.right(90)

for i in range(60):
    square(200)
    turtle.right(5)
turtle.penup()
turtle.goto(425, 200)
turtle.pendown()

def square(sidelength):
    for i in range(4):
        turtle.forward(sidelength)
        turtle.right(90)

for i in range(60):
    square(200)
    turtle.right(5)

turtle.penup()
turtle.goto(135, -350)
turtle.pendown()

turtle.circle(100)

turtle.exitonclick()