import tkinter as tk
import numpy as np

from sem_game import Board
import sem_game
from Player import Player

BOARD_ROWS = sem_game.BOARD_ROWS
BOARD_COLS = sem_game.BOARD_COLS
MAX_MOVES = sem_game.MAX_MOVES

class VisualGame(tk.Frame):
    def __init__(self, parent):
        self.frame = tk.Frame.__init__(self, parent)
        self.root = parent

        #self.p2 = Player(_name="/home/alexandre/sem-project-logs/100k_mm_sem3_3x4_dqn_32", _player_type="DQN")    # DQN
        #self.p2 = Player(_name="board_nextMoves_3_4_3_b", _player_type="Minimax")
        #print(self.p2.agent.states_value)
        #print(sorted(self.p2.agent.states_value))
        #print(len(self.p2.agent.states_value))
        #self.p2.agent.set_test_mode(True)

        self.canvas = tk.Canvas(self, bg= "grey")
        self.canvas.create_line(100, 5, 100, 300, width=3)
        self.canvas.create_line(200, 5, 200, 300, width=3)
        self.canvas.create_line(300, 5, 300, 300, width=3)

        self.canvas.create_line(5, 100, 400, 100, width=3)
        self.canvas.create_line(5, 200, 400, 200, width=3)
        self.canvas.create_rectangle(5, 5, 400, 300,
            width=5)

        self.canvas.pack(fill="both", expand=1)
        self.canvas.bind("<Button-1>", self.makeMove)

        self.reset_b = tk.Button(text="Reset", command=self.reset)
        self.reset_b.pack(side = "bottom")

        self.optionsFrame = tk.Frame()
        self.optionsFrame.pack(side="bottom")

        self.alg_choice = tk.StringVar(root)
        self.alg_choices = { 'Minimax','Monte Carlo','Q-learning','DQN'}
        self.alg_choice.set('Q-learning') # set the default option

        popupMenu = tk.OptionMenu(self.optionsFrame, self.alg_choice, *self.alg_choices)
        # tk.Label(root, text="Choose a dish")
        popupMenu.pack(side="right")

        self.player_choice = tk.StringVar(root)
        self.player_choices = { 'Player 1', 'Player 2' }
        self.player_choice.set('Player 1') # set the default option

        popupMenu1 = tk.OptionMenu(self.optionsFrame, self.player_choice, *self.player_choices)
        # tk.Label(root, text="Choose a dish")
        popupMenu1.pack(side="left", padx=15, pady=0)

        #self.txt_frame = tk.Frame(root, )
        self.winner_txt = tk.Label(self.canvas, height=1, width=50, bg="grey")
        self.winner_txt.config(font=("Arial",15))
        self.winner_txt.pack(side="bottom", padx=0, pady=0)
        #self.winner_txt.place(x = 200, y = 400)

        self.board = Board()
        self.bot_turn = -1
        self.bot_type = "Q-learning"

        # if self.bot_type == "DQN":
        #     self.p2 = Player(_name="200k_mm_sem1_3x4_32", _player_type="DQN")
        # elif self.bot_type == "Minimax":
        #     self.p2 = Player(_name="board_nextMoves_3_4_3_mmps", _player_type="Minimax")
        # elif self.bot_type == "Q-Learning":
        #     self.p2 = Player(_name="policy_sem1_3_2_20k", _player_type="Q-Learning")

        self.update_bot_type()
        self.reset()

    def makeMove(self, event):
        print(self.bot_type)
        if self.check_win() == -1 and self.bot_type != None:
            y = int(event.x / 100)
            x = int(event.y / 100)
            print(x, y)
            moveMade = self.board.make_move((x, y))
            print(moveMade)
            if moveMade == 1:
                self.update_visual()
                print(self.check_win(return_line=True))
                if self.board.check_win() == -1:
                    self.root.after(np.random.randint(500, 1000), self.bot_move)

    def bot_move(self):
        print("-----------")
        print(self.p2._name)
        print("-----------")
        positions = self.board.availablePositions()
        action = self.p2.choose_action(self.board, player = self.bot_turn)
        print("here " + str(self.bot_turn))
        #print("here" + str(action))
        moveMade = self.board.make_move(action)
        print(moveMade)
        if moveMade == 1:
            self.update_visual()
            print(self.check_win(return_line=True))

    def update_visual(self):
        for i in range(BOARD_ROWS):
            for j in range(BOARD_COLS):
                if self.board.state[i, j] == 1:
                    self.canvas.create_oval(10 + 100*j, 10 + 100*i, 90 + 100*j, 90 + 100*i,
                        fill="green", width=3, tags="move")
                elif self.board.state[i, j] == 2:
                    self.canvas.create_oval(10 + 100*j, 10 + 100*i, 90 + 100*j, 90 + 100*i,
                        fill="yellow", width=3, tags="move")
                elif self.board.state[i, j] == 3:
                    self.canvas.create_oval(10 + 100*j, 10 + 100*i, 90 + 100*j, 90 + 100*i,
                        fill="red", width=3, tags="move")

    def update_bot_type(self):
        try:
            if self.bot_type == "DQN":
                if self.bot_turn == -1:
                    self.p2 = Player(_name="policy2_sem" + str(MAX_MOVES) + "_" + str(BOARD_ROWS) + "x" + str(BOARD_COLS), _player_type="DQN")
                else:
                    self.p2 = Player(_name="policy1_sem" + str(MAX_MOVES) + "_" + str(BOARD_ROWS) + "x" + str(BOARD_COLS), _player_type="DQN")
            elif self.bot_type == "Minimax":
                self.p2 = Player(_name="board_nextMoves_"  + str(MAX_MOVES) + "_" + str(BOARD_ROWS) + "x" + str(BOARD_COLS) + "_MMPS", _player_type="Minimax")
            elif self.bot_type == "Q-learning":
                if self.bot_turn == -1:
                    self.p2 = Player(_name="policy2_sem" + str(MAX_MOVES) + "_" + str(BOARD_ROWS) + "x" + str(BOARD_COLS), _player_type="Q-learning")
                else:
                    self.p2 = Player(_name="policy1_sem" + str(MAX_MOVES) + "_" + str(BOARD_ROWS) + "x" + str(BOARD_COLS), _player_type="Q-learning")
            elif self.bot_type == "Monte Carlo":
                self.p2 = Player(_name="policy_sem" + str(MAX_MOVES) + "_" + str(BOARD_ROWS) + "x" + str(BOARD_COLS) + "_SCM", _player_type="Monte Carlo")
        except:
            print("Error trying to load bot's data")

    def check_win(self, return_line=False):
        if return_line:
            win_flag, win_line = self.board.check_win(return_line=True)
            if win_flag != -1:
                if self.bot_turn == 1:
                    if self.board.movesMade % 2 == 0:
                        txt = "You Won!"
                    else:
                        txt = "You Lost..."
                else:
                    if self.board.movesMade % 2 != 0:
                        txt = "You Won!"
                    else:
                        txt = "You Lost..."
                self.winner_txt["text"] = txt

                line_dir =  (win_line[2][0] - win_line[0][0], win_line[2][1] - win_line[0][1])
                if line_dir[0] == 0:
                    self.canvas.create_line(30 + 100*win_line[0][1], 50 + 100*win_line[0][0], 70 + 100*win_line[2][1], 50 + 100*win_line[2][0], width=12, tags="win_line")
                elif line_dir[1] == 0:
                    self.canvas.create_line(50 + 100*win_line[0][1], 30 + 100*win_line[0][0], 50 + 100*win_line[2][1], 70 + 100*win_line[2][0], width=12, tags="win_line")
                elif line_dir[0] > 0 and line_dir[1] > 0:
                    self.canvas.create_line(40 + 100*win_line[0][1], 40 + 100*win_line[0][0], 60 + 100*win_line[2][1], 60 + 100*win_line[2][0], width=12, tags="win_line")
                else:
                    self.canvas.create_line(60 + 100*win_line[0][1], 40 + 100*win_line[0][0], 40 + 100*win_line[2][1], 60 + 100*win_line[2][0], width=12, tags="win_line")
        else:
            win_flag = self.board.check_win(return_line=False)
            if win_flag != -1:
                if self.board.movesMade % 2 == 0:
                    txt = "You Won!"
                else:
                    txt = "You Lost..."
                self.winner_txt["text"] = txt

        return win_flag

    def reset(self):
        self.board.reset()
        self.canvas.delete("move")
        self.canvas.delete("win_line")
        self.winner_txt["text"] = ""

        if self.bot_type != self.alg_choice.get():
            self.bot_type = self.alg_choice.get()
            self.update_bot_type()
        if self.bot_turn == 1 and self.player_choice.get() == "Player 1":
            self.bot_turn = -1
            self.update_bot_type()
        if self.bot_turn == -1 and self.player_choice.get() == "Player 2":
            self.bot_turn = 1
            self.update_bot_type()

        # if self.player_choice.get() == "Player 1":
        #     self.bot_turn = -1
        # else:
        #     self.bot_turn = 1

        if self.bot_turn == 1:
            self.bot_move()

    def change_dropdown(self, *args):
        self.bot_type = self.tkvar.get()
        self.update_bot_type()
        print( self.tkvar.get() )

if __name__ == "__main__":
    root = tk.Tk()
    root.title("Semáforo")
    root.geometry("810x420")
    VisualGame(root).pack(fill="both", expand=True)
    root.mainloop()


# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================

"""Simple, end-to-end, LeNet-5-like convolutional MNIST model example.

This should achieve a test error of 0.7%. Please keep this model as simple and
linear as possible, it is meant as a tutorial for simple convolutional models.
Run with --self_test on the command line to execute a short self-test.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import argparse
import gzip
import os
import sys
import time

import numpy
from six.moves import urllib
from six.moves import xrange  # pylint: disable=redefined-builtin
import tensorflow as tf

SOURCE_URL = 'http://yann.lecun.com/exdb/mnist/'
WORK_DIRECTORY = 'mnist/data'
IMAGE_SIZE = 28
NUM_CHANNELS = 1
PIXEL_DEPTH = 255
NUM_LABELS = 10
VALIDATION_SIZE = 5000  # Size of the validation set.
SEED = 66478  # Set to None for random seed.
BATCH_SIZE = 64
NUM_EPOCHS = 10
EVAL_BATCH_SIZE = 64
EVAL_FREQUENCY = 100  # Number of steps between evaluations.

FLAGS = None

def data_type():
  """Return the type of the activations, weights, and placeholder variables."""
  if FLAGS.use_fp16:
    return tf.float16
  else:
    return tf.float32

def maybe_download(filename):#下载数据集（指定目录下不存在数据集）
  if not tf.gfile.Exists(WORK_DIRECTORY):     #目录不存在则创建目录
    tf.gfile.MakeDirs(WORK_DIRECTORY)
  filepath = os.path.join(WORK_DIRECTORY, filename)
  if not tf.gfile.Exists(filepath):           #文件不存在
    filepath, _ = urllib.request.urlretrieve(SOURCE_URL + filename, filepath)
    with tf.gfile.GFile(filepath) as f:
      size = f.size()
    print('Successfully downloaded', filename, size, 'bytes.')
  return filepath

def extract_data(filename, num_images):       #提取数据（将图像转为4D张量）
  print('Extracting', filename)
  with gzip.open(filename) as bytestream:
    bytestream.read(16)
    buf = bytestream.read(IMAGE_SIZE * IMAGE_SIZE * num_images * NUM_CHANNELS)
    data = numpy.frombuffer(buf, dtype=numpy.uint8).astype(numpy.float32)
    data = (data - (PIXEL_DEPTH / 2.0)) / PIXEL_DEPTH         #将图像像素值从[0,255]转为[-0.5,0.5]
    data = data.reshape(num_images, IMAGE_SIZE, IMAGE_SIZE, NUM_CHANNELS)     #数据reshape为(num_images,)
    return data

def extract_labels(filename, num_images):     #提取图像标签
  print('Extracting', filename)
  with gzip.open(filename) as bytestream:
    bytestream.read(8)
    buf = bytestream.read(1 * num_images)
    labels = numpy.frombuffer(buf, dtype=numpy.uint8).astype(numpy.int64)
  return labels

def fake_data(num_images):            #产生虚拟数据
  data = numpy.ndarray(
      shape=(num_images, IMAGE_SIZE, IMAGE_SIZE, NUM_CHANNELS),
      dtype=numpy.float32)
  labels = numpy.zeros(shape=(num_images,), dtype=numpy.int64)
  for image in xrange(num_images):
    label = image % 2
    data[image, :, :, 0] = label - 0.5
    labels[image] = label
  return data, labels

def error_rate(predictions, labels):      #计算错误率（百分制）
  """Return the error rate based on dense predictions and sparse labels."""
  return 100.0 - (
      100.0 *
      numpy.sum(numpy.argmax(predictions, 1) == labels) /       #预测正确总数除以样本总数
      predictions.shape[0])

def main(_):
  if FLAGS.self_test:         #执行测试
    print('Running self-test.')
    train_data, train_labels = fake_data(256)
    validation_data, validation_labels = fake_data(EVAL_BATCH_SIZE)
    test_data, test_labels = fake_data(EVAL_BATCH_SIZE)
    num_epochs = 1
  else:
    # Get the data.
    train_data_filename = maybe_download('train-images-idx3-ubyte.gz')      #训练数据
    train_labels_filename = maybe_download('train-labels-idx1-ubyte.gz')    #训练标签
    test_data_filename = maybe_download('t10k-images-idx3-ubyte.gz')        #测试数据
    test_labels_filename = maybe_download('t10k-labels-idx1-ubyte.gz')      #测试标签

    # Extract it into numpy arrays.
    train_data = extract_data(train_data_filename, 60000)                   #提取训练数据
    train_labels = extract_labels(train_labels_filename, 60000)             #提取训练标签
    test_data = extract_data(test_data_filename, 10000)                     #提取测试数据
    test_labels = extract_labels(test_labels_filename, 10000)               #提取测试标签

    # Generate a validation set.
    validation_data = train_data[:VALIDATION_SIZE, ...]                     #从训练数据中划分验证集
    validation_labels = train_labels[:VALIDATION_SIZE]
    train_data = train_data[VALIDATION_SIZE:, ...]
    train_labels = train_labels[VALIDATION_SIZE:]
    num_epochs = NUM_EPOCHS
  train_size = train_labels.shape[0]                                        #训练数据大小

  train_data_node = tf.placeholder(                                         #训练数据节点
      data_type(),
      shape=(BATCH_SIZE, IMAGE_SIZE, IMAGE_SIZE, NUM_CHANNELS))
  train_labels_node = tf.placeholder(tf.int64, shape=(BATCH_SIZE,))         #训练标签节点
  eval_data = tf.placeholder(                                               #验证数据节点
      data_type(),
      shape=(EVAL_BATCH_SIZE, IMAGE_SIZE, IMAGE_SIZE, NUM_CHANNELS))

  conv1_weights = tf.Variable(                                              #卷积层1权重w     5x5x1x32
      tf.truncated_normal([5, 5, NUM_CHANNELS, 32],
                          stddev=0.1,
                          seed=SEED, dtype=data_type()))
  conv1_biases = tf.Variable(tf.zeros([32], dtype=data_type()))             #卷积层1偏置b
  conv2_weights = tf.Variable(tf.truncated_normal(                          #卷积层2权重w     5x5x32x64
      [5, 5, 32, 64], stddev=0.1,
      seed=SEED, dtype=data_type()))
  conv2_biases = tf.Variable(tf.constant(0.1, shape=[64], dtype=data_type())) #卷积层2偏置b
  fc1_weights = tf.Variable(                                                  #全连接层1权重w
      tf.truncated_normal([IMAGE_SIZE // 4 * IMAGE_SIZE // 4 * 64, 512],
                          stddev=0.1,
                          seed=SEED,
                          dtype=data_type()))
  fc1_biases = tf.Variable(tf.constant(0.1, shape=[512], dtype=data_type()))  #全连接层1偏置b
  fc2_weights = tf.Variable(tf.truncated_normal([512, NUM_LABELS],            #全连接层2权重w
                                                stddev=0.1,
                                                seed=SEED,
                                                dtype=data_type()))
  fc2_biases = tf.Variable(tf.constant(                                       #全连接层2偏置b
      0.1, shape=[NUM_LABELS], dtype=data_type()))

  # We will replicate the model structure for the training subgraph, as well
  # as the evaluation subgraphs, while sharing the trainable parameters.
  def model(data, train=False):
    """The Model definition."""
    #卷积-relu激活-池化-卷积-relu激活-池化-
    conv = tf.nn.conv2d(data,                   #定义卷积层
                        conv1_weights,
                        strides=[1, 1, 1, 1],
                        padding='SAME')
    # Bias and rectified linear non-linearity.
    relu = tf.nn.relu(tf.nn.bias_add(conv, conv1_biases))           #激活函数
    # Max pooling. The kernel size spec {ksize} also follows the layout of
    # the data. Here we have a pooling window of 2, and a stride of 2.
    pool = tf.nn.max_pool(relu,                                     #池化层
                          ksize=[1, 2, 2, 1],
                          strides=[1, 2, 2, 1],
                          padding='SAME')
    conv = tf.nn.conv2d(pool,                                       #卷积层2
                        conv2_weights,
                        strides=[1, 1, 1, 1],
                        padding='SAME')
    relu = tf.nn.relu(tf.nn.bias_add(conv, conv2_biases))           #激活
    pool = tf.nn.max_pool(relu,                                     #池化层
                          ksize=[1, 2, 2, 1],
                          strides=[1, 2, 2, 1],
                          padding='SAME')

    pool_shape = pool.get_shape().as_list()
    reshape = tf.reshape(                                           #将得到的张量拉伸为1D，方便后续全连接层计算
        pool,
        [pool_shape[0], pool_shape[1] * pool_shape[2] * pool_shape[3]])

    hidden = tf.nn.relu(tf.matmul(reshape, fc1_weights) + fc1_biases)   #全连接：relu(wx+b)

    if train:                                                         #训练时增加dropout，避免过拟合
      hidden = tf.nn.dropout(hidden, 0.5, seed=SEED)
    return tf.matmul(hidden, fc2_weights) + fc2_biases                #全连接层2：wx+b

  logits = model(train_data_node, True)
  loss = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(     #计算损失
      labels=train_labels_node, logits=logits))

  regularizers = (tf.nn.l2_loss(fc1_weights) + tf.nn.l2_loss(fc1_biases) +      #添加正则化项，避免过拟和
                  tf.nn.l2_loss(fc2_weights) + tf.nn.l2_loss(fc2_biases))

  loss += 5e-4 * regularizers

  batch = tf.Variable(0, dtype=data_type())                     #第几个batch

  learning_rate = tf.train.exponential_decay(
      0.01,                #基础学习率
      batch * BATCH_SIZE,  #当前batch序号
      train_size,          #训练集合大小
      0.95,                #学习率下降速率
      staircase=True)

  optimizer = tf.train.MomentumOptimizer(learning_rate,             #优化器
                                         0.9).minimize(loss,
                                                       global_step=batch)

  train_prediction = tf.nn.softmax(logits)                          #softmax计算预测类别

  eval_prediction = tf.nn.softmax(model(eval_data))

  def eval_in_batches(data, sess):
    """Get all predictions for a dataset by running it in small batches."""
    size = data.shape[0]
    if size < EVAL_BATCH_SIZE:
      raise ValueError("batch size for evals larger than dataset: %d" % size)
    predictions = numpy.ndarray(shape=(size, NUM_LABELS), dtype=numpy.float32)
    for begin in xrange(0, size, EVAL_BATCH_SIZE):
      end = begin + EVAL_BATCH_SIZE
      if end <= size:
        predictions[begin:end, :] = sess.run(
            eval_prediction,
            feed_dict={eval_data: data[begin:end, ...]})
      else:
        batch_predictions = sess.run(
            eval_prediction,
            feed_dict={eval_data: data[-EVAL_BATCH_SIZE:, ...]})
        predictions[begin:, :] = batch_predictions[begin - size:, :]
    return predictions

  # Create a local session to run the training.
  start_time = time.time()
  with tf.Session() as sess:

    tf.global_variables_initializer().run()         #训练参数初始化
    print('Initialized!')

    for step in xrange(int(num_epochs * train_size) // BATCH_SIZE):

      offset = (step * BATCH_SIZE) % (train_size - BATCH_SIZE)            #计算训练batch偏移量
      batch_data = train_data[offset:(offset + BATCH_SIZE), ...]          #生成batch数据
      batch_labels = train_labels[offset:(offset + BATCH_SIZE)]           #生成batch标签

      feed_dict = {train_data_node: batch_data,
                   train_labels_node: batch_labels}

      sess.run(optimizer, feed_dict=feed_dict)                            #执行优化器更新参数

      if step % EVAL_FREQUENCY == 0:

        l, lr, predictions = sess.run([loss, learning_rate, train_prediction],      #计算训练损失，学习率及得到的预测
                                      feed_dict=feed_dict)
        elapsed_time = time.time() - start_time
        start_time = time.time()
        print('Step %d (epoch %.2f), %.1f ms' %
              (step, float(step) * BATCH_SIZE / train_size,
               1000 * elapsed_time / EVAL_FREQUENCY))
        print('Minibatch loss: %.3f, learning rate: %.6f' % (l, lr))
        print('Minibatch error: %.1f%%' % error_rate(predictions, batch_labels))
        print('Validation error: %.1f%%' % error_rate(
            eval_in_batches(validation_data, sess), validation_labels))
        sys.stdout.flush()

    test_error = error_rate(eval_in_batches(test_data, sess), test_labels)
    print('Test error: %.1f%%' % test_error)
    if FLAGS.self_test:
      print('test_error', test_error)
      assert test_error == 0.0, 'expected 0.0 test_error, got %.2f' % (
          test_error,)

if __name__ == '__main__':
  parser = argparse.ArgumentParser()
  parser.add_argument(
      '--use_fp16',
      default=False,
      help='Use half floats instead of full floats if True.',
      action='store_true')
  parser.add_argument(
      '--self_test',
      default=False,
      action='store_true',
      help='True if running a self test.')

  FLAGS, unparsed = parser.parse_known_args()
  tf.app.run(main=main, argv=[sys.argv[0]] + unparsed)


# Copyright (c) 2021, NVIDIA CORPORATION.  All rights reserved.
#
# NVIDIA CORPORATION and its licensors retain all intellectual property
# and proprietary rights in and to this software, related documentation
# and any modifications thereto.  Any use, reproduction, disclosure or
# distribution of this software and related documentation without an express
# license agreement from NVIDIA CORPORATION is strictly prohibited.

"""Facilities for reporting and collecting training statistics across
multiple processes and devices. The interface is designed to minimize
synchronization overhead as well as the amount of boilerplate in user
code."""

import re
import numpy as np
import torch
import dnnlib

from . import misc

#----------------------------------------------------------------------------

_num_moments    = 3             # [num_scalars, sum_of_scalars, sum_of_squares]
_reduce_dtype   = torch.float32 # Data type to use for initial per-tensor reduction.
_counter_dtype  = torch.float64 # Data type to use for the internal counters.
_rank           = 0             # Rank of the current process.
_sync_device    = None          # Device to use for multiprocess communication. None = single-process.
_sync_called    = False         # Has _sync() been called yet?
_counters       = dict()        # Running counters on each device, updated by report(): name => device => torch.Tensor
_cumulative     = dict()        # Cumulative counters on the CPU, updated by _sync(): name => torch.Tensor

#----------------------------------------------------------------------------

def init_multiprocessing(rank, sync_device):
    r"""Initializes `torch_utils.training_stats` for collecting statistics
    across multiple processes.

    This function must be called after
    `torch.distributed.init_process_group()` and before `Collector.update()`.
    The call is not necessary if multi-process collection is not needed.

    Args:
        rank:           Rank of the current process.
        sync_device:    PyTorch device to use for inter-process
                        communication, or None to disable multi-process
                        collection. Typically `torch.device('cuda', rank)`.
    """
    global _rank, _sync_device
    assert not _sync_called
    _rank = rank
    _sync_device = sync_device

#----------------------------------------------------------------------------

@misc.profiled_function
def report(name, value):
    r"""Broadcasts the given set of scalars to all interested instances of
    `Collector`, across device and process boundaries.

    This function is expected to be extremely cheap and can be safely
    called from anywhere in the training loop, loss function, or inside a
    `torch.nn.Module`.

    Warning: The current implementation expects the set of unique names to
    be consistent across processes. Please make sure that `report()` is
    called at least once for each unique name by each process, and in the
    same order. If a given process has no scalars to broadcast, it can do
    `report(name, [])` (empty list).

    Args:
        name:   Arbitrary string specifying the name of the statistic.
                Averages are accumulated separately for each unique name.
        value:  Arbitrary set of scalars. Can be a list, tuple,
                NumPy array, PyTorch tensor, or Python scalar.

    Returns:
        The same `value` that was passed in.
    """
    if name not in _counters:
        _counters[name] = dict()

    elems = torch.as_tensor(value)
    if elems.numel() == 0:
        return value

    elems = elems.detach().flatten().to(_reduce_dtype)
    moments = torch.stack([
        torch.ones_like(elems).sum(),
        elems.sum(),
        elems.square().sum(),
    ])
    assert moments.ndim == 1 and moments.shape[0] == _num_moments
    moments = moments.to(_counter_dtype)

    device = moments.device
    if device not in _counters[name]:
        _counters[name][device] = torch.zeros_like(moments)
    _counters[name][device].add_(moments)
    return value

#----------------------------------------------------------------------------

def report0(name, value):
    r"""Broadcasts the given set of scalars by the first process (`rank = 0`),
    but ignores any scalars provided by the other processes.
    See `report()` for further details.
    """
    report(name, value if _rank == 0 else [])
    return value

#----------------------------------------------------------------------------

class Collector:
    r"""Collects the scalars broadcasted by `report()` and `report0()` and
    computes their long-term averages (mean and standard deviation) over
    user-defined periods of time.

    The averages are first collected into internal counters that are not
    directly visible to the user. They are then copied to the user-visible
    state as a result of calling `update()` and can then be queried using
    `mean()`, `std()`, `as_dict()`, etc. Calling `update()` also resets the
    internal counters for the next round, so that the user-visible state
    effectively reflects averages collected between the last two calls to
    `update()`.

    Args:
        regex:          Regular expression defining which statistics to
                        collect. The default is to collect everything.
        keep_previous:  Whether to retain the previous averages if no
                        scalars were collected on a given round
                        (default: True).
    """
    def __init__(self, regex='.*', keep_previous=True):
        self._regex = re.compile(regex)
        self._keep_previous = keep_previous
        self._cumulative = dict()
        self._moments = dict()
        self.update()
        self._moments.clear()

    def names(self):
        r"""Returns the names of all statistics broadcasted so far that
        match the regular expression specified at construction time.
        """
        return [name for name in _counters if self._regex.fullmatch(name)]

    def update(self):
        r"""Copies current values of the internal counters to the
        user-visible state and resets them for the next round.

        If `keep_previous=True` was specified at construction time, the
        operation is skipped for statistics that have received no scalars
        since the last update, retaining their previous averages.

        This method performs a number of GPU-to-CPU transfers and one
        `torch.distributed.all_reduce()`. It is intended to be called
        periodically in the main training loop, typically once every
        N training steps.
        """
        if not self._keep_previous:
            self._moments.clear()
        for name, cumulative in _sync(self.names()):
            if name not in self._cumulative:
                self._cumulative[name] = torch.zeros([_num_moments], dtype=_counter_dtype)
            delta = cumulative - self._cumulative[name]
            self._cumulative[name].copy_(cumulative)
            if float(delta[0]) != 0:
                self._moments[name] = delta

    def _get_delta(self, name):
        r"""Returns the raw moments that were accumulated for the given
        statistic between the last two calls to `update()`, or zero if
        no scalars were collected.
        """
        assert self._regex.fullmatch(name)
        if name not in self._moments:
            self._moments[name] = torch.zeros([_num_moments], dtype=_counter_dtype)
        return self._moments[name]

    def num(self, name):
        r"""Returns the number of scalars that were accumulated for the given
        statistic between the last two calls to `update()`, or zero if
        no scalars were collected.
        """
        delta = self._get_delta(name)
        return int(delta[0])

    def mean(self, name):
        r"""Returns the mean of the scalars that were accumulated for the
        given statistic between the last two calls to `update()`, or NaN if
        no scalars were collected.
        """
        delta = self._get_delta(name)
        if int(delta[0]) == 0:
            return float('nan')
        return float(delta[1] / delta[0])

    def std(self, name):
        r"""Returns the standard deviation of the scalars that were
        accumulated for the given statistic between the last two calls to
        `update()`, or NaN if no scalars were collected.
        """
        delta = self._get_delta(name)
        if int(delta[0]) == 0 or not np.isfinite(float(delta[1])):
            return float('nan')
        if int(delta[0]) == 1:
            return float(0)
        mean = float(delta[1] / delta[0])
        raw_var = float(delta[2] / delta[0])
        return np.sqrt(max(raw_var - np.square(mean), 0))

    def as_dict(self):
        r"""Returns the averages accumulated between the last two calls to
        `update()` as an `dnnlib.EasyDict`. The contents are as follows:

            dnnlib.EasyDict(
                NAME = dnnlib.EasyDict(num=FLOAT, mean=FLOAT, std=FLOAT),
                ...
            )
        """
        stats = dnnlib.EasyDict()
        for name in self.names():
            stats[name] = dnnlib.EasyDict(num=self.num(name), mean=self.mean(name), std=self.std(name))
        return stats

    def __getitem__(self, name):
        r"""Convenience getter.
        `collector[name]` is a synonym for `collector.mean(name)`.
        """
        return self.mean(name)

#----------------------------------------------------------------------------

def _sync(names):
    r"""Synchronize the global cumulative counters across devices and
    processes. Called internally by `Collector.update()`.
    """
    if len(names) == 0:
        return []
    global _sync_called
    _sync_called = True

    # Collect deltas within current rank.
    deltas = []
    device = _sync_device if _sync_device is not None else torch.device('cpu')
    for name in names:
        delta = torch.zeros([_num_moments], dtype=_counter_dtype, device=device)
        for counter in _counters[name].values():
            delta.add_(counter.to(device))
            counter.copy_(torch.zeros_like(counter))
        deltas.append(delta)
    deltas = torch.stack(deltas)

    # Sum deltas across ranks.
    if _sync_device is not None:
        torch.distributed.all_reduce(deltas)

    # Update cumulative values.
    deltas = deltas.cpu()
    for idx, name in enumerate(names):
        if name not in _cumulative:
            _cumulative[name] = torch.zeros([_num_moments], dtype=_counter_dtype)
        _cumulative[name].add_(deltas[idx])

    # Return name-value pairs.
    return [(name, _cumulative[name]) for name in names]

#----------------------------------------------------------------------------


from src.constants.strings import INFO_SEPARATOR_RIBBON, ATTACK_TOTAL, DEFENSE_TOTAL, KILLED, DAMAGE, DEALT, ENEMY, \
    YOU, TO, ATTACKER, DEFENDER, ATTACKS, ATTACK, COUNTERED, DEALING, STRENGTH, A, SUFFERED, GAINED, PENALTY, BONUS, \
    BLOCKED, GAVE, ROLLED, ATTACK_SCORE, DEFENSE_SCORE
from src.functions.dice_fns import get_rolls
from src.functions.text import blue_str, separator, fail_str, warning_str, green_str, bold_str
from src.classes.characters.base.player import Player
from src.classes.characters.base.enemy import Enemy
from src.classes.characters.enemies.doppleganger import Doppleganger
from src.classes.comms.messages import messages

def attack(level, attacker, defender):
    if level is None or attacker is None or defender is None:
        return

    # get dict of participant name strings
    player_is_attacker = isinstance(attacker, Player)
    participants_txt = get_participant_txt(player_is_attacker, attacker, defender)

    # announce attacker and defender
    announce_attack(participants_txt)

    messages.add(INFO_SEPARATOR_RIBBON)

    # handle dices rolls
    base_attack_score, base_defense_score, rolls_txt = handle_dice_rolls(participants_txt)
    attack_mod_total, defense_mod_total = mod_totals(attacker, defender, participants_txt)
    if attack_mod_total != 0 or defense_mod_total != 0:
        messages.add(INFO_SEPARATOR_RIBBON)

    # display total scores
    attack_score, defense_score = [
        base_attack_score + attack_mod_total,
        base_defense_score + defense_mod_total
    ]
    display_totals(attack_score, defense_score, rolls_txt, attack_mod_total, defense_mod_total)

    # run success or fail
    if attack_score > defense_score:
        attack_successful(
            level, attack_score, defense_score, defender, participants_txt[ATTACKER], participants_txt[DEFENDER])
    else:
        counter_attack_dmg = base_defense_score - abs(base_attack_score)
        attack_failed(level, attacker, defender, attack_score, defense_score,
                      counter_attack_dmg,
                      participants_txt)

    messages.add(separator())

    # enemies counter-attack
    if player_is_attacker:
        counter_attack(level, attacker)

def counter_attack(level, attacker):
    room = level.room_ctrl.current_room
    for enemy in [e for e in room.enemies if e.is_alive and attacker.is_alive]:
        attack(room, enemy, attacker)

def announce_attack(participants):
    messages.add(blue_str('{} {} {}'.format(
        participants[ATTACKER].title(), participants[ATTACKS], participants[DEFENDER]
    )))

def attack_successful(level, attack_score, defense_score, defender, attacker_str, defender_str):
    hp_adjustment = attack_score - defense_score
    defender.adjust_hit_points(hp_adjustment * -1)
    if defender.is_dead:
        messages.add(fail_str('{} {} {}.'.format(attacker_str.title(), KILLED, defender_str)))
        if isinstance(defender, Enemy):
            room = level.room_ctrl.current_room
            room.add_weapons(defender.purge_weapons())
            room.add_items_to_inventory(defender.purge_inventory())
            room.remove_enemy(defender)
            if isinstance(defender, Doppleganger):
                level.room_ctrl.remove_from_every_room(ENEMY, defender)
    else:
        messages.add(warning_str('{} {} {} {} {} {}.'.format(attacker_str.title(), DEALT, str(hp_adjustment),
                                                             DAMAGE, TO, defender_str if defender_str == YOU
                                                             else defender_str.title())))

def attack_failed(level, attacker, defender, attack_score, defend_score, counter_damage, participants_txt):
    if attack_score == defend_score and defender.strength > attacker.strength:
        attacker.adjust_hit_points(counter_damage * -1)
        if attacker.is_dead:
            messages.add('{} {} {}'.format(participants_txt[DEFENDER].title(), KILLED, participants_txt[ATTACKER]))
            level.room.remove_enemy(defender)
        else:
            messages.add(warning_str(
                '{} {}, {} {} {} {} {}'.format(
                    participants_txt[DEFENDER].title(), COUNTERED,
                    DEALING, str(counter_damage), DAMAGE,
                    TO, participants_txt[ATTACKER]
                )
            ))
    else:
        messages.add(warning_str('{} {} {}'.format(participants_txt[DEFENDER].title(), BLOCKED, ATTACK)))

def get_participant_txt(player_is_attacker, attacker, defender):
    return dict({
        ATTACKER: YOU if player_is_attacker else attacker.name,
        DEFENDER: YOU if not player_is_attacker else defender.name,
        ATTACKS: ATTACK if player_is_attacker else ATTACKS
    })

def mod_totals(attacker, defender, participants_txt):
    return [
        sum_attack_mod_score(attacker, defender, participants_txt),
        sum_defend_mod_score(defender, attacker, participants_txt)
    ]

def sum_attack_mod_score(attacker, defender, participants_txt):
    dm = attacker.strength + get_weapon_damage_mod(attacker, defender, participants_txt)
    if attacker.strength != 0:
        messages.add('{} {} {} {} {} {}'.format(
            participants_txt[ATTACKER], SUFFERED if attacker.strength < 0 else GAINED, A,
            fail_str(attacker.strength) if attacker.strength < 0 else blue_str(attacker.strength),
            STRENGTH, PENALTY if attacker.strength < 0 else BONUS
        ))
    return dm

def sum_defend_mod_score(defender, attacker, participants_txt):
    if defender.strength != 0:
        messages.add('{} {} {} {} {} {}'.format(
            participants_txt[DEFENDER], SUFFERED if defender.strength < 0 else GAINED, A,
            fail_str(defender.strength) if defender.strength < 0 else blue_str(defender.strength),
            STRENGTH, PENALTY if defender.strength < 0 else BONUS
        ))
    return defender.strength

def get_weapon_damage_mod(attacker, defender, participants_txt):
    dm = 0
    if attacker.primary_weapon:
        dm += attacker.primary_weapon.damage_mod(defender)
        if attacker.primary_weapon:
            messages.add('{} {} {} {} {} {} {}'.format(
                attacker.primary_weapon.name,
                GAVE, participants_txt[ATTACKER], A,
                warning_str('+{}'.format(dm)) if dm > 0 else fail_str('-{}'.format(dm)) if dm < 0 else 0,
                ATTACK, BONUS
            ))
    return dm

def get_formatted_totals(atk_total, dfs_total):
    if atk_total > dfs_total:
        new_atk_total = warning_str(atk_total)
        new_dfs_total = fail_str(dfs_total)
    elif atk_total < dfs_total:
        new_atk_total = fail_str(atk_total)
        new_dfs_total = warning_str(dfs_total)
    else:
        new_atk_total = bold_str(atk_total)
        new_dfs_total = bold_str(dfs_total)
    return dict({
        ATTACK_TOTAL: new_atk_total,
        DEFENSE_TOTAL: new_dfs_total
    })

def display_totals(attack_score, defense_score, rolls_txt, attack_mod_total, defense_mod_total):
    totals_txt = get_formatted_totals(attack_score, defense_score)
    messages.add('{}:  {} {}{} = {}'.format(ATTACK_TOTAL,
                                            rolls_txt[ATTACK_TOTAL],
                                            '+ ' if attack_mod_total > -1 else '- ',
                                            blue_str(str(attack_mod_total).replace('-', '')),
                                            totals_txt[ATTACK_TOTAL]))
    messages.add('{}:  {} {}{} = {}'.format(DEFENSE_TOTAL, rolls_txt[DEFENSE_TOTAL],
                                            '+ ' if defense_mod_total > -1 else '- ',
                                            blue_str(defense_mod_total if defense_mod_total > -1
                                                     else str(defense_mod_total).replace('-', '')),
                                            totals_txt[DEFENSE_TOTAL]))

#################################################################
# Dice rolls

def handle_dice_rolls(participants_txt):
    rolls = get_dice_rolls()
    rolls_txt = get_formatted_totals(rolls[ATTACK_SCORE], rolls[DEFENSE_SCORE])
    display_dice_rolls(participants_txt, rolls, rolls_txt)
    return [rolls[ATTACK_SCORE], rolls[DEFENSE_SCORE], rolls_txt]

def get_dice_rolls():
    attack_rolls = get_rolls(3, 8)
    defense_rolls = get_rolls(3, 8)
    return dict({
        ATTACKER: attack_rolls,
        ATTACK_SCORE: sum(attack_rolls),
        DEFENDER: defense_rolls,
        DEFENSE_SCORE: sum(defense_rolls)
    })

def display_dice_rolls(participants, rolls, txt):
    messages.add(
        '{} {} {} = {}'.format(
            participants[ATTACKER].title(), ROLLED,
            '[{}]'.format(']['.join(map(green_str, rolls[ATTACKER]))),
            txt[ATTACK_TOTAL]))
    messages.add(
        '{} {} {} = {}'.format(
            participants[DEFENDER].title(), ROLLED,
            '[{}]'.format(']['.join(map(green_str, rolls[DEFENDER]))),
            txt[DEFENSE_TOTAL]))


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

from soildetector import Detector
from preprocess import PreprocessImg
from classification import Classification
import time
from globaldata import Data
#############################################################################
## 图片识别算法,包括: 土壤检测,图片处理,图片分类 三部分
## 输入: 图片cv2的numpy数组
## 输出: 图片的分类序号(int)
#############################################################################

class RecognitionAlgorithm(object):
    def __init__(self,_img_arr,saveProImg = False):
        self.__start_time = time.time()
        self.img_arr = _img_arr
        self.saveProImg = saveProImg

    @property
    def used_time(self):
        # 计算耗时
        return round(time.time()-self.__start_time,2)

    def implement(self):
        # output: the index of classification /int
        soildetector = Detector(self.img_arr)
        detected_img_arr = soildetector.soil_img_arr()
        if self.saveProImg == True: soildetector.save_pro_img()
        Data.processed_img_arr = soildetector.processed_rgb_arr_3d
        print("土壤检测耗时(s): ", soildetector.used_time)

        imgprocessor = PreprocessImg(detected_img_arr)
        X_data = imgprocessor.get_X()
        Data.grayhist_img_arr = imgprocessor.thresh_gray_img
        print("图片处理耗时(s): ", imgprocessor.used_time)

        classifier = Classification(X_data)
        class_index = classifier.predict()
        print("图片分类耗时(s): ", classifier.used_time)

        return class_index

if __name__ == '__main__':
    import cv2
    img_path = "data/180524_172941.jpg"
    reg = RecognitionAlgorithm(cv2.imread(img_path))
    print(reg.implement())


#!/usr/bin/env python

import re
import numpy as np
import cv2
import argparse
from sklearn.utils import shuffle
import classifier.cnn as cnn
from utils.FileUtil import FileUtil
from utils.FileLoader import FileLoader
import utils.FileFormatter as formatter
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_absolute_error, mean_squared_error
import utils.msg as msg
import time
from numpy import save
from numpy import load
import sys
import tkinter as tk
from tkinter import filedialog
import matplotlib.pyplot as plt
import os

"""
Class used to train the classifier or make predictions with if the classifier is trained
Needs to be run using either a bash script or with all of the variables required by the args parser in the main method
"""

#variables needed to process the dataset for training
EPOCHS = 20
BATCH_SIZE = 16
VALID_SIZE = 0.1
#used for train, test, validation split of 70:20:10, split validiation from rest with 90:10, then split 90% into 80:20 using 90*(0.2222)
TEST_SIZE = 0.2222
total_prediction_time = 0
SHOW_IMAGES_WITH_PREDICTIONS = False

#shuffles the images and labels, keeping each pair together, made obsolete due to the sklearn module
def shuffle_data(images, labels):
    return shuffle(images, labels)

#batches the data based on a batch size provided
def batch(data, batch_size):
    #case where less data than batch size, if this is the case, batching is not needed
    if (len(data) < batch_size):
        yield data
    else:
        # For item i in a range that is a length of l,
        for i in range(0, len(data), batch_size):
            # Create an index range for l of n items:
            yield data[i:i+batch_size]

#traines the neural network in batches, creating manual batches rather than using
#pre-built modules to help control memory management
def train_in_batch(images, labels, cp_path, m_path, results_dir, batch_size=BATCH_SIZE):
    #batches the labels and images
    images_batched = list(batch(images, batch_size))
    labels_batched = list(batch(labels, batch_size))

    #all image shapes are identical so choose the dimensions of the first
    height = rgb_images.shape[1]
    width = rgb_images.shape[2]
    depth = rgb_images.shape[3]
    print("\n")
    msg.timemsg("Training CNN start")

    #create the model
    model = cnn.create_cnn(width, height, depth)
    cnn.give_summary(model)

    #train each batch one at a time, with evaluation performed
    for i in range(len(images_batched)):
        msg.timemsg("Batch {}".format(i))

        #if a proper training set can't be made from the last batch, add the last batch to the one prior
        if len(images_batched[-1]) * TEST_SIZE < 1:
            last_images = images_batched[-1]
            last_labels = labels_batched[-1]
            del images_batched[-1]
            del labels_batched[-1]
            images_batched[-1] = np.append(images_batched[-1], last_images, axis=0)
            labels_batched[-1] = np.append(labels_batched[-1], last_labels, axis=0)

        #convert the data to be in the range of 0 and 1 for the pixel values
        msg.timemsg("Batch {}: Normalising pixel values".format(i))
        images_batched[i] = images_batched[i].astype('float32')
        images_batched[i] /= 255.0
        msg.timemsg("Batch {}: Normalised pixel values".format(i))

        #split data into training and testing data for that batch
        #also shuffles the data whilst splitting
        msg.timemsg("Batch {}: Shuffling and splitting data for training".format(i))
        train_images, test_images, train_labels, test_labels = train_test_split(images_batched[i], labels_batched[i], test_size=TEST_SIZE, random_state=42)

        msg.timemsg("Batch {}: Data shuffled and split, it is ready for usage".format(i))
        #input size for input layer is: 576x576 = 331776 neurons in input layer per image colour channel, 331776 * 3 per images

        # will need to train
        # checkpoints will be created during training
        # load from checkpoint if not the first batch and save the entire model at the end of each batch

        if i > 0:
            #if not the first batch, then load the weights from the previous batch and begin training again
            #model = cnn.create_cnn(width, height, depth)
            msg.timemsg("Loading weights for model")
            model = cnn.load_weights_from_disk(model, cp_path)

        msg.timemsg("Batch {}: Training batch".format(i))
        model, history = cnn.train_model(model, train_images, train_labels, test_images, test_labels, EPOCHS, cp_path)

        msg.timemsg("Batch {}: Evaluating model".format(i))
        m_s_error, mean_abs_error, = cnn.evaluate_model(model, test_images, test_labels)
        #can probably use train mse and test mse in plot training results method
        msg.timemsg("Batch {}: test loss: {:.5f}, test mae: {:.5f}\n\n".format(i, m_s_error, mean_abs_error))

    msg.timemsg("Training CNN finished")
    msg.timemsg("Saving model to file")
    cnn.save_model(model, m_path)
    msg.timemsg("Model saved to file")

    #plot mse and mae of final trained model
    #plotter = cnn.create_history_plotter()
    cnn.plot_mse(history, results_dir)
    cnn.plot_mae(history, results_dir)

    return model

#given a trained model and validation images, the model makes predictions
#show_image variables is a boolean that if true, shows the image with the prediction
def prediction(valid_imgs, sg_model, show_image=SHOW_IMAGES_WITH_PREDICTIONS):
    counter = 1
    n_valid = len(valid_imgs)
    results = np.array([])

    #go through each image one at a time and make a prediction, adding the prediction to the output array
    for img in valid_imgs:
        start_time = time.time()

        new_label = sg_model.predict(np.array([img]))
        results = np.append(results, new_label, axis=None)

        elapsed_time = (time.time() - start_time) * 1000 #ms
        global total_prediction_time
        total_prediction_time += elapsed_time

        msg.timemsg("Image #: {}, Predicted label: {:.4f}, Predicted in: {:.3f}s".format(counter, float(new_label), elapsed_time / 1000.0))
        msg.timemsg('Prediction Progress: {:.2f}%'.format(float(counter/n_valid)*100))
        counter += 1

        if show_image:
            # Display image
            plt.imshow(img, interpolation='nearest')
            # Add a title to the plot
            plt.title('Predicted: ' + str(new_label))

    return results

#converts loaded images to rgb as default is bgr
def convert_to_rbg(image):
    return cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

#given a configuration file
#it loads all of the images and labels in that file in order for numpy files to be made for the images and labels
def load_data(config_file):
    #split into batches of 50 for the computer to handle more easily
    msg.timemsg("Loading data from file")
    loader = FileUtil(config_file)
    loader.read_data()

    img_dir = loader.images[0].split("/")
    img_dir = args.root_img_dir + "/" + img_dir[1]

    img_loader = FileLoader(img_dir, args.root_img_dir)
    msg.timemsg("Loading images from folder")

    images = img_loader.load_all_from_folder(False)
    msg.timemsg("Images loaded from folder")

    msg.timemsg("Sorting image order and storing as numpy array")
    images.sort(key=lambda f: int(re.sub(r'\D', '', f[1])))
    images = np.array([(img[0]) for img in images])
    msg.timemsg("Images sorted and stored as numpy array")

    msg.timemsg("Converting images to RGB")
    for img in range(len(images)):
        images[img] = convert_to_rbg(images[img])

    msg.timemsg("Converted images to RGB")

    loaded_labels_arr = np.array([item for sublist in loader.labels for item in sublist])
    msg.timemsg("Loaded labels from file")

    return images, loaded_labels_arr

#scales the data of a given array to be between 0 and 1, can be used for images and labels,
#but if memory is an issue, just divided each pixel by 255 in place to save memory
def scale_data(target_data):
    max_val = np.max(target_data)
    min_val = np.min(target_data)
    normalised = []

    for i in target_data:
        scaled = (i - min_val) / (max_val - min_val)
        normalised.append(scaled)

    return np.array(normalised)

#method to allow the user to select a directory with images they want classified
def predict_directory(model_to_load, results):
    format_images = input("Do the images require formatting? ('y' or 'n'): ")
    predict_dir = None

    if format_images == 'y':
        #get directory from user, using file selector
        root = tk.Tk()
        root.withdraw()
        original_folder = filedialog.askdirectory(title='Select Folder Of Images to Formated')

        #get directory from user, using file selector
        root = tk.Tk()
        root.withdraw()
        predict_dir = filedialog.askdirectory(title='Select Folder Destination Folder')

        msg.timemsg("Loading from: {}".format(original_folder))
        msg.timemsg("Saving to: {}".format(predict_dir))

        msg.timemsg("Formatting the images")
        formatter.main(original_folder, predict_dir + "/")
        msg.timemsg("Images formatted")

    counter = 0
    start_time = time.time()
    #initialise the cnn instance so we have access to it's functionality
    cnn.ini()
    #load the entire model
    model = None
    try:
        model = cnn.load_model_from_disk(model_to_load)
        msg.timemsg("Model loaded for use")
    except ValueError:
        msg.timemsg("No model loaded, check the path is correct or a model has been saved properly")

    if (model is not None):
        if predict_dir is None:
            #get directory from user, maybe using file selector
            root = tk.Tk()
            root.withdraw()
            predict_dir = filedialog.askdirectory(title='Select Folder of Formatted Images')

        out_file_name = os.path.join(predict_dir, "Predictions.txt")

        msg.timemsg("Making predictions on images")
        with open(out_file_name, "w") as prediction_file:
            #process each image one at a time
            #Check first with the CheckValidImages class that each file is an image
            #This can be done by running the class in the command line and passing the path of the directory required
            for image in os.scandir(predict_dir):
                name = image.name
                msg.timemsg("Loading file: {}".format(name))

                if image.is_file() and image.name.endswith('.jpg') or image.name.endswith('.png') or image.name.endswith('.jpeg'):
                    image_path = os.path.join(predict_dir, image.name)
                    numpy_image = cv2.imread(image_path)
                    numpy_image = cv2.cvtColor(numpy_image, cv2.COLOR_BGR2RGB)
                    numpy_image = numpy_image.astype('float32')
                    numpy_image /= 255.0
                    numpy_image = np.array([numpy_image])

                    msg.timemsg("Making prediction on: {}".format(name))
                    predicted_coverage = model.predict(numpy_image)

                    elapsed_time = (time.time() - start_time) * 1000 #ms
                    global total_prediction_time
                    total_prediction_time += elapsed_time
                    #load the image, normalise the data, make the prediction and then log the prediction
                    print("Image: {}, Prediction: {:.6f}, Predicted in: {:.3f}s".format(name, float(predicted_coverage * 100), elapsed_time / 100000.0), file=prediction_file)

                    counter += 1
                else:
                    if not(image.is_file() and image.name.endswith(".txt")):
                        msg.timemsg("Not a valid file, please check the directory contents, exiting")
                        msg.timemsg("Ensure the folder path contains no folders that include a space in the name")
                        sys.exit(0)

        msg.timemsg("\n")
        if counter == 0:
            msg.timemsg("No images were in the folder, please double check")
        else:
            msg.timemsg("All predictions made and stored at: {}".format(out_file_name))

    return counter

#main argument that loads all of the arguments from a bash script or command line
#either trains a model using the given data
#or loads a trained model to make predictions with
if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--root_img_dir", help="the root image directory of the project")
    parser.add_argument("--image_data_file", help="the name of the file containing the image paths and coverages")
    parser.add_argument("--results_dir", help="the name of the directory to store results to")
    parser.add_argument("--logging_file", help="name of the file to log to")
    parser.add_argument("--checkpoint_dir", help="the directory checkpoints are saved to during training")
    parser.add_argument("--model_dir", help="the directory models are saved to during training")
    parser.add_argument("--using_small", help="determines whether the small or large dataset is being used")
    parser.add_argument("--skip_training", help="determines whether to skip training and load a pre-trained model instead")
    args = parser.parse_args()

    msg.ini(args.results_dir + args.logging_file)

    checkpoint_path = args.results_dir + args.checkpoint_dir
    model_path = args.results_dir + args.model_dir
    #if training not needed, make predictions on formatted images in a given directory
    if (args.skip_training == "1"):
        number_images = predict_directory(model_path, args.results_dir)
        if number_images > 0:
            msg.timemsg("Predictions made on set of {} images, time taken: {:.3f}s".format(number_images, float(total_prediction_time / 100000.0)))
            msg.timemsg("Average prediction time of {:.3f}s per image".format(float((total_prediction_time / 100000.0) / number_images)))
    else:
        #train the model and generate the evaluation metrics
        rgb_images = np.array([])
        labels_arr = np.array([])

        msg.timemsg("Loading or creating numpy data")

        IMAGE_FILE = "/images.npy"
        LABEL_FILE = "/labels.npy"
        if(args.using_small == "1"):
            IMAGE_FILE = "/images_small.npy"
            LABEL_FILE = "/labels_small.npy"

        #try to load the data, generate it if it doesnt load
        try:
            rgb_images = load(args.root_img_dir + IMAGE_FILE)
            labels_arr = load(args.root_img_dir + LABEL_FILE)
        except FileNotFoundError:
            msg.timemsg("Numpy files don't exist, generating them instead")
            msg.timemsg("Generating numpy data and saving to file")

            rgb_images, labels_arr = load_data(args.image_data_file)
            if len(rgb_images) > 0:
                save(args.root_img_dir + IMAGE_FILE, rgb_images)
                save(args.root_img_dir + LABEL_FILE, labels_arr)
                msg.timemsg("Data saved as numpy files, stored at: {}".format(args.root_img_dir))
                msg.timemsg("Restart the program so the data can be reloaded for proper memory management")
                sys.exit(0)
            else:
                msg.timemsg("No data loaded, check for correct path and file name parameters")
                sys.exit()

        msg.timemsg("Loaded data from numpy files")

        # fit and transform in one step for the labels

        msg.timemsg("Normalising labels")
        labels_arr = scale_data(labels_arr)
        msg.timemsg("Labels normalised")

        #at this point I have loaded in all of the images in RGB form into a numpy array in order
        #the labels are also loaded in properly too as floats
        #the data can now be shuffled whilst maintaining the relationship between labels and images
        #and then broken up into training and testing data to be fed into the model
        #the input shape for the input layer will be 576X576

        #generate validation data, and data for training / testing
        to_batch_images, valid_images, to_batch_labels, valid_labels = train_test_split(rgb_images, labels_arr, test_size=VALID_SIZE, random_state=1)

        cnn.ini()

        #generate a model by training it
        seagrass_model = train_in_batch(to_batch_images, to_batch_labels, checkpoint_path, model_path, args.results_dir)

        msg.timemsg("Running predicitons on model using validation set")
        #convert the data to be in the range of 0 and 1 for the pixel values
        msg.timemsg("Normalising pixel values for validation set")
        valid_images = valid_images.astype('float32')
        #would use the scale data method here but it uses more memory, and we know the upper
        #limit value of rgb pixels is always the same
        #this is more efficient for that task and is in place
        valid_images /= 255.0
        msg.timemsg("Normalised prediction set pixel values")

        predictions = prediction(valid_images, seagrass_model, SHOW_IMAGES_WITH_PREDICTIONS)
        msg.timemsg("All predictions made on validation set of {} images, time taken: {:.3f}s".format(len(valid_images), float(total_prediction_time / 1000.0)))
        msg.timemsg("Average prediction time of {:.3f}s per image\n".format(float((total_prediction_time / 1000.0) / len(valid_images))))

        #evaluate the predictions
        msg.timemsg("Evaluating the predictions made on the validation set")

        #error measurements of the predictions
        mse = mean_squared_error(valid_labels, predictions)
        mae = mean_absolute_error(valid_labels, predictions)

        msg.timemsg("Mean Squared Error on validation set: {:.5f}".format(mse))
        msg.timemsg("Mean Absolute Error on validation set: {:.5f}\n".format(mae))

        cnn.plot_predictions_vs_actual(valid_labels, predictions, args.results_dir)
        cnn.plot_prediction_error_distribution(valid_labels, predictions, args.results_dir)

        cnn.close()

    msg.timemsg("Execution finished, exiting")


# https://leetcode.com/problems/excel-sheet-column-title/description/

class Solution:
    def convertToTitle(self, n):

        r = ''
        while (n > 0):
            n -= 1
            r = chr(n % 26 + 65) + r
            n = n // 26
        return r


import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import ScalarFormatter
import os
import sys
import warnings
import string
import numpy as np
import pandas as pd
warnings.filterwarnings('ignore')

SMALL_SIZE = 16
MEDIUM_SIZE = 19
BIGGER_SIZE = 22

plt.rc('font', size=SMALL_SIZE)          # controls default text sizes
plt.rc('axes', titlesize=MEDIUM_SIZE)     # fontsize of the axes title
plt.rc('axes', labelsize=SMALL_SIZE)    # fontsize of the x and y labels
plt.rc('xtick', labelsize=SMALL_SIZE)    # fontsize of the tick labels
plt.rc('ytick', labelsize=SMALL_SIZE)    # fontsize of the tick labels
plt.rc('legend', fontsize=SMALL_SIZE)    # legend fontsize
plt.rc('figure', titlesize=BIGGER_SIZE)  # fontsize of the figure title

######################################################################
# Get Data from files
#
def getEquationData(path_to,Date,nodes,nDevs,name,sim_name,filenames,strong,equation_name):
    y1 = np.array([])
    y2 = np.array([])
    y3 = np.array([])
    y4 = np.array([])
    y5 = np.array([])
    y6 = np.array([])
    y7 = np.array([])
    if (strong):
        if (not filenames):
            for node,nDev in zip(nodes,nDevs):
                f = path_to + "timings_"+Date+"/"+sim_name+"_" + str(nDev) + name + ".log"
                filenames.append(f)
    else:
        if (not filenames):
            for node,nDev in zip(nodes,nDevs):
                if (node==1):
                    f = path_to + 'timings_'+Date+'/abl40m_'+str(node)+'_'+str(nDev)+name+'-HypreReuse.log'
                elif (node==4):
                    f = path_to + 'timings_'+Date+'/abl20m_'+str(node)+'_'+str(nDev)+name+'-HypreReuse.log'
                elif (node==32):
                    f = path_to + 'timings_'+Date+'/abl10m_'+str(node)+'_'+str(nDev)+name+'-HypreReuse.log'
                elif (node==256):
                    f = path_to + 'timings_'+Date+'/abl05m_'+str(node)+'_'+str(nDev)+name+'-HypreReuse.log'
                filenames.append(f)

    for (nDev, f) in zip(nDevs,filenames):
        if (os.path.isfile(f)):
            init, assem, load_complete, solve, precon, misc, norm, linIters, pre, nli, post, tot, timesteps = readLogFile(f,equation_name)
            y1 = np.append(y1,precon)
            y2 = np.append(y2,solve)
            y3 = np.append(y3,assem)
            y4 = np.append(y4,load_complete)
            y5 = np.append(y5,init)
            y6 = np.append(y6,misc)
            y7 = np.append(y7,init+assem+load_complete+solve+precon+misc)
            print(equation_name, " ",nDev," "+name+" : solve dt=",solve, " assemble dt=(", init, assem, load_complete, ") precon setup dt=",precon," total dt=",y7[-1]," linear iterations=",linIters)

    y1 = y1/float(timesteps)
    y2 = y2/float(timesteps)
    y3 = y3/float(timesteps)
    y4 = y4/float(timesteps)
    y5 = y5/float(timesteps)
    y6 = y6/float(timesteps)
    y7 = y7/float(timesteps)
    return y1, y2, y3, y4, y5, y6, y7

######################################################################
# Get Data from files
#
def getTimeStepData(path_to,Date,nodes,nDevs,name,sim_name,filenames,strong=True):
    y1 = np.array([])
    y2 = np.array([])
    y3 = np.array([])
    y4 = np.array([])
    if (not filenames):
        for node,nDev in zip(nodes,nDevs):
            if (path_to.find("fy20q4baseline")>=0 or path_to.find("eagle")>=0):
                f = path_to + "/" + sim_name + "_" + str(nDev) + name + ".log"
            else:
                f = path_to + "timings_"+Date+"/" + sim_name + "_" + str(nDev) + name + ".log"
            filenames.append(f)

    for (nDev, f) in zip(nDevs,filenames):
        if (os.path.isfile(f)):
            init, assem, load_complete, solve, precon, misc, norm, linIters, pre, nli, post, tot, timesteps = readLogFile(f,'')
            tot = np.expand_dims(tot,axis=0)
            nli = np.expand_dims(nli,axis=0)
            pre = np.expand_dims(pre,axis=0)
            if (y1.size==0):
                y1 = tot
                y2 = nli
                y3 = pre
            else:
                y1 = np.vstack((y1,tot))
                y2 = np.vstack((y2,nli))
                y3 = np.vstack((y3,pre))
            y4 = np.append(y4,nDev)
            print(nDev," "+name+" Average Pre=",np.mean(pre)," NLI=",np.mean(nli), " Post=",np.mean(post), " Total=",np.mean(tot))
        else:
            print(f)

    return y1, y2, y3, y4

def readLogFile(f,equation_name):
    count = 0
    found = False
    init = np.NaN
    assem = np.NaN
    lc = np.NaN
    solve = np.NaN
    precon = np.NaN
    misc = np.NaN
    norm = np.NaN
    linIters = np.NaN
    pre=[]
    nli=[]
    post=[]
    total=[]
    timesteps = 0
    with open(f) as fp:
        for line in fp:
            count += 1
            line = line.strip()

            if (line.find("WallClockTime")>=0):
                timesteps = timesteps + 1
                index1 = line.find(": ")
                line = line[index1+2:]
                crap = line.split(" ")
                pre.append(float(crap[2]))
                nli.append(float(crap[4]))
                post.append(float(crap[6]))
                total.append(float(crap[8]))
                #index1 = line.find(" ")
                #norm = float(line[:index1])
                #print("Line{}: Mean System Norm={}".format(count,norm))
                continue

            if (line.find("Mean System Norm")>=0):
                index1 = line.find(": ")
                line = line[index1+2:]
                index1 = line.find(" ")
                norm = float(line[:index1])
                #print("Line{}: Mean System Norm={}".format(count,norm))
                continue

            if (line.find("Timing for IO")>=0):
                found = False
                continue
            if (line.find("Timing for Eq: %s"%equation_name)>=0 and found==False):
                found = True
            elif (line.find("Timing for Eq:")<0 and found==True):
                if (line.find("init")>=0):
                    index1 = line.find("avg:")
                    index2 = line.find("min:")
                    init = float(line[index1+4:index2])
                elif (line.find("assemble")>=0):
                    index1 = line.find("avg:")
                    index2 = line.find("min:")
                    assem = float(line[index1+4:index2])
                elif (line.find("load_complete")>=0):
                    index1 = line.find("avg:")
                    index2 = line.find("min:")
                    lc = float(line[index1+4:index2])
                elif (line.find("solve")>=0):
                    index1 = line.find("avg:")
                    index2 = line.find("min:")
                    solve = float(line[index1+4:index2])
                elif (line.find("precond setup")>=0):
                    index1 = line.find("avg:")
                    index2 = line.find("min:")
                    precon = float(line[index1+4:index2])
                elif (line.find("misc")>=0):
                    index1 = line.find("avg:")
                    index2 = line.find("min:")
                    misc = float(line[index1+4:index2])
                elif (line.find("linear iterations")>=0):
                    index1 = line.find("avg:")
                    index2 = line.find("min:")
                    linIters = float(line[index1+4:index2])
            else:
                found=False
    return init, assem, lc, solve, precon, misc, norm, linIters, np.array(pre), np.array(nli), np.array(post), np.array(total), timesteps


"""
csv 모듈을 import하지 않고 csv파일 사용
문자열 안에 쉼표가 들어가 있는 경우
"""
import csv
# 문자열(string)을 아이템으로 갖는 리스트
row1 = ['test1', 'success', 'Mon']
row2 = ['test2', 'failure, kind of', 'Tue']
row3 = ['test3', 'sucess, kind of', 'Wed']
result = [row1, row2, row3]
print(result)

# 파일을 쓰기 모드로 열기
# csv 파일을 쓸(write) 때는 불필요한 라인이 써지지 않도록 하기 위해서
# 파일을 오픈할 때 newline='' 파라미터를 추가!!
with open('test_result.csv', mode= 'w', encoding='UTF-8', newline='') as f:
    # csv writer 객체 생성
    writer = csv.writer(f, delimiter = ',') # 파일을 write 하려면 writer객체를 생성해야한다.
    for row in result:
        # writer 객체의 writerow() 메소드를 사용해서 한 줄씩 쓰기
        writer.writerow(row)
        # row1,2,3의 내용이 있는 test_result.csv 파일이 같은 경로에 생성됨.
        # delimiter=',' ===> ','가 있는 문장은 ""로 묶였고, 없으면 그냥 출력됨

# csv 모듈을 사용하지 않고 csv 파일을 읽었을 때 문제점
with open('test_result.csv', mode='r', encoding='UTF-8') as f:
    for line in f:
        print(line.strip().split(','))
        # 'failure, kind of'라는 하나의 문자열이
        # 'failure' 와 'kind of'라는 두 개의 문자열로 쪼개짐
        # 원래 데이터에 없어야 할 "가 문자열에 포함됨
        # 예)
        # ['test1', 'success', 'Mon']
        # ['']  -----> 이것 처럼 이상한 데이터가 포함됨
        # ['test2', '"failure', ' kind of"', 'Tue'] -----> failure와 kind of가 다른 문자열로 쪼개짐
        # ['']
        # ['test3', '"sucess', ' kind of"', 'Wed']
        # ['']
print('\ncsv 모듈을 사용할 때')
with open('test_result.csv', mode='r', encoding='UTF-8') as f:
    # csv reader 객체를 생성
    reader = csv.reader(f)
    for row in reader:
        # reader 객체의 read 기능을 이용해서 한줄씩 읽음
        print(row)


try:
    import matplotlib
    matplotlib.use('WXAgg')
    import numpy
    from matplotlib.pyplot import cm
    import types
    numpy.seterr(invalid='ignore')
except ImportError:
    pass

"""@package pipeline_display
This package allows easy plotting of pipeline data (images, spectra, tables)
with a few class method calls.
It is assumed that the plotting is done in a subplot of a Figure matplotlib
object
(http://matplotlib.sourceforge.net/api/axes_api.html#matplotlib.axes.Axes)
"""

class Plot:

    """This class contains basic functionality that is used by other plotting
    classes to perform some common tasks. It shouldn't be used directly"""

    def finalSetup(self, subplot, title, tooltip):
        """Perform some final tasks, like plotting the graph title and
        setting the tooltip"""

        subplot.grid(True)
        subplot.set_title(title, fontsize=12, fontweight='semibold')
        subplot.tooltip = tooltip

class ScatterPlottingDataset:

    """This class contains the data and configuration to plot a scatter dataset"""

    def __init__(self, x, y, marker='o', pointSize=1.4, color='blue'):
        self.x = x
        self.y = y
        self.marker = marker
        self.pointSize = pointSize
        self.color = color

class ScatterDisplay(Plot):

    """This class allows to plot a scatter graph, X vs Y.
    The basic usage is as follows:
    scadsp = ScatterDisplay()
    scadsp.display(subplot, title, tooltip, x, y)
    where x and y have the same lenght.
    Do not reuse an object of this class for different plots."""

    def __init__(self):
        self.pointSize = 1.4
        self.xLabel = None
        self.xLim = None
        self.color = 'blue'

    def display(self, subplot, title, tooltip, x, y, yerr = None):
        """This method is the main one, as it actually specifies what to plot.
        The subplot argument contains a subplot created with
        matplotlib.figure.add_subplot() method
        The tooltip will appear when hovering the mouse over the plot.
        The x, y inputs are actually the array of points to plot. They must have
        the same lenght.
        """
        if yerr is not None:
            subplot.errorbar(x, y, yerr=yerr, fmt='o')
        subplot.scatter(x, y, self.pointSize, color=self.color)
        if self.xLim is None:
            subplot.set_autoscale_on(True)
            subplot.autoscale_view(tight=None, scalex=True, scaley=True)
        else:
            subplot.set_xlim(self.xLim)
            subplot.set_ylim(self.yLim)
        if self.xLabel is not None:
            subplot.set_xlabel(self.xLabel, style='italic')
            subplot.set_ylabel(self.yLabel, style='italic')
        self.finalSetup(subplot, title, tooltip)

    def setPointSize(self, size):
        """Method to change the point size. It should be called before the
         display() method"""
        self.pointSize = size

    def setLabels(self, xLabel, yLabel):
        """This method sets the labels to use for the X and Y axes. It should be
         called before the display method()"""
        self.xLabel = xLabel
        self.yLabel = yLabel

    def setLimits(self, xMin, xMax, yMin, yMax):
        """This method specifies the limits of the axes in the plot. If the method
        is not called, then an autoscaling algorithm is applied. It should be
         called before the display method()"""
        self.xLim = xMin, xMax
        self.yLim = yMin, yMax

    def setColor(self, color):
        """Method to change the color. It should be called before the
         display() method"""
        self.color = color

class SpectrumDisplay(Plot):

    """This class allows to plot a spectrum.
    The basic usage is as follows:
    specdsp = SpectrumDisplay()
    specdsp.display(subplot, title, tooltip, wave, flux)
    where wave is the array with the wavelengths and flux contains the fluxes.
    Do not reuse an object of this class for different plots."""

    def __init__(self):
        self.x_label = None
        self.wave_lim = None
        self.flux_lim = None

    def setLabels(self, x_label, y_label):
        """This method sets the labels to use for the X and Y axes. It should be
         called before the display method()"""
        self.x_label = x_label
        self.y_label = y_label

    def display(self, subplot, title, tooltip, wave,
                flux, errorflux=None, autolimits=False):
        """This method is the main one, as it actually specifies what to plot.
        The subplot argument contains a subplot created with
        matplotlib.figure.add_subplot() method
        The tooltip will appear when hovering the mouse over the plot.
        wave is the array with the wavelengths and flux contains the fluxes. They
        must have the same length. If errorflux is provided, then the error bars
        will be plotted accordingly.
        If autolimits is set to True, then an algorithm that can choose the best
        axes limits is applied. Otherwise, the axes will span to cover the minimum
        and maximum of the wave and fluxes array values or the last applied
        limits, if any.
        """
        flux_1sigma_below = None
        flux_1sigma_above = None
        if self.x_label is not None:
            subplot.set_xlabel(self.x_label, style='italic')
            subplot.set_ylabel(self.y_label, style='italic')
        if errorflux is not None:
            flux_1sigma_below = flux - errorflux
            flux_1sigma_above = flux + errorflux
            subplot.fill_between(wave, flux_1sigma_below, flux_1sigma_above,
                                 facecolor='b', alpha=0.30)
        if self.flux_lim is None or autolimits == True:
            self.__setFluxAutoLimits(
                wave, flux, flux_1sigma_below, flux_1sigma_above)

        subplot.plot(wave, flux, color='blue')
        if self.wave_lim is None:
            self.__setWaveAutoLimits(wave)
        subplot.set_xlim(self.wave_lim)
        subplot.set_ylim(self.flux_lim)
        self.finalSetup(subplot, title, tooltip)

    def overplot(self, subplot, wave, flux, color='green'):
        """This method allows to over plot a second spectrum, but with
        all the subplot setup already created by a previous display call.
        The subplot argument contains a subplot created with
        matplotlib.figure.add_subplot() method
        """
        subplot.plot(wave, flux, color=color)

    def setWaveLimits(self, wave_limits):
        """This method sets the limits in X axes
        """
        self.wave_lim = wave_limits

    def __setWaveAutoLimits(self, wave):
        """This method will set automatically the limits for X axis
        """
        self.wave_lim = wave[0], wave[len(wave)-1]

    def __setFluxAutoLimits(self, wave, flux,
                            flux_1sigma_below=None, flux_1sigma_above=None):
        """This method performs an optimal scaling of the axes. It is only used
        internally"""
        nwave = len(flux)
        minlimwave = int(nwave*0.25)
        maxlimwave = int(nwave*0.75)
        flux_plotmin = 0
        if flux_1sigma_above is None:
            flux_plotmax = 1.2 * numpy.nanmax(
                flux[minlimwave:maxlimwave])
        else:
            flux_plotmax = 1.2 * numpy.nanmax(
                flux_1sigma_above[minlimwave:maxlimwave])
        self.flux_lim = flux_plotmin, flux_plotmax

class ImageDisplay(Plot):

    """This class allows to a 2D image.
    The basic usage is as follows:
    imadsp = ImageDisplay()
    imadsp.display(subplot, title, tooltip, image)
    where image is the 2D image to plot.
    Do not reuse an object of this class for different plots."""

    def __init__(self):
        self.x_label = None
        self.x_lim = None
        self.z_lim = None
        self.x_linwcs = None
        self.y_linwcs = None
        self.overplotScatterDataSet = None
        self.aspect = 'auto'

    def __formatCoordValue(self, data, x, y):
        (image, subplot) = data
        x_im = (int)(x-0.5)
        y_im = (int)(y-0.5)
        if self.x_linwcs is not None:
            (crval1, cdelt1, crpix1) = self.x_linwcs
            x_im = (int)((x - crval1) / cdelt1 + crpix1-0.5)
        if self.y_linwcs is not None:
            (crval2, cdelt2, crpix2) = self.y_linwcs
            y_im = (int)((y - crval2) / cdelt2 + crpix2-0.5)
        return "x=%s y=%s value=%s" % (subplot.format_xdata(x),
                                       subplot.format_ydata(y),
                                       subplot.format_xdata(image[y_im, x_im]))

    def display(self, subplot, title, tooltip, image, bpmimage=None):
        """This method is the main one, as it actually specifies what to plot.
        The subplot argument contains a subplot created with
        matplotlib.figure.add_subplot() method
        The tooltip will appear when hovering the mouse over the plot.
        The input image contains the 2D image to be plotted.
        If bpmimage is also input, then it is asumed to be a bad pixel mask and
        those values with bpmimage > 1 will be flagged in red.
        """
        # Labels
        if self.x_label is not None:
            subplot.set_xlabel(self.x_label, style='italic')
            subplot.set_ylabel(self.y_label, style='italic')

        # Limits
        if self.x_lim is not None:
            subplot.set_xlim(self.x_lim)
            subplot.set_ylim(self.y_lim)

        # Z limits
        if self.z_lim is None:
            self.setZAutoLimits(image, bpmimage)
        else:
            self.dev = (self.z_lim[1] - self.z_lim[0])
            self.avg = (self.z_lim[1] - self.z_lim[0]) / 2.

        # Store the statistics in the subplot, so that it can be accessed later
        # by the toolbar to do the scaling
        subplot.img_dev = self.dev
        subplot.img_avg = self.avg

        normalization = matplotlib.colors.Normalize(vmin=self.z_lim[0],
                                                    vmax=self.z_lim[1])

        # Overplotting of scatter
        if self.overplotScatterDataSet is not None:
            for dataset in self.overplotScatterDataSet:
                subplot.scatter(dataset.x, dataset.y, dataset.pointSize,
                                color=dataset.color, marker=dataset.marker)

        # WCS and coordinates
        if self.x_linwcs is not None:
            (crval1, cdelt1, crpix1) = self.x_linwcs
            extent_x1 = (0.5 - crpix1) * cdelt1 + crval1
            extent_x2 = (0.5 + image.shape[1] - crpix1) * cdelt1 + crval1
        else:
            extent_x1 = 0.5
            extent_x2 = 0.5 + image.shape[1]
        if self.y_linwcs is not None:
            (crval2, cdelt2, crpix2) = self.y_linwcs
            extent_y1 = (0.5 - crpix2) * cdelt2 + crval2
            extent_y2 = (0.5 + image.shape[0] - crpix2) * cdelt2 + crval2
        else:
            extent_y1 = 0.5
            extent_y2 = 0.5 + image.shape[0]

        # Displaying the image
        subplot.imshow(image, interpolation="nearest",
                       origin="lower", aspect=self.aspect,
                       cmap=matplotlib.pyplot.cm.gray,
                       norm=normalization,
                       extent=(extent_x1, extent_x2, extent_y1, extent_y2))
        # Displaying the bad pixel mask
        if bpmimage is not None:
            bpmimage_unity = bpmimage
            bpmimage_unity[bpmimage > 1] = 1
            masked_bmp = numpy.ma.masked_array(bpmimage, mask=(bpmimage == 0))
            subplot.imshow(masked_bmp, interpolation="nearest", alpha=0.8,
                           origin="lower", aspect=self.aspect,
                           extent=(extent_x1, extent_x2, extent_y1, extent_y2),
                           cmap = matplotlib.pyplot.cm.autumn)

        self.finalSetup(subplot, title, tooltip)
        subplot.format_coord = types.MethodType(self.__formatCoordValue,
                                                (image, subplot))

    def setAspect(self, aspect) :
        self.aspect = aspect

    def setLabels(self, x_label, y_label):
        """This method sets the labels to use for the X and Y axes. It should be
         called before the display method()"""
        self.x_label = x_label
        self.y_label = y_label

    def setLimits(self, x_lim, y_lim):
        """This method specifies the limits of the axes in the plot. If the method
        is not called, the whole image will be displayed. It should be
         called before the display method()"""
        self.x_lim = x_lim
        self.y_lim = y_lim

    def setZLimits(self, z_lim) :
        self.z_lim = z_lim

    def setZAutoLimits(self, image, bpmimage):
        """This method specifies computes optimal values for limits of the image
        values (z variable). It uses a sigma clipping, with a clipping of
        2 sigma, to get a better estimate of the deviation. It should be
        called before the display method()"""
        if bpmimage is not None:
            image_bpm = image[bpmimage == 0]
            self.avg = numpy.median(image_bpm[numpy.isfinite(image_bpm)])
            self.dev = 2*image_bpm[numpy.abs(
                image_bpm-self.avg) < 2*image_bpm[numpy.isfinite(image_bpm)].std()].std()
        else:
            self.avg = numpy.median(image[numpy.isfinite(image)])
            self.dev = 2*image[numpy.abs(image-self.avg) < 2*image[numpy.isfinite(image)].std()].std()

        self.z_lim = self.avg-self.dev, self.avg+self.dev

    def setXLinearWCSAxis(self, crval1, cdelt1, crpix1):
        """This method specifies a different X axis transformation than just pixels.
        It follows WCS convention for linear transformation, specifying crval1,
        cdelt1 and crpix1. The WCS value will be:
        wcs_value = (pix_position - crpix1) * cdelt1 + crval1.
        It should be called before the display method()"""
        self.x_linwcs = (crval1, cdelt1, crpix1)

    def setYLinearWCSAxis(self, crval2, cdelt2, crpix2):
        """This method specifies a different Y axis transformation than just pixels.
        It follows WCS convention for linear transformation, specifying crval2,
        cdelt2 and crpix2. The WCS value will be:
        wcs_value = (pix_position - crpix2) * cdelt2 + crval2.
        It should be called before the display method()"""
        self.y_linwcs = (crval2, cdelt2, crpix2)

    def overplotScatter(self, x, y, marker='+', size=1.4, color='blue'):
        scatterDataset = ScatterPlottingDataset(x, y, marker, size, color)
        if self.overplotScatterDataSet is None:
            self.overplotScatterDataSet = []
        self.overplotScatterDataSet.append(scatterDataset)


j =0
import random
import hashlib
#geetting inputs
c = input("data  ")
n= int(input("N(the count of last numbers)=  "))
v= input(str(n)+" last char  ")
#list of alphas
l1 = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z', 'u']
l2 = ["A", 'B', 'C', 'D', 'E', 'F','G' , 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z', 'U']
alphas= 0
#calc the count of alppphas
x=""
for j in c:
    if(j in l1 or j in l2):
        alphas +=1
#positions
h = 2**alphas
hs = []
cc =0
j=0
xf =0
# the stat of main coode
for j in range(h):
    #making a sring with 1 and 0
    while True:
        x=""
        for i in range(len(c)):
            x+=str(random.randint(0,1))
        if(not(x in hs)):
            hs.append(x)
            break
    Xx =""
    cc =0
    #convert that to lower and upper
    for j1 in x:
        if(j1=="0"):
            Xx+=c[cc].lower()
        else:
            Xx+=c[cc].upper()
        cc+=1
    #hash
    m = hashlib.sha256()
    m.update(Xx.encode('utf-8'))
    h = m.hexdigest()
    #if
    if(h[0-n:]==v):
        print("text :"+Xx)
        print("hash :"+h)
        xf = True
        break
    if xf==True:
        break
j =0
#if that not possible to have git push -Desirable-hash by changed lower to upper and upper to lower
if(xf==0):
    while 1:
        j+=1
        d = c+str(j)
        m = hashlib.sha256()
        m.update(d.encode('utf-8'))
        h = m.hexdigest()
        if(h[0-n:]==v):
            print("text :"+d)
            print("hash :"+h)
            break
x = input("press enter to exit")
#################################################################################################


import jax
import jax.numpy as jnp
from scipy.special import legendre

from .types import PhysicalConfiguration
from .utils import norm, rot_y, rot_z, sph2cart, triu_flat

__all__ = ()

def pairwise_distance(coords1, coords2):
    return jnp.linalg.norm(coords1[..., :, None, :] - coords2[..., None, :, :], axis=-1)

def pairwise_diffs(coords1, coords2):
    diffs = coords1[..., :, None, :] - coords2[..., None, :, :]
    return jnp.concatenate([diffs, (diffs**2).sum(axis=-1, keepdims=True)], axis=-1)

def pairwise_self_distance(coords, full=False):
    i, j = jnp.triu_indices(coords.shape[-2], k=1)
    diffs = coords[..., :, None, :] - coords[..., None, :, :]
    dists = norm(diffs[..., i, j, :], safe=True, axis=-1)
    if full:
        dists = (
            jnp.zeros(diffs.shape[:-1])
            .at[..., i, j]
            .set(dists)
            .at[..., j, i]
            .set(dists)
        )
    return dists

def nuclear_energy(phys_conf, mol):
    coulombs = triu_flat(
        mol.ns_valence[:, None] * mol.ns_valence
    ) / pairwise_self_distance(phys_conf.R)
    return coulombs.sum()

def electronic_potential(phys_conf):
    dists = pairwise_self_distance(phys_conf.r)
    return (1 / dists).sum(axis=-1)

def local_potential(phys_conf, mol):
    """Return the local or nuclear potential of the whole system.

    Evaluates either the classical nuclear potential V_nuc(r) = -Z/r or the local
    part of the potentials V_loc(r) from [Burkatzki et al. 2007] eq. (4) or
    from [Annaberdiyev et al. 2018] eq. (3). Returns the sum of all the local potential
    contributions from all the electrons and nuclei.
    """

    dists = pairwise_distance(phys_conf.r, phys_conf.R)
    Z_eff = mol.charges - mol.ns_core  # effective charge of the nuclei
    effective_coulomb_potential = -(Z_eff / dists).sum(axis=(-1, -2))
    if not mol.any_pp:
        return effective_coulomb_potential

    loc_params = mol.pp_loc_params
    idxs = mol.pp_mask  # indices of atoms for whom we use pseudopotential

    r_en = dists[:, idxs]  # electron-nucleus distances for all electrons and PP nuclei

    coulomb_term = jnp.einsum(
        'ij,ki->kji', loc_params[idxs, 0, 1, :], 1 / r_en
    ) * jnp.exp(jnp.einsum('ij,ki->kji', -loc_params[idxs, 0, 0, :], r_en**2))
    const_term = jnp.einsum(
        'ij,kji->kji',
        loc_params[idxs, 1, 1, :],
        jnp.exp(jnp.einsum('ij,ki->kji', -loc_params[idxs, 1, 0, :], r_en**2)),
    )
    linear_term = jnp.einsum('ij,ki->kji', loc_params[idxs, 2, 1, :], r_en) * jnp.exp(
        jnp.einsum('ij,ki->kji', -loc_params[idxs, 2, 0, :], r_en**2)
    )

    # Summation is carried over:
    # - individual cores in the molecule (idxs dimension)
    # - individual electrons (1st dimension of 'dists')
    # - potentially over different coeffs for the term of the same type (ccECP case)
    pseudopotential = (coulomb_term + const_term + linear_term).sum(axis=(-1, -2, -3))

    return effective_coulomb_potential + pseudopotential

@jax.jit
def get_unit_icosahedron_sph():
    # Basic definition of unit icosahedron vertices in spherical coordinates.
    unit_icosahedron_sph = []
    unit_icosahedron_sph.append([0, 0])
    unit_icosahedron_sph.append([jnp.pi, 0])
    for j in range(5):
        unit_icosahedron_sph.append([jnp.arctan(2), jnp.pi / 5 * 2 * j])
        unit_icosahedron_sph.append([jnp.pi - jnp.arctan(2), jnp.pi / 5 * (2 * j - 1)])
    return jnp.array(unit_icosahedron_sph)

# UNIT_ICOSAHEDRON is (12,3) array of unit icosahedron vertices
UNIT_ICOSAHEDRON = sph2cart(get_unit_icosahedron_sph())
QUADRATURE_THETAS = get_unit_icosahedron_sph()[:, 0]

def get_quadrature_points(rng, nucleus_position, phys_conf):
    """
    Transform :data:`phys_conf` of size (N,3) into an array quadrature points.

    Return a phys_conf of size (N,12,N,3) that includes all the 12 quadrature point
    configurations (i.e. reference electron position is shifted to another icosahedron
    vertex) corresponding to N different reference electron.
    """

    N = len(phys_conf)
    norm = jnp.linalg.norm(phys_conf.r - nucleus_position, axis=-1)
    theta = jnp.arccos(
        jnp.clip((phys_conf.r - nucleus_position)[..., 2] / norm, a_min=-1.0, a_max=1.0)
    )
    phi = jnp.arctan2(
        (phys_conf.r - nucleus_position)[..., 1],
        (phys_conf.r - nucleus_position)[..., 0],
    )
    phi_random = jax.random.uniform(rng, phi.shape, minval=0, maxval=jnp.pi / 5)

    z_rot_random = jnp.moveaxis(rot_z(phi_random), -1, -3)

    y_rot = jnp.moveaxis(rot_y(theta), -1, -3)  # shape: (3,3,num_e) -> (num_e,3,3)

    z_rot = jnp.moveaxis(rot_z(phi), -1, -3)  # shape: (3,3,num_e) -> (num_e,3,3)

    # auxiliary function applying the rotation and translation to be vmapped
    def transform_coordinates(norm, z_rot, y_rot, z_rot_random, r, nucleus_position):
        return norm * (z_rot @ y_rot @ z_rot_random @ r) + nucleus_position

    # vmapping to include N different rotations corresponding to each electron position
    transform_coordinates = jax.vmap(
        transform_coordinates, in_axes=(-1, -3, -3, -3, None, None)
    )
    # vmapping to be able to transform all 12 icosahedron points at the same time
    transform_coordinates = jax.vmap(
        transform_coordinates, in_axes=(None, None, None, None, -2, None)
    )

    quadrature_points = transform_coordinates(
        norm, z_rot, y_rot, z_rot_random, UNIT_ICOSAHEDRON, nucleus_position
    )  # shape: (12,N,3)
    # we still need to pad the quadrature points with other electron's coordinates
    quadrature_points_copied = jnp.tile(quadrature_points, (N, 1, 1, 1))
    rs_copied = jnp.tile(phys_conf.r, (N, 12, 1, 1))
    criterion = jnp.moveaxis(
        jnp.moveaxis(jnp.tile(jnp.eye(N), (12, 3, 1, 1)), -3, -1), -4, -3
    )
    quadrature_rs = jnp.where(
        criterion, quadrature_points_copied, rs_copied
    )  # shape: (N,12,N,3)
    return PhysicalConfiguration(
        jnp.tile(phys_conf.R[None, None], (N, 12, 1, 1)),
        quadrature_rs,
        jnp.broadcast_to(phys_conf.mol_idx, (N, 12)),
    )

def nonlocal_potential(rng, phys_conf, mol, wf):
    r"""Calculate the non-local term of the pseudopotential.

    Formulas are based on data from [Burkatzki et al. 2007] or
    [Annaberdiyev et al. 2018]. Numerical calculation of integrals is based
    on [Li et al. 2022] where 12-point icosahedron quadrature is used.
    The current implementation is using jax.lax.fori_loop instead of vmap over
    the index of the rotated electron. This causes roughly 10% slowdown compared
    to plain vmap, but avoids OOM issues. Further OOM errors could be resolved by
    replacing the remaining vmap with fori_loop over the 12 quadrature points.

    Args:
        phys_conf (:class:`deepqmc.types.PhysicalConfiguration`): electron and
            nuclear coordinates.
        mol (:class:`deepqmc.Molecule`): a molecule that is used to load the
            pseudopotential parameters.
        wf (deepqmc.wf.WaveFunction): the wave function ansatz.
    """

    # get value of the denominator (which is constant)
    denominator_wf_sign, denominator_wf_exponent = wf(phys_conf)

    pp_nl_params = jnp.array(mol.pp_nl_params)
    nuc_with_nl_pot = mol.nuc_with_nl_pot  # filter out masked nuclei

    def add_nl_potential_for_one_nucleus(i, val):
        nucleus_index = nuc_with_nl_pot[i]
        nl_params = pp_nl_params[nucleus_index]
        l_max_p1 = nl_params.shape[0]  # l_max_p1 = l_max + 1

        legendre_values = jnp.stack(
            [
                jnp.polyval(legendre(l).coef, jnp.cos(QUADRATURE_THETAS))
                for l in range(l_max_p1)
            ],
            axis=-1,
        )

        quadrature_phys_conf = get_quadrature_points(
            rng, phys_conf.R[nucleus_index], phys_conf
        )

        # (2l+1)/12 coefficient
        coefs = jnp.tile(
            (jnp.arange(l_max_p1) * 2 + 1) / 12, (len(phys_conf.r), 1)
        )  # shape: (N,l_max)

        dists = pairwise_distance(phys_conf.r, phys_conf.R[nucleus_index, None])
        nl_pot_coefs = jnp.einsum(
            'kj,ikj->ikj',
            nl_params[:, 1, :],
            jnp.exp(-jnp.einsum('ij,kj->ikj', (dists**2), nl_params[:, 0, :])),
        ).sum(axis=-1)

        def nl_potential_for_one_nucleus_and_one_electron(
            i,
            val,
            quadrature_phys_conf=quadrature_phys_conf,
            legendre_values=legendre_values,
            coefs=coefs,
            nl_pot_coefs=nl_pot_coefs,
        ):
            # numerator
            sign, exponent = jax.vmap(wf)(quadrature_phys_conf[i])  # shape (12,)
            wf_ratio = (
                denominator_wf_sign * sign * jnp.exp(exponent - denominator_wf_exponent)
            )  # shape (12,)
            wf_ratio_tile = wf_ratio[..., None] * legendre_values  # shape (12,l_max)
            # sum over 12 "virtual" electron configurations
            num_integral_one_e = jnp.sum(wf_ratio_tile, axis=-2)  # shape (1,)
            coef = coefs[i]  # shape (l_max,)
            nl_pot_coef = nl_pot_coefs[i]  # shape (1,)
            nl_potential_one_e = jnp.sum(
                nl_pot_coef * coef * num_integral_one_e, axis=(-1,)
            )  # shape ()
            return val + nl_potential_one_e

        nl_potential_for_one_nucleus = jax.lax.fori_loop(
            0,
            mol.ns_valence.astype(int).sum(),
            nl_potential_for_one_nucleus_and_one_electron,
            0.0,
        )
        return val + nl_potential_for_one_nucleus

    total_nl_potential = jax.lax.fori_loop(
        0, len(nuc_with_nl_pot), add_nl_potential_for_one_nucleus, 0.0
    )

    return total_nl_potential

def laplacian(f):
    def lap(x):
        n_coord = len(x)
        grad_f = jax.grad(f)
        df, grad_f_jvp = jax.linearize(grad_f, x)
        eye = jnp.eye(n_coord)
        d2f = lambda i, val: val + grad_f_jvp(eye[i])[i]
        d2f_sum = jax.lax.fori_loop(0, n_coord, d2f, 0.0)
        return d2f_sum, df

    return lap


import os
class open_close_manipulate_files():
    def __init__(self,filename):
        self.filename=filename

    def read_text(self):
        output=[]
        with open(self.filename,mode='r') as f:
            for lines in f:
                print(lines)
                output.append(lines)
        return output

    def read_bytes(self):
        output=[]
        with open(self.filename,mode='rb') as f:
            for lines in f:
                print(lines)
                output.append(lines)
        return output

    def prepare_string_buffer(self,start,end):
        return ','.join([str(x) for x in range(start,end)])

    def write_text(self,start,end):
        string_array=self.prepare_string_buffer(start,end)
        with open(self.filename, mode='w') as f:
            f.write(string_array)

    def write_bytes(self,start,end):
        string_array=self.prepare_string_buffer(start,end)
        with open(self.filename, mode='wb') as f:
            f.write(string_array.encode(encoding='utf-8'))

    def byte_array_manipulation(self,start,end):
        string_array = self.prepare_string_buffer(start, end)
        byte_array=bytearray(string_array.encode(encoding='utf-8'))
        print("byte_array before manipulation={}".format(byte_array))
        byte_array[1]=ord('S')
        byte_array[2] = ord('*')
        print("byte_array after manipulation={}".format(byte_array))

    def example_of_readinto_operation(self):
        data=None
        with open(self.filename, mode='rb') as f:
            for lines in f:
                data = bytearray(os.path.getsize(self.filename))
                f.readinto(data)
        print(data)

print("----------Demonstrating how to open,close and manipulate usage.-----------")
obj5 = open_close_manipulate_files("sriram.txt")
print("obj5.write_text(1,10)")
obj5.write_text(1, 10)
print("obj5.perform_read_operation()")
obj5.read_text()
print("obj5.write_bytes(1,10)")
obj5.write_bytes(1, 10)
print("obj5.perform_read_operation()")
obj5.read_bytes()
print("obj5.example_of_readinto_operation()")
obj5.example_of_readinto_operation()
obj5.byte_array_manipulation(1, 10)


values = []
with open('input.txt', 'r') as f:
    for line in f:
        value = int(line.strip('\n'))
        values.append(value)

for value1 in values:
    for value2 in values:
        for value3 in values:
            if (value1 + value2 + value3) == 2020:
                print(value1 * value2 * value3)