import torch
import torch.nn as nn
import numpy as np
from tqdm import tqdm

from embedder import embedder
from evaluate import evaluate
from layers import GCN, InterDiscriminator

class HDI(embedder):
    def __init__(self, args):
        embedder.__init__(self, args)
        self.args = args
        self.coef_l = self.args.coef_layers
        self.criteria = nn.BCEWithLogitsLoss()

    def training(self):
        features = self.features.to(self.args.device)
        adj_list = [adj.to(self.args.device) for adj in self.adj_list]

        print("Started training...")
        print("The number of layers: {}".format(len(adj_list)))
        final_embs = []
        for n_adj, adj in enumerate(adj_list):
            print("Layer {}".format(n_adj))
            model = modeler(self.args.ft_size, self.args.hid_units).to(self.args.device)
            optimiser = torch.optim.Adam(model.parameters(), lr=self.args.lr)
            cnt_wait = 0
            best = 1e9
            for _ in tqdm(range(self.args.nb_epochs)):
                model.train()
                optimiser.zero_grad()

                # corruption function
                idx = np.random.permutation(self.args.nb_nodes)
                shuf_fts = features[idx, :].to(self.args.device)

                logits_e, logits_i, logits_j = model(features, shuf_fts, adj, self.args.sparse)

                # loss
                loss_e = self.get_loss(logits_e)
                loss_i = self.get_loss(logits_i)
                loss_j = self.get_loss(logits_j)
                loss = self.coef_l[0] * loss_e + self.coef_l[1] * loss_i + self.coef_l[2] * loss_j

                # early stop
                if loss < best:
                    best = loss
                    cnt_wait = 0
                    torch.save(model.state_dict(), 'saved_model/best_{}_{}_{}.pkl'.format(
                        self.args.dataset, self.args.embedder, n_adj))
                else:
                    cnt_wait += 1
                if cnt_wait == self.args.patience:
                    print("Early stopped!")
                    break

                loss.backward()
                optimiser.step()

            # get embedding
            model.eval()
            embeds = model.embed(features, adj, self.args.sparse)
            final_embs.append(embeds)

        # final evaluation
        print("Evaluating...")
        final_embs = torch.mean(torch.stack(final_embs), 0)   # average pooling
        macro_f1s, micro_f1s, k1 = evaluate(final_embs, self.idx_train, self.idx_val, self.idx_test, self.labels)
        return macro_f1s, micro_f1s, k1

    def get_loss(self, logits):
        """
        :param logits: [2, n_nodes]
        """
        n_nodes = logits.shape[1]
        lbl_1 = torch.ones(n_nodes)
        lbl_2 = torch.zeros(n_nodes)
        lbl = torch.stack((lbl_1, lbl_2))

        lbl = lbl.to(self.args.device)
        loss = self.criteria(logits, lbl)
        return loss

class modeler(nn.Module):
    def __init__(self, ft_size, hid_units):
        super(modeler, self).__init__()
        self.gcn = GCN(ft_size, hid_units)
        self.disc = InterDiscriminator(hid_units, ft_size)

    def forward(self, seq1, seq2, adj, sparse):
        # real
        h_1 = torch.squeeze(self.gcn(seq1, adj, sparse))
        c = torch.squeeze(torch.mean(h_1, 0))  # readout

        # negative
        h_2 = torch.squeeze(self.gcn(seq2, adj, sparse))

        # discriminator
        ret = self.disc(c, h_1, h_2, seq1, seq2)
        return ret

    # Detach the return variables
    def embed(self, seq, adj, sparse):
        h_1 = torch.squeeze(self.gcn(seq, adj, sparse))
        return h_1.detach()


# coding=utf-8
import sys

from condition_identification.document_parsing.html_parser import HtmlParser

sys.path.append("..")

def test_html_parser():
    html_parser = HtmlParser()
    sentences = []
    with open(r'C:\Users\edward\Documents\GitHub\NS_policy_recommendation\res\doc\html\广州南沙新区_自贸片区_促进总部经济发展扶持办法.html', 'r',
              encoding="UTF-8") as html_file:
        sentences = html_parser.parse_document(html_parser.load_file(html_file))
        #for sentence in sentences:
            #print(sentence)
    return sentences

if __name__ == "__main__":
    print(1)
    test_html_parser()


from classes.collabrative_filtering import collabrativefiltering_recommender
from classes.demographic_recommender import demographic_recommender_system
from classes.helper import get_reviews
import streamlit as st

reviews = get_reviews()

def recommend():
    st.title("Hybrid Recommender System")  # app name
    with st.form(key='user_id_form'):  # key of the form - emotion_clf_form
        user_id = st.text_input("Type your user id")  # text area for input data
        submit_text = st.form_submit_button(label='Submit')  # form submit button

        if submit_text:
            review_count = len(reviews[reviews.user_displayname==user_id].index.values)

            if review_count >= 2:
                df = collabrativefiltering_recommender(user_id)
                reason = 'User have favourable ratings'
                recommender = 'Collabrative Filtering'
            else:
                df = demographic_recommender_system(user_id)
                reason = "User don't have favourable ratings, so user is cold start user"
                recommender = 'Demographic Based'

            hide_table_row_index = """
            <style>
            tbody th {display:none}
            .blank {display:none}
            </style>
            """
            # CSS to inject contained in a string
            hide_dataframe_row_index = """
                        <style>
                        .row_heading.level0 {display:none}
                        .blank {display:none}
                        </style>
                        """

            # Inject CSS with Markdown
            st.markdown(hide_dataframe_row_index, unsafe_allow_html=True)

            # Display a static table
            st.dataframe(df)
            st.write(f"Recommender")
            st.success(f"{recommender}")
            st.write(f'Reason')
            st.info(f"{reason}")


from random import uniform
import numpy as np
import numba

"""
REFs:    M. Hopfensitz et al., "Multiscale Binarization of Gene Expression Data for Reconstructing Boolean Networks,"
        in IEEE/ACM Transactions on Computational Biology and Bioinformatics, vol. 9, no. 2, pp. 487-498, March-April 2012,
        doi: 10.1109/TCBB.2011.62.

        https://github.com/cran/Binarize/blob/master/src/binarizeBASCA.c    Author: Stefan Mundus
        https://github.com/cran/Binarize/blob/master/src/common.c           Author: Stefan Mundus

Algorithm to binarize a vector.
Steps:
    1. Compute a series of step functions (each function minimizes the eucledian distance between the new step function and the original data)
    2. Find strongest discontinuity in each step function
    3. Estimate location and variation of the strongest discontinuities
"""

"""
HELPER FUNCTIONS:
"""

@numba.jit(nopython=True)
def cost_ab(vect, a, b):
    """
    Calculates quadratic distance between original data points and mean of data points in range a to b inclusive
    """
    Yab = np.mean(vect[a:b + 1])
    # + 1 because we want to calculate the sum from a to b, with b inclusive
    return np.sum(((vect - Yab)**2)[a:b+1])

@numba.jit(nopython=True)
def init_cost_matrix(vect):
    N = len(vect)
    C = np.zeros( (N - 1, N), dtype = 'float64')
    C[0] = [cost_ab(vect, i, N-1) for i in range(0, N - 1 + 1)]
    return C

@numba.jit(nopython=True)
def calc_jump_height(vect, P, i, j):
    """
    Calculate jump height/size between data point Pij and Pij + 1, with P the matrix containing location of discontinuities.
    """
    N = len(vect) - 1
    if i == 0 and j > 0:
        return np.mean( vect[P[j, i] + 1:  P[j, i + 1] + 1] ) - np.mean( vect[0: P[j, i] +1] )
    elif i == j > 0:
        return np.mean( vect[P[j, i] + 1: N+1] ) - np.mean( vect[ P[j, i - 1] + 1: P[j, i] + 1] )
    elif i == j == 0:
        return np.mean(vect[  P[j, i] + 1: N +1 ]) - np.mean( vect[0: P[j, i]+1 ] )
    else:
        return np.mean(vect[P[j, i] + 1: P[j, i + 1] + 1]) - np.mean(vect[P[j, i - 1] + 1: P[j, i] + 1])

@numba.jit(nopython=True)
def calc_error(vect, P, i, j):
    """
    Calculate approximation error of a threshold at the discontinuity with respect to the original data
    This is the sum of the quadratic distances of all data points to the threshold z defined by the i-th discontinuity
    """
    N = len(vect)
    z = (vect[P[j, i]] + vect[P[j, i] + 1]) / 2
    return np.sum(((vect - z)**2)[0: N])

@numba.jit(nopython=True)
def moving_block_bootstrap(v):
    N = len(v)
    bootstrappedValues = np.zeros( (N), dtype = 'float64')
    bl = round(N**0.25) + 1
    sample_count = np.ceil(N / bl)
    m = N - bl

    index = 0

    for i in range(0, sample_count):
        rand = round(uniform(-0.5, m + 0.5))
        rand = rand if rand <= m else m

        for j in range(0, bl):
            if index >= N:
                break
            bootstrappedValues[index] = v[rand + j]
            index += 1
    return bootstrappedValues

@numba.jit(nopython=True)
def norm_dev_median(v, vect):
    N = len(vect)
    median_val = np.floor(np.median(v))
    dev = np.abs(v - median_val)
    mean_val = np.mean(dev)
    return mean_val/(N-1)

"""
MAIN FUNCTIONS
"""

@numba.jit(nopython=True)
def calc_cost_and_ind_matrix(vect):
    """
    Calculates the matrix C and ind
        C stores the cost of a step function having j intermediate (rows) discontinuities between data points i and N (columns)
        ind contains indicices of optimal break points of all step functions
    """
    N = len(vect)
    C = init_cost_matrix(vect)
    ind = np.zeros( (N - 2, N), dtype = 'int64')
    for j in range(1, N - 2 + 1):
        for i in range(0, N - j):
            cost_min = np.inf
            d_min = -1
            for d in range(i, N - j):
                cost = cost_ab(vect, i, d) + C[j - 1, d + 1]
                if cost < cost_min:
                    cost_min = cost
                    d_min = d
            C[j, i] = cost_min
            ind[j-1, i] = d_min
    return C, ind

@numba.jit(nopython=True)
def calc_P_matrix(ind):
    """
    Converts ind matrix from calc_cost_and_ind_matrix to a matrix with rows representing the number of discontinuities (1-based) and as values the location of the i-th ()
    """

    P = np.zeros( (len(ind), len(ind[0])), dtype = 'int64')
    for j in range(0, len(ind)):
        z = j
        P[j, 0] = ind[z, 0]
        if j > 0:
            z = z - 1
            for i in range(1, j + 1):
                P[j, i] = ind[z, int(P[j, i - 1]) + 1]
                z = z - 1
    return P

@numba.jit(nopython=True)
def calc_scores(vect, P):
    """
    Calculate the score of al step function discontinuities in P
    This score is the jump height divided by the approximation error
    """

    Q = np.zeros( (len(P), len(P[0])), dtype = 'float64')  # stores scores for each discontinuity
    # stores the score of the discontinuity with the maximum score for each step function
    Q_max = np.zeros( (len(P)), dtype = 'float64')
    # stores the index of the discontinuity with the maximum score for each step function
    ind_Q_max = np.zeros( (len(P)), dtype = 'int64')

    for j in range(0, len(P)):
        q_max = -1
        ind_q_max = -1
        for i in range(0, j + 1):
            # calculate jump height
            h = calc_jump_height(vect, P, i, j)
            e = calc_error(vect, P, i, j)
            q = h/e
            if q > q_max:
                q_max = q
                ind_q_max = P[j, i]
            Q[j, i] = q
        Q_max[j] = q_max
        ind_Q_max[j] = ind_q_max
    return Q, Q_max, ind_Q_max

@numba.jit(nopython=True)
def calc_threshold(vect, v):
    v_med = int(np.floor(np.median(v)))
    return (vect[v_med + 1] + vect[v_med]) / 2

@numba.jit(nopython=True)
def calc_P(v, vect, tau, n_samples):
    nom = norm_dev_median(v, vect)
    t_zero = tau - nom

    p = 1

    for i in range(0, n_samples):
        samples = moving_block_bootstrap(v)
        mdm = norm_dev_median(samples, vect)
        t_star = nom - mdm
        p += (t_star >= t_zero) * 1

    p /= (n_samples + 1)

    return p

@numba.jit(nopython=True)
def binarize(vect, tau=0.01, n_samples=999, calc_p=True, max_elements=100):
    # original step function is just the sorted vector
    vect_sorted = np.sort(vect)

    # if vector is too long, only use top features (scalability)
    if len(vect_sorted) > max_elements:
        vect_sorted = vect_sorted[0:max_elements]

    # step 1: Compute a series of step functions (each function minimizes the eucledian distance between the new step function and the original data)
    _, ind = calc_cost_and_ind_matrix(vect_sorted)
    P = calc_P_matrix(ind)

    # step 2: Find strongest discontinuity in each step function
    Q, _, v = calc_scores(vect_sorted, P)

    # step 3: Estimate location and variation of the strongest discontinuities
    threshold = calc_threshold(vect_sorted, v)
    #p_val = calc_P(v, vect_sorted, tau, n_samples) if calc_p else None

    binarized_measurements = [(val > threshold) * 1 for val in vect]

    return threshold, binarized_measurements


import gym
from gym import spaces
import pandas as pd
from IPython.display import display
from IPython.display import clear_output
import numpy as np
from loc import Location

class HunterWorldEnv(gym.Env):

    def __init__(self, agent, target, size=10, n_border=20, agent_locs=[]):
        self.size = size
        self.agent = agent
        self.target = target
        self.observation_space = spaces.Dict(
            {
                "agent": spaces.Box(0, np.array([size - 1, size - 1, 1]), dtype=int),
                "target": spaces.Box(0, size - 1, shape=(2,), dtype=int),
            }
        )
        self.action_space = spaces.Discrete(5)
        self._action_to_direction = {
            0: np.array([1, 0]),
            1: np.array([0, 1]),
            2: np.array([-1, 0]),
            3: np.array([0, -1]),
        }
        self.borders = self.create_borders(n_border, size, agent_locs)

    def create_borders(self, n, size, agent_locs):
        borders = []
        for i in range(n):
            loc = Location(np.random.randint(size), np.random.randint(size))
            while loc == self.agent.location or loc == self.target.location or loc in self.target.route or loc in agent_locs:
                loc = Location(np.random.randint(size), np.random.randint(size))
            borders.append(loc)
        return borders

    def _get_obs(self):
        agent = self.agent.get_state()
        target = self.target.get_state()
        desk = np.zeros((1, self.size + 2, self.size + 2))
        desk[0] = np.pad(self.render(), 1, constant_values=-1)
        obs = desk[:, self.agent.location.y:self.agent.location.y + 3,
                      self.agent.location.x:self.agent.location.x + 3]
        return {"agent": agent,
                "target": target,
                "state_bse": np.concatenate([agent, target]),
                "state_net": obs}

    def _get_info(self):
        return {"distance": self.agent.location.dist(self.target.location)}

    def reset(self):
        self.agent.reset()
        self.target.reset()
        observation = self._get_obs()
        info = self._get_info()
        return observation, info

    def step(self, action):
        if action == 4:
            if self.agent.location == self.target.location and self.agent.purpose == 0:
                if self.agent.attack > self.target.escape:
                    self.agent.purpose = 1
                    self.target.update()
                    return self._get_obs(), 1000, False, self._get_info()
                self.agent.attack += 0.1
                self.target.run()
                return self._get_obs(), 1000, False, self._get_info()
            return self._get_obs(), -500, False, self._get_info()

        direction = self._action_to_direction[action]
        new_loc = np.clip(self.agent.location.toNumpy() + direction, 0, self.size - 1)
        new_loc = Location(new_loc[1], new_loc[0])
        if new_loc not in self.borders:
            self.agent.location = new_loc

        observation = self._get_obs()
        info = self._get_info()
        reward = -info['distance']

        if self.agent.location == self.target.location and self.agent.purpose == 1:
            return observation, 1000, True, info

        return observation, reward, False, info

    def print(self):
        clear_output(wait=True)

        def cell_color(val):
            color = 'white'
            if val == 'A':
                color = 'blue'
            if val == 'T':
                color = 'green'
            if val == 'X':
                color = 'red'
            if val == 'O':
                color = 'yellow'
            if val == 'B':
                color = 'black'
            return 'color: %s' % color

        n = self.size
        desk = np.full((n, n), '.').astype(str)
        desk[self.agent.location.y, self.agent.location.x] = 'A'
        desk[self.target.location.y, self.target.location.x] = 'T' if self.agent.purpose == 0 else 'O'
        if self.agent.location == self.target.location and self.agent.purpose == 0:
            desk[self.agent.location.y, self.agent.location.x] = 'X'
        for border in self.borders:
            desk[border.y, border.x] = 'B'
        display(pd.DataFrame(desk).style.applymap(cell_color))

    def render(self):
        n = self.size
        desk = np.full((n, n), 0).astype(int)
        desk[self.agent.location.y, self.agent.location.x] = 1
        desk[self.target.location.y, self.target.location.x] = 2 if self.agent.purpose == 0 else 3
        if self.agent.location == self.target.location and self.agent.purpose == 0:
            desk[self.agent.location.y, self.agent.location.x] = 4
        for border in self.borders:
            desk[border.y, border.x] = 5
        return desk


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

"""
This file is part of OpenSesame.

OpenSesame is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

OpenSesame is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with OpenSesame.  If not, see <http://www.gnu.org/licenses/>.
"""

from libopensesame.py3compat import *

from PyQt4 import QtCore, QtGui
from libqtopensesame.widgets.base_widget import base_widget

class quick_open_element_symbol(base_widget):

	"""
	desc:
		A symbol element for the quick switcher.
	"""

	def __init__(self, item, phase, symbol):

		"""
		desc:
			Constructor.

		arguments:
			item:
				desc:	An inline_script item.
				type:	inline_script
			phase:
				desc:	The phase, i.e. 'Run' or 'Prepare'.
				type:	unicode
			symbol:
				desc:	A symbol tuple as returned by QProgEdit.
				type:	tuple
		"""

		super(quick_open_element_symbol, self).__init__(item.main_window)
		self.item = item
		self.phase = phase
		self.lineNo, self._type, self.name, self.argspec = symbol
		self.icon = self.theme.qlabel(u'os-symbol')
		self.label = QtGui.QLabel(u'%s %s
In: %s [%s:%d]' % (
			self._type, self.name, self.item.name, self.phase, self.lineNo))
		self.layout = QtGui.QHBoxLayout(self)
		self.layout.addWidget(self.icon)
		self.layout.addWidget(self.label)
		self.layout.addStretch()
		self.setLayout(self.layout)

	def match(self, query):

		"""
		returns:
			desc:	True if the symbol matches the query, False otherwise.
			type:	bool
		"""

		_item_type = self.item.item_type.lower()
		_item_name = self.item.name.lower()
		_name = self.name.lower()
		_type = self._type.lower()
		_phase = self.phase.lower()
		for term in query.lower().split():
			if term not in _item_type and term not in _item_name and \
				term not in _type and term not in _phase and term not in _name:
				return False
		return True

	def activate(self):

		"""
		desc:
			Opens the inline_script tab, switches to the correct phase and sets
			the cursor at the symbol line.
		"""

		self.item.open_tab()
		tab = self.item.qprogedit.tab(self.phase)
		tab.focusTab()
		tab.setCursorPosition(self.lineNo-1, 0)


# -*- coding: utf-8 -*-
"""
Created on Mon Mar 19 12:31:19 2018

@author: janvr
"""

import time
import sys

start = time.time()

class Node():
	def __init__(self, key, C):
		self.left   = None
		self.right  = None
		self.full   = [0]*C
		self.key    = key

def check_out(fname, paths):
	out = open(fname[0:6]+ "out", "r")
	output = int(out.readline())
	if output == paths: print("Correct")
	else: print("Wrong")

def sys_read_in():
	line1_str = sys.stdin.readline()
	N, C, D = line1_str.split()
	N = int(N)
	C = int(C)
	D = int(D)
	line2_str = sys.stdin.readline()
	colors_str = line2_str.split()
	colors = [int(x) for x in colors_str]
	connections = []
	for i in range(1, N):
		linex = sys.stdin.readline()
		a, b = [int(y) for y in linex.split()]
		connections.append([a, b])
	return(N, C, D, colors, connections)

def read_in(fname):
	file = open(fname)
	line1_str = file.readline()
	N, C, D = line1_str.split()
	N = int(N)
	C = int(C)
	D = int(D)
	line2_str = file.readline()
	colors_str = line2_str.split()
	colors = [int(x) for x in colors_str]
	connections = []
	for i in range(1, N):
		linex = file.readline()
		a, b = [int(y) for y in linex.split()]
		connections.append([a, b])
	return(N, C, D, colors, connections)

def make_tree(colors, connection):
	nodes = []
	for i in range(len(colors)):
		nodes.append(Node(colors[i], C))

	for p, c in connections:
		if nodes[p].left == None:
			nodes[p].left = nodes[c]
		else:
			nodes[p].right = nodes[c]
	return(nodes)

def fullnodes(node):
	count = [0]*C

	if node.left:
		count2 = fullnodes(node.left)
		count = [count[i] + count2[i]  for i in range(C)]

	if node.right:
		count2 = fullnodes(node.right)
		count = [count[i] + count2[i] for i in range(C)]

	count[node.key] += 1

	for i, x in enumerate(count):
		if x == D: node.full[i] = 1

	return(count)

def countpaths(node, fullness, pths = 0):

	fullness = [fullness[i] + x  for i, x in enumerate(node.full)]

	if node.left:  pths = countpaths(node.left,  fullness, pths)
	if node.right: pths = countpaths(node.right, fullness, pths)
	if node.left == None and node.right == None:
		if all(fullness) > 0: pths+=1

	return(pths)

fname = "pub09.in"
start1 = time.time()
N, C, D, colors, connections = read_in(fname)   #text file input
#N, C, D, colors, connections = sys_read_in()    #console input
print("Input took:", time.time()-start1)
start2 = time.time()
nodes = make_tree(colors, connections)
print("Making a tree took:", time.time()-start2)
start3 = time.time()
startnode = nodes[0]
fullnodes(startnode)
print("fullnodes took:", time.time()-start3)
start4  = time.time()
fullness = [0]*C
paths = countpaths(startnode, fullness)
print("countpaths took:", time.time()-start4)
print("Paths:", paths)
print("Total time:", time.time()-start)
check_out(fname, paths)


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

import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "seahub.settings")
import django
django.setup()

from seaserv import ccnet_api
from keeper.utils import is_in_mpg_domain_list

def get_user_stats():
    """get KEEPER user stats
    """
    try:
        users = ccnet_api.get_emailusers('DB', -1, -1) + \
        ccnet_api.get_emailusers('LDAPImport', -1, -1)
    except Exception as e:
        print ('Error: {}'.format(e))
        return

    users_activated = [u for u in users if u.is_active]
    mpg_users_activated = [u for u in users_activated if is_in_mpg_domain_list(u.email)]
    external_users_activated = [u for u in users_activated if not is_in_mpg_domain_list(u.email)]

    print("KEEPER users \n --total: {}\n --activated: {}\n   --MPG activated: {}\n   --external activated: {}".
          format(len(users), len(users_activated), len(mpg_users_activated), len(external_users_activated)))

if __name__ == "__main__":
    get_user_stats()


import sys

from Utils import util

sys.path.append("./../random_delete")
import time_restriction

project_name_list = ["project name here"]

BOOTSTRAP_SIZE = 100
TIME_FILTERING_MIN = 10
NTEXT_TH = 3
WORD_ASSOC_TH = 5
COMMENT_COS_TH = 4
NSD_SIM_COS_TH = 2

def _compare_org_new(org_dict, new_dict):
    print("===")
    print("num issue ids -- no restriction: {0:,}, with restriction: {1:,}, diff: {2:,} ({3}%)".format(len(org_dict), len(new_dict), len(org_dict) - len(new_dict), 100*round((len(org_dict)-len(new_dict))/len(org_dict), 3)))

    org_commit_set = set()
    new_commit_set = set()
    for issue_id in org_dict.keys():
        org_commit_set = org_commit_set | set(org_dict[issue_id])

    for issue_id in new_dict.keys():
        new_commit_set = new_commit_set | set(new_dict[issue_id])

    print("num commit hashes -- no restriction: {0:,}, with restriction: {1:,}, diff: {2:,} ({3}%)".format(len(org_commit_set), len(new_commit_set), len(org_commit_set) - len(new_commit_set), 100*round((len(org_commit_set)-len(new_commit_set))/len(org_commit_set), 3)))

def add_restrictions(p_name, ILA_dict):
    for issue_dir in ILA_dict.keys():

        if issue_dir=="9_1" or issue_dir=="9_2":
            temp_dir = "9"
        elif issue_dir=="14_1" or issue_dir=="14_2":
            temp_dir = "14"
        else:
            temp_dir = issue_dir

        exp40_ILA_set = set(["7", "9", "11", "14"])

        if temp_dir=="1":
            data_dict = util.load_pickle("./../deleted_data/{0}.pickle".format(ILA_dict[issue_dir]))
        elif temp_dir in exp40_ILA_set:
            print("{0}, {1}".format(temp_dir, issue_dir))
            data_dict = util.load_pickle("./../{0}/data/{1}.pickle".format(temp_dir, ILA_dict[issue_dir]))
        else:
            data_dict = util.load_pickle("./../{0}/data/{1}.pickle".format(temp_dir, ILA_dict[issue_dir]))
        util.dump_pickle("./data/{0}.pickle".format(ILA_dict[issue_dir]), data_dict)

        TimeRestriction = time_restriction.TimeRestriction(LINK_DATA_PATH="./data/{0}.pickle".format(ILA_dict[issue_dir]), verbose=1)
        issue2hash_dict = TimeRestriction.run(p_name)

        util.dump_pickle("./data/{0}_with_restriction.pickle".format(ILA_dict[issue_dir]), issue2hash_dict)

def compare_org_new(p_name, ILA_dict):
    for issue_dir in ILA_dict.keys():
        print(ILA_dict[issue_dir])

        org_dict = util.load_pickle("./data/{0}.pickle".format(ILA_dict[issue_dir]))
        new_dict = util.load_pickle("./data/{0}_with_restriction.pickle".format(ILA_dict[issue_dir]))

        _compare_org_new(org_dict, new_dict)

        print("===")

def main():
    for p_name in project_name_list:
        print("===========")
        print(p_name)

        #############################################3
        #############################################3
        #############################################3
        deleted_rate_list = [10, 20, 30, 40, 50]
        for delete_rate in deleted_rate_list:
            print("delete rate: {0}".format(delete_rate))

            #ILA_dict = {"8": "{0}_comment_costh{1}".format(p_name, COMMENT_COS_TH),
            ILA_dict = {"8": "{0}_comment_costh0.{1}_ite1".format(p_name, COMMENT_COS_TH),
                        "10": "{0}_nsd_similarity_costh{1}".format(p_name, NSD_SIM_COS_TH)}

            add_restrictions(p_name, ILA_dict)
            #compare_org_new(p_name, ILA_dict)

        for bootstrap_idx in range(BOOTSTRAP_SIZE):

            for delete_rate in deleted_rate_list:
                print("delete rate: {0}".format(delete_rate))

                ILA_dict = {"7": "{0}_{1}_word_association_th{2}_bi{3}".format(p_name, delete_rate, WORD_ASSOC_TH, bootstrap_idx),
                            "11": "{0}_{1}_pu_link_bi{2}".format(p_name, delete_rate, bootstrap_idx)}

                add_restrictions(p_name, ILA_dict)
                #compare_org_new(p_name, ILA_dict)

        #############################################3
        #############################################3
        #############################################3
        deleted_rate_list = [0, 10, 20, 30, 40, 50]
        for delete_rate in deleted_rate_list:
            print("delete rate: {0}".format(delete_rate))

            ILA_dict = {"3": "{0}_time_filtering_min_af{1}".format(p_name, TIME_FILTERING_MIN),
                        "5": "{0}_ntext_similarity_costh{1}".format(p_name, NTEXT_TH)}

            add_restrictions(p_name, ILA_dict)
            #compare_org_new(p_name, ILA_dict)

        for bootstrap_idx in range(BOOTSTRAP_SIZE):
            for delete_rate in deleted_rate_list:
                print("delete rate: {0}".format(delete_rate))

                ILA_dict = {"1": "{0}_keyword_extraction_{1}_{2}".format(p_name, delete_rate, bootstrap_idx),
                            "9_1": "{0}_{1}_loner_bi{2}".format(p_name, delete_rate, bootstrap_idx),
                            "9_2": "{0}_{1}_phantom_bi{2}".format(p_name, delete_rate, bootstrap_idx),
                            "14_1": "{0}_RF_{1}_model_bi{2}".format(p_name, delete_rate, bootstrap_idx),
                            "14_2": "{0}_SVM_{1}_model_bi{2}".format(p_name, delete_rate, bootstrap_idx)}

                add_restrictions(p_name, ILA_dict)
                #compare_org_new(p_name, ILA_dict)

if __name__=="__main__":

    main()


import os
import random

n = int(input("Enter a number: "))
array = []
summ = 0

try:
    for i in range(n):
        array.append(random.randint(-n, n))
        print(array[i], end=" ")

    print("\n")

    file = open('Numbers.txt', 'r')

    while True:
        line = file.readline()
        if not line:
            break

        summ += array[int(line)]
        print(summ, end=" ")

    print("\n")
    print("Result = " + str(summ))
except IndexError:
    print("list index out of range")


#!/usr/bin/env python3

import re
import sys

import requests

ip = sys.argv[1]

data = requests.get(f'http://{ip}:9171/_debug_toolbar/sse')
data = eval(data.text.split('\n')[2][5:])
print(data)
ids = list(map(lambda x: x[0], data))
print(ids)

for each in ids:
    r = requests.get(f'http://{ip}:9171/_debug_toolbar/{each}')
    print(re.findall('[A-Z0-9]{31}=', r.text), flush=True)


# c = input()

# dic = {"č":"c=", "ć":"c-", "dž":"dz=", "đ":"d-", "lj":"lj", "nj":"nj", "š":"s=", "ž":"z="}
# result= ''
# for i in c:
#     if i not in dic:

# croatia = ['c=', 'c-', 'dz=', 'd-', 'lj', 'nj', 's=', 'z=']
# word = input()

# for i in croatia :
#     word = word.replace(i, '*')  # input 변수와 동일한 이름의 변수
# print(len(word))

#========================

word = input()
c = ['c=', 'c-', 'dz=', 'd-', 'lj', 'nj', 's=', 'z=']
count = 0

for i in range(len(c)):
    if c[i] in word: #입력 받은 단어안에 크로아티아 문자가 포함되어있는지?
        w_count = word.count(c[i]) #입력 받은 단어 안에 크로아티아 첫번쨰 문자가 몇개있는지?
        word = word.replace(c[i], " ")
        count += w_count #입력 받은 단어 안에 크로아티아 문자가 몇개있는지 누적해줌
word = word.replace(" ", "") #공백을 공백 없애는 것으로 바꿈
count += len(word)

print(count) # 크로아티아 대체 문자들이 얼마나 있는지 탐색 후, 있다면
# 한개씩으로 만들어서 카운트에 저장 후 남은 문자열과 더해줌
# 특이점은 w_count를 사용하여 몇개 있는지 확인 후 여러개가 있다면 카운트에 더해줌

2941_크로아티아알파벳.py


def is_magic(arr):
    #  slow method

    dim = len(arr)
    sum_rows = [0] * dim
    sum_columns = [0] * dim
    sum_diag_1 = 0
    sum_diag_2 = 0
    for i in range(dim):
        for j in range(dim):
            elem = arr[i][j]
            sum_rows[i] += elem
            sum_columns[j] += elem

            if i == j:
                sum_diag_1 += elem
            if i == dim - j - 1:
                sum_diag_2 += elem

    if len(set([sum_diag_1, sum_diag_2] + sum_rows + sum_columns)) > 1:
        return False
    else:
        return True

m = []
dim = int(input())

for _ in range(dim):
    m.append( list( map(int, input().split()) ) )

ans = is_magic(m)
print("YES" if ans else "NO")


import io
import requests

def grower_export(conn, date, organization_id, ckan_config):
    """Prints a message with the current time"""
    print("capture_export...")
    # check yearMonth (yyyy-mm) format with regex
    import re
    if not re.match(r'^\d{4}-\d{2}-\d{2}$', date):
      msg = f'date format error {date}';
      raise ValueError(msg)

    # check organization_id is int
    if not isinstance(organization_id, int):
      msg = f'organization_id must be int';
      raise ValueError(msg)

    import datetime
    # create a new file called 'temp.csv' in the current directory
    # date = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M")
    file_name = f'grower_{datetime.datetime.now().strftime("%Y-%m-%d-%H-%M")}.csv'

    # check if the resource are already in the CKAN
    # get the resource list from CKAN
   # get the resource list from CKAN
    url = f"{ckan_config['CKAN_DOMAIN']}/api/3/action/package_show?id={ckan_config['CKAN_DATASET_NAME_GROWER_DATA']}"
    print("url:", url)
    response = requests.get(url,
        headers={"X-CKAN-API-Key": ckan_config['CKAN_API_KEY']}
    )
    print ('response:', response)
    # throw an error if the resource is not found
    package_data = response.json()
    # print('pacage data:', package_data)
    id = package_data['result']['id']
    # e.g. 75aeeb9c-f671-408b-8b08-f24ec0edefb0
    # using Regex to check if the id is in the format of UUID
    import re
    if not re.match(r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', id):
        raise ValueError('id format error:', id)
    resources = package_data['result']['resources']
    # go through the resource list
    for resource in resources:
        # check if the resource is already in the CKAN
        if resource['name'] == file_name:
            print('resource already in the CKAN')
            raise ValueError(f'resource {file_name} already in the CKAN')

    # create cursor
    cur = conn.cursor()
    # array of file names
    columns = ["id","first_name","last_name","email","phone","image_url", "registed_at"]
    sql = f"""
SELECT
  p.id,
  p.first_name,
  p.last_name,
  p.email,
  p.phone,
  p.image_url,
  created_at as registed_at
FROM
  planter p
LEFT JOIN
  planter_registrations pr
ON pr.planter_id = p.id
WHERE
  pr.created_at < '{date}'
  AND p.organization_id IN (
    select entity_id from getEntityRelationshipChildren({organization_id})
  )
ORDER BY p.id DESC
LIMIT 20;
        """;

    print("SQL:", sql)
    # execute query
    cur.execute(sql)
    # fetch all rows
    rows = cur.fetchall()
    lines = [','.join(columns)]
    # print rows
    print ("rows len:", len(rows))
    for row in rows:
        # join row elements with comma
        line = ",".join(str(v) for v in row)
        # add line to lines
        lines.append(line)
    print ("lines length:", len(lines))
    # close connection
    conn.close()

    print ("upload file")
    import urllib.request as urllib2
    import urllib
    import json
    import pprint
    import datetime

    try:
        # convert lines to file like object
        f = io.StringIO("\n".join(lines))
        url = f"{ckan_config['CKAN_DOMAIN']}/api/action/resource_create"
        print("url:", url)
        r = requests.post(url,
            data={
                "package_id": id,
                "url": "http://test.com/sample.csv",
                "name": file_name,
                'description': 'export grower data',
                'format': 'csv',
                'url_type': 'upload',
                'resource_type': 'file.upload',
                'mimetype': 'text/csv',
                'hash': '',
                'size': 0,
                'cache_url': '',
                'cache_last_updated': None,
                'webstore_last_updated': None,
                'webstore_url': None,
              },
            headers={"X-CKAN-API-Key": ckan_config['CKAN_API_KEY']},
            files={'upload': (file_name, f)}
            # files={'report.xls': f}
        )
        # read responce from server
        print ("r:", r)
        print ("r.text:", r.text)
    except urllib2.HTTPError as e:
        print('Error code: {0}'.format(e.code))
        print(e.read())
        raise e
    return "Done"


class Solution:
    def minOperations(self, nums, x):
        target = sum(nums) - x
        sums = {0: -1}
        acc_sum = 0
        max_length = 0
        for i in range(len(nums)):
            acc_sum += nums[i]
            if acc_sum - target in sums:
                max_length = max(max_length, i - sums[acc_sum - target])
            else:
                sums[acc_sum] = i

        if max_length == 0 and target != 0:
            return -1
        return len(nums) - max_length

LeetCode_30days_challenge/2021/January/Minimum Operations to Reduce X to Zero.py


"""pikamera is an encapsulation of picamera

    It only does three things:
        Starts a preview
        Stops a preview
        Take a snapshot

    If it is not running on a Pi, it just pretends to work
#"""
try:
    from picamera import PiCamera
    CAMERA = 'pi'
except:
    CAMERA = 'pseudo'
from time import sleep
import shutil

if CAMERA == 'pi':
    camera = PiCamera()
def Snapshot(config,path):
    if CAMERA == 'pi':
        camera.resolution = (int(config['app']['resolution_width']),int(config['app']['resolution_height']))
        camera.start_preview(fullscreen=False,window=(int(config['app']['pvw_left_margin']),
                                                    int(config['app']['pvw_top_margin']),
                                                    int(config['app']['preview_width']),
                                                    int(config['app']['preview_height'])))
        sleep(2)
        camera.capture(path)
        camera.stop_preview()
    else:
        shutil.copyfile('sample.jpg',path)

def Preview(config):
    if CAMERA == 'pi':
        camera.resolution = (int(config['app']['resolution_width']),int(config['app']['resolution_height']))
        camera.start_preview(fullscreen=False,window=(int(config['app']['pvw_left_margin']),
                                                    int(config['app']['pvw_top_margin']),
                                                    int(config['app']['preview_width']),
                                                    int(config['app']['preview_height'])))

def Stop():
    if CAMERA == 'pi':
        camera.stop_preview()


from nikola.plugin_categories import ShortcodePlugin
from nikola.utils import req_missing

from html import escape

try:
    from crossref.restful import Works
except ImportError:
    Works = None

class DoiShortcodePlugin(ShortcodePlugin):
    """Return an HTML element containing the author list, title and publication year associated to a given DOI that can be styled as a tooltip"""

    name = "doi"
    options = "data-tooltip-persistent=\"true\" data-tooltip-maxwidth=\"300px\""

    def _error(self, msg):
        self.logger.error(msg)
        return '
{}
'.format(msg)

    def handler(self, doi, text=None, site=None, data=None, lang=None, post=None):
        if Works is None:
            msg = req_missing(['crossrefapi'], 'use the doi shortcode', optional=True)
            return self._error(msg)

        works = Works()
        work = works.doi(doi)

        if text is None:
            text = doi

        if work is None:
            return self._error('Cannot find any item associated to "{0}"'.format(doi))

        doi_url = work['URL']
        authors = ", ".join([f"{author['given']} {author['family']}" for author in work['author']])
        title = work['title'][0]
        year = work['created']['date-parts'][0][0]

        tooltip = """
        {2} ({3}).
        {4}.
        {5}""".format(doi_url, text, authors, year, title, work['DOI'])

        tooltip = escape(tooltip)

        html = "{1}".format(doi_url, text, tooltip, DoiShortcodePlugin.options)

        return html, []


import os
import sys

def parent_dir():
  return os.path.abspath(
    os.path.join(
      os.path.dirname(os.path.abspath(__file__)),
      '../'
    )
  )

sys.path.append(parent_dir())

import unittest
import clangdom

def test_file(f):
  return os.path.join(
    os.path.dirname(os.path.abspath(__file__)), f)

class TestCppParsing(unittest.TestCase):
  def setUp(self):
    self.parser = clangdom.Parser()

  def test_basic_cpp(self):
    unit = self.parser.parse(test_file('simple.cpp'))
    self.assertEquals(len(unit.functions), 2)
    f = unit.functions[0]
    self.assertEquals(f.name, "f")
    self.assertEquals(len(f.params), 1)
    self.assertEquals(f.params[0].name, 'a')
    self.assertEquals(f.params[0].type, 'int')

    f = unit.functions[1]
    self.assertEquals(f.name, "main")
    self.assertEquals(len(f.params), 0)

  def test_include_files(self):
    unit = self.parser.parse(test_file('include.cpp'))
    self.assertEquals(len(unit.includes), 4)
    self.assertTrue(unit.includes[0].name.endswith('vector'))
    self.assertTrue(unit.includes[1].name.endswith('undefined'))
    self.assertTrue(unit.includes[2].name.endswith('some/fairy/land'))
    self.assertTrue(unit.includes[3].name.endswith('../and/its/parent'))

  def test_irbuilder_h(self):
    unit = self.parser.parse(test_file('IRBuilder.h'))
    self.assertEquals(len(unit.includes), 13)
    self.assertEquals(len(unit.namespaces), 1)
    self.assertEquals(len(unit.namespaces[0].classes), 4)

if __name__ == '__main__':
  unittest.main()


import numpy
import numpy as np
import logging, os
import pandas as pd
import plotly.graph_objects as go
from tabulate import tabulate
import plotly.express as px
# import EntropicFOMs
from EntropicFOMs.SyntheticData import *
from EntropicFOMs.ObjFunctions import *
import numpy.random as nprand
import numpy.linalg as nplin
logging.basicConfig(level = logging.INFO)

class sFoms(object):
    """
    The class for first order stochastic methods to solve the strongly convex optimization problem.

    Attributes
    ----------
        path:  str
            The path to the folder to which the results are going to be saved.
        optim_problem: str
            quadratic:          Smooth strongly convex quadratic cost function.
            synthetic_logit:    Smooth strongly convex logistic cost defined on synthetic data.
        init_x0:
            The initial point for the algorithm.
              None:     The initialization sampled from a standard uniform distribution.
              "fixed":  The initialization is unit vector.
              np.ndarray: (# of feat, 1)
        eig_vals: np.array
            The eigenvalues of the Hessian of quadratic objective function.
            Required by "quadratic" optimization problem..
        reg_param: np.array
            The regularization parameter of the optimization problem. Set to be 1 by default.
        noise_std: float (default: 0)
            The standard deviation of the additive Gaussian noise on the gradient of the cost function.
        data_std: float
            The standard deviation of the random matrix used to generate synthetic data.
            Required by "synthetic_logit" optimization problem.
        data_size:
            The size of the syntethic data required by "synthetic_logit" optimization problem.
        num_of_feat:
            The number of features in the "synthetic_logit" optimization problem.

    Functions
    -------
    train(learning_rate=0.01, iter_momentum=0, grad_momentum=0, stop_criteria=None, Samples=1, seed=None)
        Trains the model using stochastic first order method:
        .. math ::

            x_{k+1}=x_{k}+iter_momentum*(x_{k}-x_{k-1})-learning_rate*\tilde{\nabla}f(x_k)+\gamma*(x_{k}-x_{k-1})

    subopt_fig_data():
        Generates mean, one standard deviation below and above of the mean for the iterates of the  stochastic first order method.
    """
    # Path to the folder in which the results are going to be stored.
    # Enter local folder manually:
    # path = "local folder/",
    # or retrieve the current path and create an experiment results folder inside the current path:
    path=os.getcwd()+"/risk-averse GMM experiments/"
    # Check whether the path exists. Create if it does not
    if not os.path.isdir(path):
        os.mkdir(path)

    def __init__(self, optim_problem=None,eig_vals=None, init_x0=None, reg_param=0,
    noise_std=0, data_std=None, data_size=None, num_of_feat=None):
        self.__dict__.update(locals())
        # Quadratic objectives
        if optim_problem == "quadratic":
            if self.eig_vals is None:
                raise Exception("Enter the attribute: eig_vals ")
            self.num_of_feat = len(self.eig_vals)
            self.data_optim=None

            # Define the objective function
            self.func= lambda x: quadratic_cost(x, eigs=self.eig_vals, regulizer=self.reg_param, sigma=self.noise_std)

        # Logistic regression on synthetic data
        elif optim_problem == "synthetic_logit":
            # Check for attributes
            if (self.data_std or self.data_size or self.num_of_feat) is None:
                raise Exception("Enter the attributes: data_std, data_size and num_of_feat ")

            # Generate the synthetic data
            self.X, self.Y, self.data_optim = Create_Classification_Data(self.data_std, self.data_size, self.num_of_feat,
                                                                  seed=1)
            try:
                self.noise_std
            except:
                self.noise_std=0
            try:
                self.reg_param
            except:
                logging.info("Setting regularization parameter = 1")
                self.reg_param=float(1)
            # Define the objective function
            self.func = lambda w: logistic_cost(w, self.X, self.Y, reg=self.reg_param, sigma=self.noise_std)

        else:
            raise Exception("Unknown optimization problem. The allowed methods are 'quadratic' and 'synthetic_logit'.")

        if self.init_x0 is None and self.optim_problem is not None:
            self.init_x0 = nprand.normal(size=(self.num_of_feat, 1))
        elif self.init_x0 == "fixed" and self.optim_problem is not None:
            self.init_x0 = np.ones((self.num_of_feat, 1))

    def train(self, learning_rate=0.01, iter_momentum=0, grad_momentum=0, stop_criteria=None, Samples=1,seed=None):
        """
        Runs the first order methods under given choice of parameters to solve the optimization problem, optim_problem, until stopping criteria is satisfied.

        :param learning_rate: float
            The learning rate of the algorithm.
        :param iter_momentum: float (default: 0)
            The momentum parameter on the iterations, i.e. \beta
        :param grad_momentum: float (default: 0)
            The momentum parameter on the iterates on which the gradient is computed, i.e. \gamma
        :param stop_criteria: (default: None)
            None: The algorithm runs until the gradient norm computed at the iteration is below 1e-4 or number
                  of iterations is less than 2000.
            int: The algorithm runs until the number of iterations reaches stop_criteria.
        :param Samples: (default: 100)
            The number of sample paths that will be generated.
        :param seed: int (default: None)
            Fix the seed for random number generators.
        :return:
            csv file:
                The function values, generated using stochastic first
                order methods, together with the last iterate are saved to
                    "path/optim_problem".
        """
        if seed is not None: nprand.seed(seed)

        logging.info( f"Training model...\n"\
                      f"learning rate |  { learning_rate :2.3f} | Iteration momentum |  {iter_momentum :2.3f} "\
                      f"| Gradient Momentum | {grad_momentum : 2.3f}")
        # Store the results at fk_values.
        colnames=[str(i) for i in range(0,Samples)]
        self.suboptimality_data = pd.DataFrame([], columns=colnames)

        # Generate the results path
        if iter_momentum == 0 and grad_momentum == 0:
            method_name = "_GD" + "_Var_" + str(self.noise_std)
        elif iter_momentum != 0 and grad_momentum == 0:
            method_name = "_Polyak" + "_Var_" + str(self.noise_std)
        elif iter_momentum != 0 and grad_momentum == iter_momentum:
            method_name = "_AGD" + "_Var_" + str(self.noise_std)
        else:
            method_name = "_TMM" + "_Var_" + str(self.noise_std)

        # Assign results to attribute for further reference.
        self.suboptimality_data_path = self.path + self.optim_problem +"_subopt" + method_name + ".csv"
        self.log_path = self.path+self.optim_problem+"_log"+method_name+".txt"

        self.train_result=None # Collect last iterates of each trial

        # Generate the log for the algorithm
        alg_log=f"The Parameters: \n"\
                      f"learning rate |  { learning_rate :2.3f} | Iteration momentum |  {iter_momentum :2.3f} "\
                      f"| Gradient Momentum | {grad_momentum : 2.3f}"+"\n"
        x0=self.init_x0

        for trial in range(Samples):
            # Initiate algorithm
            run_algorithm = True
            num_of_iterations=1
            xk=x0
            xk_1=x0
            fn, gn = self.func(xk)
            iterates=[fn]
            if trial==0:
                logging.info(f"Iterations of 1st sample:")
                alg_log+=f"Iterations of 1st sample: \n"

            # Run the algorithm
            while run_algorithm:
                yk=xk+iter_momentum*(xk-xk_1)
                zk=xk+grad_momentum*(xk-xk_1)
                _, gn= self.func(zk)
                x_next=yk-learning_rate*gn
                xk_1=xk
                xk=x_next
                fn, gn = self.func(xk)
                iterates=np.append(np.around(iterates,decimals=5),fn)
                # Logging
                if trial == 0 and num_of_iterations%50==0 :
                    logging.info(f"Iteration: {num_of_iterations}| Function Value: {fn :.4f}"+f"| Norm of gradient: {np.linalg.norm(gn): .4f}")
                    alg_log+= f"Iteration: {num_of_iterations}| Function Value: {fn :.4f}"+f"| Norm of gradient: {np.linalg.norm(gn): .4f}"+"\n"
                # Stopping criteria
                num_of_iterations += 1
                if stop_criteria == None:
                    if nplin.norm(gn) < 1e-4 or num_of_iterations>=2000:
                        run_algorithm=False
                else:
                    if num_of_iterations > stop_criteria:
                        run_algorithm=False

            # Collect the iterations of trial.
            self.suboptimality_data[str(trial)]=iterates

            # Collect the last iterate of each trial
            if self.train_result is None:
                self.train_result=xk.reshape(-1, 1)
            else:
                self.train_result=np.append(self.train_result,xk.reshape(-1,1),axis=1)

        # Save data into csv file.
        self.suboptimality_data.to_csv(self.suboptimality_data_path)
        logging.info(f'Saved the suboptimality results into the path: \n' \
                     f'{self.suboptimality_data_path}')
        with open(self.log_path, 'w') as logfile:
            print(alg_log,file=logfile)

    def subopt_fig_data(self):
        """
        Generates mean, one standard deviation below and above of the mean for the iterates of the  stochastic first order method.
        :return:
        std_below: pandas.Series (# of iterations)
            The expected suboptimality - std of suboptimality at each iteration.
        mean:     pandas.Series (# of iterations)
            The expected suboptimality path generated by train().

        std_above: pandas.Series (# of iterations)
            The expected suboptimality + std of suboptimality at each iteration.
        """
        # Collect graph data
        graph_data=pd.read_csv(self.suboptimality_data_path, index_col=0)
        # Compute the mean and std
        mean, std = graph_data.mean(1), graph_data.std(1)
        std_above, std_below = mean + std, mean - std
        return std_below, mean, std_above

    def L_and_mu_logistic_reg(self):
        """
        Compute the upper bound on the L-smoothness parameter of the logistic regression over data.
        Source: http://lcsl.mit.edu/courses/isml2/isml2-2015/scribe14A.pdf
        :return:
        """
        s,f=self.X.shape
        eigs, _ =nplin.eig(np.matmul(self.X.T,self.X))
        return 1/s*eigs.max()+self.reg_param, self.reg_param

    def create_subopt_plot(self ,name, fig_color="rgba(0, 91, 247)", Figure= None):
        """
        Generates the plot of suboptimality data generated by train() function.
        :param name: str
            The name of the figure that s going to be printed in the legend
        :param fig_color: str (rgba color format, default: "rgba(0, 91, 247)")
            The color of the figure that is plotted.
        :param Figure: (default:  None)
            None:         Creates a new go.Figure() file and generates the suboptimality figure.
            go.Figure():  Generates the suboptimality figure and adds it into the go.Figure() file provided.
        :return:
            go.Figure()
        """

        if Figure == None:
            subopt_figures= go.Figure()
        else:
            subopt_figures= Figure
        std_below, mean, std_above = self.subopt_fig_data()
        subopt_figures.add_trace(go.Scatter(y=mean,
                                            name=name + "-mean",
                                            line=dict(width=5, color=fig_color[0:-1] + ",1)")))

        subopt_figures.add_trace(go.Scatter(y=std_above,
                                            name="",
                                            fill=None,
                                            # fillcolor='rgba(0, 91, 247,0.30)',
                                            line=dict(width=0, color=fig_color[0:-1] + ",0.25)"),
                                            showlegend=False))
        subopt_figures.add_trace(go.Scatter(y=std_below,
                                            name="",
                                            fill="tonexty",
                                            fillcolor=fig_color[0:-1] + ",0.25)",
                                            mode='none',
                                            showlegend=False))
        subopt_figures.update_layout(legend=dict(orientation="h",
                                                 xanchor="center",
                                                 x=0.5,
                                                 yanchor="top",
                                                 y=1,
                                                 ),
                                     font=dict(size=20),
                                     legend_font_size=25)
        return subopt_figures

    # Functions parameter selection and plotting regions
    #  Quadratic objectives
    def quad_stab_reg(self, eigs, nbins=50):
        """
        Calculates the stable region of GMM, S_q, given in [Can and Gurbuzbalaban, 2022] for convex quadratic
        objectives using grid search.
        :param eigs: np.array
            The eigenvalues of the Hessian of the quadratic objective.
        :param nbins: int
            The number of bins that is going to be used in grid search on all alpha, beta, and gamma.
        :return:
            region: np.array (alpha,beta,gamma ,rho)
                The list that contains the parameters alpha, beta, gamma belonging to stable set and the rate
                rho suggested by Lemma 3.1
        """
        beta, gamma = np.linspace(0,1,nbins), np.linspace(0,1,nbins)
        alpha= np.linspace(0,1/max(eigs),nbins)
        # Alpha should be positive
        alpha= np.delete(alpha, np.where(alpha==0))
        # check the type of eigs:
        if type(eigs) != numpy.ndarray:
            eigs=np.array(eigs)
        region=None
        for a in alpha:
            for b in beta:
                for g in gamma:
                    c_i = (1 + b) - a * (1 + g)*eigs
                    d_i = -b+a*g*eigs
                    rho_i_1=abs(c_i-np.emath.sqrt(c_i**2+4*d_i))/2
                    rho_i_2=abs(c_i+np.emath.sqrt(c_i**2+4*d_i))/2
                    rho_i=np.concatenate([[rho_i_1],[rho_i_2]])
                    rho=np.max(np.max(rho_i,0))
                    if rho<1:
                        if region is None:
                            region=np.array([[a,b,g,rho]])
                        else:
                            region=np.concatenate((region,
                                                   np.array([[a,b,g,rho]])),axis=0)
        return region

    def quad_agd_stab_reg(self, eigs, nbins=50):
        """
        Calculates the stable region of AGD given in [Can and Gurbuzbalaban, 2022] for convex quadratic
        objectives using grid search.
        :param eigs: np.array
            The eigenvalues of the Hessian of the quadratic objective.
        :param nbins: int
            The number of bins that is going to be used in grid search on all alpha, beta, and gamma.
        :return:
            region: np.array (alpha,beta,beta,rho)
                The list that contains the parameters alpha, beta, gamma belonging to stable set and the rate
                rho suggested by Lemma 3.1
        """
        beta = np.linspace(0,1,nbins)
        alpha= np.linspace(0,2/(min(eigs)+max(eigs)),nbins)
        # Alpha should be positive
        alpha= np.delete(alpha, np.where(alpha==0))
        # check the type of eigs:
        if type(eigs) != numpy.ndarray:
            eigs=np.array(eigs)
        region=None
        for a in alpha:
            for b in beta:
                g = b
                c_i = (1 + b) - a * (1 + g)*eigs
                d_i = -b+a*g*eigs
                rho_i_1=abs(c_i-np.emath.sqrt(c_i**2+4*d_i))/2
                rho_i_2=abs(c_i+np.emath.sqrt(c_i**2+4*d_i))/2
                rho_i=np.concatenate([[rho_i_1],[rho_i_2]])
                rho=np.max(np.max(rho_i,0))
                if rho<1:
                    if region is None:
                        region=np.array([[a,b,g,rho]])
                    else:
                        region=np.concatenate((region,
                                               np.array([[a,b,g,rho]])),axis=0)
        return region

    def quad_gd_stab_reg(self, eigs, nbins=50):
        """
        Calculates the stable region of GD given in [Can and Gurbuzbalaban, 2022] for convex quadratic
        objectives using grid search.
        :param eigs: np.array
            The eigenvalues of the Hessian of the quadratic objective.
        :param nbins: int
            The number of bins that is going to be used in grid search on all alpha, beta, and gamma.
        :return:
            region: np.array (alpha, 0, 0,rho)
                The list that contains the parameters alpha, beta, gamma belonging to stable set and the rate
                rho suggested by Lemma 3.1
        """
        alpha= np.linspace(0,2/(min(eigs)+max(eigs)),nbins)
        # Alpha should be positive
        alpha= np.delete(alpha, np.where(alpha==0))
        # check the type of eigs:
        if type(eigs) != numpy.ndarray:
            eigs=np.array(eigs)
        region=None
        for a in alpha:
            b=0
            g=b
            c_i = (1 + b) - a * (1 + g)*eigs
            d_i = -b+a*g*eigs
            rho_i_1=abs(c_i-np.emath.sqrt(c_i**2+4*d_i))/2
            rho_i_2=abs(c_i+np.emath.sqrt(c_i**2+4*d_i))/2
            rho_i=np.concatenate([[rho_i_1],[rho_i_2]])
            rho=np.max(np.max(rho_i,0))
            if rho<1:
                if region is None:
                    region=np.array([[a,b,g,rho]])
                else:
                    region=np.concatenate((region,
                                           np.array([[a,b,g,rho]])),axis=0)
        return region

    def quad_stab_region_borders(self,eigs, nbins=50):
        """
        Calculates the borders/frontier of the stable region of GMM based on quad_stab_reg().
        :param eigs: np.array
            The eigenvalues of the Hessian of the quadratic objective.
        :param nbins: float
            The number of bins that is going to be used in grid search on all alpha, beta, and gamma.
        :return:
            border: np.array (alpha, beta/gamma)
                The region alpha versus the maximum beta/gamma belonging to stable set at given alpha.
        """
        stable_region=self.quad_stab_reg(eigs,nbins)
        non_zero_gamma = np.where(stable_region[:, 2] != 0)
        ratio = np.array(stable_region[non_zero_gamma, 1] / stable_region[non_zero_gamma, 2])
        # Round the alpha to obtain the borders ofg the set
        alpha = np.round(np.array(stable_region[non_zero_gamma, 0]), 3)
        # Retrieve the borders
        border = None
        for a in np.unique(alpha):
            r = ratio[np.where(alpha == a)]
            if border is None:
                border = np.array([[a, r.max()]])
            else:
                border = np.concatenate((border, np.array([[a, r.max()]])))
        return border

    def quad_feas_region(self, stable_region, eigs, theta=1, std=1):
        """
        Calculates the feasible region given in [Can and Gurbuzbalaban, 2022. Prop 3.3].
        :param stable_region: np.array
            The stable region of GMM
        :param eigs: np.array
            The eigenvalues of the Hessian of quadratic objective.
        :param theta:
            The risk averseness parameter.
        :param std:
            The standard deviation on the noise in the gradient.
        :return:
            feas_region: np.array (alpha,beta,gamma, risk_meas, rate)
                The list of parameters alpha, beta, and gamma together with suggested risk measure and the rate.
        """
        # Check the format of stable region
        feas_region= None
        if len(stable_region.shape)==1:
            alpha, beta, gamma = stable_region
        else:
            for params in stable_region:
                # Stable region has alpha,beta,gamma, and rho.
                alpha,beta,gamma, rate = params
                risk_meas=self.quad_risk_measure(alpha, beta, gamma, eigs, theta, std)
                if risk_meas != None:
                    if feas_region is None:
                        feas_region = np.array([[alpha,beta,gamma,risk_meas,rate]])
                    else:
                        feas_region=np.concatenate((feas_region,np.array([[alpha,beta,gamma,risk_meas,rate]])))
        return feas_region

    def quad_feas_region_borders(self, e_vals, nbins=50, theta=1, std=1):
        """
        Calculate the border of the feasible region computed for smooth convex quadratic objectives.

        :param e_vals: np.array
            The eigenvalues of the Hessian of quadratic objective.
        :param nbins: float
            The number of bins for grid searching the parameters.
        :param theta: float
            The risk averseness paramters
        :param std:
            The standard deviation of noise on the gradient.
        :return:
            border: np.array (alpha, beta/gamma)
                The border of the feasible region. That is alpha versus maximum feasible beta/gamma for given alpha.
        """
        stable_region=self.quad_stab_reg(e_vals,nbins)
        feas_region=self.quad_feas_region(stable_region, e_vals, theta, std)
        # feas_region = [alpha,beta, gamma, risk measure, rate]
        non_zero_gamma= np.where(feas_region[:,2]!=0)
        ratio= np.array(feas_region[non_zero_gamma,1]/feas_region[non_zero_gamma,2])
        # Round the alpha to obtain the borders ofg the set
        alpha= np.round(np.array(feas_region[non_zero_gamma,0]),3)
        rate= np.array(feas_region[non_zero_gamma,3])
        # Retrieve the borders
        border=None
        for a in np.unique(alpha):
            r=ratio[np.where(alpha==a)]
            if border is None:
                border= np.array([[a,r.max()]])
            else:
                border= np.concatenate((border, np.array([[a,r.max()]])))

        # ratio= feas_region[:,1]/feas_region[:,2]
        # Save figure as html
        # fig.write_html(fig_path+"/str_cnvx_stable_region.html")
        # Save figure as png
        # fig.write_image(fig_path + "/quad_feasible_region.png")
        return border

    def quad_risk_measure(self, alpha, beta, gamma, eigs, theta=1, std=1):
        """
        Computes the entropic risk measure of strongly convex quadratic objectives,

        :param alpha, beta,gamma : float
            The parameters of the TMM algorithm on quadratic objective
        :param eigs: numpy.darray
            The eigenvalues of the Hessian of quadratic objective.
        :param theta:
            The risk parameter for entropic risk measure
        :param std:
            The variance bound on the noise.
        :return:

        risk_meas:   float or None
        ---------
             Returns none if parameters are not feasible, otherwise computes the entropic risk measure at given risk parameter and noise variance.
        """
        if type(eigs)!= numpy.ndarray:
            eigs=np.array(eigs)
        ratio_nom = alpha * (1 + beta - alpha * gamma * eigs)
        ratio_denom = 2*(1 - beta + alpha * gamma * eigs)*(2 * (1 + beta) - alpha * (1 + 2 * gamma) * eigs)
        if 0 in ratio_denom:
            return None
        else:
            ratio = ratio_nom/ratio_denom
            if max(ratio)<1/theta:
                risk_meas= -std ** 2 / theta * sum(np.log(1 - theta * ratio))
            else:
                risk_meas=None
            return risk_meas

    def quad_risk_meas_vs_rate_region_frontier(self, eigs, method="gmm", nbins=50, theta=1, std=1):
        """
        Calculates the frontier of the region quadratic risk measure vs convergence rate on convex quadratic objectives.

        :param eigs: np.array
            The eigenvalues of the Hessian of the quadratic objective function,
        :param method:
            The optimization algorithm, "gd", "agd", or "gmm".
        :param nbins:
            The number of bins used at grid search
        :param theta:
            The risk averseness parameter
        :param std:
            The standard deviation of the noise on the gradient.
        :return:
            frontier: np.array (rate, risk_measure)
                The rate versus the best risk measure found using grid search for that rate.
        """
        if method == "gmm":
            stable_region=self.quad_stab_reg(eigs,nbins)
        elif method=="agd":
            stable_region = self.quad_agd_stab_reg(eigs, nbins)
        elif method=="gd":
            stable_region = self.quad_gd_stab_reg(eigs,nbins)
        rate_sorted_index=np.argsort(stable_region[:,3])
        sorted_stable_region=stable_region[rate_sorted_index,:]
        rate_vs_risk_meas = None
        for params in sorted_stable_region:
            a,b,g,rate = params
            risk_meas=self.quad_risk_measure(a, b, g, eigs, theta, std)
            # Round the risk measure to have a better border
            if risk_meas is not None:
                # Round the rates to obtain a better border for the region
                rate=np.round(rate,2)
                if rate_vs_risk_meas is None:
                    rate_vs_risk_meas=np.array([[rate,risk_meas]])
                else:
                    rate_vs_risk_meas=np.concatenate((rate_vs_risk_meas,np.array([[rate,risk_meas]])))
        # Retrieve the frontier of the set rate_vs_risk_meas
        frontier= None
        for rho in np.unique(rate_vs_risk_meas[:,0]):
            rho_ind = np.where(rate_vs_risk_meas[:, 0] == rho)
            frontier_risk_meas = np.min(rate_vs_risk_meas[rho_ind,1])
            if frontier is None:
                frontier=np.array([[rho, frontier_risk_meas]])
            else:
                frontier=np.concatenate((frontier, np.array([[rho,frontier_risk_meas]])))
        return frontier

    def quad_evar(self, alpha, beta, gamma, eigs, nbins_theta=50, std=1, conf_level=0.95):
        """
        Computes the EVAR for convex quadratic objective by using grid search over theta to solve the minimization problem:
        ..math:
            EV@R_{1-\zeta}= \inf_{0<\theta} r_{\sigma^2}(\theta)+ \frac{2\sigma^2}{\theta}\log(1/\zeta),
        where $\zeta$ is the confidence level.

        :param alpha: float
            Learning rate of the algorithm
        :param beta: float
            Momentum parameter of the iterates of the algorithm
        :param gamma: float
            Momentum parameter of the gradient of the algorithm
        :param eigs: np.array
            The eigenvalues of the Hessian of the quadratic objective.
        :param nbins_theta:
            The number of bins for the grid searching over theta to compute EVAR by solving the minimization problem.
        :param std:
            The standard deviation of the additive Gaussian noise on the gradient of the objective.
        :param conf_level:
            The confidence level of the EV@R of the algorihtm.
        :return:
            evar: np.array
                The evar computed by solving the minimization problem using grid search over theta.
        """
        theta_range=np.linspace(0,5,nbins_theta)
        # delete theta=0
        theta_range=np.delete(theta_range,0)
        evar=float("inf")
        for theta in theta_range:
            evar_temp=self.quad_risk_measure(alpha, beta, gamma, eigs, theta, std)
            if evar_temp is not None:
                evar_temp+= 2 * std / theta * np.log(1 / conf_level)
                if evar_temp < evar:
                    evar=evar_temp

        return evar

    def frontier_quad_evar_vs_rate_region(self, eigs, method="gmm", nbins_params=50, nbins_theta=50, std=1, conf_level=0.95):
        """
        Calculates the minimum evar suggested at a given rate.

        :param eigs: np.array
            The eigenvalues of the Hessian of quadratic objective.
        :param method:
            The optimization algorithm: "gd", "agd, or "gmm".
        :param nbins_params:
            The number of bins for grid searching over the parameters alpha,beta, and gamma.
        :param nbins_theta:
            The number of bins for grid searchin over theta to find evar.
        :param std:
            The standard deviation of the additive Gaussian noise on the gradient.
        :param conf_level:
            The confidence level for the EV@R
        :return:
            frontier: np.array (rate, evar)
                The rate and the best evar computed at given rate.
        """
        if method == "gmm":
            stable_region = self.quad_stab_reg(eigs, nbins_params)
        elif method == "agd":
            stable_region = self.quad_agd_stab_reg(eigs,nbins_params)
        elif method == "gd":
            stable_region= self.quad_gd_stab_reg(eigs,nbins_params)
        rate_sorted_index = np.argsort(stable_region[:, 3])
        sorted_stable_region = stable_region[rate_sorted_index, :]
        rate_vs_evar = None

        for params in sorted_stable_region:
            a, b, g, rate = params
            evar = self.quad_evar(a, b, g, eigs, nbins_theta, std, conf_level)
            # Round the risk measure to have a better border
            if evar is not None and evar != float("inf"):
                # Round the rates to obtain a better border for the region
                rate = np.round(rate, 2)
                if rate_vs_evar is None:
                    rate_vs_evar = np.array([[rate, evar]])
                else:
                    rate_vs_evar = np.concatenate((rate_vs_evar, np.array([[rate, evar]])))

        # Retrieve the frontier of the set evar vs rate
        frontier = None
        for rho in np.unique(rate_vs_evar[:, 0]):
            rho_ind = np.where(rate_vs_evar[:, 0] == rho)
            frontier_evar = np.min(rate_vs_evar[rho_ind, 1])
            if frontier is None:
                frontier = np.array([[rho, frontier_evar]])
            else:
                frontier = np.concatenate((frontier, np.array([[rho, frontier_evar]])))
        return frontier

    def quad_evar_bound(self, alpha, beta, gamma, eigs, std, conf_level):
        """
        Calculate the evar bound for convex quadratic objectives given in [Can and Gurbuzbalaban, 2022. Theorem 1].
        :param alpha: float
            Learning rate of the algorithm
        :param beta: float
            Momentum parameter on the iterations of the algorithm.
        :param gamma: float
            Momentum parameters on the gradient of the algorithm
        :param eigs: np.array
            The eigenvalues of the Hessian of the quadratic objective.
        :param std:
            The standard deviation of the additive Gaussian noise on the gradient of the objective.
        :param conf_level:
            The confidence level of EV@R
        :return:
            evar_bound: np.array
                The evar bound comoputed at given parameters and confidence level for noise with std provided.
        """
        if type(eigs)!= numpy.ndarray:
            eigs=np.array(eigs)
        d=len(eigs)
        ratio_denom = alpha * (1 + beta - alpha * gamma * eigs)
        ratio_nom = (1 - beta + alpha * gamma * eigs)*(2 * (1 + beta) - alpha * (1 + 2 * gamma) * eigs)
        if 0 in ratio_denom:
            return None
        else:
            u_i = ratio_nom/ratio_denom
            u_min=min(2*u_i)
            theta_0=np.log(1/conf_level)/d*(np.sqrt(1+(2*d)/np.log(1/conf_level))-1)
            evar_bound= std / (u_min * theta_0) * (-d * np.log(1 - theta_0) + 2 * np.log(1 / conf_level))
            return evar_bound

    def frontier_quad_evar_bound_vs_rate(self, eigs, method="gmm", nbins_params=50, std=1, conf_level=0.95):
        """
        Calculates frontier for the region rate vs bound on the evar of quadratic objective.

        :param eigs: np.array
            The eigenvalues of the Hessian of the quadratic objective.
        :param method:
            The optimization method: "gd", "agd", or "gmm".
        :param nbins_params:
            The number of bins to grid search over the parameters alpha, beta, and gamma.
        :param std:
            The standard deviation of the additive Gaussian noise on the gradient.
        :param conf_level:
            The confidence level of EV@R
        :return:
            frontier: np.array (rho, evar_bound)
                The list of rate and the evar bound computed at parameters giving the same rate.
        """
        if method == "gmm":
            stable_region = self.quad_stab_reg(eigs, nbins_params)
        elif method == "agd":
            stable_region = self.quad_agd_stab_reg(eigs, nbins_params)
        elif method == "gd":
            stable_region = self.quad_gd_stab_reg(eigs, nbins_params)
        rate_sorted_index = np.argsort(stable_region[:, 3])
        sorted_stable_region = stable_region[rate_sorted_index, :]
        rate_vs_evar_bound = None

        for params in sorted_stable_region:
            a, b, g, rate = params
            evar_bound = self.quad_evar_bound(a, b, g, eigs, std, conf_level)
            # Round the risk measure to have a better border
            if evar_bound is not None and evar_bound != float("inf"):
                # Round the rates to obtain a better border for the region
                rate = np.round(rate, 2)
                if rate_vs_evar_bound is None:
                    rate_vs_evar_bound = np.array([[rate, evar_bound]])
                else:
                    rate_vs_evar_bound = np.concatenate((rate_vs_evar_bound, np.array([[rate, evar_bound]])))

        # Retrieve the frontier of the set evar vs rate
        frontier = None
        for rho in np.unique(rate_vs_evar_bound[:, 0]):
            rho_ind = np.where(rate_vs_evar_bound[:, 0] == rho)
            frontier_evar = np.min(rate_vs_evar_bound[rho_ind, 1])
            if frontier is None:
                frontier = np.array([[rho, frontier_evar]])
            else:
                frontier = np.concatenate((frontier, np.array([[rho, frontier_evar]])))
        return frontier

    # Strongly convex smooth objectives
    def str_cnvx_stab_reg_params(self, L, mu, vart_nbins=100, psi_nbins=100):
        """
        Finds stable region, S_q, of GMM on strongly convex smooth non-quadratic objective
        which is given in [Can and Gurbuzbalaban, 2022. Theorem 2].
        :param L: float
            The smoothness parameter of the objective
        :param mu: float
            The strong convexity constant of the objective.
        :param vart_nbins:
            The number of bins to be used to grid search over vartheta.
        :param psi_nbins:
            The number of bins to be used to grid search over psi.

        :return:
            stable_set: np.array (vartheta, psi, rate)
                The stable region found by grid search over vartheta and psi.
        """
        kappa = L/mu
        psi_vals_low = np.linspace(0, 1, psi_nbins + 1)
        psi_vals_high = np.linspace(1, 2*kappa*(1+np.sqrt(1-1/kappa)), psi_nbins + 1)
        # Erase the 1 from arrays
        psi_vals_low = np.delete(psi_vals_low, psi_nbins)
        psi_vals_high = np.delete(psi_vals_high, 0)
        psi_vals = np.concatenate([psi_vals_low, psi_vals_high])

        # Collect stable params
        stable_vart, stable_psi, stable_rate= [], [], []

        for psi in psi_vals:
            # Set S1 as given in the paper
            if psi<1:
                if psi==0:
                    vart_vals_low = 1 / (1 + kappa * (1 - psi))
                else:
                    vart_vals_low = max(2 - 1 / psi, 1 / (1 + kappa * (1 - psi)))
                vart_vals = np.linspace(vart_vals_low, 1, vart_nbins + 1)
                # Erase 1
                vart_vals = np.delete(vart_vals, vart_nbins)
            elif psi>1:
                vart_high = min(2 - 1 / psi, 1/2*(1+np.sqrt(1+4*kappa*(psi-1))))
                vart_vals = np.linspace(1,vart_high, vart_nbins + 1)
                # Erase 1
                vart_vals = np.delete(vart_vals, 0)

            for vart in vart_vals:
                alpha= (1-vart)/(L*(1-psi))
                m_psi= mu*(psi**2)-L*(1-psi)**2
                rho2=1-np.sqrt(vart*alpha*mu)
                condition= rho2*(1-alpha*m_psi/vart)-(1-alpha*psi*mu)**2
                if condition <=0:
                    stable_vart = np.append(stable_vart, vart)
                    stable_psi = np.append(stable_psi, psi)
                    stable_rate = np.append(rho2,stable_rate)

        sort_ind = np.argsort(stable_psi)
        stable_set = np.concatenate([[stable_vart[sort_ind]], [stable_psi[sort_ind]],[stable_rate[sort_ind]]])
        # stable_set=([[stable_vart],[stable_psi], [stable_rate]]) where psi !=1
        return stable_set

    def str_cnvx_agd_stab_reg_params(self, L, mu, alpha_nbins=100):
        """
        Finds stable region, S_q, of AGD on strongly convex smooth non-quadratic objective
        which is given in [Can and Gurbuzbalaban, 2022. Theorem 2].
        :param L: float
            The smoothness parameter of the objective
        :param mu: float
            The strong convexity constant of the objective.
        :param alpha_nbins: float (default: 100)
            The number of bins to be used to grid search over vartheta.
        :return:
            stable_set: np.array (alpha, rate)
                The stable region found by grid search over alpha.
        """

        kappa = L/mu
        vart, psi=1, 1
        alpha_lin=np.linspace(0,2/(L+mu),alpha_nbins)
        # Erase 0 from alpha
        alpha_lin=np.delete(alpha_lin,0)
        # Collect stable params
        stable_alpha, stable_rate= [], []

        for alpha in alpha_lin:
            # Set S0 as given in the paper
            m_psi= mu*(psi**2)-L*(1-psi)**2
            rho2=1-np.sqrt(vart*alpha*mu)
            condition= rho2*(1-alpha*m_psi/vart)-(1-alpha*psi*mu)**2
            if condition <=0:
                stable_alpha = np.append(stable_alpha, alpha)
                stable_rate = np.append(rho2,stable_rate)

        sort_ind = np.argsort(stable_alpha)
        stable_set = np.concatenate([[stable_alpha[sort_ind]],[stable_rate[sort_ind]]])
        # stable_set=([[stable_alpha],[stable_rate]])
        return stable_set

    def str_convx_MI_check(self, L, mu, vart, psi):
        """
        This function checks the MI inequality for alpha, beta, and gamma defined as
        in [Can and Gurbuzbalaban, 2022. (MI)] with respect to vartheta and psi.
        :param L: float
            The smoothness constant of the objective.
        :param mu: float
            The strong convexity constant of the objective.
        :param vart: float
            The parameter of the algorithm
        :param psi: float
            The parameter of the algorithm.
        :return:
            V: np.array
                The matrix M-X, suggested by the MI which should be negative semi-definite.
            eigs: np.array
                The eigenvalues of the matrix (sorted).
        """
        kappa = L/mu
        alpha = (1 - vart)/(L * (1 - psi))
        p = np.sqrt(vart/(2 * alpha))
        p0 = np.sqrt(mu/2)

        rho2 = 1 - np.sqrt(alpha*vart*mu)
        beta = rho2 / (1 - alpha * psi * mu) * (1 - np.sqrt(alpha * mu / vart))
        gamma = psi*beta

        P = np.array([[p], [-p + p0]])
        P= np.matmul(P,P.T)
        A=np.array([[1 + beta, -beta],[1, 0]])
        B = np.array([[-alpha],[0]])
        C = np.array([[(1 + gamma)],[-gamma]])
        delta = beta - gamma
        M1 = np.matmul(A.T,np.matmul(P,A))-rho2*P
        M2 = np.matmul(B.T,np.matmul(P,A))
        M3 = np.matmul(B.T,np.matmul(P,B))
        M1shape= M1.shape
        M2shape= M2.shape
        M3shape= M3.shape
        M =np.zeros((M1shape[0]+M2shape[0], M1shape[1]+M2shape[0]))
        M[0:M1shape[0],0:M1shape[1]]=M1
        M[0:M1shape[0],M1shape[1]:(M1shape[1]+M2shape[0])]=M2.T
        M[M1shape[0]:(M1shape[0]+M2shape[0]),0:M2shape[1]]=M2
        M[M1shape[0]:(M1shape[0]+M3shape[0]),M1shape[1]:(M1shape[1]+M3shape[1])]=M3

        X1 = 1/2 * np.array([[-L*delta**2,L*delta**2,-(1 - alpha * L) * delta],
                            [L*delta**2, -L*delta**2, (1 - alpha * L) * delta],
                            [-(1-alpha*L)*delta, (1-alpha*L)*delta, alpha*(2-alpha*L)]])

        X2 = 1 / 2 * np.array([[gamma**2 * mu, -gamma**2 * mu, -gamma],
                               [-gamma**2*mu, gamma**2*mu, gamma],
                               [-gamma, gamma, 0]])

        X3 = 1 / 2 * np.array([[(1 + gamma)**2*mu, -gamma*(1+gamma)*mu, -(1+gamma)],
                                [-gamma * (1 + gamma) * mu, gamma**2*mu, gamma],
                                [-(1 + gamma), gamma, 0]])
        X=X1+rho2*X2+(1-rho2)*X3
        V=M-X
        return V, np.linalg.eig(V)[0]

    def str_cnvx_evar_bound(self, L, mu, varth, psi, dimension, conf_lev=0.99, std=1):
        """
        Calculates the evar bound provided for strongly convex smooth non-quadratic objectives
        in [Can and Gurbuzbalaban, 2022. Theorem 3]
        :param L: float
            The smoothness parameter of the objective function.
        :param mu: float
            The strong convexity constant of the objective.
        :param varth: float
            The parameter of the algorithm.
        :param psi: float
            The parameter of the algorithm.
        :param dimension: int
            The dimension of the problem, i.e. the number of features.
        :param conf_lev: float
            The confidence level of EV@R
        :param std: float
            The standard deviation of the additive Gaussian noise on the gradient.
        :return:
            evar_bound: np.array
                The evar bound computed at given parameters.
        """
        d=dimension
        kappa=L/mu
        varphi=0.99
        if psi!=1:
            alpha=(1-varth)/(L*(1-psi))
            beta=1-np.sqrt(varth*alpha*mu)/(1-alpha*psi*mu)*(1-np.sqrt((alpha*mu)/varth))
            gamma=psi*beta

        # Define v_{\vartheta,\psi}
        v_vp=2*(L**2)/mu*(2*(beta-gamma)**2+(1-alpha*L)**2*(1+2*gamma+2*(gamma**2)))\
            +0.5*varth/alpha*(1-np.sqrt(varth*alpha*mu))

        # Define the Theta_u^{g} and \theta_\varphi^{g} as defined in equation (4.7)
        theta_u = np.sqrt(varth * mu)/(alpha*(8*v_vp*np.sqrt(alpha)+alpha*np.sqrt(varth*mu)*(varth+alpha*L)))
        theta_var=varphi*theta_u

        bbrho_1 = 0.5 * (1 - np.sqrt(varth * alpha*mu)+(theta_var*4*alpha**2*v_vp)/(2-theta_var*alpha*(varth+alpha*L)))
        bbrho_2=bbrho_1**2+(16*theta_var*alpha**2*v_vp)/(2-theta_var*alpha*(varth+alpha*L))
        bbrho=0.5*bbrho_1+0.5*np.sqrt(bbrho_2)

        if np.log(1/conf_lev)< 0.5*d/(1-bbrho)*((theta_var*alpha*(varth+alpha*L))/(2-theta_var*(varth+alpha*L)))**2:
            out=0.5*alpha*(varth+alpha*L)*(np.sqrt(d/(1-bbrho))+np.sqrt(2*np.log(1/conf_lev)))**2
        else:
            out=d*alpha*(varth+alpha*L)/((1-bbrho)*(2-theta_var*alpha*(varth+alpha*L)))\
                +2*np.log(1/conf_lev)/theta_var
        return std * out

    def str_cnvx_agd_evar_bound(self, L, mu, alpha, dimension, conf_lev=0.95, std=1):
        """
        Calculates the evar bound provided for AGD on strongly convex smooth non-quadratic objectives
        in [Can and Gurbuzbalaban, 2022. Theorem 3]

        :param L: float
            The smoothness parameter of the objective function.
        :param mu: float
            The strong convexity constant of the objective.
        :param alpha: float
            The parameter of the algorithm.
        :param dimension: int
            The dimension of the problem, i.e. the number of features.
        :param conf_lev: float
            The confidence level of EV@R
        :param std: float
            The standard deviation of the additive Gaussian noise on the gradient.
        :return:
            evar_bound: np.array
                The evar bound computed at given parameters.

        """
        d = dimension
        kappa = L / mu
        psi,varth=1, 1
        if np.log(1 / conf_lev) < d / (2 * mu * alpha * varth):
            out = np.sqrt(varth * alpha) * (np.sqrt(2 * np.log(1 / conf_lev)) + np.sqrt(d)) ** 2
        else:
            out = (1 + np.sqrt(alpha * varth * mu)) * \
                  (d / np.sqrt(alpha * varth * mu) + 2 * np.log(1 / conf_lev))
        return 0.5 * (std**2) * out / np.sqrt(mu)

    # Boundary smoothing to compensate for the grid-search
    def smoothTriangle(self, data, degree):
        """
        This part is to smooth out the region data that has been computed using grid search.
        :param data: np.array
            The region data
        :param degree: int
            The degree of smoothing, i.e. averaging scale.
        :return:
            smoothed: np.array
                The smoothed region.
        """
        triangle = np.concatenate((np.arange(degree + 1), np.arange(degree)[::-1]))  # up then down
        smoothed = [data[0]]
        for i in range(degree + 1, len(data) - degree * 2):
            point = data[i:i + len(triangle)] * triangle
            smoothed.append(np.sum(point) / np.sum(triangle))
        # Handle boundaries
        smoothed = [smoothed[0]] * int(degree + degree / 2) + smoothed
        while len(smoothed) < len(data):
            smoothed.append(smoothed[-1])
        return smoothed

    def str_cnv_stable_region_frontier(self, L, mu,vartbins=250, psibins=250):
        """
        Finds the stable region suggested by Theorem 2.

        :param L: float
             The smoothness parameter of the strongly convex smooth non-quadratic objective.
        :param mu:
            The stong convexity paramter of the objective.
        :param vartbins:
            The number of bins to be used to grid search over vartheta.
        :param psibins:
            The number of binst to be used to grid search over psi.
        :return:
            region: np/array (alpha, min(beta/gamma), max(beta/gamma))
                The frontier of the stable region with respect to alpha and the ratio beta/gamma which are defined as
                 a function of vartheta and psi as given in [Can and Gurbuzbalaban, 2022. Theorem 2].
        """
        region = self.str_cnvx_stab_reg_params(L, mu, vart_nbins=vartbins, psi_nbins=psibins)
        # region = (vartheta; psi)
        psi=region[1]
        alpha = (1 - region[0]) / (L * (1 - region[1]))
        alpha = np.round(np.array(alpha),2)
        rate = 1 - np.sqrt(region[0] * alpha * mu)
        rate_opt = 1 - np.sqrt(mu / L)
        rate /= rate_opt
        border=None
        for a in np.unique(alpha):
            # Retrieve the psi values for same a
            ind_a=np.where(alpha == a)
            psi_a= psi[ind_a]
            if border is None:
                border= np.array([[a, psi_a.min(), psi_a.max()]])
            else:
                border=np.concatenate((border, np.array([[a,psi_a.min(),psi_a.max()]])))
        # Smooth out
        for ind, bounds in enumerate(border):
            if ind<len(border)-2 and ind>0:
                if border[ind][2]<border[ind+1][2]:
                    border[ind][2]=1/4*(border[ind-2][2]+border[ind-1][2]+border[ind+1][2]+border[ind+2][2])
        # Neighborhood averaging
        for ind, bounds in enumerate(border):
            if ind<len(border)-3 and ind>1:
                border[ind][2]=1/4*(border[ind-2][2]+border[ind-1][2]+border[ind+1][2]+border[ind+2][2])
        for ind, bounds in enumerate(border):
            if ind<len(border)-2 and ind>0:
                border[ind][2]=1/3*(border[ind-1][2]+border[ind][2]+border[ind+1][2])

        # Add more alpha into region
        new_border= None
        for ind, bounds in enumerate(border):
            if new_border is None:
                new_border=bounds.reshape(1,len(bounds))
            else:
                new_border=np.concatenate((new_border,bounds.reshape(1,len(bounds))))
            if ind>0 and ind<len(border)-2:
                a=0.5*(border[ind][0]+border[ind+1][0])
                psi_min= 0.5*(border[ind][1]+border[ind+1][1])
                psi_max=0.5*(border[ind][2] + border[ind+1][2])
                new_border=np.concatenate((new_border,np.array([[a,psi_min,psi_max]])))

        # Write over the border parameter
        border=new_border
        end=len(border[:,2])-55
        # Triangular smoothing out
        border[53:end,2]=self.smoothTriangle(border[53:end,2],10)
        # border[:,2]=self.smoothTriangle(border[:,2],5)
        return border

    def rate_vs_evar_bound_frontier(self, L, mu, dimension, std=1, conf_level=0.95, varthbins=250):
        """
        Finds the rate and evar bound for agd at given parameters so that the comparison rate versus evar bound can be done.
        :param L: float
            The smoothness parameter of the objective.
        :param mu: float
            The strong convexity parameter of the objective.
        :param dimension: int
            The dimension of the problem, i.e. the number of features.
        :param std: float
            The stadnard deviation of the additive Gaussian noise on the gradient.
        :param conf_level: float
            The confidence level of the EV@R.
        :param varthbins:
            The number of bins to grid search over vartheta.
        :return:
            frontier: np.array (alpha,rate,evar)
                The list of alpha, rate and evar suggested by given alpha.
        """
        psi=1
        varth= np.linspace(0,1/L,varthbins)
        varth= np.delete(varth,len(varth)-1)
        varth= np.delete(varth,0)
        frontier= None
        for v in varth:
            evar= self.str_cnvx_evar_bound(L, mu, v, psi, dimension, conf_level, std)
            a = (1 - v) / L
            rate= 1-np.sqrt(a*v*mu)
            if frontier is None:
                frontier=np.array([[a,rate,evar]])
            else:
                frontier=np.concatenate((frontier, np.array([[a,rate,evar]])))
        return frontier


import os, builtins

from io import BytesIO
from typing import BinaryIO
from PIL import Image, CurImagePlugin
from PIL._binary import o8
from PIL._binary import o16le as o16
from PIL._binary import o32le as o32

_MAGIC_CUR = b"\0\0\2\0"

def _save_cur(im, fp, filename):
    fp.write(_MAGIC_CUR)  # (2+2)
    width, height = im.size

    hotspot = im.encoderinfo.get(
        "hotspot",
        (0, 0),
    )
    hotspotX = hotspot[0] / width
    hotspotY = hotspot[1] / height

    sizes = im.encoderinfo.get(
        "sizes",
        [(32, 32), (48, 48), (64, 64), (96, 96), (128, 128), (256, 256)],
    )
    frames = []
    for size in sorted(set(sizes)):
        if size[0] > width or size[1] > height or size[0] > 256 or size[1] > 256:
            continue

        if im.size == size:
            frames.append(im)
        else:
            # TODO: invent a more convenient method for proportional scalings
            frame = im.copy()
            frame.thumbnail(size, Image.Resampling.LANCZOS, reducing_gap=None)
            frames.append(frame)
    fp.write(o16(len(frames)))  # idCount(2)
    offset = fp.tell() + len(frames) * 16
    for frame in frames:
        width, height = frame.size
        # 0 means 256
        fp.write(o8(width if width < 256 else 0))  # bWidth(1)
        fp.write(o8(height if height < 256 else 0))  # bHeight(1)

        fp.write(b"\0")  # bColorCount(1)
        fp.write(b"\0")  # bReserved(1)

        fp.write(o16(round(hotspotX * width)))  # hotspotHorizontal(2)
        fp.write(o16(round(hotspotY * height)))  # hotspotVertical(2)

        image_io = BytesIO()
        frame.save(image_io, "png")
        image_io.seek(0)
        image_bytes = image_io.read()
        bytes_len = len(image_bytes)
        fp.write(o32(bytes_len))  # dwBytesInRes(4)
        fp.write(o32(offset))  # dwImageOffset(4)
        current = fp.tell()
        fp.seek(offset)
        fp.write(image_bytes)
        offset = offset + bytes_len
        fp.seek(current)

Image.register_save(CurImagePlugin.CurImageFile.format, _save_cur)

def _save_ani(fp: BinaryIO, frames, seconds_per_frame: float, hotspot):
    # RIFF struct
    fp.write(b"RIFF") # ID(4)
    riff_size_offset = fp.tell()
    fp.write(o32(0)) # headerSize(4)
    fp.write(b"ACON") # headerID(4)

    # 'anih' chunk
    fp.write(b"anih") # chunkHeader(4)
    fp.write(o32(36)) # chunkHeaderSize(4)

    fp.write(o32(36)) # headerSize(4)
    fp.write(o32(len(frames))) # numFrames(4)
    fp.write(o32(len(frames))) # numSteps(4)
    fp.write(o32(0)) # width(4)
    fp.write(o32(0)) # height(4)
    fp.write(o32(0)) # bitCount(4)
    fp.write(o32(0)) # numPlanes(4)
    fp.write(o32(round(seconds_per_frame * 60))) # displayRate(4)
    fp.write(o32(1)) # flags(4)

    # LIST struct
    fp.write(b"LIST") # ID(4)
    list_size_offset = fp.tell()
    fp.write(o32(0)) # headerSize(4)
    fp.write(b"fram") # headerID(4)

    for frame in frames:
        # 'icon' chunk
        image_io = BytesIO()
        frame.save(image_io, "cur", hotspot=hotspot)
        image_io.seek(0)
        image_bytes = image_io.read()

        fp.write(b"icon") # chunkHeader(4)
        fp.write(o32(len(image_bytes))) # chunkHeaderSize(4)
        fp.write(image_bytes)

        # padding
        if fp.tell() & 1:
            fp.write(o8(0))

    current = fp.tell()

    list_size = current - (list_size_offset + 4)
    fp.seek(list_size_offset)
    fp.write(o32(list_size))

    riff_size = current - (riff_size_offset + 4)
    fp.seek(riff_size_offset)
    fp.write(o32(riff_size))

def save_ani(filename, frames, seconds_per_frame: float, hotspot=(0,0)):
    created = not os.path.exists(filename)
    fp = builtins.open(filename, "w+b")
    try:
        _save_ani(fp, frames, seconds_per_frame, hotspot)
    except Exception:
        fp.close()
        if created:
            try:
                os.remove(filename)
            except PermissionError:
                pass
        raise
    fp.close()