# Python 3.6
"""
Peak handle functions.

Maintainer: Shpakov Konstantin
Link: https://github.com/shpakovkv/SignalProcess
"""

from __future__ import print_function

import matplotlib
import matplotlib.pyplot as pyplot

import os
import sys
import numpy
import bisect
import argparse

import numpy as np
import scipy.integrate as integrate

# import SignalProcess as sp
import arg_parser
import arg_checker
import file_handler
import plotter
from multiprocessing import Pool

from multiplier_and_delay import multiplier_and_delay
from data_types import SinglePeak

pos_polarity_labels = {'pos', 'positive', '+'}
neg_polarity_labels = {'neg', 'negative', '-'}

NOISEATTENUATION = 0.75
SAVETODIR = 'Peaks'
SINGLEPLOTDIR = 'SinglePlot'
MULTIPLOTDIR = 'MultiPlot'
PEAKDATADIR = 'PeakData'
DEBUG = False
PEAKFINDERDEBUG = False

def get_parser():
    """Returns final CL args parser.

    :return: argparse.parser
    """
    p_use = ('python %(prog)s [options]\n'
             '       python %(prog)s @file_with_options')
    p_desc = ('')
    p_ep = ('')

    parser = argparse.ArgumentParser(
        parents=[arg_parser.get_input_files_args_parser(),
                 arg_parser.get_mult_del_args_parser(),
                 arg_parser.get_plot_args_parser(),
                 arg_parser.get_peak_args_parser(),
                 arg_parser.get_output_args_parser(),
                 arg_parser.get_utility_args_parser()],
        prog='PeakProcess.py',
        description=p_desc, epilog=p_ep, usage=p_use,
        fromfile_prefix_chars='@',
        formatter_class=argparse.RawTextHelpFormatter
    )
    return parser

def find_nearest_idx(sorted_arr, value, side='auto'):
    """
    Returns the index of the 'sorted_arr' element closest to 'value'

    :param sorted_arr: sorted array/list of ints or floats
    :param value: the int/float number to which the
                  closest value should be found
    :param side: 'left': search among values that are lower then X
                 'right': search among values that are greater then X
                 'auto': handle all values (default)
    :type sorted_arr: array-like
    :type value: int, float
    :type side: str ('left', 'right', 'auto')
    :return: the index of the value closest to 'value'
    :rtype: int

    .. note:: if two numbers are equally close and side='auto',
           returns the index of the smaller one.
    """

    idx = bisect.bisect_left(sorted_arr, value)
    if idx == 0:
        return idx if side == 'auto' or side == 'right' else None

    if idx == len(sorted_arr):
        return idx if side == 'auto' or side == 'left' else None

    after = sorted_arr[idx]
    before = sorted_arr[idx - 1]
    if side == 'auto':
        return idx if after - value < value - before else idx - 1
    else:
        return idx if side == 'right' else idx - 1

def level_excess(x, y, level, start=0, step=1,
                 window=0, is_positive=True):
    """Checks if 'Y' values excess 'level' value
    for 'X' in range from X(start) to X(start) + window
    OR for x in range from X(start) - window to X(start)

    :param x:           array with X data
    :param y:           array with Y data
    :param level:       level value
    :param start:       start index of data
    :param step:        step with which elements of the array are traversed
                        step > 0: checks elements to the RIGHT from start idx
                        step < 0: checks elements to the LEFT from start idx
    :param window:      check window width
    :param is_positive: the direction of the check
                        True: checks whether the 'Y' value rises above 'level'
                        False: checks whether the 'Y' value
                        comes down below 'level'
    :type x:            array-like
    :type y:            array-like
    :type level:        float, int ''same as param y''
    :type start:        int
    :type step:         int
    :type window:       float, int ''same as param x''
    :type is_positive:  bool
    :return:            True and an index of first Y element that are
                        bigger/lower 'level' OR returns False and
                        an index of the last checked element
    :rtype:             tuple ''(bool, int)''
    """

    idx = start          # zero-based index
    if window == 0:      # window default value
        window = x[-1] - x[start]

    while ((idx >= 0) and (idx < len(y)) and
               (abs(x[idx] - x[start]) <= window)):
        if not is_positive and (y[idx] < level):
            # downward
            return True, idx
        elif is_positive and (y[idx] > level):
            # upward
            return True, idx
        idx += step
    return False, idx

def is_pos(polarity):
    """Checks if the polarity (str flag) is positive.

    :param polarity:    word denoting polarity
    :type polarity:     str
    :return:            True if the polarity is positive,
                        otherwise returns False
    :rtype:             bool
    """
    global pos_polarity_labels
    global neg_polarity_labels
    if polarity.lower() in pos_polarity_labels:
        return True
    if polarity.lower() in neg_polarity_labels:
        return False
    else:
        raise ValueError("Wrong polarity value ({})".format(polarity))

def is_neg(polarity):
    """Checks if the polarity (str flag) is negative.

    :param polarity:    word denoting polarity
    :type polarity:     str
    :return:            True if the polarity is negative,
                        otherwise returns False
    :rtype:             bool
    """
    return not is_pos(polarity)

def check_polarity(curve, time_bounds=(None, None)):
    """Checks whether the curve is mostly positive or negative
    on a certain interval.

    :param curve:       curve data
    :param time_bounds: the left and the right boundaries of
                        the specified interval
    :type curve:        SignalProcess.SingleCurve
    :type time_bounds:  tuple, list ''(float, float)''

    :return:            the word denoting polarity
    :rtype:             str
    """
    if time_bounds[0] is None:
        time_bounds = (0, time_bounds[1])
    if time_bounds[1] is None:
        time_bounds = (time_bounds[0], curve.points)
    integr =  integrate.trapz(curve.val[time_bounds[0]:time_bounds[1]],
                       curve.time[time_bounds[0]:time_bounds[1]])
    # print("Voltage_INTEGRAL = {}".format(integr))
    if integr >= 0:
        return 'positive'
    return 'negative'

def find_curve_front(curve,
                     level=-0.2,
                     polarity='auto',
                     save_plot=False,
                     plot_name="voltage_front.png"):
    """Find time point (x) of voltage curve edge at specific level
    Default: Negative polarity, -0.2 MV level

    :param curve: curve data
    :param level: amplitude value to find
    :param polarity: the polarity of the curve
    :param save_plot: bool flag
    :param plot_name: plot full file name to save as

    :type curve: SingleCurve
    :type level: float
    :type polarity: str '+'/'pos'/'-'/'neg'/'auto'
    :type save_plot: bool
    :type plot_name: str

    :return:  (time, amplitude) or (None, None)
    :rtype: tuple(float, float)
    """

    if polarity=='auto':
        polarity = check_polarity(curve)
        if is_pos(polarity):
            level = abs(level)
        else:
            level = -abs(level)

    front_checked, idx = level_excess(curve.time, curve.val, level,
                                      is_positive=is_pos(polarity))
    if front_checked:
        if save_plot:
            pyplot.close('all')
            pyplot.plot(curve.time, curve.val, '-b')
            pyplot.plot([curve.time[idx]], [curve.val[idx]], '*r')
            # pyplot.show()
            folder = os.path.dirname(plot_name)
            if folder != "" and not os.path.isdir(folder):
                os.makedirs(folder)
            pyplot.savefig(plot_name)
            pyplot.close('all')
        return curve.time[idx], curve.val[idx]
    return None, None

def peak_finder(x, y, level, diff_time, time_bounds=(None, None),
                tnoise=None, is_negative=True, graph=False,
                noise_attenuation=0.5):
    """Finds peaks on the curve (x, y).
    Searchs for negative peaks by default.

    :param x: array of time values
    :param y: array of amplitude values
    :param level: peak threshold (all amplitude values
                  below this level will be ignored)
    :param diff_time: the minimum difference between two neighboring peaks.
                      If the next peak is at the front (fall or rise)
                      of the previous peak, and the "distance" from
                      its maximum to that front (at the same level) is less
                      than the diff_time, this second peak will be ignored.
    :param time_bounds: tuple with the left and the right search boundaries
    :param tnoise: maximum half-period of noise fluctuation.
    :param is_negative: specify False for positive curve.
                        for negative curve (by default) the 'y' array will be
                        inverted before process and inverted again at the end.
    :param graph: specify True to display a graph with found peaks
    :param noise_attenuation: Attenuation of the second half-wave
                              with a polarity reversal (noise). If too many
                              noise maxima are defined as real peaks,
                              reduce this value.

    :type x: numpy.ndarray
    :type y: numpy.ndarray
    :type level: float, int ''same as values of param y''
    :type diff_time: float, int ''same as values of param x''
    :type time_bounds: tuple, list ''(float, float)''
    :type tnoise: float, int ''same as values of param x''
    :type is_negative: bool
    :type graph: bool
    :type noise_attenuation: float

    :return: (peaks_list, log) - the list of peaks (SinglePeak instances)
             and the process log
    :rtype: (list, str)
    """

    # print("============ peak_finder ================")
    # print("level : {}\ndiff_time : {}\ntime_bounds : {}\ntnoise : {}\n"
    #       "is_negative : {}\ngraph : {}\nnoise_attenuation : {}\n"
    #       "start_idx : {}\nstop_idx : {}"
    #       "".format(level, diff_time, time_bounds, tnoise,
    #                 is_negative, graph, noise_attenuation,
    #                 start_idx, stop_idx))
    # print("-------------------")

    # Checkout the inputs
    peak_log = ""
    assert level != 0, 'Invalid level value!'

    if is_negative:
        y = -y
        level = -level

    if not tnoise:
        tnoise = (x[1] - x[0]) * 4
        peak_log += 'Set "tnoise" to default 4 stops = ' + str(tnoise) + "\n"

    assert len(time_bounds) == 2, ("time_bounds has incorrect number of "
                                   "values. 2 expected, " +
                                   str(len(time_bounds)) + " given.")
    assert len(x) == len(y), ("The length of X ({}) is not equal to the "
                              "length of Y ({}).".format(len(x), len(y)))

    if time_bounds[0] is None:
        time_bounds = (x[0], time_bounds[1])
    if time_bounds[1] is None:
        time_bounds = (time_bounds[0], x[-1])
    start_idx = find_nearest_idx(x, time_bounds[0], side='right')
    stop_idx = find_nearest_idx(x, time_bounds[1], side='left')

    peak_list = []

    if start_idx is None or stop_idx is None:
        # the interval is [start_idx, stop_idx)
        # start_idx is included; stop_idx is excluded
        peak_log += "Time bounds is out of range.\n"
        return peak_list, peak_log

    time_delta = 0.0
    if x[0] != x[1]:
        time_delta = x[1] - x[0]
    else:
        time_part = x[stop_idx] - x[start_idx]
        time_delta = time_part / (stop_idx - start_idx - 1)
    diff_idx = int(diff_time / time_delta)

    if PEAKFINDERDEBUG:
        print("Diff_time = {}, Diff_idx = {}".format(diff_time, diff_idx))

    i = start_idx
    while i < stop_idx :
        if y[i] > level:
            max_y = y[i]  # local max (may be real peak or not)
            max_idx = i

            # search for a bigger local max within the diff_time from
            # the found one
            # y[i] == max_y condition is needed
            # for flat-top peaks (wider than diff_time)
            while (i <= stop_idx and
                   (x[i] - x[max_idx] <= diff_time or
                    y[i] == max_y)):
                if y[i] > max_y:
                    max_y = y[i]
                    max_idx = i
                i += 1
            if PEAKFINDERDEBUG:
                print("local_max = [{:.3f}, {:.3f}] i={}"
                      "".format(x[max_idx], max_y, max_idx))

            # search for a bigger value within the diff_time
            # to the left from the found local maximum
            # if found: this is a little signal fluctuation on the fall edge
            # (not a real peak)
            [is_noise, _] = level_excess(x, y, max_y, start=max_idx,
                                         step=-1, window=diff_time,
                                         is_positive=True)
            if PEAKFINDERDEBUG and is_noise:
                print('Left Excess at x({:.2f}, {:.2f}) '
                      '== Not a peak at fall edge!'.format(x[i], y[i]))

            # search for a polarity reversal within tnose from this local max
            # if found: this is a noise (not a real peak)
            if not is_noise:
                # search to the right from the local max
                [is_noise, j] = level_excess(x, y,
                                             -max_y * noise_attenuation,
                                             start=max_idx, step=1,
                                             window=tnoise,
                                             is_positive=False)

                if PEAKFINDERDEBUG and is_noise:
                    print('Noise to the right x({:.2f}, {:.2f})'
                          ''.format(x[j], y[j]))
                else:
                    # search to the left from the local max
                    [is_noise, j] = level_excess(x, y,
                                                 -max_y * noise_attenuation,
                                                 start=max_idx, step=-1,
                                                 window=tnoise,
                                                 is_positive=False)
                    if PEAKFINDERDEBUG and is_noise:
                        print('Noise to the left x({:.2f}, {:.2f})'
                              ''.format(x[j], y[j]))

            if not is_noise:
                # all checks passed, the local max is the real peak
                peak_list.append(SinglePeak(x[max_idx], max_y, max_idx))
                continue
        i += 1

    peak_log += 'Number of peaks: ' + str(len(peak_list)) + "\n"

    # LOCAL INTEGRAL CHECK
    # needed for error probability estimation
    di = int(diff_time * 2 // time_delta)    # diff window in index units

    if di > 3:
        for idx in range(len(peak_list)):
            pk = peak_list[idx]
            # square = pk.val * time_delta * di
            square = pk.val * di

            intgr_l = 0
            intgr_r = 0
            peak_log += ("Peak[{:3d}] = [{:7.2f},   {:4.1f}]   "
                         "Square factor [".format(idx, pk.time, pk.val))
            if pk.idx - di >= 0:
                intgr_l = integrate.trapz(y[pk.idx-di : pk.idx+1])
                peak_list[idx].sqr_l = intgr_l / square
                peak_log += "{:.3f}".format(intgr_l / square)
            peak_log += " | "
            if pk.idx + di < len(y):  # stop_idx
                intgr_r = integrate.trapz(y[pk.idx: pk.idx + di + 1])
                peak_list[idx].sqr_r = intgr_r / square
                peak_log += "{:.3f}".format(intgr_r / square)
            peak_log += "]"
            peak_log += "  ({:.3f})".format((intgr_r + intgr_l) / square)
            peak_log += "\n"
        if peak_list:
            peak_log += "\n"
    # integr_l, integr_r: The closer the value to unity,
    # the greater the probability that the peak is imaginary (erroneous)

    if is_negative:
        y = -y
        level = -level
        for i in range(len(peak_list)):
            peak_list[i].invert()
    if graph:
        # plotting curve
        pyplot.plot(x[start_idx:stop_idx], y[start_idx:stop_idx], '-',
                    color='#8888bb')
        pyplot.xlim(time_bounds)
        # plotting level line
        pyplot.plot([x[0], x[len(x) - 1]], [level, level], ':',
                    color='#80ff80')
        # marking overall peaks
        peaks_x = [p.time for p in peak_list]
        peaks_y = [p.val for p in peak_list]
        pyplot.scatter(peaks_x, peaks_y, s=50, edgecolors='#ff7f0e',
                       facecolors='none', linewidths=2)
        pyplot.scatter(peaks_x, peaks_y, s=80, edgecolors='#dd3328',
                       facecolors='none', linewidths=2)
        pyplot.show()

    return peak_list, peak_log

def group_peaks(data, window):
    """Groups the peaks from different curves.
    Each group corresponds to one single event (for example:
    one act of X-Ray emission, registered by several detectors).

    :param data: three-dimensional array containing data
                 on all the peaks of all curves
                 The array structure:
                 data[curve_idx][peak_idx] == SinglePeak instance
                 If a curve with curve_idx index has no peaks
                 the data[curve_idx] contains an empty list.
    :param window: peaks coincide when their X values are within
                   +/-window interval from average X (time) position
                   of peak (event). "Average" because X (time) value
                   of a peak (event) may differ from curve to curve.
    :return: peak_data - the three-dimensional array containing data
             on all the peaks (grouped by time) of all curves
             The array structure:
             peak_data[curve_idx][group_idx] == SinglePeak instance if
             this curve has a peak related to this event (group), else None
    """

    def insert_group(peak, peak_data, groups_time,
                     num_peak_in_gr, wf, gr):
        """Inserts new group of peaks to the peak_data array
        at a specific index.

        :param peak: new peak to add
        :param peak_data: the 3-dimensional array with peaks data
        :param groups_time: the list with groups average time
        :param num_peak_in_gr: the list contains
                               the number of peaks in each group
        :param wf: waveform (curve) index
        :param gr: new group index (insert on this index)
        :return: None
        """
        groups_time.insert(gr, peak.time)
        num_peak_in_gr.insert(gr, 1)
        for curve_i in range(len(peak_data)):
            if curve_i == wf:
                peak_data[curve_i].insert(gr, SinglePeak(*peak.data_full))
            else:
                peak_data[curve_i].insert(gr, None)

    def add_pk_to_gr(peak, peak_data, groups_time,
                     num_peak_in_gr, wf, gr):
        """Adds new peak (from another curve) to existing group.
        It is assumed that the group contains None value
        on the place of this peak.

        :param peak: new peak to add
        :param peak_data: the 3-dimensional array with peaks data
        :param groups_time: the list with groups average time
        :param num_peak_in_gr: the list contains
                               the number of peaks in each group
        :param wf: waveform (curve) index
        :param gr: new group index (insert on this index)
        :return: None
        """
        groups_time[gr] = ((groups_time[gr] * num_peak_in_gr[gr] +
                            peak.time) /
                           (num_peak_in_gr[gr] + 1))
        num_peak_in_gr[gr] += 1
        peak_data[wf][gr] = SinglePeak(*peak.data_full)

    if len(data) == 1 and len(data[0]) == 0:
        return [[]]

    # wf == waveform == curve
    start_wf = 0
    # skip first curves if they have no peaks
    while not data[start_wf] and start_wf < len(data):
        start_wf += 1

    # 1D array with average time value of peak group
    groups_time = [peak.time for peak in data[start_wf]]

    # 1D array with numbers of peaks in each group
    num_peak_in_gr = [1] * len(groups_time)

    dt = abs(window)
    curves_count = len(data)

    # the 3-dimensional array will contain data
    # on all the peaks (grouped by time)
    peak_data = [[]]
    for peak in data[start_wf]:
        peak_data[0].append(SinglePeak(*peak.data_full))
    for curve_idx in range(0, start_wf):
        peak_data.insert(0, [None] * len(groups_time))
    for curve_idx in range(start_wf + 1, curves_count):
        peak_data.append([None] * len(groups_time))

    if curves_count <= 1:
        return peak_data

    '''---------- making groups of peaks ------------------------------
    two peaks make group when they are close enough
    ('X' of a peak is within +/- dt interval from 'X' of the group)
    with adding new peak to a group,
    the 'X' parameter of the group changes to (X1 + X2 + ... + Xn)/n
    where n - number of peaks in group
    '''

    for wf in range(start_wf + 1, curves_count):
        '''
        wf == waveform index = curve index
        gr == group index (zero-based index of current group)
        pk == peak index (zero-based index of current peak
              in the peak list of current waveform)
        '''
        gr = 0
        pk = 0

        while data[wf] is not None and pk < len(data[wf]):  # and len(data[wf]) > 0:
            '''ADD PEAK TO GROUP
            when curve[i]'s peak[j] is in
            +/-dt interval from peaks of group[gr]
            '''
            if gr < len(groups_time) \
                    and abs(groups_time[gr] - data[wf][pk].time) <= dt:
                if (len(data[wf]) > pk + 1 and
                        (abs(groups_time[gr] - data[wf][pk].time) >
                         abs(groups_time[gr] - data[wf][pk + 1].time))):
                    # next peak of data[wf] matches better
                    # insert new group for current data[wf]'s peak
                    insert_group(data[wf][pk], peak_data, groups_time,
                                 num_peak_in_gr, wf, gr)
                    pk += 1
                elif (len(groups_time) > gr + 1 and
                          (abs(groups_time[gr] - data[wf][pk].time) >
                               abs(groups_time[gr + 1] - data[wf][pk].time))):
                    # current peak matches next group better
                    pass
                else:
                    add_pk_to_gr(data[wf][pk], peak_data, groups_time,
                                 num_peak_in_gr, wf, gr)
                    pk += 1
                if gr == len(groups_time) - 1 and pk < len(data[wf]):
                    # Last peak_data column was filled but there are
                    # more peaks in the data[wf], so adds new group
                    gr += 1

            elif (gr < len(groups_time) and
                          data[wf][pk].time < groups_time[gr] - dt):
                '''INSERT NEW GROUP
                when X-position of current peak of curve[wf] is
                to the left of current group by more than dt
                '''
                insert_group(data[wf][pk], peak_data, groups_time,
                             num_peak_in_gr, wf, gr)
                pk += 1

            elif gr >= len(groups_time) - 1:
                '''APPEND NEW GROUP
                when X-position of current peak of curve[wf] is to the right
                of the last group
                '''
                insert_group(data[wf][pk], peak_data, groups_time,
                             num_peak_in_gr, wf, len(groups_time))
                pk += 1
                gr += 1

            if gr < len(groups_time) - 1:
                gr += 1

    return peak_data

def get_peaks(data, args, verbose):
    """Searches for peaks using parameters from args namespace.

    :param data:    SignalsData instance
    :param args:    argparse.namespace with arguments
    :param verbose: shows more info during the process

    :return:        three-dimensional array containing data
                    on all the peaks of curves with index in args.curves list
                    The array structure:
                    data[curve_idx][peak_idx] == SinglePeak instance
                    For the curves not in the args.curves list:
                    data[curve_idx] == None
    """
    unsorted_peaks = [None] * data.count
    for idx in args.curves:
        if verbose:
            print("Curve #" + str(idx))
        new_peaks, peak_log = peak_finder(
            data.time(idx), data.value(idx),
            level=args.level, diff_time=args.pk_diff,
            time_bounds=args.t_bounds, tnoise=args.t_noise,
            is_negative=args.level < 0,
            noise_attenuation=args.noise_att,
            graph=False
        )
        unsorted_peaks[idx] = new_peaks
        if verbose:
            print(peak_log)
    return unsorted_peaks

def check_curves_list(curves, signals_data):
    """Checks the indexes of the curves to process.
    Raises the exception if the index is out of range.

    :param curves: the list of indexes of curves to find peaks for
    :param signals_data: SignalsData instance
    :return: None
    """
    for curve_idx in curves:
        assert curve_idx < signals_data.count, \
            ("The curve index {} is out of range. The total number "
             "of curves: {}.".format(curve_idx, signals_data.count))

def global_check(options):
    """Input options global check.

    Returns changed options with converted values.

    options -- namespace with options
    """
    # file import args check
    options = arg_checker.file_arg_check(options)

    # partial import args check
    options = arg_checker.check_partial_args(options)

    # plot args check
    options = arg_checker.plot_arg_check(options)

    # curve labels check
    arg_checker.label_check(options.labels)

    # # data manipulation args check
    # options = arg_checker.data_corr_arg_check(options)
    #
    # # save data args check
    # options = arg_checker.save_arg_check(options)
    #
    # # convert_only arg check
    # options = arg_checker.convert_only_arg_check(options)

    # peak search args check
    options = arg_checker.peak_param_check(options)

    options = arg_checker.check_utility_args(options)

    return options

def get_pk_filename(data_files, save_to, shot_name):
    """Compiles the full path to the files with peaks data.

    :param data_files:  the list of files with signals data
    :param save_to:     the folder to save peaks data to
    :param shot_name:   the name of current shot
    :return:            full path + prefix for file name with peak data
    """
    return os.path.join(os.path.dirname(data_files[0]),
                                        save_to,
                                        PEAKDATADIR,
                                        shot_name)

def get_peak_files(pk_filename):
    """Returns the list of the peak files.
    If peak files are not found or the folder containing
    peak data is not found, returns [].

    :param pk_filename: full path + prefix of file names with peak data
    :return: list of full paths
    """
    peak_folder = os.path.dirname(pk_filename)
    file_prefix = os.path.basename(pk_filename)
    if os.path.isdir(peak_folder):
        peak_file_list = []
        for name in file_handler.get_file_list_by_ext(peak_folder, '.csv', sort=True):
            if os.path.basename(name).startswith(file_prefix):
                peak_file_list.append(name)
        return peak_file_list
    return []

def read_single_peak(filename):
    """Reads one file containing the data of the peaks.

    :param   filename:  file with peak (one group of peaks) data
    :return:            grouped peaks data with one peak (group)
                        peaks[curve_idx][0] == SinglePeak instance if
                        this curve has a peak related to this event (group),
                        else peaks[curve_idx][0] == None.
    """
    data = numpy.genfromtxt(filename, delimiter=',')

    if data.ndim == 1:
        data = np.expand_dims(data, axis=1)

    peaks = []
    curves_count = data.shape[1]
    for idx in range(curves_count):
        # new_peak = SinglePeak(time=data[idx, 1], value=data[idx, 2],
        #                       sqr_l=data[idx, 3], sqr_r=data[idx, 4])
        new_peak = SinglePeak(time=data[1, idx], value=data[2, idx],
                              sqr_l=data[3, idx], sqr_r=data[4, idx])
        if new_peak.time != 0 or new_peak.val != 0:
            peaks.append([new_peak])
            # peaks[idx].append(new_peak)
        else:
            peaks.append([None])
            # peaks[idx].append(None)
    return peaks

def read_peaks(file_list):
    """Reads all the files containing the data of the peaks.

    :param file_list:   list of files with peak (one group of peaks) data
    :return:            grouped peaks data
                        peaks[curve_idx][group_idx] == SinglePeak instance if
                        this curve has a peak related to this event (group),
                        else peaks[curve_idx][group_idx] == None.
    """
    if file_list is None or len(file_list) == 0:
        return None
    else:
        groups = read_single_peak(file_list[0])
        curves_count = len(groups)
        for file_idx in range(1, len(file_list)):
            new_group = read_single_peak(file_list[file_idx])
            for wf in range(curves_count):  # wavefrorm number
                groups[wf].append(new_group[wf][0])
        return groups

def renumber_peak_files(file_list, start=1):
    """Checks the file numbering, if the numbering is not continuous
    or does not start from the specified value,
    then renames the files and changes the file_list.

    :param file_list: the list of files names
    :param start: the numbering must begin with this value
    :return: None
    """
    n1, n2 = file_handler.numbering_parser(file_list)
    digits = n2 - n1
    short_names = [os.path.basename(name) for name in file_list]
    file_nums = [int(name[n1: n2]) for name in short_names]
    dir = os.path.dirname(file_list[0])
    name_format = '{prefix}{num:0' + str(digits) + 'd}{postfix}'
    for i in range(len(file_nums)):
        if file_nums[i] != i + start:
            new_name = (name_format.format(prefix=short_names[i][0: n1],
                                           num=i + start,
                                           postfix=short_names[i][n2:]))
            new_name = os.path.join(dir, new_name)
            os.rename(file_list[i], new_name)
            file_list[i] = new_name

def do_job(args, shot_idx):
    """Process one shot according to the input arguments:
        - applies multiplier, delay
        - finds peaks
        - groups peaks from different curves by time
        - saves peaks and peak plots
        - re-read peak files after peak plot closed
          (user may delete false-positive peak files
          while peak plot window is not closed)
        - plots and saves user specified plots and multiplots

    :param args: namespace with all input args
    :param shot_idx: the number of shot to process

    :type args: argparse.Namespace
    :type shot_idx: int

    :return: None
    """
    number_of_shots = len(args.gr_files)
    if shot_idx < 0 or shot_idx > number_of_shots:
        raise IndexError("Error! The shot_index ({}) is out of range ({} shots given)."
                         "".format(shot_idx, number_of_shots))

    file_list = args.gr_files[shot_idx]
    verbose = not args.silent
    shot_name = file_handler.get_shot_number_str(file_list[0], args.num_mask,
                                                 args.ext_list)
    # get SignalsData
    data = file_handler.read_signals(file_list,
                                     start=args.partial[0],
                                     step=args.partial[1],
                                     points=args.partial[2],
                                     labels=args.labels,
                                     units=args.units,
                                     time_unit=args.time_unit)
    if verbose:
        print("The number of curves = {}".format(data.count))

    # checks the number of columns with data,
    # and the number of multipliers, delays, labels
    args.multiplier = arg_checker.check_multiplier(args.multiplier,
                                                   count=data.count)
    args.delay = arg_checker.check_delay(args.delay,
                                         count=data.count)
    arg_checker.check_coeffs_number(data.count, ["label", "unit"],
                                    args.labels, args.units)

    # multiplier and delay
    data = multiplier_and_delay(data,
                                args.multiplier,
                                args.delay)

    # find peaks
    peaks_data = None
    if args.level:
        if verbose:
            print('LEVEL = {}'.format(args.level))

        check_curves_list(args.curves, data)

        if verbose:
            print("Searching for peaks...")

        unsorted_peaks = get_peaks(data, args, verbose)

        # step 7 - group peaks [and plot all curves with peaks]
        peaks_data = group_peaks(unsorted_peaks, args.gr_width)

        # step 8 - save peaks data
        if verbose:
            print("Saving peak data...")

        # full path without peak number and extension:
        pk_filename = get_pk_filename(file_list,
                                      args.save_to,
                                      shot_name)

        file_handler.save_peaks_csv(pk_filename, peaks_data, args.labels)

        # step 9 - save multicurve plot
        multiplot_name = pk_filename + ".plot.png"

        if verbose:
            print("Saving all peaks as " + multiplot_name)
        fig = plotter.plot_multiplot(data, peaks_data, args.curves,
                                     xlim=args.t_bounds, hide=args.peak_hide)
        pyplot.savefig(multiplot_name, dpi=300)
        if args.peak_hide:
            pyplot.close(fig)

        else:
            pyplot.show()

    if args.read:
        if verbose:
            print("Reading peak data...")

        pk_filename = get_pk_filename(file_list,
                                      args.save_to,
                                      shot_name)
        peak_files = get_peak_files(pk_filename)
        peaks_data = read_peaks(peak_files)
        renumber_peak_files(peak_files)

    # plot preview and save
    if args.plot:
        plotter.do_plots(data, args, shot_name,
                         peaks=peaks_data, verbose=verbose,
                         hide=args.p_hide)

    # plot and save multi-plots
    if args.multiplot:
        plotter.do_multiplots(data, args, shot_name,
                              peaks=peaks_data, verbose=verbose,
                              hide=args.mp_hide)

def main():
    parser = get_parser()

    # # for debugging
    # file_name = '/home/shpakovkv/Projects/PythonSignalProcess/untracked/args/peak_20150515N99.arg'
    # with open(file_name) as fid:
    #     file_lines = [line.strip() for line in fid.readlines()]
    # args = parser.parse_args(file_lines)

    args = parser.parse_args()

    verbose = not args.silent

    # try:
    args = global_check(args)

    '''
    num_mask (tuple) - contains the first and last index
    of substring of filenamepyplot.show
    That substring contains the shot number.
    The last idx is excluded: [first, last).
    Read numbering_parser docstring for more info.
    '''

    num_mask = file_handler.numbering_parser([files[0] for
                                              files in args.gr_files])
    args_dict = vars(args)
    args_dict["num_mask"] = num_mask

    if args.hide_all:
        # by default backend == Qt5Agg
        # savefig() time for Qt5Agg == 0.926 s
        #                for Agg == 0.561 s
        # for single curve with 10000 points and one peak
        # run on Intel Core i5-4460 (average for 100 runs)
        # measured by cProfile
        matplotlib.use("Agg")

    # MAIN LOOP
    import time
    start_time = time.time()
    if (args.level or
            args.read):
        # for shot_idx in range(len(args.gr_files)):
        #     do_job(args, shot_idx)
        with Pool(args.threads) as p:
            p.starmap(do_job, [(args, shot_idx) for shot_idx in range(len(args.gr_files))])
    stop_time = time.time()

    # arg_checker.print_duplicates(args.gr_files)

    print()
    print("--------- Finished ---------")
    spent = stop_time - start_time
    units = "seconds"
    if spent > 3600:
        spent /= 3600
        units = "hours"
    elif spent > 60:
        spent /= 60
        units = "minutes"

    print("--- Time spent: {:.2f} {units} for {n} shots ---".format(spent, units=units, n=len(args.gr_files)))

if __name__ == '__main__':
    main()

    # TODO: cl description
    # TODO: test refactored PeakProcess
    # TODO: refactor verbose mode


"""Models for storing VCF variant statistics information."""

import enum
import hashlib
import math
import pathlib
import typing

import attr
import cattr
import json
from logzero import logger
import vcfpy

_TGenotype = typing.TypeVar("Genotype")

class Genotype(enum.Enum):
    #: Reference homozygous.
    REF = "0/0"
    #: Heterozygous alternative.
    HET = "0/1"
    #: Homozygous alternative.
    HOM = "1/1"

    @classmethod
    def from_value(cls, value: str) -> _TGenotype:
        val = value.replace("|", "/")
        for release in cls:
            if release.value == val:
                return release
        else:  # pragma: no cover
            raise ValueError("Could not get release for value %s" % value)

@attr.s(auto_attribs=True, frozen=True)
class Site:
    """Define a single site."""

    #: The genome release
    genome_release: str
    #: The contig/chromosome name.
    chromosome: str
    #: 1-based position of the site.
    position: int
    #: Reference base string in VCF notation.
    reference: str
    #: Alternative base string in VCF notation.
    alternative: str

    def with_prefix(self, prefix):
        """Return ``Site`` having the ``"chr"`` prefix or not."""
        if prefix and not self.chromosome.startswith("chr"):
            return attr.evolve(self, chromosome="chr" + self.chromosome)
        elif not prefix and self.chromosome.startswith("chr"):
            return attr.evolve(self, chromosome=self.chromosome[3:])

    @property
    def short_notation(self) -> str:
        return "-".join(map(str, [self.genome_release, self.chromosome, self.position]))

@attr.s(auto_attribs=True, frozen=True)
class VariantStats:
    """Statistics of a variant call.

    A missing genotype indicates a "no-call".  Missing coverage information indicates that the
    VCF file that this was generated from did not provide that information or a "no-call".
    """

    #: Genotype at the site.
    genotype: typing.Optional[Genotype] = None
    #: Total coverage at the site (ref + alt).
    total_cov: typing.Optional[int] = None
    #: Variant coverage at the site (alt).
    alt_cov: typing.Optional[int] = None

@attr.s(auto_attribs=True, frozen=True)
class SiteStats:
    """Variant statistics at a site."""

    #: Site
    site: Site
    #: Variant statistics.
    stats: VariantStats

@attr.s(auto_attribs=True, frozen=True)
class Sample:
    """Information regarding a sample."""

    #: Sample identifier.
    name: str

@attr.s(auto_attribs=True, frozen=True)
class SampleStats:
    """Information about variant statistics per sample."""

    #: The sample information.
    sample: Sample
    #: The site-wise variant statistics.
    site_stats: typing.List[SiteStats]

@attr.s(auto_attribs=True, frozen=True)
class SimilarityPair:
    """Store information about the similarity of a sample pair.

    By convention, the first sample is the lexicographically smaller one.
    """

    #: First sample.
    sample_i: str
    #: Second sample.
    sample_j: str
    #: Number of sites sharing no allele.
    n_ibs0: int
    #: Number of sites sharing one allele.
    n_ibs1: int
    #: Number of sites sharing both alleles.
    n_ibs2: int
    #: Number of sites where sample i is heterozygous.
    het_i: int
    #: Number of sites where sample j is heterozygous.
    het_j: int
    #: Number of sites where both samples are heterozygous.
    het_i_j: int

    @property
    def relatedness(self):
        """Compute peddy relatedness."""
        return (self.het_i_j - 2 * self.n_ibs0) / (0.5 * math.sqrt(self.het_i * self.het_j))

    @property
    def key(self):
        return (self.sample_i, self.sample_j)

def read_sites(
    *,
    path: typing.Optional[typing.Union[str, pathlib.Path]] = None,
    stream: typing.Optional[vcfpy.Reader] = None,
    genome_release: typing.Optional[str] = None,
    max_sites: typing.Optional[int] = None,
) -> typing.List[Site]:
    """Load sites from the given VCF file."""
    if not genome_release:
        raise ValueError("genome_release must be given")  # pragma: no cover
    if bool(path) == bool(stream):
        raise ValueError("Exactly one of path and stream must be provided")  # pragma: no cover
    else:
        if path:
            reader = vcfpy.Reader.from_path(path)
        else:
            reader = vcfpy.Reader.from_stream(stream)

        result = []
        for record in reader:
            for lineno, record in enumerate(reader):
                if not max_sites or lineno < max_sites:
                    result.append(
                        Site(
                            genome_release=genome_release,
                            chromosome=record.CHROM,
                            position=record.POS,
                            reference=record.REF,
                            alternative=record.ALT[0].value,
                        )
                    )
                else:
                    break  # pragma: no cover

    return result

def hash_sample_id(sample_id: str) -> str:
    return hashlib.sha256(sample_id.encode("utf-8")).hexdigest()

def sample_path(storage_path: str, sample_id: str) -> pathlib.Path:
    sample_hash = hash_sample_id(sample_id)
    output_path = (
        pathlib.Path(storage_path)
        / sample_hash[:2]
        / sample_hash[:4]
        / (sample_hash + "-stats.json")
    )
    return output_path

def write_site_stats(site_stats: typing.List[SiteStats], storage_path: str, sample_id: str) -> str:
    """Write site stats to the storage path and return path to JSON."""
    output_path = sample_path(storage_path, sample_id)
    output_path.parent.mkdir(parents=True, exist_ok=True)
    logger.info("Writing results to %s", output_path)
    with output_path.open("wt") as outputf:
        json.dump(cattr.unstructure(site_stats), outputf)
    return output_path


import os
import json
import torch
from simpletransformers.question_answering import QuestionAnsweringModel
from evaluate import in_eval

def create_parentDir(path, exist_ok=True):
    head, tail = os.path.split(path)
    os.makedirs(head, exist_ok=exist_ok)

def read_data(train_file, dev_file, test_file=None):
    train_data = json.load(open(train_file, encoding='utf-8'))
    train_data = [item for topic in train_data['data'] for item in topic['paragraphs']]
    dev_data = json.load(open(dev_file, encoding='utf-8'))
    dev_data = [item for topic in dev_data['data'] for item in topic['paragraphs'] ]
    if test_file:
        test_data = json.load(open(test_file, encoding='utf-8'))
        test_data = [item for topic in test_data['data'] for item in topic['paragraphs']]
        return train_data, dev_data, test_data
    else:
        return train_data, dev_data

def save_json(data, file):
    create_parentDir(file)
    with open(file, 'w', encoding='utf-8') as f:
        json.dump(data, f, indent=1)
    print(f'data -> {file}')

def split_preds(preds):
    submission = {}
    n_submission = {}
    a_str, a_pro = preds
    for i in range(len(a_str)):
        assert a_str[i]['id'] == a_pro[i]['id']
        id = a_str[i]['id']
        a = sorted(zip(a_str[i]['answer'], a_pro[i]['probability']), key=lambda x: x[1], reverse=True)[0][0]
        submission[id] = a
        n_submission[id] = a_str[i]['answer']
    return submission, n_submission

train_args = {
    'n_gpu': 2,
    'learning_rate': 5e-5,
    'max_seq_length': 384,
    'max_answer_length': 30,
    'doc_stride': 128,
    'num_train_epochs': 2,
    'train_batch_size': 24,
    'eval_batch_size': 24,
    'gradient_accumulation_steps': 1,
    'warmup_ratio': 0.0,
    'manual_seed': 42,
    'do_lower_case': True,
    'reprocess_input_data': True,
    'output_dir': 'outputs/',
    'save_model_every_epoch': False,
    'save_eval_checkpoints': False,
    'save_optimizer_and_scheduler': True,
    'save_steps': -1, # -1 is disable
    'overwrite_output_dir': True,
    'evaluate_during_training': False,
    'best_model_dir': 'outputs3/best/'
}

bert_base_uncased_file = '../../pretrained_data/bert-base-uncased'

os.environ['CUDA_VISIBLE_DEVICES']="6,7"
train_args['n_gpu'] = torch.cuda.device_count()

## search train and eval without num ********************************
lrs = [3e-5]  # 3e-5, 5e-5, 7e-5
num_epoch = 2
gradient_accumulation_step = 1
batch_sizes = [12] # 6, 12, 24
train_file, dev_file, test_file = 'data/train.json', 'data/dev.json', 'data/test.json'
train_data, dev_data, test_data = read_data(train_file, dev_file, test_file)

for batch_size in batch_sizes:
    for lr in lrs:
        if lr == 3e-5 and batch_size == 24:
            continue
        if lr == 7e-5 and batch_size == 6:
            continue
        output_path = f'outputs' + str(lr) + '_' + str(batch_size*gradient_accumulation_step)
        # if os.path.exists(output_path):
        #     continue
        train_args['output_dir'] = output_path
        train_args['learning_rate']  = lr
        train_args['num_train_epochs'] = num_epoch
        train_args['gradient_accumulation_steps'] = gradient_accumulation_step
        train_args['train_batch_size'] = batch_size
        model = QuestionAnsweringModel('bert', bert_base_uncased_file, args=train_args)
        model.train_model(train_data, eval_data=None)
        model.eval_model(dev_data, output_dir=f'{output_path}/eval/')
        preds, n_preds = split_preds(model.predict(test_data))
        os.makedirs(f'{output_path}/pred', exist_ok=True)
        save_json(preds, f'{output_path}/pred/predict.json')
        # save_json(n_preds, f'{output_path}/pred/n_predict.json')
        print(f"lr: {lr}, batch:{batch_size*gradient_accumulation_step}, last eval: {in_eval(dev_file, f'{output_path}/eval/predictions_test.json')}")
        print(f"lr: {lr}, batch:{batch_size*gradient_accumulation_step}, last test: {in_eval(test_file, f'{output_path}/pred/predict.json')}")
## end *************************************


import sys
sys.stdin = open('input.txt', 'r')

for test_case in range(1,11):
    q = int(input())    # 쓰레기값
    print('#{} '.format(test_case), end='')
    arr = []
    for _ in range(100):
        arr.append(list(map(int, input().split())))

    # 도착점 찾기
    k = 0
    for j in range(100):
        if arr[99][j] == 2:
            k = j
            break

    i = 98
    #print(k)
    flag = 0
    #print('====={}====='.format(k))
    # 첫 시작점을 찾은 다음
    while 1:
        #print(i, k)
        # 오른쪽으로 가야할 때
        if k + 1 <= 99 and arr[i][k + 1] == 1 and flag == 1:
            k += 1
            flag = 1
        # 왼쪽으로 가야할 때
        elif k - 1 >= 0 and arr[i][k - 1] == 1 and flag == -1:
            k -= 1
            flag = -1
        # 위 오른쪽도 있을 때
        elif k + 1 <= 99 and arr[i - 1][k + 1] == 1: #and i - 1 >= 0
            i -= 1
            flag = 1
        # 위 왼쪽도 있을 때
        elif k - 1 >= 0 and arr[i - 1][k - 1] == 1: #and i - 1 >= 0
            i -= 1
            flag = -1
        # 아래만 있을 때
        elif arr[i][k] == 1:
            i -= 1

        if i == 0:
            result = k
            break

    print(result)


#####################################################################
#
# CAS CS 320, Spring 2015
# Midterm (skeleton code)
# analyze.py
#
#  ****************************************************************
#  *************** Modify this file for Problem #5. ***************
#  ****************************************************************
#

exec(open("parse.py").read())

Node = dict
Leaf = str

def typeExpression(env, e):
    if type(e) == Leaf:
        if e == 'True':
            return 'Boolean'
        if e == 'False':
            return 'Boolean'
    if type(e) == Node:
        for label in e:
            children = e[label]
            if label == 'Number':
                return 'Number'
            elif label == 'Variable':
                x = children[0]['Variable'][0]
                if x in env and env[x] == 'Number':
                    return 'Number'
            elif label == 'Plus':
                [e1, e2] = children
                if typeExpression(env, e1) == 'Number' and typeExpression(env, e2) == 'Number':
                    return 'Number'
            elif label == 'Array':
                [x, e] = children
                x = x['Variable'][0]
                if x in env and env[x] == 'Array' and typeExpression(env, e) == 'Number':
                    return 'Number'
            return None

def typeProgram(env, s):
    if type(s) == Leaf:
        if s == 'End':
            return 'Void'
    elif type(s) == Node:
        for label in s:
            if label == 'Print':
                [e, p] = s[label]
                if typeProgram(env, p) == 'Void':
                    if typeExpression(env, e) == 'Boolean' or\
                       typeExpression(env, e) == 'Number':
                        return 'Void'
            elif label == 'Assign':
                [x, e0, e1, e2, p] = s[label]
                x = x['Variable'][0]
                if typeExpression(env, e0) == 'Number' and\
                   typeExpression(env, e1) == 'Number' and\
                   typeExpression(env, e2) == 'Number':
                     env[x] = 'Array'
                     if typeProgram(env, p) == 'Void':
                           return 'Void'
            elif label == 'For':
                [x, p1, p2] = s[label]
                x = x['Variable'][0]
                env[x] = 'Number'
                if typeProgram(env, p1) == 'Void' and typeProgram(env, p2) == 'Void':
                    return 'Void'
            return None

#eof


#!/usr/bin/python3

# HW3
# Vivek Khanolkar
# vkhanolk
# 2/11/2021

#Following code modified from FindMI.py

import sys

if len(sys.argv) != 3:
    sys.stderr.write("Usage: %s   <integer>   <modulus>\n" % sys.argv[0])
    sys.exit(1)

NUM, MOD = int(sys.argv[1]), int(sys.argv[2])

# utilizing russianPeasant method to bitwise multiply (had to modify slightly
# since wasn't working with some negative test cases)

def russianPeasant(a, b):
    ans = 0
    if (b < 0) :
        b = -b
        a = -a
    while (b > 0):
        if (b & 1): #if b is odd
            ans = ans + a

        a = a << 1
        b = b >> 1

    return ans

def divider(x, y):
    count = 0
    while (x > y):
        x -= y
        count += 1

    return count

def MI(num, mod):
    '''
    This function uses ordinary integer arithmetic implementation of the
    Extended Euclid's Algorithm to find the MI of the first-arg integer
    vis-a-vis the second-arg integer.
    '''
    #print("ANS: ", russianPeasant(-2, -5))
    #print("DIV: ", divider(15, 2))
    NUM = num; MOD = mod
    x, x_old = 0, 1
    y, y_old = 1, 0
    while mod:
        q = divider(num, mod)
        num, mod = mod, num % mod
        x, x_old = x_old - russianPeasant(q,x), x
        y, y_old = y_old - russianPeasant(q,y), y
        # print("Q: ", q)
        # print("num: ", num)
        # print("mod: ", mod)
        # print("X: ", x, "X_OLD: ", x_old)
        # print("Y: ", y, "Y_OLD: ", y_old)
        # print("________________________________")
    if num != 1:
        print("\nNO MI. However, the GCD of %d and %d is %u\n" % (NUM, MOD, num))
    else:
        MI = (x_old + MOD) % MOD
        print("\nMI of %d modulo %d is: %d\n" % (NUM, MOD, MI))

MI(NUM, MOD)


# modified https://www.tutorialspoint.com/python_data_structure/python_graph_algorithms.htm

from operator import add

class graph:
   def __init__(self,gdict=None):
      if gdict is None:
         gdict = {}
      self.gdict = gdict

pq = []
path = []
pqh = []

def ufc(graph, start, cost, goal, explored = None ):
   global pq, path, pqh

   if explored is None:
      explored = set()
      pqh.append([[start,cost]]) #need for backtracing
   explored.add(start)

   #Print exploring in real-time
   #print("exploring " + start)

   #check if goal has been explored
   if( start == goal):
      backtrace_optimal([start,cost])
      return True #stop upper recursive calls

   #add to adjacent node to priority queue
   for list1 in graph[start]:
      pq.append([list1[0], (list1[1] + cost)])

   # sort by cumulative cost; manually maintain priortiy queue
   pq.sort(key=lambda x:x[1])

   #cyclic pruning
   pq = prune_visited(pq, explored)

   # debug show priority queue
   # printll(pq)

   #keep track of history
   pqh.append(pq)

   #return False and annouce failure to find goal
   if (len(pq) == 0):
      print("No more to explore; therefore couldn't find goal")
      return False

   #pop least cost
   ll = pq[0]
   pq = pq[1:]

   #continue algorithm
   if(ufc(graph, ll[0], ll[1], goal, explored)):
      return True
   return False

def prune_visited(listoflist, visited):
   rtn_ll = []
   for list1 in listoflist:
      if (list1[0] not in visited):
         rtn_ll.append(list1)
   return rtn_ll

def backtrace_optimal(final):
   global pqh

   #print cost of optimal path
   print("\tOptimal Path Cost: " + str(final[1]))

   #add final(aka self) to path
   path=[final[0]]

   #backtrack
   pqh.reverse()
   for sub_pqh_ll in pqh:
      if (final not in sub_pqh_ll):
         final = sub_pqh_ll[0]
         path.append(final[0])
   path.reverse()

   #print
   print("\tOptimal Path: ",end='')
   for letter1 in path:
      print(letter1,end=" ")

def printll(ll):
   for list1 in ll:
      print("[" + str(list1[0]) + "," + str(list1[1]) + "],", end="")
   print("")

#graph
gdict = {
   "s" : [["b",2],["c",1],["d",10]],
   "b" : [["e",7],["s",2]],
   "c" : [["g",15], ["s",1]],
   "d" : [["s",10]],
   "e" : [["b",7],["f",1],["g",2]],
   "f" : [["e",1],["g",3]],
   "g" : [["c",15],["e",2],["f",3]]
}
# heuristic table
hdict = {
   "s" : 9,
   "b" : 7,
   "c" : 10,
   "d" : 7,
   "e" : 1,
   "f" : 1,
   "g" : 0
}

print("Uniform Cost")
ufc(gdict, "s", 0, "g")


import sys, os, struct, ctypes, ctypes.util, socket, Queue, threading
from os.path import *
sys.path.append(dirname(dirname(abspath(__file__))))
from kitchen import *
from rfid.rfid_tag_read import *
from utils.general_utils import *

def encode(x):
    if type(x) == str: # python2.x
        return x.encode('hex')
    elif type(x) == int: # python3.x
        return "{0:02x}".format(x)
    else:
        raise Exception("cannot encode data of type %s" % str(type(x)))

def hexToSignedChar(h):
    x = int(h, 16)
    # if > 127, subtract off 256 (because negative)
    if x > 0x7F: x -= 0x100
    return x

class BleBeaconRead():
    """Class for storing BLE Beacon read data.

    Attributes:
       time_str: acquisition time string
       time: acquisition time.
       addr: Beacon address.
       rssi: Beacon RSSI.
    """
    def __init__(self, time_str, addr, rssi):
        """Initialize read with data."""
        self.time_str, self.time = time_str, timeStringToDateTime(time_str)
        self.addr, self.rssi = addr, int(rssi)
    def __str__(self):
        return "%s %s %d" % (self.time_str, self.addr, self.rssi)

class BleReader():
    """Class for reading BLE Beacons.

    Attributes:
       addrs: device addresses to listen for if None if want all.
       max_time: max_time to store tag before discarding.
       data:  Beacon read list.
       listen_thread: Thread that listens to the Java program.
    """
    def __init__(self, addrs=None, max_time=1.0):
        self.addrs = addrs
        self.max_time = max_time
        self.data = Queue.Queue()
        self.listen_thread = threading.Thread(target=self._listen)
        self.listen_thread.daemon = True; self.listen_thread.start()

    def release(self):
        self.listen_thread.join()

    def _listen(self):
        # Setup BLE reading.
        if not os.geteuid() == 0:
            raise Exception("BLE reading requires sudo.")
        btlib = ctypes.util.find_library("bluetooth")
        if not btlib:
            raise Exception("Can't find required bluetooth libraries (install bluez)")
        bluez = ctypes.CDLL(btlib, use_errno=True)
        dev_id = bluez.hci_get_route(None)
        hci_filter = struct.pack("<IQH", 0x00000010, 0x4000000000000000, 0) # allows LE advertising events
        sock = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_RAW, socket.BTPROTO_HCI)
        sock.bind((dev_id,))
        sock.setsockopt(socket.SOL_HCI, socket.HCI_FILTER, hci_filter)
        err = bluez.hci_le_set_scan_parameters(sock.fileno(), 0, 0x10, 0x10, 0, 0, 1000);
        if err < 0 and ctypes.get_errno() != 5:
            raise Exception("Set scan parameters failed")
        elif err >= 0:
            err = bluez.hci_le_set_scan_enable(sock.fileno(), 1, 0, 1000)
            if err < 0:
                raise Exception("Set scan enable failed")
        # Loop to read.
        while True:
                data = sock.recv(1024)
                addr = ':'.join([encode(x) for x in data[12:6:-1]])
                rssi = hexToSignedChar(encode(data[-1]))
                t_str = dateTimeToTimeString(datetime.datetime.utcnow())
                if self.addrs==None or addr in self.addrs:
                    self.data.put(BleBeaconRead(t_str, addr, rssi))
                while not self.data.empty():
                    t = datetime.datetime.utcnow()
                    try:
                        td = (t-self.data.queue[0].time).total_seconds()
                        if td > self.max_time:
                            self.data.get()
                        else: break
                    except Exception as e:
                        # Queue might have been emptied between checking empty and accessing.
                        pass

    def read(self):
        beacon_reads = []
        while not self.data.empty():
            beacon_reads.append(self.data.get_nowait())
        return beacon_reads

class BeaconPresenceClassifier:
    """Class for classifying whether or not an Beacon is present."""
    def eval(self, data, stable_start, stable_end):
        score = 0.0
        for (time, rssi) in data:
            score += (time>stable_start and time<stable_end) and rssi >= -69
        return score

class BeaconArrivalClassifier:
    """Class for classifying whether or not an Beacon just arrived."""
    def eval(self, data, prev_stable_start, prev_stable_end, stable_start, stable_end):
        score = 0.0
        for (time, rssi) in data:
            score += (time>stable_start and time<stable_end) and rssi >= -69
            score -= (time>prev_stable_start and time<prev_stable_end) and rssi >= -69
        return score

class BeaconRemovalClassifier:
    """Class for classifying whether or not an Beacon just arrived."""
    def eval(self, data, prev_stable_start, prev_stable_end, stable_start, stable_end):
        score = 0.0
        for (time, rssi) in data:
            score -= (time>stable_start and time<stable_end) and rssi >= -69
            score += (time>prev_stable_start and time<prev_stable_end) and rssi >= -69
        return score


import sympy

def perturbed_quants(terms, order):
    ep = sympy.symbols('epsilon', real=True)
    replacements = []
    new_vars = []
    for term in terms:
        raw = str(term)
        expanded = [ raw+'%d'%expand for expand in range(order+1)]
        symbs = sympy.symbols(' '.join(expanded), real=True)
        total = 0
        for ind in range(order+1):
            total += (symbs[ind]*ep**ind)
        replacements.append((term, total))
        new_vars.append(symbs)
    return replacements, new_vars

def main():
    pth, pr, th, r = sympy.symbols('p_theta p_r theta r', real=True)
    alph, m, n, ep = sympy.symbols('alpha m n epsilon', real=True)
    coords = [th, r]
    momenta = [pth, pr]
    sympy.pprint(momenta)
    sympy.pprint(coords)
    ham = (pth**2*r**(-2)+pr**2)/(2*m)+alph*r**n
    sympy.pprint(ham)
    pr_dot = -ham.diff(r)
    sympy.pprint(pr_dot)
    pth0 = sympy.solve(pr_dot, pth)[1]
    perturbing, new_vars = perturbed_quants([r], 2)
    pth0 = pth0.subs(r, new_vars[0][0])
    sympy.pprint(pth0)
    pr_dot1 = pr_dot.subs(pth, pth0).expand().simplify()
    sympy.pprint(pr_dot1)
    # sympy.pprint(pr_dot1.expand().simplify())
    sympy.pprint(perturbing)
    pr_dot2 = pr_dot1.subs(perturbing)
    sympy.pprint(pr_dot2)
    sympy.pprint(pr_dot2.expand().simplify().collect(ep))

if __name__ == '__main__':
    main()
'''
austin shenans
laser tag?
http://www.blazertag.com/
esther's follies?
https://www.esthersfollies.com/
http://www.austincityguide.com/listings/south-congress-avenue-shopping
escape rooms?
AR adventure?
https://www.thearadventureaustin.com/
transcendental brunch
http://moonshinegrill.com/menus/
'''


trip_price = float(input())
puzzle_count = int(input())
doll_count = int(input())
bear_count = int(input())
minions_count = int(input())
truck_count = int(input())
order_sum = puzzle_count * 2.6 + doll_count * 3 + bear_count * 4.1 \
            + minions_count * 8.2 + truck_count * 2
#print(order_sum)
number_toys = puzzle_count + doll_count + bear_count \
            + minions_count + truck_count
if number_toys >= 50:
    order_sum = order_sum * 0.75
rent = order_sum * 0.1
net_profit = order_sum - rent
diff = abs(net_profit - trip_price)

if net_profit >= trip_price:
    print(f'Yes! {diff:.2f} lv left.')
else:
    print(f'Not enough money! {diff:.2f} lv needed.')


from tkinter import filedialog as fd
from tkinter import messagebox
from PIL import Image
import customtkinter as ctk
import requests
from io import BytesIO
from PIL import Image
import os

def select_file():
    filetypes = (
        ('All files', '*.*'),
        ('text files', '*.txt'),
    )

    file_path = fd.askopenfilenames(
        title='Open a file',
        initialdir='/',
        filetypes=filetypes
    )
    return file_path

def open_ctk_img(file_path, size=None):
    # Cria um objeto de imagem
    img = Image.open(file_path)
    if size == None:
        img = ctk.CTkImage(light_image=img)
    else:
        img = ctk.CTkImage(light_image=img, size=size)
    return img

def get_image(image_url, size=(200, 200), resize=True):
    response = requests.get(image_url)
    img = Image.open(BytesIO(response.content))
    if (resize == False):
        return img

    return img.resize(size)

def saveResults(image_paths):
    save_path = fd.askdirectory()
    folder_name = "Results_CIICAM"

    # Create the full path by joining the downloads path and folder name
    parent_folder_path = os.path.join(save_path, folder_name)

    # Create the folder
    if not os.path.exists(parent_folder_path):
        os.makedirs(parent_folder_path)

    print("Folder created at:", parent_folder_path)

    for i, page in enumerate(image_paths):

        # Specify the name of the folder you want to create
        folder_name = "Image_"+str(i+1)

        # Create the full path by joining the parent folder path and folder name
        folder_path = os.path.join(parent_folder_path, folder_name)

        # Create the folder
        if not os.path.exists(folder_path):
            os.makedirs(folder_path)

        for j, list_path in enumerate(page):

            if (j == 0):
                image = Image.open(list_path)
                image_name = 'original_'+str(i+1)+'.jpg'
            else:
                image = get_image(list_path, resize=False)
                image_name = 'similarity'+str(j)+'.jpg'

            image.save(os.path.join(folder_path, image_name))

    print("Images saved in:", parent_folder_path)

def save_images(images,names = [], save_path = '', ask_path = False):
    save_path = fd.askdirectory() if ask_path else save_path
    if save_path != '':
        names = names if len(names)!= 0 else [i for i in range(len(images))]
        if not os.path.exists(save_path):
            os.makedirs(save_path)

        for i in range(len(images)):
            caminho_imagem = os.path.join(save_path, f"{names[i]}.png")
            images[i].save(caminho_imagem)
    else:
         messagebox.showinfo("Alerta", "Nenhum diretório foi selecionado!")


"""
Nが200,000もある
2つを選ぶと10**10となり間に合わない
Nの時間計算量で求める必要がある
"""
from collections import Counter

N = int(input())
A = list(map(int, input().split()))
C = Counter(A)

ans = 0
for combi in [(100,400), (200,300)]:
    l, r = combi
    ans += C[l] * C[r]
print(ans)


number = 1
while number < 5:
    print(number)
    # number++ #py中没有num++运算符号
    number += 1
    # number = number+1
prompt = 'Tell me a num'
prompt += '\nEnter quit to end the program '
message = ''
while message != 'quit':  # 等于quit就退出
    message = input(prompt)
    message != 'quit' and print(message)  # python中与操作符号是and


import torch
import torch.nn.functional as F
import math

"""
DISCLAIMER: most of these functions were implemented by me (Vaclav Vavra)
during the MPV course in the Spring semester of 2020, mostly with the help
of the provided template.
"""
def get_gausskernel_size(sigma, force_odd = True):
    ksize = 2 * math.ceil(sigma * 3.0) + 1
    if ksize % 2 == 0 and force_odd:
        ksize +=1
    return int(ksize)

def gaussian_filter2d(x: torch.Tensor, sigma: float) -> torch.Tensor:
    r"""
    DISCLAIMER: this is a function implemented by me (Vaclav Vavra)
    during the MPV course in spring semester 2020 with the help of the provided
    template.

    Function that blurs a tensor using a Gaussian filter.

    Arguments:
        sigma (Tuple[float, float]): the standard deviation of the kernel.

    Returns:
        Tensor: the blurred tensor.

    Shape:
        - Input: :math:`(B, C, H, W)`
        - Output: :math:`(B, C, H, W)`

    """
    ksize = get_gausskernel_size(sigma)
    kernel_inp = torch.linspace(-float(ksize // 2), float(ksize // 2), ksize)
    kernel1d = gaussian1d(kernel_inp, sigma).reshape(1, -1)
    outx = filter2d(x, kernel1d)
    out = filter2d(outx, kernel1d.t())
    return out

def gaussian1d(x: torch.Tensor, sigma: float) -> torch.Tensor:
    '''
    DISCLAIMER: this is a function implemented by me (Vaclav Vavra)
    during the MPV course in spring semester 2020 with the help of the provided
    template.

    Function that computes values of a (1D) Gaussian with zero mean and variance sigma^2
    '''
    coef = 1./ (math.sqrt(2.0*math.pi)*sigma)
    out = coef*torch.exp(-(x**2)/(2.0*sigma**2))
    return out

def spatial_gradient_first_order(x: torch.Tensor, mask=torch.tensor([[0.5, 0, -0.5]]).float(), smoothed: bool = False, sigma: float = 1.0) -> torch.Tensor:
    r"""
    DISCLAIMER: this is a function implemented by me (Vaclav Vavra)
    during the MPV course in spring semester 2020 with the help of the provided
    template.

    Computes the first order image derivative in both x and y directions using Gaussian derivative

    Return:
        torch.Tensor: spatial gradients

    Shape:
        - Input: :math:`(B, C, H, W)`
        - Output: :math:`(B, C, 2, H, W)`

    """
    #b, c, h, w = x.shape
    if smoothed:
        filtered_input = gaussian_filter2d(x, sigma)
    else:
        filtered_input = x
    outx = filter2d(filtered_input, mask)
    outy = filter2d(filtered_input, mask.t())
    return outx, outy

def filter2d(x: torch.Tensor, kernel: torch.Tensor) -> torch.Tensor:
    """
    DISCLAIMER: this is a function implemented by me (Vaclav Vavra)
    during the MPV course in spring semester 2020 with the help of the provided
    template.

    Function that convolves a tensor with a kernel.

    The function applies a given kernel to a tensor. The kernel is applied
    independently at each depth channel of the tensor. Before applying the
    kernel, the function applies padding according to the specified mode so
    that the output remains in the same shape.

    Args:
        input (torch.Tensor): the input tensor with shape of
          :math:`(B, C, H, W)`.
        kernel (torch.Tensor): the kernel to be convolved with the input
          tensor. The kernel shape must be :math:`(kH, kW)`.
    Return:
        torch.Tensor: the convolved tensor of same size and numbers of channels
        as the input.
    """
    assert len(kernel.size()) == 2
    assert len(x.size()) == 4
    b, c, h, w = x.shape
    height, width = kernel.size()
    tmp_kernel = kernel[None,None,...].to(x.device).to(x.dtype)
    padding_shape = [width // 2, width // 2, height // 2, height // 2]
    input_pad: torch.Tensor = F.pad(x, padding_shape, mode='replicate')
    out = F.conv2d(input_pad,
                   tmp_kernel.expand(c, -1, -1, -1),
                   groups=c,
                   padding=0,
                   stride=1)
    return out


import configparser
from tkinter import ttk
import datetime
from datetime import timedelta
import tkinter as tk
import os
from distutils.dir_util import copy_tree
from datepicker import Datepicker
# import win32print

class Data(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent, controller)
        self.controller = controller

        self.data = datetime.date.today()
        print(self.data - timedelta(days=1))

        self.config = self.leggi_file_ini()

        self.mesi_dict = {'Gennaio': 1, 'Febbraio': 2, 'Marzo': 3,
                          'Aprile': 4, 'Maggio': 5, 'Giugno': 6,
                          'Luglio': 7, 'Agosto': 8, 'Settembre': 9,
                          'Ottobre': 10, 'Novembre': 11, 'Dicembre': 12}

        # STRINGVAR
        self.data_scelta = tk.StringVar()
        self.data_scelta.set(self.data.strftime('%d-%m-%Y'))

        # LABELFRAME Date
        self.lblfrm_intervallo_date = tk.LabelFrame(self,
                                                    text='Data da elaborare',
                                                    labelanchor='n',
                                                    font=(self.config['Font']['font'], 20),
                                                    foreground='blue')

        # DATEPICKER
        self.picker = Datepicker(self.lblfrm_intervallo_date, dateformat='%d-%m-%Y', datevar=self.data_scelta)

        # COMBOBOX per selezione mese
        self.cmb_box_mese = ttk.Combobox(self.lblfrm_intervallo_date,
                                         state='readonly',
                                         values=list(self.mesi_dict.keys()))
        self.cmb_box_mese.current(0)
        self.cmb_box_mese.bind('<<ComboboxSelected>>', self.combo_selected)

        # CHECKBUTTON per selezionare data 'ieri'
        self.ieri = tk.Checkbutton(self.lblfrm_intervallo_date, text="Ieri", command=self._ieri)

        # LAYOUT
        self.lblfrm_intervallo_date.grid()
        self.picker.grid()
        self.ieri.grid(sticky='w')
        self.cmb_box_mese.grid()

    @staticmethod
    def leggi_file_ini():
        ini = configparser.ConfigParser()
        ini.read('config.ini')
        return ini

    def combo_selected(self, event):
        self.data_scelta.set('2017-' + '0' + str(self.mesi_dict[self.cmb_box_mese.get()]))

    def _ieri(self):
        self.data_scelta.set((self.data - timedelta(days=1)).strftime('%d-%m-%Y'))

if __name__ == "__main__":
    root = tk.Tk()
    main = tk.Frame(root)
    x = (root.winfo_screenwidth() - root.winfo_reqwidth()) / 2
    y = (root.winfo_screenheight() - root.winfo_reqheight()) / 2
    root.geometry("600x300+%d+%d" % (x, y))
    root.title('PyInsta')
    notebook = ttk.Notebook(main)
    tab1 = Data(notebook, main)
    notebook.add(tab1, text='Data', compound='left')
    main.grid()
    notebook.grid()
    root.mainloop()


import gym
import sys
import itertools
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
from queue import Queue
from agent import Agent

def is_int(string):
    try: return int(string) > 0
    except: return False

def process(state):
    batch = []
    for frame in state:
        image = Image.fromarray(frame).convert('L')
        matrix = np.array(image)
        for x in range(85, 96):
            for y in range(96):
                matrix[x][y] = 0
        batch += [matrix / 255]
    return np.array(batch).transpose(1, 2, 0)

args = sys.argv[1:]
if len(args) in [2, 3] and args[0] in ['train', 'continue'] and is_int(args[1]):
    mode = args[0]
    episodes = int(args[1])
    show = 'human' if len(args) == 3 and args[2] == 'show' else 'rgb_array'
elif len(args) == 1 and args[0] == 'test':
    mode = args[0]
    show = 'human'
else:
    print('wrong format')
    exit(1)

steering = [-1, 0, 1]
gas = [0, 1]
breaking = [0, .2]
agent = Agent(
    list(itertools.product(steering, gas, breaking)), 500, 50,
    alpha=.01, gamma=.95, epsilon=1, epsilon_lower=.1, epsilon_decay=.99
)

env = gym.make('CarRacing-v2', render_mode=show)
if mode in ['continue', 'test']:
    agent.load()

if mode == 'test':
    should_break = False

    def take_action(action):
        global frames, should_break
        step_reward = 0
        step_game_over = False
        for _ in range(3):
            observation, reward, game_over, _, _ = env.step(action)
            step_reward += reward
            step_game_over |= game_over
        should_break |= step_game_over
        frames.get()
        frames.put(observation)
        return process(frames.queue), step_reward, step_game_over

    frames = Queue(3)
    observation, _ = env.reset()
    frames.put(observation)
    frames.put(observation)
    frames.put(observation)
    for _ in range(20):
        take_action((0, .5, 0))
    while not should_break:
        agent.step(process(frames.queue), take_action)
    exit(0)

rewards = []
for episode in range(1, episodes + 1):
    episode_reward = 0
    negative_rewards = 0
    should_break = False

    def take_action(action):
        global frames, episode_reward, negative_rewards, should_break
        step_reward = 0
        step_game_over = False
        for _ in range(3):
            observation, reward, game_over, _, _ = env.step(action)
            step_reward += reward
            step_game_over |= game_over
        episode_reward += step_reward
        negative_rewards = negative_rewards + 1 if step_reward < 0 else 0
        should_break |= step_game_over
        frames.get()
        frames.put(observation)
        return process(frames.queue), step_reward, step_game_over

    frames = Queue(3)
    observation, _ = env.reset()
    frames.put(observation)
    frames.put(observation)
    frames.put(observation)
    for _ in range(20):
        take_action((0, .5, 0))
    step = 0
    while not should_break:
        agent.step(process(frames.queue), take_action)
        step += 1
        negative_rewards = 0 if step < 50 + episode else negative_rewards
        should_break |= negative_rewards == 20
    agent.replay()
    if episode % 5 == 0:
        agent.calibrate()
    agent.save()
    rewards += [episode_reward]
    print(f'episode {episode}/{episodes}: reward {episode_reward}')

plt.plot(range(len(rewards)), rewards)
plt.show()


import math
from sys import stdin
from collections import defaultdict

class exist_negative_cycle(Exception):
    pass

inf = float('inf')

# ベルマンフォード
# O(E + V)
def bellman_ford(g, size, start=0):
    d = [inf] * size
    d[start] = 0

    for _ in range(size):
        for u in g:
            for v, d in g[u]:
                if d[v] > d[u] + d:
                    d[v] = d[u] + d

    for u in g:
        for v, d in g[u]:
            if d[v] > d[u] + d:
                raise exist_negative_cycle
    return d

v, e, r = map(int, input().split())

g = defaultdict(list)
for i in range(e):
    s, t, d = map(int, readline().split())
    g[s].append((t, d))

try:
    d = bellman_ford(g, v, r)
    for di in d:
        print('INF' if math.isinf(di) else di)

except exist_negative_cycle:
    print('NEGATIVE CYCLE')


from lxml import html
from lxml.etree import XPath

TBODY_XPATH = XPath('//table[@class="observations"]/tbody')
OBSERVATION_XPATH = XPath('./td//text()[normalize-space()]')
DETAILS_XPATH = XPath('./td/div/table/tbody/tr/td//text()')

def _clean_cell(value):
    """
    Removes dashes and strips whitespace from the given value.
    """
    return value.replace('\u2014', '').strip()

class WebObsResultsParser(object):
    """
    Parser for WebObs search results page.

    The parser reads an HTML page with search results (presented as a table)
    and parses the table into a list of observations.
    """

    def __init__(self, html_source):
        """
        Creates the parser and feeds it source code of the page.
        """
        self.empty = "There were no results for this search." in html_source
        if not self.empty:
            root = html.fromstring(html_source)
            self.tbody = TBODY_XPATH(root)[0]

    def get_observations(self):
        """
        Parses the HTML table into a list of dictionaries, each of which
        represents a single observation.
        """
        if self.empty:
            return []
        rows = list(self.tbody)
        observations = []
        for row_observation, row_details in zip(rows[::2], rows[1::2]):
            data = {}
            cells = OBSERVATION_XPATH(row_observation)
            data['name'] = _clean_cell(cells[0])
            data['date'] = _clean_cell(cells[1])
            data['magnitude'] = _clean_cell(cells[3])
            data['obscode'] = _clean_cell(cells[6])
            cells = DETAILS_XPATH(row_details)
            data['comp1'] = _clean_cell(cells[0])
            data['chart'] = _clean_cell(cells[3]).replace('None', '')
            data['comment_code'] = _clean_cell(cells[4])
            data['notes'] = _clean_cell(cells[5])
            observations.append(data)
        return observations


from conan.tools.files import copy
from conan.tools.cmake import CMakeDeps
from conan.tools.cmake import CMakeToolchain
from conan import ConanFile
from os.path import join

class Pkg(ConanFile):
    settings = "os", "compiler", "build_type", "arch"

    def requirements(self):
        self.requires("fmt/10.1.1")
        self.requires("glew/2.2.0")
        self.requires("glfw/3.3.8")
        self.requires("imgui/cci.20230105+1.89.2.docking")
        self.requires("boost/1.83.0")
        self.requires("glm/cci.20230113")
        self.requires("nlohmann_json/3.11.2")
        self.requires("spdlog/1.12.0")
        self.requires("libsodium/cci.20220430")
        self.requires("luajit/2.1.0-beta3")
        self.requires("stb/cci.20220909")

    def build_requirements(self):
        self.tool_requires("cmake/3.22.6")

    def generate(self):
        for dep in self.dependencies.values():
            copy(self, "imgui_impl_glfw.cpp", join(dep.package_folder, "res/bindings"), join(self.build_folder, "../GrizzlyCore/bindings"))
            copy(self, "imgui_impl_opengl3.cpp", join(dep.package_folder, "res/bindings"), join(self.build_folder, "../GrizzlyCore/bindings"))
            copy(self, "imgui_impl_glfw.h", join(dep.package_folder, "res/bindings"), join(self.build_folder, "../GrizzlyCore/bindings"))
            copy(self, "imgui_impl_opengl3.h", join(dep.package_folder, "res/bindings"), join(self.build_folder, "../GrizzlyCore/bindings"))
            copy(self, "imgui_impl_opengl3_loader.h", join(dep.package_folder, "res/bindings"), join(self.build_folder, "../GrizzlyCore/bindings"))
            copy(self, "stb_image.c", join(dep.package_folder, "include/"), join(self.build_folder, "../GrizzlyCore/bindings"))

        cmake = CMakeDeps(self)
        ct = CMakeToolchain(self)
        cmake.generate()
        ct.generate()


import pandas as pd
import matplotlib.pyplot as plt
import os

class CoefficientAnalyzer:
    def __init__(self, data_file):
        """
        Initialize the CoefficientAnalyzer class.
        """
        # Load the data for all trials from the CSV
        self.data = pd.read_csv(data_file)

    def calculate_coefficient(self, v_initial, v_final):
        """
        Calculate the coefficient of restitution 'e'.
        """
        return abs(v_final) / abs(v_initial)

    def calculate_uncertainty(self, v_initial, sigma_vi, v_final, sigma_vf):
        """
        Calculate the uncertainty in the coefficient of restitution 'e' using the error propagation formula.
        """
        partial_e_vi = -v_final / v_initial**2
        partial_e_vf = 1 / v_initial

        return (
            (partial_e_vi**2 * sigma_vi**2)
            + (partial_e_vf**2 * sigma_vf**2)
        ) ** 0.5

    def process_all_trials(self):
        """Process all trials in the data."""
        self.data["e_calculated"] = self.data.apply(
            lambda row: self.calculate_coefficient(
                row["v initial"], row["v final"]
            ),
            axis=1,
        )
        self.data["e_uncertainty"] = self.data.apply(
            lambda row: self.calculate_uncertainty(
                row["v initial"],
                row["uncertainty vi"],
                row["v final"],
                row["uncertainty vf"],
            ),
            axis=1,
        )

        # Calculate unweighted mean and standard error on the mean
        self.e_mean = self.data["e_calculated"].mean()
        self.sigma = self.data["e_calculated"].std()  # standard deviation
        self.N = len(self.data)
        self.sigma_mean = self.sigma / (self.N**0.5)

        # Calculate weighted mean and its standard error
        weights = 1 / self.data["e_uncertainty"] ** 2
        self.e_weighted_mean = sum(self.data["e_calculated"] * weights) / sum(weights)
        self.sigma_weighted_mean = (sum(weights)) ** -0.5

    def display_results(self):
        """Display the results for all trials."""
        print("Trial-wise results:")
        print(self.data)
        print("\nUnweighted mean (ē):", self.e_mean)
        print("Standard deviation (σ):", self.sigma)
        print("Standard error on the mean (σ̄e):", self.sigma_mean)
        print("\nWeighted mean (ē_w):", self.e_weighted_mean)
        print("Standard error on the weighted mean (σ̄_ew):", self.sigma_weighted_mean)

    def plot_e_vs_vi(self):
        """Plot e against v_initial and save the figure in the 'figures' folder."""
        plt.figure(figsize=(10, 6))
        plt.scatter(self.data["v initial"], self.data["e_calculated"], marker='o', color='blue', label="e values")
        plt.axhline(y=self.e_mean, color='r', linestyle='--', label=f"Unweighted mean $\\bar{{e}}$ = {self.e_mean:.4f}")
        plt.axhline(y=self.e_weighted_mean, color='g', linestyle='-.', label=f"Weighted mean $\\bar{{e}}_w$ = {self.e_weighted_mean:.4f}")
        plt.xlabel("Initial Velocity ($v_i$)")
        plt.ylabel("Coefficient of Restitution (e)")
        plt.title("Distribution of Coefficient of Restitution values")
        plt.legend()
        plt.grid(True)
        plt.tight_layout()

        # Create 'figures' directory if not exists
        if not os.path.exists('figures'):
            os.makedirs('figures')

        # Save the plot
        plt.savefig('experiment1/figures/e_vs_vi_plot.png')
        plt.show()

    def plot_histogram(self):
            """Plot a histogram of e values and overlay vertical lines for e_bar and e_w_bar."""
            plt.figure(figsize=(10, 6))
            plt.hist(self.data["e_calculated"], bins=10, color='lightblue', edgecolor='black', alpha=0.7, label="Frequency of e values")
            plt.axvline(x=self.e_mean, color='r', linestyle='--', label=f"Unweighted mean $\\bar{{e}}$ = {self.e_mean:.4f}")
            plt.axvline(x=self.e_weighted_mean, color='g', linestyle='-.', label=f"Weighted mean $\\bar{{e}}_w$ = {self.e_weighted_mean:.4f}")
            plt.xlabel("Coefficient of Restitution (e)")
            plt.ylabel("Frequency")
            plt.title("Frequency Distribution of Coefficient of Restitution values")
            plt.legend()
            plt.grid(True, which='both', linestyle='--', linewidth=0.5)
            plt.tight_layout()

            # Save the plot in 'figures' directory
            plt.savefig('experiment1/figures/e_histogram.png')
            plt.show()

analyzer = CoefficientAnalyzer("experiment1/data.csv")  # Adjust the file path
analyzer.process_all_trials()
analyzer.display_results()
analyzer.plot_e_vs_vi()
analyzer.plot_histogram()


import bpy
from bpy.types import NodeSocket

from . import ProkitekturaContainerNode

class ProkitekturaDemoAdvancedAttr(ProkitekturaContainerNode, bpy.types.Node): # make ProkitekturaNode the first super() in multiple inheritance
    # Optional identifier string. If not explicitly defined, the python class name is used.
    bl_idname = "ProkitekturaDemoAdvancedAttr"
    # Label for nice name display
    bl_label = "Advanced Attr"
    # Icon identifier
    bl_icon = 'SOUND'

    # list for iteration over advanced properties
    def declareProperties(self, propList):
        super().declareProperties(propList)
        propList.extend((
            {"type":"std", "name":"countGroundLevel","check":"activateProp1", "text":"count ground level", "pythName":"groundLevel" },
            {"type":"std", "name":"specificLevel",   "check":"activateProp2", "text":"levels",             "pythName":"levels" },
            {"type":"adv", "name":"prop1",           "check":"activateProp3", "text":"str",                "pythName":"strProp" },
            {"type":"adv", "name":"prop2",           "check":"activateProp4", "text":"check",              "pythName":"boolProp" },
            {"type":"adv", "name":"prop3",           "check":"activateProp5", "text":"int",                "pythName":"intProp" },
            {"type":"adv", "name":"prop4",           "check":"activateProp6", "text":"levels",             "pythName":"enumProp" }
        ))

    # list for iteration over advanced properties
    def declareCheckedSockets(self, socketList):
        super().declareCheckedSockets(socketList)
        socketList.extend((
            {"type":"std", "class":"ProkitekturaSocketEnum", "text":"std1", "pythName":"std1"},
            {"type":"std", "class":"ProkitekturaSocketEnum", "text":"std2", "pythName":"std2"},
            {"type":"adv", "class":"ProkitekturaSocketEnum", "text":"adv1", "pythName":"adv1"},
            {"type":"adv", "class":"ProkitekturaSocketEnum", "text":"adv2", "pythName":"adv2"}
        ))

    optionsList = (
        ("all", "all levels", "All"),
        ("ground", "ground level only", "Ground level only"),
        ("allButLast", "all but last one", "All levels but the last one"),
        ("last", "last level", "The last level"),
        ("specific", "specific level", "Specific level"),
        ("even", "even levels", "Even levels"),
        ("odd", "odd levels", "Odd levels")
    )

    propList = []
    socketList = []

    # examples of mandatory  attributes
    countGroundLevel: bpy.props.BoolProperty(name = "Count Ground Level",description = "Shall we count the the ground level for the setting below",default = False)
    specificLevel: bpy.props.IntProperty(name = "Specific Level",description = "The number of the specific level",subtype = 'UNSIGNED',default = 1,min = 0)

    # examples of advanced properties
    prop1: bpy.props.StringProperty(name = "Property1", description = "Name for property1",default = 'prop1')
    prop2: bpy.props.BoolProperty(name = "Property2", description = "Name for property2",default = False)
    prop3: bpy.props.IntProperty(name = "Property3", description = "Value for property1",default = 1)
    prop4: bpy.props.EnumProperty(name = "Property4", description = "Level for property1",items = optionsList,default = "all")

    # example of activation checks for advanced properties
    activateProp1: bpy.props.BoolProperty(name = "Activate1", description = "activate1", default = True)
    activateProp2: bpy.props.BoolProperty(name = "Activate2", description = "activate2", default = True)
    activateProp3: bpy.props.BoolProperty(name = "Activate3", description = "activate3", default = False)
    activateProp4: bpy.props.BoolProperty(name = "Activate4", description = "activate4", default = False)
    activateProp5: bpy.props.BoolProperty(name = "Activate5", description = "activate5", default = False)
    activateProp6: bpy.props.BoolProperty(name = "Activate6", description = "activate6", default = False)

    def init(self, context):
        if not self.propList:
            self.declareProperties(self.propList)
        if not self.socketList:
            self.declareCheckedSockets(self.socketList)

        self.init_sockets_checked(context,self.socketList)
        super().init(context)

    def draw_buttons(self, context, layout):
        self.draw_buttons_common(context, layout)
        self.draw_buttons_checked(context, layout, self.propList)
#        self.draw_buttons_container(context, layout)


##################################################################################
# Your goal is to follow the comments and complete the the tasks asked of you.
#
# Good luck designing your proportional derivative controller!
##################################################################################

class PD_Controller:

    def __init__(self, kp = 0.0, kd = 0.0, start_time = 0):
        """Initialize the class variables.

        :param kp: Optional float of the kp (proportional) value
        :param kd: Optional float of the kd (derivative) value
        :param start_time: Optional float of the start time
        """

        # The PD controller can be initalized with a specific kp and kd value
        self.kp_ = float(kp)
        self.kd_ = float(kd)

        # Define last_error_ and set to 0.0
        self.last_error_ = 0.0

        # Store relevant data
        self.last_timestamp_ = 0.0
        self.set_point_ = 0.0
        self.start_time_ = start_time
        self.error_sum_ = 0.0

        # Control effort history
        self.u_p = [0]
        self.u_d = [0]

    def setTarget(self, target):
        """Set the altitude target

        :param target: Float of the target altitude
        """
        self.set_point_ = float(target)

    def setKP(self, kp):
        """Set the kp (proportional) value.

        :param kp: Float of the kp value
        """
        self.kp_ = float(kp)

    def setKD(self, kd):
        """Set the kd (derivative) value.

        :param kd:: Float of the kd value
        """
        self.kd_ = float(kd)

    def update(self, measured_value, timestamp):
        """Calculate delta_time using the _last_timestamp and the provided
        timestamp argument.

        :param measured_value: Float of the measured value
        :param timestamp: Timestamp of the current time
        :return: Float of the control effort
        """

        delta_time = timestamp - self.last_timestamp_
        if delta_time == 0:
            # Delta time is zero
            return 0

        # Calculate the error
        error = self.set_point_ - measured_value

        # Set the last_timestamp_
        self.last_timestamp_ = timestamp

        # Find error_sum_
        self.error_sum_ += error * delta_time

        # Calculate the delta_error
        delta_error = (error - self.last_error_) / delta_time

        # Update the past error with the current error
        self.last_error_ = error

        # Proportional error
        p = self.kp_ * error

        # Calculate the derivative error
        d = self.kd_ * delta_error

        # Set the control effort (u), which is the sum of all your errors. In
        #   this case it is justthe proportional and derivative error.
        u = p + d

        # Here we are storing the control effort history for post control
        #   observations.
        self.u_p.append(p)
        self.u_d.append(d)

        return u


from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium.webdriver.firefox.webdriver import WebDriver
from django.conf import settings
import os

SCREEN_DUMP_LOCATION = os.path.join(settings.BASE_DIR, "screendumps")

class StatusViewsTests(StaticLiveServerTestCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.selenium = WebDriver()
        cls.selenium.implicitly_wait(10)

    @classmethod
    def tearDownClass(cls):
        cls.selenium.quit()
        super().tearDownClass()

    def test_landing_page_title(self):
        self.selenium.get(f"{self.live_server_url}")
        self.selenium.save_screenshot(
            f"{SCREEN_DUMP_LOCATION}/test_landing_page_title.png"
        )

        self.assertIn("Sprinkler Controller ][", self.selenium.title)