from __future__ import annotations
from typing import (
    Iterable,
    Any,
    Iterator,
    Sequence,
    Callable,
    Optional,
    NamedTuple,
)
from typing_extensions import Self, TypeAlias
import copy
import enum
import dataclasses

from ezdxf.math import BoundingBox2d, Matrix44, Vec2, UVec
from ezdxf.npshapes import NumpyPath2d, NumpyPoints2d, EmptyShapeError
from ezdxf.tools import take2

from .backend import BackendInterface
from .config import Configuration
from .properties import BackendProperties
from .type_hints import Color

class RecordType(enum.Enum):
    """Enum, determines the data record type.

    Attributes:
        POINTS:
        SOLID_LINES:
        PATH:
        FILLED_PATHS:
    """

    POINTS = enum.auto()  # n=1 point; n=2 line; n>2 filled polygon
    SOLID_LINES = enum.auto()
    PATH = enum.auto()
    FILLED_PATHS = enum.auto()

@dataclasses.dataclass
class DataRecord:
    type: RecordType
    property_hash: int
    handle: str  # top-level entity handle
    data: Any

    def bbox(self) -> BoundingBox2d:
        bbox = BoundingBox2d()
        try:
            if self.type == RecordType.FILLED_PATHS:
                for path in self.data:
                    bbox.extend(path.extents())
            else:
                bbox.extend(self.data.extents())
        except EmptyShapeError:
            pass
        return bbox

class Recorder(BackendInterface):
    """Records the output of the Frontend class."""

    def __init__(self) -> None:
        self.config = Configuration()
        self.background: Color = "#000000"
        self.records: list[DataRecord] = []
        self.properties: dict[int, BackendProperties] = dict()

    def player(self) -> Player:
        """Returns a :class:`Player` instance with the original recordings! Make a copy
        of this player to protect the original recordings from being modified::

            safe_player = recorder.player().copy()

        """
        player = Player()
        player.config = self.config
        player.background = self.background
        player.records = self.records
        player.properties = self.properties
        player.has_shared_recordings = True
        return player

    def configure(self, config: Configuration) -> None:
        self.config = config

    def set_background(self, color: Color) -> None:
        self.background = color

    def store(
        self, type_: RecordType, properties: BackendProperties, data: Any
    ) -> None:
        # exclude top-level entity handle to reduce the variance:
        # color, lineweight, layer, pen
        prop_hash = hash(properties[:4])
        self.records.append(
            DataRecord(
                type=type_, property_hash=prop_hash, handle=properties.handle, data=data
            )
        )
        self.properties[prop_hash] = properties

    def draw_point(self, pos: Vec2, properties: BackendProperties) -> None:
        self.store(RecordType.POINTS, properties, NumpyPoints2d((pos,)))

    def draw_line(self, start: Vec2, end: Vec2, properties: BackendProperties) -> None:
        self.store(RecordType.POINTS, properties, NumpyPoints2d((start, end)))

    def draw_solid_lines(
        self, lines: Iterable[tuple[Vec2, Vec2]], properties: BackendProperties
    ) -> None:
        def flatten() -> Iterator[Vec2]:
            for s, e in lines:
                yield s
                yield e

        self.store(RecordType.SOLID_LINES, properties, NumpyPoints2d(flatten()))

    def draw_path(self, path: NumpyPath2d, properties: BackendProperties) -> None:
        assert isinstance(path, NumpyPath2d)
        self.store(RecordType.PATH, properties, path)

    def draw_filled_polygon(
        self, points: NumpyPoints2d, properties: BackendProperties
    ) -> None:
        assert isinstance(points, NumpyPoints2d)
        self.store(RecordType.POINTS, properties, points)

    def draw_filled_paths(
        self, paths: Iterable[NumpyPath2d], properties: BackendProperties
    ) -> None:
        paths = tuple(paths)
        if len(paths) == 0:
            return

        assert isinstance(paths[0], NumpyPath2d)
        self.store(RecordType.FILLED_PATHS, properties, paths)

    def enter_entity(self, entity, properties) -> None:
        pass

    def exit_entity(self, entity) -> None:
        pass

    def clear(self) -> None:
        raise NotImplementedError()

    def finalize(self) -> None:
        pass

class Override(NamedTuple):
    """Represents the override state for a data record.

    Attributes:
        properties: original or modified :class:`BackendProperties`
        is_visible: override visibility e.g. switch layers on/off

    """

    properties: BackendProperties
    is_visible: bool = True

OverrideFunc: TypeAlias = Callable[[BackendProperties], Override]

class Player:
    """Plays the recordings of the :class:`Recorder` backend on another backend."""

    def __init__(self) -> None:
        self.config = Configuration()
        self.background: Color = "#000000"
        self.records: list[DataRecord] = []
        self.properties: dict[int, BackendProperties] = dict()
        self._bbox = BoundingBox2d()
        self.has_shared_recordings: bool = False

    def __copy__(self) -> Self:
        """Returns a copy of the player with non-shared recordings."""
        player = self.__class__()
        # config is a frozen dataclass:
        player.config = self.config
        player.background = self.background
        # recordings are mutable - transformed inplace:
        player.records = copy.deepcopy(self.records)
        # the properties dict may grow, but entries will never be removed:
        player.properties = self.properties
        player.has_shared_recordings = False
        return player

    copy = __copy__

    def recordings(self) -> Iterator[tuple[RecordType, BackendProperties, Any]]:
        """Yields all recordings as `(RecordType, BackendProperties, Data)` tuples.

        The content of the `Data` field is determined by the enum :class:`RecordType`:

        - :attr:`RecordType.POINTS` returns a :class:`NumpyPoints2d` instance,
          len() == 1 is a point, len() == 2 is a line, len() > 2 is a filled polygon
        - :attr:`RecordType.SOLID_LINES` returns a :class:`NumpyPoints2d` instance
          where each pair (n, n+1) represents the start- and end point of a line
        - :attr:`RecordType.PATH`: returns a :class:`NumpyPath2d` instance that
          represents a linear 2D path
        - :attr:`RecordType.FILLED_PATHS` returns a tuple (exterior_paths, holes),
          where exterior_paths and holes are tuples of :class:`NumpyPath2d`.

        """
        props = self.properties
        for record in self.records:
            properties = BackendProperties(
                *props[record.property_hash][:4], record.handle
            )
            yield record.type, properties, record.data

    def replay(
        self, backend: BackendInterface, override: Optional[OverrideFunc] = None
    ) -> None:
        """Replay the recording on another backend that implements the
        :class:`BackendInterface`. The optional `override` function can be used to
        override the properties and state of data records, it gets the :class:`BackendProperties`
        as input and must return an :class:`Override` instance.
        """

        backend.configure(self.config)
        backend.set_background(self.background)
        for record_type, properties, data in self.recordings():
            if override:
                state = override(properties)
                if not state.is_visible:
                    continue
                properties = state.properties
            if record_type == RecordType.POINTS:
                if len(data) == 0:
                    continue
                if len(data) > 2:
                    backend.draw_filled_polygon(data, properties)
                    continue
                vertices = data.vertices()
                if len(vertices) == 1:
                    backend.draw_point(vertices[0], properties)
                else:
                    backend.draw_line(vertices[0], vertices[1], properties)
            elif record_type == RecordType.SOLID_LINES:
                backend.draw_solid_lines(take2(data.vertices()), properties)
            elif record_type == RecordType.PATH:
                backend.draw_path(data, properties)
            elif record_type == RecordType.FILLED_PATHS:
                backend.draw_filled_paths(data, properties)
        backend.finalize()

    def transform(self, m: Matrix44) -> None:
        """Transforms the recordings inplace by a transformation matrix `m` of type
        :class:`~ezdxf.math.Matrix44`.
        """
        for record in self.records:
            if record.type == RecordType.FILLED_PATHS:
                for p in record.data:
                    p.transform_inplace(m)
            else:
                record.data.transform_inplace(m)

        if self._bbox.has_data:
            # works for 90-, 180- and 270-degree rotation
            self._bbox = BoundingBox2d(m.fast_2d_transform(self._bbox.rect_vertices()))

    def bbox(self) -> BoundingBox2d:
        """Returns the bounding box of all records as :class:`~ezdxf.math.BoundingBox2d`."""
        if not self._bbox.has_data:
            self.update_bbox()
        return self._bbox

    def update_bbox(self) -> None:
        bbox = BoundingBox2d()
        for record in self.records:
            bbox.extend(record.bbox())
        self._bbox = bbox

    def crop_rect(self, p1: UVec, p2: UVec, distance: float) -> None:
        """Crop recorded shapes inplace by a rectangle defined by two points.

        The argument `distance` defines the approximation precision for paths which have
        to be approximated as polylines for cropping but only paths which are really get
        cropped are approximated, paths that are fully inside the crop box will not be
        approximated.

        Args:
            p1: first corner of the clipping rectangle
            p2: second corner of the clipping rectangle
            distance: maximum distance from the center of the curve to the
                center of the line segment between two approximation points to
                determine if a segment should be subdivided.

        """
        crop_rect = BoundingBox2d([Vec2(p1), Vec2(p2)])
        self.records = crop_records_rect(self.records, crop_rect, distance)
        self._bbox = BoundingBox2d()  # determine new bounding box on demand

def crop_records_rect(
    records: list[DataRecord], crop_rect: BoundingBox2d, distance: float
) -> list[DataRecord]:
    """Crop recorded shapes inplace by a rectangle."""
    from .clipper import ClippingRect

    def sort_paths(np_paths: Sequence[NumpyPath2d]):
        _inside: list[NumpyPath2d] = []
        _crop: list[NumpyPath2d] = []

        for np_path in np_paths:
            bbox = BoundingBox2d(np_path.extents())
            if not crop_rect.has_intersection(bbox):
                # path is complete outside the cropping rectangle
                pass
            elif crop_rect.inside(bbox.extmin) and crop_rect.inside(bbox.extmax):
                # path is complete inside the cropping rectangle
                _inside.append(np_path)
            else:
                _crop.append(np_path)

        return _crop, _inside

    def crop_paths(
        np_paths: Sequence[NumpyPath2d],
    ) -> list[NumpyPath2d]:
        return list(clipper.clip_filled_paths(np_paths, distance))

    # an undefined crop box crops nothing:
    if not crop_rect.has_data:
        return records
    cropped_records: list[DataRecord] = []
    size = crop_rect.size
    # a crop box size of zero in any dimension crops everything:
    if size.x < 1e-12 or size.y < 1e-12:
        return cropped_records

    clipper = ClippingRect()
    clipper.push(NumpyPath2d.from_vertices(crop_rect.rect_vertices()), None)
    for record in records:
        record_box = record.bbox()
        if not crop_rect.has_intersection(record_box):
            # record is complete outside the cropping rectangle
            continue
        if crop_rect.inside(record_box.extmin) and crop_rect.inside(record_box.extmax):
            # record is complete inside the cropping rectangle
            cropped_records.append(record)
            continue

        if record.type == RecordType.FILLED_PATHS:
            paths_to_crop, inside = sort_paths(record.data)
            cropped_paths = crop_paths(paths_to_crop) + inside
            if cropped_paths:
                record.data = tuple(cropped_paths)
                cropped_records.append(record)
        elif record.type == RecordType.PATH:
            # could be split into multiple parts
            for p in clipper.clip_paths([record.data], distance):  # type: ignore
                cropped_records.append(
                    DataRecord(
                        record.type,
                        record.property_hash,
                        record.handle,
                        p,
                    )
                )
        elif record.type == RecordType.POINTS:
            count = len(record.data)
            if count == 1:
                pass
            elif count == 2:
                s, e = record.data.vertices()
                record.data = NumpyPoints2d(clipper.clip_line(s, e))
            else:  # filled polygon
                record.data = clipper.clip_polygon(record.data)
            cropped_records.append(record)
        elif record.type == RecordType.SOLID_LINES:
            points: list[Vec2] = []  # type: ignore
            for s, e in take2(record.data.vertices()):
                points.append(clipper.clip_line(s, e))  # type: ignore
            record.data = NumpyPoints2d(points)
            cropped_records.append(record)
        else:
            raise ValueError("invalid record type")
    return cropped_records


# -*- coding: utf-8 -*-
# arg1 : file of filenames : csv files with positions of breaks (absolute path)
# arg2 : shustring_file.out or csv files with repeat data
# arg3 : type of repeat file : shustring or csv

################################ IMPORT ################################################################################################

#from sequana.lazy import pylab
from sequana.lazy import pandas as pd
#from sequana.lazy import numpy as np
#from random import shuffle
import sys
import os
from scipy.stats import chisquare

################################ PARAMETERS ##############################################################################################

fof_breaks_csv = str(sys.argv[1])
file_shustring = str(sys.argv[2])
type_repeat_file = str(sys.argv[3])

if type_repeat_file not in ["shustring","csv"]:
	print("arg 3 error : type of file not available : possible values : shustring csv")

threshold = 1000

################################ FUNCTIONS ##############################################################################################

def is_non_zero_file(fpath):
    return os.path.isfile(fpath) and os.path.getsize(fpath) > 0

def step_repeat_len(df_shustring, threshold):
	"""
	Convert repetitivity curve into step curve with repeat len
	returns begining and end as list of tuples
	"""

	nb_row = df_shustring.shape[0]
	i = 0
	step_repeat_seq = []
	be_repeats = []
	e = 0

	# use list because much faster
	list_len_shus = list(df_shustring.iloc[:,1])

	while(i < nb_row):
		# begining of repeat
		if (list_len_shus[i] > threshold):
			b = i
			#print(b)
			# add 0 for non repeat area
			step_repeat_seq = step_repeat_seq + [0 for j in range(e,b,1)]
			# compute new end of repeat
			len_repeat = list_len_shus[i]
			e = b + len_repeat
			# add len of repeat for all repeat area
			step_repeat_seq = step_repeat_seq + [len_repeat for j in range(b,e,1)]
			# save (b,e)
			be_repeats.append((b,e))
			# update i
			i = e-1
		i +=1

	step_repeat_seq = step_repeat_seq + [0 for j in range(e,i,1)]

	# merge repeats that are fragmented
	prev_tup = be_repeats[0]
	b = prev_tup[0]
	be_repeats_concat = []
	for tup in be_repeats[1:len(be_repeats)]:
		if tup[0] == prev_tup[1]:
			# concat
			e = tup[1]
			prev_tup = tup
		else:
			# real end of repeat : append result and update b, e
			e = prev_tup[1]
			be_repeats_concat.append((b,e))
			prev_tup = tup
			b = prev_tup[0]

	return step_repeat_seq, be_repeats_concat

def rotate_breaks(df):
	# rotate the break if needed
	# rotate whole break
	ind_rotate = df[((df["begin"] % len_genome) != df["begin"]) & ((df["end"] % len_genome) != df["end"])].index.tolist()
	for i in ind_rotate:
		df.loc[i,("begin")] = df.loc[i,("begin")] % len_genome
		df.loc[i,("end")] = df.loc[i,("end")] % len_genome

	# if break bypass the start : cut in two parts
	ind_rotate = df[((df["begin"] % len_genome) == df["begin"]) & ((df["end"] % len_genome) != df["end"])].index.tolist()
	for i in ind_rotate:
		df.loc[i,("begin")] = df.loc[i,("begin")] % len_genome
		df.loc[i,("end")] = len_genome
		df.append(df.iloc[i,],ingnore_index=True)
		df.loc[df.shape[0],("begin")] = 0
		df.loc[df.shape[0],("end")] = df[i,("end")] % len_genome

	# sort by position and update index
	df.sort_values(by="begin",inplace=True)
	df.index = range(df.shape[0])

def find_overlap(start_break, end_break, start_rep, end_rep):
	start_overlap, end_overlap = max(start_break, start_rep), min(end_break, end_rep)
	if start_overlap > end_overlap:
		start_overlap, end_overlap = None, None
	return start_overlap, end_overlap

def find_nb_overlap_repeats(start_break, end_break, df_repeats):
	"""
	df_repeats must be sorted by begin position !!
	"""
	rep_i = 0
	nb_repeat_overlap = 0
	while rep_i < df_repeats.shape[0]:
		start_rep = df_repeats.loc[rep_i,"begin"]
		end_rep = df_repeats.loc[rep_i,"end"]

		# check if repeat can overlap the break
		if not (start_break > end_rep) :
			# check if start inside repeat
			# print("repeat : %d , %d" %(start_rep, end_rep))
			start_break_inside = (start_break >=  start_rep) & (start_break <= end_rep)
			end_break_inside = (end_break >=  start_rep) & (end_break <= end_rep)
			start_rep_inside = (start_rep >=  start_break) & (start_rep <= end_break)
			end_rep_inside = (end_rep >=  start_break) & (end_rep <= end_break)
			if start_break_inside | end_break_inside :
				# find overlap
				start_overlap, end_overlap = find_overlap(start_break, end_break, start_rep, end_rep)
				# print("overlap : %d , %d" %(start_overlap, end_overlap))
				nb_repeat_overlap += (end_overlap - start_overlap)
			elif start_rep_inside | end_rep_inside :
				# find overlap
				start_overlap, end_overlap = find_overlap(start_break, end_break, start_rep, end_rep)
				# print("overlap : %d , %d" %(start_overlap, end_overlap))
				nb_repeat_overlap += (end_overlap - start_overlap)
		# all repeats after this cannot overlap
		if end_break < start_rep:
			break
		rep_i +=1

	return nb_repeat_overlap

################################ INPUT DATA ##############################################################################################

print("Breakpoints files")

# list of all files
f = open(fof_breaks_csv, 'r')
list_files_breaks = []
list_df_breaks_files = []
for path in f.readlines():
	f_path = path.split('\n')[0]
	list_files_breaks.append(f_path)

	# save short name (for plots)
	short_name = f_path.split("/")[-1]
	list_df_breaks_files.append(short_name.replace(".csv",""))
f.close()

# load all breaks files and set header
list_df_breaks = []
list_df_breaks_names = []
for i in range(len(list_files_breaks)):
	f_path = list_files_breaks[i]
	if is_non_zero_file(f_path):
		# import df
		df_breaks = pd.read_csv(f_path,header=None,sep=",")
		df_breaks.columns = ["begin","end"]
		# add filename in df
		df_breaks["filename"] = [list_df_breaks_files[i]]*df_breaks.shape[0]
		# save in list
		list_df_breaks.append(df_breaks)
		list_df_breaks_names.append(list_df_breaks_files[i])
	else:
		print("%s : empty file, not imported" % list_df_breaks_files[i])

if type_repeat_file == "shustring":
	print("Shustring file")
	# load shustring file with pandas, and re-read to set header and find genome length
	df_shustring = pd.read_csv(file_shustring,comment='#',header=None,sep="\s+")

	f = open(file_shustring, 'r')
	l = f.readline()
	# first line contains genome length
	len_genome = int(l.split("\t")[1])
	# find header
	while(l):
		if (l[0] == '#') & (l[1] != '>'):
			header_df = l.replace('\n','').replace('#','').split('\t')
			break
		l = f.readline()
	f.close()
	df_shustring.columns = header_df

	print("Find repeats location and save as csv file")
	step_repeat_seq, be_repeats_concat = step_repeat_len(df_shustring, threshold)
	df_repeats = pd.DataFrame(be_repeats_concat)
	df_repeats.columns = ["begin","end"]
	path_outfile = file_shustring + "_df_begin_end.csv"
	# if csv file doesn't exist, save it
	if not os.path.isfile(path_outfile):
		f = open(path_outfile, 'a')
		f.write("# %d\n" %len_genome)
		df_repeats.to_csv(f,mode = 'a', index=None)
		f.close()

elif type_repeat_file == "csv":
	print("Repeat csv file")
	df_repeats = pd.read_csv(file_shustring,comment='#')

	f = open(file_shustring, 'r')
	l = f.readline()
	# first line contains genome length
	len_genome = int(l.replace("\n","").split(" ")[1])
	f.close()

################################ EXECUTE ##############################################################################################

# expected frequency of repeats in genome
freq_exp_inside_repeat = 0
for rep_i in range(df_repeats.shape[0]):
	freq_exp_inside_repeat += (df_repeats.loc[rep_i,"end"] - df_repeats.loc[rep_i,"begin"])
freq_exp_outside_repeat = len_genome - freq_exp_inside_repeat
# normalisation
freq_exp_inside_repeat = freq_exp_inside_repeat / float(len_genome)
freq_exp_outside_repeat = freq_exp_outside_repeat / float(len_genome)

# observed frequency of repeats between contigs (breaks)
for df_i in range(len(list_df_breaks)):
	df = list_df_breaks[df_i]
	df["nb_outside_repeat"] = [0]*df.shape[0]
	df["nb_inside_repeat"] = [0]*df.shape[0]

	# rotate breaks if needed
	rotate_breaks(df)

	for break_i in range(df.shape[0]):
		start_break = df.loc[break_i,"begin"]
		end_break = df.loc[break_i,"end"]

		nb_repeat_overlap = find_nb_overlap_repeats(start_break, end_break, df_repeats)

		df.loc[break_i,"nb_inside_repeat"] = nb_repeat_overlap
		df.loc[break_i,"nb_outside_repeat"] = (end_break - start_break) - nb_repeat_overlap

	# results repeats
	nb_obs_inside_repeat = df["nb_inside_repeat"].sum()
	nb_obs_outside_repeat = df["nb_outside_repeat"].sum()
	total = nb_obs_inside_repeat + nb_obs_outside_repeat
	# normalisation
	# nb_obs_inside_repeat = nb_obs_inside_repeat / float(total)
	# nb_obs_outside_repeat = nb_obs_outside_repeat / float(total)

	result_chi = chisquare(f_obs=[nb_obs_inside_repeat,nb_obs_outside_repeat],f_exp=[round(freq_exp_inside_repeat*total), round(freq_exp_outside_repeat*total)])
	print("\n" + list_df_breaks_names[df_i])
	print("Expected (repeat / non repeat) : ", [round(freq_exp_inside_repeat*total), round(freq_exp_outside_repeat*total)])
	print("Observed (repeat / non repeat) : ", [nb_obs_inside_repeat,nb_obs_outside_repeat])
	print(result_chi)


def add_exports_for_repos(repos):
    """
    This function will add needed entries to /etc/exports.  It will not remove any
    entries from the file.  It will then restart the server if necessary
    """
    current_exports = _get_current_exports()
    needed_exports = _get_exports_for_repos(repos)

    if not needed_exports.difference(current_exports):
        if not _server_is_running():
            _restart_server()
        return

    _write_exports_config(current_exports.union(needed_exports))
    _restart_server()


from datetime import timedelta, datetime
from time import strptime
from unittest import TestCase, mock

from parameterized import parameterized
from requests.cookies import RequestsCookieJar, create_cookie

from src.chain_smoker.config import Cookie
from src.chain_smoker.test_methods import ExpectedTest, ExpectedStatusCodeTest, ContainsTest, ContainsCookiesTest

class ExpectedTestTestCase(TestCase):
    @parameterized.expand([
        (1, 1, True),
        (1, 2, False),
        (1, '1', False),
        ('foo', 'foo', True),
        ('foo', 'bar', False),
        ({'id': 1}, {'id': 1}, True),
        ({'id': 1}, {'id': 2}, False),
    ])
    def test_run_test(self, input_value, other_value, expected_result):
        self.assertEqual(ExpectedTest(input_value, '', '').test(other_value), expected_result)

class ExpectedStatusCodeTestTestCase(TestCase):
    @parameterized.expand([
        (200, mock.Mock(status_code=200), True),
        (200, mock.Mock(status_code=400), False),
    ])
    def test_run_test(self, input_value, other_value, expected_result):
        self.assertEqual(ExpectedStatusCodeTest(input_value, '', '').test(other_value), expected_result)

    @parameterized.expand([
        (200, mock.Mock(status_code=200), False),
        (200, mock.Mock(status_code=400), True),
    ])
    def test_run_test_inverse(self, input_value, other_value, expected_result):
        self.assertEqual(ExpectedStatusCodeTest(input_value, '', '', inverse=True).test(other_value), expected_result)

class ContainsTestTestCase(TestCase):
    @parameterized.expand([
        ('Foo bar', 'The story of Foo bar is extremely important to remember', True),
        ('Baz', 'The story of Foo bar is extremely important to remember', False),
        ({'text': 'Foo bar'}, {'text': 'Foo bar'}, True),
        ({'text': 'Foo bar'}, {'id': 1, 'text': 'Foo bar'}, True),
        ({'id': 1, 'text': 'Foo bar'}, {'text': 'Foo bar'}, False),
        ({'foo': 'bar'}, {'foo': 'baz'}, False),
        ({'foo': 'bar'}, {'id': 1, 'text': 'Foo bar'}, False),
        ({'nested_obj': {'id': 1, 'field': 'name'}}, {'nested_obj': {'id': 1, 'field': 'name'}}, True),
        ({'nested_obj': {'id': 1, 'field': 'name'}}, {'nested_obj': {'id': 1, 'field': 'name-2'}}, False),
        ({'nested_obj': {'id': 1, 'field': 'name'}}, {'nested_obj': {'address': 'name'}}, False),
        ({'baz': {'id': 1, 'field': 'name'}}, {'nested_obj': {'id': 1, 'field': 'name'}}, False),
        ({'id': 1}, [{'id': 1}], True),
        ({'id': 1}, [{'id': 2}], False),
        (1, [1, 2, 3], True),
        (4, [1, 2, 3], False),
        ({'payload': 1}, {'payload': [1, 2, 3]}, True),
        ({'payload': 4}, {'payload': [1, 2, 3]}, False),
        (['foo', 'bar'], {'foo': 1, 'bar': 2, 'baz': 3}, True),
        (['foo'], {'foo': 1, 'bar': 2}, True),
        (['baz'], {'foo': 1, 'bar': 2}, False),
    ])
    def test_run_test(self, input_value, other_value, expected_result):
        self.assertEqual(ContainsTest(input_value, '', '').test(other_value), expected_result)

    @parameterized.expand([
        ('Foo bar', 'The story of Foo bar is extremely important to remember', False),
        ('Baz', 'The story of Foo bar is extremely important to remember', True),
        ({'text': 'Foo bar'}, {'text': 'Foo bar'}, False),
        ({'text': 'Foo bar'}, {'id': 1, 'text': 'Foo bar'}, False),
        ({'id': 1, 'text': 'Foo bar'}, {'text': 'Foo bar'}, False),
        ({'foo': 'bar'}, {'foo': 'baz'}, True),
        ({'foo': 'bar'}, {'id': 1, 'text': 'Foo bar'}, True),
        ({'nested_obj': {'id': 1, 'field': 'name'}}, {'nested_obj': {'id': 1, 'field': 'name'}}, False),
        ({'nested_obj': {'id': 1, 'field': 'name'}}, {'nested_obj': {'address': 'name'}}, True),
        ({'id': 1}, [{'id': 1}], False),
        ({'id': 1}, [{'id': 2}], True),
        (1, [1, 2, 3], False),
        (4, [1, 2, 3], True),
        ({'payload': 1}, {'payload': [1, 2, 3]}, False),
        ({'payload': 4}, {'payload': [1, 2, 3]}, True),
        (['foo', 'bar'], {'foo': 1, 'bar': 2, 'baz': 3}, False),
        (['foo'], {'foo': 1, 'bar': 2}, False),
        (['baz'], {'foo': 1, 'bar': 2}, True),
    ])
    def test_run_test_inverse(self, input_value, other_value, expected_result):
        self.assertEqual(ContainsTest(input_value, '', '', inverse=True).test(other_value), expected_result)

class ContainsCookiesTestTesCase(TestCase):
    @parameterized.expand([
        (dict(key='foo', domain='example.com'), dict(name='foo', domain='example.com', value='2'), True),
        (dict(key='foo', domain='example.com', value='2'), dict(name='foo', domain='example.com', value='2'), True),
        (dict(key='foo', domain='example.com', value='3'), dict(name='foo', domain='example.com', value='2'), False),
        (dict(key='foo', domain='foo.com'), dict(name='foo', domain='example.com', value='2'), False),
        (dict(key='foo', domain='example.com'), dict(name='foo', domain='foo.com', value='2'), False),
        (dict(key='foo', domain='example.com'), dict(name='bar', domain='example.com', value='2'), False),
        (
            dict(key='foo', domain='example.com', value='2', max_age='5m'),
            dict(
                name='foo',
                value='2',
                domain='example.com',
                expires=(datetime.now() + timedelta(minutes=5)).timestamp()
            ),
            True
        ),
        (
            dict(key='foo', domain='example.com', value='2', max_age='5m'),
            dict(
                name='foo',
                value='2',
                domain='example.com',
                expires=(datetime.now() + timedelta(minutes=5)).timestamp()
            ),
            True
        ),
        (
            dict(key='foo', domain='example.com', value='2', max_age='5m'),
            dict(
                name='foo',
                value='2',
                domain='example.com',
                expires=(datetime.now() + timedelta(minutes=6)).timestamp()
            ),
            False
        ),
        (
            dict(key='foo', domain='example.com', value='2', max_age='session'),
            dict(
                name='foo',
                value='2',
                domain='example.com',
                expires=(datetime.now() + timedelta(minutes=5)).timestamp()
            ),
            False
        ),
        (
            dict(key='foo', domain='example.com', value='2', max_age='5m'),
            dict(name='foo', value='2', domain='example.com', expires=None),
            False
        ),
        (
            dict(key='foo', domain='example.com', value='2', max_age='Session'),
            dict(name='foo', value='2', domain='example.com', expires=None),
            True
        ),
        (
            dict(key='foo', domain='example.com', value='2', max_age='session'),
            dict(name='foo', value='2', domain='example.com', expires=None),
            True
        ),
    ])
    def test_run_test(self, test_cookie_conf, response_cookie_conf, expected_result):
        test = ContainsCookiesTest([Cookie(**test_cookie_conf)], '', '')
        jar = RequestsCookieJar()
        if response_cookie_conf is not None:
            jar.set_cookie(
                create_cookie(**response_cookie_conf)
            )
        self.assertEqual(test.test(jar), expected_result)

    def test_run_test_inverse(self):
        test = ContainsCookiesTest([Cookie(key='foo', domain='example.com', value='2')], '', '', inverse=True)
        with self.assertRaises(NotImplementedError):
            test.test({})

    @parameterized.expand([
        ('Session', 'session'),
        ('session', 'session'),
        ('5m', timedelta(minutes=5)),
        ('5W', timedelta(weeks=5)),
        ('5M', timedelta(weeks=4*5)),
        ('5D', timedelta(days=5)),
        ('5d', timedelta(days=5)),
    ])
    def test_get_max_age(self, input_value, expected_output_value):
        now = datetime.now()
        cookie = Cookie(key='foo', domain='example.com', max_age=input_value)

        if not isinstance(expected_output_value, str):
            expected_output_value = now + expected_output_value

        with mock.patch('src.chain_smoker.test_methods.datetime', create=True) as now_mock:
            now_mock.timedelta = timedelta
            now_mock.datetime.strptime = strptime
            now_mock.datetime.now.return_value = now
            self.assertEqual(ContainsCookiesTest([cookie], '', '')._get_max_age(cookie), expected_output_value)


from models import Person,Article,Category
from flask import render_template,request,session,url_for,redirect
from blog import app,db
from forms import SigninForm,SignupForm,ArticleCreateForm,ArticleUpdateForm,CategoryCreateForm

@app.route('/')
def index():
    articles = Article.all()
    return render_template('index.html',articles=articles)

@app.route('/signup',methods=['POST','GET'])
def signup():
    form = SignupForm()
    if request.method == 'POST':
        if not form.validate():
            return render_template('signup.html',form=form)
        else:
            newperson = Person(form.firstname.data,form.lastname.data,form.email.data,form.password.data)
            db.session.add(newperson)
            db.session.commit()
            session['email']=newperson.email
            return redirect(url_for('profile'))
    elif request.method=='GET':
        return render_template('signup.html',form=form)

@app.route('/profile',methods=['POST','GET'])
def profile():
    if 'email' not in session:
        return redirect(url_for('signin'))
    person = Person.query.filter_by(email=session['email']).first()
    if person:
        article = Article()
        form = ArticleCreateForm()
        if request.method=='POST' and form.validate_on_submit():
            form.populate_obj(article)
            db.session.add(article)
            db.session.commit()
            return redirect(url_for('index'))
        else:
            return render_template('create.html',form=form)
    return render_template('profile.html')

@app.route('/signin',methods=['POST','GET'])
def signin():
    if 'email' in session:
        return redirect(url_for('index'))
    form = SigninForm()
    if request.method=='POST':
        if form.validate():
            session['email']=form.email.data
            return redirect(url_for('profile'))
        else:
            return render_template('signin.html',form=form)
    elif request.method=='GET':
        return render_template('signin.html',form=form)

@app.route('/signout')
def signout():
    if 'email' not in session:
        return redirect(url_for('signin'))
    session.pop('email',None)
    return redirect(url_for('index'))

@app.route('/article/<int:id>/<slug>')
def show_article(id,slug):
    article = Article.find_by_id(id)
    return render_template('show_article.html',article=article)

@app.route('/article/<int:id>/<slug>/edit',methods=['POST','GET'])
def update_article(id,slug):
    article = Article.find_by_id(id)
    if article is None:
        return HTTPNotFound(404)
    form = ArticleUpdateForm(request.form,article)
    if form.validate_on_submit():
        form.populate_obj(article)
        db.session.add(article)
        db.session.commit()
        return redirect(url_for('index'))
    return render_template('create.html',form=form)

@app.errorhandler(404)
def HTTPNotFound(e):
    return render_template('error.html'),404

@app.route('/category/create',methods=['GET','POST'])
def category_create():
    form = CategoryCreateForm()
    category = Category()
    if form.validate_on_submit():
        form.populate_obj(category)
        db.session.add(category)
        db.session.commit()
        return redirect(url_for('profile'))
    return render_template('category.html',form=form)


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

import time
import datetime
import os
from app.common import definitions, values
from shutil import copyfile

prev_trace_func = None

def create():
    log_file_name = "log-" + str(time.time())
    log_file_path = definitions.DIRECTORY_LOG_BASE + "/" + log_file_name
    definitions.FILE_MAIN_LOG = log_file_path
    with open(definitions.FILE_MAIN_LOG, 'w+') as log_file:
        log_file.write("[Start] " + values.TOOL_NAME + " started at " + str(datetime.datetime.now()) + "\n")
    if os.path.exists(definitions.FILE_LAST_LOG):
        os.remove(definitions.FILE_LAST_LOG)
    if os.path.exists(definitions.FILE_ERROR_LOG):
        os.remove(definitions.FILE_ERROR_LOG)
    if os.path.exists(definitions.FILE_COMMAND_LOG):
        os.remove(definitions.FILE_COMMAND_LOG)
    with open(definitions.FILE_ERROR_LOG, 'w') as fp:
        pass
    with open(definitions.FILE_COMMAND_LOG, 'w') as fp:
        pass
    with open(definitions.FILE_LAST_LOG, 'w+') as last_log:
        last_log.write("[Start] " + values.TOOL_NAME + " started at " + str(datetime.datetime.now()) + "\n")

def log(log_message):
    log_message = "[" + str(time.asctime()) + "]" + log_message

    if "COMMAND" in log_message:
        with open(definitions.FILE_COMMAND_LOG, 'a') as log_file:
            log_file.write(log_message)
    with open(definitions.FILE_MAIN_LOG, 'a') as log_file:
        log_file.write(log_message)
    with open(definitions.FILE_LAST_LOG, 'a') as log_file:
        log_file.write(log_message)

def configuration(message):
    message = str(message).strip().replace("\t", "").replace("\n", "")
    message = str(message).strip().lower().replace("[config]", "")
    message = "[CONFIGURATION]: " + str(message) + "\n"
    log(message)

def information(message):
    message = str(message).strip().replace("\t", "").replace("\n", "")
    message = "[INFO]: " + str(message) + "\n"
    log(message)

def trace(function_name, arguments):
    global prev_trace_func
    if function_name != prev_trace_func:
        prev_trace_func = function_name
        message = "[TRACE]: " + function_name + "\n"
        log(message)

def note(message):
    message = str(message).strip().lower().replace("[note]", "")
    message = "[NOTE]: " + str(message) + "\n"
    log(message)

def command(message):
    message = str(message).strip().replace("\t", "").replace("\n", "")
    message = "[COMMAND]: " + str(message) + "\n"
    log(message)

def error(message):
    with open(definitions.FILE_ERROR_LOG, 'a') as last_log:
        last_log.write(str(message) + "\n")
    message = str(message).strip().replace("\t", "").replace("\n", "")
    message = "[ERROR]: " + str(message) + "\n"
    log(message)

def output(message):
    message = str(message).strip().replace("\t", "").replace("\n", "")
    message = "[OUTPUT]: " + str(message) + "\n"
    log(message)

def warning(message):
    message = str(message).strip().replace("\t", "").replace("\n", "")
    message = "[WARNING]: " + str(message) + "\n"
    log(message)

def data(message, data=None, is_patch=False):
    if values.DEBUG_DATA or is_patch:
        message = str(message).strip()
        message = "[DATA]: " + str(message) + "\n"
        log(message)
        if data:
            data = "[DATA]: " + str(data) + "\n"
            log(data)

def debug(message):
    message = str(message).strip().replace("\t", "").replace("\n", "")
    message = "[DEBUG]: " + str(message) + "\n"
    log(message)

def store():
    copyfile(definitions.FILE_MAIN_LOG, definitions.DIRECTORY_LOG + "/log-latest")
    copyfile(definitions.FILE_COMMAND_LOG, definitions.DIRECTORY_LOG + "/log-command")
    copyfile(definitions.FILE_ERROR_LOG, definitions.DIRECTORY_LOG + "/log-error")
    copyfile(definitions.FILE_MAKE_LOG, definitions.DIRECTORY_LOG + "/log-make")

def end(time_duration, is_error=False):
    output("[END] " + values.TOOL_NAME + " ended at " + str(datetime.datetime.now()))
    output("Initialization: " + time_duration[definitions.KEY_DURATION_INITIALIZATION] + " minutes")
    output("Build: " + time_duration[definitions.KEY_DURATION_BUILD_ANALYSIS] + " minutes")
    output("Diff Analysis: " + time_duration[definitions.KEY_DURATION_DIFF_ANALYSIS] + " minutes")
    output("Slicing: " + time_duration[definitions.KEY_DURATION_SLICE] + " minutes")
    output("Clone Analysis: " + time_duration[definitions.KEY_DURATION_CLONE_ANALYSIS] + " minutes")
    output("AST Analysis: " + time_duration[definitions.KEY_DURATION_EXTRACTION] + " minutes")
    output("Map Generation: " + time_duration[definitions.KEY_DURATION_MAP_GENERATION] + " minutes")
    output("Translation: " + time_duration[definitions.KEY_DURATION_TRANSLATION] + " minutes")
    output("Evolution: " + time_duration[definitions.KEY_DURATION_EVOLUTION] + " minutes")
    output("Transplantation: " + time_duration[definitions.KEY_DURATION_TRANSPLANTATION] + " minutes")
    output("Verification: " + time_duration[definitions.KEY_DURATION_VERIFICATION] + " minutes")
    # output("Reverse Transplantation: " + time_duration[Definitions.KEY_DURATION_REVERSE] + " minutes")
    # output("Evaluation: " + time_duration[Definitions.KEY_DURATION_EVALUATION] + " minutes")
    output("Comparison: " + time_duration[definitions.KEY_DURATION_COMPARISON] + " minutes")
    output("Summarizing: " + time_duration[definitions.KEY_DURATION_SUMMARIZATION] + " minutes")
    if is_error:
        output(values.TOOL_NAME + " exited with an error after " + time_duration[
            definitions.KEY_DURATION_TOTAL] + " minutes")
    else:
        output(values.TOOL_NAME + " finished successfully after " + time_duration[
            definitions.KEY_DURATION_TOTAL] + " minutes")


# python 基础规则介绍

import time
# 使用 import 导入需要的组件模块

# 通过缩进而不是用 {} 来控制类、函数；也并不需要以 : 作为语句的结束符号，而是以新行作为上一行的结束符
if 1 > 0:
    print('缩进测试')
else:
    print('缩进测试')

'''
 常见数据类型使用介绍
  整型   100
  浮点数  100.4
  字符串  '字符串'
  布尔值  False True
  类型之间的转换也很方便如
  int('123') 将字符串转为 int
  str(123) 将 整型数字转为 字符串
'''
print(type(123))  # 数据类型获取

'''
变量的定义和使用
Python 中的变量不需要声明。每个变量在使用前都必须赋值，变量赋值以后该变量才会被创建。
'''
p = 100
b = p
print(b / 5)

'''
 序列
 序列程序都是有序排列，可以通过下标偏移量访问其单个或多个成员
 字符串、列表、元祖(元组（tuple）与列表类似，不同之处在于元组的元素不能修改)都属于序列
 “字符串”
 ['列表元素',123,False]
 ("abc","ef")
 序列的几种基本操作方式：
    成员关系操作符   对象[not]in 序列
    连接操作     序列+序列
    重复操作     序列*3
    切片操作     序列[0:整数]

'''
tiangan = '甲乙丙丁戊己庚辛壬癸'
print(tiangan[3])
print(tiangan[0:4])  # 左闭右开，从偏移量为0的元素开始到偏移量为4的元素，但不包含4
print('甲乙' not in tiangan)
print(tiangan * 3)

# 元组测试
yuanzu = ("甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸")
yz = ()  # 空元组
yz2 = ("sd",)  # 声明只有一个元素的元组
(a, b) = ("甲", "乙")
# 列表测试
a_list = ["a", "b"]
a_list.append("d")
print(a_list)

"""
条件与循环
for 循环
    for x in 序列
while 循环
    while True:
        todo

"""
# a = input('请输入条件判断值：')
a = 'abc'

if a == 'abc':
    print("条件成立")
elif a == 'ed':
    print("elseif 判断")
else:
    print("输入的值 %s 不匹配" % a)
# for 循环
for i in a:
    print(i)

# while 循环
num = 5
while True:
    if num == 7:
        num += 3
        continue  # 跳过档次循环
    print(num)
    num += 1
    if num == 110:
        break  # 循环停止

'''
字典（映射类型，类似于Java中的 Map）
'''
dic_man = {'name': '陈小远', 'age': 18}
dic_man['addr'] = "这是我的住址"
print(dic_man)

'''
 列表推导式和字典推导式
'''
# 实现从 1到10 所有偶数的平方
b_list = []

for i in range(1, 11):  # 使用常规方式实现
    if (i % 2 == 0):
        b_list.append(i * i)

b_list = [i * i for i in range(1, 11) if (i % 2 == 0)]  # 使用列表推导式的方式
b_dic = {i: 0 for i in dic_man}  # 字典推导式
print(b_list)
print(b_dic)


import sys
input = sys.stdin.readline

s = input()[:-1]
m = int(input())
left = list(s)
right = []
for _ in range(m):
    inp = list(input().split())
    cmd = inp[0]
    if cmd == 'L':
        if left:
            w = left.pop()
            right.append(w)
    if cmd == 'D':
        if right:
            w = right.pop()
            left.append(w)
    if cmd == 'B':
        if left: # left가 비워져있지 않을 때 == 커서가 문장의 맨 앞
            left.pop()
    if cmd == 'P':
        left.append(inp[1])
    # print(left, right)
print(''.join(left+right[::-1]))


import numpy as np
import json
from utilities import json_numpy
import utilities.constants as c
import random
import time

def activation(x, output=False):
    activation_function = c.OUTPUT_ACTIVATION if output else c.ACTIVATION
    if activation_function in ['sigmoid', 'Sigmoid', 'SIGMOID']:
        return 1 / (1 + np.exp(-x))
    elif activation_function in ['ReLU', 'relu', 'RELU']:
        return x * (x > 0)
    elif activation_function in ['linear', 'Linear', 'line']:
        return x

def d_activation(x, output=False):
    activation_function = c.OUTPUT_ACTIVATION if output else c.ACTIVATION
    if activation_function in ['sigmoid', 'Sigmoid', 'SIGMOID']:
        return x * (1 - x)
    elif activation_function in ['ReLU', 'relu', 'RELU']:
        return 1 * (x > 0)
    elif activation_function in ['linear', 'Linear', 'line']:
        return np.ones_like(x)

class NeuralNetwork:
    def __init__(self, inputs, hidden, outputs, learning_rate=0.1):
        self.inputs = inputs
        if isinstance(hidden, int):
            hidden = [hidden]
        self.hidden = np.array(hidden)
        self.outputs = outputs
        self.learning_rate = learning_rate

        # create weights and gradients for first hidden layer (defined based on number of inputs)
        self.weights = [np.random.uniform(-1, 1, size=(self.hidden[0], self.inputs)).astype(np.float128)]
        self.gradients = [np.zeros((self.hidden[0], self.inputs), np.float128)]

        # create weights and gradients for interior hidden layers (defined based on previous layer)
        if self.hidden.ndim > 0:
            for idx, hidden_col in enumerate(self.hidden[1:]):
                self.weights.append(np.random.uniform(-1, 1, size=(hidden_col, self.hidden[idx])).astype(np.float128))
                self.gradients.append(np.zeros((hidden_col, self.hidden[idx]), np.float128))

        # create weights and gradients for output layer (defined based on number of outputs)
        self.weights.append(np.random.uniform(-1, 1, size=(self.outputs, self.hidden[-1],)).astype(np.float128))
        self.gradients.append(np.zeros((self.outputs, self.hidden[-1],), np.float128))

        # create bias and bias_gradients lists of matrices (one per hidden layer and one for the output)
        self.bias = []
        self.bias_gradients = []
        for idx, hidden_col in enumerate(self.hidden):
            self.bias.append(np.random.uniform(-1, 1, size=(hidden_col, 1)).astype(np.float128))
            self.bias_gradients.append(np.zeros((hidden_col, 1), np.float128))
        self.bias.append(np.random.uniform(-1, 1, size=(self.outputs, 1)).astype(np.float128))
        self.bias_gradients.append(np.zeros((self.outputs, 1), np.float128))

        # Create the dictionary variables for the optimization methods
        self.v = {}
        self.s = {}
        for i in range(self.hidden.size + 1):
            self.v["dW" + str(i)] = 0
            self.v["db" + str(i)] = 0
            self.s["dW" + str(i)] = 0
            self.s["db" + str(i)] = 0

    def gradient_zeros(self):
        self.gradients = [np.zeros((self.hidden[0], self.inputs), np.float128)]
        self.bias_gradients = [np.zeros((self.hidden[0], 1), np.float128)]
        if self.hidden.ndim > 0:
            for idx, hidden_col in enumerate(self.hidden[1:]):
                self.gradients.append(np.zeros((hidden_col, self.hidden[idx]), np.float128))
                self.bias_gradients.append(np.zeros((hidden_col, 1), np.float128))
        self.gradients.append(np.zeros((self.outputs, self.hidden[-1]), np.float128))
        self.bias_gradients.append(np.zeros((self.outputs, 1), np.float128))

    def copy_from(self, neural_net):
        self.weights = neural_net.weights
        self.bias = neural_net.bias

    def save_to_file(self, file_name='NeuralNet.json'):
        json_file = {
            'weights': self.weights,
            'biases': self.bias}
        try:
            with open(file_name, 'w') as file:
                json.dump(
                    json_file,
                    file,
                    ensure_ascii=False,
                    cls=json_numpy.EncodeFromNumpy)
                print('weights saved to file')
        except:
            print('cannot save to ', file)

    def load_from_file(self, file_name='NeuralNet.json'):
        try:
            with open(file_name) as file:
                json_file = json.load(file, cls=json_numpy.DecodeToNumpy)
                print('weights loaded from file')
                self.weights = json_file['weights']
                self.bias = json_file['biases']
        except:
            print('cannot open ', file_name)

    def forward_propagation(self, input_values, **kwargs):
        # create hidden results list for results matrices per hidden layer
        hidden_results = []

        # prepare the input values for matrix multiplication
        input_values = np.array(input_values)[np.newaxis].T

        # calculate results for the first hidden layer (depending on the inputs)
        hidden_results.append(activation(np.matmul(self.weights[0], input_values) + self.bias[0]))

        # calculate results for subsequent hidden layers if any (depending on the previous layer)
        if self.hidden.ndim > 0:
            for idx, hidden_cells in enumerate(self.hidden[1:]):
                hidden_results.append(activation(np.matmul(self.weights[idx + 1],
                                                           hidden_results[idx]) +
                                                 self.bias[idx + 1]))

        # calculate final result and return, if explicit is set then return all the intermediate results as well
        output = []
        if 'explicit' in kwargs.keys():
            if kwargs.get('explicit') in ['yes', 'y', 1]:
                output = hidden_results
        output.append(activation(np.matmul(self.weights[-1], hidden_results[-1]) + self.bias[-1], True))
        return output

    def train_once(self, inputs, targets):
        # get the results including the hidden layers' (intermediate results)
        results = self.forward_propagation(inputs, explicit='yes')

        # prepare the targets and inputs for matrix operations
        targets = np.array(targets)[np.newaxis].T
        input_values = np.array(inputs)[np.newaxis].T

        # calculate the derivative error_matrix (targets vs outputs), index 0
        d_error_matrix = [((targets - results[-1]) * d_activation(results[-1], True)) / c.BATCH_SIZE]

        # calculate the derivative error_matrix of the hidden layers from last to first but insert in the correct order
        for idx in range(len(results) - 1, 0, -1):
            d_error_matrix.insert(0, np.matmul(self.weights[idx].T, d_error_matrix[0] * d_activation(results[idx])))

        # calculate the gradient for the weights that feed the output layer
        self.weights[-1] -= self.learning_rate * np.matmul(d_error_matrix[-1], results[-2].T)
        self.bias[-1] -= self.learning_rate * d_error_matrix[-1]

        # calculate the gradient for all subsequent hidden layers
        for idx, weight_col in enumerate(self.weights[1:-1]):
            weight_col -= self.learning_rate * np.matmul(d_error_matrix[idx + 1], results[idx].T)
            self.bias[idx + 1] -= self.learning_rate * d_error_matrix[idx + 1]

        # calculate the gradient for the first layer weights (input -> first hidden layer)
        self.weights[0] -= self.learning_rate * np.matmul(d_error_matrix[0], input_values.T)
        self.bias[0] -= self.learning_rate * d_error_matrix[0]

    def calculate_gradient(self, inputs, targets):
        # get the results including the hidden layers' (intermediate results)
        results = self.forward_propagation(inputs, explicit='yes')

        # prepare the targets and inputs for matrix operations
        input_values = np.array(inputs)[np.newaxis].T
        # targets = np.array(targets)[np.newaxis].T

        # calculate the error (outputs vs targets), index 0
        error = [(results[-1] - targets) / c.BATCH_SIZE]

        loss = (np.sum((targets - results[-1]) ** 2) / len(targets))

        # calculate the error of the hidden layers from last to first but insert in the correct order
        for idx in range(len(results) - 2, -1, -1):
            error.insert(0, np.matmul(self.weights[idx + 1].T, error[0]))

        # modify weights and biases gradients (input -> first hidden layer)
        self.gradients[0] += np.matmul((error[0] * d_activation(results[0])), input_values.T)
        self.bias_gradients[0] += (error[0] * d_activation(results[0]))

        # modify weights and biases gradients (all subsequent hidden layers and output)
        for idx, gradient_cols in enumerate(self.gradients[1:-1]):
            gradient_cols += np.matmul((error[idx + 1] * d_activation(results[idx + 1])),
                                       results[idx].T)
            self.bias_gradients[idx + 1] += (error[idx + 1] * d_activation(results[idx + 1]))

        self.gradients[-1] += np.matmul((error[-1] * d_activation(results[-1], True)),
                                        results[-2].T)
        self.bias_gradients[-1] += (error[-1] * d_activation(results[-1], True))

        return loss

    @staticmethod
    def cyclic_learning_rate(learning_rate, epoch):
        max_lr = learning_rate * c.MAX_LR_FACTOR
        cycle = np.floor(1 + (epoch / (2 * c.LR_STEP_SIZE)))
        x = np.abs((epoch / c.LR_STEP_SIZE) - (2 * cycle) + 1)
        return learning_rate + (max_lr - learning_rate) * np.maximum(0, (1 - x))

    def apply_gradients(self, epoch):
        true_epoch = epoch - c.BATCH_SIZE
        eta = self.learning_rate * (1 / (1 + c.DECAY_RATE * true_epoch))

        if c.CLR_ON:
            eta = self.cyclic_learning_rate(eta, true_epoch)

        if epoch % 100 == 0:
            print('epoch: ', true_epoch, '| LR:', eta)

        for i, weight_col in enumerate(self.weights):

            if c.OPTIMIZATION == 'vanilla':
                weight_col -= eta * np.array(self.gradients[i]) / c.BATCH_SIZE
                self.bias[i] -= eta * np.array(self.bias_gradients[i]) / c.BATCH_SIZE

            elif c.OPTIMIZATION == 'SGD_momentum':
                self.v["dW" + str(i)] = ((c.BETA1 * self.v["dW" + str(i)])
                                         + (eta * np.array(self.gradients[i])))
                self.v["db" + str(i)] = ((c.BETA1 * self.v["db" + str(i)])
                                         + (eta * np.array(self.bias_gradients[i])))

                weight_col -= self.v["dW" + str(i)] / c.BATCH_SIZE
                self.bias[i] -= self.v["db" + str(i)] / c.BATCH_SIZE

            elif c.OPTIMIZATION == 'NAG':
                v_prev = {"dW" + str(i): self.v["dW" + str(i)], "db" + str(i): self.v["db" + str(i)]}

                self.v["dW" + str(i)] = (c.NAG_COEFF * self.v["dW" + str(i)]
                                         - eta * np.array(self.gradients[i]))
                self.v["db" + str(i)] = (c.NAG_COEFF * self.v["db" + str(i)]
                                         - eta * np.array(self.bias_gradients[i]))

                weight_col += -1 * ((c.BETA1 * v_prev["dW" + str(i)])
                                    + (1 + c.BETA1) * self.v["dW" + str(i)]) / c.BATCH_SIZE
                self.bias[i] += ((-1 * c.BETA1 * v_prev["db" + str(i)])
                                 + (1 + c.BETA1) * self.v["db" + str(i)]) / c.BATCH_SIZE

            elif c.OPTIMIZATION == 'RMSProp':
                self.s["dW" + str(i)] = ((c.BETA1 * self.s["dW" + str(i)])
                                         + ((1 - c.BETA1) * (np.square(np.array(self.gradients[i])))))
                self.s["db" + str(i)] = ((c.BETA1 * self.s["db" + str(i)])
                                         + ((1 - c.BETA1) * (np.square(np.array(self.bias_gradients[i])))))

                weight_col -= (eta * (np.array(self.gradients[i])
                                      / (np.sqrt(self.s["dW" + str(i)] + c.EPSILON)))) / c.BATCH_SIZE
                self.bias[i] -= (eta * (np.array(self.bias_gradients[i])
                                        / (np.sqrt(self.s["db" + str(i)] + c.EPSILON)))) / c.BATCH_SIZE

            if c.OPTIMIZATION == "ADAM":
                # decaying averages of past gradients
                self.v["dW" + str(i)] = ((c.BETA1 * self.v["dW" + str(i)])
                                         + ((1 - c.BETA1) * np.array(self.gradients[i])))
                self.v["db" + str(i)] = ((c.BETA1 * self.v["db" + str(i)])
                                         + ((1 - c.BETA1) * np.array(self.bias_gradients[i])))

                # decaying averages of past squared gradients
                self.s["dW" + str(i)] = ((c.BETA2 * self.s["dW" + str(i)])
                                         + ((1 - c.BETA2) * (np.square(np.array(self.gradients[i])))))
                self.s["db" + str(i)] = ((c.BETA2 * self.s["db" + str(i)])
                                         + ((1 - c.BETA2) * (np.square(np.array(self.bias_gradients[i])))))

                if c.ADAM_BIAS_Correction:
                    # bias-corrected first and second moment estimates
                    self.v["dW" + str(i)] = self.v["dW" + str(i)] / (1 - (c.BETA1 ** true_epoch))
                    self.v["db" + str(i)] = self.v["db" + str(i)] / (1 - (c.BETA1 ** true_epoch))
                    self.s["dW" + str(i)] = self.s["dW" + str(i)] / (1 - (c.BETA2 ** true_epoch))
                    self.s["db" + str(i)] = self.s["db" + str(i)] / (1 - (c.BETA2 ** true_epoch))

                # apply to weights and biases
                weight_col -= ((eta * (self.v["dW" + str(i)]
                                       / (np.sqrt(self.s["dW" + str(i)]) + c.EPSILON)))) / c.BATCH_SIZE
                self.bias[i] -= ((eta * (self.v["db" + str(i)]
                                         / (np.sqrt(self.s["db" + str(i)]) + c.EPSILON)))) / c.BATCH_SIZE

        self.gradient_zeros()

    def RL_train(self, replay_memory, target_network, experience, iteration):
        loss = 0
        states_to_train = []
        targets_to_train = []
        batch = experience(*zip(*replay_memory))
        states = np.array(batch.state)
        actions = np.array(batch.action)
        rewards = np.array(batch.reward)
        next_states = np.array(batch.next_state)
        eps = np.finfo(np.float32).eps.item()

        if c.REWARD_NORMALIZATION:
            rewards_normalized = (rewards - rewards.mean()) / (rewards.std() + eps)
        else:
            rewards_normalized = rewards

        for i in range(len(replay_memory)):
            empty_cells = np.where(next_states[i] == 0)
            if len(empty_cells[0]) == 0:
                target_q = rewards_normalized[i]
            else:
                # calculate max_Q2
                next_state = c.one_hot(next_states[i])
                results_q2 = target_network.forward_propagation(next_state)
                results_q2 = results_q2[0]
                max_q2 = np.max(results_q2)
                target_q = rewards_normalized[i] + c.GAMMA * max_q2

            # form targets vector
            state = c.one_hot(states[i])
            targets = self.forward_propagation(state)
            targets = targets[0]
            old_targets = targets.copy()
            targets[actions[i], 0] = target_q
            loss += (np.sum((targets - old_targets) ** 2) / len(targets)) / (c.BATCH_SIZE * c.EPOCHS)
            states_to_train.append(state)
            targets_to_train.append(targets)

        for i in range(c.EPOCHS):
            ti = time.perf_counter()
            loss2 = 0
            states_to_train_batch = random.sample(states_to_train, c.BATCH_SIZE)
            targets_to_train_batch = random.sample(targets_to_train, c.BATCH_SIZE)
            for j in range(c.BATCH_SIZE):
                loss2 += self.calculate_gradient(states_to_train_batch[j], targets_to_train_batch[j])
            self.apply_gradients(iteration)
            tf = time.perf_counter()
            print('Epoch ', i, '/', c.EPOCHS, f"{tf - ti:0.4f}s", ' - loss: ', loss2 / c.BATCH_SIZE)

        return loss


import json
import numpy as np
from django.shortcuts import render
from django.views import View
from RepBenchWeb.views.config import *
from RepBenchWeb.views import data_loader
import RepBenchWeb.models.datasetsConfig as datasetsConfig
from RepBenchWeb.models import DataSet, InjectedContainer

class DatasetView(View):
    default_nbr_of_ts_to_display = 5
    template = DISPLAY_DATASET_TEMPLATE
    # encoder = NpEncoder
    data_fetch_url_name = "get_data"
    load_data_container = data_loader.load_data_container

    def data_set_info_context(self, setname):
        dataSet = DataSet.objects.get(title=setname)

        # generatate correlation data for highcharts
        df = dataSet.df
        # corr = df.corr().round(3)
        # corr_data = []
        # for i, row in enumerate(corr.values):
        #     for j, v in enumerate(row):
        #         corr_data.append([i, j, v])
        # columns = df.columns.tolist()

        context = {}
        context["data_info"] = dataSet.get_info()
        # context["columns"] = columns
        # context["corr_data"] = corr_data
        # context.update(dataSet.get_catch_22_features())
        return context

    def data_set_default_context(self, request, setname=datasetsConfig.default_set):
        data_container = data_loader.load_data_container(setname)
        df = data_container.original_data
        context = {"setname": setname}
        context["RepBenchWeb"] = int(request.GET.get("RepBenchWeb", self.default_nbr_of_ts_to_display))
        context["data_fetch_url_name"] = self.data_fetch_url_name
        return context, df

    def get(self, request, setname=datasetsConfig.default_set):
        context, df = self.data_set_default_context(request, setname)
        context.update(self.data_set_info_context(setname))
        return render(request, self.template, context=context)

# def sliders_view(request, setname="BAFU"):
#     from RepBenchWeb.catch22.features import features
#     dataSet = DataSet.objects.get(title=setname)
#     context = {} #dataSet.get_catch_22_features()
#     ## add abr and description to the features
#     for ts  , feature_dict  in context["catch22"].items():
#         for feature_name , feature_valued_dict in feature_dict.items():
#             feature_valued_dict['abr'] = features[feature_name]["abr"]
#             feature_valued_dict['description'] = features[feature_name]["description"]
#     return RepBenchJsonRespone(context)

def display_datasets(request=None,option="Display" , synthetic=False):
    context = {"datasets": {dataSet.title: dataSet.get_info()
                            for dataSet in DataSet.objects.all()},
               "syntheticDatasets": {dataSet.title: dataSet.get_info()
                                     for dataSet in InjectedContainer.objects.all() if
                                     dataSet.title is not None and dataSet.title != ""},

                "option" : option}

    if synthetic:
        return render(request, DISPLAY_DATASETS_SYNTHETIC_TEMPLATE, context=context)

    return render(request, DISPLAY_DATASETS_TEMPLATE, context=context)

def delete_dataset(request,setname):
    print("delete_dataset")
    print(setname)
    try:
        dataSet = DataSet.objects.get(title=setname)
        dataSet.delete()
        response = f"{setname} deleted"
        response = f"could not delete {setname}"
        raise Exception
    except:
        try:
            dataSet = InjectedContainer.objects.get(title=setname)
            print(dataSet,"EEEEEEEE")
            dataSet.delete()
            response = f"{setname} (synthetic) deleted"
        except Exception as e:
            print(f"could not delete {setname} in {DataSet.objects.all()} or {InjectedContainer.objects.all()}" )
            raise e
            response = f"could not delete {setname}"

    return display_datasets(request)


# https://judge.softuni.org/Contests/Practice/Index/3194#5

SIZE = 5
AIM, TARGET, EMPTY = "A", "x", "."
directions = {
    'up': (-1, 0),
    'down': (1, 0),
    'left': (0, -1),
    'right': (0, 1),
}

def aim_move(mtrx, pos, stp, direct):
    r0, c0 = pos
    r = r0 + directions[direct][0] * stp
    c = c0 + directions[direct][1] * stp
    if 0 <= r < SIZE and 0 <= c < SIZE:
        if mtrx[r][c] == TARGET:
            return r0, c0
        mtrx[r0][c0] = EMPTY
        mtrx[r][c] = AIM
        return r, c
    return r0, c0

def shoot(mtrx, pos, direct):
    r0, c0 = pos
    while True:
        r = r0 + directions[direct][0]
        c = c0 + directions[direct][1]
        if r < 0 or c < 0 or r >= SIZE or c >= SIZE:
            break
        if mtrx[r][c] == TARGET:
            mtrx[r][c] = EMPTY
            return [r, c]
        r0, c0 = r, c

matrix, player_position = [], []
target_count = 0
for i in range(SIZE):
    line = input().split()
    if AIM in line:
        player_position = [i, line.index(AIM)]
    target_count += line.count(TARGET)
    matrix.append(line)

targets_left = target_count
targets_hit = []
for _ in range(int(input())):
    if targets_left == 0:
        break
    command = input()
    command_args = command.split()
    action, direction = command_args[0], command_args[1]

    if action == "move":
        steps = int(command_args[2])
        player_position = aim_move(matrix, player_position, steps, direction)
    elif action == "shoot":
        target = shoot(matrix, player_position, direction)
        if target:
            targets_hit.append(target)
            targets_left -= 1

if targets_left:
    print(f"Training not completed! {targets_left} targets left.")
else:
    print(f"Training completed! All {target_count} targets hit.")
print(*targets_hit, sep="\n")


from datetime import datetime, timedelta
from datetime import date
import csv
import calendar
import os
import time

__all__ = ['find_holidays','is_holiday','last_day_of_month','getNextWeeklyExpiry',
            'getMonthlyExpiry','get_max_date']
dir_name = os.path.dirname(__file__)
holiday_file = os.path.join(dir_name,'./holidays.csv')

holidaydates: date = []

def get_max_date()->date:
    "Latest date of bhav copy"
    latest_date = date.today()
    while is_holiday(latest_date):
        latest_date = latest_date - timedelta(days=1)

    if latest_date < date.today():
        return latest_date

    localtime = time.localtime(time.time())
    hour = localtime.tm_hour

    if hour < 19 :
        return latest_date - timedelta(days=1)
    else:
        return latest_date

def find_holidays()->list:
    if holidaydates:
        return holidaydates
    else:
        with open(holiday_file) as f:
            csv_reader = csv.reader(f, delimiter=',')
            for entry in csv_reader:
                holidaydates.append(datetime.strptime(entry[2], "%d-%b-%y").date())

            return holidaydates

def is_holiday(dt: date) -> bool:
    "finds if NSE is closed for the given date "
    holiday_list = find_holidays()
    if dt in holiday_list or dt.weekday() == 5 or dt.weekday() == 6:
        return True
    else:
        return False

def last_day_of_month(d: datetime.date) -> date:
    """Calculate current month last date"""
    return d.replace(day=calendar.monthrange(d.year, d.month)[1])

def getNextWeeklyExpiry(fromdate: date) -> date:
    """get nearest weekly expiry from the given date"""
    # assume given date is expiry
    expDate = fromdate
    # time delta
    tomorrow = timedelta(days=+1)
    yesterday = timedelta(days=-1)
    # increment from given date until next thursday(3) is found.
    for i in range(7):
        if expDate.weekday() == 3:
            if is_holiday(expDate):  # if thurnsday is holiday
                if expDate != fromdate:  # if given date not holiday thursday
                    expDate = expDate + yesterday  # expiry is one day before
                    print(f"next weekly expiry date:{expDate.strftime('%d-%m-%Y')}")
                    return (expDate)
                else:  # if given date is  holiday thursday
                    fromdate = fromdate + tomorrow
                    return getNextWeeklyExpiry(fromdate)  # get expiry of next week
            else:
                print(f"next weekly expiry date:{expDate.strftime('%d-%m-%Y')}")
                return (expDate)
        # go to next day
        expDate = expDate + tomorrow

def getMonthlyExpiry(fromDate: date):
    # time delta
    tomorrow = timedelta(days=+1)
    yesterday = timedelta(days=-1)
    # assume given last day of month is expiry
    lastDay = last_day_of_month(fromDate)
    expDate = lastDay
    # find closest thursday(3) to end of the month
    for i in range(7):
        if expDate.weekday() == 3:  # on thursday
            if is_holiday(expDate):  # if last thurday is holiday
                expDate = expDate + yesterday  # wedensday is the expiry
                break  # exit from loop as expiry is found

            else:  # not holiday
                break  # exit from loop as expiry is found
        # go to next day
        else:
            expDate = expDate + yesterday

    if expDate >= fromDate:  # if month expiry is after given date
        print(f"Monthly expiry date:{expDate.strftime('%d-%m-%Y')}")
        return (expDate)

    else:  # given date is past the month expiry
        return getMonthlyExpiry(lastDay + tomorrow)  # get next month expiry

if __name__ == '__main__':
    curDate = date.today()
    print(f"current date:{curDate.strftime('%d-%m-%Y')}")
    getNextWeeklyExpiry(curDate)
    print(last_day_of_month(curDate))


from contextlib import ExitStack
from unittest import mock

import numpy as np
import torch
from ax.core.arm import Arm
from ax.core.data import Data
from ax.core.experiment import Experiment
from ax.core.metric import Metric
from ax.core.objective import Objective
from ax.core.observation import (
    ObservationData,
    ObservationFeatures,
    recombine_observations,
)
from ax.core.optimization_config import OptimizationConfig
from ax.core.outcome_constraint import ScalarizedOutcomeConstraint
from ax.core.search_space import SearchSpace, SearchSpaceDigest
from ax.core.types import ComparisonOp
from ax.modelbridge.base import ModelBridge
from ax.modelbridge.torch import TorchModelBridge
from ax.modelbridge.transforms.base import Transform
from ax.models.torch_base import TorchGenResults, TorchModel
from ax.utils.common.testutils import TestCase
from ax.utils.testing.core_stubs import (
    get_branin_data,
    get_branin_experiment,
    get_branin_search_space,
    get_search_space_for_range_value,
)
from ax.utils.testing.modeling_stubs import get_observation1, transform_1, transform_2
from botorch.utils.datasets import FixedNoiseDataset

class TorchModelBridgeTest(TestCase):
    @mock.patch(
        f"{ModelBridge.__module__}.ModelBridge.__init__",
        autospec=True,
        return_value=None,
    )
    # pyre-fixme[2]: Parameter must be annotated.
    def test_TorchModelBridge(self, mock_init, dtype=None, device=None) -> None:
        ma = TorchModelBridge(
            # pyre-fixme[6]: For 1st param expected `Experiment` but got `None`.
            experiment=None,
            # pyre-fixme[6]: For 2nd param expected `SearchSpace` but got `None`.
            search_space=None,
            # pyre-fixme[6]: For 3rd param expected `Data` but got `None`.
            data=None,
            # pyre-fixme[6]: For 4th param expected `TorchModel` but got `None`.
            model=None,
            transforms=[],
            torch_dtype=dtype,
            torch_device=device,
        )
        dtype = dtype or torch.double
        self.assertEqual(ma.dtype, dtype)
        self.assertEqual(ma.device, device)
        self.assertFalse(mock_init.call_args[-1]["fit_out_of_design"])
        self.assertIsNone(ma._last_observations)
        tkwargs = {"dtype": dtype, "device": device}
        # Test `_fit`.
        feature_names = ["x1", "x2", "x3"]
        model = mock.MagicMock(TorchModel, autospec=True, instance=True)
        search_space = mock.MagicMock(SearchSpace, autospec=True, instance=True)
        type(search_space).parameters = mock.PropertyMock(
            return_value={fn: None for fn in feature_names}  # only need `.keys()`
        )
        X = torch.tensor([[1.0, 2.0, 3.0], [2.0, 3.0, 4.0]], **tkwargs)
        datasets = {
            "y1": FixedNoiseDataset(
                X=X,
                Y=torch.tensor([[3.0], [1.0]], **tkwargs),
                Yvar=torch.tensor([[4.0], [2.0]], **tkwargs),
            ),
            "y2": FixedNoiseDataset(
                X=X,
                Y=torch.tensor([[2.0], [0.0]], **tkwargs),
                Yvar=torch.tensor([[2.0], [1.0]], **tkwargs),
            ),
        }
        observation_features = [
            ObservationFeatures(parameters=dict(zip(feature_names, Xi.tolist())))
            for Xi in X
        ]
        observation_data = [
            ObservationData(
                metric_names=["y1", "y2"],
                means=np.array(y1 + y2),  # here y is already a list
                covariance=np.diag(yvar1 + yvar2),  # here yvar is already a list
            )
            for y1, y2, yvar1, yvar2 in zip(
                datasets["y1"].Y().tolist(),
                datasets["y2"].Y().tolist(),
                datasets["y1"].Yvar().tolist(),
                datasets["y2"].Yvar().tolist(),
            )
        ]
        observations = recombine_observations(observation_features, observation_data)
        ssd = SearchSpaceDigest(
            feature_names=feature_names, bounds=[(0, 1)] * 3  # pyre-ignore
        )

        with mock.patch(
            f"{TorchModelBridge.__module__}.extract_search_space_digest",
            return_value=ssd,
        ):
            ma._fit(
                model=model,
                search_space=search_space,
                observations=observations,
            )
        model_fit_args = model.fit.mock_calls[0][2]
        self.assertEqual(model_fit_args["datasets"], list(datasets.values()))
        self.assertEqual(model_fit_args["metric_names"], ["y1", "y2"])
        self.assertEqual(model_fit_args["search_space_digest"], ssd)
        self.assertIsNone(model_fit_args["candidate_metadata"])
        self.assertEqual(ma._last_observations, observations)

        with mock.patch(f"{TorchModelBridge.__module__}.logger.info") as mock_logger:
            ma._fit(
                model=model,
                search_space=search_space,
                observations=observations,
            )
        mock_logger.assert_called_once_with(
            "The observations are identical to the last set of observations "
            "used to fit the model. Skipping model fitting."
        )

        # Test `_update`
        with mock.patch(
            f"{TorchModelBridge.__module__}.extract_search_space_digest",
            return_value=ssd,
        ):
            ma._update(
                search_space=search_space,
                observations=observations,
            )
        model_update_args = model.update.mock_calls[0][2]
        self.assertEqual(model_update_args["datasets"], list(datasets.values()))
        self.assertEqual(model_update_args["metric_names"], ["y1", "y2"])
        self.assertEqual(model_update_args["search_space_digest"], ssd)
        self.assertIsNone(model_update_args["candidate_metadata"])

        # Test `_predict`
        model.predict.return_value = (
            torch.tensor([[3.0, 2.0]], **tkwargs),
            torch.tensor([[[4.0, 0.0], [0.0, 3.0]]], **tkwargs),
        )
        pr_obs_data_expected = ObservationData(
            metric_names=["y1", "y2"],
            means=np.array([3.0, 2.0]),
            covariance=np.diag([4.0, 3.0]),
        )
        pr_obs_data = ma._predict(observation_features=observation_features[:1])
        self.assertTrue(torch.equal(model.predict.mock_calls[0][2]["X"], X[:1]))
        self.assertEqual(
            pr_obs_data,
            [pr_obs_data_expected],
        )

        # Test `_gen`

        # Hack in some properties set in the (mocked) `Modelbridge.__init__`
        ma.transforms = {}
        ma.fit_time_since_gen = 0.0
        ma._arms_by_signature = {}
        ma._fit_out_of_design = False
        ma._model_key = None
        ma._model_kwargs = None
        ma._bridge_kwargs = None

        model.gen.return_value = TorchGenResults(
            points=torch.tensor([[1.0, 2.0, 3.0]], **tkwargs),
            weights=torch.tensor([1.0], **tkwargs),
            gen_metadata={"foo": 99},
        )

        with ExitStack() as es:
            es.enter_context(
                mock.patch(
                    f"{TorchModelBridge.__module__}.extract_search_space_digest",
                    return_value=ssd,
                )
            )
            es.enter_context(
                mock.patch(
                    f"{ModelBridge.__module__}.clamp_observation_features",
                    side_effect=lambda of, ss: of,
                )
            )
            es.enter_context(
                mock.patch(
                    f"{ModelBridge.__module__}.ModelBridge._get_serialized_model_state",
                    return_value={},
                )
            )
            es.enter_context(
                mock.patch(
                    f"{TorchModelBridge.__module__}.TorchModelBridge."
                    "_array_callable_to_tensor_callable",
                    return_value=torch.round,
                )
            )
            gen_run = ma.gen(
                n=3,
                search_space=search_space,
                optimization_config=OptimizationConfig(
                    objective=Objective(metric=Metric("y1"), minimize=False),
                ),
                pending_observations={
                    "y2": [
                        ObservationFeatures(
                            parameters={"x1": 1.0, "x2": 2.0, "x3": 3.0}
                        )
                    ]
                },
                fixed_features=ObservationFeatures(parameters={"x2": 3.0}),
                model_gen_options={"option": "yes"},
            )
        gen_args = model.gen.mock_calls[0][2]
        self.assertEqual(gen_args["n"], 3)
        self.assertEqual(gen_args["search_space_digest"].bounds, [(0, 1)] * 3)
        gen_opt_config = gen_args["torch_opt_config"]
        self.assertTrue(
            torch.equal(
                gen_opt_config.objective_weights,
                torch.tensor([1.0, 0.0], **tkwargs),
            )
        )
        self.assertIsNone(gen_opt_config.outcome_constraints)
        self.assertIsNone(gen_opt_config.linear_constraints)
        self.assertEqual(gen_opt_config.fixed_features, {1: 3.0})
        X_pending_y1, X_pending_y2 = gen_opt_config.pending_observations
        self.assertTrue(torch.equal(X_pending_y1, torch.tensor([], **tkwargs)))
        self.assertTrue(
            torch.equal(X_pending_y2, torch.tensor([[1.0, 2.0, 3.0]], **tkwargs))
        )
        self.assertEqual(gen_opt_config.model_gen_options, {"option": "yes"})
        self.assertIs(gen_opt_config.rounding_func, torch.round)
        self.assertFalse(gen_opt_config.is_moo)
        self.assertEqual(gen_args["search_space_digest"].target_fidelities, {})
        self.assertEqual(len(gen_run.arms), 1)
        self.assertEqual(gen_run.arms[0].parameters, {"x1": 1.0, "x2": 2.0, "x3": 3.0})
        self.assertEqual(gen_run.weights, [1.0])
        self.assertEqual(gen_run.fit_time, 0.0)
        self.assertEqual(gen_run.gen_metadata, {"foo": 99})

        # Test `_cross_validate`
        model.cross_validate.return_value = (
            torch.tensor([[3.0, 2.0]], **tkwargs),
            torch.tensor([[[4.0, 0.0], [0.0, 3.0]]], **tkwargs),
        )
        cv_obs_data_expected = ObservationData(
            metric_names=["y1", "y2"],
            means=np.array([3.0, 2.0]),
            covariance=np.diag([4.0, 3.0]),
        )
        cv_test_points = [
            ObservationFeatures(parameters={"x1": 1.0, "x2": 3.0, "x3": 2.0})
        ]
        X_test = torch.tensor([[1.0, 3.0, 2.0]], **tkwargs)

        with mock.patch(
            f"{TorchModelBridge.__module__}.extract_search_space_digest",
            return_value=ssd,
        ):
            cv_obs_data = ma._cross_validate(
                search_space=search_space,
                cv_training_data=observations,
                cv_test_points=cv_test_points,
            )
        model_cv_args = model.cross_validate.mock_calls[0][2]
        self.assertEqual(model_cv_args["datasets"], list(datasets.values()))
        self.assertTrue(torch.equal(model_cv_args["X_test"], X_test))
        self.assertEqual(model_cv_args["search_space_digest"], ssd)
        self.assertEqual(cv_obs_data, [cv_obs_data_expected])

        # Transform observations
        # This functionality is likely to be deprecated (T134940274)
        # so this is not a thorough test.
        ma.transform_observations(observations)

        # Transform observation features
        obsf = [ObservationFeatures(parameters={"x": 1.0, "y": 2.0})]
        ma.parameters = ["x", "y"]
        X = ma._transform_observation_features(obsf)
        self.assertTrue(torch.equal(X, torch.tensor([[1.0, 2.0]], **tkwargs)))
        # test fit out of design
        ma = TorchModelBridge(
            # pyre-fixme[6]: For 1st param expected `Experiment` but got `None`.
            experiment=None,
            # pyre-fixme[6]: For 2nd param expected `SearchSpace` but got `None`.
            search_space=None,
            # pyre-fixme[6]: For 3rd param expected `Data` but got `None`.
            data=None,
            # pyre-fixme[6]: For 4th param expected `TorchModel` but got `None`.
            model=None,
            transforms=[],
            torch_dtype=dtype,
            torch_device=device,
            fit_out_of_design=True,
        )
        self.assertTrue(mock_init.call_args[-1]["fit_out_of_design"])

    def test_TorchModelBridge_float(self) -> None:
        self.test_TorchModelBridge(dtype=torch.float)

    def test_TorchModelBridge_cuda(self) -> None:
        if torch.cuda.is_available():
            self.test_TorchModelBridge(device=torch.device("cuda"))

    @mock.patch(f"{TorchModel.__module__}.TorchModel", autospec=True)
    @mock.patch(f"{ModelBridge.__module__}.ModelBridge.__init__")
    # pyre-fixme[3]: Return type must be annotated.
    # pyre-fixme[2]: Parameter must be annotated.
    def test_evaluate_acquisition_function(self, _, mock_torch_model):
        ma = TorchModelBridge(
            # pyre-fixme[6]: For 1st param expected `Experiment` but got `None`.
            experiment=None,
            # pyre-fixme[6]: For 2nd param expected `SearchSpace` but got `None`.
            search_space=None,
            # pyre-fixme[6]: For 3rd param expected `Data` but got `None`.
            data=None,
            # pyre-fixme[6]: For 4th param expected `TorchModel` but got `None`.
            model=None,
            transforms=[],
            torch_dtype=torch.float64,
            torch_device=torch.device("cpu"),
        )
        # These attributes would've been set by `ModelBridge` __init__, but it's mocked.
        ma.model = mock_torch_model()
        t = mock.MagicMock(Transform, autospec=True, wraps=Transform(None, None, None))
        ma.transforms = {"ExampleTransform": t}
        ma.parameters = ["x", "y"]
        model_eval_acqf = mock_torch_model.return_value.evaluate_acquisition_function
        model_eval_acqf.return_value = torch.tensor([5.0], dtype=torch.float64)

        ma._model_space = get_branin_search_space()
        ma._optimization_config = None
        ma.outcomes = ["test_metric"]

        with self.assertRaisesRegex(ValueError, "optimization_config"):
            ma.evaluate_acquisition_function(
                observation_features=[
                    ObservationFeatures(parameters={"x": 1.0, "y": 2.0})
                ],
            )

        with mock.patch(
            "ax.modelbridge.torch.extract_search_space_digest",
            return_value=SearchSpaceDigest(feature_names=[], bounds=[]),
        ):
            acqf_vals = ma.evaluate_acquisition_function(
                observation_features=[
                    ObservationFeatures(parameters={"x": 1.0, "y": 2.0})
                ],
                optimization_config=OptimizationConfig(
                    objective=Objective(metric=Metric(name="test_metric"))
                ),
            )

        self.assertEqual(acqf_vals, [5.0])
        t.transform_observation_features.assert_any_call(
            [ObservationFeatures(parameters={"x": 1.0, "y": 2.0})],
        )
        t.transform_observation_features.reset_mock()
        model_eval_acqf.assert_called_once()
        self.assertTrue(
            torch.equal(  # `call_args` is an (args, kwargs) tuple
                model_eval_acqf.call_args[1]["X"],
                torch.tensor([[[1.0, 2.0]]], dtype=torch.float64),
            )
        )

        # Test evaluating at multiple points.
        # Case 1: List[ObsFeat, ObsFeat], should be 2 x 1 x d.
        with mock.patch(
            "ax.modelbridge.torch.extract_search_space_digest",
            return_value=SearchSpaceDigest(feature_names=[], bounds=[]),
        ):
            acqf_vals = ma.evaluate_acquisition_function(
                observation_features=[
                    ObservationFeatures(parameters={"x": 1.0, "y": 2.0}),
                    ObservationFeatures(parameters={"x": 1.0, "y": 2.0}),
                ],
                optimization_config=OptimizationConfig(
                    objective=Objective(metric=Metric(name="test_metric"))
                ),
            )
        t.transform_observation_features.assert_any_call(
            [ObservationFeatures(parameters={"x": 1.0, "y": 2.0})],
        )
        t.transform_observation_features.reset_mock()
        self.assertTrue(
            torch.equal(  # `call_args` is an (args, kwargs) tuple
                model_eval_acqf.call_args[-1]["X"],
                torch.tensor([[[1.0, 2.0]], [[1.0, 2.0]]], dtype=torch.float64),
            )
        )
        # Case 2: List[List[ObsFeat, ObsFeat]], should be 1 x 2 x d.
        with mock.patch(
            "ax.modelbridge.torch.extract_search_space_digest",
            return_value=SearchSpaceDigest(feature_names=[], bounds=[]),
        ):
            acqf_vals = ma.evaluate_acquisition_function(
                observation_features=[
                    [
                        ObservationFeatures(parameters={"x": 1.0, "y": 2.0}),
                        ObservationFeatures(parameters={"x": 1.0, "y": 2.0}),
                    ]
                ],
                optimization_config=OptimizationConfig(
                    objective=Objective(metric=Metric(name="test_metric"))
                ),
            )
        t.transform_observation_features.assert_any_call(
            [
                ObservationFeatures(parameters={"x": 1.0, "y": 2.0}),
                ObservationFeatures(parameters={"x": 1.0, "y": 2.0}),
            ],
        )
        self.assertTrue(
            torch.equal(  # `call_args` is an (args, kwargs) tuple
                model_eval_acqf.call_args[-1]["X"],
                torch.tensor([[[1.0, 2.0], [1.0, 2.0]]], dtype=torch.float64),
            )
        )

    @mock.patch(
        f"{ModelBridge.__module__}.observations_from_data",
        autospec=True,
        return_value=([get_observation1()]),
    )
    @mock.patch(
        f"{ModelBridge.__module__}.unwrap_observation_data",
        autospec=True,
        return_value=(2, 2),
    )
    @mock.patch(
        f"{ModelBridge.__module__}.gen_arms",
        autospec=True,
        return_value=([Arm(parameters={})], {}),
    )
    @mock.patch(
        f"{ModelBridge.__module__}.ModelBridge.predict",
        autospec=True,
        return_value=({"m": [1.0]}, {"m": {"m": [2.0]}}),
    )
    @mock.patch(f"{TorchModelBridge.__module__}.TorchModelBridge._fit", autospec=True)
    @mock.patch(
        f"{TorchModel.__module__}.TorchModel.gen",
        return_value=TorchGenResults(
            points=torch.tensor([[1, 2]]),
            weights=torch.tensor([1.0]),
        ),
        autospec=True,
    )
    # pyre-fixme[3]: Return type must be annotated.
    def test_best_point(
        self,
        _mock_gen,
        _mock_fit,
        _mock_predict,
        _mock_gen_arms,
        _mock_unwrap,
        _mock_obs_from_data,
    ):
        exp = Experiment(search_space=get_search_space_for_range_value(), name="test")
        oc = OptimizationConfig(
            objective=Objective(metric=Metric("a"), minimize=False),
            outcome_constraints=[],
        )
        search_space = get_search_space_for_range_value()
        modelbridge = TorchModelBridge(
            search_space=search_space,
            model=TorchModel(),
            transforms=[transform_1, transform_2],
            experiment=exp,
            data=Data(),
            optimization_config=oc,
        )

        self.assertEqual(
            list(modelbridge.transforms.keys()),
            ["Cast", "transform_1", "transform_2"],
        )

        # _fit is mocked, which sets these
        modelbridge.parameters = list(search_space.parameters.keys())
        modelbridge.outcomes = ["a"]

        with mock.patch(
            f"{TorchModel.__module__}.TorchModel.best_point",
            return_value=torch.tensor([1.0, 2.0]),
            autospec=True,
        ):
            run = modelbridge.gen(n=1, optimization_config=oc)
            # pyre-fixme[23]: Unable to unpack `Optional[Tuple[Arm,
            #  Optional[Tuple[Dict[str, float], Optional[Dict[str, typing.Dict[str,
            #  float]]]]]]]` into 2 values.
            arm, predictions = run.best_arm_predictions
            # pyre-fixme[23]: Unable to unpack `Optional[Tuple[Arm,
            #  Optional[Tuple[Dict[str, float], Optional[Dict[str, typing.Dict[str,
            #  float]]]]]]]` into 2 values.
            model_arm, model_predictions = modelbridge.model_best_point()
        self.assertEqual(arm.parameters, {})
        self.assertEqual(predictions[0], {"m": 1.0})
        self.assertEqual(predictions[1], {"m": {"m": 2.0}})
        self.assertEqual(model_predictions[0], {"m": 1.0})
        self.assertEqual(model_predictions[1], {"m": {"m": 2.0}})

        # test optimization config validation - raise error when
        # ScalarizedOutcomeConstraint contains a metric that is not in the outcomes
        with self.assertRaises(ValueError):
            modelbridge.gen(
                n=1,
                optimization_config=OptimizationConfig(
                    objective=Objective(metric=Metric("a"), minimize=False),
                    outcome_constraints=[
                        ScalarizedOutcomeConstraint(
                            metrics=[Metric("wrong_metric_name")],
                            weights=[1.0],
                            op=ComparisonOp.LEQ,
                            bound=0,
                        )
                    ],
                ),
            )

        with mock.patch(
            f"{TorchModel.__module__}.TorchModel.best_point",
            side_effect=NotImplementedError,
            autospec=True,
        ):
            res = modelbridge.model_best_point()
        self.assertIsNone(res)

    @mock.patch(
        f"{ModelBridge.__module__}.observations_from_data",
        autospec=True,
        return_value=([get_observation1()]),
    )
    @mock.patch(
        f"{ModelBridge.__module__}.unwrap_observation_data",
        autospec=True,
        return_value=(2, 2),
    )
    @mock.patch(
        f"{ModelBridge.__module__}.gen_arms",
        autospec=True,
        return_value=[Arm(parameters={})],
    )
    @mock.patch(
        f"{ModelBridge.__module__}.ModelBridge.predict",
        autospec=True,
        return_value=({"m": [1.0]}, {"m": {"m": [2.0]}}),
    )
    @mock.patch(f"{TorchModelBridge.__module__}.TorchModelBridge._fit", autospec=True)
    # pyre-fixme[56]: Pyre was not able to infer the type of argument
    #  `numpy.array([[[1.000000]], [[2.000000]]])` to decorator factory
    #  `unittest.mock.patch`.
    @mock.patch(
        f"{TorchModel.__module__}.TorchModel.feature_importances",
        return_value=np.array([[[1.0]], [[2.0]]]),
        autospec=True,
    )
    # pyre-fixme[3]: Return type must be annotated.
    def test_importances(
        self,
        _mock_feature_importances,
        _mock_fit,
        _mock_predict,
        _mock_gen_arms,
        _mock_unwrap,
        _mock_obs_from_data,
    ):
        exp = Experiment(search_space=get_search_space_for_range_value(), name="test")
        search_space = get_search_space_for_range_value()
        modelbridge = TorchModelBridge(
            search_space=search_space,
            model=TorchModel(),
            transforms=[transform_1, transform_2],
            experiment=exp,
            data=Data(),
        )
        # _fit is mocked, which typically sets these
        modelbridge.parameters = list(search_space.parameters.keys())
        modelbridge.outcomes = ["a", "b"]

        self.assertEqual(modelbridge.feature_importances("a"), {"x": [1.0]})
        self.assertEqual(modelbridge.feature_importances("b"), {"x": [2.0]})

    @mock.patch(
        f"{TorchModel.__module__}.TorchModel.gen",
        return_value=TorchGenResults(
            points=torch.tensor([[1, 2], [2, 3]]),
            weights=torch.tensor([1.0, 2.0]),
            candidate_metadata=[
                {"some_key": "some_value_0"},
                {"some_key": "some_value_1"},
            ],
        ),
        autospec=True,
    )
    @mock.patch(f"{TorchModel.__module__}.TorchModel.update", autospec=True)
    @mock.patch(f"{TorchModel.__module__}.TorchModel.fit", autospec=True)
    # pyre-fixme[3]: Return type must be annotated.
    def test_candidate_metadata_propagation(
        self,
        # pyre-fixme[2]: Parameter must be annotated.
        mock_model_fit,
        # pyre-fixme[2]: Parameter must be annotated.
        mock_model_update,
        # pyre-fixme[2]: Parameter must be annotated.
        mock_model_gen,
    ):
        exp = get_branin_experiment(with_status_quo=True, with_batch=True)
        # Check that the metadata is correctly re-added to observation
        # features during `fit`.
        # pyre-fixme[16]: `BaseTrial` has no attribute `_generator_run_structs`.
        preexisting_batch_gr = exp.trials[0]._generator_run_structs[0].generator_run
        preexisting_batch_gr._candidate_metadata_by_arm_signature = {
            preexisting_batch_gr.arms[0].signature: {
                "preexisting_batch_cand_metadata": "some_value"
            }
        }
        modelbridge = TorchModelBridge(
            experiment=exp,
            search_space=exp.search_space,
            model=TorchModel(),
            transforms=[],
            data=get_branin_data(),
        )

        datasets = mock_model_fit.call_args[1].get("datasets")
        X_expected = torch.tensor(
            [list(exp.trials[0].arms[0].parameters.values())],
            dtype=torch.double,
        )
        for dataset in datasets:
            self.assertTrue(torch.equal(dataset.X(), X_expected))

        self.assertEqual(
            mock_model_fit.call_args[1].get("candidate_metadata"),
            [[{"preexisting_batch_cand_metadata": "some_value"}]],
        )

        # Check that `gen` correctly propagates the metadata to the GR.
        gr = modelbridge.gen(n=1)
        self.assertEqual(
            gr.candidate_metadata_by_arm_signature,
            {
                gr.arms[0].signature: {"some_key": "some_value_0"},
                gr.arms[1].signature: {"some_key": "some_value_1"},
            },
        )

        # Check that the metadata is correctly re-added to observation
        # features during `update`.
        batch = exp.new_batch_trial(generator_run=gr)
        modelbridge.update(
            experiment=exp, new_data=get_branin_data(trial_indices=[batch.index])
        )
        datasets = mock_model_update.call_args[1].get("datasets")
        X_expected = torch.cat((X_expected, torch.tensor([[1, 2]])), dim=-2)
        for dataset in datasets:
            self.assertTrue(torch.equal(dataset.X(), X_expected))

        self.assertEqual(
            mock_model_update.call_args[1].get("candidate_metadata"),
            [
                [
                    {"preexisting_batch_cand_metadata": "some_value"},
                    # new data contained data just for arm '1_0', not for '1_1',
                    # so we don't expect to see '{"some_key": "some_value_1"}'
                    # in candidate metadata.
                    {"some_key": "some_value_0"},
                ]
            ],
        )

        # Check that `None` candidate metadata is handled correctly.
        mock_model_gen.return_value = TorchGenResults(
            points=torch.tensor([[2, 4], [3, 5]]),
            weights=torch.tensor([1.0, 2.0]),
            candidate_metadata=None,
        )
        gr = modelbridge.gen(n=1)
        self.assertIsNone(gr.candidate_metadata_by_arm_signature)

        # Check that the metadata is correctly re-added to observation
        # features during `update`.
        batch = exp.new_batch_trial(generator_run=gr)
        modelbridge.update(
            experiment=exp, new_data=get_branin_data(trial_indices=[batch.index])
        )
        datasets = mock_model_update.call_args[1].get("datasets")
        X_expected = torch.cat((X_expected, torch.tensor([[2, 4]])), dim=-2)
        for dataset in datasets:
            self.assertTrue(torch.equal(dataset.X(), X_expected))

        self.assertEqual(
            mock_model_update.call_args[1].get("candidate_metadata"),
            [
                [
                    {"preexisting_batch_cand_metadata": "some_value"},
                    {"some_key": "some_value_0"},
                    {},
                ]
            ],
        )

        # Check that no candidate metadata is handled correctly.
        exp = get_branin_experiment(with_status_quo=True)

        with mock.patch(
            f"{TorchModelBridge.__module__}."
            "TorchModelBridge._validate_observation_data",
            autospec=True,
        ):
            modelbridge = TorchModelBridge(
                search_space=exp.search_space,
                experiment=exp,
                model=TorchModel(),
                data=Data(),
                transforms=[],
            )
            # Hack in outcome names to bypass validation (since we did not pass any
            # to the model so _fit did not populate this)
            # pyre-fixme[8]: Attribute has type `List[str]`; used as `str`.
            # pyre-fixme[8]: Attribute has type `Set[str]`; used as `str`.
            modelbridge.outcomes = modelbridge._metric_names = next(iter(exp.metrics))
        gr = modelbridge.gen(n=1)
        self.assertIsNone(mock_model_fit.call_args[1].get("candidate_metadata"))
        self.assertIsNone(gr.candidate_metadata_by_arm_signature)
        batch = exp.new_batch_trial(generator_run=gr)
        modelbridge.update(
            experiment=exp, new_data=get_branin_data(trial_indices=[batch.index])
        )


'''
@Description: 百分之分数转化成等级
@version: 1.0
@Company: Lifegen
@Author: CornC.fcx
@Date: 2019-05-17 17:06:28
@LastEditors: CornC.fcx
@LastEditTime: 2019-05-17 17:13:32
'''

'''
score > 90        -------> A
80 < score <= 90  -------> B
60 < score <= 80  -------> C
score < 60        -------> D
'''

score = int(input('Input Score (100) : '))
if score > 90:
    grade = 'A'
elif score > 80 and score <= 90:
    grade = 'B'
elif score > 60 and score <= 80:
    grade = 'C'
else:
    grade = 'D'

print('You Grade is ', grade)