#!/usr/bin/env python
from __future__ import absolute_import, division, print_function

#%matplotlib inline

import os
import csv
import sys
import yaml
import random
import numpy as np
import matplotlib.pyplot as plt

import torch
import torch.nn as nn
import torch.backends.cudnn as cudnn
import torch.utils.data
import torch.utils.data.distributed
import torchvision.utils as vutils
import torchvision.transforms as transforms

from torch.utils.data.dataloader import DataLoader

from PIL import Image
from tqdm import tqdm
from datetime import datetime
from itertools import product
from collections import namedtuple
from collections import OrderedDict
from labml_helpers.module import Module

from utils.classes.DecayLR import DecayLR
from utils.classes.dataloaders import MyDataLoader
from utils.classes.ReplayBuffer import ReplayBuffer
from utils.models.cycle.Generators import Generator
from utils.models.cycle.Discriminator import Discriminator
from utils.tools.yaml_reader import read_yaml
from utils.tools.pytorch_fid.src.pytorch_fid.fid_score import calculate_fid_given_paths

from numpy import cov
from numpy import trace
from numpy import iscomplexobj
from scipy.linalg import sqrtm

# Clear the terminal
os.system("cls")

class GradientPenalty(Module):
    def __call__(self, x: torch.Tensor, f: torch.Tensor):

        batch_size = x.shape[0]

        gradients, *_ = torch.autograd.grad(outputs=f, inputs=x, grad_outputs=f.new_ones(f.shape), create_graph=True)

        gradients = gradients.reshape(batch_size, -1)

        norm = gradients.norm(2, dim=-1)

        return torch.mean((norm - 1) ** 2)

class Epoch:
    def __init__(self):
        self.count = 0
        self.loss = 0
        self.num_correct = 0
        self.start_time = None
        self.duration = None

class Run:
    def __init__(self):
        self.params = None
        self.count = 0
        self.data = []
        self.start_time = None
        self.duration = None

class RunTrainManager:

    """ [ Insert documentation ] """

    def __init__(
        self,
        dataset,
        channels: int,
        parameters: OrderedDict,
        dir_dataset: str = "./dataset",
        dir_outputs: str = "./outputs",
        dir_results: str = "./results",
        dir_weights: str = "./weights",
        save_epoch_freq: int = 1,
        show_image_freq: int = 5,
        show_graph_freq: int = 10,
        validation_percentage: float = 0.0,
    ) -> None:

        """ [ Insert documentation ] """

        # Arguments
        self.parameters = parameters
        self.dataset = dataset
        self.dataset_group = dataset.dataset_group
        self.channels = channels
        self.validation_percentage = validation_percentage

        # Configuration
        self.DIR_DATASET = f"{dir_dataset}/{self.dataset_group}"
        self.DIR_OUTPUTS = f"{dir_outputs}/{self.dataset_group}"
        self.DIR_RESULTS = f"{dir_results}/{self.dataset_group}"
        self.DIR_WEIGHTS = f"{dir_weights}/{self.dataset_group}"
        self.SHOW_IMAGE_FREQ = show_image_freq
        self.SHOW_GRAPH_FREQ = show_graph_freq
        self.SAVE_EPOCH_FREQ = save_epoch_freq
        self.RUN_PATH = None

        # Variables
        self.run = Run()
        self.epoch = Epoch()
        self.net_G_A2B = None
        self.net_G_B2A = None
        self.net_D_A = None
        self.net_D_B = None
        self.loader = None
        self.validation_batch_index = None
        self.batch_is_validation = False
        self.freq_update_discriminator = 2

        # Runs
        self.runs = self.__build_cycle(parameters)

        pass

    """ [ Public ] Starts the training loop """

    def start_cycle(self) -> None:

        # Iterate over every run, based on the configurated params
        for run in self.runs:

            # Clear occupied CUDA memory
            torch.cuda.empty_cache()

            # Set a random seed for reproducibility
            random.seed(run.manual_seed)
            torch.manual_seed(run.manual_seed)

            # Create the directory path for this run
            self.RUN_PATH = self.get_run_path(run, self.dataset.name, self.channels)

            # Make required directories for storing the training output
            self.makedirs(path=os.path.join(self.DIR_WEIGHTS, self.RUN_PATH), dir="weights")
            self.makedirs(path=os.path.join(self.DIR_OUTPUTS, self.RUN_PATH), dir="outputs")

            # Create a per-epoch csv log file
            self.__create_per_epoch_csv_logs()

            # Create Generator and Discriminator models # in_channels; # out_channels
            self.net_G_A2B = Generator(in_channels=self.channels, out_channels=self.channels).to(run.device)
            self.net_G_B2A = Generator(in_channels=self.channels, out_channels=self.channels).to(run.device)
            self.net_D_A = Discriminator(in_channels=self.channels, out_channels=self.channels).to(run.device)
            self.net_D_B = Discriminator(in_channels=self.channels, out_channels=self.channels).to(run.device)

            # Apply weights
            self.net_G_A2B.apply(self.__initialize_weights)
            self.net_G_B2A.apply(self.__initialize_weights)
            self.net_D_A.apply(self.__initialize_weights)
            self.net_D_B.apply(self.__initialize_weights)

            # define loss functions
            self.cycle_loss = torch.nn.L1Loss().to(run.device)
            self.identity_loss = torch.nn.L1Loss().to(run.device)
            self.adversarial_loss = torch.nn.MSELoss().to(run.device)

            # Optimizers
            self.optimizer_G_A2B = torch.optim.Adam(
                self.net_G_A2B.parameters(), lr=run.learning_rate_gen, betas=(0.5, 0.999)
            )
            self.optimizer_G_B2A = torch.optim.Adam(
                self.net_G_B2A.parameters(), lr=run.learning_rate_gen, betas=(0.5, 0.999)
            )
            self.optimizer_D_A = torch.optim.Adam(
                self.net_D_A.parameters(), lr=run.learning_rate_dis, betas=(0.5, 0.999)
            )
            self.optimizer_D_B = torch.optim.Adam(
                self.net_D_B.parameters(), lr=run.learning_rate_dis, betas=(0.5, 0.999)
            )

            # Learning rates
            self.lr_lambda = DecayLR(run.num_epochs, 0, run.decay_epochs).step
            self.lr_scheduler_G_A2B = torch.optim.lr_scheduler.LambdaLR(self.optimizer_G_A2B, lr_lambda=self.lr_lambda)
            self.lr_scheduler_G_B2A = torch.optim.lr_scheduler.LambdaLR(self.optimizer_G_B2A, lr_lambda=self.lr_lambda)
            self.lr_scheduler_D_A = torch.optim.lr_scheduler.LambdaLR(self.optimizer_D_A, lr_lambda=self.lr_lambda)
            self.lr_scheduler_D_B = torch.optim.lr_scheduler.LambdaLR(self.optimizer_D_B, lr_lambda=self.lr_lambda)

            # Buffers train
            self.fake_A_buffer = ReplayBuffer()
            self.fake_B_buffer = ReplayBuffer()

            # Dataloader train set
            self.loader = DataLoader(
                dataset=self.dataset, batch_size=run.batch_size, num_workers=run.num_workers, shuffle=run.shuffle
            )

            # Determine the batch-index from which the validation set starts
            self.validation_batch_index: int = int(round(int(len(self.loader) * (1 - self.validation_percentage)), 0))

            # Keep track of the per-epoch losses
            self.losses_G_A2B, self.losses_G_B2A = [], []
            self.losses_G_A2B_adv, self.losses_G_B2A_adv = [], []
            self.losses_D_A, self.losses_D_B = [], []

            # Keep track of the per-epoch average MSE loss
            self.avg_mse_loss_generated_A_array = []
            self.avg_mse_loss_generated_B_array = []

            # Keep track of the per-epoch average MSE loss
            self.avg_rmse_loss_generated_A_array = []
            self.avg_rmse_loss_generated_B_array = []

            # # Keep track of the per-epoch average MSE loss
            self.avg_fid_score_generated_A_array = []
            self.avg_fid_score_generated_B_array = []

            # Keep track of the FID score
            self.fid_score_A, self.fid_score_B = 0, 0

            # Keep track of the per-epoch noise factor
            self.noise_factor_array = []

            """ Iterate over the epochs in the run """

            # Iterate through all the epochs
            for epoch in range(0, run.num_epochs):

                # Keep track of the per-batch losses during one epoch
                self.batch_losses_G_A2B, self.batch_losses_G_B2A = [], []
                self.batch_losses_G_A2B_adv, self.batch_losses_G_B2A_adv = [], []
                self.batch_losses_D_A, self.batch_losses_D_B = [], []

                self.batch_losses_error_D_real_A, self.batch_losses_error_D_real_B = [], []
                self.batch_losses_error_D_fake_A, self.batch_losses_error_D_fake_B = [], []

                self.full_batch_losses_G_A, self.full_batch_losses_G_B = [], []

                # Keep track of the MSE losses of the generated images
                self.cum_mse_loss_generated_A, self.cum_mse_loss_generated_B = 0, 0
                self.avg_mse_loss_generated_A, self.avg_mse_loss_generated_B = 0, 0

                # Keep track of the RMSE losses of the generated images
                self.cum_rmse_loss_generated_A, self.cum_rmse_loss_generated_B = 0, 0
                self.avg_rmse_loss_generated_A, self.avg_rmse_loss_generated_B = 0, 0

                # Keep track of the FID scores of the generated images
                self.cum_fid_score_generated_A, self.cum_fid_score_generated_B = 0, 0
                self.avg_fid_score_generated_A, self.avg_fid_score_generated_B = 0, 0

                # Create a per-batch csv log file
                self.__create_per_batch_csv_logs(epoch)

                # Set error variables at 0 at the begin of each epoch
                self.__set_error_variables_to_zero()

                """ Iterate over training data using the progress bar """

                # Create progress bar
                self.progress_bar = tqdm(enumerate(self.loader), total=len(self.loader))

                # Iterate over the data loader_train
                for i, data in self.progress_bar:

                    # Determine whether this batch is for training or validation
                    self.batch_is_validation = True if i > self.validation_batch_index else False

                    # Read data
                    self.__read_data(run, data)

                    # Update generator networks
                    self.__update_generators(i)

                    # Add noise to the discriminator input
                    self.__add_discriminator_noise(run, epoch)

                    # Update discriminator networks
                    self.__update_discriminators(i)

                    # Update RMSE loss on generated images
                    self.__update_rmse_loss(i)

                    # Save the real-time output images for every {SHOW_IMG_FREQ} images
                    self.__save_realtime_output(i)

                    # Save per-epoch logs
                    self.__save_per_epoch_logs(epoch)

                    # Save the per-batch losses in a plot
                    self.__save_plot_per_batch(i, epoch)

                    # Print a progress bar in the terminal
                    self.__print_progress(run, i, epoch)

                """ Call the end-of-epoch functions """

                # Update FID Score on the generated images for current epoch, once every 5 epochs
                self.__update_fid_score(run, i)

                # Update learning rates after each epoch
                self.__update_learning_rate()

                # Save the output at the end of each epoch
                self.__save_end_epoch_output(epoch)

                # Save some logs at the end of each epoch
                self.__save_end_epoch_logs(epoch, run)

                # Save the losses in a plot of each epoch
                self.__save_plot_per_epoch()

            """ Save final model """

            # Save last check points, after every run
            torch.save(self.net_G_A2B.state_dict(), f"{self.DIR_WEIGHTS}/{self.RUN_PATH}/net_G_A2B/net_G_A2B.pth")
            torch.save(self.net_G_B2A.state_dict(), f"{self.DIR_WEIGHTS}/{self.RUN_PATH}/net_G_B2A/net_G_B2A.pth")
            torch.save(self.net_D_A.state_dict(), f"{self.DIR_WEIGHTS}/{self.RUN_PATH}/net_D_A/net_D_A.pth")
            torch.save(self.net_D_B.state_dict(), f"{self.DIR_WEIGHTS}/{self.RUN_PATH}/net_D_B/net_D_B.pth")

        pass

    """ [ Private ] Build the training cycle according the parameters """

    def __build_cycle(self, parameters) -> list:

        """ Create a list of runs using all configured parameter combinations """

        run = namedtuple("Run", parameters.keys())

        self.runs = []

        for v in product(*parameters.values()):
            self.runs.append(run(*v))

        return self.runs

    """ [ Private ] Functions to initialize variables and weights """

    def __initialize_weights(self, m):

        """ Custom weights initialization called on a Generator or Discriminator network """

        classname = m.__class__.__name__

        if classname.find("Conv") != -1:
            torch.nn.init.normal_(m.weight, 0.0, 0.02)

        elif classname.find("BatchNorm") != -1:
            torch.nn.init.normal_(m.weight, 1.0, 0.02)
            torch.nn.init.zeros_(m.bias)

        pass

    def __set_error_variables_to_zero(self) -> None:

        """ Variables for error tracking on train set """

        # Per batch error for D_A, D_B
        self.error_D_A, self.error_D_B = 0, 0

        # Average error on D_A, D_B
        self.cum_error_D_A, self.cum_error_D_B = 0, 0
        self.avg_error_D_A, self.avg_error_D_B = 0, 0

        # Per batch error for G
        self.error_G_A2B, self.error_G_B2A = 0, 0

        # Average error on G
        self.cum_error_G_A2B, self.avg_error_G_A2B = 0, 0
        self.cum_error_G_B2A, self.avg_error_G_B2A = 0, 0

        # Average error on G
        self.cum_error_G_A2B_adv_loss, self.avg_error_G_A2B_adv_loss = 0, 0
        self.cum_error_G_B2A_adv_loss, self.avg_error_G_B2A_adv_loss = 0, 0

        """ Variables for error tracking on validation set """

        # Per batch error for D_A, D_B
        self.v__error_D_A, self.v__error_D_B = 0, 0

        # Average error on D_A, D_B
        self.v__cum_error_D_A, self.v__cum_error_D_B = 0, 0
        self.v__avg_error_D_A, self.v__avg_error_D_B = 0, 0

        # Per batch error for G
        self.v__error_G_A2B, self.v__error_G_B2A = 0, 0

        # Average error on G
        self.v__cum_error_G_A2B, self.v__avg_error_G_A2B = 0, 0
        self.v__cum_error_G_B2A, self.v__avg_error_G_B2A = 0, 0

        pass

    """ [ Private ] Functions to create a .csv file """

    def __create_per_batch_csv_logs(self, epoch: int) -> None:

        """ Create a per-batch csv logs file of the current epoch """

        with open(f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/logs/EP{epoch}__logs.csv", "w", newline="") as file:
            writer = csv.writer(file)
            writer.writerow(
                [
                    "Epoch",
                    "error_D_A",
                    "error_D_B",
                    "error_G_A",
                    "error_G_B",
                    "loss_GAN_A2B",
                    "loss_GAN_B2A",
                    "loss_cycle_ABA",
                    "loss_cycle_BAB",
                    "loss_identity_A2B",
                    "loss_identity_B2A",
                    "v_error_D_A",
                    "v_error_D_B",
                    "v_error_G_A",
                    "v_error_G_B",
                ]
            )

        pass

    def __create_per_epoch_csv_logs(self) -> None:

        """ Create a per-epoch csv logs file of the current run """

        # Create csv for the logs file of this run
        with open(f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/logs.csv", "w", newline="") as file:
            writer = csv.writer(file)
            writer.writerow(
                [
                    "Epoch",
                    "Noise factor",
                    "FID Score A",
                    "FID Score B",
                    "Average MSE loss A",
                    "Average MSE loss B",
                    "Average RMSE loss A",
                    "Average RMSE loss B",
                    "Average FID score A",
                    "Average FID score B",
                    "avg_error_D_A",
                    "avg_error_D_B",
                    "avg_error_G_A",
                    "avg_error_G_B",
                    "v__avg_error_D_A",
                    "v__avg_error_D_B",
                    "v__avg_error_G_A",
                    "v__avg_error_G_B",
                ]
            )

        pass

    """ [ Private ] Functions to read- and adjust the training data """

    def __random_flip(self, real_label: torch.Tensor, fake_label: torch.Tensor, probability: float = 0.25):

        """ Randomly flip labels following a given probability """

        random_percentage = random.uniform(0, 1)

        if random_percentage > probability:
            return real_label, fake_label
        else:
            return fake_label, real_label

    def __smooth_one_hot(self, true_label: torch.Tensor, classes: int, smoothing: float = 0.25):

        """ Smoothen one-hot encoced labels y_ls = (1 - α) * y_hot + α / K """

        random_noise = random.uniform(1, 2)

        smooth_label = (1 - smoothing) * true_label + (smoothing * random_noise) / classes

        return smooth_label

    def __read_data(self, run, data) -> None:

        """ Read a left-to-right dataset or a stereo-to-disparity dataset """

        # Get image A and image B from a l2r dataset
        if self.dataset_group == "l2r":
            self.real_image_A = data["left"].to(run.device)
            self.real_image_B = data["right"].to(run.device)

        # Get image A and B from a s2d dataset
        elif self.dataset_group == "s2d":
            real_image_A_left = data["A_left"].to(run.device)
            real_image_A_right = data["A_right"].to(run.device)
            real_image_B = data["B"].to(run.device)

            # Store the images non-locally to access it elsewhere
            self.real_image_A_left = real_image_A_left
            self.real_image_A_right = real_image_A_right
            self.real_image_B = real_image_B

            # Add the left- and right into one image (image A)
            # self.real_image_A = real_image_A_left
            self.real_image_A = torch.add(real_image_A_left, real_image_A_right)

            # Normalize again
            if self.channels == 1:
                transforms.Normalize(mean=(0.5), std=(0.5))(self.real_image_A)
            elif self.channels == 3:
                transforms.Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5))(self.real_image_A)

        else:
            raise Exception(f"Can not read input images, given group '{self.dataset_group}' is incorrect.")

        # Real data label is 1, fake data label is 0.
        self.real_label = torch.full((run.batch_size, self.channels), 1, device=run.device, dtype=torch.float32)
        self.fake_label = torch.full((run.batch_size, self.channels), 0, device=run.device, dtype=torch.float32)

        pass

    def __add_discriminator_noise(self, run, epoch: int) -> None:

        """ Add decaying Gaussian noise to discriminator A and B real/fake inputs """

        # Set mean and standard deviation
        mean, std = 0.5, 0.5

        # Calculate until which epoch there is noise
        self.NOISE_UNTIL_PERCENTAGE = 0.0  # default: 0.80
        self.RANDOM_FLIP_PERCENTAGE = 0.0  # default: 0.10
        self.SMOOTHENING_PERCENTAGE = 0.0  # default: 0.10

        # Noise is gone at epoch x
        noise_until_epoch = self.NOISE_UNTIL_PERCENTAGE * run.num_epochs

        # Calculate noise factor
        if self.NOISE_UNTIL_PERCENTAGE == 0:
            self.noise_factor = 0
        elif epoch > 0 and epoch <= int(round(run.num_epochs * self.NOISE_UNTIL_PERCENTAGE)):
            self.noise_factor = round(1 - (epoch / noise_until_epoch), 3)
        elif epoch > int(round(run.num_epochs * self.NOISE_UNTIL_PERCENTAGE)):
            self.noise_factor = 0
        else:
            self.noise_factor = 1

        # Create the noise for the real images
        noise_real_A = (torch.randn(self.real_image_A.size()) * std + mean).to(run.device)
        noise_real_B = (torch.randn(self.real_image_B.size()) * std + mean).to(run.device)

        # Create the noise for the fake images
        noise_fake_A = (torch.randn(self.generated_image_B2A.size()) * std + mean).to(run.device)
        noise_fake_B = (torch.randn(self.generated_image_A2B.size()) * std + mean).to(run.device)

        # Add decaying noise to the real images (only used by discriminator)
        self.real_image_A_noise = self.real_image_A + (noise_real_A * self.noise_factor)
        self.real_image_B_noise = self.real_image_B + (noise_real_B * self.noise_factor)

        # Add decaying noise to the fake images (only used by discriminator)
        self.generated_image_B2A_noise = self.generated_image_B2A + (noise_fake_A * self.noise_factor)
        self.generated_image_A2B_noise = self.generated_image_A2B + (noise_fake_B * self.noise_factor)

        """ Label smoothing and random flipping """

        # Smoothen the labels (only used by the discriminator)
        self.real_smooth_label = self.__smooth_one_hot(self.real_label, 2, self.SMOOTHENING_PERCENTAGE)
        self.fake_smooth_label = self.__smooth_one_hot(self.fake_label, 2, self.SMOOTHENING_PERCENTAGE)

        # Randomly flip the smooth labels (only used by the discriminator)
        self.real_smooth_label, self.fake_smooth_label = self.__random_flip(
            self.real_smooth_label, self.fake_smooth_label, self.RANDOM_FLIP_PERCENTAGE
        )

        pass

    """ [ Private ] Functions to update the networks """

    def __update_generators(self, i: int) -> None:

        """ Update Generator networks: A2B and B2A """

        # Only zero the gradients when using training data
        if self.batch_is_validation == False:

            # Zero the gradients
            self.optimizer_G_A2B.zero_grad()
            self.optimizer_G_B2A.zero_grad()

        """ Generator losses """

        # GAN loss: D_A(G_B2A(B))
        self.generated_image_B2A = self.net_G_B2A(self.real_image_B)
        self.generated_output_A = self.net_D_A(self.generated_image_B2A)
        self.loss_GAN_B2A = self.adversarial_loss(self.generated_output_A, self.real_label)

        # GAN loss: D_B(G_A2B(A))
        self.generated_image_A2B = self.net_G_A2B(self.real_image_A)
        self.generated_output_B = self.net_D_B(self.generated_image_A2B)
        self.loss_GAN_A2B = self.adversarial_loss(self.generated_output_B, self.real_label)

        """ Identity loss: helps to preserve colour and prevent reverse colour in the result  """

        lambda_A2B = 10  # 10 by default
        lambda_B2A = 10  # 10 by default
        lambda_id = 0.5  # 0.5 by default, set to 0.0 for s2d because colour preservation is not desired

        # Identity loss should not be used for s2d datasets (colour shouldn't be preseved in the result)
        if self.dataset_group == "s2d":

            self.loss_identity_B2A = 0
            self.loss_identity_A2B = 0

        elif lambda_id == 0.0:

            self.loss_identity_B2A = 0
            self.loss_identity_A2B = 0

        else:

            # G_B2A should be fed the real A
            self.identity_image_B2A = self.net_G_B2A(self.real_image_A)
            self.loss_identity_B2A = (
                self.identity_loss(self.identity_image_B2A, self.real_image_A) * lambda_B2A * lambda_id
            )

            # G_A2B should be fed the real B
            self.identity_image_A2B = self.net_G_A2B(self.real_image_B)
            self.loss_identity_A2B = (
                self.identity_loss(self.identity_image_A2B, self.real_image_B) * lambda_A2B * lambda_id
            )

        """ Cycle loss, re-generates the original input image. So: A -> B -> A and B -> A -> B """

        # Apply a gradient penalty to the cycle consistency-loss
        # Check if done, read 3.3 https://ssnl.github.io/better_cycles/report.pdf

        gradient_penalty = GradientPenalty()

        self.gradient_penalty_A = gradient_penalty(self.generated_image_B2A, self.generated_output_A)
        self.gradient_penalty_B = gradient_penalty(self.generated_image_A2B, self.generated_output_B)

        # Cycle loss: A -> B -> A
        self.recovered_image_A = self.net_G_B2A(self.generated_image_A2B)
        self.loss_cycle_ABA = (
            self.cycle_loss(self.recovered_image_A, self.real_image_A)
            * lambda_A2B
            # * self.gradient_penalty_B
        )

        # Cycle loss: B -> A -> B
        self.recovered_image_B = self.net_G_A2B(self.generated_image_B2A)
        self.loss_cycle_BAB = (
            self.cycle_loss(self.recovered_image_B, self.real_image_B)
            * lambda_B2A
            # * self.gradient_penalty_A
        )

        """ Calculate the generator errors """

        # Only update weights when using training data
        if self.batch_is_validation == False:

            # Error G_A2B
            self.error_G_A2B = self.loss_GAN_A2B + self.loss_identity_A2B + self.loss_cycle_ABA

            # Error G_B2A
            self.error_G_B2A = self.loss_GAN_B2A + self.loss_identity_B2A + self.loss_cycle_BAB

            # Average error on G_A2B
            self.cum_error_G_A2B += self.error_G_A2B
            self.avg_error_G_A2B = self.cum_error_G_A2B / (i + 1)

            # Average error on G_B2A
            self.cum_error_G_B2A += self.error_G_B2A
            self.avg_error_G_B2A = self.cum_error_G_B2A / (i + 1)

            # Average error on G_A2B (adversarial loss only, so we can compare to the Discriminator)
            self.cum_error_G_A2B_adv_loss += self.loss_GAN_A2B
            self.avg_error_G_A2B_adv_loss = self.cum_error_G_A2B_adv_loss / (i + 1)

            # Average error on G_B2A (adversarial loss only, so we can compare to the Discriminator)
            self.cum_error_G_B2A_adv_loss += self.loss_GAN_B2A
            self.avg_error_G_B2A_adv_loss = self.cum_error_G_B2A_adv_loss / (i + 1)

            # Calculate gradients for G_A and G_B
            self.error_G_A2B.backward()
            self.error_G_B2A.backward()

            # Update the Generator networks
            self.optimizer_G_A2B.step()
            self.optimizer_G_B2A.step()

        else:

            # Validation error G_A2B
            self.v__error_G_A2B = self.loss_GAN_A2B + self.loss_identity_A2B + self.loss_cycle_ABA

            # Validation error G_B2A
            self.v__error_G_B2A = self.loss_GAN_B2A + self.loss_identity_B2A + self.loss_cycle_BAB

            # Average validation error on G_A2B
            self.v__cum_error_G_A2B += self.v__error_G_A2B
            self.v__avg_error_G_A2B = self.v__cum_error_G_A2B / (i + 1)

            # Average validation error on G_B2A
            self.v__cum_error_G_B2A += self.v__error_G_B2A
            self.v__avg_error_G_B2A = self.v__cum_error_G_B2A / (i + 1)

        pass

    def __update_discriminators(self, i: int) -> None:

        """ Update discriminator A """

        # Only zero the gradient when using training data
        if self.batch_is_validation == False:

            # Set D_A gradients to zero
            self.optimizer_D_A.zero_grad()

        # Real A image loss
        self.real_output_A = self.net_D_A(self.real_image_A_noise)
        self.error_D_real_A = self.adversarial_loss(self.real_output_A, self.real_smooth_label)

        # Fake image A loss
        self.generated_image_B2A_noise = self.fake_A_buffer.push_and_pop(self.generated_image_B2A_noise)
        self.generated_output_A = self.net_D_A(self.generated_image_B2A_noise.detach())
        self.error_D_fake_A = self.adversarial_loss(self.generated_output_A, self.fake_smooth_label)

        # Only update weights when using training data
        if self.batch_is_validation == False:

            # Combined loss and calculate gradients
            self.error_D_A = (self.error_D_real_A + self.error_D_fake_A) / 2

            # Cumulative and average error of D_A
            self.cum_error_D_A += self.error_D_A
            self.avg_error_D_A = self.cum_error_D_A / (i + 1)

            # Calculate gradients for D_A
            self.error_D_A.backward()

            # Update D_A weights
            self.optimizer_D_A.step()

        else:

            # Combined loss and calculate gradients
            self.v__error_D_A = (self.error_D_real_A + self.error_D_fake_A) / 2

            # Cumulative and average validation error of D_A
            self.v__cum_error_D_A += self.v__error_D_A
            self.v__avg_error_D_A = self.v__cum_error_D_A / (i + 1)

        """ Update discriminator B """

        # Only zero the gradient when using training data
        if self.batch_is_validation == False:

            # Set D_B gradients to zero
            self.optimizer_D_B.zero_grad()

        # Real B image loss
        self.real_output_B = self.net_D_B(self.real_image_B_noise)
        self.error_D_real_B = self.adversarial_loss(self.real_output_B, self.real_smooth_label)

        # Fake image B loss
        self.generated_image_A2B_noise = self.fake_B_buffer.push_and_pop(self.generated_image_A2B_noise)
        self.generated_output_B = self.net_D_B(self.generated_image_A2B_noise.detach())
        self.error_D_fake_B = self.adversarial_loss(self.generated_output_B, self.fake_smooth_label)

        # Only update weights when using training data
        if self.batch_is_validation == False:

            # Combined loss and calculate gradients
            self.error_D_B = (self.error_D_real_B + self.error_D_fake_B) / 2

            # Cumulative and average error of D_A
            self.cum_error_D_B += self.error_D_B
            self.avg_error_D_B = self.cum_error_D_B / (i + 1)

            # Calculate gradients for D_B
            self.error_D_B.backward()

            # Update D_B weights
            self.optimizer_D_B.step()

        else:

            # Combined validation loss and calculate gradients
            self.v__error_D_B = (self.error_D_real_B + self.error_D_fake_B) / 2

            # Cumulative and average validation error of D_B
            self.v__cum_error_D_B += self.v__error_D_B
            self.v__avg_error_D_B = self.v__cum_error_D_B / (i + 1)

        pass

    def __update_rmse_loss(self, i: int) -> None:

        """ Calculate a per-batch, cumulative and average (R)MSE loss of the generated outputs of generators A2B and B2A """

        # Initiate a mean square error (MSE) loss function
        mse_loss = nn.MSELoss()

        # Calculate the mean square error (MSE) loss
        mse_loss_generated_A = mse_loss(self.generated_image_B2A.detach(), self.real_image_A)
        mse_loss_generated_B = mse_loss(self.generated_image_A2B.detach(), self.real_image_B)

        # Calculate the sum of all mean square error (MSE) losses
        self.cum_mse_loss_generated_A += mse_loss_generated_A
        self.cum_mse_loss_generated_B += mse_loss_generated_B

        # Calculate the average mean square error (MSE) loss
        self.avg_mse_loss_generated_A = self.cum_mse_loss_generated_A / (i + 1)
        self.avg_mse_loss_generated_B = self.cum_mse_loss_generated_B / (i + 1)

        """ Calculate a per-batch, cumulative and average (R)MSE loss of the generated outputs of generators A2B and B2A """

        # Calculate the root mean square error (RMSE) loss
        rmse_loss_generated_A = np.sqrt(mse_loss_generated_A.cpu())
        rmse_loss_generated_B = np.sqrt(mse_loss_generated_B.cpu())

        # Calculate the sum of all root mean square error (RMSE) losses
        self.cum_rmse_loss_generated_A += rmse_loss_generated_A
        self.cum_rmse_loss_generated_B += rmse_loss_generated_B

        # Calculate the average root mean square error (RMSE) loss
        self.avg_rmse_loss_generated_A = self.cum_rmse_loss_generated_A / (i + 1)
        self.avg_rmse_loss_generated_B = self.cum_rmse_loss_generated_B / (i + 1)

        pass

    def __update_fid_score(self, run, i: int) -> None:

        """ Calculate the FID Score """

        if i % 1 == 0:

            paths_B2A = [
                f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/realtime/features/B2A/fake",
                f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/realtime/features/B2A/real",
            ]

            paths_A2B = [
                f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/realtime/features/A2B/fake",
                f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/realtime/features/A2B/real",
            ]

            self.fid_score_A = calculate_fid_given_paths(paths=paths_B2A, batch_size=run.batch_size, device=run.device)
            self.fid_score_B = calculate_fid_given_paths(paths=paths_A2B, batch_size=run.batch_size, device=run.device)

        pass

    def __update_learning_rate(self) -> None:

        """ Update the learning rate of each network """

        self.lr_scheduler_G_A2B.step()
        self.lr_scheduler_G_B2A.step()
        self.lr_scheduler_D_A.step()
        self.lr_scheduler_D_B.step()

    """ [ Private ] Functions to print-, save- and plot """

    def __print_progress(self, run, i: int, epoch: int) -> None:

        """ Print progress """

        # Print correct output if the current batch is used for training
        if self.batch_is_validation == False:

            self.progress_bar.set_description(
                #
                f"[{self.dataset_group.upper()}][{epoch + 1}/{run.num_epochs}][{i + 1}/{len(self.loader)}]"
                f"[{self.noise_factor:.2f}|{self.SMOOTHENING_PERCENTAGE:.2f}|{self.RANDOM_FLIP_PERCENTAGE:.2f}]  ||  "
                #
                f"D_A, D_B: {self.avg_error_D_A:.3f} ; {self.avg_error_D_B:.3f} || "
                f"G_A2B, G_B2A: {self.avg_error_G_A2B_adv_loss:.3f} ; {self.avg_error_G_B2A_adv_loss:.3f} || "
                f"FID_A, FID_B: {self.fid_score_A:.3f} ; {self.fid_score_B:.3f} ||"
                #
            )

        # Print correct output if the current batch is used for validation # deprecated
        else:

            self.progress_bar.set_description(
                f"[{self.dataset_group.upper()}][{epoch + 1}/{run.num_epochs}][{i + 1}/{len(self.loader)}][nf={self.noise_factor:.3f}]  ||  "
                f"v__avg_error_D_A: {self.v__avg_error_D_A:.3f} ; "
                f"v__avg_error_D_B: {self.v__avg_error_D_B:.3f}  || "
                f"v__avg_error_G_A2B: {self.v__avg_error_G_A2B:.3f} ; "
                f"v__avg_error_G_B2A: {self.v__avg_error_G_B2A:.3f} || "
            )

        pass

    def __save_realtime_output(self, i: int) -> None:

        """ Save per-epoch all the generated images for the FID score calculation """

        # Write images real-time to feature vector directory
        filepath_fake_A = f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/realtime/features/B2A/fake/{i + 1:04d}___fake_A.png"
        filepath_real_A = f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/realtime/features/B2A/real/{i + 1:04d}___real_A.png"
        filepath_fake_B = f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/realtime/features/A2B/fake/{i + 1:04d}___fake_B.png"
        filepath_real_B = f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/realtime/features/A2B/real/{i + 1:04d}___real_B.png"

        # Save all the generated (fake) image s
        vutils.save_image(self.generated_image_B2A.detach(), filepath_fake_A, normalize=True)
        vutils.save_image(self.generated_image_A2B.detach(), filepath_fake_B, normalize=True)
        vutils.save_image(self.real_image_A.detach(), filepath_real_A, normalize=True)
        vutils.save_image(self.real_image_B.detach(), filepath_real_B, normalize=True)

        """ Save used data and network outputs, real-time as a png image (useful for visualization during training) """

        if i % self.SHOW_IMAGE_FREQ == 0:

            if self.batch_is_validation == False:

                # Filepath and filename for the real-time TRAINING output images
                (
                    filepath_real_A,
                    filepath_real_B,
                    filepath_real_A_noise,
                    filepath_real_B_noise,
                    filepath_fake_A,
                    filepath_fake_B,
                    filepath_fake_A_noise,
                    filepath_fake_B_noise,
                ) = (
                    f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/A/real_sample.png",
                    f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/B/real_sample.png",
                    f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/A/real_sample_noise.png",
                    f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/B/real_sample_noise.png",
                    f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/A/fake_sample.png",
                    f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/B/fake_sample.png",
                    f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/A/fake_sample_noise.png",
                    f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/B/fake_sample_noise.png",
                )

            else:
                #
                #  Filepath and filename for the real-time VALIDATION output images
                (
                    filepath_real_A,
                    filepath_real_B,
                    filepath_real_A_noise,
                    filepath_real_B_noise,
                    filepath_fake_A,
                    filepath_fake_B,
                    filepath_fake_A_noise,
                    filepath_fake_B_noise,
                ) = (
                    f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/A/v__real_sample.png",
                    f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/B/v__real_sample.png",
                    f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/A/v__real_sample_noise.png",
                    f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/B/v__real_sample_noise.png",
                    f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/A/v__fake_sample.png",
                    f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/B/v__fake_sample.png",
                    f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/A/v__fake_sample_noise.png",
                    f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/B/v__fake_sample_noise.png",
                )

            """ Save images in corresponding A and D folder """

            # Save real input images
            vutils.save_image(self.real_image_A, filepath_real_A, normalize=True)
            vutils.save_image(self.real_image_B, filepath_real_B, normalize=True)

            # Save real input images with noise
            vutils.save_image(self.real_image_A_noise, filepath_real_A_noise, normalize=True)
            vutils.save_image(self.real_image_B_noise, filepath_real_B_noise, normalize=True)

            # Save the generated (fake) image
            vutils.save_image(self.generated_image_B2A.detach(), filepath_fake_A, normalize=True)
            vutils.save_image(self.generated_image_A2B.detach(), filepath_fake_B, normalize=True)

            # Save the generated (fake) image
            vutils.save_image(self.generated_image_B2A_noise.detach(), filepath_fake_A_noise, normalize=True)
            vutils.save_image(self.generated_image_A2B_noise.detach(), filepath_fake_B_noise, normalize=True)

            """ Save in /realtime folder """

            # Images B2A (disparity -> stereo)
            vutils.save_image(
                self.generated_image_B2A.detach(),
                f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/realtime/B2A_generated_image_B2A.png",
                normalize=True,
            )
            vutils.save_image(
                self.real_image_B.detach(),
                f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/realtime/B2A_real_image_B.png",
                normalize=True,
            )

            # Images A2B (stereo -> disparity)
            vutils.save_image(
                self.generated_image_A2B.detach(),
                f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/realtime/A2B_generated_image_A2B.png",
                normalize=True,
            )
            vutils.save_image(
                self.real_image_A.detach(),
                f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/realtime/A2B_real_image_A_stereo.png",
                normalize=True,
            )

            if self.dataset_group == "s2d":
                vutils.save_image(
                    self.real_image_A_left.detach(),
                    f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/realtime/A2B_real_image_A_left.png",
                    normalize=True,
                )
                vutils.save_image(
                    self.real_image_A_right.detach(),
                    f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/realtime/A2B_real_image_A_right.png",
                    normalize=True,
                )

        pass

    def __save_per_epoch_logs(self, epoch: int) -> None:

        """ Stores the current errors and losses as a newline to the previously created .csv file """

        # Create csv for the logs file of this run
        with open(f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/logs/EP{epoch}__logs.csv", "a+", newline="") as file:
            writer = csv.writer(file)
            writer.writerow(
                [
                    "Epoch",
                    f"{self.error_D_A:.4f}",
                    f"{self.error_D_B:.4f}",
                    f"{self.error_G_A2B:.4f}",
                    f"{self.error_G_B2A:.4f}",
                    f"{self.loss_GAN_A2B:.4f}",
                    f"{self.loss_GAN_B2A:.4f}",
                    f"{self.loss_cycle_ABA:.4f}",
                    f"{self.loss_cycle_ABA:.4f}",
                    f"{self.loss_identity_B2A:.4f}",
                    f"{self.loss_identity_B2A:.4f}",
                    f"{self.v__error_D_A:.4f}",
                    f"{self.v__error_D_B:.4f}",
                    f"{self.v__error_G_A2B:.4f}",
                    f"{self.v__error_G_B2A:.4f}",
                ]
            )

        pass

    def __save_end_epoch_output(self, epoch) -> None:

        """ Stores the current data and output as .png images (currently called at the end of each epoch) """

        # Filepath and filename for the per-epoch output images
        (
            filepath_real_A,
            filepath_real_B,
            filepath_real_A_noise,
            filepath_real_B_noise,
            filepath_fake_A,
            filepath_fake_B,
            filepath_fake_A_noise,
            filepath_fake_B_noise,
        ) = (
            f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/A/epochs/EP{epoch}___real_sample.png",
            f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/B/epochs/EP{epoch}___real_sample.png",
            f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/A/epochs/EP{epoch}___real_sample_noise.png",
            f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/B/epochs/EP{epoch}___real_sample_noise.png",
            f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/A/epochs/EP{epoch}___fake_sample.png",
            f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/B/epochs/EP{epoch}___fake_sample.png",
            f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/A/epochs/EP{epoch}___fake_sample_noise.png",
            f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/B/epochs/EP{epoch}___fake_sample_noise.png",
        )

        # Save real input images
        vutils.save_image(self.real_image_A, filepath_real_A, normalize=True)
        vutils.save_image(self.real_image_B, filepath_real_B, normalize=True)

        # Save real input images with noise
        vutils.save_image(self.real_image_A_noise, filepath_real_A_noise, normalize=True)
        vutils.save_image(self.real_image_B_noise, filepath_real_B_noise, normalize=True)

        # Save the generated (fake) image
        vutils.save_image(self.generated_image_B2A.detach(), filepath_fake_A, normalize=True)
        vutils.save_image(self.generated_image_A2B.detach(), filepath_fake_B, normalize=True)

        # Save the generated (fake) image
        vutils.save_image(self.generated_image_B2A_noise.detach(), filepath_fake_A_noise, normalize=True)
        vutils.save_image(self.generated_image_A2B_noise.detach(), filepath_fake_B_noise, normalize=True)

        # Check point dir
        model_weights_dir = f"{self.DIR_WEIGHTS}/{self.RUN_PATH}"

        # Check points, save weights after each epoch
        torch.save(self.net_G_A2B.state_dict(), f"{model_weights_dir}/net_G_A2B/net_G_A2B_epoch_{epoch}.pth")
        torch.save(self.net_G_B2A.state_dict(), f"{model_weights_dir}/net_G_B2A/net_G_B2A_epoch_{epoch}.pth")
        torch.save(self.net_D_A.state_dict(), f"{model_weights_dir}/net_D_A/net_D_A_epoch_{epoch}.pth")
        torch.save(self.net_D_B.state_dict(), f"{model_weights_dir}/net_D_B/net_D_B_epoch_{epoch}.pth")

        pass

    def __save_end_epoch_logs(self, epoch, run) -> None:

        """ Saves the noise, mse losses and average errors to a newline in the previously created .csv file (at the end of each epoch) """

        with open(f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/logs.csv", "a+", newline="") as file:
            writer = csv.writer(file)
            writer.writerow(
                [
                    epoch,
                    f"{self.noise_factor:.3f}",
                    f"{self.fid_score_A:.3f}",
                    f"{self.fid_score_B:.3f}",
                    f"{self.avg_mse_loss_generated_A:.3f}",
                    f"{self.avg_mse_loss_generated_B:.3f}",
                    f"{self.avg_rmse_loss_generated_A:.3f}",
                    f"{self.avg_rmse_loss_generated_B:.3f}",
                    f"{self.avg_fid_score_generated_A:.3f}",
                    f"{self.avg_fid_score_generated_B:.3f}",
                    f"{self.avg_error_D_A:.5f}",
                    f"{self.avg_error_D_B:.5f}",
                    f"{self.avg_error_G_A2B:.5f}",
                    f"{self.avg_error_G_B2A:.5f}",
                    f"{self.v__avg_error_D_A:.5f}",
                    f"{self.v__avg_error_D_B:.5f}",
                    f"{self.v__avg_error_G_A2B:.5f}",
                    f"{self.v__avg_error_G_B2A:.5f}",
                ]
            )

        pass

    def __save_plot_per_batch(self, i, epoch) -> None:

        """ Append the current per-batch losses to the arrays containing the network losses """

        # Total losses for all networks
        self.batch_losses_G_A2B.append(self.error_G_A2B.cpu().detach().numpy())
        self.batch_losses_G_B2A.append(self.error_G_B2A.cpu().detach().numpy())
        self.batch_losses_D_A.append(self.error_D_A.cpu().detach().numpy())
        self.batch_losses_D_B.append(self.error_D_B.cpu().detach().numpy())

        # Individual
        self.batch_losses_error_D_real_A.append(self.error_D_real_A.cpu().detach().numpy())
        self.batch_losses_error_D_real_B.append(self.error_D_real_B.cpu().detach().numpy())
        self.batch_losses_error_D_fake_A.append(self.error_D_fake_A.cpu().detach().numpy())
        self.batch_losses_error_D_fake_B.append(self.error_D_fake_B.cpu().detach().numpy())

        # Adversarial losses only of all networks
        self.batch_losses_G_A2B_adv.append(self.loss_GAN_A2B.cpu().detach().numpy())
        self.batch_losses_G_B2A_adv.append(self.loss_GAN_B2A.cpu().detach().numpy())

        """ Plot losses """

        if i % self.SHOW_GRAPH_FREQ == 0:

            # Create figure
            self.per_batch_figure, self.per_batch_axes = plt.subplots(4, 1, figsize=(8, 12))

            # Set titles
            self.per_batch_axes[0].set_title(f"Adversarial loss of generators A2B & B2A (epoch {epoch})")
            self.per_batch_axes[1].set_title(f"Adversarial loss of discriminators A & B (epoch {epoch})")
            self.per_batch_axes[2].set_title(f"Adversarial loss of Generator A2B and Discriminator B (epoch {epoch})")
            self.per_batch_axes[3].set_title(f"Adversarial loss of Generator B2A and Discriminator A (epoch {epoch})")

            # Set labels
            self.per_batch_axes[0].set(xlabel="Batch", ylabel="G total Loss")
            self.per_batch_axes[1].set(xlabel="Batch", ylabel="D total Loss")
            self.per_batch_axes[2].set(xlabel="Batch", ylabel="Adv. loss")
            self.per_batch_axes[3].set(xlabel="Batch", ylabel="Adv. loss")

            # Add gridlines
            self.per_batch_axes[0].grid()
            self.per_batch_axes[1].grid()
            self.per_batch_axes[2].grid()
            self.per_batch_axes[3].grid()

            # Plot generator values
            # self.per_batch_axes[0].plot(self.batch_losses_G_A2B, label="Total loss G_A2B", color="tab:blue")
            # self.per_batch_axes[0].plot(self.batch_losses_G_B2A, label="Total loss G_B2A", color="tab:orange")
            self.per_batch_axes[0].plot(self.batch_losses_G_A2B_adv, label="Adv. loss G_A2B", color="tab:blue")
            self.per_batch_axes[0].plot(self.batch_losses_G_B2A_adv, label="Adv. loss G_B2A", color="tab:orange")

            # Plot discriminator values
            self.per_batch_axes[1].plot(self.batch_losses_D_A, label="D_A", color="tab:blue")
            self.per_batch_axes[1].plot(self.batch_losses_D_B, label="D_B", color="tab:orange")

            # Plot the adversarial losses of all the A2B networks
            self.per_batch_axes[2].plot(self.batch_losses_G_A2B_adv, label="G_A2B", color="tab:blue")
            self.per_batch_axes[2].plot(self.batch_losses_D_B, label="D_B", color="tab:orange")

            # Plot the adversarial losses of all the B2A networks
            self.per_batch_axes[3].plot(self.batch_losses_G_B2A_adv, label="G_B2A", color="tab:blue")
            self.per_batch_axes[3].plot(self.batch_losses_D_A, label="D_A", color="tab:orange")

            # Add legends
            self.per_batch_axes[0].legend(loc="lower left", frameon=True).get_frame()
            self.per_batch_axes[1].legend(loc="lower left", frameon=True).get_frame()
            self.per_batch_axes[2].legend(loc="lower left", frameon=True).get_frame()
            self.per_batch_axes[3].legend(loc="lower left", frameon=True).get_frame()

            # Adjust layout and save
            self.per_batch_figure.tight_layout(h_pad=2.0, w_pad=0.0)
            self.per_batch_figure.savefig(f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/plots/EP{epoch}__plot.png")

            # Close figure
            plt.close(self.per_batch_figure)
            plt.close("all")

        pass

    def __save_plot_per_epoch(self) -> None:

        """ Append the current average losses, mse losses and noise factors to the arrays containing the network losses """

        # Generator adversarial losses
        self.losses_G_A2B_adv.append(self.avg_error_G_A2B_adv_loss.cpu().detach().numpy())
        self.losses_G_B2A_adv.append(self.avg_error_G_B2A_adv_loss.cpu().detach().numpy())

        # Generator total losses
        self.losses_G_A2B.append(self.avg_error_G_A2B.cpu().detach().numpy())
        self.losses_G_B2A.append(self.avg_error_G_B2A.cpu().detach().numpy())

        # Discriminator adversarial (total) losses
        self.losses_D_A.append(self.avg_error_D_A.cpu().detach().numpy())
        self.losses_D_B.append(self.avg_error_D_B.cpu().detach().numpy())

        # MSE losses
        self.avg_mse_loss_generated_A_array.append(self.avg_mse_loss_generated_A.cpu().detach().numpy())
        self.avg_mse_loss_generated_B_array.append(self.avg_mse_loss_generated_B.cpu().detach().numpy())

        # RMSE losses
        self.avg_rmse_loss_generated_A_array.append(self.avg_rmse_loss_generated_A.cpu().detach().numpy())
        self.avg_rmse_loss_generated_B_array.append(self.avg_rmse_loss_generated_B.cpu().detach().numpy())

        # FID score
        self.avg_fid_score_generated_A_array.append(self.fid_score_A)
        self.avg_fid_score_generated_B_array.append(self.fid_score_B)

        # Noise factor
        self.noise_factor_array.append(self.noise_factor)

        """ Plot losses """

        # Create figure
        self.per_epoch_figure, self.per_epoch_axes = plt.subplots(6, 1, figsize=(12, 18))

        # self.per_batch_axes[0].set_title(f"Total loss of generator A2B & B2A (epoch {epoch})")
        # Set titles
        self.per_epoch_axes[0].set_title(f"The total loss (adv. + cc.) of Generators A2B & B2A (during training)")
        self.per_epoch_axes[1].set_title(f"The total loss (adv.) of Discriminators A & B (during training)")
        self.per_epoch_axes[2].set_title(f"Decaying noise factor (during training)")
        self.per_epoch_axes[3].set_title(f"Average MSE losses per-epoch of the generated images (during training)")
        self.per_epoch_axes[4].set_title(f"Average RMSE losses per-epoch of the generated images (during training)")
        self.per_epoch_axes[5].set_title(f"Per-epoch FID score of the generated images (during training)")

        # Set labels
        self.per_epoch_axes[0].set(xlabel="Epoch", ylabel="G loss")
        self.per_epoch_axes[1].set(xlabel="Epoch", ylabel="D loss")
        self.per_epoch_axes[2].set(xlabel="Epoch", ylabel="Noise factor")
        self.per_epoch_axes[3].set(xlabel="Epoch", ylabel="MSE")
        self.per_epoch_axes[4].set(xlabel="Epoch", ylabel="RMSE")
        self.per_epoch_axes[5].set(xlabel="Epoch", ylabel="FID")

        # Add gridlines
        self.per_epoch_axes[0].grid()
        self.per_epoch_axes[1].grid()
        self.per_epoch_axes[2].grid()
        self.per_epoch_axes[3].grid()
        self.per_epoch_axes[4].grid()
        self.per_epoch_axes[5].grid()

        # Plot generator values
        # self.per_epoch_axes[0].plot(self.losses_G_A2B, label="Total loss G_A2B", color="tab:blue")
        # self.per_epoch_axes[0].plot(self.losses_G_B2A, label="Total loss G_B2A", color="tab:orange")
        self.per_epoch_axes[0].plot(self.losses_G_A2B_adv, label="Adv. loss G_A2B", color="tab:blue")
        self.per_epoch_axes[0].plot(self.losses_D_B, label="Adv. loss D_B", color="tab:orange")

        # Plot discriminator values
        self.per_epoch_axes[1].plot(self.losses_G_B2A_adv, label="Adv. loss G_B2A", color="tab:blue")
        self.per_epoch_axes[1].plot(self.losses_D_A, label="Adv. loss D_A", color="tab:orange")

        # Plot noise factor values
        self.per_epoch_axes[2].plot(self.noise_factor_array, label="Noise factor", color="tab:gray")

        # Plot MSE loss values
        self.per_epoch_axes[3].plot(self.avg_mse_loss_generated_A_array, label="MSE Loss B2A", color="forestgreen")
        self.per_epoch_axes[3].plot(self.avg_mse_loss_generated_B_array, label="MSE Loss A2B", color="red")

        # Plot RMSE loss values
        self.per_epoch_axes[4].plot(self.avg_rmse_loss_generated_A_array, label="RMSE Loss B2A", color="forestgreen")
        self.per_epoch_axes[4].plot(self.avg_rmse_loss_generated_B_array, label="RMSE Loss A2B", color="red")

        # Plot FID scores values
        self.per_epoch_axes[5].plot(self.avg_fid_score_generated_A_array, label="FID score B2A", color="forestgreen")
        self.per_epoch_axes[5].plot(self.avg_fid_score_generated_B_array, label="FID score A2B", color="red")

        # Add legends
        self.per_epoch_axes[0].legend(loc="lower left", frameon=True).get_frame()
        self.per_epoch_axes[1].legend(loc="lower left", frameon=True).get_frame()
        self.per_epoch_axes[2].legend(loc="lower left", frameon=True).get_frame()
        self.per_epoch_axes[3].legend(loc="lower left", frameon=True).get_frame()
        self.per_epoch_axes[4].legend(loc="lower left", frameon=True).get_frame()
        self.per_epoch_axes[5].legend(loc="lower left", frameon=True).get_frame()

        # Adjust layout and save
        self.per_epoch_figure.tight_layout(h_pad=2.0, w_pad=0.0)
        self.per_epoch_figure.savefig(f"{self.DIR_OUTPUTS}/{self.RUN_PATH}/plot.png")

        # Close figure
        plt.close(self.per_epoch_figure)
        plt.close("all")

        pass

    """ [ Static ] Functions to calculate the FID Score, create directories and get the run path """

    @staticmethod
    def calculate_fid(act1, act2):

        """ Calclate FID score for a source- and target image

        d^2 = ||mu_1 – mu_2||^2 + Tr(C_1 + C_2 – 2*sqrt(C_1*C_2)), where:

            d^2:    FID,
            mu_1:   mu1,
            mu_2:   mu2,
            C_1:    sigma1,
            C_2:    sigma2,
            Tr:     trace,

        """

        # Calculate mean and covariance statistics
        mu1, sigma1 = act1.mean(axis=0), cov(act1, rowvar=False)
        mu2, sigma2 = act2.mean(axis=0), cov(act2, rowvar=False)

        # Calculate sum squared difference between means
        ssdiff = np.sum((mu1 - mu2) ** 2.0)

        # Calculate sqrt of product between cov
        covmean = sqrtm(sigma1.dot(sigma2))

        # Check and correct imaginary numbers from sqrt
        if iscomplexobj(covmean):
            covmean = covmean.real

        # Calculate FID score
        FID = ssdiff + trace(sigma1 + sigma2 - 2.0 * covmean)

        return FID

    @staticmethod
    def makedirs(path: str, dir: str):

        """ Create the required directories to store the output data/images """

        if dir == "outputs":
            try:
                os.makedirs(os.path.join(path, "realtime", "features", "B2A", "fake"))
                os.makedirs(os.path.join(path, "realtime", "features", "B2A", "real"))
                os.makedirs(os.path.join(path, "realtime", "features", "A2B", "fake"))
                os.makedirs(os.path.join(path, "realtime", "features", "A2B", "real"))
                os.makedirs(os.path.join(path, "logs"))
                os.makedirs(os.path.join(path, "plots"))
                os.makedirs(os.path.join(path, "A"))
                os.makedirs(os.path.join(path, "B"))
                os.makedirs(os.path.join(path, "A", "epochs"))
                os.makedirs(os.path.join(path, "B", "epochs"))
            except OSError:
                pass

        elif dir == "weights":
            try:
                os.makedirs(os.path.join(path, "net_G_A2B"))
                os.makedirs(os.path.join(path, "net_G_B2A"))
                os.makedirs(os.path.join(path, "net_D_A"))
                os.makedirs(os.path.join(path, "net_D_B"))
            except OSError:
                pass

    @staticmethod
    def get_run_path(run, dataset_name: str, channels: int, use_one_directory: bool = False) -> str:

        """ Create and return the directory path for the passed run """

        # Store today's date in string format
        TODAY_DATE = datetime.today().strftime("%Y-%m-%d")
        TODAY_TIME = datetime.today().strftime("%H.%M.%S")

        # Determine how many digits need to USED
        digits = len(str(run.num_epochs))

        # Create a unique name for this run
        RUN_NAME = f"{TODAY_TIME}___EP{str(run.num_epochs).zfill(digits)}_DE{str(run.decay_epochs).zfill(digits)}_LRG{run.learning_rate_gen}_CH{channels}"

        # Combine the paths to a single path
        RUN_PATH = f"{dataset_name}/{TODAY_DATE}/{RUN_NAME}"

        # Only enable this boolean during development. It overwrite the previous data to avoid deleting a lot of created folders
        if use_one_directory:
            RUN_PATH = f"QUICK_DEV_DIR"

        return RUN_PATH

PARAMETERS: OrderedDict = OrderedDict(
    # System configuration and reproducebility
    device=[torch.device("cuda" if torch.cuda.is_available() else "cpu")],
    num_workers=[8],
    manual_seed=[999],
    # Dataset
    dataset_group=["s2d"],
    dataset_name=["Test_Set_RGB_DISPARITY"],
    dataset_mode=["train"],
    shuffle=[True],
    # Data dimensions
    batch_size=[1],
    channels=[1],
    # Model learning
    learning_rate_dis=[0.0002],
    learning_rate_gen=[0.0002],
    num_epochs=[100],
    decay_epochs=[50],
)

# Execute main code
if __name__ == "__main__":

    # Clear the terminal
    os.system("cls")

    try:

        mydataloader = MyDataLoader()

        channels = 3
        group = "s2d"

        dataset = mydataloader.get_dataset(group, "Test_Set_RGB_DISPARITY", "train", (100, 180), channels, False)
        manager = RunTrainManager(dataset, channels, PARAMETERS)
        manager.start_cycle()

        """

        # Test_Set_RGB_DISPARITY,       originally (1242, 2208),    train at (68, 120) # (100, 180)
        # DIODE,                        originally (768, 1024),     train at (40, 54)
        # DIML,                         originally (384, 640),      train at (64, 106)

        # Test_Set_RGB_DISPARITY
        dataset = mydataloader.get_dataset(group, "Test_Set_RGB_DISPARITY", "train", (68, 120), channels, False)
        manager = RunTrainManager(dataset, channels, PARAMETERS)
        manager.start_cycle()

        # DIML DATASET
        # dataset = mydataloader.get_dataset("s2d", "DIML", "test_disparity", (64, 106), channels, False)
        # manager = RunTrainManager(dataset, channels, PARAMETERS)
        # manager.start_cycle()

        """

    except KeyboardInterrupt:
        try:
            sys.exit(0)
        except SystemExit:
            os._exit(0)


import cmath
import math
import random as rn

guesses = set()

def guess_a_letter():

    print("\nLetters already used: {}\n".format(guesses))
    guess = input("Guess a letter: ")

    while not(len(guess) == 1) or guesses.issuperset([guess]):

        if len(guess) == 1:
            print("You already used that letter")
        else:
            print("Your guess has to be a single letter")
        guess = input("Try another Letter: ")
    guesses.add(guess)
    return guess

file = open("listofwords.txt","r")
words = []

for word in file:
    words.append(word)
file.close()

words = [new_word[0:len(new_word) - 1] for new_word in words]

choice = input("Play handman? y/n: ")
Play = False
if choice == "y":
    Play = True

while Play:
    keyword = words[rn.randint(0,len(words) - 1)]
    progress = ["--- "] * len(keyword)
    num_tries = 4
    while num_tries >= 1:
        print("*"* 20)
        print("{} tries remaining\n".format(num_tries))
        print("Word: ", progress)
        guess = guess_a_letter()
        index = keyword.find(guess,0)
        if index == -1:
            print("{} is not in the word".format(guess))
            num_tries -= 1

        else:
            while not(index == -1):
                progress[index] = guess
                index = keyword.find(guess,index + 1)
            if progress.count("--- ") == 0:
                break
    if num_tries < 1:
        print("Nice try! The word was {}".format(keyword))
    else:
        print("You guessed it!: {}".format(keyword))
    choice = input("Play again: y/n: ")
    if not (choice == "y"):
        Play = False


import os
import re
import pandas as pd
import numpy as np

tped_dir = '/gpfs/alpine/syb105/proj-shared/Projects/GeoBio_CoMet/data/original/preprocessed_d-cutoff_1000_n-cutoff_0.01_pos_342-29665/ns-as-1s_mutation_count_filtered_100'
sequence_nums_path =  os.path.join(tped_dir, 'sequence_nums.tsv')
out_dir = os.path.join(tped_dir, 'small_combined')
try:
    os.mkdir(out_dir)
except OSError:
    pass
num_seqs_per_file = 40
out_path = os.path.join(out_dir, 'seq_from_each_' + str(num_seqs_per_file) + '.tped')

tped_paths = []
for r, d, f in os.walk(tped_dir):
    for tped in f:
        if re.search('^mutation.*tped', tped):
            tped_paths.append(os.path.join(r, tped))

tped_paths.sort()

sequence_nums = pd.read_csv(sequence_nums_path, header=None, index_col=0, sep='\t')

for i, tped_path in enumerate(tped_paths):
    print(i)
    head, tail = os.path.split(tped_path)
    num_seqs = np.arange(2, sequence_nums.loc[tail].values[0] + 1)
    seq_lines = np.random.choice(num_seqs, num_seqs_per_file, replace=False)
    with open(tped_path, 'r') as in_file:
        line_num = 0
        for line in in_file:
            line_num += 1
            # only get the alignment sequence from the first tped
            if i == 0 and line_num == 1:
                with open(out_path, 'a') as out_file:
                    out_file.write(line)
            elif line_num in seq_lines:
                with open(out_path, 'a') as out_file:
                    out_file.write(line)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Sep  26 14:36:19 2019

@author: jonathan
"""
import pybullet as p
import numpy as np

import time
import sys
#some python interpreters need '.' added
sys.path.append(".")
import math

import pybullet_data
from Slider2 import Slider2
from Wheel import Wheel
from InertiaMeter import InertiaMeter
from Camera import Camera

class Simulation:

    def __init__(self, start_pos = [0, 0, 0.8], start_orientation = [-0.8923991008299614, 0.09904576054312739, 0.23911761839446172, 0.36964381061999463], urdfRootPath = '/urdf'):
        self.startPos = start_pos
        self.startOrientation = start_orientation
        self.urdfRootPath = urdfRootPath
        self.connect()
        self.reset()

    def buildJointNameToIdDict(self):
        nJoints = p.getNumJoints(self.robot)
        self.jointNameToId = {}
        for i in range(nJoints):
            jointInfo = p.getJointInfo(self.robot, i)
            self.jointNameToId[jointInfo[1].decode('UTF-8')] = jointInfo[0]

    def reset(self):
        #self.startOrientation = p.getQuaternionFromEuler([-2.3561944901795866, 0.5235987755999999, 0])
        self.robot = p.loadURDF("%s/dai.urdf" % self.urdfRootPath, self.startPos, self.startOrientation)
        self.buildJointNameToIdDict()

        self.slider1 = Slider2(self.jointNameToId["SliderMotor1"], self.robot)
        self.slider2 = Slider2(self.jointNameToId["SliderMotor2"], self.robot)
        self.slider3 = Slider2(self.jointNameToId["SliderMotor3"], self.robot)

        self.wheel1a = Wheel(self.jointNameToId["WheelMotor1a"], self.robot)
        self.wheel2a = Wheel(self.jointNameToId["WheelMotor2a"], self.robot)
        self.wheel3a = Wheel(self.jointNameToId["WheelMotor3a"], self.robot)
        self.wheel1b = Wheel(self.jointNameToId["WheelMotor1b"], self.robot)
        self.wheel2b = Wheel(self.jointNameToId["WheelMotor2b"], self.robot)
        self.wheel3b = Wheel(self.jointNameToId["WheelMotor3b"], self.robot)

        self.inertiaMeter = InertiaMeter(self.robot)

        self.camera1 = Camera()
        self.camera2 = Camera(((0,0,10), (0,0,0), (0,1,0)), (45.0, 1.0, 0.1, 10.1), 224, 224)

    def stepSimulation(self):
        p.stepSimulation()

    def connect(self):
        self.physicsClient = p.connect(p.GUI)# p.DIRECT for non-GUI // p.GUI for GUI
        p.resetSimulation()
        #p.setTimeStep(0.1)
        p.setRealTimeSimulation(1) #0 is off
        p.setAdditionalSearchPath(pybullet_data.getDataPath()) #sets path to use examples from bullet3/data
        planeId = p.loadURDF("%s/J-Plane.urdf" % self.urdfRootPath)
        p.setGravity(0,0,-9.80665)

    def disconnect(self):
        p.disconnect()


import sys

for line in sys.stdin:
    line=line.strip()
    line=line.split("\t")
    line=str(line[0]).split(",")

    if(len(line)>4):
        hospital=line[2]
        if('"' in hospital):
            hospital= hospital.replace('"', '')

        pin=line[-5][-3:-1]
        if('-' in pin):
                pin= pin.replace('-', '')
        elif('.' in pin):
                pin= pin.replace('.', '')
        elif(' ' in pin):
                pin= pin.replace(' ', '')
        elif(pin.isalpha()==False):
            if(len(pin)==1):
                pin= "41100"+pin
            elif(len(pin)==2):
                pin= "4110"+pin
            value=pin+"\t"+hospital
            print(value)

            with open("nursing.txt","a") as file:
                file.write(value+"\n")


import tweepy
#mainmenu
print("1.Retrive Tweets")
print("2.Count the followers")
print("3.Determine the sentiment")
print("4.Determine location,language and time zone")
print("5.compare tweets")
print("6.Analyre top usage")
print("7.Tweet a message")
print("8.exit")
choice=int(input("Enter your choice: "))
consumer_key='######'
cosumer_secret='########'
access_token='#########'
access_token_secret='##########'
auth=tweepy.OAuthHandler(consumer_key,cosumer_secret)
auth.set_access_token(access_token,access_token_secret)
api=tweepy.API(auth)
def search():
    user_search=input("enter any hash tag search")
    tweets=api.search('user_search',lang="en",count=5,tweet_mode="extended")
    print(tweets)
    for tweet in tweets:
         print("--------------")
         print(tweet.full_text)
         print("--------------")
def follow():
        user_id=input("enter any id to count the follower:")
        user=api.get_user(user_id)
        print(user.screen_name)
        print(user.friends_count)
        return
def status():
    status=input("enter any status")
    user_id = input("enter any id to count the follow:")
    api.update_status(status,user_id)
if choice==1:
    search()
if choice==2:
    follow()
if choice==3:
    status()
if choice==8:
    exit()


import sys
import ast
import math
import argparse
import pandas as pd
import numpy as np

def main(args):
    predictor = args.predictor
    columns = args.feature_columns
    csv_file_path = args.csv_file
    sf_name = args.scoring_function
    predictor_pos_value = args.predictor_positive_value
    maxv = args.maxv
    noise = args.noise

    # Validate columns
    if columns != '*':
        try:
            list_cols = ast.literal_eval(columns)
            list_col_ints = [int(x) for x in list_cols]
            columns = list_col_ints

        except Exception as e:
            print('Error: Please specify columns as a JSON array or *')
            sys.exit(0)

    # Load file
    csv_file = pd.read_csv(csv_file_path)

    # Transform strings to lower case
    csv_file = csv_file.applymap(lambda s:s.lower() if type(s) == str else s)

    # Transform floats to integers
    csv_file = csv_file.applymap(lambda f:math.floor(f*100000) if type(f) == float else f)

    # Extract features and predictor
    if columns == '*':
        features = csv_file.loc[:, csv_file.columns != predictor]
    else:
        features = csv_file.iloc[:, columns]
    labels = csv_file[predictor].astype(str)

    # Check whether the problem is multi class
    multi_class = False
    if len(labels.unique()) > 2:
        multi_class = True

    # Setup arrays
    background_knowledge = []
    scoring_function = []
    constants = []
    mode_declarations = ['#maxv({0}).'.format(maxv)]

    # Background Knowledge
    if multi_class:
        background_knowledge.append(':- class(X), class(Y), X < Y.')

    # Extract feature names and predicates
    df_feature_names = []
    predicates = []
    for col in features.columns.values:
        df_feature_names.append(col)
        predicates.append(col.replace(' ','_').lower())

    # Head mode declarations
    if not multi_class:
        mode_declarations.append('#modeh({0}).'.format(predictor_pos_value))
    else:
        mode_declarations.append('#modeh(class(const(class))).')

    # Construct constants and body mode declarations
    for p_idx, p in enumerate(predicates):
        mode_declarations.append('#modeb(1, {0}(const({0}))).'.format(p))
        unique_values = features[df_feature_names[p_idx]].unique()
        for u in unique_values:
            constants.append('#constant({0}, {1}).'.format(p, u))
    if multi_class:
        for l in np.unique(labels):
            constants.append('#constant(class, {0}).'.format(l))

    # Examples
    def construct_inner(row):
            ex_str_inner = ''
            for p_idx, p in enumerate(predicates):
                ex_str_inner += '\t{0}({1}).\n'.format(p, row[p_idx])
            return ex_str_inner

    # Coverage scoring function additions
    def add_to_cov_sf(row, row_idx):
        scoring_function.append('#bias("pos_example(eg(id{0})) :- not n(eg(id{0})).").'.format(row_idx))
        for p_idx, p in enumerate(predicates):
            scoring_function.append('#bias("n(eg(id{0})) :- in_body({1}(V)), V != {2}.").'.format(row_idx, p, row[p_idx]))

    def add_to_cov_sf_multi_class(row, row_idx, class_id):
        scoring_function.append('#bias("pos_example(eg(id{0})) :- in_head(class({1})), not n(eg(id{0})).").'.format(row_idx, class_id))
        for p_idx, p in enumerate(predicates):
            scoring_function.append('#bias("n(eg(id{0})) :- in_body({1}(V)), V != {2}.").'.format(row_idx, p, row[p_idx]))

    if not multi_class:
        mask = labels == str(predictor_pos_value)
        positive_ex_features = features[mask]
        negative_ex_features = features[~mask]

        def construct_example(row, row_idx, positive):
            if positive:
                ex = '#pos(eg(id{0})@{1}, {{ {2} }}, {{ }}, {{\n'.format(row_idx, noise, predictor_pos_value)
            else:
                ex = '#pos(eg(id{0})@{1}, {{ }}, {{ {2} }}, {{\n'.format(row_idx, noise, predictor_pos_value)

            ex += construct_inner(row)

            ex += f'}}).'

            if sf_name == 'cov' and positive:
                add_to_cov_sf(row, row_idx)
            return ex

        positive_examples = [construct_example(row, idx, True) for idx, row in enumerate(positive_ex_features[df_feature_names].values)]
        amount_neg_examples = len(positive_ex_features[df_feature_names].values)
        negative_examples = [construct_example(row, idx+amount_neg_examples, False) for idx, row in enumerate(negative_ex_features[df_feature_names].values)]

        examples = positive_examples + negative_examples
    else:
        unique_labels = np.unique(labels)
        def construct_multi_class_example(row, row_idx, positive):
            class_id = labels[row_idx]

            # Build non classes for exclusion set
            non_label_mask = unique_labels != class_id
            non_class_ids = unique_labels[non_label_mask]
            non_class_str = ''
            for ncid in non_class_ids:
                non_class_str += 'class({0}), '.format(ncid)
            non_class_str = non_class_str[:-2]

            if positive:
                ex = '#pos(eg(id{0})@{1}, {{ class({2}) }}, {{ {3} }}, {{\n'.format(row_idx, noise, class_id, non_class_str)
            else:
                ex = '#pos(eg(id{0})@{1}, {{ {3} }}, {{ class({2}) }}, {{\n'.format(row_idx, noise, class_id, non_class_str)

            ex += construct_inner(row)

            ex += f'}}).'

            if sf_name == 'cov' and positive:
                add_to_cov_sf_multi_class(row, row_idx, class_id)

            return ex

        examples = [construct_multi_class_example(row, idx, True) for idx, row in enumerate(features[df_feature_names].values)]

    # Scoring Function
    # Length function specified here
    # TODO others
    if sf_name == 'len':
        scoring_function.append('#bias("penalty(1, head(X)) :- in_head(X).").')
        scoring_function.append('#bias("penalty(1, body(X)) :- in_body(X).").')
    elif sf_name == 'cov':
        scoring_function.append('#bias("penalty(1,ID) :- pos_example(ID).").')
        scoring_function.append('#bias(":- not penalty(1, _).").')
        scoring_function.append('#bias("invert(1000).").')

    # Print to std out
    print('% Examples')
    for e in examples:
        print(e)

    print()
    print('% Background Knowledge')
    for b in background_knowledge:
        print(b)

    print()
    print('% Constants')
    for c in constants:
        print(c)

    print()
    print('% Mode Declarations')
    for m in mode_declarations:
        print(m)

    print()
    print('% Scoring Function')
    for s in scoring_function:
        print(s)

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Convert CSV data to a FastLAS learning task. The output is returned using stdout.')
    parser.add_argument('csv_file', help='The CSV file to convert')
    parser.add_argument('predictor', help='The name of the predictor column.')
    parser.add_argument('--feature_columns', default='*',
        help='Which columns to convert. Specify as a JSON array of 0 indexed '\
             'column indexes, e.g. "[0,1,4]". Alternatively, specify "*" for all columns (default: *)')
    parser.add_argument('--predictor_positive_value',
        help='The value of the predictor for a positive class in binary classification (default: accept).',
        default='accept')
    parser.add_argument('--scoring_function', help='The scoring function to use, len or cov (default: len).', default='len')
    parser.add_argument('--maxv', help='The maximum number of variables '\
        'to allow in the body of learned rules (default: 1).', default=1)
    parser.add_argument('--noise', help='The noise weight for each example (default: 1000).', default=1000)

    args = parser.parse_args()
    main(args)


from __future__ import print_function

class Ploca():
    # vanjski pozivi rade s brojevima stupaca 1-7
    # interno, radi se s indeksima

    OZNAKA_CPU = 1
    OZNAKA_IGRAC = 2

    def _dodaj_redak(self):
        self.polje.append([0] * 7)

    def _ukupno_redaka(self):
        return len(self.polje)

    def __init__(self):
        self.privremena_oznaka = 't'
        # napravi 6*7 polje nula
        self.polje = []
        i = 0
        while (i < 6):
            self._dodaj_redak()
            i += 1

    """ ******** VANJSKE ***************"""

    def odigraj_potez(self, oznaka, stupac):
        if stupac > 7 or stupac < 1:
            return

        # provjeri treba li povecati polje
        if self.polje[len(self.polje)-1][stupac - 1] != 0:
            self._dodaj_redak()

        #pronadi zadnju oznaku i dodaj iznad nje
        i = len(self.polje) -1
        while (i >=0):
            if self.polje[i][stupac - 1] != 0:
                self.polje[i + 1][stupac - 1] = oznaka
                break
            i -= 1
        else:
            # inace, ako nema, dodaj na dno polja
            self.polje[0][stupac - 1] = oznaka

    def ponisti_potez(self, stupac):
        if stupac > 7 or stupac < 1:
            return
        # ukloni oznaku
        i = self._ukupno_redaka() - 1
        while (i >= 0):
            if self.polje[i][stupac-1] != 0:
                self.polje[i][stupac-1] = 0
                break
            i -= 1

        # provjeri treba li smanjiti polje
        # ispusteno, zasad

    def ispisi_polje(self):
        for x in range(1,8):
            print(x, sep=' ', end=' ')
        print()
        print('-' * 14)

        for x in reversed(self.polje):
            i = 0
            while (i < len(x)):
                print(x[i], sep=' ', end=' ')
                i += 1
            print() # \n
        print()

    def provjeri_kraj(self, zadnji_potez):
        # vanjska! - prima 1-7
        # vraca poruku oblika (bool gotovo, oznaka)

        # provjera ulaznog argumenta
        if zadnji_potez<1 or zadnji_potez>7:
            return

        # ima li ista u stupcu?
        if self.polje[0][zadnji_potez - 1] == 0:
            return (False, 0)

        # pronadi indekse zadnje plocice (stupac vec imamo)
        i = len(self.polje) -1
        while(i >= 0):
            if self.polje[i][zadnji_potez - 1] != 0:
                break
            i -= 1

        indeksi = (i, zadnji_potez - 1)
        oznaka = self.polje[indeksi[0]][indeksi[1]]

        # provjera vodoravno
        (lista, indeks) = self._dohvati_redak(indeksi)
        if self._provjeri_listu(lista, indeks) == True:
            return (True, oznaka)

        # provjera okomito
        (lista, indeks) = self._dohvati_stupac(indeksi)
        if self._provjeri_listu(lista, indeks) == True:
            return (True, oznaka)

        # provjera dijagonalno /
        (lista, indeks) = self._dohvati_lijevu_dijagonalu(indeksi)
        if self._provjeri_listu(lista, indeks) == True:
            return (True, oznaka)
        # provjera dijagonalno \

        (lista, indeks) = self._dohvati_desnu_dijagonalu(indeksi)
        if self._provjeri_listu(lista, indeks) == True:
            return (True, oznaka)

        # inace
        return (False, 0)

    """ ********** Provjere dovrsenosti *********** """

    def _provjeri_listu(self, lista, indeks):
        # provjerava listu od indeksa u potrazi za 4 slijedna znaka

        oznaka = lista[indeks]

        # pomici se ulijevo koliko mozes
        i = indeks
        while i > 0 and lista[i - 1] == oznaka:
            i -= 1

        #print (lista, indeks, oznaka, i)

        # provjeri niz
        if lista[i : i + 4] == [oznaka] * 4:
            return True
        else:
            return False

    def _dohvati_redak(self, indeksi):
        # indeksi su redak i stupac elementa ciji redak trazimo
        # povratne vrijednosti su lista elemenata retka (udesno) i indeks
        # trazenog elementa u listi
        # manje vise ovo je tu samo zbog preglednosti i logicke organizacije

        if indeksi[0] < 0 or indeksi[0] >= self._ukupno_redaka():
            return

        redak = self.polje[indeksi[0]]
        indeks = indeksi[1]

        return (redak, indeks)

    def _dohvati_stupac(self, indeksi):
        # indeksi su redak i stupac elementa ciji stupac trazimo
        # povratne vrijednosti su lista elemenata stupca (odozdo) i indeks
        # trazenog elementa u listi

        if indeksi[0] < 0 or indeksi[0] >= self._ukupno_redaka():
            return

        # radi jednostavnosti elementu cemo pridjeliti privremenu oznaku

        oznaka = self.polje[indeksi[0]][indeksi[1]]
        self.polje[indeksi[0]][indeksi[1]] = self.privremena_oznaka

        stupac = []
        i = 0
        j = self._ukupno_redaka()
        while (i < j):
            stupac.append(self.polje[i][indeksi[1]])
            i += 1

        indeks = stupac.index(self.privremena_oznaka)
        self.polje[indeksi[0]][indeksi[1]] = oznaka
        stupac[indeks] = oznaka

        return (stupac, indeks)

    def _dohvati_lijevu_dijagonalu(self, indeksi):
        # lijeva dijagonala je /

        i = indeksi[0]
        j = indeksi[1]

        # privremena vrijednost
        oznaka = self.polje[indeksi[0]][indeksi[1]]
        self.polje[indeksi[0]][indeksi[1]] = self.privremena_oznaka

        dijagonala = []

        # spusti se do pocetka
        while i > 0 and j > 0:
            i -= 1
            j -= 1

        # napuni dijagonalu prema vrhu
        i_max = self._ukupno_redaka()
        j_max = 7
        while i < i_max and j < j_max:
            dijagonala.append(self.polje[i][j])
            i += 1
            j += 1

        # makni temp vrijednost
        indeks = dijagonala.index(self.privremena_oznaka)
        self.polje[indeksi[0]][indeksi[1]] = oznaka
        dijagonala[indeks] = oznaka

        return (dijagonala, indeks)

    def _dohvati_desnu_dijagonalu(self, indeksi):
        # desna dijagonala je \

        i = indeksi[0]
        j = indeksi[1]

        # privremena vrijednost
        oznaka = self.polje[indeksi[0]][indeksi[1]]
        self.polje[indeksi[0]][indeksi[1]] = self.privremena_oznaka

        dijagonala = []

        # spusti se do pocetka
        while i > 0 and j < 6:
            i -= 1
            j += 1

        # napuni dijagonalu prema vrhu
        i_max = self._ukupno_redaka()
        while i < i_max and j >= 0:
            dijagonala.append(self.polje[i][j])
            i += 1
            j -= 1

        # makni temp vrijednost
        indeks = dijagonala.index(self.privremena_oznaka)
        self.polje[indeksi[0]][indeksi[1]] = oznaka
        dijagonala[indeks] = oznaka

        return (dijagonala, indeks)

        """ **********************************************   """

    @staticmethod
    def ucitaj():
        fin = open('ploca.txt', 'r')
        fst = fin.read()

        lines = fst.split("\n")
        if lines[-1] == '':
             lines.pop() # zadnji element je neki zbrckani
        polje = []

        for x in reversed(lines):
            polje.append( [int(p) for p in x.split(" ")] )

        p = Ploca()

        for x in polje:
            i = 0
            while i < 7:
                if x[i] != 0:
                    p.odigraj_potez(x[i], i+1)
                i += 1

        return p


from django.conf.urls import url
from .views import *

urlpatterns = [
    url(r'^$', cart, name='cart'),
    url(r'^addtocart/', add_to_cart, name='add_to_cart'),
    url(r'^removecart/(\d+)/', remove_from_cart, name='removefromcart'),
    url(r'^inccart/(\d+)/', add_count_cart, name='addammount'),
    url(r'^deccart/(\d+)/', dec_count_cart, name='decammount'),
    url(r'^updatecart/(\d+)/', updatecart, name='updatecart'),
    url(r'^dumpcart/', dumpcart, name='emptycart'),
    url(r'^orders/', check_order, name='checkorders'),

]


import tkinter as tk
import time
import math

def update_timer():
    global countdown, circle, first_iteration
    if countdown >= 0:
        if first_iteration:
            first_iteration = False
            window.after(50, update_timer)  # Adjust the initial delay to 200 milliseconds
        else:
            angle = 360 - (360 * countdown / total_time)
            canvas.delete(circle)
            circle = canvas.create_arc(50, 50, 150, 150, start=90, extent=angle, outline="blue", width=5)
            countdown_label.config(text=str(countdown))
            countdown -= 1
            window.after(1000, update_timer)
    elif countdown == -1:
        canvas.delete(circle)
        countdown_label.config(text="Time's up")

def start_countdown():
    global total_time, countdown, circle, first_iteration
    total_time = int(entry.get())
    countdown = total_time
    countdown_label.config(text=str(countdown))
    first_iteration = True
    window.after(50, update_timer)  # Start the first iteration with a 200-millisecond delay

window = tk.Tk()
window.title("Countdown Timer")

entry_label = tk.Label(window, text="Enter Countdown Time:")
entry_label.pack()
entry = tk.Entry(window)
entry.pack()

start_button = tk.Button(window, text="Start Countdown", command=start_countdown)
start_button.pack()

canvas = tk.Canvas(window, width=200, height=200)
canvas.pack()

# Draw the initial circle
circle = canvas.create_arc(50, 50, 150, 150, start=90, extent=0, outline="blue", width=5)

# Create a label for the countdown
countdown_label = tk.Label(window, text="0", font=("Helvetica", 24))
countdown_label.pack()

first_iteration = True

window.mainloop()


# The bed
# def foo(value, words_database=[]):
#     words_database.append(value)
#     print(words_database)

# The good!
def foo(value, my_list = None):
    if my_list is None:
        my_list = []
    my_list.append(value)
    print(my_list)

foo(1)
foo(2, [3, 4])
foo(5, [])
foo(8, [1])
foo(10)


num1 = int(input(""))
num2 = int(input(""))
total = 0

for i in range(1, num1 + 1):
    for p in range(1, num2 + 1):
        if p + i == 10:
            total += 1

if total == 1:
    print(f"There is {total} way to get the sum 10.")
else:
    print(f"There are {total} ways to get the sum 10.")
#2 8, 3 7, 4 6, 5 5, 6 4


import pandas as pd
from keras.layers import Dense, Dropout, Activation, Input
from keras.models import Model

base = pd.read_csv('games.csv')

# Após a análise da base iremos excluir algumas colunas
# Vendas em outros lugares; vendas globais; desenvolvedor
base = base.drop('Other_Sales', axis = 1)
base = base.drop('Global_Sales', axis = 1)
base = base.drop('Developer', axis = 1)

# remove as linhas vazias
base = base.dropna(axis = 0)
# remove dados que não são seguros para a rede neural
base = base.loc[base['NA_Sales'] > 1]
base = base.loc[base['EU_Sales'] > 1]

# Temos nomes repetidos, por isso iremos remover esses dados
base['Name'].value_counts()
nome_jogos = base.Name
base = base.drop('Name', axis = 1)

# Separando a base de aprendizagem para as bases de resultados
previsores = base.iloc[:, [0,1,2,3,7,8,9,10,11]].values
venda_na = base.iloc[:, 4].values
venda_eu = base.iloc[:, 5].values
venda_jp = base.iloc[:, 6].values

from sklearn.preprocessing import LabelEncoder, OneHotEncoder
labelencoder = LabelEncoder()
previsores[:, 0] = labelencoder.fit_transform(previsores[:, 0])
previsores[:, 2] = labelencoder.fit_transform(previsores[:, 2])
previsores[:, 3] = labelencoder.fit_transform(previsores[:, 3])
previsores[:, 8] = labelencoder.fit_transform(previsores[:, 8])

# s 1 0
# r 0 1
from sklearn.compose import ColumnTransformer
onehotencoder = ColumnTransformer(transformers=[("OneHot", OneHotEncoder(), [0,2,3,8])],remainder='passthrough')
previsores = onehotencoder.fit_transform(previsores).toarray()

camada_entrada = Input(shape=(61,))
camada_oculta1 = Dense(units = 32, activation = 'sigmoid')(camada_entrada)
camada_oculta2 = Dense(units = 32, activation = 'sigmoid')(camada_oculta1)
camada_saida1 = Dense(units = 1, activation = 'linear')(camada_oculta2)
camada_saida2 = Dense(units = 1, activation = 'linear')(camada_oculta2)
camada_saida3 = Dense(units = 1, activation = 'linear')(camada_oculta2)

regressor = Model(inputs = camada_entrada,
                  outputs = [camada_saida1, camada_saida2, camada_saida3])
regressor.compile(optimizer = 'adam',
                  loss = 'mse')
regressor.fit(previsores, [venda_na, venda_eu, venda_jp],
              epochs = 5000, batch_size = 100)
previsao_na, previsao_eu, previsao_jp = regressor.predict(previsores)


import screen
import localocr
from wiki import Wiki
from google_search import GoogleSearch
def main():
    screen.shot()
    question_options = localocr.getText()
    wiki_search = Wiki(question_options['question'], question_options['options'])
    wiki_result = wiki_search.getResults()
    google_search = GoogleSearch()
    google_search_result = google_search.getResult(question_options['question'], question_options['options'])
    wiki_index = wiki_result.index(max(wiki_result))
    google_index = google_search_result.index(max(google_search_result))

    print("WIKI FOUND: " + str(question_options['options'][wiki_index]) + "\t Frequency of Option: " + str(wiki_result[wiki_index]))
    print("GOOGLE FOUND: " + str(question_options['options'][google_index]) +"\t Number of Results: " + str(google_search_result[google_index]))

if __name__== '__main__':
    main()