"""
VPfit

Fit Voigt Profiles using MCMC. Uses bayesian model selection to pick the appropriate number
of profiles for a given absorption.

The main class containing the fitting functionality is `VPfit`.

"""

import matplotlib.pyplot as plt

import datetime

import numpy as np
import pymc as mc

# Voigt modules
# from scipy.special import wofz
from astropy.modeling.models import Voigt1D

# peak finding modules
from scipy.signal import find_peaks_cwt
from scipy.signal import savgol_filter

from physics import *

#TODO: change the way that "Models are instantiated" (need to understand that first)
import warnings
warnings.filterwarnings("ignore", message="Instantiating a Model object directly is deprecated. We recommend passing variables directly to the Model subclass.")

class VPfit():

    def __init__(self, noise=None):
        """
        Initialise noise variable
        """
        self.std_deviation = 1./(mc.Uniform("sd", 0, 1) if noise is None else noise)**2
        self.verbose = False

    @staticmethod
    def GaussFunction(x, amplitude, centroid, sigma):
        """
        Gaussian.

        Args:
            x (numpy array): wavelength array
            amplitude (float)
            centroid (float): must be between the limits of wavelength_array
            sigma (float)
        """
        return amplitude * np.exp(-0.5 * ((x - centroid) / sigma) ** 2)

    @staticmethod
    def VoigtFunction(x, centroid, amplitude, L_fwhm, G_fwhm):
        """
        Return the Voigt line shape at x with Lorentzian component HWHM gamma
        and Gaussian component HWHM alpha.

        Source: http://scipython.com/book/chapter-8-scipy/examples/the-voigt-profile/

        Args:
            x (numpy array): wavelength array
            centroid (float): center of profile
            alpha (float): Gaussian HWHM
            gamma (float): Lorentzian HWHM
        """

        #sigma = alpha / np.sqrt(2 * np.log(2))
        #return np.real(wofz((x - centroid + 1j*gamma)/sigma/np.sqrt(2))) / sigma / np.sqrt(2*np.pi)

        v = Voigt1D(x_0=centroid, amplitude_L=amplitude, fwhm_L=L_fwhm, fwhm_G=G_fwhm)
        return v(x)

    @staticmethod
    def GaussianWidth(G_fwhm):
        """
        Find the std deviation of the Gaussian associated with a Voigt profile, from the
        Full Width Half Maximum (FWHM)

        Args:
            G_fwhm (float): Full Width Half Maximum (FWHM)
        """
        return G_fwhm / (2.* np.sqrt(2.*np.log(2.)))

    def trunc_normal(self, name, mu, tau, a, b):
        """
        Take a sample from a truncated Gaussian distribution with PyMC Normal.
        Args:
            name (string): name of the component
            mu (float): mean of the distribution
            tau (float): precision of the distribution, which corresponds to 1/sigma**2 (tau > 0).
            a (float): lower bound of the distribution
            b (float): upper bound of the distribution
        """
        for i in range(1000):
            x = pymc.Normal(name, mu, tau)
            if (a < x.value) and (b > x.value):
                break
        if (a > x.value) or (b < x.value):
            raise ValueError('Computational time exceeded.')
        return x

    @staticmethod
    def Chisquared(observed, expected, noise):
        """
        Calculate the chi-squared goodness of fit

        Args:
            observed (array)
            expected (array): same shape as observed
        """
        return sum( ((observed - expected) /  noise)**2 )

    @staticmethod
    def ReducedChisquared(observed, expected, noise, freedom):
        """
        Calculate the reduced chih-squared goodness of fit

        Args:
            observed (array)
            expected (array): same shape as observed
            freedom (int): degrees of freedom
        """
        return VPfit.Chisquared(observed, expected, noise) / freedom

    def plot(self, wavelength_array, flux_array, clouds=None, n=1, onesigmaerror = 0.02,
            start_pix=None, end_pix=None, filename=None):
        """
        Plot the fitted absorption profile

        Args:
            wavelength_array (numpy array):
            flux_array (numpy array): original flux array, same length as wavelength_array
            filename (string): for saving the plot
	    clouds (pandas dataframe): dataframe containing details on each absorption feature
            n (int): number of *fitted* absorption profiles
            onesigmaerror (float): noise on profile plot
        """

        if not start_pix:
            start_pix = 0
        if not end_pix:
            end_pix = len(wavelength_array)

        f, (ax1, ax2, ax3) = plt.subplots(3, sharex=True, sharey=False, figsize=(10,10))

        ax1.plot(wavelength_array, (flux_array - self.total.value) / onesigmaerror)
        ax1.hlines(1, wavelength_array[0], wavelength_array[-1], color='red', linestyles='-')
        ax1.hlines(-1, wavelength_array[0], wavelength_array[-1], color='red', linestyles='-')
        ax1.hlines(3, wavelength_array[0], wavelength_array[-1], color='red', linestyles='--')
        ax1.hlines(-3, wavelength_array[0], wavelength_array[-1], color='red', linestyles='--')

        ax2.plot(wavelength_array, flux_array, color='black', linewidth=1.0)

        if clouds is not None:
            for c in range(len(clouds)):
                if c==0:
                    ax2.plot(wavelength_array, Tau2flux(clouds.ix[c]['tau'][start_pix:end_pix]),
                             color="red", label="Actual", lw=1.5)
                else:
                    ax2.plot(wavelength_array, Tau2flux(clouds.ix[c]['tau'][start_pix:end_pix]),
                             color="red", lw=1.5)

        for c in range(n):
            if c==0:
                ax2.plot(wavelength_array, Tau2flux(self.estimated_profiles[c].value),
                         color="green", label="Fit")
            else:
                ax2.plot(wavelength_array, Tau2flux(self.estimated_profiles[c].value),
                         color="green")

        ax2.legend()

        ax3.plot(wavelength_array, flux_array, label="Measured")
        ax3.plot(wavelength_array, self.total.value, color='green', label="Fit", linewidth=2.0)
        ax3.legend()

        f.subplots_adjust(hspace=0)

        if hasattr(self, 'fit_time'): ax1.set_title("Fit time: " + self.fit_time)
        ax1.set_ylabel("Residuals")
        ax2.set_ylabel("Normalised Flux")
        ax3.set_ylabel("Normalised Flux")
        ax3.set_xlabel("$ \lambda (\AA)$")

        if filename:
            plt.savefig(filename)
        else:
            plt.show()

    def find_local_minima(self, f_array, window=101):
        """
        Find the local minima of an absorption profile.

        Args:
            f_array: flux array
            window: smoothing window, pixels
        Returns:
            indices of local minima in flux_array
        """

        # smooth flux profile
        smoothed_flux = savgol_filter(f_array, window, 1)

        return find_peaks_cwt(smoothed_flux * -1, np.array([window/3]))

    def initialise_components(self, frequency_array, n, sigma_max):
        """
        Initialise each fitted component of the model in optical depth space. Each component
        consists of three variables, height, centroid and sigma. These variables are encapsulated
        in a deterministic profile variable. The variables are stored in a dictionary, `estimated_variables`,
        and the profiles in a list, `estimated_profiles`.

        Args:
            frequency_array (numpy array)
            n (int): number of components
            sigma_max (float): maximum permitted range of fitted sigma values
        """

        self.estimated_variables = {}
        self.estimated_profiles = []

        for component in range(n):

            self.estimated_variables[component] = {}

            @mc.stochastic(name='xexp_%d' % component)
            def xexp(value=0.5):
                if value < 0:
                    return -np.inf
                else:
                    return np.log(value * np.exp(-value))

            self.estimated_variables[component]['amplitude'] = xexp
            #self.estimated_variables[component]['height'] = mc.Uniform("est_height_%d" % component, 0, 5)

            self.estimated_variables[component]['centroid'] = mc.Uniform("est_centroid_%d" % component,
                                                                         frequency_array[0], frequency_array[-1])

            self.estimated_variables[component]['sigma'] = mc.Uniform("est_sigma_%d" % component, 0, sigma_max)

            @mc.deterministic(name='component_%d' % component, trace = True)
            def profile(x=frequency_array,
                        centroid=self.estimated_variables[component]['centroid'],
                        sigma=self.estimated_variables[component]['sigma'],
                        height=self.estimated_variables[component]['amplitude']):

                return self.GaussFunction(x, height, centroid, sigma )

            self.estimated_profiles.append(profile)

    def initialise_voigt_profiles(self, frequency_array, n, sigma_max, local_minima=[]):
        """
        Args:
            frequency_array (numpy array)
            n (int): number of components
            sigma_max (float): maximum permitted range of fitted sigma values
        """

        #if n < len(local_minima):
        #    raise ValueError("Less profiles than number of minima.")

        self.estimated_variables = {}
        self.estimated_profiles = []

        for component in range(n):

            self.estimated_variables[component] = {}

            @mc.stochastic(name='xexp_%d' % component)
            def xexp(value=0.5):
                if value < 0:
                    return -np.inf
                else:
                    return np.log(value * np.exp(-value))

            self.estimated_variables[component]['amplitude'] = xexp

            self.estimated_variables[component]['centroid'] = mc.Uniform("est_centroid_%d" % component,
                                                                            frequency_array[0], frequency_array[-1])

            self.estimated_variables[component]['L_fwhm'] = mc.Uniform("est_L_%d" % component, 0, sigma_max)
            self.estimated_variables[component]['G_fwhm'] = mc.Uniform("est_G_%d" % component, 0, sigma_max)

            @mc.deterministic(name='component_%d' % component, trace = True)
            def profile(x=frequency_array,
                        centroid=self.estimated_variables[component]['centroid'],
                        amplitude=self.estimated_variables[component]['amplitude'],
                        L=self.estimated_variables[component]['L_fwhm'],
                        G=self.estimated_variables[component]['G_fwhm']):
                return self.VoigtFunction(x, centroid, amplitude, L, G)

            self.estimated_profiles.append(profile)

    def initialise_model(self, frequency, flux, n, local_minima=[], voigt=False):
        """
        Initialise deterministic model of all absorption features, in normalised flux.

        Args:
            frequency (numpy array)
            flux (numpy array): flux values at each wavelength
            n (int): number of absorption profiles to fit
        """

        self.sigma_max = (frequency[-1] - frequency[0]) / 2.

        # always reinitialise profiles, otherwise starts sampling from previously calculated parameter values.
        if(voigt):
            if self.verbose:
                print("Initialising Voigt profile components.")
            self.fwhm_max = self.sigma_max * 2*np.sqrt(2*np.log(2.))
            self.initialise_voigt_profiles(frequency, n, self.fwhm_max, local_minima=local_minima)
        else:
            if self.verbose:
                print("Initialising Gaussian profile components.")
            self.initialise_components(frequency, n, self.sigma_max)

        # deterministic variable for the full profile, given in terms of normalised flux
        @mc.deterministic(name='profile', trace=False)
        def total(profile_sum=self.estimated_profiles):
            return Tau2flux(sum(profile_sum))

        self.total = total

        # represent full profile as a normal distribution
        self.profile = mc.Normal("obs", self.total, self.std_deviation, value=flux, observed=True)

        # create model with parameters of all profiles to be fitted
        # TODO: fix the deprecation error (this is the only case where a "Model" is instantiated):
        # /home/jacobc/anaconda2/envs/vamp27pymc237/lib/python2.7/site-packages/pymc/MCMC.py:81:
        # UserWarning: Instantiating a Model object directly is deprecated. We recommend passing variables directly to the Model subclass.  warnings.warn(message)

        self.model = mc.Model([self.estimated_variables[x][y] for x in self.estimated_variables \
                            for y in self.estimated_variables[x]])# + [std_deviation])

    def map_estimate(self, iterations=2000):
        """
        Compute the Maximum A Posteriori estimates for the initialised model
        """

        self.map = mc.MAP(self.model)
        self.map.fit(iterlim=iterations, tol=1e-3)

    def mcmc_fit(self, iterations=15000, burnin=100, thinning=15, step_method=mc.AdaptiveMetropolis):
        """
        MCMC fit of `n` absorption profiles to a given spectrum

        Args:
            iterations (int): How long to sample the MCMC for
            burnin (int): How many iterations to ignore from the start of the chain
            thinning (int): How many iterations to ignore each tally
            step_method (PyMC steo method object)
        """

        try:
            getattr(self, 'map')
        except AttributeError:
            print("\nWARNING: MAP estimate not provided. \nIt is recommended to compute this \
            in advance of running the MCMC so as to start the sampling with good initial values.")

        # create MCMC object
        self.mcmc = mc.MCMC(self.model)

        # change step method
        if step_method != mc.AdaptiveMetropolis:
            print("Changing step method for each parameter.")
            for index, item in self.estimated_variables.items():
                self.mcmc.use_step_method(step_method, item['sigma'])
                self.mcmc.use_step_method(step_method, item['centroid'])
                self.mcmc.use_step_method(step_method, item['height'])
        else:
            print("Using Adaptive Metropolis step method for each parameter.")

        # fit the model
        starttime=datetime.datetime.now()
        self.mcmc.sample(iter=iterations, burn=burnin, thin=thinning)
        self.fit_time = str(datetime.datetime.now() - starttime)
        print("\nTook:", self.fit_time, " to finish.")

    def find_bic(self, frequency_array, flux_array, n, noise_array, freedom, voigt=False,
                iterations=3000, thin=15, burn=300, thorough=False):
        """
        Initialise the Voigt model and run the MCMC fitting for a particular number of
        regions and return the Bayesian Information Criterion. Used to identify the
        most appropriate number of profles in the region.

        Args:
            frequency_array (numpy array)
            flux_array (numpy array)
            n (int): number of minima to fit in the region
            noise_array (numpy array)
            freedom (int): number of degrees of freedom
            voigt (Boolean): switch to fit as Voigt profile or Gaussian
            iterations, thin, burn (ints): MCMC parameters
        """

        self.bic_array = []
        self.red_chi_array = []
        for i in range(3):
            self.initialise_model(frequency_array, flux_array, n, voigt=voigt)
            self.map = mc.MAP(self.model)
            self.mcmc = mc.MCMC(self.model)
            if thorough:
                self.map.fit(iterlim=iterations, tol=1e-3)
                self.mcmc.sample(iter=iterations, burn=burn, thin=thin, progress_bar=False)
                self.map.fit(iterlim=iterations, tol=1e-3)
            self.mcmc.sample(iter=iterations, burn=burn, thin=thin, progress_bar=False)
            self.map.fit(iterlim=iterations, tol=1e-3)
            self.bic_array.append(self.map.BIC)
            self.red_chi_array.append(self.ReducedChisquared(flux_array, self.total.value, noise_array, freedom))
        return

    def chain_covariance(self, n, voigt=False):
        """
        Find the covariance matrix of the MCMC chain variables
        Args:
            n (int): the number of lines in the region
            voigt (boolean): switch to use Voigt parameters
        Returns:
            cov (numpy array): the covariance matrix
            cov =   [ var(amplitude),           cov(amplitude, sigma),  cov(amplitude, centroid) ]
                    [ cov(sigma, amplitude),    var(sigma),             cov(sigma, centroid)     ]
                    [ cov(centroid, amplitude), cov(centroid, sigma),   var(centroid)            ]

        """
        cov = np.zeros((n, 3, 3))
        for i in range(n):
            amp_samples = self.mcmc.trace('xexp_'+str(i))[:]
            if not voigt:
                sigma_samples = self.mcmc.trace('est_sigma_'+str(i))[:]
            elif voigt:
                gfwhm_samples = self.mcmc.trace('est_G_'+str(i))[:]
                sigma_samples = self.GaussianWidth(gfwhm_samples)
            c_samples = self.mcmc.trace('est_centroid_'+str(i))[:]

            cov[i] = np.cov(np.array((amp_samples, sigma_samples, c_samples)))
        return cov


text = open('bifidCiphertext.txt').read() #only letters and spaces

def freqCount(text):
    text = text.upper().replace(' ','') #so if called from other files, still makes sure of this
    LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'

    counter = [[0]*26,[0]*26,[0]*26,[0]*26,[0]*26,[0]*26,[0]*26,[0]*26,[0]*26,[0]*26,[0]*26,[0]*26,[0]*26,[0]*26,[0]*26,[0]*26,[0]*26,[0]*26,[0]*26,[0]*26,[0]*26,[0]*26,[0]*26,[0]*26,[0]*26,[0]*26,]
    #index in this list signifies first letter, index in list in this list signifies second letter

    for i in range(0,len(text),2): #loop through input(ciphertext) in blocks of 2 (b/c this is for bifid analysis)
        firstPos = LETTERS.find(text[i])
        secondPos = LETTERS.find(text[i+1])
        counter[firstPos][secondPos] += 1 #add one in counter for the appropriate digraph
    return counter

counter = freqCount(text)

def getCommonDigraphs(text,amount): #amount is number of digraphs to return, ordered most common first
    counter = freqCount(text)
    LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'

    for i in range(amount):
        #loop through digraphs, find most common one
        currentMax = 0
        currentDigraph = ''
        for L in range(len(counter)):
            for i in range(len(counter[L])):
                if counter[L][i] > currentMax:
                    currentMax = counter[L][i]
                    currentDigraph = LETTERS[L] + LETTERS[i]
                    counter[L][i] = -1 #set to negative so when run through next time, won't retrieve this one
        print(currentDigraph,currentMax)

def analyze(text):
    text = text.upper().replace(' ','')
    row1 = text[:len(text)//2]
    row2 = text[len(text)//2:]

    #put rows together into a string in which ciphertext digraphs correspond to plaintext digraphs
    newString = ''
    for i in range(len(row1)):
        newString += row1[i] + row2[i]

    print(getCommonDigraphs(newString, 10))

    newString = newString.replace('HE','he')
    newString = newString.replace('RQ','th')
    newString = newString.replace('QQ','ta')
    newString = newString.replace('NM','co')
    newString = newString.replace('IQ','sa')
    newString = newString.replace('QM','re')
    newString = newString.replace('ST','st')

    print(newString)

analyze(text)


import csv
import os

PyBank = os.path.join("Resources", "budget_data.csv")

 # Create list to store data
monthchange = []
profit = []
date = []

# Track output variables
TotalMonths_count = 0
net_total_pl = 0
total_change_profit =  0
Initial_profit = []
greatest_increase_profits = 0
greatest_decrease_losses = 0

    # print(f"CSV Header: {csv_header}")

with open(PyBank) as csvfile:
    csvreader = csv.reader(csvfile, delimiter=',')

    print(csvreader)

    csv_header = next(csvreader)

    for row in csvreader:
       # date.append(row[0]) #not doing anything
        TotalMonths_count +=1

        #profit.append(row[1])  #not needed
        net_total_pl += int(row[1])

        final_profit = int(row[1])
        if TotalMonths_count ==1:
            monthchange = row[1]
            Initial_profit.append(row[1])
            index = 0
        else:
            monthchange = final_profit -int(Initial_profit[index])
            Initial_profit.append(row[1])
            index = index + 1

            total_change_profit = total_change_profit + monthchange

        #Find low and high profits amount and date

        if int(monthchange) > greatest_increase_profits:
            greatest_increase_profits = int(monthchange)
            greatest_month = row[0]

        if int(monthchange) < greatest_decrease_losses:
            greatest_decrease_losses = int(monthchange)
            lowest_month = row[0] # this line of code will reference the day once it finds the lowest profit

average_profits = total_change_profit/(TotalMonths_count - 1)

print(f"Financial Analysis")
print(f"------------------------")
print(f"Total Months: {(TotalMonths_count)}")
print(f"Total:  $ {net_total_pl}")
print(f"Average Change: $ {average_profits}")
print(f"Greatest Increase in Profits: {greatest_month} (${greatest_increase_profits})")
print(f"Greatest Decrease in Profits: {lowest_month} (${greatest_decrease_losses})")

Results_output = os.path.join("PyBankResults.txt")

with open(Results_output, 'w') as txtfile:
    txtfile.write(f"Financial Analysis\n")
    txtfile.write(f"------------------------\n")
    txtfile.write(f"Total Months: {(TotalMonths_count)}\n")
    txtfile.write(f"Total:  $ {net_total_pl}\n")
    txtfile.write(f"Average Change: $ {average_profits}\n")
    txtfile.write(f"Greatest Increase in Profits: {greatest_month} (${greatest_increase_profits})\n")
    txtfile.write(f"Greatest Decrease in Profits: {lowest_month} (${greatest_decrease_losses})\n")


'''
The number, 197, is called a circular prime because all rotations of the digits: 197, 971, and 719, are themselves prime.

There are thirteen such primes below 100: 2, 3, 5, 7, 11, 13, 17, 31, 37, 71, 73, 79, and 97.

How many circular primes are there below one million?
'''

from primesieve import *

N = 1000000

primes = set(generate_primes(N))

total = 0
for p in primes:
	circular = True
	p1 = p2 = str(p)
	while True:
		p2 = p2[-1] + p2[:-1]
		if p2 == p1: break
		elif int(p2) not in primes:
			circular = False
			break
	if circular: total += 1

print('Answer:', total)


from tubeserv.api import api
from tubeserv.api.exceptions import InternalException

from flask import jsonify, request, g

from tubeserv.database import db
from tubeserv.database.models import Video, Session

from tubeserv.extractors import YouTubeExtractor, SoundCloudExtractor, DefaultExtractor

from threading import Thread

from random import choice

def metafetcher(video, videos):
    provider, url = video.provider, video.url
    seed = choice(videos)

    if provider == 0:
        video.title = YouTubeExtractor(url).extract_title()
    elif provider == 1:
        video.title = SoundCloudExtractor(url).extract_title()

    if seed[0] == 0:
        video.related_url = YouTubeExtractor(seed[1]).extract_related()
    else:
        sc_title = SoundCloudExtractor(seed[1]).extract_title()
        sc_yt = DefaultExtractor(sc_title).extract_video()
        video.related_url = YouTubeExtractor(sc_yt).extract_related()

    try:
        db.session.add(video)
        db.session.commit()
    except Exception as e:
        db.session.rollback()

@api.route("/sessions/<uuid:session>/videos/", methods=["POST"], defaults={"video":""})
@api.route("/sessions/<uuid:session>/videos/<int:video>", methods=["GET", "PUT", "PATCH", "DELETE"])
def video(**kwargs):
    session = getattr(g, "session", None)
    video = getattr(g, "video", None)
    json = getattr(g, "json", None)
    res = None

    metafetcher_thread = None

    if video:
        if request.method in ["PUT","PATCH"]:
            video.state = json.get("state", video.state)
        elif request.method in ["DELETE"]:
            res = video.to_dict()
            db.session.delete(video)
    else:
        video = Video(session_id=session.id,
                client_id=json.get("client_id"))
        video.url = json.get("url")
        video.provider = json.get("provider", 0)

        videos = [(v.provider, v.url) for v in
                Video.query.filter(Video.session_id == session.id).all()]
        videos.append((video.provider, video.url))

        metafetcher_thread = Thread(target=metafetcher, args=(video, videos))

        db.session.add(video)

    try:
        db.session.commit()

        if metafetcher_thread is not None:
            db.session.refresh(video)
            metafetcher_thread.start()

        res = video.to_dict()
    except Exception as e:
        db.session.rollback()

        raise InternalException()

    return jsonify(res)


# -*- coding: utf-8 -*-

import os
import sys
from eom_env import *
import eom_app.app as app

def main():
    options = {
        # app_path 网站程序根路径（应该是本文件所在目录的上一级目录）
        'app_path': PATH_APP_ROOT,

        # cfg_path 网站配置文件路径
        # 'cfg_path': PATH_CONF,

        # log_path 网站运行时日志文件路径
        # 'log_path': PATH_LOG,

        # static_path 网站静态文件路径
        'static_path': os.path.join(PATH_APP_ROOT, 'static'),

        # data_path 网站数据文件路径
        'data_path': PATH_DATA,

        # template_path 网站模板文件路径
        'template_path': os.path.join(PATH_APP_ROOT, 'view'),

        # res_path 网站资源文件路径
        'res_path': os.path.join(PATH_APP_ROOT, 'res')
    }

    return app.run(options)

if __name__ == '__main__':
    sys.exit(main())


from __future__ import absolute_import
from __future__ import unicode_literals

from django.apps import apps
from django.conf import settings
from django.db import migrations

from corehq.sql_db.operations import RawSQLMigration

def get_view_migrations():
    sql_views = [
        'awc_location_months.sql',
        'agg_awc_monthly.sql',
        'agg_ccs_record_monthly.sql',
        'agg_child_health_monthly.sql',
        'daily_attendance.sql',
        'agg_awc_daily.sql',
        'child_health_monthly.sql',
        'disha_indicators.sql',
        'ccs_record_monthly_view.sql',
        'agg_ls_monthly.sql',
        'service_delivery_monthly.sql',
        'aww_incentive_report_monthly.sql',
    ]
    migrator = RawSQLMigration(('custom', 'icds_reports', 'migrations', 'sql_templates', 'database_views'))
    operations = []
    for view in sql_views:
        operations.append(migrator.get_migration(view))
    return operations

def _citus_composite_key_sql(model_cls):
    pkey_name = '{}_pkey'.format(model_cls._meta.db_table)
    fields = list(model_cls._meta.unique_together)[0]
    columns = [model_cls._meta.get_field(field).column for field in fields]
    index = '{}_{}_uniq'.format(model_cls._meta.db_table, '_'.join(columns))
    sql = """
        CREATE UNIQUE INDEX {index} on "{table}" ({cols});
        ALTER TABLE "{table}" DROP CONSTRAINT {pkey_name};
        ALTER TABLE "{table}" ADD CONSTRAINT {pkey_name} PRIMARY KEY USING INDEX {index};
    """.format(
        table=model_cls._meta.db_table,
        pkey_name=pkey_name,
        index=index,
        cols=','.join(columns)
    )
    reverse_sql = """
        ALTER TABLE "{table}" DROP CONSTRAINT IF EXISTS {index},
        DROP CONSTRAINT IF EXISTS {pkey_name};
        ALTER TABLE "{table}" ADD CONSTRAINT {pkey_name} PRIMARY KEY ({pkey});
        DROP INDEX IF EXISTS {index};
    """.format(
        table=model_cls._meta.db_table,
        pkey_name=pkey_name,
        pkey=model_cls._meta.pk.name,
        index=index,
    )
    if getattr(settings, 'UNIT_TESTING', False):
        return sql, reverse_sql
    else:
        return migrations.RunSQL.noop, reverse_sql

def get_composite_primary_key_migrations(models_to_update):
    operations = []
    for model_name in models_to_update:
        model = apps.get_model('icds_reports', model_name)
        sql, reverse_sql = _citus_composite_key_sql(model)
        operations.append(migrations.RunSQL(
            sql,
            reverse_sql,
            state_operations=[
                migrations.AlterUniqueTogether(
                    name=model_name,
                    unique_together=model._meta.unique_together,
                ),
            ]
        ))
    return operations


from typing import List

import numpy as np
import pandas as pd

import seaborn as sns
import matplotlib.pyplot as plt

class ConfusionMatrix:
    """
    Class that represents a confusion matrix for any number of classes.
    """
    def __init__(self,
                 actual_classes: np.ndarray,
                 predicted_classes: np.ndarray,
                 class_order: List[object]=None):
        assert len(actual_classes) == len(predicted_classes)
        # ensure that all the unique predicted values are in the actual values
        assert all([x in np.unique(actual_classes) for x in np.unique(predicted_classes)])

        confusion_matrix = pd.crosstab(pd.Series(list(actual_classes)),
                                       pd.Series(list(predicted_classes)),
                                       margins=True)

        if class_order is not None:
            expected_indexes = class_order + ['All']
        else:
            expected_indexes = list(confusion_matrix.index.values)

        new_indexes = [x if x != 'All' else 'Total' for x in expected_indexes]

        # change row order/names
        confusion_matrix = confusion_matrix.reindex(index=expected_indexes)
        confusion_matrix.index = new_indexes
        confusion_matrix.index.name = 'actual'
        # change column order/names
        confusion_matrix = confusion_matrix.reindex(columns=expected_indexes)
        confusion_matrix.columns = new_indexes
        confusion_matrix.columns.name = 'predicted'
        # NaN values could be found when e.g. predictions are all from single class
        confusion_matrix.fillna(value=0, inplace=True)

        self._confusion_matrix = confusion_matrix

    @property
    def matrix(self) -> pd.DataFrame:
        """
        :return: The matrix as a DataFrame.
        """
        return self._confusion_matrix

    @property
    def matrix_proportions(self) -> pd.DataFrame:
        """
        :return: The matrix as a DataFrame, showing frequencies rather than absolute values.
        """
        # noinspection PyTypeChecker
        return self.matrix / self.total_observations

    @property
    def total_observations(self) -> int:
        """
        :return: the total number of observations
        """
        return self.matrix.loc['Total', 'Total']

    def plot(self,
             include_totals: bool=False,
             proportions: bool=False):
        """
        :param include_totals: if `include_totals` is True, it includes a row and column for totals
        :param proportions: if `proportions` is True, uses the matrix_proportions
        :return: a heatmap plot of the confusion matrix
        """
        ax = plt.axes()
        matrix = round(self.matrix_proportions, 3) if proportions else self.matrix
        matrix = matrix if include_totals else matrix.drop(index='Total', columns='Total')
        sns.heatmap(ax=ax, data=matrix, annot=True, cmap="Blues")
        ax.set_title('Predicted vs Actual Classifications')
        plt.xticks(rotation=20, ha='right')
        plt.yticks(rotation=0)
        plt.xlabel('Predicted', labelpad=20)
        plt.ylabel('Actual', labelpad=20)
        plt.tight_layout()


import shutil
import os

import torch

class AverageMeter(object):
    """Computes and stores the average and current value"""

    def __init__(self):
        self.val = None
        self.avg = None
        self.sum = None
        self.count = None
        self.reset()

    def reset(self):
        self.val = 0
        self.avg = 0
        self.sum = 0
        self.count = 0

    def update(self, val, n=1):
        self.val = val
        self.sum += val * n
        self.count += n
        self.avg = self.sum / self.count

def save_IPN_checkpoint(model_I, model_P, ckpt_dir, epoch=None):
    os.makedirs(ckpt_dir, exist_ok=True)
    if epoch is None:
        dict_network = model_I.state_dict()
        torch.save(dict_network, os.path.join(ckpt_dir, f'I.pth'))
        dict_network = model_P.state_dict()
        torch.save(dict_network, os.path.join(ckpt_dir, f'P.pth'))
    else:
        dict_network = model_I.state_dict()
        torch.save(dict_network, os.path.join(ckpt_dir, f'I_{epoch}.pth'))
        dict_network = model_P.state_dict()
        torch.save(dict_network, os.path.join(ckpt_dir, f'P_{epoch}.pth'))


import numpy as np
from keras.preprocessing.image import load_img, img_to_array

def create_dataset(fold, names, base_dir):
    dataset = np.ndarray(shape=(len(names), 50, 160, 3), dtype = np.float32)
    i = 0
    for f in names:
        img = load_img(base_dir + fold + "/" + f)
        x = img_to_array(img)
        x = (x - 128) / 128
        dataset[i] = x
        i += 1
    dataset = dataset[:,:,:,0]
    dataset = np.expand_dims(dataset, axis = 3)
    return dataset

def create_labels(names):
    labels = [np.zeros((len(names),10)) for i in range(5)]
    i = 0
    for n in range(len(names)):
        seq = names[n][0:5]
        for digit in range(len(seq)):
            label = int(seq[digit])
            labels[digit][n][label] = 1
        i += 1
    return labels


import networkx as nx
import matplotlib.pyplot as plot

A=nx.DiGraph()
A.add_edge('Pc1','Pc1')
A.add_edge('Pc1','Pc2')
A.add_edge('Pc1','Pc3')
A.add_edge('Pc1','Pc4')
A.add_edge('Pc1','Pc5')
A.add_edge('Pc1','Pc6')
A.add_edge('Pc1','Pc7')
nodes_reflex = {'Pc1'}
nodes_no_reflex = {'Pc2','Pc3','Pc4','Pc5','Pc6','Pc6','Pc7'}
posicion=nx.spring_layout(A)
nx.draw_networkx_nodes(A,posicion,nodelist=nodes_reflex,
                       node_size=500, node_color= 'r')
nx.draw_networkx_nodes(A,posicion,nodelist=nodes_no_reflex,
                       node_size=500, node_color= 'y')
nx.draw_networkx_edges(A,posicion, width=2)
nx.draw_networkx_labels(A,posicion, font_size=11,font_family='arial')

plot.axis('off')
plot.savefig("Graf6.eps")
plot.show(A)


import numpy as np
import matplotlib.pyplot as plt

import subprocess
from tempfile import mkstemp

from sklearn.tree import export_graphviz

from IPython.core.display import HTML

def plot_surface(clf, X, y, n_steps=250, subplot=None, show=True,
                 ylim=None, xlim=None):
    if subplot is None:
        fig = plt.figure()
    else:
        plt.subplot(*subplot)

    if xlim is None:
        xlim = X[:, 0].min(), X[:, 0].max()
    if ylim is None:
        ylim = X[:, 1].min(), X[:, 1].max()
    xx, yy = np.meshgrid(np.linspace(xlim[0], xlim[1], n_steps),
                         np.linspace(ylim[0], ylim[1], n_steps))

    z = clf.predict(np.c_[xx.ravel(), yy.ravel()])[:, 1]

    z = z.reshape(xx.shape)
    plt.contourf(xx, yy, z, alpha=0.8, cmap=plt.cm.RdBu_r)
    plt.scatter(X[:, 0], X[:, 1], c=y)
    plt.xlim(*xlim)
    plt.ylim(*ylim)

    if show:
        plt.show()


from django.shortcuts import redirect
from django.urls import path

from . import views

urlpatterns = [
    path('', views.redirect_main),

    path('find/<str:query>', views.search, name='search'),
    path('find/', views.search, name='search_empty'),

    path('movie/<int:movie_id>/method/<str:method_name>', views.recommend, name='recommend'),
    path('movie/<int:movie_id>', views.recommend, name='recommend_default'),

    path('test/', views.test, name='test'),
]