import math
import numpy as np
from typing import List, Optional
import torch
import torch.nn as nn
import torch.nn.functional as F
from copy import copy
from copy import deepcopy
from collections import defaultdict

from detrex.layers import MLP, box_cxcywh_to_xyxy, box_xyxy_to_cxcywh, generalized_box_iou
from detrex.utils import inverse_sigmoid
from detrex.modeling import SetCriterion
from detrex.modeling.criterion.criterion import sigmoid_focal_loss

from detectron2.structures import Boxes, ImageList, Instances
from detectron2.structures.boxes import matched_pairwise_iou
from detrex.utils import get_world_size, is_dist_avail_and_initialized

from projects.co_mot.util import checkpoint
from projects.co_mot.util.misc import (NestedTensor, nested_tensor_from_tensor_list, accuracy)

class MOT(nn.Module):
    """ Implement CO-MOT: Bridging the Gap Between End-to-end and Non-End-to-end Multi-Object Tracking
    Args:
        backbone (nn.Module): backbone module
        position_embedding (nn.Module): position embedding module
        neck (nn.Module): neck module to handle the intermediate outputs features
        transformer (nn.Module): transformer module
        embed_dim (int): dimension of embedding
        num_classes (int): Number of total categories.
        num_queries (int): Number of proposal dynamic anchor boxes in Transformer
        criterion (nn.Module): Criterion for calculating the total losses.
        pixel_mean (List[float]): Pixel mean value for image normalization.
            Default: [123.675, 116.280, 103.530].
        pixel_std (List[float]): Pixel std value for image normalization.
            Default: [58.395, 57.120, 57.375].
        aux_loss (bool): Whether to calculate auxiliary loss in criterion. Default: True.
        select_box_nums_for_evaluation (int): the number of topk candidates
            slected at postprocess for evaluation. Default: 300.
        device (str): Training device. Default: "cuda".
    """

    def __init__(
        self,
        backbone: nn.Module,
        position_embedding: nn.Module,
        neck: nn.Module,
        transformer: nn.Module,
        embed_dim: int,
        num_classes: int,
        num_queries: int,
        criterion: nn.Module,
        track_embed: nn.Module,
        track_base: nn.Module,
        post_process: nn.Module,
        aux_loss: bool = True,
        device="cuda",
        g_size=1,
    ):
        super().__init__()
        # define backbone and position embedding module
        self.backbone = backbone
        self.position_embedding = position_embedding

        # define neck module
        self.neck = neck

        # number of dynamic anchor boxes and embedding dimension
        self.num_queries = num_queries
        self.embed_dim = embed_dim

        # define transformer module
        self.transformer = transformer

        # define classification head and box head
        self.class_embed = nn.Linear(embed_dim, num_classes)
        self.bbox_embed = MLP(embed_dim, embed_dim, 4, 3)
        self.num_classes = num_classes

        # where to calculate auxiliary loss in criterion
        self.aux_loss = aux_loss
        self.criterion = criterion

        self.device = device

        # initialize weights
        prior_prob = 0.01
        bias_value = -math.log((1 - prior_prob) / prior_prob)
        self.class_embed.bias.data = torch.ones(num_classes) * bias_value
        nn.init.constant_(self.bbox_embed.layers[-1].weight.data, 0)
        nn.init.constant_(self.bbox_embed.layers[-1].bias.data, 0)
        for _, neck_layer in self.neck.named_modules():
            if isinstance(neck_layer, nn.Conv2d):
                nn.init.xavier_uniform_(neck_layer.weight, gain=1)
                nn.init.constant_(neck_layer.bias, 0)

        # if two-stage, the last class_embed and bbox_embed is for region proposal generation
        num_pred = transformer.decoder.num_layers
        self.class_embed = nn.ModuleList([deepcopy(self.class_embed) for i in range(num_pred)])
        self.bbox_embed = nn.ModuleList([deepcopy(self.bbox_embed) for i in range(num_pred)])
        nn.init.constant_(self.bbox_embed[0].layers[-1].bias.data[2:], -2.0)

        # two-stage
        self.transformer.decoder.class_embed = self.class_embed
        self.transformer.decoder.bbox_embed = self.bbox_embed

        # hack implementation for two-stage
        for bbox_embed_layer in self.bbox_embed:
            nn.init.constant_(bbox_embed_layer.layers[-1].bias.data[2:], 0.0)

        # for Track
        self.track_embed = track_embed
        self.post_process = post_process  # TrackerPostProcess(g_size=g_size)
        self.track_base = track_base

        # for shadow
        self.g_size = g_size

        # for init of query
        self.position = nn.Embedding(num_queries, 4)
        self.position_offset = nn.Embedding(num_queries*g_size, 4)
        self.query_embed = nn.Embedding(num_queries, embed_dim)
        self.query_embed_offset = nn.Embedding(num_queries*g_size, embed_dim)

        nn.init.uniform_(self.position.weight.data, 0, 1)
        nn.init.normal_(self.position_offset.weight.data, 0, 10e-6)  # 默认为10e-6
        nn.init.normal_(self.query_embed_offset.weight.data, 0, 10e-6) # 默认为10e-6

    def _generate_empty_tracks(self, g_size=1, batch_size=1):
        track_instances = Instances((1, 1))
        num_queries, d_model = self.query_embed.weight.shape  # (300, 512)
        device = self.query_embed.weight.device
        track_instances.ref_pts = self.position.weight.view(-1, 1, 4).repeat(1, g_size, 1).view(-1, 4) + self.position_offset.weight
        track_instances.query_pos = self.query_embed.weight.view(-1, 1, d_model).repeat(1, g_size, 1).view(-1, d_model) +  self.query_embed_offset.weight
        track_instances.ref_pts = track_instances.ref_pts.view(-1, 1, 4).repeat(1, batch_size, 1)
        track_instances.query_pos = track_instances.query_pos.view(-1, 1, d_model).repeat(1, batch_size, 1)

        track_instances.output_embedding = torch.zeros((len(track_instances), batch_size, d_model), device=device)  # motr decode输出的feature，把这个输入qim中可以获得track的query，某个目标跟踪过程中不再使用query_pos
        track_instances.obj_idxes = torch.full((len(track_instances), batch_size), -1, dtype=torch.long, device=device)  # ID
        track_instances.matched_gt_idxes = torch.full((len(track_instances), batch_size), -1, dtype=torch.long, device=device) # 与匹配到的gt在该图片中的索引
        track_instances.disappear_time = torch.zeros((len(track_instances), batch_size), dtype=torch.long, device=device)  # 消失时间，假如目标跟踪多久后删除该目标
        track_instances.iou = torch.zeros((len(track_instances), batch_size), dtype=torch.float, device=device) #  与对应GT的IOU
        track_instances.scores = torch.zeros((len(track_instances), batch_size), dtype=torch.float, device=device)   # 实际是当前帧检测输出的置信度
        track_instances.track_scores = torch.zeros((len(track_instances), batch_size), dtype=torch.float, device=device)
        track_instances.pred_boxes = torch.zeros((len(track_instances), batch_size, 4), dtype=torch.float, device=device)  # 检测或跟踪query输出的box框
        track_instances.pred_logits = torch.zeros((len(track_instances), batch_size, self.num_classes), dtype=torch.float, device=device)  # 检测或跟踪query输出的置信度argsigmod
        track_instances.group_ids = torch.arange(g_size, dtype=torch.long, device=device).repeat(num_queries).view(-1, 1).repeat(1, batch_size)
        track_instances.labels = torch.full((len(track_instances), batch_size), -1, dtype=torch.long, device=device)

        return track_instances.to(self.query_embed.weight.device)

    def clear(self):
        self.track_base.clear()

    def _forward_single_image(self, samples, track_instances, gtboxes=None):
        """Forward function of `MOT`.
        """

        # original features
        features = self.backbone(samples.tensors)  # output feature dict
        img_masks = samples.mask

        # project backbone features to the reuired dimension of transformer
        # we use multi-scale features in DINO
        multi_level_feats = self.neck(features)
        multi_level_masks = []
        multi_level_position_embeddings = []
        for feat in multi_level_feats:
            multi_level_masks.append(
                F.interpolate(img_masks[None].float(), size=feat.shape[-2:]).to(torch.bool).squeeze(0)
            )
            multi_level_position_embeddings.append(self.position_embedding(multi_level_masks[-1]))

        # prepare label query embedding
        input_query_label = track_instances.query_pos
        input_query_bbox = track_instances.ref_pts
        attn_mask = None

        # feed into transformer 包括encode + decode
        (
            inter_states,
            init_reference,
            inter_references,
            enc_state,
            enc_reference,  # [0..1]
        ) = self.transformer(
            multi_level_feats,
            multi_level_masks,
            multi_level_position_embeddings,
            input_query_label,
            ref_pts=input_query_bbox,
            attn_mask=attn_mask,
        )

        # Calculate output coordinates and classes.
        outputs_classes = []
        outputs_coords = []
        for lvl in range(inter_states.shape[0]):
            if lvl == 0:
                reference = init_reference
            else:
                reference = inter_references[lvl - 1]
            reference = inverse_sigmoid(reference)
            outputs_class = self.class_embed[lvl](inter_states[lvl])
            tmp = self.bbox_embed[lvl](inter_states[lvl])
            if reference.shape[-1] == 4:
                tmp += reference
            else:
                assert reference.shape[-1] == 2
                tmp[..., :2] += reference
            outputs_coord = tmp.sigmoid()
            outputs_classes.append(outputs_class)
            outputs_coords.append(outputs_coord)
        outputs_class = torch.stack(outputs_classes)
        # tensor shape: [num_decoder_layers, bs, num_query, num_classes]
        outputs_coord = torch.stack(outputs_coords)
        # tensor shape: [num_decoder_layers, bs, num_query, 4]

        # prepare for loss computation
        output = {"pred_logits": outputs_class[-1], "pred_boxes": outputs_coord[-1]}
        if self.aux_loss:
            output["aux_outputs"] = self._set_aux_loss(outputs_class, outputs_coord)
        output['hs'] = inter_states[-1]
        return output

    def _post_process_single_image(self, frame_res, track_instances, is_last):
        with torch.no_grad():
            if self.training:
                track_scores = frame_res['pred_logits'].sigmoid().max(dim=-1).values
            else:
                track_scores = frame_res['pred_logits'].sigmoid().max(dim=-1).values

        track_instances.scores = track_scores.transpose(0, 1)
        track_instances.pred_logits = frame_res['pred_logits'].transpose(0, 1)
        track_instances.pred_boxes = frame_res['pred_boxes'].transpose(0, 1)
        track_instances.output_embedding = frame_res['hs'].transpose(0, 1)

        if self.training:
            # the track id will be assigned by the mather.
            frame_res['track_instances'] = track_instances
            track_instances = self.criterion.match_for_single_frame(frame_res)  # 找匹配（跟踪query+检测query）分配GT的ID+ 算loss
        else:
            # each track will be assigned an unique global id by the track base.
            self.track_base.update(track_instances, g_size=self.g_size)  # 为存在的目标分配ID，并删除长时间消失的目标ID

        tmp = {}
        tmp['track_instances'] = track_instances
        if not is_last: # 经过这步后将仅保留有ID的目标，且更新了track的query和pos
            out_track_instances = self.track_embed(tmp, g_size=self.g_size)  # 更新跟踪的query，用于下一帧（检测query为学习的，跟踪query则为上一帧输出经过qim变换的特征）
            frame_res['track_instances'] = out_track_instances
        else:
            frame_res['track_instances'] = None

        # print('post:', t1-t0, t2-t1)
        return frame_res

    # 获取当前帧的跟踪框
    @torch.no_grad()
    def inference_single_image(self, img, ori_img_size, track_instances=None):
        if not isinstance(img, NestedTensor):
            img = nested_tensor_from_tensor_list(img)  # 补pad并或者pad的mask
        if track_instances is None:
            track_instances = self._generate_empty_tracks(g_size=self.g_size) # 初始化decode的输入或者说目标query，包括（query+pose）
        else:
            track_instances = Instances.cat([self._generate_empty_tracks(g_size=self.g_size), track_instances])

        res = self._forward_single_image(img, track_instances=track_instances)  # backbone+encode+decode，获得decode的输出和中间迭代过程输出的box和最后输出的feat(hs,作为经过QIM后可作为下一帧的query)
        res = self._post_process_single_image(res, track_instances, False)  # train是算loss，test时过滤有效跟踪框+为下一帧更新query/pos

        track_instances = res['track_instances']
        track_instances = self.post_process(track_instances, ori_img_size)  # 把box框换算到图像大小
        ret = {'track_instances': track_instances}
        if 'ref_pts' in res: # 把参考点也换算到图像大小
            ref_pts = res['ref_pts']
            img_h, img_w = ori_img_size
            scale_fct = torch.Tensor([img_w, img_h]).to(ref_pts)
            ref_pts = ref_pts * scale_fct[None]
            ret['ref_pts'] = ref_pts
        return ret

    def forward(self, data: dict):
        # 准备
        def fn(frame, gtboxes, track_instances):
            frame = nested_tensor_from_tensor_list(frame)
            frame_res = self._forward_single_image(frame, track_instances, gtboxes)
            return frame_res

        track_instances = None
        if self.training:
            self.criterion.initialize_for_single_clip(data['gt_instances'])
            frames = data['imgs']  # list of Tensor.
            outputs = {
                'pred_logits': [],
                'pred_boxes': [],
            }
            for frame_index, (frame, gt) in enumerate(zip(frames, data['gt_instances'])):
                for f in frame:
                    f.requires_grad = False
                is_last = frame_index == len(frames) - 1
                nbatch = len(frame)
                gtboxes = None

                if track_instances is None:
                    track_instances = self._generate_empty_tracks(g_size=self.g_size, batch_size=nbatch)
                else:
                    track_instances = Instances.cat([
                        self._generate_empty_tracks(g_size=self.g_size, batch_size=nbatch),
                        track_instances])

                if frame_index < len(frames) - 1:
                    args = [frame, gtboxes, track_instances]
                    params = tuple((p for p in self.parameters() if p.requires_grad))
                    frame_res = checkpoint.CheckpointFunction.apply(fn, len(args), *args, *params)
                else:
                    frame = nested_tensor_from_tensor_list(frame)
                    frame_res = self._forward_single_image(frame, track_instances, gtboxes)

                frame_res = self._post_process_single_image(frame_res, track_instances, is_last)

                track_instances = frame_res['track_instances']
                outputs['pred_logits'].append(frame_res['pred_logits'])
                outputs['pred_boxes'].append(frame_res['pred_boxes'])

            # compute loss
            outputs['losses_dict'] = self.criterion.losses_dict
            loss_dict = self.criterion(outputs, data)
            weight_dict = self.criterion.weight_dict
            for k in loss_dict.keys():
                if k in weight_dict:
                    loss_dict[k] *= weight_dict[k]
            return loss_dict
        else:
            assert len(data) == 1
            device = self.device
            outputs = []
            for i, data_ in enumerate(data[0]['data_loader']):   # tqdm(loader)):
                cur_img, ori_img, proposals, f_path = [d[0] for d in data_]
                cur_img = cur_img.to(device)
                if track_instances is not None:
                    track_instances.remove('boxes')
                    # track_instances.remove('labels')
                seq_h, seq_w, _ = ori_img.shape

                # 内部包含backboe+encode+decode+跟踪匹配关系+跟踪目标过滤（从query中过滤）
                try:
                    res = self.inference_single_image(cur_img, (seq_h, seq_w), track_instances)
                except:
                    res = self.inference_single_image(cur_img, (seq_h, seq_w), track_instances)
                track_instances = res['track_instances']

                predictions = deepcopy(res)
                if len(predictions['track_instances']):
                    scores = predictions['track_instances'].scores.reshape(-1, self.g_size)
                    keep_idxs = torch.arange(len(predictions['track_instances']), device=scores.device).reshape(-1, self.g_size)
                    keep_idxs = keep_idxs.gather(1, scores.max(-1)[1].reshape(-1, 1)).reshape(-1)
                    predictions['track_instances'] = predictions['track_instances'][keep_idxs]

                predictions = _filter_predictions_with_confidence(predictions, 0.5)
                predictions = _filter_predictions_with_area(predictions)
                outputs.append(predictions['track_instances'].to('cpu'))

            return [outputs]

    @torch.jit.unused
    def _set_aux_loss(self, outputs_class, outputs_coord):
        # this is a workaround to make torchscript happy, as torchscript
        # doesn't support dictionary with non-homogeneous values, such
        # as a dict having both a Tensor and a list.
        return [
            {"pred_logits": a, "pred_boxes": b}
            for a, b in zip(outputs_class[:-1], outputs_coord[:-1])
        ]

def _filter_predictions_with_area(predictions, area_threshold=100):
    if "track_instances" in predictions:
        preds = predictions["track_instances"]
        wh = preds.boxes[:, 2:4] - preds.boxes[:, 0:2]
        areas = wh[:, 0] * wh[:, 1]
        keep_idxs = areas > area_threshold
        predictions = copy(predictions) # don't modify the original
        predictions["track_instances"] = preds[keep_idxs]
    return predictions

def _filter_predictions_with_confidence(predictions, confidence_threshold=0.5):
    if "track_instances" in predictions:
        preds = predictions["track_instances"]
        keep_idxs = preds.scores > confidence_threshold
        predictions = copy(predictions) # don't modify the original
        predictions["track_instances"] = preds[keep_idxs]
    return predictions

class ClipMatcher(SetCriterion):
    """This class computes the loss for Conditional DETR.
    The process happens in two steps:
        1) we compute hungarian assignment between ground truth boxes and the outputs of the model
        2) we supervise each pair of matched ground-truth / prediction (supervise class and box)
    """

    def __init__(
        self,
        num_classes,
        matcher,
        weight_dict,
        losses: List[str] = ["class", "boxes"],
        eos_coef: float = 0.1,
        loss_class_type: str = "focal_loss",
        alpha: float = 0.25,
        gamma: float = 2.0,
        g_size=1
    ):
        """Create the criterion.
        Parameters:
            num_classes: number of object categories, omitting the special no-object category
            matcher: module able to compute a matching between targets and proposals
            weight_dict: dict containing as key the names of the losses and as values their relative weight.
            eos_coef: relative classification weight applied to the no-object category
            losses: list of all the losses to be applied. See get_loss for list of available losses.
            focal_alpha: alpha in Focal Loss
        """
        super().__init__(num_classes, matcher, weight_dict, losses, eos_coef, loss_class_type, alpha, gamma)
        self.num_classes = num_classes
        self.matcher = matcher
        self.weight_dict = weight_dict
        self.losses = losses
        self.focal_loss = True
        self.losses_dict = defaultdict(float)
        self._current_frame_idx = 0
        self.g_size = g_size

    def initialize_for_single_clip(self, gt_instances: List[Instances]):  # 训练过程中每个视频段之前调用，传入GT值
        self.gt_instances = gt_instances
        self.num_samples = 0
        self.sample_device = None
        self._current_frame_idx = 0
        self.losses_dict = defaultdict(float)

    def _step(self):
        self._current_frame_idx += 1

    def calc_loss_for_track_scores(self, track_instances: Instances):
        gt_instances_i = self.gt_instances[self._current_frame_idx]
        outputs = {
            'pred_logits': track_instances.track_scores[None],
        }
        device = track_instances.track_scores.device

        num_tracks = len(track_instances)
        src_idx = torch.arange(num_tracks, dtype=torch.long, device=device)
        tgt_idx = track_instances.matched_gt_idxes  # -1 for FP tracks and disappeared tracks

        track_losses = self.get_loss('labels',
                                     outputs=outputs,
                                     gt_instances=[gt_instances_i],
                                     indices=[(src_idx, tgt_idx)],
                                     num_boxes=1)
        self.losses_dict.update(
            {'frame_{}_track_{}'.format(self._current_frame_idx, key): value for key, value in
             track_losses.items()})

    def get_num_boxes(self, num_samples):
        num_boxes = torch.as_tensor(num_samples, dtype=torch.float, device=self.sample_device)
        if is_dist_avail_and_initialized():
            torch.distributed.all_reduce(num_boxes)
        num_boxes = torch.clamp(num_boxes / get_world_size(), min=1).item()
        return num_boxes

    def loss_labels(self, outputs, gt_instances: List[Instances], indices, num_boxes, log=False):
        """Classification loss (Binary focal loss)
        targets dicts must contain the key "labels" containing a tensor of dim [nb_target_boxes]
        """
        assert "pred_logits" in outputs
        src_logits = outputs["pred_logits"]

        idx = self._get_src_permutation_idx(indices)
        target_classes = torch.full(
            src_logits.shape[:2],
            self.num_classes,
            dtype=torch.int64,
            device=src_logits.device,
        )
        # The matched gt for disappear track query is set -1.
        labels = []
        for gt_per_img, (_, J) in zip(gt_instances, indices):
            labels_per_img = torch.ones_like(J) * self.num_classes
            # set labels of track-appear slots to 0.
            if len(gt_per_img) > 0:
                labels_per_img[J != -1] = gt_per_img.labels[J[J != -1]]
            labels.append(labels_per_img)
        target_classes_o = torch.cat(labels)
        target_classes[idx] = target_classes_o

        # Computation classification loss
        if self.loss_class_type == "ce_loss":
            # loss_class = F.cross_entropy(
            #     src_logits.transpose(1, 2), target_classes, self.empty_weight
            # )
            loss_class = F.cross_entropy(
                src_logits.transpose(1, 2), target_classes, self.empty_weight
            )
        elif self.loss_class_type == "focal_loss":
            # src_logits: (b, num_queries, num_classes) = (2, 300, 80)
            # target_classes_one_hot = (2, 300, 80)
            gt_labels_target = F.one_hot(target_classes, num_classes=self.num_classes + 1)[:, :, :-1]  # no loss for the last (background) class
            gt_labels_target = gt_labels_target.to(src_logits)
            loss_class = sigmoid_focal_loss(
                    src_logits.flatten(1),
                    gt_labels_target.flatten(1),
                    num_boxes=num_boxes,
                    alpha=self.alpha,
                    gamma=self.gamma
                )

        losses = {"loss_ce": loss_class}

        if log:
            # TODO this should probably be a separate loss, not hacked in this one here
            losses['class_error'] = 100 - accuracy(src_logits[idx], target_classes_o)[0]

        return losses

    def loss_boxes(self, outputs, gt_instances: List[Instances], indices: List[tuple], num_boxes):
        """Compute the losses related to the bounding boxes, the L1 regression loss and the GIoU loss
        targets dicts must contain the key "boxes" containing a tensor of dim [nb_target_boxes, 4]
        The target boxes are expected in format (center_x, center_y, w, h), normalized by the image size.
        """
        assert "pred_boxes" in outputs
        # We ignore the regression loss of the track-disappear slots.
        #TODO: Make this filter process more elegant.
        filtered_idx = []
        for src_per_img, tgt_per_img in indices:
            keep = tgt_per_img != -1
            filtered_idx.append((src_per_img[keep], tgt_per_img[keep]))
        indices = filtered_idx
        idx = self._get_src_permutation_idx(indices)
        src_boxes = outputs["pred_boxes"][idx]
        target_boxes = torch.cat([gt_per_img.boxes[i] for gt_per_img, (_, i) in zip(gt_instances, indices)], dim=0)

        # for pad target, don't calculate regression loss, judged by whether obj_id=-1
        target_obj_ids = torch.cat([gt_per_img.obj_ids[i] for gt_per_img, (_, i) in zip(gt_instances, indices)], dim=0) # size(16)
        mask = (target_obj_ids != -1)

        loss_bbox = F.l1_loss(src_boxes[mask], target_boxes[mask], reduction="none")

        losses = {}
        losses["loss_bbox"] = loss_bbox.sum() / num_boxes
        loss_giou = 1 - torch.diag(
            generalized_box_iou(
                box_cxcywh_to_xyxy(src_boxes[mask]),
                box_cxcywh_to_xyxy(target_boxes[mask])
            )
        )
        losses["loss_giou"] = loss_giou.sum() / num_boxes

        return losses

    def get_loss(self, loss, outputs, targets, indices, num_boxes, **kwargs):
        loss_map = {
            'labels': self.loss_labels,
            "boxes": self.loss_boxes,
        }
        assert loss in loss_map, f"do you really want to compute {loss} loss?"
        return loss_map[loss](outputs, targets, indices, num_boxes, **kwargs)

    def match_for_single_frame(self, outputs: dict):
        """This performs the loss computation.
        Parameters:
             outputs: dict of tensors, see the output specification of the model for the format
             targets: list of dicts, such that len(targets) == batch_size.
                      The expected keys in each dict depends on the losses applied, see each loss' doc

             return_indices: used for vis. if True, the layer0-5 indices will be returned as well.

        """
        outputs_without_aux = {k: v for k, v in outputs.items() if k != "aux_outputs"}

        gt_instances_i = self.gt_instances[self._current_frame_idx]  # gt instances of i-th image.
        track_instances: Instances = outputs_without_aux['track_instances']
        pred_logits_i = track_instances.pred_logits  # predicted logits of i-th image.
        pred_boxes_i = track_instances.pred_boxes  # predicted boxes of i-th image.

        if not (track_instances.obj_idxes !=-1).any():  # 没有跟踪
            outputs_i = {
                'pred_logits': pred_logits_i.transpose(0,1),
                'pred_boxes': pred_boxes_i.transpose(0,1),
            }

            # Retrieve the matching between the outputs of the last layer and the targets
            indices = self.matcher(outputs_i, gt_instances_i, g_size=self.g_size)
            indices = [(ind[0].to(pred_logits_i.device), ind[1].to(pred_logits_i.device)) for ind in indices]

            track_instances.matched_gt_idxes[...] = -1
            for i, ind in enumerate(indices):
                track_instances.matched_gt_idxes[ind[0], i] = ind[1]
                track_instances.obj_idxes[ind[0], i] = gt_instances_i[i].obj_ids[ind[1]].long()

                active_idxes = (track_instances.obj_idxes[:, i] >= 0) & (track_instances.matched_gt_idxes[:, i] >= 0) # 当前帧能够匹配到的目标
                active_track_boxes = track_instances.pred_boxes[active_idxes, i]
                if len(active_track_boxes) > 0:
                    gt_boxes = gt_instances_i[i].boxes[track_instances.matched_gt_idxes[active_idxes, i]]
                    active_track_boxes = box_cxcywh_to_xyxy(active_track_boxes)
                    gt_boxes = box_cxcywh_to_xyxy(gt_boxes)
                    track_instances.iou[active_idxes, i] = matched_pairwise_iou(Boxes(active_track_boxes), Boxes(gt_boxes))

            self.num_samples += sum(len(t.boxes) for t in gt_instances_i)*self.g_size
            self.sample_device = pred_logits_i.device
            for loss in self.losses:
                new_track_loss = self.get_loss(loss,
                                            outputs=outputs_i,
                                            gt_instances=gt_instances_i,
                                            indices=indices,
                                            num_boxes=1)
                self.losses_dict.update(
                    {'frame_{}_{}'.format(self._current_frame_idx, key): value for key, value in new_track_loss.items()})

            if 'aux_outputs' in outputs: # 此处匹配时对新生儿时重新算对应关系的，不直接使用最后一层box输出的对应关系
                for i, aux_outputs in enumerate(outputs['aux_outputs']):
                    unmatched_outputs_layer = {
                        'pred_logits': aux_outputs['pred_logits'],
                        'pred_boxes': aux_outputs['pred_boxes'],
                    }

                    matched_indices_layer = self.matcher(unmatched_outputs_layer, gt_instances_i, g_size=self.g_size)
                    matched_indices_layer = [(ind[0].to(pred_logits_i.device), ind[1].to(pred_logits_i.device)) for ind in matched_indices_layer]

                    for loss in self.losses:
                        if loss == 'masks':
                            # Intermediate masks losses are too costly to compute, we ignore them.
                            continue
                        l_dict = self.get_loss(loss,
                                            aux_outputs,
                                            gt_instances=gt_instances_i,
                                            indices=matched_indices_layer,
                                            num_boxes=1, )
                        self.losses_dict.update(
                            {'frame_{}_aux{}_{}'.format(self._current_frame_idx, i, key): value for key, value in
                            l_dict.items()})
        else:
            track_instances.matched_gt_idxes[...] = -1
            def match_for_single_decoder_layer(unmatched_outputs, matcher, untracked_gt_instances, unmatched_track_idxes, untracked_tgt_indexes):
                new_track_indices = matcher(unmatched_outputs,
                                                [untracked_gt_instances], g_size=self.g_size)  # list[tuple(src_idx, tgt_idx)]

                src_idx = new_track_indices[0][0]
                tgt_idx = new_track_indices[0][1]
                # concat src and tgt.
                new_matched_indices = torch.stack([unmatched_track_idxes[src_idx], untracked_tgt_indexes[tgt_idx]],
                                                dim=1).to(pred_logits_i.device)
                return new_matched_indices
            for ibn, gt_ins in enumerate(gt_instances_i):
                # step1. inherit and update the previous tracks.
                obj_idxes = gt_ins.obj_ids
                i, j = torch.where(track_instances.obj_idxes[:, ibn:ibn+1] == obj_idxes)  # 获取跟踪query与之相同ID的对应索引
                track_instances.matched_gt_idxes[i, ibn] = j

                full_track_idxes = torch.arange(len(track_instances), dtype=torch.long, device=pred_logits_i.device)
                matched_track_idxes = (track_instances.obj_idxes[:, ibn] >= 0)  # occu >=0表明该query为跟踪query
                prev_matched_indices = torch.stack(
                    [full_track_idxes[matched_track_idxes], track_instances.matched_gt_idxes[matched_track_idxes, ibn]], dim=1)  # 检测或跟踪与gt的对应关系

                # step2. select the unmatched slots.
                # note that the FP tracks whose obj_idxes are -2 will not be selected here.
                unmatched_track_idxes = full_track_idxes[track_instances.obj_idxes[:, ibn] == -1]  # 获取检测query

                # step3. select the untracked gt instances (new tracks).
                tgt_indexes = track_instances.matched_gt_idxes[:, ibn]
                tgt_indexes = tgt_indexes[tgt_indexes != -1]  # 获取跟踪query匹配GT，非新生儿（除了这些之外便是新生儿）

                tgt_state = torch.zeros(len(gt_ins), device=pred_logits_i.device)
                tgt_state[tgt_indexes] = 1 # 新生儿为0，跟踪对应的GT为1
                full_tgt_idxes = torch.arange(len(gt_ins), device=pred_logits_i.device)
                untracked_tgt_indexes = full_tgt_idxes[tgt_state == 0]
                untracked_gt_instances = gt_ins[untracked_tgt_indexes]  # 新生儿的索引

                # step4. do matching between the unmatched slots and GTs.该过程就是DET匈牙利匹配过程
                unmatched_outputs = {
                    'pred_logits': track_instances.pred_logits[unmatched_track_idxes, ibn].unsqueeze(0),
                    'pred_boxes': track_instances.pred_boxes[unmatched_track_idxes, ibn].unsqueeze(0),
                }
                # new_matched_indices = match_for_single_decoder_layer(unmatched_outputs, self.matcher, untracked_gt_instances, unmatched_track_idxes, untracked_tgt_indexes)
                new_matched_indices = match_for_single_decoder_layer(unmatched_outputs, self.matcher, untracked_gt_instances, unmatched_track_idxes, untracked_tgt_indexes)

                # step5. update obj_idxes according to the new matching result. 分配GT的ID给track和GT所在的索引
                track_instances.obj_idxes[new_matched_indices[:, 0], ibn] = gt_ins.obj_ids[new_matched_indices[:, 1]].long()
                track_instances.matched_gt_idxes[new_matched_indices[:, 0], ibn] = new_matched_indices[:, 1]

                # step6. calculate iou.
                active_idxes = (track_instances.obj_idxes[:, ibn] >= 0) & (track_instances.matched_gt_idxes[:, ibn] >= 0) # 当前帧能够匹配到的目标
                active_track_boxes = track_instances.pred_boxes[active_idxes, ibn]
                if len(active_track_boxes) > 0:
                    gt_boxes = gt_ins.boxes[track_instances.matched_gt_idxes[active_idxes, ibn]]
                    active_track_boxes = box_cxcywh_to_xyxy(active_track_boxes)
                    gt_boxes = box_cxcywh_to_xyxy(gt_boxes)
                    track_instances.iou[active_idxes, ibn] = matched_pairwise_iou(Boxes(active_track_boxes), Boxes(gt_boxes))

                # step7. merge the unmatched pairs and the matched pairs.
                matched_indices = torch.cat([new_matched_indices, prev_matched_indices], dim=0)

                # step8. calculate losses.
                self.num_samples += len(gt_ins)*self.g_size
                self.sample_device = pred_logits_i.device
                outputs_i = {
                    'pred_logits': pred_logits_i[:, ibn].unsqueeze(0),
                    'pred_boxes': pred_boxes_i[:, ibn].unsqueeze(0),
                }
                for loss in self.losses:
                    new_track_loss = self.get_loss(loss,
                                                    outputs=outputs_i,
                                                    gt_instances=[gt_ins],
                                                    indices=[(matched_indices[:, 0], matched_indices[:, 1])],
                                                    num_boxes=1)
                    for key, value in new_track_loss.items():
                        self.losses_dict['frame_{}_{}'.format(self._current_frame_idx, key)] += value

                if 'aux_outputs' in outputs: # 此处匹配时对新生儿时重新算对应关系的，不直接使用最后一层box输出的对应关系
                    for i, aux_outputs in enumerate(outputs['aux_outputs']):
                        unmatched_outputs_layer = {
                            'pred_logits': aux_outputs['pred_logits'][ibn, unmatched_track_idxes].unsqueeze(0),
                            'pred_boxes': aux_outputs['pred_boxes'][ibn, unmatched_track_idxes].unsqueeze(0),
                        }
                        new_matched_indices_layer = match_for_single_decoder_layer(unmatched_outputs_layer, self.matcher, gt_ins[full_tgt_idxes], unmatched_track_idxes, full_tgt_idxes)
                        matched_indices_layer = torch.cat([new_matched_indices_layer, prev_matched_indices], dim=0)
                        outputs_layer = {
                            'pred_logits': aux_outputs['pred_logits'][ibn].unsqueeze(0),
                            'pred_boxes': aux_outputs['pred_boxes'][ibn].unsqueeze(0),
                        }
                        for loss in self.losses:
                            if loss == 'masks':
                                # Intermediate masks losses are too costly to compute, we ignore them.
                                continue
                            l_dict = self.get_loss(loss,
                                                outputs_layer,
                                                gt_instances=[gt_ins],
                                                indices=[(matched_indices_layer[:, 0], matched_indices_layer[:, 1])],
                                                num_boxes=1, )
                            for key, value in l_dict.items():
                                self.losses_dict['frame_{}_aux{}_{}'.format(self._current_frame_idx, i, key)] += value

        self._step()
        return track_instances

    def forward(self, outputs, input_data: dict):
        # losses of each frame are calculated during the model's forwarding and are outputted by the model as outputs['losses_dict].
        losses = outputs.pop("losses_dict")
        num_samples = self.get_num_boxes(self.num_samples)
        for loss_name, loss in losses.items():
            losses[loss_name] /= num_samples
        return losses

class RuntimeTrackerBase(object):  # 实际为一个跟踪ID分配器
    def __init__(self, score_thresh=0.6, filter_score_thresh=0.5, miss_tolerance=10):
        self.score_thresh = score_thresh
        self.filter_score_thresh = filter_score_thresh
        self.miss_tolerance = miss_tolerance
        self.max_obj_id = 0

    def clear(self):
        self.max_obj_id = 0

    def update(self, track_instances: Instances, g_size=1):
        assert track_instances.obj_idxes.shape[1] == 1

        device = track_instances.obj_idxes.device

        num_queries = len(track_instances)
        Cindx = torch.arange(num_queries, device=device).reshape(num_queries//g_size, g_size)
        active_idxes = torch.full((num_queries,), False, dtype=torch.bool, device=device)
        # active_idxes[Cindx[track_instances.scores.reshape(-1, g_size).max(-1)[0] >= self.score_thresh].view(-1)] = True
        active_idxes[Cindx[track_instances.scores.reshape(-1, g_size).min(-1)[0] >= self.score_thresh].view(-1)] = True
        # active_idxes[Cindx[track_instances.scores.reshape(-1, g_size).mean(-1) >= self.score_thresh].view(-1)] = True
        track_instances.disappear_time[active_idxes] = 0 # 假如当前帧检测到目标，则disappear_time=0

        active_debug = track_instances.scores.reshape(-1, g_size) >= self.score_thresh
        if not (active_debug == active_debug[:,0:1]).any():
            print(track_instances.scores)

        new_obj = (track_instances.obj_idxes.reshape(-1) == -1) & (active_idxes) # 挑选新生儿，obj_idxes=-1表示为检测query
        disappeared_obj = (track_instances.obj_idxes.reshape(-1) >= 0) & (~active_idxes)  # 跟踪中假如置信度偏低则 disappear_time++
        num_new_objs = new_obj.sum().item() // g_size

        track_instances.obj_idxes[new_obj, 0] = (self.max_obj_id + torch.arange(num_new_objs, device=device)).view(-1, 1).repeat(1, g_size).view(-1)  # 分配ID
        self.max_obj_id += num_new_objs  # max_obj_id为已有多人ID

        track_instances.disappear_time[disappeared_obj] += 1
        to_del = disappeared_obj & (track_instances.disappear_time[:, 0] >= self.miss_tolerance) # 假如当前帧检测不到，且消失很长时间，则把ID删掉，
        track_instances.obj_idxes[to_del, 0] = -1

class TrackerPostProcess(nn.Module):
    """ This module converts the model's output into the format expected by the coco api"""
    def __init__(self, g_size=1):
        super().__init__()

        self.g_size = g_size

    @torch.no_grad()
    def forward(self, track_instances: Instances, target_size) -> Instances:
        """ Perform the computation
        Parameters:
            outputs: raw outputs of the model
            target_sizes: tensor of dimension [batch_size x 2] containing the size of each images of the batch
                          For evaluation, this must be the original image size (before any data augmentation)
                          For visualization, this should be the image size after data augment, but before padding
        """
        out_logits = track_instances.pred_logits
        out_bbox = track_instances.pred_boxes

        # scores = out_logits[..., 0].sigmoid()
        prob = out_logits.sigmoid()
        if len(prob):
            num_query, bn, cls_num = prob.shape
            scores, labels = prob.reshape(-1, self.g_size, bn, cls_num).max(1)[0].reshape(-1, 1, bn, cls_num).repeat(1, self.g_size, 1, 1).reshape(-1, bn, cls_num).max(-1)
        else:
            scores = out_logits[..., 0].sigmoid()
            labels = torch.full_like(scores, 0, dtype=torch.long)

        # convert to [x0, y0, x1, y1] format
        boxes = box_cxcywh_to_xyxy(out_bbox)
        # and from relative [0, 1] to absolute [0, height] coordinates
        img_h, img_w = target_size
        scale_fct = torch.Tensor([img_w, img_h, img_w, img_h]).to(boxes)
        boxes = boxes * scale_fct[None, :]

        track_instances.boxes = boxes
        track_instances.scores = scores
        mask = (track_instances.labels[:, 0] == -1) | (track_instances.labels[:, 0] == labels[:, 0])
        track_instances.labels = labels # torch.full_like(scores, 0)
        # track_instances.remove('pred_logits')
        # track_instances.remove('pred_boxes')
        # if len(track_instances) != len(track_instances[mask]):
        #     print(track_instances)
        track_instances = track_instances[mask]

        return track_instances

def img():
    image = np.ascontiguousarray(((img.tensors[0].permute(1,2,0).cpu()*torch.tensor([0.229, 0.224, 0.225])+torch.tensor([0.485, 0.456, 0.406]))*255).numpy().astype(np.uint8))
    img_h, img_w, _ = image.shape
    bboxes = track_instances.ref_pts.cpu().numpy().reshape(-1, 2, 2)
    bboxes[..., 0] *= img_w
    bboxes[..., 1] *= img_h
    bboxes[:, 0] -= bboxes[:, 1]/2
    bboxes[:, 1] += bboxes[:, 0]
    import cv2
    for i in range(68):
        image_copy = image.copy()
        for box in bboxes[5*i:5*(i+1)]:
            cv2.rectangle(image_copy, pt1 = (int(box[0, 0]), int(box[0, 1])), pt2 =(int(box[1, 0]), int(box[1, 1])), color = (0, 0, 255), thickness = 2)
        cv2.imwrite('tmp2/%d.jpg'%i, image_copy)


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

"""a few exemplary plots for exploring prediction
accuracies
ZZJ, 11.18.2019
"""

from read_data import read_label_annot
import matplotlib.pyplot as plt

def plot_auc(evals):
    annot = read_label_annot()
    annot['auroc'] = evals['auroc']
    annot['aupr'] = evals['aupr']
    cats = ['TF', 'Pol', 'Histone', 'DNase']
    for i in range(len(cats)):
        plt.clf()
        cat = cats[i]
        print(cat, i, j)
        ax = annot.loc[annot.category==cat].auroc.hist(cumulative=True)
        ax.set_xlim(0.5, 1)
        ax.set_xlabel("AUROC")
        ax.set_ylabel("CDF")
        avg = annot.loc[annot.category==cat].auroc.mean()
        ax.set_title("%s, avg auc=%.3f"%(cat, avg))
        plt.savefig("%s.png"%cat)


from __future__ import division, print_function
# coding=utf-8
import sys
import os
import glob
import re
import numpy as np

# Keras
from keras.models import Sequential
import keras
from keras.applications.imagenet_utils import preprocess_input, decode_predictions
from keras.models import load_model
from keras.preprocessing import image
from keras.layers import Dense, Dropout, Activation
from keras.preprocessing.image import ImageDataGenerator
import numpy as np
from keras.preprocessing import image

# Flask utils
from flask import Flask, redirect, url_for, request, render_template
from werkzeug.utils import secure_filename
from gevent.pywsgi import WSGIServer

# Define a flask app
app = Flask(__name__)

# Model saved with Keras model.save()
MODEL_PATH = 'models/cola_crisp_a.h5'

v_model=keras.applications.vgg16.VGG16()
model=Sequential()
for layer in v_model.layers[:-1]:
    model.add(layer)
for layer in model.layers:
    layer.trainable=False

model.add(Dropout(0.4))
model.add(Dense(2,activation="softmax"))

model.load_weights('models/weights.h5')

# Load your trained model
#model = Sequential()

#model = load_model(MODEL_PATH)
model._make_predict_function()          # Necessary
print('Model loaded. Start serving...')

# You can also use pretrained model from Keras
# Check https://keras.io/applications/
#from keras.applications.resnet50 import ResNet50
#model = ResNet50(weights='imagenet')
print('Model loaded. Check http://127.0.0.1:5000/')

def model_predict(img_path, model):
    img = image.load_img(img_path, target_size=(224, 224))

    # Preprocessing the image
    x = image.img_to_array(img)
    # x = np.true_divide(x, 255)
    x = np.expand_dims(x, axis=0)

    # Be careful how your trained model deals with the input
    # otherwise, it won't make correct prediction!
    x = preprocess_input(x, mode='caffe')

    preds = model.predict(x)
    return preds

@app.route('/', methods=['GET'])
def index():
    # Main pagex
    return render_template('index.html')

@app.route('/predict', methods=['GET', 'POST'])
def upload():
    if request.method == 'POST':
        # Get the file from post request
        f = request.files['image']

        # Save the file to ./uploads
        basepath = os.path.dirname(__file__)
        #bpath = os.path.join(basepath,'uploads')
        file_path = os.path.join(
            basepath, 'uploads', secure_filename(f.filename))
        bpath = os.path.join(basepath,'uploads')

        print(file_path+"\n\n\n")
        f.save(file_path)

        img = image.load_img(file_path, target_size = (224,224))
        img = image.img_to_array(img)
        img = np.expand_dims(img, axis = 0)
        lk=model.predict_classes(img)
        if lk==0:
        	result="Coco-Cola"
        else:
        	result="Crisp"
        return result
    return None

if __name__ == '__main__':
    app.run(port=5000, debug=True)

    # Serve the app with gevent
    http_server = WSGIServer(('0.0.0.0', 5000), app)
    http_server.serve_forever()


from flask import Blueprint

import form.struct
from service.dblist import DBList

forms = Blueprint("forms", __name__, url_prefix="/form")

dbs = Blueprint('dblist', __name__, url_prefix="/db")

@forms.route('/')
def index():
    print(form.struct)
    return form.struct.FormStruct().incorporate_field([{
        "text": "fdgs",
        "lang_type": "golang",
        "type": "int"
    }])

@dbs.route('/')
def dbindex():
    try:
        table_list = DBList().get_table_struct_for_mysql()
        return {
            "code": 100,
            "data": table_list
        }
    except Exception as e:
        print(e)
        return {
            "code": 99,
            "data": "error"
        }


import pygame
from constants import LENGTHWIN, WIDTHWIN, WHITE
from grid import Grid

pygame.init()

WIN = pygame.display.set_mode((LENGTHWIN, WIDTHWIN))
clock = pygame.time.Clock()
grid = Grid(WIN)
pygame.display.set_caption("Jeu de Sudoku")

def draw():
    WIN.fill(WHITE)
    grid.draw()
    pygame.display.update()

def main():
    while True:
        clock.tick(60)
        draw()

        for event in pygame.event.get():
            if event.type == pygame.QUIT or (
                event.type == pygame.KEYDOWN and event.key == pygame.K_q
            ):
                pygame.quit()
                return 0

            if event.type == pygame.MOUSEBUTTONDOWN:
                pos = pygame.mouse.get_pos()
                grid.caseClique(pos)

            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_s:
                    grid.solve()
                if event.key == pygame.K_c:
                    grid.clear()
                if event.key == pygame.K_f:
                    grid.solve_wait()

main()


# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import hashlib
import json
import os

import mozpack.path as mozpath
from mozpack.files import FileFinder

GENERATED_SOURCE_EXTS = (".rs", ".c", ".h", ".cc", ".cpp")

def sha512_digest(data):
    """
    Generate the SHA-512 digest of `data` and return it as a hex string.
    """
    return hashlib.sha512(data).hexdigest()

def get_filename_with_digest(name, contents):
    """
    Return the filename that will be used to store the generated file
    in the S3 bucket, consisting of the SHA-512 digest of `contents`
    joined with the relative path `name`.
    """
    digest = sha512_digest(contents)
    return mozpath.join(digest, name)

def get_generated_sources():
    """
    Yield tuples of `(objdir-rel-path, file)` for generated source files
    in this objdir, where `file` is either an absolute path to the file or
    a `mozpack.File` instance.
    """
    import buildconfig

    # First, get the list of generated sources produced by the build backend.
    gen_sources = os.path.join(buildconfig.topobjdir, "generated-sources.json")
    with open(gen_sources, "r") as f:
        data = json.load(f)
    for f in data["sources"]:
        # Exclute symverscript
        if mozpath.basename(f) != "symverscript":
            yield f, mozpath.join(buildconfig.topobjdir, f)
    # Next, return all the files in $objdir/ipc/ipdl/_ipdlheaders.
    base = "ipc/ipdl/_ipdlheaders"
    finder = FileFinder(mozpath.join(buildconfig.topobjdir, base))
    for p, f in finder.find("**/*.h"):
        yield mozpath.join(base, p), f
    # Next, return any source files that were generated into the Rust
    # object directory.
    rust_build_kind = "debug" if buildconfig.substs.get("MOZ_DEBUG_RUST") else "release"
    base = mozpath.join(buildconfig.substs["RUST_TARGET"], rust_build_kind, "build")
    finder = FileFinder(mozpath.join(buildconfig.topobjdir, base))
    for p, f in finder:
        if p.endswith(GENERATED_SOURCE_EXTS):
            yield mozpath.join(base, p), f

def get_s3_region_and_bucket():
    """
    Return a tuple of (region, bucket) giving the AWS region and S3
    bucket to which generated sources should be uploaded.
    """
    region = "us-west-2"
    level = os.environ.get("MOZ_SCM_LEVEL", "1")
    bucket = {
        "1": "gecko-generated-sources-l1",
        "2": "gecko-generated-sources-l2",
        "3": "gecko-generated-sources",
    }[level]
    return (region, bucket)


from flask import Flask, jsonify, request
from flask_restful import Resource
from Helpers.DatabaseHelper import DatabaseHelper
import csv
import time
from datetime import datetime
import traceback as tb
from Constants.Status import *
import json
from Helpers.utils import autheticateTenant
from Helpers.authenticate import *
from Controllers.DefChangeController import DefChangeController

class MaintainBusinessRulesController(Resource):
	def __init__(self):
		self.domain_info = autheticateTenant()
		if self.domain_info:
			tenant_info = json.loads(self.domain_info)
			self.tenant_info = json.loads(tenant_info['tenant_conn_details'])
			self.dcc=DefChangeController()
			self.db=DatabaseHelper(self.tenant_info)

	@authenticate
	def get(self, id=None, source_id=None, page=0, col_name=None,business_rule=None):
		print(request.endpoint)
		if request.endpoint == "business_rule_export_to_csv_ep":
			source = request.args.get('source_id')
			return self.export_to_csv(source_id=source)
		if request.endpoint == "business_rule_linkage_ep":
			return self.list_reports_for_rule(business_rule=business_rule)
		if request.endpoint == "business_rule_linkage_multiple_ep":
			source = request.args.get('source_id')
			rules = request.args.get('rules')
			#print(source,rules)
			return self.list_reports_for_rule_list(source_id=source,business_rule_list=rules)
		if request.endpoint == "business_rule_drill_down_rules_ep":
			print('Inside rules drilldown ep')
			source = request.args.get('source_id')
			rules = request.args.get('rules')
			page = request.args.get('page')
			business_date = request.args.get('business_date')
			qualified_data_version = request.args.get('qualified_data_version')
			return self.get_business_rules_list_by_source(rules=rules,source=source,page=page
														,business_date=business_date
														,qualified_data_version=qualified_data_version)
		if request.endpoint == 'get_br_source_suggestion_list_ep':
			source_id = request.args.get('source_id')
			return self.get_source_suggestion_list(source_id=source_id)
		if request.endpoint == 'get_br_source_column_suggestion_list_ep':
			table_name = request.args.get('table_name')
			return self.get_source_column_suggestion_list(table_name=table_name)
		if id:
			return self.render_business_rule_json(id)
		elif page and source_id==None and col_name == None:
			return self.render_business_rules_json(page)
		elif source_id and page and col_name == None:
			return self.render_business_rules_json(page,source_id)
		elif col_name:
			direction = request.args.get('direction')
			return self.render_business_rules_json(page, "source_id",(col_name,direction))
	def post(self,page=None):

		if request.endpoint == "business_rules_ep_filtered":
			filter_conditions = request.get_json(force=True)
			return self.get_business_rules_filtered(filter_conditions)

		if request.endpoint == "validate_python_expr_ep":
			expr_obj=request.get_json(force=True)
			return self.validate_python_expression(expr_obj)

		br = request.get_json(force=True)
		return self.dcc.insert_data(br)

	def put(self, id=None):
		if id == None:
			return BUSINESS_RULE_EMPTY
		data = request.get_json(force=True)
		return self.dcc.update_or_delete_data(data, id)

	# def delete(self, id=None):
	# 	if id == None:
	# 		return BUSINESS_RULE_EMPTY
	# 	res = self.delete_business_rules(id)
	# 	return res
	def render_business_rules_json(self, page=0, source_id="source_id", order=None):
		startPage =  int(page)*100
		business_rules_dict = {}
		business_rules_list = []
		if order:
			cur = self.db.query('select * from business_rules where source_id=' + str(source_id) + ' order by ' + order[0] + ' ' + order[1] + ' limit ' + str(startPage) + ', 100')
		else:
			# cur = self.db.query('select * from business_rules where source_id=' + str(source_id) + ' limit ' + str(startPage) + ', 100')
			cur = self.db.query('select * from business_rules where source_id=' + str(source_id))
		business_rules = cur.fetchall()
		cols = [i[0] for i in cur.description]
		business_rules_dict['cols'] = cols

		for br in business_rules:
			business_rules_list.append(br)
		business_rules_dict['rows'] = business_rules_list
		count = self.db.query('select count(*) as count from business_rules where source_id=' + str(source_id)).fetchone()
		business_rules_dict['count'] = count['count']
		json_dump = business_rules_dict
		return json_dump
	def get_business_rules_filtered(self,filter_conditions):
		filter_string = ""
		for filter_condition in filter_conditions:
			filter_string = filter_string + " AND " + filter_condition['field_name'] + "='" + filter_condition['value'] + "'"
		query = 'select * from business_rules where 1' + filter_string
		cur = self.db.query(query)
		data = cur.fetchall()
		if data:
			return data
		return NO_BUSINESS_RULE_FOUND
	def get_business_rules_list_by_source(self,rules,source,page,business_date,qualified_data_version):

		if page is None:
			page = 0
		startPage = int(page) * 100
		data_dict = {}
		where_clause = ''
		sql = 'select br.* from business_rules br {} where 1 '
		if source is not None:
			where_clause += ' and br.source_id ={}'.format(source,)
		if rules is not None:
			where_clause += ' and br.business_rule in (\''+rules.replace(',','\',\'')+'\')'
		if business_date and business_date !='null' and business_date !='undefined':
			brvsql = "select br_version from qualified_data_vers " + \
			      " where business_date={0} and source_id={1} and version={2}".format(business_date,source,qualified_data_version)
			brv = self.db.query(brvsql).fetchone()

			sql = sql.format(", business_rules_vers v")
			where_clause += " and v.source_id=br.source_id and v.version={}".format(brv['br_version'],) + \
							" and instr(concat(',',v.id_list,','),concat(',',br.id,','))"
		else:
			sql = sql.format("")

		cur = self.db.query(sql + where_clause + " limit " + str(startPage) + ", 100")
		data = cur.fetchall()
		cols = [i[0] for i in cur.description]
		count = self.db.query(sql.replace('br.*','count(*) as count ') + where_clause).fetchone()
		data_dict['cols'] = cols
		data_dict['rows'] = data
		data_dict['count'] = count['count']
		data_dict['table_name'] = 'business_rules'
		data_dict['sql'] = sql

		return data_dict

	def render_business_rule_json(self, id):
		query = 'select * from business_rules where id = %s'
		cur = self.db.query(query, (id, ))
		data = cur.fetchone()
		if data:
			return data
		return NO_BUSINESS_RULE_FOUND
	def ret_source_data_by_id(self, table_name,id):
	    query = 'select * from ' + table_name + ' where id = %s'
	    cur = self.db.query(query, (id, ))
	    data = cur.fetchone()
	    for k,v in data.items():
	    	if isinstance(v,datetime):
	    		data[k] = data[k].isoformat()
	    		print(data[k], type(data[k]))
	    if data:
	        return data
	    return NO_BUSINESS_RULE_FOUND

	def insert_business_rules(self, data):

	    table_name = data['table_name']
	    update_info = data['update_info']
	    update_info_cols = update_info.keys()

	    sql="insert into "+table_name + "("
	    placeholders="("
	    params=[]

	    for col in update_info_cols:
	        sql+=col+","
	        placeholders+="%s,"
	        if col=='id':
	        	params.append(None)
	        else:
	        	params.append(update_info[col])

	    placeholders=placeholders[:len(placeholders)-1]
	    placeholders+=")"
	    sql=sql[:len(sql)-1]
	    sql+=") values "+ placeholders

	    params_tuple=tuple(params)
	    print(sql)
	    print(params_tuple)
	    res=self.db.transact(sql,params_tuple)
	    self.db.commit()

	    return self.ret_source_data_by_id(table_name,res)

	def update_business_rules(self, data, id):

	    table_name=data['table_name']
	    update_info=data['update_info']
	    update_info_cols=update_info.keys()

	    sql= 'update '+table_name+ ' set '
	    params=[]
	    for col in update_info_cols:
	        sql+=col +'=%s,'
	        params.append(update_info[col])

	    sql=sql[:len(sql)-1]
	    sql+=" where id=%s"
	    params.append(id)
	    params_tuple=tuple(params)

	    print(sql)
	    print(params_tuple)

	    res=self.db.transact(sql,params_tuple)

	    if res==0:
	        self.db.commit()
	        return self.ret_source_data_by_id(table_name,id)

	    self.db.rollback()
	    return UPDATE_ERROR

	def delete_business_rules(self, id):
		sql = 'delete from business_rules where id=%s'
		params = (id, )
		res = self.db.transact(sql, params)
		if res == 0:
			self.db.commit()
			return res

		self.db.rollback()
		return DATABASE_ERROR

	def export_to_csv(self, source_id='ALL'):

		sql = 'select * from business_rules where 1=1'

		if source_id != 'ALL':
			sql += " and source_id='" + str(source_id) + "'"

		cur = self.db.query(sql)

		business_rules = cur.fetchall()

		 # keys=business_rules[0].keys()

		keys = [i[0] for i in cur.description]
		filename="business_rules_source_" + str(source_id) + "_" + str(time.time())+ ".csv"

		with open('./static/'+filename, 'wt') as output_file:
			dict_writer = csv.DictWriter(output_file, keys)
			dict_writer.writeheader()
			dict_writer.writerows(business_rules)
		return { "file_name": filename }

	def list_reports_for_rule(self,**kwargs):

		parameter_list = ['business_rule']

		if set(parameter_list).issubset(set(kwargs.keys())):
			business_rule = kwargs['business_rule']

		else:
			return BUSINESS_RULE_EMPTY

		sql = "select distinct report_id,sheet_id,cell_id from report_calc_def, in_use \
		where cell_business_rules like '%," + business_rule + ",%'"
		cur=self.db.query(sql)
		report_list = cur.fetchall()

		return [(rpt['report_id'], rpt['sheet_id'], rpt['cell_id']) for rpt in report_list]

	def list_reports_for_rule_list(self,**kwargs):

		parameter_list = ['source_id', 'business_rule_list']

		if set(parameter_list).issubset(set(kwargs.keys())):
			source_id = kwargs['source_id']
			business_rule_list = kwargs['business_rule_list'].split(',')

		else:
			return BUSINESS_RULE_EMPTY

		sql = "select report_id,sheet_id,cell_id,cell_business_rules,in_use from report_calc_def where source_id=" + str(
			source_id)
		cur=self.db.query(sql)
		data_list = cur.fetchall()

		result_set = []
		if len(business_rule_list)==1 and business_rule_list[0]=='undefined':
			result_set = data_list
		else:
			for data in data_list:
				# print(data['cell_business_rules'])
				cell_rule_list = data['cell_business_rules'].split(',')
				# print(type(cell_rule_list))
				if set(business_rule_list).issubset(set(cell_rule_list)):
					# print(data)
					result_set.append(data)

		return result_set

	def get_source_suggestion_list(self,source_id='ALL'):

		data_dict={}
		where_clause = ''

		sql = "select source_id, source_table_name " + \
		        " from data_source_information " + \
				" where 1 "
		country_suggestion = self.db.query(sql).fetchall()
		if source_id is not None and source_id !='ALL':
		     where_clause =  " and source_id = " + source_id

		source = self.db.query(sql + where_clause).fetchall()

		data_dict['source_suggestion'] = source

		if not data_dict:
		    return {"msg":"No report matched found"},404
		else:
		    return data_dict

	def get_source_column_suggestion_list(self,table_name):
		data_dict = self.db.query("describe " + table_name).fetchall()

		#Now build the agg column list
		return data_dict

	def validate_python_expression(self,expr_obj):
		py_expr_val=expr_obj['expr']
		py_attr=expr_obj['attr']
		py_sample=expr_obj['sample']
		print(py_attr)

		if not py_attr and not py_sample:
			status='INVALID'
			msg="Expression can't be validated"
		else:
			# Sampling option can also be extended for testing
			#input would be a business_date and no of records for Sampling
			#We need to loop through the set and publish the result array for
			#the sampling set e.g. [{attr:{<id:,business_date:,..>},msg:,status:}]
			#on error no need to continue with the sample, just break and display result
			py_items=[]
			if py_attr:
				py_items.append({"attr":py_attr,"msg":"","status":""})
			if py_sample:
				table_name = py_sample['table_name']
				business_date = py_sample['business_date']
				columns = py_sample['columns']
				sample_size = py_sample['sample_size']
				sample_data = self.db.query("select " + columns + " from " + table_name + " where business_date=" + str(business_date) + " and " + columns.replace(","," is not null and ")+ " is not null LIMIT 0," + str(sample_size)).fetchall()
				for data in sample_data:
					py_items.append({"attr":data,"msg":"","status":""})

			for py_item in py_items:
				print(py_item)
				py_expr = py_expr_val
				for attr in py_item['attr'].keys():
					py_expr=py_expr.replace("["+attr+"]","'" + str(py_item['attr'][attr]) + "'")
				try:
					msg=[]
					status='VALID'
					print(py_expr)
					val=eval(py_expr.replace("DERIVED","'DERIVED'"))
					msg.append('Executed expression gives a value of '+str(val))
				except:
					status='INVALID'
					msg=tb.format_exc().splitlines()[-3:]

				py_item["msg"] = msg
				py_item["status"] = status

		#print(msg[-3:])

		return py_items


from rest_framework import serializers

from .models import Ayah, Sura

class SuraSerializer(serializers.ModelSerializer):

    class Meta:
        model = Sura
        fields = ['index', 'name']

class AyahSerializer(serializers.ModelSerializer):
    sura_index = serializers.IntegerField(source='sura.pk')
    sura_name = serializers.CharField(source='sura.name')
    ayah_number = serializers.IntegerField(source='number')

    class Meta:
        model = Ayah
        fields = ['sura_index', 'sura_name', 'ayah_number', 'text']


from typing import Dict, FrozenSet, List, Optional, Set, TypeVar, TypedDict

DigitSignal = FrozenSet[str]

class Entry(TypedDict):
    signal_patterns: List[str]
    output_digits: List[str]

def parse_entries(message: str) -> List[Entry]:
    entries: List[Entry] = []
    for line in message.splitlines():
        entry: Entry = {
            'signal_patterns': [],
            'output_digits': [],
        }
        parts = line.split(' | ')
        # TODO: These should be sets, not sorted strings, but how much cleanup do I bother with?
        entry['signal_patterns'] = list(map(lambda item: sort_string(item), parts[0].split(' ')))
        entry['output_digits'] = list(map(lambda item: sort_string(item), parts[1].split(' ')))
        entries.append(entry)
    return entries

# TODO: Remove (not needed)
def sort_string(s: str) -> str:
    return ''.join(sorted(list(s)))

T = TypeVar('T')

def get_single_item(s: FrozenSet[T]) -> T:
    assert len(s) == 1
    return list(s)[0]

def compute_signal_digit_mapping(entry: Entry) -> Dict[int, DigitSignal]:

    # Index signals by length
    signals_by_length: Dict[int,FrozenSet[DigitSignal]] = {}
    for signal_pattern_str in entry['signal_patterns']:
        # We use sets so they duplicates are filtered out
        signal_pattern: DigitSignal = frozenset(signal_pattern_str)
        signals_by_length.setdefault(len(signal_pattern), frozenset())
        signals_entry: Optional[FrozenSet[DigitSignal]] = signals_by_length.get(len(signal_pattern))
        assert signals_entry is not None
        signals_by_length[len(signal_pattern)] = signals_entry.union(frozenset({ signal_pattern }))

    known_numbers: Dict[int, DigitSignal] = {}
    known_segments: Dict[str, str] = {}

    # Breadown of Digits vs Segement Counts:
    #
    # (0,6,9): 6 segments
    # (1): 2 segements
    # (2,3,5): 5 segments
    # (4): 4 segements
    # (7): 3 segments
    # (8): 7 segments

    # 1.  We know signals "1", "4", "7", "8" (by segment counts)
    known_numbers[1] = list(signals_by_length[2])[0]
    known_numbers[4] = list(signals_by_length[4])[0]
    known_numbers[7] = list(signals_by_length[3])[0]
    known_numbers[8] = list(signals_by_length[7])[0]

    # 2.  We know segment "a" is "7" - "1"
    known_segments['a'] = get_single_item(known_numbers[7] - known_numbers[1])

    # 3.  We know signal "6" is any 6-segement signal that does not fully contain "1"
    for digit_signal in signals_by_length[6]:
        if len(known_numbers[1] - digit_signal) > 0:
            known_numbers[6] = digit_signal
            break
    assert 6 in known_numbers

    # 3b. We know segment "c" is the segment of "1" missing from "6"
    known_segments['c'] = get_single_item(known_numbers[1] - known_numbers[6])

    #  3c. We know segment "f" is the other segment from "1"
    known_segments['f'] = get_single_item(known_numbers[1] - set(known_segments['c']))

    # 4.  We know signal "5" is any 5-segement signal that does not contain "c"
    for digit_signal in signals_by_length[5]:
        if known_segments['c'] not in digit_signal:
            known_numbers[5] = digit_signal
            break
    assert 5 in known_numbers

    # 5.  We know signal "2" is any 5-segement signal that does not contain "f"
    for digit_signal in signals_by_length[5]:
        if known_segments['f'] not in digit_signal:
            known_numbers[2] = digit_signal
            break
    assert 2 in known_numbers

    # 6.  We know signal "3" is the remaining 5-segment signal
    for digit_signal in signals_by_length[5]:
        if digit_signal != known_numbers[2] and digit_signal != known_numbers[5]:
            known_numbers[3] = digit_signal
            break
    assert 3 in known_numbers

    # 7.  We know segment "b" is "5" - "2" - "1"
    known_segments['b'] = get_single_item(known_numbers[5] - known_numbers[2] - known_numbers[1])

    # 8.  We know segment "e" is "2" - "5" - "1"
    known_segments['e'] = get_single_item(known_numbers[2] - known_numbers[5] - known_numbers[1])

    # 9.  We know signal "9" is any 6-segement signal that does not contain "e"
    for digit_signal in signals_by_length[6]:
        if known_segments['e'] in digit_signal:
            continue
        known_numbers[9] = digit_signal
        break
    assert 9 in known_numbers

    # 10. We know signal "0" is the remaining 6-segment signal
    known_numbers[0] = get_single_item( \
        signals_by_length[6].difference({ known_numbers[6] }).difference({ known_numbers[9] }))

    # Not needed
    #
    # # 11. We know segment "d" is "8" - "0"
    # known_segments['d'] = get_single_item(known_numbers[8] - known_numbers[0])
    #
    # # 12. We know segment "g" is the remaining segment
    # all_segments = {'a','b','c','d','e','f','g'}
    # known_segments['d'] = get_single_item(all_segments - set(known_segments.values()))

    return known_numbers

def compute_output_value(entry: Entry) -> int:
    signal_digit_mapping: Dict[int, DigitSignal] = compute_signal_digit_mapping(entry)
    output_value = 0
    digit_multiplier = 1
    for output_digit_segments_str in reversed(entry['output_digits']):
        output_digit_segments = set(list(output_digit_segments_str))
        for digit,segments in signal_digit_mapping.items():
            if output_digit_segments != segments:
                continue
            output_value += digit * digit_multiplier
            digit_multiplier *= 10
            break
    return output_value

def compute_sum_of_output_values(entries: List[Entry]) -> int:
    sum: int = 0
    for entry in entries:
        sum += compute_output_value(entry)
    return sum


'''
https://github.com/kuangliu/pytorch-cifar/blob/master/models/densenet.py
DenseNet in PyTorch
'''
import math
import torch
import torch.nn as nn
import torch.nn.functional as F

class Bottleneck(nn.Module):
    def __init__(self, in_planes, growth_rate):
        super(Bottleneck, self).__init__()
        self.bn1 = nn.BatchNorm2d(in_planes)
        self.conv1 = nn.Conv2d(in_planes, 4*growth_rate, kernel_size=1, bias=False)
        self.bn2 = nn.BatchNorm2d(4*growth_rate)
        self.conv2 = nn.Conv2d(4*growth_rate, growth_rate, kernel_size=3, padding=1, bias=False)

    def forward(self, x):
        out = self.conv1(F.relu(self.bn1(x)))
        out = self.conv2(F.relu(self.bn2(out)))
        out = torch.cat([out, x], 1)
        return out

class Transition(nn.Module):
    def __init__(self, in_planes, out_planes):
        super(Transition, self).__init__()
        self.bn = nn.BatchNorm2d(in_planes)
        self.conv = nn.Conv2d(in_planes, out_planes, kernel_size=1, bias=False)

    def forward(self, x):
        out = self.conv(F.relu(self.bn(x)))
        out = F.avg_pool2d(out, 2)
        return out

class RobustDenseNet(nn.Module):
    '''
    DenseNet121:
        depth_configs=[6, 12, 24, 16]
        channel_configs=[64, 128, 256, 512]
        growth_rate=32

    DenseNet169:
        depth_configs=[6, 12, 32, 32]
        channel_configs=[64, 128, 256, 640]
        growth_rate=32

    DenseNet201:
        depth_configs=[6, 12, 48, 32]
        channel_configs=[64, 128, 256, 896]
        growth_rate=32

    DenseNet161:
        depth_configs=[6, 12, 36, 24]
        channel_configs=[96, 192, 384, 1056]
        growth_rate=48
    '''
    def __init__(self, depth_configs=[6, 12, 24, 16], channel_configs=[64, 128, 256, 512],
                 growth_rate=32, reduction=0.5, num_classes=10):

        super(RobustDenseNet, self).__init__()
        self.growth_rate = growth_rate
        block = Bottleneck

        self.conv1 = nn.Conv2d(3, channel_configs[0], kernel_size=3, padding=1, bias=False)
        stages = []

        for i, nblocks in enumerate(depth_configs):
            block_out = nblocks * growth_rate + channel_configs[i]
            if i < len(depth_configs) - 1:
                out_planes = channel_configs[i+1]
                stage = nn.Sequential(self._make_dense_layers(block, channel_configs[i], nblocks),
                                      Transition(block_out, out_planes))
            else:
                stage = nn.Sequential(self._make_dense_layers(block, channel_configs[i], nblocks))
            stages.append(stage)

        self.stages = nn.Sequential(*stages)
        self.bn = nn.BatchNorm2d(block_out)
        self.linear = nn.Linear(block_out, num_classes)

    def _make_dense_layers(self, block, in_planes, nblock):
        layers = []
        for i in range(nblock):
            layers.append(block(in_planes, self.growth_rate))
            in_planes += self.growth_rate
        return nn.Sequential(*layers)

    def forward(self, x):
        out = self.conv1(x)
        out = self.stages(out)
        out = F.avg_pool2d(F.relu(self.bn(out)), 4)
        out = out.view(out.size(0), -1)
        out = self.linear(out)
        return out


# Code to simulate the environment when trained
import os
#ignore tensorflow warnings
import warnings
warnings.filterwarnings("ignore")

import gym
import numpy as np
import matplotlib
matplotlib.use('pdf') # To avoid plt.show issues in virtualenv
import matplotlib.pyplot as plt

from stable_baselines.common.policies import MlpPolicy
from stable_baselines import PPO2
from Trajectory_gen import Navigation

env = Navigation(Random_reset=False, Process_perturbations=True)

tieme_steps_to_simulate = env.max_Episode_time_steps + 1 ## define the number of timesteps to simulate

######################################
##      POLICY LOADING SECTION      ##
######################################

# use of os.path.exists() to check and load the last policy evaluated by training
# function.
print("Policy loading...")

Policy_loading_mode = input("Insert loading mode\nlast: loads last policy saved\nbest: loads best policy saved\nsel: loads a specified policy\n-----> ")

if Policy_loading_mode == "last":
  for i in range(100, 0, -1): ## function look for the last policy evaluated.
    fileName_toFind = "/home/ghost/giorgio_diliberi/ReinforcementLearning/SBs_2_on_remote/Trajectory_gen/Policies/PPO_Quad_" + str(i) + ".zip"

    if os.path.exists(fileName_toFind):
      print("last policy found is PPO_Quad_" + str(i))

      Policy2Load = "Policies/PPO_Quad_" + str(i)

      break

elif Policy_loading_mode == "best":

  Policy2Load = "EvalClbkLogs/best_model.zip" # best policy name

else:

  Policy2Load = input("enter the number of policy to load (check before if exists): ")

model = PPO2.load(Policy2Load)
print("Policy ", Policy2Load, " loaded!")

######################################
##     SIMULATION SECTION           ##
######################################

#model = PPO2.load("Policies/PPO_Quad_1")  # uncomment this line to load a specific policy instead of the last one

obs = env.reset()

# info vectors initialization for simulation history
info_u = [env.state[0]]
info_v = [env.state[1]]
info_X = [env.state[2]]
info_Y = [env.state[3]]
action_memory = np.array([0., 0.]) ## vector to store actions during the simulation
#Throttle_memory = [env.dTt]
episode_reward = [env.getReward()]

time=0.
info_time=[time] # elapsed time vector

# SIMULATION

for i in range(tieme_steps_to_simulate): #last number is excluded

    action, _state = model.predict(obs, deterministic=True) # Add deterministic true for PPO to achieve better performane

    obs, reward, done, info = env.step(action)

    info_u.append(info["u"])
    info_v.append(info["v"])
    info_X.append(info["X"])
    info_Y.append(info["Y"])
    action_memory = np.vstack([action_memory, action])
    episode_reward.append(reward) # save the reward for all the episode

    time=time + env.timeStep # elapsed time since simulation start
    info_time.append(time)

    #env.render()
    if done:
      # obs = env.reset()
      break

## PLOT AND DISPLAY SECTION

plt.figure(1)
plt.plot(info_time, info_u)
plt.plot(info_time, info_v)
plt.xlabel('time')
plt.ylabel('Velocity [m/s]')
plt.title('u,v')
plt.legend(['u', 'v'])
plt.savefig('SimulationResults/Velocity.jpg')

plt.figure(2)
plt.plot(info_time, info_X)
plt.plot(info_time, info_Y)
plt.xlabel('time')
plt.ylabel('Position NE [m]')
plt.title('X,Y')
plt.legend(['X', 'Y'])
plt.savefig('SimulationResults/Position.jpg')

plt.figure(3)
plt.plot(info_time, episode_reward)
plt.xlabel('time')
plt.ylabel('Reward')
plt.title('Episode Reward')
plt.savefig('SimulationResults/reward.jpg')

plt.figure(4)
plt.plot(info_time, action_memory[:, 0])
plt.plot(info_time, action_memory[:, 1])
plt.xlabel('time')
plt.ylabel('Actions')
plt.title('Actions in episode [-1, 1]')
plt.legend(['tg_theta', 'tg_phi'])
plt.savefig('SimulationResults/action.jpg')

plt.figure(5)
plt.plot(info_X, info_Y)
plt.xlabel('X_Pos [m]')
plt.ylabel('Y_Pos [m]')
plt.title('Trajectory')
plt.savefig('SimulationResults/Trajectory.jpg')


"""
Laboratorio de Programación Básica en Python para Manejo de Datos
-----------------------------------------------------------------------------------------

Este archivo contiene las preguntas que se van a realizar en el laboratorio.

No puede utilizar pandas, numpy o scipy. Se debe utilizar solo las funciones de python
básicas.

Utilice el archivo `data.csv` para resolver las preguntas.

"""

def pregunta_01():
    """
    Retorne la suma de la segunda columna.

    Rta/
    214

    """

    f1 = []
    with open('data.csv', 'r') as file:
        lineas = file.read().splitlines()
        for i in lineas:
            lineas = i.split('\t')
            f1.append(lineas)

    s = [int(row[1]) for row in f1]

    return sum(s)

def pregunta_02():
    """
    Retorne la cantidad de registros por cada letra de la primera columna como la lista
    de tuplas (letra, cantidad), ordendas alfabéticamente.

    Rta/
    [
        ("A", 8),
        ("B", 7),
        ("C", 5),
        ("D", 6),
        ("E", 14),
    ]

    """
    from itertools import groupby

    f2 = []
    dict1 = []
    with open('data.csv', 'r') as file:
        lineas = file.read().splitlines()
        for i in lineas:
            lineas = i.split('\t')
            f2.append(lineas)

    l = sorted([row[0] for row in f2])

    for k, g in groupby(l):
       a = (k,len(list(g)))
       dict1.append(a)

    return dict1

def pregunta_03():
    """
    Retorne la suma de la columna 2 por cada letra de la primera columna como una lista
    de tuplas (letra, suma) ordendas alfabeticamente.

    Rta/
    [
        ("A", 53),
        ("B", 36),
        ("C", 27),
        ("D", 31),
        ("E", 67),
    ]

    """

    from operator import itemgetter
    from itertools import groupby

    f3 = []
    dict2 = []
    with open('data.csv', 'r') as file:
        lineas = file.read().splitlines()
        for i in lineas:
            lineas = i.split('\t')
            f3.append(lineas)

    l1 = [[row[0], row[1]] for row in f3]

    l1 = sorted(l1, key=itemgetter(0))
    for k, g in groupby(l1, itemgetter(0)):
        lista_g = (list(g))
        b = (k,sum([int(num) for elem in lista_g for num in elem[1]]))
        dict2.append(b)

    return dict2

def pregunta_04():
    """
    La columna 3 contiene una fecha en formato `YYYY-MM-DD`. Retorne la cantidad de
    registros por cada mes, tal como se muestra a continuación.

    Rta/
    [
        ("01", 3),
        ("02", 4),
        ("03", 2),
        ("04", 4),
        ("05", 3),
        ("06", 3),
        ("07", 5),
        ("08", 6),
        ("09", 3),
        ("10", 2),
        ("11", 2),
        ("12", 3),
    ]

    """

    from itertools import groupby

    f4 = []
    dict3 = []
    with open('data.csv', 'r') as file:
        lineas = file.read().splitlines()
        for i in lineas:
            lineas = i.split('\t')
            f4.append(lineas)
    fecha = [row[2] for row in f4]
    mes = [num[5:7] for num in fecha]
    l2 = sorted(mes)

    for k, g in groupby(l2):
        c=(k,len(list(g)))
        dict3.append(c)

    return dict3

def pregunta_05():
    """
    Retorne una lista de tuplas con el valor maximo y minimo de la columna 2 por cada
    letra de la columa 1.

    Rta/
    [
        ("A", 9, 2),
        ("B", 9, 1),
        ("C", 9, 0),
        ("D", 8, 3),
        ("E", 9, 1),
    ]

    """

    from operator import itemgetter
    from itertools import groupby

    f5 = []
    dict4 = []
    with open('data.csv', 'r') as file:
        lineas = file.read().splitlines()
        for i in lineas:
            lineas = i.split('\t')
            f5.append(lineas)

    l = [[row[0], row[1]] for row in f5]

    l = sorted(l, key=itemgetter(0))
    for k1, g1 in groupby(l, itemgetter(0)):
        lista_g = (list(g1))
        d= (k1,max([int(num) for elem in lista_g for num in elem[1]]),min([int(num) for elem in lista_g for num in elem[1]]))
        dict4.append(d)

    return dict4

def pregunta_06():
    """
    La columna 5 codifica un diccionario donde cada cadena de tres letras corresponde a
    una clave y el valor despues del caracter `:` corresponde al valor asociado a la
    clave. Por cada clave, obtenga el valor asociado mas pequeño y el valor asociado mas
    grande computados sobre todo el archivo.

    Rta/
    [
        ("aaa", 1, 9),
        ("bbb", 1, 9),
        ("ccc", 1, 10),
        ("ddd", 0, 9),
        ("eee", 1, 7),
        ("fff", 0, 9),
        ("ggg", 3, 10),
        ("hhh", 0, 9),
        ("iii", 0, 9),
        ("jjj", 5, 17),
    ]

    """
    from itertools import groupby
    from operator import itemgetter

    max_min_dict = {}
    with open('data.csv','r') as f:
        for line in f:
            fields = line.split("\t")
            column = fields[4]
            elements = column.split(',')
            values = [int(element.split(':')[1]) for element in elements]
            key_value_pairs = [tuple(element.split(':')) for element in elements]
            for key, value in key_value_pairs:
                value = int(value)
                if key in max_min_dict:
                    max_val, min_val = max_min_dict[key][0], max_min_dict[key][1]
                    max_min_dict[key] = (max(value, max_val), min(value, min_val))
                else:
                    max_min_dict[key] = (value, value)
    max_min_list = [(k, v[1], v[0]) for k, v in max_min_dict.items()]
    max_min_list.sort()

    return max_min_list

def pregunta_07():
    """
    Retorne una lista de tuplas que asocien las columnas 0 y 1. Cada tupla contiene un
    valor posible de la columna 2 y una lista con todas las letras asociadas (columna 1)
    a dicho valor de la columna 2.

    Rta/
    [
        (0, ["C"]),
        (1, ["E", "B", "E"]),
        (2, ["A", "E"]),
        (3, ["A", "B", "D", "E", "E", "D"]),
        (4, ["E", "B"]),
        (5, ["B", "C", "D", "D", "E", "E", "E"]),
        (6, ["C", "E", "A", "B"]),
        (7, ["A", "C", "E", "D"]),
        (8, ["E", "D", "E", "A", "B"]),
        (9, ["A", "B", "E", "A", "A", "C"]),
    ]

    """
    from operator import itemgetter
    from itertools import groupby

    f = []
    dict6 = []
    with open('data.csv', 'r') as file:
        lineas = file.read().splitlines()
        for i in lineas:
            lineas = i.split('\t')
            f.append(lineas)

    l = [[row[1], row[0]] for row in f]

    l = sorted(l, key=itemgetter(0))
    for k, g in groupby(l, itemgetter(0)):
        lista_g = (list(g))
        a1= (int(k),[num for elem in lista_g for num in elem[1]])
        dict6.append(a1)

    return dict6

def pregunta_08():
    """
    Genere una lista de tuplas, donde el primer elemento de cada tupla contiene  el valor
    de la segunda columna; la segunda parte de la tupla es una lista con las letras
    (ordenadas y sin repetir letra) de la primera  columna que aparecen asociadas a dicho
    valor de la segunda columna.

    Rta/
    [
        (0, ["C"]),
        (1, ["B", "E"]),
        (2, ["A", "E"]),
        (3, ["A", "B", "D", "E"]),
        (4, ["B", "E"]),
        (5, ["B", "C", "D", "E"]),
        (6, ["A", "B", "C", "E"]),
        (7, ["A", "C", "D", "E"]),
        (8, ["A", "B", "D", "E"]),
        (9, ["A", "B", "C", "E"]),
    ]

    """

    from operator import itemgetter
    from itertools import groupby

    f = []
    dict7 = []
    with open('data.csv', 'r') as file:
        lineas = file.read().splitlines()
        for i in lineas:
            lineas = i.split('\t')
            f.append(lineas)

    l = [[row[1], row[0]] for row in f]

    l = sorted(l, key=itemgetter(0))
    for k, g in groupby(l, itemgetter(0)):
        lista_g = (list(g))
        a2=((int(k), sorted(list(set([num for elem in lista_g for num in elem[1]])))))
        dict7.append(a2)

    return dict7

def pregunta_09():
    """
    Retorne un diccionario que contenga la cantidad de registros en que aparece cada
    clave de la columna 5.

    Rta/
    {
        "aaa": 13,
        "bbb": 16,
        "ccc": 23,
        "ddd": 23,
        "eee": 15,
        "fff": 20,
        "ggg": 13,
        "hhh": 16,
        "iii": 18,
        "jjj": 18,
    }

    """
    count_dict = {}
    with open('data.csv','r') as f:
        for line in f:
            fields = line.split("\t")
            column = fields[4]
            elements = column.split(',')
            key_value_pairs = [tuple(element.split(':')) for element in elements]
            for key, value in key_value_pairs:
                if key in count_dict:
                    count_dict[str(key)] += int(1)
                else:
                    count_dict[str(key)] = int(1)

        count_dict_sorted = dict(sorted(count_dict.items()))
    return count_dict_sorted

def pregunta_10():
    """
    Retorne una lista de tuplas contengan por cada tupla, la letra de la columna 1 y la
    cantidad de elementos de las columnas 4 y 5.

    Rta/
    [
        ("E", 3, 5),
        ("A", 3, 4),
        ("B", 4, 4),
        ...
        ("C", 4, 3),
        ("E", 2, 3),
        ("E", 3, 3),
    ]

    """

    from itertools import groupby
    from operator import itemgetter

    f = []
    dict9 = []
    with open('data.csv', 'r') as file:
        lineas = file.read().splitlines()
        for i in lineas:
            lineas = i.split('\t')
            f.append(lineas)

    l = [[row[0], row[3], row[4]] for row in f]

    for k, g in groupby(l, itemgetter(0,1,2)):
        lista_g = list(g)
        b2=(k[0], len([l1 for l2 in [num.split('\t') for elem in lista_g for num in elem][1] for l1 in l2 if l1 != ","]),len([num.split(',') for elem in lista_g for num in elem][2]))
        dict9.append(b2)

    return dict9

def pregunta_11():
    """
    Retorne un diccionario que contengan la suma de la columna 2 para cada letra de la
    columna 4, ordenadas alfabeticamente.

    Rta/
    {
        "a": 122,
        "b": 49,
        "c": 91,
        "d": 73,
        "e": 86,
        "f": 134,
        "g": 35,
    }

    """
    with open('data.csv','r') as f:
        sum_dict = {}
        for line in f:
            fields = line.split("\t")
            letter = fields[0]
            value = int(fields[1])
            column = fields[3]
            letters = column.split(",")
            for l in letters:
                if l in sum_dict:
                    sum_dict[l] += value
                else:
                    sum_dict[l] = value

    return dict(sorted(sum_dict.items()))

def pregunta_12():
    """
    Genere un diccionario que contengan como clave la columna 1 y como valor la suma de
    los valores de la columna 5 sobre todo el archivo.

    Rta/
    {
        'A': 177,
        'B': 187,
        'C': 114,
        'D': 136,
        'E': 324
    }

    """
    diccionario = {}
    with open("data.csv", "r") as archivo:
        for linea in archivo:
            fields = linea.split("\t")
            letra = fields[0]
            column = fields[4]
            elements = column.split(',')
            valores = [int(element.split(':')[1]) for element in elements]
            suma = sum(valores)
            if letra not in diccionario:
                diccionario[letra] = 0
            diccionario[letra] += suma
    return dict(sorted(diccionario.items()))