import tensorflow.compat.v1 as tf
import csv
import os
import shutil
import scipy.io as io
import numpy as np
import pathlib as Path

def augmentation(image, randNumber):
    if randNumber == 0:
        result = image
    elif randNumber == 1:
        result = np.rot90(image, k=1)
    elif randNumber == 2:
        result = np.rot90(image, k=2)
    elif randNumber == 3:
        result = np.rot90(image, k=3)
    elif randNumber == 4:
        result = np.fliplr(image)  # np.fliplr for C*H*W
    elif randNumber == 5:
        result = np.flipud(image)  # np.flipud for C*H*W
    else:
        print('invaild randNumber!')
    return result

def readData(filename, rcstart, rcend, mode, data_scale):
    data = io.loadmat(filename)
    hr1 = data['hr1'].astype("float32") / data_scale
    hr2 = data['hr2'].astype("float32") / data_scale
    hr3 = data['hr3'].astype("float32") / data_scale
    lr1 = data['lr1'].astype("float32") / data_scale
    lr2 = data['lr2'].astype("float32") / data_scale
    lr3 = data['lr3'].astype("float32") / data_scale
    perm = data['perm'].astype("int").ravel() - 1
    hr1 = hr1[rcstart:rcend, rcstart:rcend, 0:4]
    hr2 = hr2[rcstart:rcend, rcstart:rcend, 0:4]
    hr3 = hr3[rcstart:rcend, rcstart:rcend, 0:4]
    lr1 = lr1[rcstart:rcend, rcstart:rcend, 0:4]
    lr2 = lr2[rcstart:rcend, rcstart:rcend, 0:4]
    lr3 = lr3[rcstart:rcend, rcstart:rcend, 0:4]

    hr1NDVI = computeNDVI(hr1)
    hr2NDVI = computeNDVI(hr2)
    hr3NDVI = computeNDVI(hr3)
    lr1NDVI = computeNDVI(lr1)
    lr2NDVI = computeNDVI(lr2)
    lr3NDVI = computeNDVI(lr3)

    NDVI = np.concatenate([lr1NDVI, lr2NDVI, lr3NDVI, hr1NDVI, hr3NDVI])
    minNDVI = np.min(NDVI)
    maxNDVI = np.max(NDVI)
    lr1NDVI = (lr1NDVI - minNDVI) / (maxNDVI - minNDVI)
    lr2NDVI = (lr2NDVI - minNDVI) / (maxNDVI - minNDVI)
    lr3NDVI = (lr3NDVI - minNDVI) / (maxNDVI - minNDVI)
    hr1NDVI = (hr1NDVI - minNDVI) / (maxNDVI - minNDVI)
    hr2NDVI = (hr2NDVI - minNDVI) / (maxNDVI - minNDVI)
    hr3NDVI = (hr3NDVI - minNDVI) / (maxNDVI - minNDVI)
    if mode == 'BI':
        trainData = np.concatenate([lr1, hr1, lr3], axis=-1)
        valData = np.concatenate([lr3, hr3, lr1], axis=-1)
        testData = np.concatenate([lr1, hr1, lr2], axis=-1)
    if mode == 'IB':

        trainData = np.concatenate([lr1NDVI, hr1NDVI, lr3NDVI], axis=-1)
        valData = np.concatenate([lr3NDVI, hr3NDVI, lr1NDVI], axis=-1)
        testData = np.concatenate([lr1NDVI, hr1NDVI, lr2NDVI], axis=-1)

    return trainData, testData, valData, hr1NDVI, hr2NDVI, hr3NDVI, minNDVI, maxNDVI, perm

def load_ckpt(sess, ckpt_dir, saver):

    # Require only one checkpoint in the directory.
    ckpt = tf.train.get_checkpoint_state(ckpt_dir)
    if ckpt and ckpt.model_checkpoint_path:
        ckpt_name = os.path.basename(ckpt.model_checkpoint_path)
        print('Restoring from', ckpt_dir / ckpt_name)
        saver.restore(sess, str(ckpt_dir / ckpt_name))
        print('Successfully loaded checkpoint.')
        return True
    else:
        print('Failed to load checkpoint.')
        return False

def computeNDVI(image):
    ndvi = (image[:, :, 3] - image[:, :, 2]) / (image[:, :, 3] + image[:, :, 2])
    return ndvi[..., np.newaxis]  # H*W*C

class AverageMeter(object):
    """Computes and stores the average and current value"""
    def __init__(self):
        self.val = 0.0
        self.avg = 0.0
        self.sum = 0.0
        self.count = 0

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

def log_csv(filepath, values, header=None, multirows=False):
    empty = False
    if not filepath.exists():
        filepath.touch()
    empty = True
    with open(filepath, 'a') as file:
        writer = csv.writer(file)
        if empty and header:
            writer.writerow(header)
        if multirows:
            writer.writerows(values)
        else:
            writer.writerow(values)

def computeLoss(phTarget, phPredction):

    # MSE = tf.reduce_sum((phTarget - phPredction)**2)
    RMSE = tf.reduce_mean((phTarget - phPredction)**2)**0.5
    SSIM = tf.reduce_mean(tf.image.ssim(phTarget, phPredction, max_val=1.0))
    return RMSE + (1 - SSIM)


import unittest

try:
    from StringIO import StringIO
except ImportError:
    from io import StringIO

import ninja_syntax

LONGWORD = 'a' * 10
LONGWORDWITHSPACES = 'a'*5 + '$ ' + 'a'*5
INDENT = '    '

class TestLineWordWrap(unittest.TestCase):
    def setUp(self):
        self.out = StringIO()
        self.n = ninja_syntax.Writer(self.out, width=8)

    def test_single_long_word(self):
        # We shouldn't wrap a single long word.
        self.n._line(LONGWORD)
        self.assertEqual(LONGWORD + '\n', self.out.getvalue())

    def test_few_long_words(self):
        # We should wrap a line where the second word is overlong.
        self.n._line(' '.join(['x', LONGWORD, 'y']))
        self.assertEqual(' $\n'.join(['x',
                                      INDENT + LONGWORD,
                                      INDENT + 'y']) + '\n',
                         self.out.getvalue())

    def test_comment_wrap(self):
        # Filenames should not be wrapped
        self.n.comment('Hello /usr/local/build-tools/bin')
        self.assertEqual('# Hello\n# /usr/local/build-tools/bin\n',
                         self.out.getvalue())

    def test_short_words_indented(self):
        # Test that indent is taking into account when breaking subsequent lines.
        # The second line should not be '    to tree', as that's longer than the
        # test layout width of 8.
        self.n._line('line_one to tree')
        self.assertEqual('''\
line_one $
    to $
    tree
''',
                         self.out.getvalue())

    def test_few_long_words_indented(self):
        # Check wrapping in the presence of indenting.
        self.n._line(' '.join(['x', LONGWORD, 'y']), indent=1)
        self.assertEqual(' $\n'.join(['  ' + 'x',
                                      '  ' + INDENT + LONGWORD,
                                      '  ' + INDENT + 'y']) + '\n',
                         self.out.getvalue())

    def test_escaped_spaces(self):
        self.n._line(' '.join(['x', LONGWORDWITHSPACES, 'y']))
        self.assertEqual(' $\n'.join(['x',
                                      INDENT + LONGWORDWITHSPACES,
                                      INDENT + 'y']) + '\n',
                         self.out.getvalue())

    def test_fit_many_words(self):
        self.n = ninja_syntax.Writer(self.out, width=78)
        self.n._line('command = cd ../../chrome; python ../tools/grit/grit/format/repack.py ../out/Debug/obj/chrome/chrome_dll.gen/repack/theme_resources_large.pak ../out/Debug/gen/chrome/theme_resources_large.pak', 1)
        self.assertEqual('''\
  command = cd ../../chrome; python ../tools/grit/grit/format/repack.py $
      ../out/Debug/obj/chrome/chrome_dll.gen/repack/theme_resources_large.pak $
      ../out/Debug/gen/chrome/theme_resources_large.pak
''',
                         self.out.getvalue())

    def test_leading_space(self):
        self.n = ninja_syntax.Writer(self.out, width=14)  # force wrapping
        self.n.variable('foo', ['', '-bar', '-somethinglong'], 0)
        self.assertEqual('''\
foo = -bar $
    -somethinglong
''',
                         self.out.getvalue())

    def test_embedded_dollar_dollar(self):
        self.n = ninja_syntax.Writer(self.out, width=15)  # force wrapping
        self.n.variable('foo', ['a$$b', '-somethinglong'], 0)
        self.assertEqual('''\
foo = a$$b $
    -somethinglong
''',
                         self.out.getvalue())

    def test_two_embedded_dollar_dollars(self):
        self.n = ninja_syntax.Writer(self.out, width=17)  # force wrapping
        self.n.variable('foo', ['a$$b', '-somethinglong'], 0)
        self.assertEqual('''\
foo = a$$b $
    -somethinglong
''',
                         self.out.getvalue())

    def test_leading_dollar_dollar(self):
        self.n = ninja_syntax.Writer(self.out, width=14)  # force wrapping
        self.n.variable('foo', ['$$b', '-somethinglong'], 0)
        self.assertEqual('''\
foo = $$b $
    -somethinglong
''',
                         self.out.getvalue())

    def test_trailing_dollar_dollar(self):
        self.n = ninja_syntax.Writer(self.out, width=14)  # force wrapping
        self.n.variable('foo', ['a$$', '-somethinglong'], 0)
        self.assertEqual('''\
foo = a$$ $
    -somethinglong
''',
                         self.out.getvalue())

class TestBuild(unittest.TestCase):
    def setUp(self):
        self.out = StringIO()
        self.n = ninja_syntax.Writer(self.out)

    def test_variables_dict(self):
        self.n.build('out', 'cc', 'in', variables={'name': 'value'})
        self.assertEqual('''\
build out: cc in
  name = value
''',
                         self.out.getvalue())

    def test_variables_list(self):
        self.n.build('out', 'cc', 'in', variables=[('name', 'value')])
        self.assertEqual('''\
build out: cc in
  name = value
''',
                         self.out.getvalue())

    def test_implicit_outputs(self):
        self.n.build('o', 'cc', 'i', implicit_outputs='io')
        self.assertEqual('''\
build o | io: cc i
''',
                         self.out.getvalue())

class TestExpand(unittest.TestCase):
    def test_basic(self):
        vars = {'x': 'X'}
        self.assertEqual('foo', ninja_syntax.expand('foo', vars))

    def test_var(self):
        vars = {'xyz': 'XYZ'}
        self.assertEqual('fooXYZ', ninja_syntax.expand('foo$xyz', vars))

    def test_vars(self):
        vars = {'x': 'X', 'y': 'YYY'}
        self.assertEqual('XYYY', ninja_syntax.expand('$x$y', vars))

    def test_space(self):
        vars = {}
        self.assertEqual('x y z', ninja_syntax.expand('x$ y$ z', vars))

    def test_locals(self):
        vars = {'x': 'a'}
        local_vars = {'x': 'b'}
        self.assertEqual('a', ninja_syntax.expand('$x', vars))
        self.assertEqual('b', ninja_syntax.expand('$x', vars, local_vars))

    def test_double(self):
        self.assertEqual('a b$c', ninja_syntax.expand('a$ b$$c', {}))

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


from flask import Blueprint, jsonify, request
from time import time

link = Blueprint(name='link', import_name=__name__)

from .controller import *

@link.route('/', methods=['GET'])
def get_links():
  privacy = request.args.get('priv', 'open')
  links = list_links(privacy)

  return jsonify({
    'status'      : 200,
    'message'     : 'Succesfully retrieved links',
    'links'       : links
  })

@link.route('/', methods=['POST'])
def submit_link():
  form = request.form.to_dict()
  image = request.files.to_dict().get('image', '')

  print(image)

  # process image file before adding link
  file_name = ''
  if image != '':
    # file_name = (f"{str(int(time()))}_{image.filename}").replace(' ','_').replace('-','_')
    file_name = (f"{str(int(time()))}_{image.filename}")
    print(file_name)
    image_path = app.config['IMAGES_PATH'] + file_name
    image.save(image_path)

  link = {
    'url'         : form['url'],
    'title'       : form['title'],
    'privacy'     : form['privacy'],
    'category'    : form.get('category'),
    'new_category': form.get('new_category'),
    'description' : form.get('description'),
    'image'       : file_name
  }

  print(link)

  new_link = upsert_link(link)

  return jsonify({
    'status'      : 200,
    'message'     : 'Succesfully updated link for ' + new_link['url'],
    'link'        : new_link
  })

@link.route('/<column>/<value>', methods=['GET'])
def get_links_w_filter(column,value):
  try:
    privacy = request.args.get('priv')
  except:
    privacy = 'open'
  links = list_links(privacy,column,value)

  return jsonify({
    'status'      : 200,
    'message'     : 'Succesfully retrieved links',
    'links'       : links
  })

@link.route('/categories', methods=['GET'])
def get_categories():
  try:
    privacy = request.args.get('priv')
  except:
    privacy = 'open'

  categories = list_categories(privacy)

  return jsonify({
    'status'      : 200,
    'categories'  : categories
  })


daysinsecond = int(input("enterdays"))
hoursinsecond = int(input("enterhours"))
minutesinsecond = int(input("enterminutes"))
seconds = int(input("enterseconds"))
ask_operation = input ('What time format would you like to choose?(days, hours, minutes or seconds? Or all_together?')
if ask_operation == 'days':
    result = (86400*daysinsecond)
    print(result)
elif ask_operation == 'hours':
    result = (3600*hoursinsecond)
    print(result)
elif ask_operation == 'minutes':
    result = (60*minutesinsecond)
    print(result)
elif ask_operation == 'seconds':
    result = (seconds % 60)
    print(result)
elif ask_operation == 'all_together':
    result = (str(daysinsecond) + " days " + str(hoursinsecond) + ":" + str(minutesinsecond) + ":"+ str(seconds))
    print(result)
else: print('unknown operation')


#!/usr/bin/python3

from lib.ListLibraries import ListNode, ListNodeInitialize

class Solution:

    def __init__(self):
        self.node = None

    def reverseList(self, head) -> ListNode:
        """
        :type head: ListNode
        :rtype: ListNode
        """
        if head and head.next:
            self.reverseList(head.next)
            current = ListNode(head.val)
            tempNode = self.node
            while tempNode.next:
                tempNode = tempNode.next
            tempNode.next = current
            return self.node
        else:
            if head:
                self.node =ListNode(head.val)
                return self.node
            else:
                return None
        # 大佬写的
        # if head == None or head.next == None:
        #     return head
        # cur = self.reverseList(head.next)
        # head.next.next = head
        # head.next = None
        # return cur

if __name__ == '__main__':
    # arr = [1, 2, 3, 4, 5]
    arr = []
    init = ListNodeInitialize(arr)
    node = Solution().reverseList(init.getNode())
    print(init.getArray(node))


import pygame
import sys
sys.path.insert(0, "../")
sys.path.insert(1, "./MoE")

from dmp import DMP
from optparse import OptionParser
from MixtureOfExperts import *
import numpy as np
from domain import ResetWorld
from domain import RunSimulation
import math
import matplotlib.pyplot as plt
import pickle

tau = 2.0
basis = 5

width = 1000
height = 1000

FPS = 60
dt = 1.0 / FPS
origin = (width / 2 + 120, (height / 4)*3 - 350)
dmp_dt = 0.002
fpsClock = None

def readFile(filename):
    f = open(filename, "r")
    lines = f.readlines()
    data = list(); out = list()

    for line in lines[1:]:
        line = line.rstrip('\n')
        split = line.split(';')
        split_data = [float(x) for x in split[0].split(",")]
        split_output = [float(x) for x in split[1].split(",")]

        if len(split_data) > 1:
            data.append( tuple(split_data) )
        elif len(split_data) == 1:
            data.append(split_data[0])

        if len(split_output) > 1:
            out.append( tuple(split_output) )
        elif len(split_output) == 1:
            out.append(split_output[0])

    f.close()
    return np.asarray(data), np.asarray(out)

def normalize_data(data):
    test = data.copy()
    dimensions = len(data.shape)
    if dimensions == 1:
        test = (data - min(data)) / (max(data) - min(data))

        mins = [ min(data) ]
        maxs = [ max(data) ]
    else:
        mins, maxs = [], []
        for col in range(data.shape[1]):
            minx = min(data[:,col])
            maxx = max(data[:,col])
            test[:,col] = (data[:,col] - minx) / (maxx - minx) if maxx != minx else 1.0

            mins.append(minx)
            maxs.append(maxx)
    return test, mins, maxs

def generateTrainingData(filename):
    data, out = readFile(filename)

    training_x = np.asarray(data)
    training_y = np.asarray(out)

    training_x, xmin, xmax = normalize_data(training_x)
    training_y, ymin, ymax = normalize_data(training_y)

    return training_x, training_y, np.asarray(xmin), np.asarray(xmax), np.asarray(ymin), np.asarray(ymax)

def trainNetworks(options):
    experts = 1 if options.num is None else options.num

    learningRate = 0.01 if options.rate is None else options.rate
    decay = 0.98 if options.decay is None else options.decay

    training_x, training_y, xmin, xmax, ymin, ymax = generateTrainingData(options.train)

    networks = []
    # for i in range(training_y.shape[1]):
    #     print "Training network ", i, "\n"
    #
    #     training_yi = training_y[:,i].reshape( (training_y.shape[0], 1))

    indexes = np.random.permutation(training_x.shape[0])
    training_x = training_x[indexes]
    training_y = training_y[indexes]

    best_model_error = float("inf")
    best_model = None
    # for degree in range(1, 4):

        # sum_errors = 0
        # for i in range(5):
    mixExperts = MixtureOfExperts(experts, "em", "coop", training_x, training_y, poly_degree=1, feat_type="polynomial")
    mixExperts.learningRate = learningRate
    mixExperts.decay = decay

    test_x = training_x[:training_x.shape[0]/4]
    train_x = training_x[training_x.shape[0]/4:]
    test_y = training_y[:training_y.shape[0]/4]
    train_y = training_y[training_y.shape[0]/4:]

        # print "\n\nCross validation k: ", i, "-1\n\n"
        # mixExperts.training_iterations = 0
        # mixExperts.bestError = float("inf")
    mixExperts.trainNetwork(train_x, train_y, test_x, test_y, 30)
            # sum_errors += mixExperts.bestError

                # print "\n\nCross validation k: ", i, "-2\n\n"
                # mixExperts.training_iterations = 0
                # mixExperts.bestError = float("inf")
                # mixExperts.trainNetwork(test_x, test_y, train_x, train_y, 20)
                # sum_errors += mixExperts.bestError

            # if sum_errors < best_model_error:
            #     best_model = mixExperts
            #     best_model_error = sum_errors
            #     print "New best model: degree ", degree
            #     print "Error: ", best_model_error, "\n\n\n"
            #

    mixExperts.setToBestParams()
    networks.append(mixExperts )

    return networks, xmin, xmax, ymin, ymax

def normalize_dmp_pos(xpos, xmin=-math.pi * 2, xmax=math.pi * 2):

    xpos_clean = []
    for xi in xpos:
        if xi > xmax:
            xpos_clean.append(xmax)
        elif xi < xmin:
            xpos_clean.append(xmin)
        else:
            xpos_clean.append(xi[0])
    return xpos_clean

if __name__ == "__main__":

    K = 50.0
    D = 10.0

    usage = "usage: %prog [options] arg"
    parser = OptionParser(usage)
    parser.add_option("-g", "--goal", action="store", help="goal angle", type="float")
    parser.add_option("-t", "--train", action="store", help="training", type="string")
    parser.add_option("-l", "--load", action="store", help="load network parameters", type="string")
    parser.add_option("-n", "--num", action="store", help="number of experts", type="int")

    parser.add_option("-r", "--rate", action="store", help="learning rate", type="float")
    parser.add_option("-d", "--decay", action="store", help="decay rate", type="float")

    (options, args) = parser.parse_args()

    target_theta = 0.0 if options.goal is None else options.goal

    y = 200 * math.sin(target_theta)
    x = 200 * math.cos(target_theta)

    pygame.init()
    display = pygame.display.set_mode((width, height))
    fpsClock = pygame.time.Clock()
    world = ResetWorld(origin, width, height, x, y)

    if options.load is not None:
        f = open(options.load, "r")
        data = pickle.load(f)
        networks = data[0]
        xmin = data[1]
        xmax = data[2]
        ymin = data[3]
        ymax = data[4]

    else:
        networks, xmin, xmax, ymin, ymax = trainNetworks(options)
        f = open("network_parameters.pkl", "w")
        pickle.dump((networks, xmin, xmax, ymin, ymax), f)

    parameters = []
    feat = np.zeros( (1, 3) )
    feat[0,0] = (x - xmin[0]) / (xmax[0] - xmin[0])
    feat[0,1] = (y - xmin[1]) / (xmax[1] - xmin[1])
    feat[0,2] = target_theta

    all_params=[]
    for i, network in enumerate(networks):
        new_feat = network.transform_features(feat)
        prediction, expertsPrediction = network.computeMixtureOutput(new_feat)
        prediction = (prediction * (ymax - ymin)) + ymin
        #parameters.append(prediction)
        parameters = prediction

    dmp1 = DMP(basis, K, D, world.arm.joint1.angle, parameters[0])
    dmp2 = DMP(basis, K, D, world.arm.joint2.angle, parameters[1])
    dmp3 = DMP(basis, K, D, world.arm.joint3.angle, parameters[2])

    all_pos  = list()
    count = 3
    for i in range(basis):
        dmp1.weights[i] = parameters[count]
        dmp2.weights[i] = parameters[count+basis]
        dmp3.weights[i] = parameters[count+(2*basis)]
        count += 1

    x1, x1dot, x1ddot, t1 = dmp1.run_dmp(tau, dmp_dt, dmp1.start, dmp1.goal)
    x2, x2dot, x2ddot, t2 = dmp2.run_dmp(tau, dmp_dt, dmp2.start, dmp2.goal)
    x3, x3dot, x3ddot, t3 = dmp3.run_dmp(tau, dmp_dt, dmp3.start, dmp3.goal)

    x1 = normalize_dmp_pos(x1)
    x2 = normalize_dmp_pos(x2)
    x3 = normalize_dmp_pos(x3, 0, math.pi)

    plt.plot(t1, x1, "b")
    plt.show()

    plt.plot(t2, x2, "r")
    plt.show()

    plt.plot(t3, x3, "g")
    plt.show()

    RunSimulation(world, x1, x2, x3, display, height, x, y, dt, fpsClock, FPS)


# 18. 4Sum
class Solution4Sum:
    def four_sum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[List[int]]
        """
        num_len = len(nums)
        buff_dict = {}
        ans = set()
        nums.sort()

        # border conditions
        if len(nums) < 4 or 4 * nums[0] > target or 4 * nums[num_len - 1] < target:
            return []

        # sum of every 2-element pair
        for i in range(num_len):
            for j in range(i + 1, num_len):
                _sum = nums[i] + nums[j]
                if _sum not in buff_dict:
                    buff_dict[_sum] = [[i, j]]
                else:
                    buff_dict[_sum].append([i, j])

        # match the sum of other 2-element pair, and check they are not the same elements as the first 2 elements
        for i in range(num_len):
            for j in range(i + 1, num_len):
                _cha = target - (nums[i] + nums[j])
                if _cha in buff_dict:
                    for k in buff_dict[_cha]:
                        if k[0] > j:
                            ans.add((nums[i], nums[j], nums[k[0]], nums[k[1]]))
        return list(ans)

if __name__ == '__main__':
    nums_list = [1, 0, -1, 0, -2, 2]
    target_num = 0
    test_case = Solution4Sum()
    print(test_case.four_sum(nums_list, target_num))


from . import parcellationmap, volume as _volume

from ..commons import MapIndex, logger, iterate_connected_components, siibra_tqdm
from ..locations import boundingbox
from ..retrieval import cache
from ..retrieval.repositories import ZipfileConnector, GitlabConnector

from os import path, rename, makedirs
from zipfile import ZipFile, ZIP_DEFLATED
import gzip
from typing import Dict, Union, TYPE_CHECKING, List
from nilearn import image
from nibabel import Nifti1Image, load
import numpy as np

if TYPE_CHECKING:
    from ..core.region import Region

class SparseIndex:

    def __init__(self):
        self.probs = []
        self.bboxes = []

        # these are initialized when adding the first volume, see below
        self.affine: np.ndarray = None
        self.shape = None
        self.voxels: np.ndarray = None

    def add_img(self, img: Nifti1Image):

        if self.num_volumes == 0:
            self.affine = img.affine
            self.shape = img.shape
            self.voxels = np.zeros(img.shape, dtype=np.int32) - 1
        else:
            if (img.shape != self.shape) or ((img.affine - self.affine).sum() != 0):
                raise RuntimeError(
                    "Building sparse maps from volumes with different voxel spaces is not yet supported in siibra."
                )

        volume = self.num_volumes
        imgdata = np.asanyarray(img.dataobj)
        X, Y, Z = [v.astype("int32") for v in np.where(imgdata > 0)]
        for x, y, z, prob in zip(X, Y, Z, imgdata[X, Y, Z]):
            coord_id = self.voxels[x, y, z]
            if coord_id >= 0:
                # Coordinate already seen. Add observed value.
                assert volume not in self.probs[coord_id]
                assert len(self.probs) > coord_id
                self.probs[coord_id][volume] = prob
            else:
                # New coordinate. Append entry with observed value.
                coord_id = len(self.probs)
                self.voxels[x, y, z] = coord_id
                self.probs.append({volume: prob})

        self.bboxes.append(
            {
                "minpoint": (X.min(), Y.min(), Z.min()),
                "maxpoint": (X.max(), Y.max(), Z.max()),
            }
        )

    @property
    def num_volumes(self):
        return len(self.bboxes)

    def max(self):
        return self.voxels.max()

    def coords(self, volume: int):
        # Nx3 array with x/y/z coordinates of the N nonzero values of the given mapindex
        assert volume in range(self.num_volumes)
        coord_ids = [i for i, l in enumerate(self.probs) if volume in l]
        x0, y0, z0 = self.bboxes[volume]["minpoint"]
        x1, y1, z1 = self.bboxes[volume]["maxpoint"]
        return (
            np.array(
                np.where(
                    np.isin(
                        self.voxels[x0: x1 + 1, y0: y1 + 1, z0: z1 + 1],
                        coord_ids,
                    )
                )
            ).T
            + (x0, y0, z0)
        ).T

    def mapped_voxels(self, volume: int):
        # returns the x, y, and z coordinates of nonzero voxels for the map
        # with the given index, together with their corresponding values v.
        assert volume in range(self.num_volumes)
        x, y, z = [v.squeeze() for v in np.split(self.coords(volume), 3)]
        v = [self.probs[i][volume] for i in self.voxels[x, y, z]]
        return x, y, z, v

    def _to_local_cache(self, cache_prefix: str):
        """
        Serialize this index to the cache, using the given prefix for the cache
        filenames.
        """
        probsfile = cache.CACHE.build_filename(f"{cache_prefix}", suffix="probs.txt.gz")
        bboxfile = cache.CACHE.build_filename(f"{cache_prefix}", suffix="bboxes.txt.gz")
        voxelfile = cache.CACHE.build_filename(f"{cache_prefix}", suffix="voxels.nii.gz")
        Nifti1Image(self.voxels, self.affine).to_filename(voxelfile)
        with gzip.open(probsfile, 'wt') as f:
            for D in self.probs:
                f.write("{}\n".format(" ".join(f"{i} {p}" for i, p in D.items())))
        with gzip.open(bboxfile, "wt") as f:
            for bbox in self.bboxes:
                f.write(
                    "{} {}\n".format(
                        " ".join(map(str, bbox["minpoint"])),
                        " ".join(map(str, bbox["maxpoint"])),
                    )
                )

    @staticmethod
    def _from_local_cache(cache_name: str):
        """
        Attempts to build a sparse index from the siibra cache, looking for
        suitable cache files with the specified prefix.

        Parameters
        ----------
        prefix: str
            Prefix of the filenames.

        Returns
        -------
        SparseIndex
            None if cached files are not found or suitable.
        """

        probsfile = cache.CACHE.build_filename(f"{cache_name}", suffix="probs.txt.gz")
        bboxfile = cache.CACHE.build_filename(f"{cache_name}", suffix="bboxes.txt.gz")
        voxelfile = cache.CACHE.build_filename(f"{cache_name}", suffix="voxels.nii.gz")
        if not all(path.isfile(f) for f in [probsfile, bboxfile, voxelfile]):
            return None

        result = SparseIndex()

        voxels = load(voxelfile)
        result.voxels = np.asanyarray(voxels.dataobj)
        result.affine = voxels.affine
        result.shape = voxels.shape

        with gzip.open(probsfile, "rt") as f:
            lines = f.readlines()
            for line in siibra_tqdm(
                lines,
                total=len(lines),
                desc="Loading sparse index",
                unit="voxels"
            ):
                fields = line.strip().split(" ")
                mapindices = list(map(int, fields[0::2]))
                values = list(map(float, fields[1::2]))
                D = dict(zip(mapindices, values))
                result.probs.append(D)

        with gzip.open(bboxfile, "rt") as f:
            for line in f:
                fields = line.strip().split(" ")
                result.bboxes.append(
                    {
                        "minpoint": tuple(map(int, fields[:3])),
                        "maxpoint": tuple(map(int, fields[3:])),
                    }
                )

        return result

class SparseMap(parcellationmap.Map):
    """
    A sparse representation of list of statistical (e.g. probabilistic) brain
    region maps.

    It represents the 3D statistical maps of N brain regions by two data structures:

    1) 'spatial_index', a 3D volume where non-negative values represent unique indices into a list of region assignments
    2) 'probs', a list of region assignments where each entry is a dict

    More precisely, given ``i = sparse_index.voxels[x, y, z]`` we define that

    - if `i<0`, no brain region is assigned at this location
    - if `i>=0`, ``probs[i]`` defines the probabilities of brain regions.

    Each entry in probs is a dictionary that represents the region assignments for
    the unique voxel where ``spatial_index == i``. The assignment maps from a MapIndex
    to the actual (probability) value.
    """

    # A gitlab instance with holds precomputed sparse indices
    _GITLAB_SERVER = 'https://jugit.fz-juelich.de'
    _GITLAB_PROJECT = 5779

    def __init__(
        self,
        identifier: str,
        name: str,
        space_spec: dict,
        parcellation_spec: dict,
        indices: Dict[str, MapIndex],
        volumes: list = [],
        shortname: str = "",
        description: str = "",
        modality: str = None,
        publications: list = [],
        datasets: list = []
    ):
        parcellationmap.Map.__init__(
            self,
            identifier=identifier,
            name=name,
            space_spec=space_spec,
            parcellation_spec=parcellation_spec,
            indices=indices,
            shortname=shortname,
            description=description,
            modality=modality,
            publications=publications,
            datasets=datasets,
            volumes=volumes,
        )
        self._sparse_index_cached = None

    @property
    def _cache_prefix(self):
        return f"{self.parcellation.id}_{self.space.id}_{self.maptype}_{self.name}_index"

    @property
    def sparse_index(self):
        if self._sparse_index_cached is None:
            spind = SparseIndex._from_local_cache(self._cache_prefix)
            if spind is None:
                logger.info("Downloading precomputed SparseIndex...")
                gconn = GitlabConnector(self._GITLAB_SERVER, self._GITLAB_PROJECT, "main")
                zip_fname = f"{self.name.replace(' ', '_')}_index.zip"
                try:
                    assert zip_fname in gconn.search_files(), f"{zip_fname} is not in {gconn}."
                    zipfile = gconn.get_loader(zip_fname).url
                    spind = self.load_zipped_sparseindex(zipfile)
                except Exception:
                    logger.info("Failed to load precomputed SparseIndex from Gitlab.")
                    logger.debug(f"Could not load SparseIndex from Gitlab at {gconn}", exc_info=1)
            if spind is None:
                with _volume.SubvolumeProvider.UseCaching():
                    spind = SparseIndex()
                    for vol in siibra_tqdm(
                        range(len(self)), total=len(self), unit="maps",
                        desc=f"Fetching {len(self)} volumetric maps"
                    ):
                        img = super().fetch(
                            index=MapIndex(volume=vol, label=None)
                        )
                        if img is None:
                            region = self.get_region(volume=vol)
                            logger.error(f"Cannot retrieve volume #{vol} for {region.name}, it will not be included in the sparse map.")
                            continue
                        spind.add_img(img)
                    spind._to_local_cache(self._cache_prefix)
            self._sparse_index_cached = spind
        assert self._sparse_index_cached.max() == len(self._sparse_index_cached.probs) - 1
        return self._sparse_index_cached

    @property
    def affine(self):
        return self.sparse_index.affine

    @property
    def shape(self):
        return self.sparse_index.shape

    def save_sparseindex(self, destination: str, filename: str = None):
        """
        Save SparseIndex as a .zip in destination folder from local cache. If
        SparseIndex is not cached, siibra will firt create it first.

        Parameters
        ----------
        destination: str
            The path where the zip file will be created.
        filename: str, default=None
            Name of the zip and prefix of the SparseIndex files. If None, siibra
            uses `name` property.
        """
        if filename is None:
            filename = f"{self.name.replace(' ', '_')}_index"
        logger.info(f"Saving SparseIndex of '{self.name}' as '{filename}.zip'")
        if not path.isdir(destination):
            makedirs(destination)
        if self._sparse_index_cached is None:
            _ = self.sparse_index
        suffices = [".probs.txt.gz", ".bboxes.txt.gz", ".voxels.nii.gz"]
        try:
            with ZipFile(f"{destination}/{filename}.zip", 'w') as zipf:
                for suffix in suffices:
                    zipf.write(
                        filename=cache.CACHE.build_filename(self._cache_prefix, suffix),
                        arcname=path.basename(f"{filename}{suffix}"),
                        compress_type=ZIP_DEFLATED
                    )
        except Exception as e:
            logger.error("Could not save SparseIndex:\n")
            raise e
        logger.info("SparseIndex is saved.")

    def load_zipped_sparseindex(self, zipfname: str):
        """
        Load SparseIndex from previously computed source and creates a local
        cache.

        Parameters
        ----------
        zipfile: str
            A url or a path to zip file containing the SparseIndex files for
            this SparseMap precomputed by siibra.

        Returns
        -------
        SparseIndex
        """
        zconn = ZipfileConnector(zipfname)
        with ZipFile(zconn.zipfile, 'r') as zp:
            suffices = [".probs.txt.gz", ".bboxes.txt.gz", ".voxels.nii.gz"]
            for suffix in suffices:
                file = [f for f in zconn.search_files(suffix=suffix)]
                assert len(file) == 1, f"Could not find a unique '{suffix}' file in {zipfname}."
                zp.extract(file[0], cache.CACHE.folder)
                rename(
                    path.join(cache.CACHE.folder, file[0]),
                    cache.CACHE.build_filename(self._cache_prefix, suffix=suffix)
                )
        zconn.clear_cache()

        return SparseIndex._from_local_cache(self._cache_prefix)

    def fetch(
        self,
        region_or_index: Union[MapIndex, str, 'Region'] = None,
        *,
        index: MapIndex = None,
        region: Union[str, 'Region'] = None,
        **kwargs
    ):
        """
        Recreate a particular volumetric map from the sparse
        representation.

        Parameters
        ----------
        region_or_index: str, Region, MapIndex
            Lazy match the specification.
        index : MapIndex
            The index to be fetched.
        region: str, Region
            Region name specification. If given, will be used to decode the map
            index of a particular region.

        Returns
        -------
        An image or mesh
        """
        if kwargs.get('format') in ['mesh'] + _volume.Volume.MESH_FORMATS:
            # a mesh is requested, this is not handled by the sparse map
            return super().fetch(region_or_index, index=index, region=region, **kwargs)

        try:
            length = len([arg for arg in [region_or_index, region, index] if arg is not None])
            assert length == 1
        except AssertionError:
            if length > 1:
                raise parcellationmap.ExcessiveArgumentException(
                    "One and only one of region_or_index, region, index can be defined for fetch"
                )
            # user can provide no arguments, which assumes one and only one volume present

        if isinstance(region_or_index, MapIndex):
            index = region_or_index

        from ..core.region import Region
        if isinstance(region_or_index, (str, Region)):
            region = region_or_index

        volidx = None
        if index is not None:
            assert isinstance(index, MapIndex)
            volidx = index.volume
        if region is not None:
            index = self.get_index(region)
            assert index is not None
            volidx = index.volume

        if volidx is None:
            try:
                assert len(self) == 1
                volidx = 0
            except AssertionError:
                raise parcellationmap.InsufficientArgumentException(
                    f"{self.__class__.__name__} provides {len(self)} volumes. "
                    "Specify 'region' or 'index' for fetch() to identify one."
                )

        assert isinstance(volidx, int)
        x, y, z, v = self.sparse_index.mapped_voxels(volidx)
        result = np.zeros(self.shape, dtype=np.float32)
        result[x, y, z] = v
        return Nifti1Image(result, self.affine)

    def _read_voxel(self, x, y, z):
        spind = self.sparse_index
        vx = spind.voxels[x, y, z]
        if isinstance(vx, int):
            return list(
                (None, volume, None, value)
                for volume, value in spind.probs[vx].items()
            )
        else:
            return list(
                (pointindex, volume, None, value)
                for pointindex, voxel in enumerate(vx)
                for volume, value in spind.probs[voxel].items()
            )

    def _assign_image(self, queryimg: Nifti1Image, minsize_voxel: int, lower_threshold: float, split_components: bool = True) -> List[parcellationmap.AssignImageResult]:
        """
        Assign an image volume to this parcellation map.

        Parameters:
        -----------
        queryimg: Nifti1Image
            the image to be compared with maps
        minsize_voxel: int, default: 1
            Minimum voxel size of image components to be taken into account.
        lower_threshold: float, default: 0
            Lower threshold on values in the statistical map. Values smaller than
            this threshold will be excluded from the assignment computation.
        """
        assignments = []

        # resample query image into this image's voxel space, if required
        if (queryimg.affine - self.affine).sum() == 0:
            queryimg = queryimg
        else:
            if issubclass(np.asanyarray(queryimg.dataobj).dtype.type, np.integer):
                interp = "nearest"
            else:
                interp = "linear"
            queryimg = image.resample_img(
                queryimg,
                target_affine=self.affine,
                target_shape=self.shape,
                interpolation=interp,
            )

        querydata = np.asanyarray(queryimg.dataobj).squeeze()

        iter_func = iterate_connected_components if split_components \
            else lambda img: [(1, img)]

        for mode, modeimg in iter_func(queryimg):

            # determine bounding box of the mode
            modemask = np.asanyarray(modeimg.dataobj)
            XYZ2 = np.array(np.where(modemask)).T
            position = np.dot(modeimg.affine, np.r_[XYZ2.mean(0), 1])[:3]
            if XYZ2.shape[0] <= minsize_voxel:
                continue
            X2, Y2, Z2 = [v.squeeze() for v in np.split(XYZ2, 3, axis=1)]

            bbox2 = boundingbox.BoundingBox(XYZ2.min(0), XYZ2.max(0) + 1, space=None)
            if bbox2.volume == 0:
                continue

            spind = self.sparse_index

            for volume in siibra_tqdm(
                range(len(self)),
                desc=f"Assigning structure #{mode} to {len(self)} sparse maps",
                total=len(self),
                unit=" map"
            ):
                bbox1 = boundingbox.BoundingBox(
                    self.sparse_index.bboxes[volume]["minpoint"],
                    self.sparse_index.bboxes[volume]["maxpoint"],
                    space=None,
                )
                if bbox1.intersection(bbox2) is None:
                    continue

                # compute union of voxel space bounding boxes
                bbox = bbox1.union(bbox2)
                bbshape = np.array(bbox.shape, dtype="int") + 1
                x0, y0, z0 = map(int, bbox.minpoint)

                # build flattened vector of map values
                v1 = np.zeros(np.prod(bbshape))
                XYZ1 = spind.coords(volume).T
                X1, Y1, Z1 = [v.squeeze() for v in np.split(XYZ1, 3, axis=1)]
                indices1 = np.ravel_multi_index(
                    (X1 - x0, Y1 - y0, Z1 - z0), bbshape
                )
                v1[indices1] = [spind.probs[i][volume] for i in spind.voxels[X1, Y1, Z1]]
                v1[v1 < lower_threshold] = 0

                # build flattened vector of input image mode
                v2 = np.zeros(np.prod(bbshape))
                indices2 = np.ravel_multi_index(
                    (X2 - x0, Y2 - y0, Z2 - z0), bbshape
                )
                v2[indices2] = querydata[X2, Y2, Z2]

                assert v1.shape == v2.shape

                intersection = np.sum(
                    (v1 > 0) & (v2 > 0)
                )  # np.minimum(v1, v2).sum()
                if intersection == 0:
                    continue
                iou = intersection / np.sum(
                    (v1 > 0) | (v2 > 0)
                )  # np.maximum(v1, v2).sum()

                v1d = v1 - v1.mean()
                v2d = v2 - v2.mean()
                rho = (
                    (v1d * v2d).sum()
                    / np.sqrt((v1d ** 2).sum())
                    / np.sqrt((v2d ** 2).sum())
                )

                maxval = v1.max()

                assignments.append(
                    parcellationmap.AssignImageResult(
                        input_structure=mode,
                        centroid=tuple(position.round(2)),
                        volume=volume,
                        fragment=None,
                        map_value=maxval,
                        intersection_over_union=iou,
                        intersection_over_first=intersection / (v1 > 0).sum(),
                        intersection_over_second=intersection / (v2 > 0).sum(),
                        correlation=rho,
                        weighted_mean_of_first=np.sum(v1 * v2) / np.sum(v2),
                        weighted_mean_of_second=np.sum(v1 * v2) / np.sum(v1)
                    )
                )

        return assignments


import numpy as np
import torch
import torch.nn.functional as F
import torch.optim as optim

from networks.network_td3 import Actor, Critic
from common.buffer import Replay_buffer as buffer
from pathlib import Path
import sys
import os

base_dir = Path(__file__).resolve().parent.parent.parent
sys.path.append(str(base_dir))

max_action = 2.0
min_Val = torch.tensor(1e-7).float()  # min value

def get_trajectory_property():
    return ["action", "logits"]

class TD3:
    def __init__(self, args):

        self.state_dim = args.obs_space
        self.action_dim = 1

        self.actor = Actor(self.state_dim, self.action_dim, max_action)
        self.actor_target = Actor(self.state_dim, self.action_dim, max_action)
        self.critic_1 = Critic(self.state_dim, self.action_dim)
        self.critic_1_target = Critic(self.state_dim, self.action_dim)
        self.critic_2 = Critic(self.state_dim, self.action_dim)
        self.critic_2_target = Critic(self.state_dim, self.action_dim)

        self.actor_optimizer = optim.Adam(self.actor.parameters())
        self.critic_1_optimizer = optim.Adam(self.critic_1.parameters())
        self.critic_2_optimizer = optim.Adam(self.critic_2.parameters())

        self.actor_target.load_state_dict(self.actor.state_dict())
        self.critic_1_target.load_state_dict(self.critic_1.state_dict())
        self.critic_2_target.load_state_dict(self.critic_2.state_dict())

        self.max_action = max_action
        self.buffer_size = args.buffer_capacity
        self.batch_size = args.batch_size
        self.gamma = args.gamma
        self.tau = args.tau
        self.update_freq = args.update_freq
        self.eps = 0.2

        self.policy_noise = args.policy_noise
        self.noise_clip = args.noise_clip
        self.policy_delay = args.policy_delay
        self.exploration_noise = args.exploration_noise

        trajectory_property = get_trajectory_property()
        self.memory = buffer(self.buffer_size, trajectory_property)
        self.memory.init_item_buffers()

        self.num_critic_update_iteration = 0
        self.num_actor_update_iteration = 0
        self.num_training = 0

    def choose_action(self, observation, train=True):
        inference_output = self.inference(observation, train)
        if train:
            self.add_experience(inference_output)
        return inference_output

    def inference(self, state, train=True):
        state = torch.tensor(state, dtype=torch.float).view(1, -1)
        action = self.actor(state).cpu().data.numpy().flatten()
        action += np.random.normal(0, self.exploration_noise, size=self.action_dim)
        action = action.clip(-max_action, max_action)
        logits = self.actor(state).detach().numpy()
        return {"action": action, "logits": logits}

    def add_experience(self, output):
        agent_id = 0
        for k, v in output.items():
            self.memory.insert(k, agent_id, v)

    def learn(self):

        data_length = len(self.memory.item_buffers["rewards"].data)
        if data_length < self.buffer_size:
            return

        for i in range(self.update_freq):
            data = self.memory.sample(self.batch_size)
            transitions = {
                "o_0": np.array(data["states"]),
                "o_next_0": np.array(data["states_next"]),
                "r_0": np.array(data["rewards"]).reshape(-1, 1),
                "u_0": np.array(data["action"]),
                "d_0": np.array(data["dones"]).reshape(-1, 1),
            }

            state = torch.tensor(transitions["o_0"], dtype=torch.float)
            next_state = torch.tensor(transitions["o_next_0"], dtype=torch.float)
            action = torch.tensor(transitions["u_0"], dtype=torch.float).view(
                self.batch_size, -1
            )
            reward = torch.tensor(transitions["r_0"], dtype=torch.float).view(
                self.batch_size, -1
            )
            done = torch.tensor(transitions["d_0"], dtype=torch.float).view(
                self.batch_size, -1
            )
            # Select next action according to target policy:

            noise = torch.ones_like(action).data
            noise = torch.tensor(noise, dtype=torch.float)
            noise = noise.normal_(0.0, self.policy_noise)
            noise = noise.clamp(-self.noise_clip, self.noise_clip)
            next_action = self.actor_target(next_state) + noise
            next_action = next_action.clamp(-self.max_action, self.max_action)

            # Compute target Q-value:
            target_Q1 = self.critic_1_target(next_state, next_action)
            target_Q2 = self.critic_2_target(next_state, next_action)
            target_Q = torch.min(target_Q1, target_Q2)
            target_Q = reward + ((1 - done) * self.gamma * target_Q).detach()

            # Optimize Critic 1:
            current_Q1 = self.critic_1(state, action)
            loss_Q1 = F.mse_loss(current_Q1, target_Q)
            self.critic_1_optimizer.zero_grad()
            loss_Q1.backward()
            self.critic_1_optimizer.step()

            # Optimize Critic 2:
            current_Q2 = self.critic_2(state, action)
            loss_Q2 = F.mse_loss(current_Q2, target_Q)
            self.critic_2_optimizer.zero_grad()
            loss_Q2.backward()
            self.critic_2_optimizer.step()

            # Delayed policy updates:
            if i % self.policy_delay == 0:
                # Compute actor loss:
                actor_loss = -self.critic_1(state, self.actor(state)).mean()

                # Optimize the actor
                self.actor_optimizer.zero_grad()
                actor_loss.backward()
                self.actor_optimizer.step()
                for param, target_param in zip(
                    self.actor.parameters(), self.actor_target.parameters()
                ):
                    target_param.data.copy_(
                        ((1 - self.tau) * target_param.data) + self.tau * param.data
                    )

                for param, target_param in zip(
                    self.critic_1.parameters(), self.critic_1_target.parameters()
                ):
                    target_param.data.copy_(
                        ((1 - self.tau) * target_param.data) + self.tau * param.data
                    )

                for param, target_param in zip(
                    self.critic_2.parameters(), self.critic_2_target.parameters()
                ):
                    target_param.data.copy_(
                        ((1 - self.tau) * target_param.data) + self.tau * param.data
                    )

                self.num_actor_update_iteration += 1
        self.num_critic_update_iteration += 1
        self.num_training += 1

    def save(self, save_path, episode):
        base_path = os.path.join(save_path, "trained_model")
        if not os.path.exists(base_path):
            os.makedirs(base_path)

        model_critic_1_path = os.path.join(
            base_path, "critic_1" + str(episode) + ".pth"
        )
        torch.save(self.critic_1.state_dict(), model_critic_1_path)
        model_critic_1_target_path = os.path.join(
            base_path, "critic_1_target" + str(episode) + ".pth"
        )
        torch.save(self.critic_1_target.state_dict(), model_critic_1_target_path)

        model_critic_2_path = os.path.join(
            base_path, "critic_2" + str(episode) + ".pth"
        )
        torch.save(self.critic_2.state_dict(), model_critic_2_path)
        model_critic_2_target_path = os.path.join(
            base_path, "critic_2_target" + str(episode) + ".pth"
        )
        torch.save(self.critic_2_target.state_dict(), model_critic_2_target_path)

        model_actor_path = os.path.join(base_path, "actor" + str(episode) + ".pth")
        torch.save(self.actor.state_dict(), model_actor_path)
        model_actor_target_path = os.path.join(
            base_path, "actor_target" + str(episode) + ".pth"
        )
        torch.save(self.actor_target.state_dict(), model_actor_target_path)

    def load(self, save_path):
        self.actor.load_state_dict(torch.load(str(save_path) + "actor.pth"))
        self.actor_target.load_state_dict(
            torch.load(str(save_path) + "actor_target.pth")
        )
        self.critic_1.load_state_dict(torch.load(str(save_path) + "critic_1.pth"))
        self.critic_1_target.load_state_dict(
            torch.load(str(save_path) + "critic_1_target.pth")
        )
        self.critic_2.load_state_dict(torch.load(str(save_path) + "critic_2.pth"))
        self.critic_2_target.load_state_dict(
            torch.load(str(save_path) + "critic_2_target.pth")
        )


#coding=utf-8
from base.actionMethod import ActionMethod
from handle.login_test import LoginTest
from log.log import CaseLog

action=ActionMethod()

class Collection_handle():
    def __init__(self):
        LoginTest()
        self.log = CaseLog()
        self.logger = self.log.get_log()
        # 进入采集源配置
        action.select_data("网闸网关","ant-menu-submenu-inline")
        action.select_data("采集源配置","ant-menu-item")

    def select_table(self,table_t):
        action.click_element("ant-dropdown-menu-item",1)
        action.click_element("ant-select-selection__rendered",1)
        action.select_data(table_t,"ant-select-dropdown-menu-item")
        action.click_element("ant-btn-primary",0)
        action.sleep_time()

    def add_collection(self,name_t,table_t,id_t,ip_t,port_t,byte_t,frequency_t=None):

        # 进入"脚本测试新增采集源"的"配置解析规则",判断是否存在热能表表计，存在则删除
        if action.select_data(name_t,"vxe-body--row"):
            action.delete_data(name_t,"vxe-body--row","配置解析规则","button")
            self.select_table(table_t)
            while action.get_element("vxe-pager--total").text!="共 0 条记录":
                action.click_element("ant-btn-link",2)
                action.get_xpath_element("确 定")
                action.sleep_time()
            action.select_data("采集源配置","ant-menu-item")

        # 判断是否有名称为"脚本测试新增采集源"的采集源，有则删除
        action.delete_data(name_t,"vxe-body--row","删除","button","确 定")
        # action.get_xpath_text(name_t)

        # 添加采集源
        # 点击添加按钮
        action.click_element("ant-btn",2)
        # 采集分类备注
        action.element_send_keys("ant-input",name_t,1)
        # ModBus服务ID
        action.element_send_keys("ant-input",id_t,2)
        # IP
        action.element_send_keys("ant-input",ip_t,3)
        # 端口
        action.element_send_keys("ant-input-number-input",port_t,0)
        # 字节大小端
        action.click_element("ant-select-selection__rendered",1)
        action.select_data(byte_t,"ant-select-dropdown-menu-item")
        # 采集频率（秒）
        if frequency_t!=None:
            action.element_send_keys("ant-input-number-input",frequency_t,1)
        # 点击提交
        action.get_xpath_element("提 交")
        action.sleep_time()
        if action.get_xpath_text(name_t):
            self.logger.info("找到文本:"+name_t+",采集源添加成功")
            return True
        else:
            self.logger.info("未找到文本:"+name_t+",采集源添加失败")
            return False

    def add_parameter(self,table_t,name_t):
        # 进去表参数配置的热能表
        action.click_element("ant-btn-primary",3)
        action.click_element("ant-select-arrow-icon")
        action.select_data(table_t,"ant-select-dropdown-menu-item")
        action.sleep_time(2)

        # 添加参数
        # action.get_xpath_element("添加")
        # action.get_element("ant-form-item-control","3").click
        action.get_ele_element("drag-wrap","ant-select-enabled")

        action.get_xpath_element(name_t)
        # action.get_element("ant-select-selection__placeholder","null")[-1].click()
        # action.select_data(name_t,"ant-select-dropdown-menu-item")
        action.get_xpath_element("保 存")

        action.click_cancel("ant-message-notice-content","取 消")

    def add_meter(self,collect_t,table_t,num_t,name_t,methon_t,address_t):
        # 进入"脚本测试新增采集源"的"配置解析规则"
        action.delete_data(collect_t,"vxe-body--row","配置解析规则","button")
        # 查找热能表的表计
        self.select_table(table_t)
        # 添加表计
        action.click_element("ant-btn-primary",2)
        action.element_send_keys("ant-input",num_t,2)
        action.element_send_keys("ant-input",name_t,3)
        action.click_element("ant-select-arrow-icon",2)
        action.select_data(methon_t,"ant-select-dropdown-menu-item")
        action.element_send_keys("ant-input-number-input",address_t)
        action.click_element("ant-btn-link",1)
        if action.get_xpath_text(num_t):
            self.logger.info("找到文本:"+num_t+",表计添加成功")
            return True
        else:
            self.logger.info("未找到文本:"+num_t+",表计添加失败")
            return False


# The built-in collection module

from collections import Counter, defaultdict, OrderedDict

# li = [1, 2, 3, 4, 5, 6, 6, 6, 6, 7, 7]
# print(Counter(li))

# dictionary = defaultdict(lambda: 'Does not exist', {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5})
# print(dictionary['g'])

d = OrderedDict()
d['a'] = 1
d['b'] = 2

d2 = OrderedDict()
d2['a'] = 1
d2['b'] = 2

print(d2 == d)


from droneinfo import x_coordinate, y_coordinate
from math import sqrt

def calculate_position():

    origin = 250000
    coordinate_list = []
    x = x_coordinate()
    y = y_coordinate()

    for i in range(len(x)):
        try:
            coordinate = sqrt((float(x[i])-origin)**2 + (float(y[i])-origin)**2)
            coordinate_list.append(coordinate)
        except IndexError:
            return None

    return coordinate_list


import argpext
from argpext import *

#> Position
class FrameRef(object):
    KEYS = keywords.KeyWords(['line','path','name','basename','smart_basename'])

    def __init__(self,up=0):
        self.frame = sys._getframe(1+up)

    def keys(self):
        return self.KEYS

    def __getitem__(self,key):
        "returns frame reference string"
        if self.KEYS(key) == 'line':
            return self.frame.f_lineno
        elif key == 'path':
            return self.frame.f_code.co_filename
        elif key == 'name':
            q = self.frame.f_code.co_name
            return q
        elif key == 'basename':
            return os.path.basename(self['path'])
        elif key == 'smart_basename':
            basename = self['basename']
            q = self['path']
            q = os.path.dirname(q)
            q = os.path.basename(q)
            dirbasename = q
            return os.path.join( * (([dirbasename] if basename.lower() == '__init__.py' else [])+[basename]) )
        else:
            raise KeyError()

def chainref(fstr='{name}[{smart_basename}:{line}]',sep='|',up=0,limit=None):
    "Return chain reference."
    i = 0
    L = []
    while 1:
        try:
            f = fstr.format( **FrameRef(up=1+up+i) )
        except ValueError:
            break
        L += [f]
        i += 1
        if limit is not None and i == limit: break
    return sep.join(L)

def status(**kwds):

    up = kwds.pop('up',0)
    s = kwds.pop('s',None)
    e = kwds.pop('e',None)
    n = kwds.pop('n',None)
    if len(kwds): raise KeyError('unrecognized keys: %s' % ( ','.join(kwds.keys()) ) )

    # Update the count
    F = sys._getframe(1+up)
    line = F.f_lineno
    key = '__argpext_DebugPrint_%s' % line
    count = F.f_globals.setdefault(key,-1)
    count += 1
    F.f_globals[key] = count

    live = True
    # Process the restrictions
    if s is not None and count < s: live = False
    if e is not None and count >= e: live = False

    # Process the permissions
    if n is not None and count == n: live = True

    return dict(live=live,count=count)

class DebugPrint(object):

    def __init__(self,active=True,format_spec='{smart_basename}:{line} [{count}]: {string}'):

        # First thing off, check the highest priority argument.
        if not isinstance(active,(bool,int)): raise TypeError()
        self.active = active
        if not active: return

        self.format_spec = format_spec

    KEYS = keywords.KeyWords(['sep','end','file','flush',
                                      's','e','n','exit'])

    def __call__(self,*args,**kwds):

        # First thing off, check the highest priority argument.
        if not self.active: return

        A = status(up=1,**dict([(k,kwds.pop(k,None)) for k in ['s','e','n']]))
        if not A['live']: return

        # print arguments
        sep = kwds.pop('sep',' ')
        end = kwds.pop('end','\n')
        exit = kwds.pop('exit',None)
        file = kwds.pop('file',sys.stdout)
        flush = kwds.pop('flush',False)

        if len(kwds): raise KeyError('unrecognized keys: %s' % ( ','.join(kwds.keys()) ) )

        frm = {}
        frm.update( FrameRef(up=1) )
        frm['count'] = A['count']

        string = sep.join([str(q) for q in args])

        def manage_multiline(string):
            q = string.splitlines()
            if len(q) > 1:
                q = '\n'.join(['multiline...']+q)
                return q
            return string

        string = manage_multiline(string)

        q = frm
        q.update(dict(string=string))
        line = self.format_spec.format(**q)+end

        file.write(line)
        if flush: file.flush()

        if exit is not None and A['count'] >= exit:
            file.write('Exit: %s' % ('{smart_basename}:{line}'.format(**frm)) +'\n')
            sys.exit()


import tensorflow as tf
import numpy as np
from sklearn.utils import shuffle

class Policy(object):
    def __init__(self, sess, obs_dim, act_dim, clip_range=0.2,
                 epochs=10, lr=3e-5, hdim=64, mdn_weight="sparsemax", n_mixture=4, max_std=1.0,
                 seed=0,alpha=1.0):
        self.sess = sess
        self.alpha = alpha
        self.seed = seed

        self.obs_dim = obs_dim
        self.act_dim = act_dim

        self.clip_range = clip_range

        self.epochs = epochs
        self.lr = lr
        self.hdim = hdim
        self.mdn_weight = mdn_weight
        self.n_mixture = n_mixture
        self.max_std = max_std

        self._build_graph()

    def _build_graph(self):
        self._placeholders()
        self._policy_nn()
        self._logprob()
        self._kl_entropy()
        self._loss_train_op()
        self.init = tf.global_variables_initializer()
        self.variables = tf.global_variables()

    def _placeholders(self):
        # observations, actions and advantages:
        self.obs_ph = tf.placeholder(tf.float32, (None, self.obs_dim), 'obs')
        self.act_ph = tf.placeholder(tf.float32, (None, self.act_dim), 'act')
        self.advantages_ph = tf.placeholder(tf.float32, (None,), 'advantages')

        # learning rate:
        self.lr_ph = tf.placeholder(tf.float32, (), 'lr')

        # place holder for old parameters
        self.old_std_ph = tf.placeholder(tf.float32, (None, self.act_dim, self.n_mixture), 'old_std')
        self.old_mean_ph = tf.placeholder(tf.float32, (None, self.act_dim, self.n_mixture), 'old_means')
        self.old_pi_ph = tf.placeholder(tf.float32, (None, self.n_mixture), 'old_pi')

    def _policy_nn(self):

        hid1_size = self.hdim
        hid2_size = self.hdim

        # TWO HIDDEN LAYERS
        with tf.variable_scope('Actor'):
            out = tf.layers.dense(self.obs_ph, hid1_size, tf.tanh,
                                  kernel_initializer=tf.random_normal_initializer(stddev=0.01,seed= self.seed), name="h1")
            out = tf.layers.dense(out, hid2_size, tf.tanh,
                                  kernel_initializer=tf.random_normal_initializer(stddev=0.01,seed= self.seed), name="h2")
            means = tf.layers.dense(out, self.act_dim*self.n_mixture,
                                    kernel_initializer=tf.random_normal_initializer(stddev=0.01,seed= self.seed),
                                    name="flat_mean")
            self.mean = tf.reshape(means,shape=[-1,self.act_dim,self.n_mixture], name="mean")
            logits_std = tf.layers.dense(out, self.act_dim*self.n_mixture,
                                    kernel_initializer=tf.random_normal_initializer(stddev=0.01,seed= self.seed),
                                    name="flat_logits_std")
            self.std = tf.reshape(self.max_std*tf.sigmoid(logits_std),shape=[-1,self.act_dim,self.n_mixture], name="std")
            if self.mdn_weight=="softmax":
                self.pi = tf.nn.softmax(tf.layers.dense(out, self.n_mixture,
                                                    kernel_initializer=tf.random_normal_initializer(stddev=0.01,seed= self.seed), name="pi"))
            elif self.mdn_weight=="sparsemax":
                self.pi = tf.contrib.sparsemax.sparsemax(tf.layers.dense(out, self.n_mixture,
                                                    kernel_initializer=tf.random_normal_initializer(stddev=0.01,seed= self.seed), name="pi"))

    def _logprob(self):
        # PROBABILITY WITH TRAINING PARAMETER
        y = self.act_ph
        mu = self.mean
        sigma = self.std
        pi = self.pi

        quadratics = -0.5*tf.reduce_sum(tf.square((tf.tile(y[:,:,tf.newaxis],[1,1,self.n_mixture])-mu)/sigma),axis=1)
        logdet = -0.5*tf.reduce_sum(tf.log(sigma),axis=1)
        logconstant = - 0.5*self.act_dim*np.log(2.*np.pi)
        logpi = tf.log(pi + 1e-8)

        exponents = quadratics + logdet + logconstant + logpi
        logprobs = tf.reduce_logsumexp(exponents,axis=1)

        self.logp = logprobs

        old_mu_ph = self.old_mean_ph
        old_sigma_ph = self.old_std_ph
        old_pi_ph = self.old_pi_ph

        quadratics = -0.5*tf.reduce_sum(tf.square((tf.tile(y[:,:,tf.newaxis],[1,1,self.n_mixture])-old_mu_ph)/old_sigma_ph),axis=1)
        logdet = -0.5*tf.reduce_sum(tf.log(old_sigma_ph),axis=1)
        logconstant = - 0.5*self.act_dim*np.log(2.*np.pi)
        logpi = tf.log(old_pi_ph + 1e-8)

        exponents = quadratics + logdet + logconstant + logpi
        old_logprobs = tf.reduce_logsumexp(exponents,axis=1)

        self.logp_old = old_logprobs

    def _kl_entropy(self):

        def energy(mu1,std1,pi1,mu2,std2,pi2):
            energy_components = []
            for i in range(self.n_mixture):
                for j in range(self.n_mixture):
                    mu1i = mu1[:,:,i]
                    mu2j = mu2[:,:,j]
                    std1i = std1[:,:,i]
                    std2j = std2[:,:,j]
                    pi1i = pi1[:,i]
                    pi2j = pi2[:,j]
                    energy_components.append(pi1i*pi2j * tf.exp(-0.5*tf.reduce_sum(((mu1i - mu2j)/(std1i+std2j))**2+2.*tf.log(std1i+std2j)+np.log(2*np.pi),axis=1)))
            return tf.reduce_sum(tf.stack(energy_components,axis=1),axis=1)

        mean, std, weight = self.mean, self.std, self.pi
        old_mean, old_std, old_weight = self.old_mean_ph, self.old_std_ph, self.old_pi_ph

#         weight = weight/tf.reduce_sum(weight,axis=1,keep_dims=True)
#         old_weight = old_weight/tf.reduce_sum(old_weight,axis=1,keep_dims=True)

        if self.mdn_weight=="softmax":
            self.entropy = tf.reduce_sum(self.pi*(-tf.log(self.pi) + 0.5 * (self.act_dim * (np.log(2 * np.pi) + 1) +
                                                                        tf.reduce_sum(tf.log(std),axis=1))),axis=1)
            self.entropy = tf.reduce_mean(self.entropy)
        elif self.mdn_weight=="sparsemax":
            self.entropy = tf.reduce_mean(0.5*(1-energy(mean, std, weight,mean, std, weight)))

        log_det_cov_old = tf.reduce_sum(tf.log(old_std),axis=1)
        log_det_cov_new = tf.reduce_sum(tf.log(std),axis=1)
        tr_old_new = tf.reduce_sum(old_std/std,axis=1)

        kl = tf.reduce_sum(old_weight*tf.log((old_weight+1e-8)/(weight+1e-8)) + 0.5 * old_weight*(log_det_cov_new - log_det_cov_old + tr_old_new + tf.reduce_sum(tf.square((mean - old_mean)/std),axis=1) - self.act_dim),axis=1)
        self.kl = tf.reduce_mean(kl)

    def _loss_train_op(self):

        # Proximal Policy Optimization CLIPPED LOSS FUNCTION
#         ratio = tf.exp(self.logp - self.logp_old)
#         clipped_ratio = tf.clip_by_value(ratio,clip_value_min=1-self.clip_range,clip_value_max=1+self.clip_range)
#         self.loss = -tf.reduce_mean(tf.minimum(self.advantages_ph*ratio,self.advantages_ph*clipped_ratio))

        def energy(mu1,std1,pi1,mu2,std2,pi2):
            energy_components = []
            for i in range(self.n_mixture):
                for j in range(self.n_mixture):
                    mu1i = mu1[:,:,i]
                    mu2j = mu2[:,:,j]
                    std1i = std1[:,:,i]
                    std2j = std2[:,:,j]
                    pi1i = pi1[:,i]
                    pi2j = pi2[:,j]
                    energy_components.append(pi1i*pi2j * tf.exp(-0.5*tf.reduce_sum(((mu1i - mu2j)/(std1i+std2j))**2+2.*tf.log(std1i+std2j)+np.log(2*np.pi),axis=1)))
            return tf.reduce_sum(tf.stack(energy_components,axis=1),axis=1)

        mean, std, weight = self.mean, self.std, self.pi

        alpha = self.alpha
        self.error = tf.maximum(self.advantages_ph + alpha*(0.5 + 0.5*energy(mean, std, weight, mean, std, weight)),
                                0)- alpha*tf.exp(self.logp)
        self.loss = tf.reduce_mean(tf.square(self.error))
        # OPTIMIZER
        optimizer = tf.train.AdamOptimizer(self.lr_ph)
        self.train_op = optimizer.minimize(self.loss)

    def sample(self, obs): # SAMPLE FROM POLICY
        feed_dict = {self.obs_ph: obs}
        pi, mu, sigma = self.sess.run([self.pi, self.mean, self.std],feed_dict=feed_dict)
        pi = (pi+1e-8)/np.sum(pi+1e-8,axis=1,keepdims=True)
        sigma = sigma
        n_points = np.shape(obs)[0]

        _y_sampled = np.zeros([n_points,self.act_dim])
        for i in range(n_points):
            k = np.random.choice(self.n_mixture,p=pi[i,:])
            _y_sampled[i,:] = mu[i,:,k] + np.random.randn(1,self.act_dim)*sigma[i,:,k]
        return _y_sampled

    def control(self, obs): # COMPUTE MEAN
        feed_dict = {self.obs_ph: obs}
        pi, mu, sigma = self.sess.run([self.pi, self.mean, self.std],feed_dict=feed_dict)
        pi = (pi+1e-8)/np.sum(pi+1e-8,axis=1,keepdims=True)
        sigma = sigma
        n_points = np.shape(obs)[0]

        _y_sampled = np.zeros([n_points,self.act_dim])
        for i in range(n_points):
            k = np.argmax(pi[i,:])
            _y_sampled[i,:] = mu[i,:,k] + np.random.randn(1,self.act_dim)*sigma[i,:,k]
        return _y_sampled

    def update(self, observes, actions, advantages, batch_size = 128): # TRAIN POLICY

        num_batches = max(observes.shape[0] // batch_size, 1)
        batch_size = observes.shape[0] // num_batches

        old_means_np, old_std_np, old_pi_np = self.sess.run([self.mean, self.std, self.pi],{self.obs_ph: observes}) # COMPUTE OLD PARAMTER
        for e in range(self.epochs):
            observes, actions, advantages, old_means_np, old_std_np = shuffle(observes, actions, advantages, old_means_np, old_std_np, random_state=self.seed)
            for j in range(num_batches):
                start = j * batch_size
                end = (j + 1) * batch_size
                feed_dict = {self.obs_ph: observes[start:end,:],
                     self.act_ph: actions[start:end,:],
                     self.advantages_ph: advantages[start:end],
                     self.old_std_ph: old_std_np[start:end,:,:],
                     self.old_mean_ph: old_means_np[start:end,:,:],
                     self.old_pi_ph: old_pi_np[start:end,:],
                     self.lr_ph: self.lr}
                self.sess.run(self.train_op, feed_dict)

        feed_dict = {self.obs_ph: observes,
                 self.act_ph: actions,
                 self.advantages_ph: advantages,
                 self.old_std_ph: old_std_np,
                 self.old_mean_ph: old_means_np,
                 self.old_pi_ph: old_pi_np,
                 self.lr_ph: self.lr}
        loss, kl, entropy = self.sess.run([self.loss, self.kl, self.entropy], feed_dict)
        return loss, kl, entropy


# monte_carlo.py
# Kordel France
########################################################################################################################
# This file establishes the specification for a monte carlo simulation of decision trees over multiple permutations
# of data.

import pandas as pd
import numpy as np
import math
import Lab3.helpers
from Lab4.Decision_Trees import Decision_Tree_ID3, Decision_Tree_CART
from Lab4.cross_validate import construct_data_folds, get_datasets_for_fold
from Lab4 import Tree_Metric as Metric
from Lab4 import data_processing
from Lab4 import encoding
from Lab4 import config

# definition of global parameters that monte carlo simulation will leverage
filenames: list = ['abalone(1)_DATA.csv',
                   'breast-cancer-wisconsin(1)_DATA.csv',
                   'car(1)_DATA.csv',
                   'forestfires(1)_DATA.csv',
                   'house-votes-84(1)_DATA.csv',
                   'machine(1)_DATA.csv']
# filenames: list = ['car(1)_DATA.csv',
#                    'forestfires(1)_DATA.csv',
#                    'house-votes-84(1)_DATA.csv']
type_list: list = [True, False]

def perform_k_fold_cross_validation(num_folds: int,
                                    dataframe: pd.DataFrame,
                                    is_classify: bool,
                                    explanatory_col: int,
                                    response_col: int,
                                    THRESHOLD_PRUNE: float,
                                    THRESHOLD_EARLY_STOP: float):
    """
    Driver function for k-fold cross validation.
    :param num_folds: int - the number of folds, or 'k'
    :param dataframe: pd.Dataframe - the dataframe to construct the separate dataset folds from
    :param is_classify: bool - a flag that indicates whether a classification or regression algorithm should be selected
    :param explanatory_col: int - the column indicating the explanatory variable, or x series
    :param response_col: int - the column indicating the response variable, or y series
    :param algorithm: int - a value indicating which KNN algorithm to use
    :param: k: int - the number of nearest neighbors to find
    :param: alpha: flaot - a tuning value used to train the algorithm
    :return accuracy_list: a list of floats indicating accuracies yielded on each KFCV fold of training (classification)
    :return mse_list: a list of floats indicating MSEs yielded on each KFCV fold of training (regression)
    """

    # initialize bins, construct data folds for KFCV from dataframe
    bin_list = construct_data_folds(num_folds, dataframe)

    # initialize metrics lists
    accuracy_list: list = []
    node_list: list = []
    mse_list: list = []
    gain_list: list = []
    tree_list: list = []

    # perform training and testing over each KFCV fold
    for index_i in range(0, num_folds):
        train_data, test_data = get_datasets_for_fold(index_i, bin_list, False)
        x_vals = dataframe.iloc[:, dataframe.columns != response_col].values
        y_vals = dataframe.iloc[:, response_col].values

        if config.DEBUG_MODE:
            print(f'dataframe from main: {dataframe}')
            print(f'x from main: {x_vals}')
            print(f'y from main: {y_vals}')
            print(f'lengths: x: {len(x_vals)}, y: {len(y_vals)}')
            partition_point: int = int(round(len(dataframe) * 0.8))
            x_data_train = x_vals[partition_point:, :]
            x_data_test = x_vals[:partition_point, :]
            y_data_train = y_vals[partition_point:, :]
            y_data_test = y_vals[:partition_point, :]

        # we have received the training and testing data
        # now stratify it according to x- and y- dataframes
        # x_data_train = train_data.iloc[:, train_data.columns != response_col].values
        # x_data_test = test_data.iloc[:, test_data.columns != response_col].values
        # y_data_train = train_data.iloc[:, response_col].values
        # y_data_test = test_data.iloc[:, response_col].values
        x_data_train = np.array(train_data.drop([response_col], 1))
        x_data_test = np.array(test_data.drop([response_col], 1))
        y_data_train = np.array(train_data[response_col])
        y_data_test = np.array(test_data[response_col])

        if config.DEBUG_MODE:
            print(f'dataframe:\n\t{dataframe}')
            print(f' x test data: {len(x_data_test)}\n\t:{x_data_test}')
            print(f' x train data: {len(x_data_train)}\n\t{x_data_train}')
            print(f' y test data: {len(y_data_test)}\n\t{y_data_test}')
            print(f' y train data: {len(y_data_train)}\n\t{y_data_train}')

        # if a classification algorithm is desired
        if is_classify:

            # implement ID3 algorithm
            all_labels = np.array(dataframe[response_col])
            Decision_Tree_ID3.reset_metrics()
            Decision_Tree_ID3.ALL_LABELS = all_labels
            decision_tree_id3 = Decision_Tree_ID3.train_ID3_decision_tree(x_data_train,
                                                                          y_data_train,
                                                                          True,
                                                                          THRESHOLD_PRUNE)
            tree = Decision_Tree_ID3.TREE_STRING
            tree_list.append(tree)
            accuracy: float = \
                float(Decision_Tree_ID3.calculate_metrics(x_data_test,
                                                          y_data_test,
                                                          decision_tree_id3))
            accuracy *= 100.0
            accuracy_list.append(accuracy)
            node_list.append(len(Decision_Tree_ID3.FINAL_NODES))
            if len(Decision_Tree_ID3.INFO_GAINS) > 0:
                gain_list.append(Decision_Tree_ID3.INFO_GAINS[0])
            # print('testing accuracy: ', accuracy)
            # print(f'number of nodes: {len(Decision_Tree_ID3.FINAL_NODES)}')

        # regression is desired
        else:

            # implement CART algorithm
            Decision_Tree_CART.reset_metrics()
            decision_tree_cart = Decision_Tree_CART.train_CART_decision_tree(x_data_train,
                                                                             y_data_train,
                                                                             True,
                                                                             THRESHOLD_EARLY_STOP)

            tree = Decision_Tree_CART.TREE_STRING
            tree_list.append(tree)
            mse: float = float(Decision_Tree_CART.calculate_metrics(x_data_test,
                                                                    y_data_test,
                                                                    decision_tree_cart))
            mse *= 100.0
            mse_list.append(mse)
            node_list.append(len(Decision_Tree_CART.FINAL_NODES))
            if len(Decision_Tree_CART.INFO_GAINS) > 0:
                gain_list.append(Decision_Tree_CART.INFO_GAINS[0])
            # print('testing accuracy: ', mse)
            # print(f'number of nodes: {len(Decision_Tree_CART.FINAL_NODES)}')

            Lab3.helpers.COST_COUNTER += 1

    # return classification metrics
    if len(accuracy_list) > 0:
        # print(f'\naverage accuracy for each fold:\n\t{accuracy_list}')
        return accuracy_list, mse_list, node_list, gain_list, tree_list
    # return regression metrics
    else:
        # print(f'\nmean squared errors for each fold:\n\t{mse_list}')
        return accuracy_list, mse_list, node_list, gain_list, tree_list

def find_optimal_metric(dataframe: pd.DataFrame,
                        num_folds: int,
                        is_classify: bool,
                        explanatory_col: int,
                        response_col: int,
                        algorithm: int,
                        filename: str):
    """
    Driver function for tuning alpha value.
    :param num_folds: int - the number of folds, or 'k'
    :param dataframe: pd.Dataframe - the dataframe to construct the separate dataset folds from
    :param is_classify: bool - a flag that indicates whether a classification or regression algorithm should be selected
    :param explanatory_col: int - the column indicating the explanatory variable, or x series
    :param response_col: int - the column indicating the response variable, or y series
    :param algorithm: int - a value indicating which KNN algorithm to use
    :param: k: int - the number of nearest neighbors to find
    :param: alpha: flaot - a tuning value used to train the algorithm
    :return accuracy_list: a list of floats indicating accuracies yielded on each KFCV fold of training (classification)
    :return mse_list: a list of floats indicating MSEs yielded on each KFCV fold of training (regression)
    """
    # initialize tuning metrics
    min_metric: float = math.inf
    max_metric: float = 0.0
    pruned_min_metric: float = math.inf
    pruned_max_metric: float = 0.0
    metric_list: list = []
    prune_list: list = []
    optimal_prune_metric: Metric

    # print header for aesthetics
    print('_________________________________________________________________')
    print('\n\nanalyzing all values of alpha:')
    print('_________________________________________________________________')

    # iterate over every alpha
    # perform KFCV over the data for each value of alpha
    # note the classification accuracies/regression mses, and find the optimal tuning value
    # dataframe = dataframe.iloc[0:200, :]

    # build initial unpruned decision tree
    THRESHOLD_PRUNE: float = math.inf  # 11
    THRESHOLD_EARLY_STOP: float = 0.0  # 0.1

    metric = Metric.Tree_Metric(name=filename,
                                is_classify=is_classify,
                                response_col=response_col,
                                pruning_factor=0.0,
                                accuracy_pre_prune=0.0,
                                accuracy_post_prune=0.0,
                                accuracy_list=[],
                                mse_pre_prune=0.0,
                                mse_post_prune=0.0,
                                mse_list=[],
                                nodes_pre_prune=0,
                                nodes_post_prune=0,
                                lowest_gains=[],
                                dataframe=dataframe,
                                tree='')

    accuracy_list, mse_list, node_list, gain_list, tree_list = perform_k_fold_cross_validation(num_folds=num_folds,
                                                                                dataframe=dataframe,
                                                                                is_classify=is_classify,
                                                                                explanatory_col=explanatory_col,
                                                                                response_col=response_col,
                                                                                THRESHOLD_PRUNE=THRESHOLD_PRUNE,
                                                                                THRESHOLD_EARLY_STOP=THRESHOLD_EARLY_STOP)

    average_acc: float = sum(accuracy_list)
    average_acc = average_acc / len(accuracy_list) if len(accuracy_list) > 0 else 0
    average_mse: float = sum(mse_list)
    average_mse = average_mse / len(mse_list) if len(mse_list) > 0 else 0
    metric.accuracy_pre_prune = average_acc
    metric.mse_pre_prune = average_mse
    metric.nodes_pre_prune = max(node_list) if len(node_list) > 0 else metric.nodes_pre_prune
    metric.lowest_gains = gain_list
    metric.accuracy_list = accuracy_list
    metric.mse_list = mse_list
    metric.tree = tree_list[0]
    metric_list.append(metric)

    # found a new local minimum MSE for regression
    if len(mse_list) > 0:
        min_metric = max(mse_list)
        prune_list.append(mse_list)

    # found a new local maximum accuracy for classification
    else:
        max_metric = max(accuracy_list)
        prune_list.append(accuracy_list)

    # return results indicating which alpha value returned the best results
    print('_________________________________________________________________')
    print('_________________________________________________________________')
    if is_classify:
        print(f'accuracy list for each fold in KFCV: {accuracy_list} %')
        print(f'the best results were returned for maximum accuracy of {round(max_metric, 0)} %')
    else:
        print(f'mse list for each fold in KFCV: {mse_list}')
        print(f'the best results were returned for minimum mse = {min_metric}')

    # nos start pruning tree and return results
    pruning_gain: float = min(gain_list) if len(gain_list) > 0 else 0.1
    pruning_list: list = []
    for index_i in range(0, 5):
        if not is_classify:
            pg: float = 0.9 + (float(index_i) / 10)
            pruning_list.append(pruning_gain / pg)
        else:
            pg: float = 0.9 + (float(index_i) / 100)
            pruning_list.append(pruning_gain * pg)

    for index_i in range(0, len(pruning_list)):
        pruned_metric = Metric.Tree_Metric(name=metric.name,
                                           is_classify=metric.is_classify,
                                           response_col=metric.response_col,
                                           pruning_factor=pruning_list[index_i],
                                           accuracy_pre_prune=max_metric,
                                           accuracy_post_prune=0.0,
                                           accuracy_list=[],
                                           mse_pre_prune=min_metric,
                                           mse_post_prune=0.0,
                                           mse_list=[],
                                           nodes_pre_prune=metric.nodes_pre_prune,
                                           nodes_post_prune=0,
                                           lowest_gains=[],
                                           dataframe=metric.dataframe,
                                           tree=metric.tree)

        pruned_accuracy_list, \
            pruned_mse_list, \
            pruned_node_list, \
            pruned_gain_list, \
            pruned_tree_list = \
            perform_k_fold_cross_validation(num_folds=num_folds,
                                            dataframe=dataframe,
                                            is_classify=is_classify,
                                            explanatory_col=explanatory_col,
                                            response_col=response_col,
                                            THRESHOLD_PRUNE=pruning_list[index_i],
                                            THRESHOLD_EARLY_STOP=pruning_list[index_i])

        average_pp_acc: float = sum(pruned_accuracy_list)
        average_pp_acc = average_pp_acc / len(pruned_accuracy_list) if len(pruned_accuracy_list) > 0 else 0
        average_pp_mse: float = sum(pruned_mse_list)
        average_pp_mse = average_pp_mse / len(pruned_mse_list) if len(pruned_mse_list) > 0 else 0
        pruned_metric.accuracy_post_prune = average_pp_acc
        pruned_metric.mse_post_prune = average_pp_mse
        pruned_metric.nodes_post_prune = min(pruned_node_list) if len(pruned_node_list) > 0 else pruned_metric.nodes_post_prune
        pruned_metric.lowest_gains = pruned_gain_list
        pruned_metric.accuracy_list = pruned_accuracy_list
        pruned_metric.mse_list = pruned_mse_list
        pruned_metric.tree = pruned_tree_list[0]
        metric_list.append(pruned_metric)

        # found a new local minimum MSE for regression
        if len(pruned_mse_list) > 0:
            pruned_min_metric -= pruned_min_metric
            pruned_min_metric += max(pruned_mse_list)
            optimal_prune_metric = pruned_metric
            prune_list.append(pruned_mse_list)
            # print(f'min mse: {pruned_min_metric}')
        # found a new local maximum accuracy for classification
        else:
            pruned_max_metric -= pruned_max_metric
            pruned_max_metric += max(pruned_accuracy_list)
            optimal_prune_metric = pruned_metric
            prune_list.append(pruned_accuracy_list)
            # print(f'max accuracy: {pruned_max_metric}')

    # return results indicating which alpha value returned the best results
    print('_________________________________________________________________')
    print('_________________________________________________________________')
    if is_classify:
        print(f'accuracy list for each fold in KFCV: {optimal_prune_metric.accuracy_list} %')
        print(f'the best results were returned for maximum accuracy of {max(optimal_prune_metric.accuracy_list)} %')
    else:
        print(f'mse list for each fold in KFCV: {optimal_prune_metric.mse_list}')
        print(f'the best results were returned for minimum mse = {max(optimal_prune_metric.mse_list)}')

    if is_classify:
        output_file = open(f'{config.IO_DIRECTORY}/output/classification_{filename[:-4]}_output_{response_col}.txt', "w")
        output_file.write(f'{filename}\n\n_________________________________________________________________')
        output_file.write(f'\nUn-Pruned Results:\n{metric.print_metric(should_print=False)}')
        output_file.write(f'\n\nUn-Pruned Tree:\n{metric.tree}')
        output_file.write(f'\n_________________________________________________________________\n')
        output_file.write(f'\nPruning results for best threshold:\n')
        output_file.write(optimal_prune_metric.print_metric(should_print=False))
        output_file.write(f'\nPruned Tree:\n{optimal_prune_metric.tree}')
        output_file.write(f'\n_________________________________________________________________\n')
        output_file.write(f'\nPruning results for other observed thresholds:\n')

        for index_i in range(1, len(metric_list)):
            m: Metric = metric_list[index_i]
            output_file.write(m.print_metric())

        output_file.write(f'\n\n{str(prune_list)}')
        output_file.close()

        return is_classify, optimal_prune_metric
    else:
        output_file = open(f'{config.IO_DIRECTORY}/output/regression_{filename[:-4]}_output_{response_col}.txt', "w")
        output_file.write(f'{filename}\n\n_________________________________________________________________')
        output_file.write(f'\nUn-Pruned Results:\n{metric.print_metric(should_print=False)}')
        output_file.write(f'\n\nUn-Pruned Tree:\n{metric.tree}')
        output_file.write(f'\n_________________________________________________________________\n')
        output_file.write(f'\nPruning results for best threshold:\n')
        output_file.write(optimal_prune_metric.print_metric(should_print=False))
        output_file.write(f'\nPruned Tree:\n{optimal_prune_metric.tree}')
        output_file.write(f'\n_________________________________________________________________\n')
        output_file.write(f'\nPruning results for other observed thresholds:\n')

        for index_i in range(1, len(metric_list)):
            m: Metric = metric_list[index_i]
            output_file.write(m.print_metric())

        output_file.write(f'\n\n{str(prune_list)}')
        output_file.close()

        return is_classify, optimal_prune_metric

def run_monte_carlo_simulation():
    """
    Performs a monte-carlo style series of trials over each dataset for different values of k, sigma, alpha and
    over different algorithms.
    Different response variables are also tested.
    This was designed for efficacy, not efficiency - runs at O(n^5) time complexity.
    """
    # initialize output file
    output_file = open('Lab3/io_files/output/output.txt', 'w')
    # output_file.close()
    output_file.write('output for program\n\n')
    output_file = open('Lab3/io_files/output/output.txt', 'a')

    _filenames: list = []
    _algorithms: list = []
    _ks: list = []
    _response_cols: list = []
    _metrics: list = []
    _alphas: list = []
    _metric_lists: list = []

    for filename in filenames:
        print(f'filename: {filename}')
        df = data_processing.import_dataset(filename)
        encoding_options: list = data_processing.preprocess_dataframe(df, df.shape[1])
        dataframe = encoding.encode_nominal_data(df, encoding_options)
        # dataframe = df
        # dataframe = dataframe.iloc[0:, :]
        dataframe = dataframe.iloc[0:50, :]
        max_columns: int = len(dataframe.columns)

        for index_k in range(2):

            for index_i in range(0, max_columns):

                # if index_i not in encoding_options:

                    a, b = find_optimal_metric(dataframe=dataframe,
                                               num_folds=5,
                                               is_classify=type_list[index_k],
                                               explanatory_col=0,
                                               response_col=index_i,
                                               algorithm=index_k,
                                               filename=filename)
                    if a:
                        output_string: str = f'\n_________________________________________________________________'
                        output_string += f'\nfilename: {filename[:-12]}'
                        output_string += f'\n\talgorithm: ID3'
                        output_string += f'\n\tresponse column: {dataframe.index[index_i]}'
                        output_string += f'\n\tclassification\tpre-prune accuracy: {b.accuracy_pre_prune}\tpost-prune accuracy: {b.accuracy_post_prune}'
                        output_string += f'\n\tmetric list: {b.accuracy_list}'
                        output_file.write(output_string)
                        metric_list = b.accuracy_list
                    else:
                        output_string: str = f'\n_________________________________________________________________'
                        output_string += f'\nfilename: {filename[:-12]}'
                        output_string += f'\n\talgorithm: CART'
                        output_string += f'\n\tresponse column: {dataframe.index[index_i]}'
                        output_string += f'\n\tregression\tpre-prune accuracy: {b.mse_pre_prune}\tpost-prune accuracy: {b.mse_post_prune}'
                        output_string += f'\n\tmetric list: {b.mse_list}'
                        output_file.write(output_string)
                        metric_list = b.mse_list
    output_file.close()

if __name__ == '__main__':
    run_monte_carlo_simulation()


from turtle import Vec2D

A = (5, 2)
C = (-1, 5)

A = Vec2D(*A)
C = Vec2D(*C)
AC = C - A

u = 1/abs(AC)*AC
print(u)

lg = 3
B = A + lg*u

print(B)

Tkinter/I - Information générales et transversales/4 - Quelques téchniques générales/11 - Importer des vecteurs/3 - Calcule de point A/module1.py


"""
1. Write a Python program that matches a string that has an `'a'`
followed by zero or more `'b'`'s.
"""
import re
s1 = str(input())

res = re.match(r'a[b]*',s1)

print(res.group(0))