"""
Author: Matthias Grajewski (grajewski@fh-aachen.de) and Luis Hasenauer
This file is part of faultapprox-python (https://github.com/mgrajewski/faultapprox-python)
"""
from warnings import warn

import numpy as np
import numpy.typing as npt

from scipy.linalg import norm
from typing import Tuple

from checkPoints import component_closed
from computeClassification import compute_classification
from newtriplets import start_pairs, single_triplet_by_bisection
from entities.Entities import FaultApproxParameters, ProblemDescr
from geometry import estimate_curvature_2d, reg_ls

GROWTH_FACTOR = 1.5

def __eval_poly(t, coeffs, q_mat, x_mean):
    ndim = x_mean.shape[0]
    n_points = t.shape[0]

    deg = coeffs.shape[0] - 1

    int_mat = np.zeros((n_points, deg + 1))
    for i in range(deg + 1):
        int_mat[:, i] = t ** i

    x = int_mat @ coeffs

    if ndim == 2:
        x = np.vstack((t, x)).T @ q_mat.T + x_mean
    else:
        x = np.vstack((t, x, np.zeros(n_points, 1))).T @ q_mat + x_mean

    return x

def expand_2d(left_domain: npt.ArrayLike, idx_points: npt.ArrayLike, i_class: int, j_class: int,
              i_seg: int, class_vals: npt.ArrayLike,
              points_i_class: npt.ArrayLike, points_j_class: npt.ArrayLike, normal_vec_plane: npt.ArrayLike,
              problem_descr, fault_approx_pars: FaultApproxParameters, mode: int) -> \
                        Tuple[npt.ArrayLike, npt.ArrayLike, int, npt.ArrayLike, npt.ArrayLike, bool, bool]:
    """
    This function extends a fault line up to its end.

    We assume that the points on the fault line are ordered. There are two modes of operation: mode 0 for extending the
    fault line before its current starting point and mode 1 for extending it beyond its current end.
    Depending on mode, we take the first or last three points (if existing) of the fault line and fit an interpolating
    polynomial curve. We then create new points on this line such that their distance is approximately the distance of
    the ones already existing. These points and the corresponding auxiliary points are used to obtain new points on the
    fault line.
    We repeat this process until we leave the domain, or we intrude in a subdomain neither belonging to i_class nor
    j_class. In this case, we take the first of these points and discard all others of this kind. We adjust its
    parameter by bisection to obtain the maximal parameter where no third class is involved. This point marks
    approximately the end of the fault line.
    For any fault line, we store at which side of the domain the fault line leaves the domain if so in the array
    left_domain. This information is necessary for constructing the approximating polygons of any subdomain in function
    ReconstructSubdomains.

    The information of left_domain is coded as follows:
    1: left boundary,
    2: right boundary,
    3: bottom boundary,
    4: top boundary,
    0: fault line end inside the domain

    This routine is applied for expanding fault surfaces in 3D as well. Here, we take some points from the fault
    surface and treat them as points defining a curve to continue. Again, we need points near the curve on both sides
    of the fault line. Using just a vector normal to the curve as in 2D does not work. Therefore, we need additional
    information which is provided by normal_vec_plane.

    Args:
        left_domain (npt.ArrayLike): See note.
        idx_points (npt.ArrayLike): Array of point indices according to their position on the fault line.
        i_class (int): We deal with the i_segs-th part of the fault line between classes i_class und j_class.
        j_class (int)
        i_seg (int)
        class_vals (npt.ArrayLike):
            Array containing the class values. These are not necessarily the class indices. Imagine that
            f(\Omega) = {1,2,5}. Then, class_vals = [1,2,5], whereas the class indices range from 0 to 2.
            Shape: (nclasses,)
        points_i_class (npt.ArrayLike): The fault points near the current fault line in i_class.
        points_j_class (npt.ArrayLike): The fault points near the current fault line in j_class.
        normal_vec_plane (npt.ArrayLike):
        problem_descr (ProblemDescr):
            Object containing all problem-relevant parameters. We refer to its documentation in Entities.py for details.
        fault_approx_pars (FaultApproxParameters):
            Object containing all parameters relevant for the fault detection algorithm. We refer to its documentation
            in Entities.py for details.
        mode (int): Mode of operation (0 or 1, see description above).

    Returns:
        Tuple[ npt.ArrayLike, npt.ArrayLike, int, npt.ArrayLike, npt.ArrayLike, bool]:
            idx_points_enlarged: array of point indices according to their position on the fault line, but enlarged by
            the indices of the points added
            resort: If the fault line is closed, the start and the end of the fault line in fact overlap. This requires
            a complete resorting of the points. We do not resort inside this routine but indicate the requirement by
            setting resort = true.
            points_added: If really points have been added, this variable is True.

    Raises:
        Exception: If there is an invalid mode in expand_2d.
    """
    num_points_on_curve = 4

    points_added = False

    # maximal number of expansion cycles (to prevent endless loops)
    max_cycles = 500

    finished = False
    bdomain_left = False

    num_points = points_i_class.shape[0]

    if num_points < 2:
        warn("Not enough triplets provided for expanding the fault line.")
        resort = False
        points_added = False
        idx_points_enlarged = idx_points
        n_points = num_points
        return idx_points_enlarged, left_domain, n_points, points_i_class, points_j_class, resort, points_added

    # dist_vec contains actual distance between consecutive points.
    dist_vec_aux = (points_i_class[idx_points[0:num_points - 1], 0] -
                    points_i_class[idx_points[1:num_points], 0]) ** 2 + \
                   (points_i_class[idx_points[0:num_points - 1], 1] - points_i_class[idx_points[1:num_points], 1]) ** 2

    dist_vec_aux = np.sqrt(dist_vec_aux)
    dist_vec = np.zeros(num_points, dtype=float)
    dist_vec[idx_points] = np.concatenate(([dist_vec_aux[0]],
                                           0.5 * (dist_vec_aux[0:num_points - 2] + dist_vec_aux[1:num_points - 1]),
                                           [dist_vec_aux[-1]]))

    # requirement for complete resorting (not necessary by default)
    resort = False

    # for computing valid point pairs as starting values for bisection
    alpha = fault_approx_pars.alpha

    # Points closer than eps are regarded as identical.
    eps = fault_approx_pars.eps

    n_points, ndim = points_i_class.shape

    # auxiliary vector
    aux_vec_loc = np.zeros(3)

    # index array for the extended set points on the surface (at the beginning, this is the original set of points)
    idx_points_enlarged = idx_points

    # number of cycles in expansion
    ncycles = 0

    while not finished and ncycles < max_cycles:

        ncycles = ncycles + 1

        # extend at the beginning
        if mode == 0:
            idx_set = np.arange(min(n_points, num_points_on_curve) - 1, -1, -1)

        # extend at the end
        elif mode == 1:
            idx_set = np.arange(max(0, n_points - num_points_on_curve), n_points)
        else:
            raise Exception("Invalid mode of operation in expand_2d.")

        num_points_to_interpolate = idx_set.shape[0]

        points_on_fault = 0.5 * (points_i_class[idx_points_enlarged[idx_set], :] +
                                 points_j_class[idx_points_enlarged[idx_set], :])

        # extrapolate current points
        new_point_on_fault, avg_dist, extra_par, data_pars, q_mat, x_mean, coeffs = extrapolate_on_fault_line(
            points_on_fault, 1, fault_approx_pars)

        finished_surf_point = False

        i_try = 0

        while i_try <= 3 and not finished_surf_point:

            i_try += 1

            extra_par = 1.0/2**(i_try-1)*(extra_par - data_pars[num_points_to_interpolate - 1]) + \
                data_pars[num_points_to_interpolate - 1]

            new_point_on_fault = __eval_poly(extra_par, coeffs, q_mat, x_mean)

            # distance of the new point relative to the average distance of the existing points
            rel_dist = norm(new_point_on_fault - points_on_fault[num_points_to_interpolate - 1, :], 2, axis=1) / \
                avg_dist

            # logical array indicating which points on the extrapolated fault line are outside the domain
            outside_domain = np.any(np.logical_or(new_point_on_fault < problem_descr.x_min,
                                                  new_point_on_fault > problem_descr.x_max), axis=1)

            # compute normal vector
            aux_vec_loc[0:ndim] = new_point_on_fault - points_on_fault[num_points_to_interpolate - 1, :]

            normal_vec_loc = np.cross(aux_vec_loc, normal_vec_plane)
            normal_vec_loc = normal_vec_loc[0:ndim] / norm(normal_vec_loc, axis=0)

            # this is purely heuristic
            step_size = np.maximum(0.95 * fault_approx_pars.abstol_bisection,
                                   0.5 * np.minimum(alpha * avg_dist,
                                                    3.0 * fault_approx_pars.abstol_bisection * (rel_dist ** 2 + 1)))

            step_size = step_size[:, np.newaxis]

            point_left = new_point_on_fault + step_size * normal_vec_loc
            point_left = np.minimum(np.maximum(problem_descr.x_min + eps, point_left), problem_descr.x_max - eps)

            point_right = new_point_on_fault - step_size * normal_vec_loc
            point_right = np.minimum(np.maximum(problem_descr.x_min + eps, point_right), problem_descr.x_max - eps)

            if not outside_domain:
                aux_arr = compute_classification(np.concatenate((point_left, point_right), axis=0), problem_descr)

                class_point_left = aux_arr[0]
                class_point_right = aux_arr[1]

                outside_sub = (class_point_left != class_vals[i_class] and class_point_left != class_vals[j_class]) or \
                              (class_point_right != class_vals[i_class] and class_point_right != class_vals[j_class])

            else:
                class_point_left = -1
                class_point_right = -1

                outside_sub = False

            # If there are some points on the curve which are neither in i_class nor in j_class, we have reached the
            # boundary of the domain or the subdomain and have therefore reached the last cycle of the expansion
            # loop. As points outside the valid domain have class -1, this includes the case the domain has been left.
            # Note that if the "central" points from which the left and the right points was generated is outside the
            # domain, we automatically assume that this holds for both the left and the right points. Concentrating here
            # on just point_left therefore does not harm.
            if outside_domain:
                finished = True
                bdomain_left = True

                # Approximate the intersection of the extrapolated line and the domain boundary. We know that
                # data_pars[num_points_to_interpolate-1] belongs to a point inside the domain. Therefore, it is a lower
                # bound for the parameter of the intersection. On the other hand, the point corresponding to extra_par
                # does not belong to the domain. Therefore, this is an upper bound for the parameter.
                x_new, iedge = extra_new_point_on_domain_bdry(data_pars[num_points_to_interpolate-1], extra_par,
                                                              points_on_fault[-1, :], new_point_on_fault[0], coeffs,
                                                              q_mat, x_mean, problem_descr, fault_approx_pars)

                # update_vec is not necessarily the normal vector of the extrapolated fault line in x_new, so we call it
                # update_vec.
                update_vec = np.cross(normal_vec_plane, aux_vec_loc)

                # from x_new, we need to create a pair of points as starting point for bisection. We need to ensure that
                # the line segment between these two points is parallel to the appropriate domain boundary. We do this
                # by modifying update_vec.
                if iedge == 1:
                    # x2 is minimal (bottom boundary/front surface of cube)
                    update_vec[1] = 0.0
                elif iedge == 2:
                    # x1 is maximal (right boundary/right surface of cube)
                    update_vec[0] = 0.0
                elif iedge == 3:
                    # x2 is maximal (top boundary/back surface of cube)
                    update_vec[1] = 0.0
                elif iedge == 4:
                    # x1 is minimal (left boundary/left surface of cube)
                    update_vec[0] = 0.0
                elif iedge == 5:
                    # x3 is minimal (bottom surface of cube)
                    update_vec[2] = 0.0
                elif iedge == 6:
                    # x3 is maximal (top surface of cube)
                    update_vec[2] = 0.0

                rel_dist = norm(x_new - points_on_fault[num_points_to_interpolate - 1, :]) / avg_dist
                update_vec = update_vec[0:ndim]

                # heuristic control of step length
                update_vec = update_vec / norm(update_vec) * np.max((0.95 * fault_approx_pars.abstol_bisection,
                                                                     3.0 * fault_approx_pars.abstol_bisection *
                                                                     (rel_dist ** 2 + 1.0)))

                x_new_left = x_new + update_vec
                x_new_right = x_new - update_vec

                # Ensure that the new points near the boundary are really inside the domain.
                x_new_left = np.maximum(np.minimum(x_new_left, problem_descr.x_max - eps), problem_descr.x_min + eps)
                x_new_right = np.maximum(np.minimum(x_new_right, problem_descr.x_max - eps), problem_descr.x_min + eps)

                [class_left, class_right] = compute_classification(np.vstack((x_new_left, x_new_right)), problem_descr)
                left_domain[i_class, j_class, i_seg] = iedge

                # Find starting values for bisection.
                point_pairs, idx_ok, class_points_ok = start_pairs(x_new_left.reshape((-1, ndim)),
                                                                   x_new_right.reshape((-1, ndim)),
                                                                   np.array([class_left]), np.array([class_right]),
                                                                   class_vals[i_class], class_vals[j_class],
                                                                   problem_descr)
                class_points_ok = class_points_ok[0]

                if idx_ok:
                    point_left, point_right, finished_surf_point = single_triplet_by_bisection(point_pairs[0, 0:ndim],
                                                                                point_pairs[0,
                                                                                ndim:2 * ndim],
                                                                                class_points_ok[0],
                                                                                class_points_ok[1],
                                                                                problem_descr,
                                                                                fault_approx_pars)

                left_domain[i_class, j_class, i_seg] = iedge

            # There is no indication that the fault line has left the domain. Therefore, we can continue to expand
            # unless it ends inside the domain.
            elif outside_sub:
                finished = True
                bdomain_left = False

                # Did we expand the fault line beyond its end? If so, either point_left or point_right belongs neither
                # to i_class nor to j_class.
                # In this case, take the last known point in the subdomain and the first known outside, discard all
                # others and find the end of the extrapolated fault line by bisection.
                # However, this is a heuristic approach, as it may happen that e.g. due to insufficient distance to the
                # extrapolation curve, both points belong to the same class.
                #                 %                      II
                #                 %        -------------------------->     x point_left
                #                 % ______x________x________x_____         x extrapolated point
                #                 %                               \--__    x point_right
                #                 %              I                 \   ---__
                #                 %                                 \       ---__
                #                 %                                  \    III    ---__
                #
                # Then, we do not detect that we extrapolated the fault line beyond its end. We will fail to find a
                # valid starting pair such that prolongation stops and no point is added, even if the end of the fault
                # line is not yet reached.

                # A fault line ends inside the domain by intersecting a subdomain with a third class.
                # We aim at finding the intersection of the extrapolated fault line with that third subdomain using
                # bisection.

                # A lower bound t_min for the parameter of the intersection is the parameter of the last known
                # point inside the domain.
                t_min = data_pars[num_points_to_interpolate - 1]

                # Correspondingly, the parameter of the first point on the extrapolated fault line which is known
                # to be outside the subdomain is an upper bound for the parameter of the intersection.
                t_max = extra_par[0]

                # This point is known to be inside the subdomain.
                x_min = __eval_poly(np.array([t_min]), coeffs, q_mat, x_mean)[0]
                x_max = __eval_poly(np.array([t_max]), coeffs, q_mat, x_mean)[0]
                dist = norm(x_max - x_min)

                x_min_aux_all = np.empty((0, 2*ndim))
                x_min_all = np.empty((0, ndim))
                class_min_all = np.empty((0, ndim), dtype=int)

                iiter = 1
                while iiter < 10 and dist > fault_approx_pars.abstol_bisection:

                    t_new = 0.5 * (t_min + t_max)

                    x_new = __eval_poly(np.array([t_new]), coeffs, q_mat, x_mean)[0]

                    aux_vec_loc[0:ndim] = x_new - x_min
                    normal_vec_loc = np.cross(aux_vec_loc, normal_vec_plane)
                    normal_vec_loc = normal_vec_loc[0:ndim]/np.linalg.norm(normal_vec_loc, 2)
                    rel_dist = norm(points_on_fault[num_points_to_interpolate-1, :] - x_new, 2)/avg_dist

                    # this is purely heuristic
                    step_size = max(0.95 * fault_approx_pars.abstol_bisection, 0.5 *
                                    min(alpha * avg_dist,
                                        3.0 * fault_approx_pars.abstol_bisection * (rel_dist ** 2 + 1)))

                    x_new_left = x_new + step_size*normal_vec_loc
                    x_new_right = x_new - step_size*normal_vec_loc

                    x_new_left = np.maximum(np.minimum(x_new_left, problem_descr.x_max - eps),
                                            problem_descr.x_min + eps)
                    x_new_right = np.maximum(np.minimum(x_new_right, problem_descr.x_max - eps),
                                             problem_descr.x_min + eps)

                    class_new = compute_classification(np.vstack((x_new_left, x_new_right)), problem_descr)

                    if np.any(np.logical_and(class_new != class_vals[i_class], class_new != class_vals[j_class])):
                        t_max = t_new
                        x_max = x_new
                    else:
                        t_min = t_new
                        x_min = x_new
                        x_min_aux_all = np.vstack((x_min_aux_all, np.hstack((x_new_left, x_new_right))))
                        x_min_all = np.vstack((x_min_all, x_min))
                        class_min_all = np.vstack((class_min_all, class_new))

                    dist = norm(x_max - x_min)

                    iiter += 1

                iiter = 0
                finished_surf_point = False

                while iiter < x_min_all.shape[0] and not finished_surf_point:

                    iiter += 1
                    x_new = x_min_all[-iiter]

                    # We need a point which is inside the current subdomain, and we know that x_min is inside the
                    # domain.
                    # However, if t_min is (almost) at its initial value, x_min is in fact the last known point on the
                    # fault. It does not make sense to add this point again.
                    if norm(x_new - points_on_fault[num_points_to_interpolate-1]) > \
                            fault_approx_pars.abstol_bisection:

                        x_new_left = x_min_aux_all[-1, 0:ndim]
                        x_new_right = x_min_aux_all[-1, ndim: 2*ndim]

                        class_new[0] = compute_classification(x_new, problem_descr)[0]

                        x_new_suitable = class_new[0] == class_vals[i_class] or class_new[0] == class_vals[j_class]

                        if x_new_suitable:
                            # x_new belongs to a valid point pair
                            if class_new[0] == class_vals[i_class] and class_min_all[-iiter, 0] == class_vals[j_class]:
                                x_new_left = x_new
                                class_point_left = class_new[0]
                                x_new_right = x_min_aux_all[-iiter, 0:ndim]
                                class_point_right = class_min_all[-iiter, 0]
                            elif class_new[0] == class_vals[j_class] and class_min_all[-iiter, 0] == \
                                    class_vals[i_class]:
                                x_new_left = x_new
                                class_point_left = class_new[0]
                                x_new_right = x_min_aux_all[-iiter, 0:ndim]
                                class_point_right = class_min_all[-iiter, 0]
                            elif class_new[0] == class_vals[j_class] and class_min_all[-iiter, 1] == \
                                    class_vals[i_class]:
                                x_new_left = x_new
                                class_point_left = class_new[0]
                                x_new_right = x_min_aux_all[-iiter, ndim:2*ndim]
                                class_point_right = class_min_all[-iiter, 1]
                            elif class_new[0] == class_vals[i_class] and class_min_all[-iiter, 1] == \
                                    class_vals[j_class]:
                                x_new_left = x_new
                                class_point_left = class_new[0]
                                x_new_right = x_min_aux_all[-iiter, ndim:2*ndim]
                                class_point_right = class_min_all[-iiter, 1]

                            point_left, point_right, finished_surf_point = single_triplet_by_bisection(
                                x_new_left, x_new_right, class_point_left, class_point_right, problem_descr,
                                fault_approx_pars)
                            class_points_ok = np.array([class_point_left, class_point_right], dtype=int)

                left_domain[i_class, j_class, i_seg] = 0

            # expansion continues
            else:

                # Find starting values for bisection.
                point_pairs, idx_ok, class_points_ok = start_pairs(point_left,
                                                                   point_right,
                                                                   np.array([class_point_left]),
                                                                   np.array([class_point_right]),
                                                                   class_vals[i_class],
                                                                   class_vals[j_class],
                                                                   problem_descr)

                class_points_ok = class_points_ok[0]

                # If no point has been found at all, stop. If we have no indication that the fault line left the domain,
                # we must assume that it ended inside.
                if idx_ok:
                    point_left, point_right, finished_surf_point = single_triplet_by_bisection(point_pairs[0, 0:ndim],
                                                                                point_pairs[0,
                                                                                ndim:2 * ndim],
                                                                                class_points_ok[0],
                                                                                class_points_ok[1],
                                                                                problem_descr,
                                                                                fault_approx_pars)
                else:
                    left_domain[i_class, j_class, i_seg] = 0
                    finished = True

            # Add the new point if appropriate.
            if finished_surf_point:

                # Compute the distance to the currently last point on the fault line.
                # If this distance is tiny, replace this point by the new one.
                last_current_point = points_on_fault[-1, :]
                if class_points_ok[0] == class_vals[i_class]:
                    dist_to_new_point = norm(last_current_point - point_left)
                else:
                    dist_to_new_point = norm(last_current_point - point_right)

                # The new point is sufficiently far away from the last known one: add it.
                if dist_to_new_point > avg_dist * fault_approx_pars.min_dist_factor:
                    i_idx_add = n_points
                    n_points += 1

                    if mode == 0:
                        idx_points_enlarged = np.hstack((n_points-1, idx_points_enlarged))
                    elif mode == 1:
                        idx_points_enlarged = np.hstack((idx_points_enlarged, n_points-1))

                # The new point is almost the last known one: replace the last known one by the new one and stop
                # expanding.
                else:
                    i_idx_add = idx_points_enlarged[idx_set[-1]]
                    finished = True

                if class_points_ok[0] == class_vals[i_class]:
                    if i_idx_add == points_i_class.shape[0]:
                        points_i_class = np.concatenate((points_i_class, point_left.reshape(1, -1)), axis=0)
                        points_j_class = np.concatenate((points_j_class, point_right.reshape(1, -1)), axis=0)
                    else:
                        points_i_class[i_idx_add] = point_left
                        points_j_class[i_idx_add] = point_right
                else:
                    if i_idx_add == points_i_class.shape[0]:
                        points_i_class = np.concatenate((points_i_class, point_right.reshape(1, -1)), axis=0)
                        points_j_class = np.concatenate((points_j_class, point_left.reshape(1, -1)), axis=0)
                    else:
                        points_i_class[i_idx_add] = point_right
                        points_j_class[i_idx_add] = point_left

                # If the fault line appears to start or end inside the domain, it may be closed. We test this here.
                if left_domain[i_class, j_class, i_seg] <= 0:
                    closed_comp = component_closed(points_i_class, idx_points_enlarged, dist_vec, n_points, mode)

                    if closed_comp:
                        # If the fault component is closed, it does not start or end at the domain boundary. We finish
                        # expanding this fault line and state that it starts and ends inside the domain.
                        left_domain[i_class, j_class, i_seg] = 0

                        # The new point is in fact somewhere between existing points. This requires resorting.
                        resort = True
                        finished = True

                points_added = True
                if i_idx_add < dist_vec.shape[0]:
                    dist_vec[i_idx_add] = dist_to_new_point
                else:
                    dist_vec = np.hstack((dist_vec, dist_to_new_point))

            # No suitable point for adding found: try again. We assume in this case that the fault line starts or ends
            # inside the domain unless found otherwise.
            else:
                if left_domain[i_class, j_class, i_seg] < 0 and not bdomain_left:
                    left_domain[i_class, j_class, i_seg] = 0

    return idx_points_enlarged, left_domain, n_points, points_i_class, points_j_class, resort, points_added

def extrapolate_on_fault_line(points_on_curve: npt.ArrayLike, num_points_new: int,
                              fault_approx_pars: FaultApproxParameters) -> Tuple[npt.ArrayLike, float,
                                                                                npt.ArrayLike, npt.ArrayLike,
                                                                                npt.ArrayLike, npt.ArrayLike,
                                                                                npt.ArrayLike]:
    """This function computes num_points_new points on an extrapolated fault line. It is constructed using the points
    given in points_on_curve and fitting a polynomial in local coordinates. As the points are not known exactly, we do
    not interpolate exactly, but penalise the second derivative in a least-squares approximation. Following Morozov, we
    choose the regularisation such that the maximal residual is approx. fault_approx_pars.abstol_bisection.
    These points are constructed such that their distance is the minimum of GROW_FACTOR*the average distance of the
    existing points, fault_approx_pars.max_dist_for_surface_points and the requirement that the deviation from a
    straight line is at most (approximately) fault_approx_pars.err_max.

    Args:
        points_on_curve (npt.ArrayLike):
            Points on the curve representing a fault line (must be ordered). Shape: (npoints, ndim)
        num_points_new (int):
            Number of new points on the curve aka fault line constructed by extrapolation.
        fault_approx_pars (FaultApproxParameters):
            Class containing all relevant parameters for our method of detecting fault lines.

    Returns:
        new_points_on_curve (npt.ArrayLike):
            cartesian coordinates of the new points. Shape: (number of new points, ndim)
        avg_dist (float):
            average distance of the points on the fault line used for extrapolation
        extra_pars (npt.ArrayLike):
            Parameters aka local coordinates of the new points on the polynomial curve.
        data_pars (npt.ArrayLike):
            Parameters aka local coordinates of the points in points_on_curve.
        q_mat (npt.ArrayLike):
            orthogonal matrix; the local coordinates are computed by xloc = q_mat@x + x_mean
        x_mean (npt.ArrayLike):
            origin of the local coordinate system; the local coordinates are computed by xloc = q_mat@x + x_mean
        coeffs (npt.ArrayLike):
            coefficients of the extrapolating polynomial
    """

    num_points_on_curve, ndim = points_on_curve.shape

    # we need at least two points for extrapolation
    if num_points_on_curve < 2:
        warn('Too few points to extrapolate')
        new_points_on_curve = np.array([0, 2])
        avg_dist = 0.0
        data_pars = np.array([])
        extra_pars = np.array([])
        q_mat = np.zeros((ndim, ndim))
        x_mean = np.zeros(0)
        coeffs = np.array([])
        return new_points_on_curve, avg_dist, extra_pars, data_pars, q_mat, x_mean, coeffs

    # average distance of the points on the fault line used for extrapolation
    avg_dist = norm(points_on_curve[num_points_on_curve - 1] - points_on_curve[0], 2) / (num_points_on_curve - 1)

    x_mean = (1.0 / num_points_on_curve) * np.ones(num_points_on_curve).dot(points_on_curve)

    points_shifted = points_on_curve - x_mean

    q_mat = np.linalg.svd(points_shifted)[2].T
    points_rot = points_shifted @ q_mat

    # We limit the step size for extrapolation based upon curvature. It does not make sense to extrapolate very far
    # with a lot of numerical amount and to adaptively refine in between the current and the new points anyway
    # afterwards. To do so, we compute the step size which would lead to the maximal admissible deviation
    # fault_approx_pars.err_max from a straight line segment. This is a natural upper bound for the step size in
    # extrapolation.
    #
    # The maximal deviation d of a curve with curvature c from an interpolating line segment with length l is
    #   d = 1/4 c l^2 + 1/16 c^3 l^4 + h.o.t
    # Rearranging leads to l = 2/c^2(sqrt{1+4cd} - 1), which is numerically unstable if cd is small. We set (cl)^2 = v
    # and search for the bigger root of the quadratic equation
    #   v^2 + 4 v - 16cd = 0, aka v^2 + 2pv - q = 0.
    # According to Vieta, q = vmin*vmax with the two roots vmin and vmax. Therefore,
    #   vmin = - (2 + sqrt{4+ 16cd}) = -2 (1 + sqrt{1 + 4cd}),
    # which is stable to evaluate and vmax = -16cd/vmin, and ultimately lmax = sqrt{vmax}/c. Inserting vmax yields
    #   lmax = sqrt{-16cd/vmin}/c = 4 sqrt{d/(-c vmin)}

    # Estimating curvature is possible only for more than two points on the fault line and two dimensions.
    if num_points_on_curve > 2 and ndim == 2:
        curv = estimate_curvature_2d(points_shifted, num_points_on_curve - 1,
                                     fault_approx_pars.abstol_bisection)[0] + \
               estimate_curvature_2d(points_shifted, num_points_on_curve - 2, fault_approx_pars.abstol_bisection)[0]

        if curv > 1e-10:
            lmax = 2.0 * (1 + np.sqrt(1.0 + 4 * curv * fault_approx_pars.err_max))
            lmax = 4.0 * np.sqrt(fault_approx_pars.err_max / (curv * lmax))
        else:
            lmax = 1e10

    # Limiting step size based upon curvature does not work, if only two points on the fault line are known.
    else:
        lmax = 1e10

    step_size = min([fault_approx_pars.max_dist_for_surface_points, GROWTH_FACTOR * avg_dist, lmax])

    if np.all(points_rot[0:num_points_on_curve - 1, 0] < points_rot[1:num_points_on_curve, 0]):
        extra_pars = points_rot[num_points_on_curve - 1, 0] + step_size * np.arange(1, num_points_new + 1)
    elif np.all(points_rot[0:num_points_on_curve - 1, 0] > points_rot[1:num_points_on_curve, 0]):
        extra_pars = points_rot[num_points_on_curve - 1, 0] - step_size * np.arange(1, num_points_new + 1)

    # It is not possible to extrapolate the fault line by any function respecting the order of the points. This is
    # usually the case if the points are on a curve with very strong curvature. Therefore, we just take the two last
    # points and perform linear extrapolation.
    else:
        num_points_on_curve = 2
        if points_rot[num_points_on_curve - 2, 0] < points_rot[num_points_on_curve - 1, 0]:
            extra_pars = points_rot[num_points_on_curve - 1, 0] + step_size * np.arange(1, num_points_new + 1)
        else:
            extra_pars = points_rot[num_points_on_curve - 1, 0] - step_size * np.arange(1, num_points_new + 1)

    int_mat = np.zeros((num_points_on_curve, num_points_on_curve))
    penalty_mat = np.zeros((num_points_on_curve, num_points_on_curve))
    for i in range(num_points_on_curve):
        int_mat[:, i] = points_rot[0: num_points_on_curve, 0] ** i
        penalty_mat[:, i] = i * (i - 1) * points_rot[0: num_points_on_curve, 0] ** (np.max((0, i - 2)))

    # Regularization by penalizing the second derivative is pointless if only two points exist so far: Then, we perform
    # linear extrapolation, and its second derivative is zero anyway.
    if num_points_on_curve > 2:
        penalty_mat = penalty_mat.T @ penalty_mat
        coeffs = reg_ls(int_mat, penalty_mat, points_rot[:, 1], fault_approx_pars.abstol_bisection, 2)
    else:
        coeffs = np.linalg.solve(int_mat, points_rot[0:num_points_on_curve, 1])

    new_points_on_curve = __eval_poly(extra_pars, coeffs, q_mat, x_mean)
    data_pars = points_rot[:, 0]

    # It may happen that the true distance of the extrapolated points to the existing ones is too large, as the arc
    # length on the curve is much greater than the distance of the parameters. In this case, we adjust the parameters
    # accordingly. For the sake of simplicity, we consider the true distance of the last existing to the first new
    # point only.
    true_dist = np.linalg.norm(new_points_on_curve - points_on_curve[num_points_on_curve - 1, :], axis=1)

    if true_dist[0] > 1.1 * fault_approx_pars.max_dist_for_surface_points:
        tmax = extra_pars[0]
        tmin = data_pars[num_points_on_curve - 1]

        iiter = 1

        # The maximal number of iterations should never be reached in practical examples. If so, the adjusted values are
        # at least better than the original ones.
        tnew = 0.0
        while iiter < 20:
            tnew = 0.5 * (tmin + tmax)
            xnew = __eval_poly(np.array([tnew]), coeffs, q_mat, x_mean)

            dist = norm(xnew - points_on_curve[num_points_on_curve - 1, :])
            res = dist - fault_approx_pars.max_dist_for_surface_points

            # if the distance to the existing points is max_dist_for_surface_points up to 10%
            if np.abs(res) < 0.1 * fault_approx_pars.max_dist_for_surface_points:
                break
            else:
                if dist > fault_approx_pars.max_dist_for_surface_points:
                    tmax = tnew
                else:
                    tmin = tnew

            iiter = iiter + 1

        extra_pars = data_pars[num_points_on_curve - 1] + \
            np.arange(1, num_points_new + 1) * (tnew - data_pars[num_points_on_curve - 1])
        new_points_on_curve = __eval_poly(extra_pars, coeffs, q_mat, x_mean)

    return new_points_on_curve, avg_dist, extra_pars, data_pars, q_mat, x_mean, coeffs

def extra_new_point_on_domain_bdry(t_min: float, t_max: float, x_min: npt.ArrayLike, x_max: npt.ArrayLike,
                                   coeffs: npt.ArrayLike, q_mat: npt.ArrayLike, x_mean: npt.ArrayLike,
                                   problem_descr: ProblemDescr, fault_approx_pars: FaultApproxParameters) ->\
        Tuple[npt.ArrayLike, int]:
    """
    This function aims at finding the intersection of an extrapolated fault line with the domain boundary using
    bisection. We exploit that the domain is either a rectangle or a cuboid.
    We assume that the fault line has been extrapolated with a polynomial in local coordinates. We approximate the
    parameter value t of the intersection of that polynomial with the domain boundary by bisection.
    For doing so, we need a lower and upper bound t_min and t_max for that parameter t along with the corresponding
    points x_min and x_max.

    Args:
        t_min (float):
            lower bound for the parameter value of the intersection
        t_max (float):
            upper bound for the parameter value of the intersection
        x_min (npt.ArrayLike):
            point on the extrapolating polynomial corresponding to t_min given in global coordinates
        x_max (npt.ArrayLike):
            point on the extrapolating polynomial corresponding to t_max given in global coordinates
        coeffs (npt.ArrayLike):
            coefficients of the extrapolating polynomial
        q_mat (npt.ArrayLike):
            orthogonal matrix describing the local coordinate system
        x_mean (npt.ArrayLike):
            origin of the local coordinate system
        problem_descr (ProblemDescr):
            Object containing all problem-relevant parameters. We refer to its documentation in Entities.py for details.
        fault_approx_pars (FaultApproxParameters):
            Object containing all parameters relevant for the fault detection algorithm. We refer to its documentation
            in Entities.py for details.

    Returns:
        x_new (npt.ArrayLike):
            approximate intersection of fault line and domain boundary
        iedge (int):
            index of the domain boundary part, at which the fault line leaves the domain
    """

    # indices of the domain edges (in 2D):
    #        y /\
    #          │     3
    #          ├───────────┐
    #          │           │
    #        4 │           │ 2
    #          │           │
    #          └───────────│─────> x
    #                1
    #
    # indices of the domain surfaces (in 3D)
    #                   6          3              z
    #                   ┊         ╱               △      y
    #               ┌───┊───────╱───┐             │     ╱
    #             ╱ │   ┊     ╱   ╱ │             │   ╱
    #           ╱   │   v   ↙   ╱   │             │ ╱
    #         ╱     │         ╱     │             └─────────> x
    #        ┌───────────────┐      │
    #  4┈┈┈┈┈│┈>    │        │    <┈┈┈┈┈┈┈2
    #        │      └--------│------┘
    #        │     ╱         │     ╱
    #        │   ╱   ↗  △    │   ╱
    #        │ ╱   ╱    ┊    │ ╱
    #        └───╱──────┊────┘
    #          ╱        ┊
    #         1         5

    i_aux_min = np.array([4, 1, 5], dtype=int)
    i_aux_max = np.array([2, 3, 6], dtype=int)

    # Points closer than eps are considered identical.
    eps = fault_approx_pars.eps

    ndim = x_min.shape[0]

    # euclidean distance of these points
    dist = norm(x_max - x_min)
    iiter = 1

    while iiter < 10:

        # The factor 0.5 in the following condition is just a safety factor (iterating one time more does not increase
        # the number of function evaluations).
        if dist < 0.5 * fault_approx_pars.abstol_bisection:
            break
        else:
            t_new = 0.5 * (t_min + t_max)
            x_new = __eval_poly(np.array([t_new]), coeffs, q_mat, x_mean)[0]

            if np.any(np.logical_or(x_new < problem_descr.x_min, x_new > problem_descr.x_max)):
                t_max = t_new
                x_max = x_new
            else:
                t_min = t_new
                x_min = x_new

            # The distance will approximately halve per iterations step, but only approximately (the
            # extrapolation is not necessarily a straight line).
            dist = norm(x_max - x_min)

        iiter = iiter + 1

    # index of the edge or facet at which the fault line leaves the domain
    iedge = 0

    for i in range(0, ndim):
        if x_max[i] > problem_descr.x_max[i]:
            iedge = i_aux_max[i]
        elif x_max[i] < problem_descr.x_min[i]:
            iedge = i_aux_min[i]

    # The approximation of the intersection is not necessarily inside the domain, so we enforce this here.
    x_new = np.maximum(np.minimum(x_new, problem_descr.x_max - eps), problem_descr.x_min + eps)

    # Moreover, ensure that the new point is up to eps on the domain boundary (it is somewhere inside near a boundary
    # up to abstol_bisection, but we can do better).
    if iedge == 1:
        # x2 is minimal (bottom boundary/front surface of cube)
        x_new[1] = problem_descr.x_min[1] + eps
    elif iedge == 2:
        # x1 is maximal (right boundary/right surface of cube)
        x_new[0] = problem_descr.x_max[0] - eps
    elif iedge == 3:
        # x2 is maximal (top boundary/back surface of cube)
        x_new[1] = problem_descr.x_max[1] - eps
    elif iedge == 4:
        # x1 is minimal (left boundary/left surface of cube)
        x_new[0] = problem_descr.x_min[0] + eps
    elif iedge == 5:
        # x3 is minimal (bottom surface of cube)
        x_new[2] = problem_descr.x_min[1] + eps
    elif iedge == 6:
        # x3 is maximal (top surface of cube)
        x_new[2] = problem_descr.x_max[0] - eps

    return x_new, iedge

def test2d():
    """Testfunction 2d.

    """
    from tests.test_funcs.TestFunc2D import func_fd_2d_cl3_c0_01
    problem_descr = ProblemDescr()
    problem_descr.test_func = func_fd_2d_cl3_c0_01
    problem_descr.x_min = np.array([0, 0])
    problem_descr.x_max = np.array([1, 1])
    fault_approx_pars = FaultApproxParameters()
    fault_approx_pars.max_dist_for_surface_points = 0.05
    fault_approx_pars.abstol_bisection = 0.001
    fault_approx_pars.num_points_local = 10

    points_on_curve = np.array([[0, 0], [1, 1], [2, 4]])
    num_points_new = 2
    new_points_on_curve, avg_dist, extra_pars, data_pars, q_mat, x_mean, coeffs = \
        extrapolate_on_fault_line(points_on_curve, num_points_new, fault_approx_pars)

    print(new_points_on_curve)

def test3d():
    """Testfunction 3d.
    """
    from tests.test_funcs.TestFunc2D import func_fd_2d_cl2_c1_03
    left_domain = np.zeros((2, 2), dtype=int)
    idx_points_surf_ordered = np.array([1, 2]) - 1
    i_class = 1
    j_class = 2
    i_seg = 1
    class_vals = np.array([1, 2])
    points_i_class = np.array([[0.028, 0.295, 0.336], [0.001, 0.295, 0.337]])
    points_j_class = np.array([[0.028, 0.295, 0.335], [0, 0.295, 0.336]])
    normal_vec_plane = np.array([-1, 0, 0])
    problem_descr = ProblemDescr()
    problem_descr.test_func = func_fd_2d_cl2_c1_03
    problem_descr.x_min = np.array([0, 0, 0])
    problem_descr.x_max = np.array([1, 1, 1])
    fault_approx_pars = FaultApproxParameters()
    fault_approx_pars.max_dist_for_surface_points = 0.05
    fault_approx_pars.num_points_local = 5
    fault_approx_pars.abstol_bisection = 0.001
    mode = 1
    idx_points_surf_ordered_enlarged, left_domain, n_points, points_i_class, points_j_class, resort, points_added =\
        expand_2d(left_domain, idx_points_surf_ordered, i_class, j_class, i_seg, class_vals,
                  points_i_class, points_j_class, normal_vec_plane, problem_descr, fault_approx_pars,
                  mode)
    print(idx_points_surf_ordered_enlarged, left_domain, n_points, points_i_class, points_j_class, resort, points_added,
          sep="\n\n")

if __name__ == '__main__':
    test2d()


#random lib, while loop, for loop을 이용해서 유저와 input을 주고 받으며 자유로운 interaction 하나 만들기
from tkinter import *
import random
from random import sample

ui = Tk() # class  = book, we use ui variable to clarify
ui.title("lotto generator") # our ui's title = calculator
ui.resizable(False, False) # parameter = 2
ui.geometry("280x300")
ui.configure(background = 'green')
display = Entry(ui, width=28, justify='right')
display.grid(row = 0, column = 0 , columnspan = 4, pady=10, padx=4)

def cancel_all():
    display.delete(0, END)
def lotto():
    Val = eval(Entry.get(display))
    lotte = random.sample(range(1,1001), Val)
    for each in range(1,1001):
        if lotte == 40:
            display.delete(0,END)
            display.insert(END, "You are the first!")
        elif lotte == 46:
            display.delete(0,END)
            display.insert(END, "You are second!")
        elif lotte == 682:
            display.delete(0,END)
            display.insert(END, "You are third")
        else:
            display.delete(0,END)
            display.insert(END, "You fail! Try again!")
    print(lotte)
def buttonPress(value):#parameter = 메개변수
    """value이라는 변수 (1, 2, 3, + , / ...)을 받고 display.inser("end", value)"""
    display.insert(END, value)
btn_CE = Button(ui, text='CE', width = 5, command = cancel_all)
btn_e = Button(ui, text='=', width = 5, command = lotto)
#Number
btn_zero = Button(ui, text='0', width = 5, command = lambda: buttonPress('0')) #얘내들을 바꿔줘야해
btn_one = Button(ui, text='1', width = 5, command = lambda: buttonPress('1'))#근데 여기서는 규칙상 '1' string 문자형태로 써야함
btn_Two = Button(ui, text='2', width = 5, command =  lambda: buttonPress('2'))
btn_Three = Button(ui, text='3', width = 5, command =  lambda: buttonPress('3'))
btn_four = Button(ui, text='4', width = 5, command =  lambda: buttonPress('4'))
btn_five = Button(ui, text='5', width = 5, command =  lambda: buttonPress('5'))
btn_six = Button(ui, text='6', width = 5, command =  lambda: buttonPress('6'))
btn_seven = Button(ui, text='7', width = 5, command =  lambda: buttonPress('7'))
btn_eight = Button(ui, text='8', width = 5, command =  lambda: buttonPress('8'))
btn_nine = Button(ui, text='9', width = 5, command =  lambda: buttonPress('9'))
#+, -, /, *

#button grid
#computer always think that 0 = one 1 = scond
btn_CE.grid(row = 1, column= 2)
btn_e.grid(row = 1, column = 3)
btn_zero.grid(row = 5, column = 0)
btn_one.grid(row = 2, column = 0)
btn_Two.grid(row = 2, column = 1)
btn_Three.grid(row = 2, column = 2)
btn_four.grid(row = 3, column = 0)
btn_five.grid(row = 3, column = 1)
btn_six.grid(row = 3, column = 2)
btn_seven.grid(row = 4, column = 0)
btn_eight.grid(row = 4, column = 1)
btn_nine.grid(row = 4, column = 2)
ui.mainloop() # continuously loop


import torch
import numpy

w = torch.tensor([1.],requires_grad=True)
x = torch.tensor([2.],requires_grad=False)
a = torch.add(x,w)
b = torch.add(w,1)
y = torch.mul(a,b)

#f=(x+w)*(w+1) = w2 + (x+1)w + x
#df(w) = 2w + x +1
y.backward()
print(w.grad)
print(x.grad)

# -------------
w = torch.tensor([1.],requires_grad=True)
x = torch.tensor([2.],requires_grad=True)

for i in range(3):
    a = torch.add(x,w)
    b = torch.add(w,1)
    y = torch.mul(a,b)

    y.backward()
    print(w.grad)
    w.grad.zero_()

# --------------
a = torch.rand(3,4)
b = torch.rand(4)
x = a+b


import os
import argparse
import scipy.io as sio
import numpy as np

import tensorflow as tf
from tensorflow.keras.layers import *
from tensorflow.keras import backend as K

import sklearn.metrics
from skimage.transform import resize
import matplotlib.pyplot as plt
from itertools import cycle
from utils import *
# make sure that you have the focal loss package installed
from focal_loss import SparseCategoricalFocalLoss, BinaryFocalLoss

import matplotlib

def add_arguments(parser):
    parser.add_argument('--num_epochs', type=int, default=200, help='number of epochs in training')
    parser.add_argument('--lr_init', type=float, default=0.0001, help='learning rate')
    parser.add_argument('--gamma', type=float, default=2, help='gamma factor used for focal loss')
    parser.add_argument('--model_continue', action='store_true', help='restart training by loading the most recent saved weights')
    parser.add_argument('--model_savedir',type=str, default='/shared/anastasio-s1/MRI/xiaohui/mouse_optical/sleep-stage/paper/checkpoint', help='path to save trained weights')
    parser.add_argument('--gradcam_label', type=int, default=0, help='states users want to compute for GradCAM')

    return parser

class model:
    def __init__(self, project):
        self.project = project
        self.cp_callbacks = tf.keras.callbacks.ModelCheckpoint(
                filepath=os.path.join(self.project.params.model_savedir, "ckpt_{epoch}"),
                monitor="val_loss",
                save_best_only=True,
                save_weights_only=True,
                verbose=0,
                save_freq='epoch')

        if self.project.params.loss =='binary_ce':
            self.loss = 'binary_crossentropy'
        elif self.project.params.loss == 'binary_focal':
            self.loss = BinaryFocalLoss(gamma=2)
        elif self.project.params.loss =='categorical_ce':
            self.loss = 'categorical_crossentropy'
        elif self.project.params.loss =='categorical_focal':
            self.loss = SparseCategoricalFocalLoss(gamma=self.project.params.gamma, class_weight=([0.25, 0.25, 0.25]))
        print(self.loss)

    def cnn2d(self):
        """2D CNN for adjacency matrix classification"""
        inputs = Input(shape=(self.project.params.num_frames, self.project.params.num_frames, len(self.project.params.brain_idx)))

        # block 1
        x = Conv2D(filters=32, kernel_size=3, strides=1, padding='same')(inputs)
        x = LeakyReLU()(x)
        x = MaxPool2D(pool_size=(2,2))(x)

        # block 2
        x = Conv2D(filters=64, kernel_size=3, strides=1, padding='same')(x)
        x = LeakyReLU()(x)
        x = MaxPool2D(pool_size=(2,2))(x)

        # block 3
        x = Conv2D(filters=128, kernel_size=3, strides=1, padding='same')(x)
        x = LeakyReLU()(x)
        x = GlobalAveragePooling2D()(x)

        if self.project.params.num_classes == 2:
            outputs = Dense(units=1, activation='sigmoid')(x)
        else:
            outputs = Dense(units=self.project.params.num_classes, activation='softmax')(x)
        model = tf.keras.Model(inputs=inputs, outputs=outputs)
        print(model.summary())
        return model

    def train(self, train_dataset, val_dataset, num_epochs):

        # Create directory to save model weights
        if not os.path.exists(self.project.params.model_savedir):
            os.makedirs(self.project.params.model_savedir)

        # Distribute data in multi-GPUs in training
        if self.project.params.mode == 'train':
            strategy = tf.distribute.MirroredStrategy()
            print('Number of devices: {}'.format(strategy.num_replicas_in_sync))

            with strategy.scope():
                self.model = self.cnn2d()
                print(self.loss)
                self.model.compile(optimizer=tf.keras.optimizers.Adam(self.project.params.lr_init), loss=self.loss, metrics=['accuracy'])

                # Resume training from saved checkpoints
                if self.project.params.model_continue:
                    self.model.load_weights(tf.train.latest_checkpoint(self.project.params.model_savedir))
                    print('... Resume training from latest checkpoint ...')
                    self.model.trainable = True

        self.model.fit(train_dataset,
                epochs=num_epochs,
                verbose=1,
                validation_data=val_dataset,
                validation_freq=1,
                callbacks=[self.cp_callbacks],
                workers=4,
                use_multiprocessing=True)

    def test(self, dataset):
        # Use single GPU in inference
        self.model = self.cnn2d()
        self.model.compile(optimizer=tf.keras.optimizers.Adam(self.project.params.lr_init), loss=self.loss, metrics=["accuracy"])
        self.model.load_weights(tf.train.latest_checkpoint(self.project.params.model_savedir))
        self.model.trainable = False
        print('... Trained model loaded ...')

        y_trues = []
        y_scores = []
        y_preds = []

        if self.project.params.num_classes == 2:
            for x, y in dataset:
                y_trues.extend(y.numpy())
                y_score = self.model.predict(x)
                y_scores.extend(y_score)
                y_preds.extend(y_score>0.5)

        else:
            for x, y in dataset:
                y_trues.extend(y.numpy())
                y_score = self.model.predict(x)
                y_scores.extend(y_score)
                y_preds.extend(np.argmax(y_score, axis=1))

        results = {
                'y_trues': y_trues,
                'y_preds': y_preds,
                'y_scores': y_scores
                }

        if self.project.params.mode == 'test_subjectwise':
            sio.savemat(os.path.join('../Results', f"{self.project.params.test_mice}_{self.project.params.mice_flist}_{self.project.params.timelen}s.mat"), results)
            print('Saved results ...')
        else:
            sio.savemat(os.path.join('../Results', self.project.params.dataset[11:] + f"_class{self.project.params.num_classes}_test.mat"), results)
            print('Saved results ...')
        plot_roc(np.squeeze(y_trues), np.squeeze(y_scores), self.project.params.loss, self.project.params.num_classes)

    def gradcam(self, dataset):
        # function to compute GradCAM
        import matplotlib.cm as cm
        from tensorflow import keras
        from IPython.display import Image, display
        import matplotlib.pyplot as plt

        self.model = self.cnn2d()
        self.model.compile(optimizer=tf.keras.optimizers.Adam(self.project.params.lr_init), loss='categorical_crossentropy', metrics=[tf.keras.metrics.AUC(multi_label=True), 'accuracy'])
        self.model.load_weights(tf.train.latest_checkpoint(self.project.params.model_savedir))
        self.model.trainable = False
        print('... 2D Trained Model Loaded ...')

        last_conv_layer = self.model.get_layer("leaky_re_lu_2")
        last_conv_layer_model = keras.Model(self.model.inputs, last_conv_layer.output)

        classifier_input = keras.Input(shape=last_conv_layer.output.shape[1:])
        x = classifier_input
        classifier_layer_names = ["global_average_pooling2d", "dense"]
        for layer_name in classifier_layer_names:
            x = self.model.get_layer(layer_name)(x)
        classifier_model = keras.Model(classifier_input, x)

        n = 0
        gradcams = []
        ams = []
        for idx, (x, y) in enumerate(dataset):
            with tf.GradientTape() as tape:
                last_conv_layer_output = last_conv_layer_model(x)
                tape.watch(last_conv_layer_output)

                preds = classifier_model(last_conv_layer_output)
                top_pred_index = tf.argmax(preds[0])
                top_class_channel = preds[:, top_pred_index]

            if top_pred_index.numpy() == self.project.params.gradcam_label and top_pred_index.numpy() == y:

                grads = tape.gradient(top_class_channel, last_conv_layer_output)

                pooled_grads = tf.reduce_mean(grads, axis=(0,1,2))
                last_conv_layer_output = last_conv_layer_output.numpy()[0]
                pooled_grads = pooled_grads.numpy()

                for i in range(pooled_grads.shape[-1]):
                        last_conv_layer_output[:,:,i] *= pooled_grads[i]

                heatmap = np.mean(last_conv_layer_output, axis=-1)
                heatmap = np.maximum(heatmap, 0)/np.max(heatmap)
                heatmap = np.uint8(255*heatmap)
                heatmap = resize(heatmap, (self.project.params.num_frames, self.project.params.num_frames))

                gradcams.append(heatmap)
                ams.append(x.numpy())

                n = n+1
                print(n)

                if n == 5: # define a number of GradCAM to save
                    sio.savemat(f"../Results/gradcam/2020_config14_{self.project.params.test_mice}_class{self.project.params.num_classes}_label{self.project.params.gradcam_label}_{self.project.params.timelen}s_area${self.project.params.brain_area}.mat",{"gradcam": gradcams, "am":ams})
                    print("Grad-CAM saved...")
                    break

            #superimposed_img = jet_heatmap * 0.4 #+ img
            #superimposed_img = keras.preprocessing.image.array_to_img(superimposed_img)

            #plt.imshow(superimposed_img)
            #plt.show()


import datetime
import numpy as np
import pandas as pd
import mysql.connector
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import Dense, LSTM

# Mengambil data dari database MySQL
mydb = mysql.connector.connect(
  host="localhost",
  user="root",
  password="",
  database="pengujian"
)

# Mengambil data dari database MySQL
mycursor = mydb.cursor()
mycursor.execute("SELECT * FROM honda")
data = mycursor.fetchall()

# Membuat DataFrame dari data MySQL
df = pd.DataFrame(data, columns=['tahun', 'minat', 'trand', 'penjualan'])

# Mengubah tipe data kolom 'sales' menjadi float
df['penjualan'] = df['penjualan'].astype(float)

# Menormalkan data menggunakan MinMaxScaler
scaler = MinMaxScaler(feature_range=(0, 1))
scaled_data = scaler.fit_transform(df[['penjualan']])

# Membuat fungsi untuk membuat dataset dengan time steps
def create_dataset(data, time_steps=1):
    X_data, Y_data = [], []
    for i in range(len(data)-time_steps):
        X_data.append(data[i:(i+time_steps), 0])
        Y_data.append(data[i+time_steps, 0])
    return np.array(X_data), np.array(Y_data)

# Membuat dataset untuk training dan testing
time_steps = 12 # Contoh: menggunakan 12 bulan sebelumnya untuk memprediksi penjualan bulan berikutnya
data, _ = create_dataset(scaled_data, time_steps)

# Reshape data agar sesuai dengan format input LSTM
data = np.reshape(data, (data.shape[0], data.shape[1], 1))

# Membuat model LSTM
model = Sequential()
model.add(LSTM(50, return_sequences=True, input_shape=(time_steps, 1)))
model.add(LSTM(50))
model.add(Dense(1))
model.compile(loss='mean_squared_error', optimizer='adam')
model.fit(data, scaled_data[time_steps:], epochs=100, batch_size=16, verbose=2)

# Menggunakan model untuk memprediksi penjualan kendaraan bermotor selama 3 tahun ke depan
last_data = scaled_data[-time_steps:]
future_predictions = []
for i in range(36): # Memprediksi selama 3 tahun ke depan
    input_data = np.reshape(last_data, (1, time_steps, 1))
    prediction = model.predict(input_data)
    future_predictions.append(prediction[0][0])
    last_data = np.append(last_data[1:], prediction[0][0])

# Invers scaling untuk mendapatkan hasil yang sebenarnya
future_predictions = scaler.inverse_transform(np.array(future_predictions).reshape(-1, 1))

# Menambahkan nilai tahun ke data prediksi
last_year = df['tahun'].max()
future_years = [str(last_year+datetime.timedelta(i+1)) for i in range(len(future_predictions))]
future_predictions_df = pd.DataFrame(columns=['tahun', 'penjualan'])
future_predictions_df['tahun'] = future_years
future_predictions_df['penjualan'] = float('nan')

for i, prediction in enumerate(future_predictions):
    future_predictions_df.loc[future_predictions_df['tahun']==future_years[i], 'penjualan'] = prediction

# Gabungkan DataFrame df dengan future_predictions_df
df = pd.concat([df, future_predictions_df])

# Cetak DataFrame hasil gabungan
print(df)


import pytest
import os
import json

@pytest.fixture(scope='module', autouse=True)
def setup_test(utils):
    # test multiple repos in one file
    repofile_foo = os.path.join(utils.config['repo_path'], 'yum.repos.d', 'foo.repo')
    utils.edit_config(
        {
            'name': 'Foo Repo',
            'enabled': '1',
            'baseurl': 'http://pkgs.foo.org/foo'
        },
        section='foo',
        filename=repofile_foo
    )
    utils.edit_config(
        {
            'name': 'Foo Debug Repo',
            'enabled': '0',
            'baseurl': 'http://pkgs.foo.org/foo-debug'
        },
        section='foo-debug',
        filename=repofile_foo
    )
    repofile_bar = os.path.join(utils.config['repo_path'], 'yum.repos.d', 'bar.repo')
    utils.edit_config(
        {
            'name': 'Bar Repo',
            'enabled': '1',
            'baseurl': 'http://pkgs.bar.org/bar'
        },
        section='bar',
        filename=repofile_bar
    )
    yield
    teardown_test(utils)

def teardown_test(utils):
    os.remove(os.path.join(utils.config['repo_path'], 'yum.repos.d', 'foo.repo'))
    os.remove(os.path.join(utils.config['repo_path'], 'yum.repos.d', 'bar.repo'))
    os.remove(os.path.join(utils.config['repo_path'], "yum.repos.d", 'test.repo'))
    os.remove(os.path.join(utils.config['repo_path'], "yum.repos.d", 'test1.repo'))

def find_repo(repolist, id):
    for repo in repolist:
        if repo['Repo'] == id:
            return True
    return False

def test_repolist(utils):
    ret = utils.run(['tdnf', 'repolist'])
    assert ret['retval'] == 0

# -j returns a list of repos. Easier to parse.
def test_repolist_json(utils):
    ret = utils.run(['tdnf', 'repolist', '-j'])
    assert ret['retval'] == 0
    repolist = json.loads("\n".join(ret['stdout']))
    assert find_repo(repolist, 'foo')
    assert not find_repo(repolist, 'foo-debug')
    assert find_repo(repolist, 'bar')

# disabled repo should be listed when we enable it on the command line
def test_repolist_json_enable_one(utils):
    ret = utils.run(['tdnf', 'repolist', '--enablerepo=foo-debug', '-j'])
    assert ret['retval'] == 0
    repolist = json.loads("\n".join(ret['stdout']))
    assert find_repo(repolist, 'foo')
    assert find_repo(repolist, 'foo-debug')
    assert find_repo(repolist, 'bar')

def test_repolist_all(utils):
    ret = utils.run(['tdnf', 'repolist', 'all'])
    assert ret['retval'] == 0

def test_repolist_json_all(utils):
    ret = utils.run(['tdnf', 'repolist', 'all', '-j'])
    assert ret['retval'] == 0
    repolist = json.loads("\n".join(ret['stdout']))
    assert find_repo(repolist, 'foo')
    assert find_repo(repolist, 'foo-debug')
    assert find_repo(repolist, 'bar')

def test_repolist_enabled(utils):
    ret = utils.run(['tdnf', 'repolist', 'enabled'])
    assert ret['retval'] == 0

def test_repolist_disabled(utils):
    ret = utils.run(['tdnf', 'repolist', 'disabled'])
    assert ret['retval'] == 0

def test_repolist_json_disabled(utils):
    ret = utils.run(['tdnf', 'repolist', 'disabled', '-j'])
    assert ret['retval'] == 0
    repolist = json.loads("\n".join(ret['stdout']))
    assert not find_repo(repolist, 'foo')
    assert find_repo(repolist, 'foo-debug')
    assert not find_repo(repolist, 'bar')

def test_repolist_invalid(utils):
    ret = utils.run(['tdnf', 'repolist', 'invalid_repo'])
    assert ret['retval'] == 901

# memcheck
def test_repolist_memcheck(utils):
    ret = utils.run_memcheck(['tdnf', 'repolist'])
    assert ret['retval'] == 0

# multiple repoid
def test_multiple_repoid(utils):
    reponame = 'test.repo'
    repofile_test = os.path.join(utils.config['repo_path'], 'yum.repos.d', reponame)
    utils.edit_config(
        {
            'name': 'Test Repo',
            'enabled': '1',
            'baseurl': 'http://pkgs.test.org/test'
        },
        section='test',
        filename=repofile_test
    )

    reponame = 'test1.repo'
    repofile_test1 = os.path.join(utils.config['repo_path'], 'yum.repos.d', reponame)
    utils.edit_config(
        {
            'name': 'Test Repo',
            'enabled': '1',
            'baseurl': 'http://pkgs.test1.org/test1'
        },
        section='test',
        filename=repofile_test1
    )

    ret = utils.run(['tdnf',
                     '--disablerepo=*', '--enablerepo={}'.format(reponame),
                     'makecache'])
    assert ret['retval'] == 1037


from random import random
class ListNode:
    def __init__(self, val):
        self.val = val
        self.next = None

class Solution(object):

    def __init__(self, head: ListNode):
        self.head = head

    def get_random(self, head):
        curr = self.head
        counter = 1
        choice = -1

        while curr is not None:
            if random() < 1/choice:
                choice = curr.val
            curr = curr.next
            counter += 1
        return choice


# PROGRAM 3-5: LOAN QUALIFIER_1
# This program determines whether a bank custeomer
# qualifies for a loan.

MIN_SALARY = 30000.0 # the minimum annual salray
MIN_YEARS = 2        # the minimum years on the job
print()
# Get the customer's annual salary.
salary = float(input('Enter your annual salary: '))

# Get the number of years on the current job.
years_on_job = int(input('Enter the number of' +
                        ' years emplyed: '))

# Determine whether the customer qualifies.
if salary >= MIN_SALARY:
    if years_on_job >= MIN_YEARS:
        print('You qualify for the loan.')
    else:
        print('You must have been employed',
        'for at least', MIN_YEARS,
        'years to qualify.')
else:
        print('You must earn at least $',
        format(MIN_SALARY, ',.2f'),
        ' per year to qualify.', sep='')
print()
# Program 3-6: GRADER.PY
# This program gets a numeric test score from
# user and displays the corresponding letter grade.

# Named constants to represent the grade thresholds
A_Score = 90
B_Score = 80
C_Score = 70
D_Score = 60

# Get a test score from the user.
score = int(input('Enter your test score: '))

# Determine the grade.

if score >= A_Score:
    print('Your grade is A.')
else:
    if score >= B_Score:
        print('Your grade is B.')
    else:
     if score >= C_Score:
         print('Your grade is C.')
     else:
        if score >= D_Score:
            print('Your grade is D.')
        else:
            print('Your grade is F.')


import numpy as np
import matplotlib.pyplot as plt
from skimage import io, img_as_float
from skimage.color import rgb2gray

###############################################################################
# all functions ###############################################################
###############################################################################

def plot_images(images, titles=None):
    """This function takes a list of images with
    their titles as arguments and plots side by side."""
    n_ims = len(images)
    if titles is None:
        titles = ['(%d)' % i for i in range(1, n_ims + 1)]
    fig = plt.figure()
    n = 1
    for image, title in zip(images, titles):
        a = fig.add_subplot(1, n_ims, n)  # Subplot
        if image.ndim == 2:  # Is image grayscale?
            plt.gray()
        plt.imshow(image)
        a.set_title(title)
        a.set_xlabel('Number of pixels')
        a.set_ylabel('Number of pixels')
        n += 1
    fig.set_size_inches(np.array(fig.get_size_inches()) * n_ims)
    plt.show()

class generateDataOnClick:
    def __init__(self, verbose=0):
        self.position_on_click_accumulator = []
        self.verbose = verbose

        if self.verbose > 0:
            print(" ")
            print("To use this function: clik on the positions you are")
            print("interested in, then once finished close the plot.")
            print(" ")

    def position_on_click(self, event):
        # x, y = event.x, event.y
        if event.button == 1:
            if event.inaxes is not None:
                if self.verbose > 0:
                    print('data coords: ' + str(event.xdata) + " , " + str(event.ydata))
                self.position_on_click_accumulator.append((event.xdata, event.ydata))
                plt.axvline(event.xdata, color='r')
                plt.show()

    def return_positions(self):
        return self.position_on_click_accumulator

def compute_distance(tuple_1, tuple_2):
    return np.sqrt((tuple_1[0] - tuple_2[0])**2 + (tuple_1[1] - tuple_2[1])**2)

###############################################################################
# processing of one image #####################################################
###############################################################################

# Name of the file ------------------------------------------------------------
filename = 'DSC_0476.JPG'
upload_image = io.imread(filename)  # Upload the image
image_rgb = img_as_float(upload_image[0])  # Convert the image to a np array
image_gray = rgb2gray(image_rgb)  # Convert to gray image

# Show both color and gray images ---------------------------------------------
plot_images(images=[image_rgb[300:, :], image_gray[300:, :]],
            titles=['RGB image', 'Gray image'])

# select two points on the image for performing calibration -------------------
plt.figure()
plt.imshow(image_gray)
plt.xlabel('Number of pixels')
plt.ylabel('Number of pixels')

generate_data_on_click_object = generateDataOnClick(verbose=1)

plt.connect('button_press_event', generate_data_on_click_object.position_on_click)
plt.show()

selected_positions_pixels = generate_data_on_click_object.return_positions()

print(selected_positions_pixels)

# perform calibration ---------------------------------------------------------
# distances in the real world are given in m
distance_real_world = 0.07
distance_pixels = compute_distance(selected_positions_pixels[0], selected_positions_pixels[1])

pixels_to_real_world = distance_real_world / distance_pixels

print("pixels_to_real_world: " + str(pixels_to_real_world))

# measure the length of a droplet ---------------------------------------------
plt.figure()
plt.imshow(image_gray)
plt.xlabel('Number of pixels')
plt.ylabel('Number of pixels')

generate_data_on_click_object = generateDataOnClick(verbose=1)

plt.connect('button_press_event', generate_data_on_click_object.position_on_click)
plt.show()

selected_positions_pixels = generate_data_on_click_object.return_positions()

length_droplet_meters = compute_distance(selected_positions_pixels[0], selected_positions_pixels[1]) * pixels_to_real_world
print("length droplet (m): " + str(length_droplet_meters))


import datetime
import random
import sys
from interfaces.inicial import *
from interfaces.cadastroAluno import *
from interfaces.cadastroInst import *
from interfaces.cadastroCurso import *
from interfaces.listaInstitutos import *
from interfaces.listaCursos import *
from interfaces.listaAlunos import *
from interfaces.geraCarteirinha import *
from interfaces.listaCarteirinha import *
from interfaces.consultarCarteirinha import *
from PyQt5.QtWidgets import *
import sqlite3

class Main(QMainWindow, Ui_SistemaCarteirinha):

    def __init__(self, parent=None):
        super().__init__(parent)
        super().setupUi(self)
        self.btnCadastroAluno.clicked.connect(self.abrirjanelacadastroaluno)
        self.btnCadastoInsti.clicked.connect(self.abrircadastroinstituto)
        self.cadastrarCurso.clicked.connect(self.abrircadastrocurso)
        self.btnListInsti.clicked.connect(self.abrirlistainst)
        self.btnListCursos.clicked.connect(self.abrirlistacursos)
        self.btnListAluno.clicked.connect(self.abrirlistaalunos)
        self.btnGerarCarteirinha.clicked.connect(self.gerarcarteirinha)
        self.btnListCarteirinhas.clicked.connect(self.listarcarteirinhas)
        self.btnConsultarCarteirinha.clicked.connect(self.consultarcarteirinha)

    def abrircadastroinstituto(self):
        stack.setCurrentIndex(1)

    def abrirjanelacadastroaluno(self):
        stack.setCurrentIndex(2)

    def abrircadastrocurso(self):
        stack.setCurrentIndex(3)

    def abrirlistainst(self):
        stack.setCurrentIndex(4)

    def abrirlistacursos(self):
        stack.setCurrentIndex(5)

    def abrirlistaalunos(self):
        stack.setCurrentIndex(6)

    def gerarcarteirinha(self):
        stack.setCurrentIndex(7)

    def listarcarteirinhas(self):
        stack.setCurrentIndex(8)

    def consultarcarteirinha(self):
        stack.setCurrentIndex(9)

class CadastroAluno(QMainWindow, Ui_CadAluno):

    def __init__(self):
        super().__init__()
        super().setupUi(self)
        self.labelMensagem.setText('')
        self.btnSalvar.clicked.connect(self.coletardadosaluno)
        self.btnVoltar.clicked.connect(self.voltarinicio)

    def voltarinicio(self):
        stack.setCurrentIndex(0)

    def coletardadosaluno(self):
        nome = self.entradaNome.text()
        cpf = self.entradaCpf.text()
        data_nascimento = self.entradaNascimento.text()
        email = self.entradaEmail.text()
        iniciocurso = self.entradaAnoInicio.text()
        curso = self.entradaCurso.text()
        instituto = self.entradaInstituto.text()
        status = self.entradaStatus.text()
        conexao = sqlite3.connect('database.db')
        cursor = conexao.cursor()
        cursor.execute('SELECT CODIGO_CURSO FROM CURSOS_OFERECIDOS WHERE NOME = ?', [curso.upper()])
        numCurso = cursor.fetchone()
        cursor.execute('SELECT CNPJ FROM INSTITUTO WHERE CNPJ = ?', [instituto])
        cnpj = cursor.fetchone()

        if numCurso == None:
            self.labelMensagem.setText('Curso não existe!')
        elif cnpj == None:
            self.labelMensagem.setText('')
            self.labelMensagem.setText('Instituto não existe!')
        else:
            cursor.execute('INSERT INTO ALUNO (NOME, CPF, DATA_NASCIMENTO, EMAIL, CURSO, INSTITUTO, ANO_INICIO, STATUS)'
                   'VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
                    [nome.upper(), cpf, data_nascimento, email.upper(), numCurso[0], instituto.upper(), iniciocurso,
                     status.upper()])
            conexao.commit()
            self.labelMensagem.setText('')
            self.labelMensagem.setText('Aluno cadastrado com sucesso!')
        cursor.close()
        conexao.close()

class CadastroInstituto(QMainWindow, Ui_CadInst):

    def __init__(self):
        super().__init__()
        super().setupUi(self)
        self.btnVoltar.clicked.connect(self.voltarinicio)
        self.btnSalvar.clicked.connect(self.coletardadosinst)

    def voltarinicio(self):
        stack.setCurrentIndex(0)

    def coletardadosinst(self):

        nome = self.entradaNome.text()
        cnpj = self.entradaCnpj.text()
        endereco = self.entradaEndereco.text()
        telefone = self.entradaTelefone.text()
        conexao = sqlite3.connect('database.db')
        cursor = conexao.cursor()
        cursor.execute('INSERT INTO INSTITUTO(NOME, CNPJ, ENDERECO, TELEFONE) VALUES (?, ?, ?, ?)',
                       [nome.upper(), cnpj, endereco.upper(), telefone])
        conexao.commit()
        cursor.close()
        conexao.close()

class CadastroCurso(QMainWindow, Ui_cadCurso):
    def __init__(self):
        super().__init__()
        super().setupUi(self)
        self.btnVoltar.clicked.connect(self.voltarinicio)
        self.btnSalvar.clicked.connect(self.coletardadoscurso)

    def voltarinicio(self):
        stack.setCurrentIndex(0)

    def coletardadoscurso(self):
        nome = self.entradaNome.text()
        codigo = self.entradaCodigo.text()
        instituto = self.entradaInst.text()

        conexao = sqlite3.connect('database.db')
        cursor = conexao.cursor()

        cursor.execute('SELECT CNPJ FROM INSTITUTO WHERE CNPJ = ?', [instituto])
        cnpj = cursor.fetchone()
        if cnpj == None:
            self.labelMensagem.setText('Instituto não existe!')
        else:
            cursor.execute('INSERT INTO CURSOS_OFERECIDOS(NOME, CODIGO_CURSO, INSTITUTO) VALUES (?, ?, ?)',
                       [nome.upper(), codigo, instituto.upper()])
            conexao.commit()
            self.labelMensagem.setText('')
            self.labelMensagem.setText('Curso cadastrado com sucesso!')

        cursor.close()
        conexao.close()

class ListarInstitutos(QMainWindow, Ui_listInstituto):
    def __init__(self):
        super().__init__()
        super().setupUi(self)
        self.btnVoltar.clicked.connect(self.voltarinicio)
        self.tabelaInst.setHorizontalHeaderLabels(["Nome", "CNPJ", "Endereço", "Telefone"])
        self.listardados()

    def voltarinicio(self):
        stack.setCurrentIndex(0)

    def listardados(self):
        conexao = sqlite3.connect('database.db')
        cursor = conexao.cursor()
        cursor.execute('SELECT * FROM INSTITUTO')
        dados = cursor.fetchall()
        self.tabelaInst.setRowCount(len(dados))
        numLinha = 0
        for linha in dados:
            self.tabelaInst.setItem(numLinha, 0, QtWidgets.QTableWidgetItem(linha[1]))
            self.tabelaInst.setItem(numLinha, 1, QtWidgets.QTableWidgetItem(str(linha[0])))
            self.tabelaInst.setItem(numLinha, 2, QtWidgets.QTableWidgetItem(linha[2]))
            self.tabelaInst.setItem(numLinha, 3, QtWidgets.QTableWidgetItem(str(linha[3])))
            numLinha = numLinha + 1

        cursor.close()
        conexao.close()

class ListarCursos(QMainWindow, Ui_listaCursos):
    def __init__(self):
        super().__init__()
        super().setupUi(self)
        self.btnVoltar.clicked.connect(self.voltarinicio)
        self.tabelaCursos.setHorizontalHeaderLabels(["Código do Curso", "Nome", "Instituto"])
        self.listardados()

    def voltarinicio(self):
        stack.setCurrentIndex(0)

    def listardados(self):
        conexao = sqlite3.connect('database.db')
        cursor = conexao.cursor()
        cursor.execute('SELECT C.CODIGO_CURSO, C.NOME, I.NOME '
                       'FROM CURSOS_OFERECIDOS C, INSTITUTO I '
                       'WHERE C.INSTITUTO = I.CNPJ')
        dados = cursor.fetchall()
        self.tabelaCursos.setRowCount(len(dados))
        numLinha = 0
        for linha in dados:
            self.tabelaCursos.setItem(numLinha, 0, QtWidgets.QTableWidgetItem(str(linha[0])))
            self.tabelaCursos.setItem(numLinha, 1, QtWidgets.QTableWidgetItem(linha[1]))
            self.tabelaCursos.setItem(numLinha, 2, QtWidgets.QTableWidgetItem(linha[2]))
            numLinha = numLinha + 1
        cursor.close()
        conexao.close()

class ListarAlunos(QMainWindow, Ui_listAlunos):
    def __init__(self):
        super().__init__()
        super().setupUi(self)
        self.btnVoltar.clicked.connect(self.voltarinicio)
        self.tabelaAlunos.setHorizontalHeaderLabels(["Nome", "CPF", "Dada de Nascimento", "Email", "Curso", "Instituto", "Status"])
        self.btnTodos.clicked.connect(self.listartodos)
        self.btnGraduados.clicked.connect(self.listargraduados)
        self.btnGraduandos.clicked.connect(self.listargraduandos)

    def voltarinicio(self):
        stack.setCurrentIndex(0)

    def listartodos(self):
        conexao = sqlite3.connect('database.db')
        cursor = conexao.cursor()
        cursor.execute('SELECT A.NOME, A.CPF, A.DATA_NASCIMENTO, A.EMAIL, C.NOME, I.NOME, A.STATUS '
                       'FROM ALUNO A, CURSOS_OFERECIDOS C, INSTITUTO I '
                       'WHERE A.CURSO = C.CODIGO_CURSO AND A.INSTITUTO = I.CNPJ')
        dados = cursor.fetchall()
        self.tabelaAlunos.setRowCount(len(dados))
        numLinha = 0
        for linha in dados:
            self.tabelaAlunos.setItem(numLinha, 0, QtWidgets.QTableWidgetItem(linha[0]))
            self.tabelaAlunos.setItem(numLinha, 1, QtWidgets.QTableWidgetItem(str(linha[1])))
            self.tabelaAlunos.setItem(numLinha, 2, QtWidgets.QTableWidgetItem(str(linha[2])))
            self.tabelaAlunos.setItem(numLinha, 3, QtWidgets.QTableWidgetItem(linha[3]))
            self.tabelaAlunos.setItem(numLinha, 4, QtWidgets.QTableWidgetItem(linha[4]))
            self.tabelaAlunos.setItem(numLinha, 5, QtWidgets.QTableWidgetItem(linha[5]))
            self.tabelaAlunos.setItem(numLinha, 6, QtWidgets.QTableWidgetItem(linha[6]))
            numLinha = numLinha + 1

    def listargraduados(self):
        conexao = sqlite3.connect('database.db')
        cursor = conexao.cursor()
        cursor.execute('SELECT A.NOME, A.CPF, A.DATA_NASCIMENTO, A.EMAIL, C.NOME, I.NOME, A.STATUS '
                       'FROM ALUNO A, CURSOS_OFERECIDOS C, INSTITUTO I '
                       'WHERE A.CURSO = C.CODIGO_CURSO AND A.INSTITUTO = I.CNPJ AND A.STATUS = ?', ['GRADUADO'])
        dados = cursor.fetchall()
        self.tabelaAlunos.setRowCount(len(dados))
        numLinha = 0
        for linha in dados:
            self.tabelaAlunos.setItem(numLinha, 0, QtWidgets.QTableWidgetItem(linha[0]))
            self.tabelaAlunos.setItem(numLinha, 1, QtWidgets.QTableWidgetItem(str(linha[1])))
            self.tabelaAlunos.setItem(numLinha, 2, QtWidgets.QTableWidgetItem(str(linha[2])))
            self.tabelaAlunos.setItem(numLinha, 3, QtWidgets.QTableWidgetItem(linha[3]))
            self.tabelaAlunos.setItem(numLinha, 4, QtWidgets.QTableWidgetItem(linha[4]))
            self.tabelaAlunos.setItem(numLinha, 5, QtWidgets.QTableWidgetItem(linha[5]))
            self.tabelaAlunos.setItem(numLinha, 6, QtWidgets.QTableWidgetItem(linha[6]))
            numLinha = numLinha + 1

    def listargraduandos(self):
        conexao = sqlite3.connect('database.db')
        cursor = conexao.cursor()
        cursor.execute('SELECT A.NOME, A.CPF, A.DATA_NASCIMENTO, A.EMAIL, C.NOME, I.NOME, A.STATUS '
                       'FROM ALUNO A, CURSOS_OFERECIDOS C, INSTITUTO I '
                       'WHERE A.CURSO = C.CODIGO_CURSO AND A.INSTITUTO = I.CNPJ AND A.STATUS = ?', ['GRADUANDO'])
        dados = cursor.fetchall()
        self.tabelaAlunos.setRowCount(len(dados))
        numLinha = 0
        for linha in dados:
            self.tabelaAlunos.setItem(numLinha, 0, QtWidgets.QTableWidgetItem(linha[0]))
            self.tabelaAlunos.setItem(numLinha, 1, QtWidgets.QTableWidgetItem(str(linha[1])))
            self.tabelaAlunos.setItem(numLinha, 2, QtWidgets.QTableWidgetItem(str(linha[2])))
            self.tabelaAlunos.setItem(numLinha, 3, QtWidgets.QTableWidgetItem(linha[3]))
            self.tabelaAlunos.setItem(numLinha, 4, QtWidgets.QTableWidgetItem(linha[4]))
            self.tabelaAlunos.setItem(numLinha, 5, QtWidgets.QTableWidgetItem(linha[5]))
            self.tabelaAlunos.setItem(numLinha, 6, QtWidgets.QTableWidgetItem(linha[6]))
            numLinha = numLinha + 1

class GerarCarteirinha(QMainWindow, Ui_geraCarteirinha):

    def __init__(self):
        super().__init__()
        super().setupUi(self)
        self.btnVoltar.clicked.connect(self.voltarinicio)
        self.btnGerar.clicked.connect(self.coletardados)
        self.btnRenovar.clicked.connect(self.renovar)

    def voltarinicio(self):
        stack.setCurrentIndex(0)

    def coletardados(self):
        cpf = self.entradaCPF.text()

        conexao = sqlite3.connect('database.db')
        cursor = conexao.cursor()

        cursor.execute('SELECT CPF FROM ALUNO WHERE CPF = ?', [cpf])
        alunoexiste = cursor.fetchone()
        cursor.execute('SELECT ALUNO FROM CARTEIRINHAS WHERE ALUNO =?', [cpf])
        carteirinhaexiste = cursor.fetchone()
        cursor.execute('SELECT STATUS FROM ALUNO WHERE CPF =?', [cpf])
        status = cursor.fetchone()

        if alunoexiste == None:

            self.labelMensagem.setText('Aluno não existe!')

        elif carteirinhaexiste != None:

            self.labelMensagem.setText('Carteirinha já existe, deseja renovar?')

        elif status[0] == 'GRADUADO':

            self.labelMensagem.setText('Aluno já graduado, não é possível gerar carteirinha!')

        else:
            cursor.execute('SELECT A.NOME, C.NOME, A.ANO_INICIO, I.NOME, A.CPF '
                           'FROM ALUNO A, INSTITUTO I, CURSOS_OFERECIDOS C '
                           'WHERE A.CPF = ? AND A.INSTITUTO = I.CNPJ AND A.CURSO = C.CODIGO_CURSO', [cpf])
            resultado = cursor.fetchone()

            codigo = random.randrange(1, 100000)
            self.labelNome.setText('Nome:'+' '+resultado[0])
            self.labelInstituto.setText(resultado[3])
            self.labelCurso.setText('Curso:'+' '+resultado[1])
            self.labelAno.setText('Ano de inicio:'+' '+str(resultado[2]))
            self.labelCodigo.setText('Código da carteirinha:'+' '+str(codigo))
            data = datetime.date.today()
            validade = '{0}-{1}-{2}'.format(str(data.day), str(data.month), str(data.year+5))
            self.labelValidade.setText('Validade:'+' '+str(validade))
            cursor.execute('INSERT INTO CARTEIRINHAS VALUES (?, ?, ?)', [resultado[4], codigo, validade])
            conexao.commit()
            cursor.close()
            conexao.close()

    def renovar(self):
        cpf = self.entradaCPF.text()
        conexao = sqlite3.connect('database.db')
        cursor = conexao.cursor()
        cursor.execute('SELECT A.NOME, I.NOME, C.NOME, A.ANO_INICIO  '
                       'FROM ALUNO A, INSTITUTO I, CURSOS_OFERECIDOS C '
                       'WHERE A.CPF = ? AND A.INSTITUTO = I.CNPJ AND A.CURSO = C.CODIGO_CURSO ', [cpf])

        resultado1 = cursor.fetchone()
        cursor.execute('SELECT CODIGO FROM CARTEIRINHAS WHERE ALUNO = ?', [cpf])
        resultado2 = cursor.fetchone()
        self.labelNome.setText('Nome:' + ' ' + resultado1[0])
        self.labelInstituto.setText(resultado1[1])
        self.labelCurso.setText('Curso:' + ' ' + resultado1[2])
        self.labelAno.setText('Ano de inicio:' + ' ' + str(resultado1[3]))
        self.labelCodigo.setText('Código da carteirinha:' + ' ' + str(resultado2[0]))
        data = datetime.date.today()
        validade = '{0}-{1}-{2}'.format(str(data.day), str(data.month), str(data.year + 5))
        self.labelValidade.setText('Validade:' + ' ' + str(validade))
        cursor.execute('UPDATE CARTEIRINHAS SET DATA_VALIDADE = ? WHERE ALUNO = ?', [validade, cpf])
        conexao.commit()
        cursor.close()
        conexao.close()

class ListarCarteirinha(QMainWindow, Ui_listaCarteirinhas):
    def __init__(self):
        super().__init__()
        super().setupUi(self)
        self.btnVoltar.clicked.connect(self.voltarinicio)
        self.tabelaCarteirinha.setHorizontalHeaderLabels(["CPF Aluno", "Código Carteirinha", "Validade"])
        self.listar()

    def voltarinicio(self):
        stack.setCurrentIndex(0)

    def listar(self):
        conexao = sqlite3.connect('database.db')
        cursor = conexao.cursor()
        cursor.execute('SELECT * FROM CARTEIRINHAS')
        dados = cursor.fetchall()
        self.tabelaCarteirinha.setRowCount(len(dados))
        numLinha = 0
        for linha in dados:
            self.tabelaCarteirinha.setItem(numLinha, 0, QtWidgets.QTableWidgetItem(str(linha[0])))
            self.tabelaCarteirinha.setItem(numLinha, 1, QtWidgets.QTableWidgetItem(str(linha[1])))
            self.tabelaCarteirinha.setItem(numLinha, 2, QtWidgets.QTableWidgetItem(str(linha[2])))
            numLinha = numLinha + 1

        cursor.close()
        conexao.close()

class ConsultarCarteirinha(QMainWindow, Ui_consultarCarteirinha):

    def __init__(self):
        super().__init__()
        super().setupUi(self)
        self.btnVoltar.clicked.connect(self.voltarinicio)
        self.btnConsultar.clicked.connect(self.coletardados)

    def voltarinicio(self):
        stack.setCurrentIndex(0)

    def coletardados(self):
        entrada = self.entradaCPF.text()
        conexao = sqlite3.connect('database.db')
        cursor = conexao.cursor()
        cursor.execute('SELECT ALUNO FROM CARTEIRINHAS WHERE ALUNO = ? ', [entrada])
        cpf = cursor.fetchone()
        cursor.execute('SELECT ALUNO FROM CARTEIRINHAS WHERE CODIGO = ? ', [entrada])
        cpf2 = cursor.fetchone()

        if cpf != None:

            cursor.execute('SELECT A.NOME, I.NOME, C.NOME, A.ANO_INICIO '
                           'FROM ALUNO A, INSTITUTO I, CURSOS_OFERECIDOS C '
                           'WHERE A.CPF = ? AND A.INSTITUTO = I.CNPJ AND A.CURSO = C.CODIGO_CURSO', (cpf))
            resultado = cursor.fetchone()
            cursor.execute('SELECT CODIGO, DATA_VALIDADE FROM CARTEIRINHAS WHERE ALUNO = ?', (cpf))
            resultado2 = cursor.fetchone()
            self.labelInstituto.setText(resultado[1])
            self.labelNome.setText('Nome:'+' '+resultado[0])
            self.labelCurso.setText('Curso:'+' '+resultado[2])
            self.labelAno.setText('Ano de inicio:'+' '+str(resultado[3]))
            self.labelCodigo.setText('Código da carteirinha:'+' '+str(resultado2[0]))
            self.labelValidade.setText('Validade'+ ' '+str(resultado2[1]))

        elif cpf2 != None:
            cursor.execute('SELECT CODIGO, DATA_VALIDADE FROM CARTEIRINHAS WHERE ALUNO = ?', (cpf2))
            resultado = cursor.fetchone()
            cursor.execute('SELECT A.NOME, I.NOME, C.NOME, A.ANO_INICIO '
                           'FROM ALUNO A, INSTITUTO I, CURSOS_OFERECIDOS C '
                           'WHERE A.CPF = ? AND A.INSTITUTO = I.CNPJ AND A.CURSO = C.CODIGO_CURSO', (cpf2))
            resultado2 = cursor.fetchone()

            self.labelInstituto.setText(resultado2[1])
            self.labelNome.setText('Nome:'+' '+resultado2[0])
            self.labelCurso.setText('Curso:'+ ' '+resultado2[2])
            self.labelAno.setText('Ano de inicio:'+' '+str(resultado2[3]))
            self.labelCodigo.setText('Código da carteirinha:'+' '+str(resultado[0]))
            self.labelValidade.setText('Validade:'+' '+str(resultado[1]))
        else:
            self.labelMensagem.setText('Aluno ou carteirinha não existe!')

if __name__ == '__main__':
    qt = QApplication(sys.argv)
    main = Main()
    stack = QtWidgets.QStackedWidget()
    stack.insertWidget(0, main)
    cadinst = CadastroInstituto()
    stack.insertWidget(1, cadinst)
    cadastroaluno = CadastroAluno()
    stack.insertWidget(2, cadastroaluno)
    cadcurso = CadastroCurso()
    stack.insertWidget(3, cadcurso)
    listainst = ListarInstitutos()
    stack.insertWidget(4, listainst)
    listacursos = ListarCursos()
    stack.insertWidget(5, listacursos)
    listaalunos = ListarAlunos()
    stack.insertWidget(6, listaalunos)
    gerarcarteirinha = GerarCarteirinha()
    stack.insertWidget(7, gerarcarteirinha)
    listarcarteirinha = ListarCarteirinha()
    stack.insertWidget(8, listarcarteirinha)
    consultacarteirinha = ConsultarCarteirinha()
    stack.insertWidget(9, consultacarteirinha)
    stack.setCurrentIndex(0)
    stack.setFixedWidth(600)
    stack.setFixedHeight(600)
    stack.show()
    qt.exec_()


"""
Some functions handling angular calculations
"""

from __future__ import absolute_import, division, print_function, unicode_literals

import numpy as np

__all__ = ['mean_angle', 'delta_angle']

def mean_angle(angles, weights=None):
	"""
	Compute mean angle

	:param angles:
		List or numpy array with angles in degrees
	:param weights:
		List or numpy array with weights (default: None)
	"""
	rad_angles = np.radians(angles)
	## Mean Y component
	sa = np.average(np.sin(rad_angles), weights=weights)
	## Mean X component
	ca = np.average(np.cos(rad_angles), weights=weights)
	## Take the arctan of the averages, and convert to degrees,
	mean_angle = np.degrees(np.arctan2(sa, ca))
	return mean_angle

def delta_angle(angle1, angle2):
	"""
	Compute difference between two angles

	:param angle1:
		Float, first angle in degrees
	:param angle2:
		Float, second angle in degrees

	:return:
		Float, angle difference in degrees
	"""
	## Convert angles to unit vectors
	rad_angle1 = np.radians(angle1)
	rad_angle2 = np.radians(angle2)
	v1_x, v1_y = np.cos(rad_angle1), np.sin(rad_angle1)
	v2_x, v2_y = np.cos(rad_angle2), np.sin(rad_angle2)
	v1, v2 = np.array([v1_x, v1_y]), np.array([v2_x, v2_y])

	rad_delta = np.arccos(np.dot(v1, v2))
	if np.isnan(rad_delta):
		if (v1 == v2).all():
			rad_delta = 0.0
		else:
			rad_delta = np.pi
	## This is equivalent
	#rad_delta = np.arctan2(v2_y, v2_x) - np.arctan2(v1_y, v1_x)
	return np.degrees(rad_delta)