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

"""
    File name: reservoir_net.py
    Description: a neural network with readout units and a feedback loop
    Authors: Roman Pogodin, adapted from Moritz Deger and Joseph Heng
    Python version: 3.6
"""

import numpy as np
from warnings import warn

class ReservoirNet(object):
    """
    A recurrent neural net with trainable output units. Variable names are chosen to be consistent
    with the reference paper.

    This is an implementation of
    Hoerzer, G. M.; Legenstein, R. & Maass, W.
    Emergence of Complex Computational Structures From Chaotic Neural Networks
    Through Reward-Modulated Hebbian Learning. Cereb Cortex, 2012
    """
    def __init__(self, num_rec=1000, num_out=1, amplitude_fb_weights=1.0, randseed=None,
                 learning_rule='hebb', hebb_rate=0.0005, hebb_decay=20.0 * 1e3, hebb_const=False, update_prob=1.0,
                 delayed_updates=False):
        """
        :param num_rec:                 number of recurrent neurons
        :param num_out:                 number of output units (== target function dimension)
        :param amplitude_fb_weights:    strength of feedback connection from the output units
        :param randseed:                random seed for all random operations
        :param learning_rule:           'hebb' for reward-modulated Hebbian rule of 'force' for FORCE
        :param hebb_rate:               initial learning rate for the Hebbian rule
        :param hebb_decay:              hyperbolic decay constant for the Hebbian rule's learning rate
        :param hebb_const:              whether Hebbian learning uses constant learning rate or not
        :param update_prob:             probability of the weights' update
        :param delayed_updates:         if true, all updates are collected and applied on apply_weight_updates()
        """
        self._num_rec = num_rec
        self._num_out = num_out
        self._theta_state = 0.05  # amplitude of firing rates noise
        self._theta_expl = 0.5  # amplitude of output units noise
        self._rec_connect_prob = 0.1  # probability of a recurrent connection
        self._tau = 50.0  # membrane time constant, ms
        self._tau_avg = 5.0  # decay constant for smoothing filters
        self._dt = 1.0  # time step, ms
        self._lambda = 1.5  # strength of recurrent connection, controls the chaotic regime
        self._eta_init = hebb_rate  # initial learning rate
        self._tau_eta = hebb_decay  # decay constant for the learning rate, ms
        self._hebb_const = hebb_const  # constant learning rate; makes hebb_decay useless
        self._update_prob = update_prob

        self._rand_generator = np.random.RandomState(randseed)
        self._amplitude_fb_weights = amplitude_fb_weights
        self._external_noise_amplitude = 0.05  # Gaussian noise amplitude for input signals

        self.x = np.zeros(self._num_rec)  # membrane potential
        self.r = np.zeros(self._num_rec)  # firing rate
        self.z = np.zeros(self._num_out)  # noisy output
        self.zhat = np.zeros(self._num_out)  # noiseless output
        self.zbar = np.zeros(self._num_out)  # temporally smoothed noisy output
        self.f = np.zeros(self._num_out)  # target function
        self.weights_out = self._make_weights_out()  # output weights (plastic)

        self._xi_state = np.zeros(self._num_rec)  # firing rates noise
        self._xi_expl = np.zeros(self._num_out)  # output noise
        self._weights_rec = self._make_weights_rec()  # recurrent weights
        self._weights_fb = self._make_weights_fb()  # feedback weights
        self._filter_decay = self._dt / self._tau_avg
        self._dynamics_decay = self._dt / self._tau
        self._external_input = np.zeros(self._num_rec)
        self._time = 0  # inner time, used for the learning rates
        self._learning_rule = learning_rule

        self._update_indicator = 1.0  # for stochastic weights update

        if self._learning_rule == 'hebb':
            self._eta = self._eta_init
            self._p = 0.
            self._pbar = 0.
            self._modulation = 0.
        elif self._learning_rule == 'force':
            self._covariance = np.eye(self._num_rec)  # times alpha, which is 1.0 in the FORCE paper
            self._cov_r_prod = self._covariance.dot(self.r)

        self._memorised_state = None  # used to memorise the state after training

        self._delayed_updates = delayed_updates
        if self._delayed_updates:
            self._collected_updates = np.zeros_like(self.weights_out)

    def _make_indicator_rec(self):
        """
        Creates a binary recurrent connectivity matrix.
        :return: a (_num_rec, _num_rec) 0/1 matrix
        """
        size = (self._num_rec, self._num_rec)
        return self._rand_generator.binomial(1, self._rec_connect_prob, size=size)

    def _make_weights_rec(self):
        """
        Makes random normal recurrent weights.
        :return: a (_num_rec, _num_rec) real matrix
        """
        size = (self._num_rec, self._num_rec)
        std = np.sqrt(1.0 / (self._rec_connect_prob * self._num_rec))
        return self._rand_generator.normal(loc=0.0, scale=std, size=size) * self._make_indicator_rec()

    def _make_weights_fb(self):
        """
        Makes random uniform feedback weights.
        :return: a (_num_rec, _num_out) real matrix
        """
        return self._rand_generator.uniform(low=-self._amplitude_fb_weights,
                                            high=self._amplitude_fb_weights,
                                            size=(self._num_rec, self._num_out))

    def _make_weights_out(self):
        """
        Sets initial output weights to zero.
        :return: a zero (_num_out, _num_rec) matrix
        """
        return np.zeros((self._num_out, self._num_rec))

    def _get_dx(self):
        """Return update of the membrane potential."""
        return self._dynamics_decay * (-self.x + self._lambda * np.dot(self._weights_rec, self.r) +
                                       np.dot(self._weights_fb, self.z) + self._external_input)

    def _set_xi_state(self):
        """Sets noise in firing rates"""
        self._xi_state = self._rand_generator.uniform(low=-self._theta_state,
                                                      high=self._theta_state, size=self._num_rec)

    def _set_r(self):
        """Sets firing rates (including noise)"""
        self.r = np.tanh(self.x) + self._xi_state

    def _set_xi_expl(self):
        """Sets noise in readout neurons"""
        self._xi_expl = self._rand_generator.uniform(low=-self._theta_expl,
                                                     high=self._theta_expl, size=self._num_out)

    def _set_zhat(self):
        """Sets activity in readout neurons (without noise)"""
        self.zhat = np.dot(self.weights_out, self.r)

    def _set_z(self):
        """Sets activity in readout neurons (including noise)"""
        self.z = self.zhat + self._xi_expl

    def _set_zbar(self):
        """Sets low-pass filtered version of activity in readout neurons (including noise)"""
        self.zbar = (1.0 - self._filter_decay) * self.zbar + self._filter_decay * self.z

    def _update_external_input(self, external_input=None):
        """Updates external input and adds noise (if None, sets it to zero)."""
        if external_input is not None:  # add noise
            self._external_input = external_input + self._external_noise_amplitude * \
                                   self._rand_generator.normal(loc=0.0, scale=1.0, size=(self._num_rec,))
        else:
            self._external_input.fill(0)

    def _update_hebb_params(self):
        """Updates parameters of the reward-modulated Hebbian learning rule."""
        self._p = -np.dot(self.z - self.f, self.z - self.f)
        self._pbar = (1.0 - self._filter_decay) * self._pbar + self._filter_decay * self._p
        self._modulation = float(self._p > self._pbar)
        if not self._hebb_const:
            self._eta = self._eta_init / (1.0 + self._time / self._tau_eta)

    def _update_force_params(self):
        """Updates _covariance estimate of the FORCE rule."""
        self._cov_r_prod = self._covariance.dot(self.r)  # old cov, new r
        self._covariance -= np.outer(self._cov_r_prod, self._cov_r_prod / (1.0 + self.r.dot(self._cov_r_prod)))

    def _get_dw_out(self):
        """Returns learning rule-dependent weights update."""
        if self._learning_rule == 'hebb':
            return np.outer(self._modulation * self._eta * (self.z - self.zbar), self.r)
        if self._learning_rule == 'force':
            self._cov_r_prod = self._covariance.dot(self.r)  # new cov, new r
            return np.outer(self.f - self.zhat, self._cov_r_prod)

    def _update_activity(self):
        """Updates activity of the network."""
        self.x += self._get_dx()
        self._set_xi_state()
        self._set_r()
        self._set_xi_expl()
        self._set_zhat()  # noiseless readout
        self._set_z()    # noisy readout
        self._set_zbar()  # average

    def _update_plasticity(self, target_function):
        """
        Updates plasticity of the network.

        Must be called after _update_activity().
        :param target_function: a (_num_out) array (or a scalar for _num_out=1)
        :return: None
        """
        self._time += 1  # only used for plasticity (self._eta decay)
        self.f = target_function

        if self._learning_rule == 'hebb':
            self._update_hebb_params()
        elif self._learning_rule == 'force':
            self._update_force_params()

        if self._update_prob < (1.0 - 1e-12):
            self._update_indicator = self._rand_generator.binomial(1, self._update_prob)

        if self._delayed_updates:
            self._collected_updates += self._get_dw_out() * self._update_indicator
        else:
            self.weights_out += self._get_dw_out() * self._update_indicator

    def reset_network_activity(self):
        """
        Sets dynamics of the network to zero.
        :return: None
        """
        self.x = np.zeros(self._num_rec)
        self.r = np.zeros(self._num_rec)
        self.z = np.zeros(self._num_out)
        self.zhat = np.zeros(self._num_out)
        self.zbar = np.zeros(self._num_out)

    def memorise_state(self):
        """Saves the network's state. Does not save plasticity parameters."""
        self._memorised_state = (self.x.copy(), self.r.copy(), self.z.copy(), self.zhat.copy(), self.zbar.copy())

    def recover_memorised_state(self):
        """Recovers the saved network's state."""
        if self._memorised_state is not None:
            self.x, self.r, self.z, self.zhat, self.zbar = self._memorised_state
            self._memorised_state = None
        else:
            warn("Nothing to recover")

    def update(self, external_input=None, target_function=None):
        """
        Updates the state of the network and the output weights (if target_function is not None).
        :param external_input:  None or a (_num_rec) vector. In the latter case Gaussian noise will be added.
        :param target_function: None (for no plasticity) or a (_num_out) array.
        :return: None
        """
        self._update_external_input(external_input)
        self._update_activity()

        if target_function is not None:
            self._update_plasticity(target_function)

    def are_weights_updated(self):
        """Indicates whether the weights were updated last time or not."""
        if self._learning_rule == 'hebb':
            return int(self._modulation * self._update_indicator)
        return int(self._update_indicator)

    def are_updates_delayed(self):
        return self._delayed_updates

    def apply_weight_updates(self):
        if not self._delayed_updates:
            warn('Updates were not collected')
            return

        self.weights_out += self._collected_updates
        self._collected_updates.fill(0.0)


import torch
if torch.__version__ >= '1.8':
    import torch_npu

import torch.nn.functional as F
# from SSD import _C as C
import numpy as np
import itertools

from math import sqrt

def calc_iou_tensor(box1, box2):
    """
        Calculation of IoU based on two boxes tensor,
        Reference to https://github.com/kuangliu/pytorch-ssd
        input:
            box1 (N, 4)
            box2 (M, 4)
        output:
            IoU (N, M)
    """
    N = box1.size(0)
    M = box2.size(0)

    be1 = box1.unsqueeze(1).expand(-1, M, -1)
    be2 = box2.unsqueeze(0).expand(N, -1, -1)

    # Left Top & Right Bottom
    lt = torch.max(be1[:, :, :2], be2[:, :, :2])
    rb = torch.min(be1[:, :, 2:], be2[:, :, 2:])

    delta = rb - lt
    delta[delta < 0] = 0
    intersect = delta[:, :, 0] * delta[:, :, 1]

    delta1 = be1[:, :, 2:] - be1[:, :, :2]
    area1 = delta1[:, :, 0] * delta1[:, :, 1]
    delta2 = be2[:, :, 2:] - be2[:, :, :2]
    area2 = delta2[:, :, 0] * delta2[:, :, 1]

    iou = intersect / (area1 + area2 - intersect)
    return iou

class DefaultBoxes(object):
    def __init__(self, fig_size, feat_size, steps, scales, aspect_ratios, \
                       scale_xy=0.1, scale_wh=0.2):

        self.feat_size = feat_size
        self.fig_size = fig_size

        self.scale_xy_ = scale_xy
        self.scale_wh_ = scale_wh

        # According to https://github.com/weiliu89/caffe
        # Calculation method slightly different from paper
        self.steps = steps
        self.scales = scales

        fk = fig_size/np.array(steps)
        self.aspect_ratios = aspect_ratios

        self.default_boxes = []
        # size of feature and number of feature
        for idx, sfeat in enumerate(self.feat_size):

            sk1 = scales[idx]/fig_size
            sk2 = scales[idx+1]/fig_size
            sk3 = sqrt(sk1*sk2)
            all_sizes = [(sk1, sk1), (sk3, sk3)]

            for alpha in aspect_ratios[idx]:
                w, h = sk1*sqrt(alpha), sk1/sqrt(alpha)
                all_sizes.append((w, h))
                all_sizes.append((h, w))
            for w, h in all_sizes:
                for i, j in itertools.product(range(sfeat), repeat=2):
                    cx, cy = (j+0.5)/fk[idx], (i+0.5)/fk[idx]
                    self.default_boxes.append((cx, cy, w, h))

        self.dboxes = torch.tensor(self.default_boxes, dtype=torch.float).cpu()
        self.dboxes.clamp_(min=0, max=1)
        # For IoU calculation
        self.dboxes_ltrb = self.dboxes.clone()
        self.dboxes_ltrb[:, 0] = self.dboxes[:, 0] - 0.5*self.dboxes[:, 2]
        self.dboxes_ltrb[:, 1] = self.dboxes[:, 1] - 0.5*self.dboxes[:, 3]
        self.dboxes_ltrb[:, 2] = self.dboxes[:, 0] + 0.5*self.dboxes[:, 2]
        self.dboxes_ltrb[:, 3] = self.dboxes[:, 1] + 0.5*self.dboxes[:, 3]

    @property
    def scale_xy(self):
        return self.scale_xy_

    @property
    def scale_wh(self):
        return self.scale_wh_

    def __call__(self, order="ltrb"):
        if order == "ltrb": return self.dboxes_ltrb
        if order == "xywh": return self.dboxes

# This class is from https://github.com/kuangliu/pytorch-ssd
class Encoder(object):
    """
        Inspired by https://github.com/kuangliu/pytorch-ssd
        Transform between (bboxes, lables) <-> SSD output

        dboxes: default boxes in size 8732 x 4,
            encoder: input ltrb format, output xywh format
            decoder: input xywh format, output ltrb format

        encode:
            input  : bboxes_in (Tensor nboxes x 4), labels_in (Tensor nboxes)
            output : bboxes_out (Tensor 8732 x 4), labels_out (Tensor 8732)
            criteria : IoU threshold of bboexes

        decode:
            input  : bboxes_in (Tensor 8732 x 4), scores_in (Tensor 8732 x nitems)
            output : bboxes_out (Tensor nboxes x 4), labels_out (Tensor nboxes)
            criteria : IoU threshold of bboexes
            max_output : maximum number of output bboxes
    """

    def __init__(self, dboxes):
        self.dboxes = dboxes(order="ltrb")
        self.dboxes_xywh = dboxes(order="xywh").unsqueeze(dim=0)
        self.nboxes = self.dboxes.size(0)
        #print("# Bounding boxes: {}".format(self.nboxes))
        self.scale_xy = dboxes.scale_xy
        self.scale_wh = dboxes.scale_wh

        self.dboxes = self.dboxes.cpu()
        self.dboxes_xywh = self.dboxes_xywh.cpu()
        self.dboxes_npu = self.dboxes.npu()
        self.dboxes_xywh_npu = self.dboxes_xywh.npu()
    def encode(self, bboxes_in, labels_in, criteria = 0.5):

        # try:
        # print("bboxes_in.shape, self.dboxes.shape", bboxes_in.shape, self.dboxes.shape)
        ious = calc_iou_tensor(bboxes_in, self.dboxes)
        best_dbox_ious, best_dbox_idx = ious.max(dim=0)
        best_bbox_ious, best_bbox_idx = ious.max(dim=1)

        # set best ious 2.0
        best_dbox_ious.index_fill_(0, best_bbox_idx, 2.0)

        idx = torch.arange(0, best_bbox_idx.size(0), dtype=torch.int64)
        best_dbox_idx[best_bbox_idx[idx]] = idx

        # filter IoU > 0.5
        masks = best_dbox_ious > criteria
        labels_out = torch.zeros(self.nboxes, dtype=torch.long).cpu()

        labels_out[masks] = labels_in[best_dbox_idx[masks]]
        bboxes_out = self.dboxes.clone()
        bboxes_out[masks, :] = bboxes_in[best_dbox_idx[masks], :]
        # Transform format to xywh format
        x, y, w, h = 0.5*(bboxes_out[:, 0] + bboxes_out[:, 2]), \
                    0.5*(bboxes_out[:, 1] + bboxes_out[:, 3]), \
                    -bboxes_out[:, 0] + bboxes_out[:, 2], \
                    -bboxes_out[:, 1] + bboxes_out[:, 3]
        bboxes_out[:, 0] = x
        bboxes_out[:, 1] = y
        bboxes_out[:, 2] = w
        bboxes_out[:, 3] = h
        # except:
        #     labels_out = torch.zeros(self.nboxes, dtype=torch.long)
        #     bboxes_out = torch.zeros(self.nboxes, 4)
        # print("bboxes_out.shape, labels_out.shape", bboxes_out.shape, labels_out.shape)
        return bboxes_out, labels_out

    def scale_back_batch(self, bboxes_in, scores_in):
        """
            Do scale and transform from xywh to ltrb
            suppose input Nx4xnum_bbox Nxlabel_numxnum_bbox
        """
        bboxes_in = bboxes_in.permute(0, 2, 1)
        scores_in = scores_in.permute(0, 2, 1)

        bboxes_in[:, :, :2] = self.scale_xy*bboxes_in[:, :, :2]
        bboxes_in[:, :, 2:] = self.scale_wh*bboxes_in[:, :, 2:]

        bboxes_in[:, :, :2] = bboxes_in[:, :, :2]*self.dboxes_xywh_npu[:, :, 2:] + self.dboxes_xywh_npu[:, :, :2]
        bboxes_in[:, :, 2:] = bboxes_in[:, :, 2:].exp()*self.dboxes_xywh_npu[:, :, 2:]

        # Transform format to ltrb
        l, t, r, b = bboxes_in[:, :, 0] - 0.5*bboxes_in[:, :, 2],\
                     bboxes_in[:, :, 1] - 0.5*bboxes_in[:, :, 3],\
                     bboxes_in[:, :, 0] + 0.5*bboxes_in[:, :, 2],\
                     bboxes_in[:, :, 1] + 0.5*bboxes_in[:, :, 3]

        bboxes_in[:, :, 0] = l
        bboxes_in[:, :, 1] = t
        bboxes_in[:, :, 2] = r
        bboxes_in[:, :, 3] = b

        return bboxes_in, F.softmax(scores_in, dim=-1)

    def decode_batch(self, bboxes_in, scores_in,  criteria = 0.45, max_output=200):
        bboxes, probs = self.scale_back_batch(bboxes_in, scores_in)
        N,A,C = probs.shape
        bboxes = bboxes.unsqueeze(-2).repeat([1,1,80,1])
        probs = probs[...,1:]
        nmsed_boxes, nmsed_scores, nmsed_classes, nmsed_num = torch_npu.npu_batch_nms(bboxes.half(), probs.half(),
                                                                                0.05, criteria,
                                                                                max_output, max_output)

        return nmsed_boxes, nmsed_classes, nmsed_scores

    # perform non-maximum suppression###############################################################
    def decode_single(self, bboxes_in, scores_in, criteria, max_output, max_num=200):
    #     # Reference to https://github.com/amdegroot/ssd.pytorch
        scores_in = scores_in.transpose(1, 0)
        #
        #     # Sort every row (in hopefully a single kernel launch)
        #     # NOTE: Not masked out things yet
        #     # NOTE: descending sort is easier to reason about
        #     # NOTE: Indices are to _global_ bboxes, we're not going to mask them
        score_sorted, score_sorted_idx = scores_in.sort(dim=1, descending=True)

        #     # Now generate the mask on the sorted scores
        mask = score_sorted > 0.05

        #     # number of default boxes per class that have a score > 0.05
        splits = mask.sum(dim=1).tolist()

        # only keep scores & indices for default boxes that contribute to this class
        # NOTE: Not masking out bboxes, all indices are global
        score_sorted = score_sorted[mask].split(splits)
        score_sorted_idx = score_sorted_idx[mask].split(splits)

        # assemble prefix sum of splits
        offsets = torch.tensor([0] + list(itertools.accumulate(splits)), dtype=torch.int32, device=bboxes_in.device)
        bboxes_out, scores_out, labels_out = C.nms(1, # N
                                                  scores_in.shape[0],
                                                  offsets,
                                                  torch.cat(score_sorted),
                                                  torch.cat(score_sorted_idx),
                                                  bboxes_in.contiguous(), # VITAL otherwise we get bad results :(
                                                  criteria,
                                                  max_num)

        _, max_ids = scores_out.sort(dim=0)
        max_ids = max_ids[-max_output:]
        return bboxes_out[max_ids, :], labels_out[max_ids], scores_out[max_ids]

def npu_multiclass_nms(multi_bboxes,
                       multi_scores,
                       score_thr=0.05,
                       nms_thr=0.5,
                       max_num=200,
                       max_output = 200,
                       score_factors=None):
    """NMS for multi-class bboxes using npu api.

    Origin implement from mmdetection is
    https://github.com/open-mmlab/mmdetection/blob/master/mmdet/core/post_processing/bbox_nms.py#L7

    This interface is similar to the original interface, but not exactly the same.

    Args:
        multi_bboxes (Tensor): shape (n, #class, 4) or (n, 4)
        multi_scores (Tensor): shape (n, #class+1), where the last column
            contains scores of the background class, but this will be ignored.
            On npu, in order to keep the semantics unblocked, we will unify the dimensions
        score_thr (float): bbox threshold, bboxes with scores lower than it
            will not be considered.
        nms_thr (float): NMS IoU threshold. In the original implementation, a dictionary of {"iou_threshold": 0.45}
            was passed, which is simplified here.
        max_num (int): if there are more than max_num bboxes after NMS,
            only top max_num will be kept; if there are less than max_num bboxes after NMS,
            the output will zero pad to max_num. On the NPU, the memory needs to be requested in advance,
            so the current max_num cannot be set to -1 at present
        score_factors (Tensor): The factors multiplied to scores before applying NMS

    Returns:
        tuple: (bboxes, labels), tensors of shape (k, 5) and (k, 1). Labels are 0-based.
    """

    num_classes = multi_scores.size(1)-1
    num_boxes = multi_scores.size(0)
    #print(num_classes,num_boxes)                     #####################
    if score_factors is not None:
        multi_scores = multi_scores[:, :-1] * score_factors[:, None]
    else:
        multi_scores = multi_scores[:, :-1]
    multi_bboxes = multi_bboxes.reshape(1, num_boxes, multi_bboxes.numel() // 4 // num_boxes, 4)
    multi_scores = multi_scores.reshape(1, num_boxes, num_classes)

    nmsed_boxes, nmsed_scores, nmsed_classes, nmsed_num = torch_npu.npu_batch_nms(multi_bboxes.half(),
                                                                                multi_scores.half(),
                                                                                score_thr, nms_thr,
                                                                                max_num, max_num)
    print(nmsed_boxes.shape, nmsed_scores.shape, nmsed_classes.shape, nmsed_num.shape)
    nmsed_boxes = nmsed_boxes.reshape(nmsed_boxes.shape[1:])
    nmsed_scores = nmsed_scores.reshape(nmsed_scores.shape[1])
    nmsed_classes = nmsed_classes.reshape(nmsed_classes.shape[1])
    #print(nmsed_boxes.shape,nmsed_scores.shape,nmsed_classes.shape)
    #return torch.cat([nmsed_boxes, nmsed_scores[:, None]], -1), nmsed_classes
    _, max_ids = nmsed_scores.sort(dim=0)
    max_ids = max_ids[-max_output:]
    #print(type(nmsed_classes))
    ones = torch.ones(200).npu()
    nmsed_classes = nmsed_classes + ones
    #print(nmsed_boxes[max_ids, :], nmsed_classes[max_ids], nmsed_scores[max_ids])
    #return nmsed_boxes, nmsed_classes, nmsed_scores
    return nmsed_boxes[max_ids, :], nmsed_classes[max_ids], nmsed_scores[max_ids]

def npu_batched_multiclass_nms(
        multi_bboxes,
        multi_scores,
        score_thr=0.05,
        nms_thr=0.5,
        max_num=200,
        score_factors=None):
    """NMS for batched multi-class bboxes using npu api.

    Origin implement from mmdetection is
    https://github.com/open-mmlab/mmdetection/blob/master/mmdet/core/post_processing/bbox_nms.py#L7

    This interface is similar to the original interface, but not exactly the same.
    This interface implements the nms method under batch.

    Args:
        multi_bboxes (Tensor): shape (bs, n, #class, 4) or (bs, n, 4)
        multi_scores (Tensor): shape (bs, n, #class+1), where the last column
            contains scores of the background class, but this will be ignored.
            On npu, in order to keep the semantics unblocked, we will unify the dimensions
        score_thr (float): bbox threshold, bboxes with scores lower than it
            will not be considered.
        nms_thr (float): NMS IoU threshold. In the original implementation, a dictionary of {"iou_threshold": 0.45}
            was passed, which is simplified here.
        max_num (int): if there are more than max_num bboxes after NMS,
            only top max_num will be kept; if there are less than max_num bboxes after NMS,
            the output will zero pad to max_num. On the NPU, the memory needs to be requested in advance,
            so the current max_num cannot be set to -1 at present
        score_factors (Tensor): The factors multiplied to scores before applying NMS

    Returns:
        tuple: (bboxes, labels), tensors of shape (bs, k, 5) and (bs, k, 1). Labels are 0-based.
    """

    num_classes = multi_scores.size(2) - 1
    num_boxes = multi_scores.size(1)
    batch_size = multi_scores.size(0)
    if score_factors is not None:
        multi_scores = multi_scores[..., :-1] * score_factors[..., None]
    else:
        multi_scores = multi_scores[..., :-1]
    multi_bboxes = multi_bboxes.reshape(batch_size, num_boxes, multi_bboxes.numel() // 4 // num_boxes // batch_size, 4)
    multi_scores = multi_scores.reshape(batch_size, num_boxes, num_classes)

    nmsed_boxes, nmsed_scores, nmsed_classes, nmsed_num = torch_npu.npu_batch_nms(multi_bboxes.half(),
                                                                                multi_scores.half(),
                                                                                score_thr, nms_thr,
                                                                                max_num, max_num)

    return torch.cat([nmsed_boxes, nmsed_scores[..., None]], -1), nmsed_classes, nmsed_scores

def dboxes300_coco():
    figsize = 300
    feat_size = [38, 19, 10, 5, 3, 1]
    steps = [8, 16, 32, 64, 100, 300]
    # use the scales here: https://github.com/amdegroot/ssd.pytorch/blob/master/data/config.py
    scales = [21, 45, 99, 153, 207, 261, 315]
    aspect_ratios = [[2], [2, 3], [2, 3], [2, 3], [2], [2]]
    dboxes = DefaultBoxes(figsize, feat_size, steps, scales, aspect_ratios)
    return dboxes

def build_ssd300_coder():
    return Encoder(dboxes300_coco())


import warnings
from copy import deepcopy
from typing import Any, List, Optional, Tuple, Union

import cirq
import numpy as np
import numpy.typing as npt
from cirq.value.linear_dict import _format_coefficient

from mitiq import QPROGRAM
from mitiq.interface import (
    CircuitConversionError,
    UnsupportedCircuitError,
    convert_to_mitiq,
)

class NoisyOperation:
    """An operation (or sequence of operations) which a noisy quantum computer
    can actually implement.p
    """

    def __init__(
        self,
        circuit: QPROGRAM,
        channel_matrix: Optional[npt.NDArray[np.complex64]] = None,
    ) -> None:
        """Initializes a NoisyOperation.

        Args:
            circuit: A short circuit which, when executed on a given noisy
                quantum computer, generates a noisy channel. It typically
                contains a single-gate or a short sequence of gates.
            channel_matrix: Superoperator representation of the noisy channel
                which is generated when executing the input ``circuit`` on the
                noisy quantum computer.

        Raises:
            TypeError: If ``ideal`` is not a ``QPROGRAM``.
        """
        self._native_circuit = deepcopy(circuit)

        try:
            cirq_circuit, native_type = convert_to_mitiq(circuit)
        except (CircuitConversionError, UnsupportedCircuitError):
            raise TypeError(
                "Failed to convert to an internal Mitiq representation"
                f"the input circuit:\n{type(circuit)}\n"
            )

        self._circuit = cirq_circuit
        self._native_type = native_type

        dimension = 2**self.num_qubits

        if channel_matrix is None:
            self._channel_matrix = None

        elif channel_matrix.shape != (
            dimension**2,
            dimension**2,
        ):
            raise ValueError(
                f"Arg `channel_matrix` has shape {channel_matrix.shape}"
                " but the expected shape is"
                f" {dimension ** 2, dimension ** 2}."
            )
        self._channel_matrix = deepcopy(channel_matrix)

    @property
    def circuit(self) -> cirq.Circuit:
        """Returns the circuit of the NoisyOperation as a Cirq circuit."""
        return self._circuit

    @property
    def native_circuit(self) -> QPROGRAM:
        """Returns the circuit used to initialize the NoisyOperation."""
        return self._native_circuit

    @property
    def qubits(self) -> Tuple[cirq.Qid, ...]:
        return tuple(self._circuit.all_qubits())

    @property
    def num_qubits(self) -> int:
        return len(self.qubits)

    @property
    def channel_matrix(self) -> npt.NDArray[np.complex64]:
        if self._channel_matrix is None:
            raise ValueError("The channel matrix is unknown.")
        return deepcopy(self._channel_matrix)

    def __add__(self, other: Any) -> "NoisyOperation":
        if not isinstance(other, NoisyOperation):
            raise ValueError(
                f"Arg `other` must be a NoisyOperation but was {type(other)}."
            )

        if self.qubits != other.qubits:
            raise NotImplementedError

        if self._channel_matrix is None or other._channel_matrix is None:
            matrix = None
        else:
            matrix = other._channel_matrix @ self._channel_matrix

        return NoisyOperation(self._circuit + other._circuit, matrix)

    def __str__(self) -> str:
        return self._native_circuit.__str__()

class NoisyBasis:
    """A set of noisy operations which a quantum computer can actually
    implement, assumed to form a basis of n-qubit unitary matrices.

    This class has been removed since Mitiq v0.24.0.
    """

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        raise NotImplementedError(
            "The NoisyBasis class has been removed since Mitiq v0.24.0."
            " Please replace it with a list of NoisyOperation objects."
        )

class OperationRepresentation:
    """A decomposition (basis expansion) of an operation or sequence of
    operations in a basis of noisy, implementable operations.
    """

    def __init__(
        self,
        ideal: QPROGRAM,
        noisy_operations: List[NoisyOperation],
        coeffs: List[float],
        is_qubit_dependent: bool = True,
    ) -> None:
        """Initializes an OperationRepresentation.

        Args:
            ideal: The ideal operation desired to be implemented.
            basis_expansion: Representation of the ideal operation in a basis
                of `NoisyOperation`s.
            is_qubit_dependent: If True, the representation
                corresponds to the operation on the specific qubits defined in
                `ideal`. If False, the representation is valid for the same
                gate even if acting on different qubits from those specified in
                `ideal`.

        Raises:
            TypeError: If all keys of `basis_expansion` are not instances of
                `NoisyOperation`s.
        """
        if not all(isinstance(o, NoisyOperation) for o in noisy_operations):
            raise TypeError(
                "All elements of `noisy_operations` must be "
                "of type `NoisyOperation`."
            )

        if not all(isinstance(c, float) for c in coeffs):
            raise TypeError("All elements of `coeffs` must be floats.")

        self._native_ideal = deepcopy(ideal)
        self._ideal, self._native_type = convert_to_mitiq(ideal)
        self._noisy_operations = noisy_operations
        self._coeffs = coeffs
        self._norm = sum(abs(c) for c in coeffs)
        self._distribution = [abs(c) / self._norm for c in coeffs]
        self.is_qubit_dependent = is_qubit_dependent
        self._validate()

    def _validate(self) -> None:
        """Validates initialization arguments."""
        if len(self._noisy_operations) != len(self._coeffs):
            raise ValueError(
                "`noisy_operations` and `coeffs` must have equal length"
                f" but {len(self._noisy_operations)}!={len(self._coeffs)}."
            )
        if not np.isclose(sum(self._coeffs), 1.0, atol=10**-4):
            warnings.warn("The sum of the coefficients is different from 1.")
        for op in self._noisy_operations:
            if self._ideal.all_qubits() != op.circuit.all_qubits():
                raise ValueError(
                    "The operation to represent acts on"
                    f" {self._ideal.all_qubits()}. Noisy operations"
                    f" must act on the same qubits but {op} acts on:"
                    f" {op.circuit.all_qubits()}"
                )

    @property
    def ideal(self) -> cirq.Circuit:
        return self._ideal

    @property
    def basis_expansion(self) -> List[Tuple[float, NoisyOperation]]:
        return [(c, o) for c, o in zip(self._coeffs, self._noisy_operations)]

    @property
    def noisy_operations(self) -> List[NoisyOperation]:
        return self._noisy_operations

    @property
    def coeffs(self) -> List[float]:
        """Returns the coefficients of the quasi-probability distribution."""
        return self._coeffs

    @property
    def norm(self) -> float:
        """Returns the 1-norm of the quasi-probability distribution."""
        return self._norm

    @property
    def distribution(self) -> List[float]:
        """Returns the probability distribution obtained from taking
        the absolute value and normalizing the quasi-probability distribution.
        """
        return self._distribution

    def sample(
        self, random_state: Optional[Union[int, np.random.RandomState]] = None
    ) -> Tuple[NoisyOperation, int, float]:
        """Returns a randomly sampled NoisyOperation from the basis expansion.

        Args:
            random_state: Defines the seed for sampling if provided.
        """
        if not random_state:
            rng = np.random
        elif isinstance(random_state, int):
            rng = np.random.RandomState(random_state)  # type: ignore
        elif isinstance(random_state, np.random.RandomState):
            rng = random_state  # type: ignore
        else:
            raise TypeError(
                "Arg `random_state` should be of type `np.random.RandomState` "
                f"or `int`, but was {type(random_state)}."
            )

        idx = rng.choice(len(self.coeffs), p=self.distribution)
        coeff, noisy_op = self.basis_expansion[idx]
        return noisy_op, int(np.sign(coeff)), coeff

    def __str__(self) -> str:
        lhs = str(self._ideal) + " = "
        rhs = ""
        for c, circ in zip(self.coeffs, self.noisy_operations):
            c_str = _format_coefficient(".3f", c)
            if c_str:
                if c_str[0] not in ["+", "-"]:
                    c_str = "+" + c_str
                if len(self._ideal.all_qubits()) == 1:
                    # Print single-qubit circuits horizontally
                    rhs += f"{c_str}*({circ!s})"
                else:
                    # Print multi-qubit circuits vertically
                    rhs += "\n\n" + f"{c_str}\n{circ!s}"
        # Handle special cases as in cirq.value.linear_dict._format_terms()
        if not rhs:
            rhs = f"{0:.3f}"
        # Remove "+" in the first term of a single-qubit representation
        if rhs[0] == "+":
            rhs = rhs[1:]
        # Remove "+" in the first term of a multi-qubit representation
        if rhs[0:3] == "\n\n+":
            rhs = "\n\n" + rhs[3:]
        return lhs + rhs

    def __eq__(self, other: Any) -> bool:
        """Checks if two representations are equivalent. This function returns
        True if the representations have the same ideal operation, the same
        coefficients and equivalent NoisyOperation(s) (same gates but not
        necessarily same channel_matrix since channel_matrix is optional).
        """
        if self.is_qubit_dependent != other.is_qubit_dependent:
            return False

        if self._native_type != other._native_type:
            return False

        if not self._ideal == other._ideal:
            return False

        if len(self.basis_expansion) != len(other.basis_expansion):
            return False

        for c_a, op_a in self.basis_expansion:
            found = False
            for c_b, op_b in other.basis_expansion:
                if op_a._circuit == op_b._circuit:
                    found = True
                    break
            if not found:
                return False
            if not np.isclose(c_a, c_b):
                return False
        return True


import serial
import time
import sqlite3

# Sensor Keys
# 1 = AM2320 humid; 2 = AM2320 temp; 3 = MQ4; 4 = MQ7; 5 = BMP180 pressure;
# 6 = BMP180 temp; 7 = soil temp; 8 = soil moisture

class Getnanoreadings:

	def __init__(self):
		self.ser = self.connect_to_nano()

	@staticmethod
	def connect_to_nano():
		try:
			ser = serial.Serial('/dev/ttyUSB0', 9600)
		except Exception as e:
			print('not on USB0, trying USB1')
			ser = serial.Serial('/dev/ttyUSB1', 9600)
		time.sleep(2)
		return ser

	def read_sensor(self, sensor_key):
		self.ser.write(str(sensor_key).encode())
		time.sleep(1)
		output = self.ser.readline()
		return output.decode().rstrip()

	def data_clean(self, curr_data_point, past_data_point):
		if not 0 <= curr_data_point <= 100:
			x = past_data_point
		else:
			x = curr_data_point
		low = past_data_point - past_data_point * 0.1
		high = past_data_point + past_data_point * 0.1
		if not low <= curr_data_point <= high:
			x = past_data_point
		else:
			x = curr_data_point
		return x

	def sql_insert(self):
		data = []
		data.append(time.ctime())
		print('reading data')
		try:
			conn = sqlite3.connect('data.db')
		except Exception as e:
			print(e)
			conn = sqlite3.connect('/home/pi/Documents/Garden-Monitor/data.db')
		# c = conn.cursor()
		# c.execute('SELECT * FROM from_nano ORDER BY ROWID DESC LIMIT 1')
		# last_data = c.fetchone()
		for i in range(8):
			curr_data = self.read_sensor(i + 1)
			# data_point = self.data_clean(curr_data, last_data)
			data.append(curr_data)
			print(curr_data)
		print('inserting data')
		c = conn.cursor()
		c.execute(
			"INSERT INTO from_nano(datatime, humidity, air_temp1, methane_levels, CO_levels, pressure, air_temp2, soil_temp, soil_moisture) VALUES(?,?,?,?,?,?,?,?,?)",
			data)
		conn.commit()
		print('finished')
		conn.close()


import matplotlib.pyplot as plt
import numpy as np
from matplotlib.font_manager import FontProperties  # 字体管理器
import matplotlib.patheffects as pe
import matplotlib.cm as cm
import math

from numpy.core.fromnumeric import size

class DrawTransCDF():
    def get_tag(self, num):
        if self.item_type == 'NULL':  # 留空判定
                return ''
        if self.en_switch:
            if self.item_type == "Character":
                return 'C' + str(num-1)
            if self.item_type == "Type":
                if num == self.total_item_types:
                    return 'ALL'
                return str(num)
            if self.item_type == 'Weapon':
                return 'R' + str(num)
            return 'TYPE SET ERROR!'
        if self.item_type == "Character":
            return str(num-1) + '命'
        if self.item_type == "Type":
            if num == self.total_item_types:
                return '集齐'
            return str(num) + '种'
        if self.item_type == 'Weapon':
            return str(num) + '精'
        return 'TYPE SET ERROR!'

    def plot_img(self, cdf_ans, format='png'):
        data_size = np.shape(cdf_ans)
        calc_pull = data_size[1]

        self.fig_size = [6, max(self.fig_size_y, math.log(calc_pull))]
        x_tick = 1/self.tick_number
        y_tick = self.tick_step*int(round(calc_pull/(self.tick_step*self.fig_size[1]/self.fig_size[0]*self.tick_number)))

        fig = plt.figure(dpi=self.img_dpi, figsize=self.fig_size)
        ax = fig.gca()

        # 设置网格刻度和画图范围

        ax.set_xticks(np.arange(0, 1.01, x_tick))
        ax.set_yticks(np.arange(0, int(calc_pull/y_tick)*y_tick+1, y_tick))
        # The Ultimate Guide To Set Aspect Ratio in Matplotlib https://www.pythonpool.com/matplotlib-aspect-ratio/
        # ax.set_aspect((1.1/(calc_pull+y_tick))*((calc_pull+y_tick)/y_tick)/(1.1/x_tick)) # ((calc_pull+y_tick)/y_tick)/((1.1)/x_tick)

        plt.xlim(-0.05, 1.05)
        plt.ylim(-y_tick/2, calc_pull+y_tick/2)

        # https://stackoverflow.com/questions/9127434/how-to-create-major-and-minor-gridlines-with-different-linestyles-in-python
        plt.grid(b=True, which='major', color='lightgray', linestyle='-', linewidth=1)
        plt.grid(b=True, which='minor', color='lightgray', linestyle='-', linewidth=0.5)
        plt.minorticks_on()  # https://stackoverflow.com/questions/9127434/how-to-create-major-and-minor-gridlines-with-different-linestyles-in-python

        # 设置字体偏置
        if self.open_mark_bias:
            x_bias = 1/self.bias_rate
            y_bias = calc_pull/self.bias_rate
        else:
            x_bias = 0
            y_bias = 0

        # 绘制图线
        for i in range(1, min(data_size[0], self.item_num+1)):
            plt.plot(   cdf_ans[i][1:calc_pull],
                        range(1, calc_pull),
                        # label='theory',
                        linewidth=2,
                        c=self.line_colors[i-1])
            for j in range(calc_pull):
                for each_pos in self.attention_pos:
                    if cdf_ans[i][j] >= each_pos >= cdf_ans[i][j-1]:
                        loc_x_bias = self.x_bias_num*x_bias
                        loc_y_bias = self.y_bias_num*y_bias
                        # 这里打的点实际上不是真的点，是为了图像好看而插值到直线上的
                        plt.scatter(each_pos, (each_pos-cdf_ans[i][j-1])/(cdf_ans[i][j]-cdf_ans[i][j-1])+j-1,
                                    color=self.line_colors[i-1],
                                    s=1,
                                    zorder=self.item_num+1,
                                    path_effects=[pe.withStroke(linewidth=2, foreground="white")])
                        # 文字描边 https://stackoverflow.com/questions/25426599/matplotlib-how-to-buffer-label-text/25428107
                        # 官方文档 https://matplotlib.org/stable/tutorials/advanced/patheffects_guide.html
                        plt.text(each_pos+loc_x_bias, j+loc_y_bias, str(j),
                                fontproperties=self.text_font,
                                path_effects=[pe.withStroke(linewidth=self.font_stroke_width, foreground="white")])
                        # 标记%和对应竖虚线
                        if i == min(data_size[0], self.item_num+1)-1:
                            plt.plot([each_pos, each_pos], [-y_tick/2, j+y_tick*2], c='gray', linewidth=1.5, linestyle=':')
                            plt.text(each_pos-8*x_bias, j+y_tick*1.7, str(int(each_pos*100))+"%",
                                    c='gray',
                                    fontproperties=self.mark_font,
                                    path_effects=[pe.withStroke(linewidth=self.font_stroke_width, foreground="white")])
                        # 50%处标注说明文字
                        if each_pos == 0.5:
                            put_tag = self.get_tag(i)
                            plt.text(each_pos+loc_x_bias+self.mid_bias_num*x_bias, j+loc_y_bias, put_tag,
                                    c=self.line_colors[i-1],
                                    fontproperties=self.mark_font,
                                    path_effects=[pe.withStroke(linewidth=self.font_stroke_width, foreground="white")])
        plt.title(self.img_title, fontproperties=self.title_font)
        plt.xlabel(self.xlabel, fontproperties=self.text_font)
        plt.ylabel(self.ylabel, fontproperties=self.text_font)
        plt.text(0, calc_pull, self.img_description+self.auther, c='#B0B0B0',
                fontproperties=self.mark_font,
                path_effects=[pe.withStroke(linewidth=self.font_stroke_width, foreground="white")],
                horizontalalignment='left',
                verticalalignment='top')
        if self.save_img:
            plt.savefig('./fig/'+self.img_name+'.'+format, bbox_inches='tight' , pad_inches=0.15)  #
        if self.show_img:
            plt.show()

    def __init__(self):
        self.img_dpi = 100
        self.fig_size_y = 7
        self.back_line_color = "lightgray"
        self.attention_pos = [0.1, 0.25, 0.5, 0.75, 0.9, 0.99]
        self.item_type = "Character"
        self.img_title = "Cumulative Distribution Function"
        self.img_name = "Cumulative Distribution Function"

        self.item_num = 7               # 绘制抽取物品数量 7时意味着 1100抽后才会开始自动延长
        self.total_item_types = 5       # 物品种类
        self.save_img = 0               # 是否保存图片
        self.show_img = 1               # 是否显示图片
        self.tick_step = 50             # y方向刻度对齐数值
        self.tick_number = 10           # x方向刻度个数
        # 字体偏移量及其它参数
        self.font_stroke_width = 1.5    # 字体描边大小
        self.open_mark_bias = 1         # 是否开启字体位置偏置
        self.bias_rate = 100            # 字体偏置比例
        self.x_bias_num = -5.8          # x方向偏移量
        self.y_bias_num = 0.5           # y方向偏移量
        self.mid_bias_num = -5.5        # 中部偏移量
        # 设置绘图字体
        self.text_font = FontProperties(fname=r"./fonts/SourceHanSansSC-Medium.otf", size=10)
        self.title_font = FontProperties(fname=r"./fonts/SourceHanSansSC-Bold.otf", size=15)
        self.mark_font = FontProperties(fname=r"./fonts/SourceHanSansSC-Bold.otf", size=10)
        # 设置图示语言
        self.en_switch = 0
        self.xlabel = "抽到概率"
        self.ylabel = "投入抽数"
        self.auther = "@一棵平衡树"
        # 设置线条颜色
        # https://stackoverflow.com/questions/12236566/setting-different-color-for-each-series-in-scatter-plot-on-matplotlib
        self.line_colors = cm.Blues(np.linspace(0.5, 0.9, self.item_num))

        # 设置额外说明
        self.img_description = ''

def plot_distribution(D, suptitle=None):
    # 输入的D为一维数组，从0开始
    end_pull = len(D)

    # 导入字体
    text_font = FontProperties(fname=r"./fonts/SourceHanSansSC-Medium.otf", size=10)
    title_font = FontProperties(fname=r"./fonts/SourceHanSansSC-Bold.otf", size=10)
    suptitle_font = FontProperties(fname=r"./fonts/SourceHanSansSC-Bold.otf", size=15)
    mark_font = FontProperties(fname=r"./fonts/SourceHanSansSC-Bold.otf", size=10)

    # 自适应偏移量
    x_bias = end_pull / 100
    y_bias = 1/ 100

    # 绘图大小
    plt.figure(figsize=(9, 8))

    # 设置大图标题
    if suptitle != None:
        plt.suptitle(suptitle, fontproperties=suptitle_font)

    # 上图为累积分布函数
    plt.subplot(211)
    DD = D.cumsum()
    plt.plot(range(end_pull), DD)
    plt.title('累积分布函数', fontproperties=title_font)
    plt.xlabel('抽数', fontproperties=text_font)
    plt.ylabel('累积概率', fontproperties=text_font)
    attention_pos = [0.1, 0.25, 0.5, 0.75, 0.9, 0.99]
    for i in range(end_pull):   # 设置标记
        for each_pos in attention_pos:
            if DD[i] >= each_pos >= DD[i-1]:
                # 打点标记
                plt.scatter(i, DD[i], s=5, zorder=10, color='slateblue',
                            path_effects=[pe.withStroke(linewidth=2, foreground="white")])
                # 标记%和对应竖虚线
                plt.axvline(x=i, c="lightgray", ls="--", lw=1, zorder=0)
                plt.text(i+2.5*x_bias, each_pos-5*y_bias, str(i)+'抽  '+str(int(each_pos*100))+"%",
                        c='gray',
                        fontproperties=mark_font,
                        path_effects=[pe.withStroke(linewidth=2, foreground="white")])

    # 下图为分布列
    plt.subplot(212)
    plt.plot(range(end_pull), D, c='salmon')
    plt.fill_between(range(end_pull), D, np.zeros(end_pull), alpha=0.2, color='salmon')
    plt.title('分布列', fontproperties=title_font)
    plt.xlabel('抽数', fontproperties=text_font)
    plt.ylabel('本抽概率', fontproperties=text_font)

    x0 = np.arange(end_pull)
    expectation = (x0*D).sum()
    # 标记期望和对应竖虚线
    plt.axvline(x=expectation, c="lightgray", ls="--", lw=1, zorder=0)
    plt.text(expectation+1*x_bias, D.max()/2, '期望:'+str(round(expectation, 2))+'抽',
            c='gray',
            fontproperties=mark_font,
            path_effects=[pe.withStroke(linewidth=2, foreground="white")])
    # 标记分布峰值和对应竖虚线
    max_pos = D.argmax()
    y_pos_const = 0.75
    if (max_pos-expectation)/end_pull > 0.01:
        plt.axvline(x=max_pos, c="lightgray", ls="--", lw=1, zorder=0)
        y_pos_const = 0.5
    plt.text(max_pos+1*x_bias, y_pos_const*D.max(), '峰值:'+str(max_pos)+'抽',
            c='gray',
            fontproperties=mark_font,
            path_effects=[pe.withStroke(linewidth=2, foreground="white")])
    plt.tight_layout()
    plt.show()

if __name__ =='__main__':
    pass


#!/usr/bin/env python
# -*- coding:utf-8 -*-

"""
积分墙相关 数据 记录各个设备激活与否的相关信息：
    {'ts': 0,
    'source': 'yijifen',                            # 广告平台来源
    'callback': 'http://www.baidu.com',             # 回调地址
    'deviceid': '0C74C2E9F2D0'                      # 设备标识
    'idfa': '99AD5E51-1164-47E4-BF0F-69646AE8F5B6', # 设备标识
    'mac': '0C74C2E9F2D0',                          # 设备标识
    'activate': False,                              # 是否激活
    'uids': {                                       # qiku 腾讯云平台才有的字段，标记用户达到的阶段
        uid: [step1, step2, step3]
        }
    }
"""
import urllib
import time
import settings
import game_config

from models.code import Adver
from lib.utils import http
from logics.user import User as UserL
# from logics.gift import add_gift

URL = 'https://itunes.apple.com/us/app/chao-ji-ying-xiong/id683031739?l=zh&ls=1&mt=8'
YOUXIDUO_AD_URL = 'https://itunes.apple.com/cn/app/chao-ji-ying-xiong/id683031739?mt=8'
APPID = '683031739'

def ad_callback(env, lv=2):
    """游戏内回调各种广告平台
    """
    device_mark = env.get_argument('device_mark', '')
    info = Adver.get(device_mark)
    if info and not info['activate']:
        callback_url = info['callback']
        need_lv = game_config.scorewall.get(info['source'], lv)
        need_lv = 2 if need_lv < 2 else 5 if need_lv > 5 else need_lv
        if lv != need_lv:
            return
        if callback_url:
            activateip = env.request.headers.get('X-Real-Ip', '')
            acts = time.time()
            info['acts'] = int(acts)
            info['activateip'] = activateip

            if info['source'] == 'anwo':
                callback_url += '&acts=%(acts)s&activateip=%(activateip)s' % {'acts': int(acts * 1000), 'activateip': activateip}
            try:
                rc, data = http.get(callback_url, timeout=2)
            except:
                rc = 599
            if rc == 200:
                info['activate'] = True
                Adver.activate(device_mark, info)

def update_qiku_mid_info(user, mid=None):
    """
    更新设备激活
    """
    mid = mid or user.mid_qiku
    mac = user.device_mark
    info = Adver.get(mid)
    if not info:
        info = {
            'ts': time.time(),
            'source': 'qiku',
            'callback': '',
            'deviceid': mac,
            'idfa': mid,
            'mac': mac,
            'activate': False,
            'openudid': '',
            'uids': {
                # uid: [step1, step2, step3]
            }
        }
    if user.uid not in info['uids']:
        info['uids'][user.uid] = []
    Adver.update(mid, info)


# -*- coding:utf-8 -*-
from typing import List

class Solution:
    def findSubsequences(self, nums: List[int]) -> List[List[int]]:
        nums_len = len(nums)
        used = [False for i in range(nums_len)]
        ans = []

        from copy import deepcopy
        def dfs(idx, path):
            path_len = len(path)
            print(path)
            if path_len >= 2:
                tmp = deepcopy(path)
                tmp_sort = sorted(tmp)

                if tmp_sort == tmp and path not in ans:
                    ans.append(deepcopy(path))

            for i in range(idx, nums_len):
                # print(i, i - 1, nums[i], nums[i - 1])
                # if i > 0 and nums[i] == nums[i - 1] and not used[i - 1]: continue

                used[i] = True
                path.append(nums[i])
                dfs(i + 1, path)
                used[i] = False
                path.pop()

        dfs(0, [])
        return ans

if __name__ == '__main__':
    nums = [4, 6, 7, 7]
    # nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 1, 1, 1, 1]
    ans = Solution().findSubsequences(nums)
    print(ans)


import cv2
import numpy as np

orb = cv2.ORB_create()
# detection
frame1 = cv2.imread("Images/1.png")
frame2 = cv2.imread("Images/2.png")
pts1 = cv2.goodFeaturesToTrack(np.mean(frame1, axis=2).astype(np.uint8), 3000, qualityLevel=0.01, minDistance=7)
pts2 = cv2.goodFeaturesToTrack(np.mean(frame2, axis=2).astype(np.uint8), 3000, qualityLevel=0.01, minDistance=7)

kps1 = [cv2.KeyPoint(x=f[0][0], y=f[0][1], _size=20) for f in pts1]
kps1, des1 = orb.compute(frame1, kps1)

kps2 = [cv2.KeyPoint(x=f[0][0], y=f[0][1], _size=20) for f in pts2]
kps2, des2 = orb.compute(frame2, kps2)

bf = cv2.BFMatcher(cv2.NORM_HAMMING)
matches = bf.knnMatch(des1, des2, k=2)
print(matches)


"""Generic functionality related to files and I/O"""
import csv
import email.parser
import email.policy
import hashlib
import os
import re
import zipfile
from dataclasses import dataclass, field
from pathlib import Path
from typing import Iterable, Optional, Pattern, Sequence

@dataclass
class ArchiveMapping:
    """A mapping between an archive file name and its corresponding source filesystem path"""
    source_file_name: Path
    archive_file_name: Path

@dataclass
class FunctionLayerMappings:
    """A function and requirements layer mapping and digest container"""
    function_mappings: Sequence[ArchiveMapping] = field(default=list)
    function_digest: str = None
    requirements_mappings: Sequence[ArchiveMapping] = field(default=list)
    requirements_digest: str = None

def format_file_size(size_in_bytes: float) -> str:
    """Return a string representation of the specified size as its largest 2^10 representation

    Examples:
        >>> format_file_size(2048)
        '2.00 KiB'
        >>> format_file_size(16252928.0)
        '15.50 MiB'

    Args:
        size_in_bytes: a size in bytes

    Returns: a string representation of the specified size as its largest 2^10 representation"""
    for unit in ('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB'):
        if abs(size_in_bytes) < 1024.0:
            return f'{size_in_bytes:.2f} {unit}'
        size_in_bytes /= 1024.0
    return f'{size_in_bytes:.2f} YiB'

def get_digest(source_file_names: Sequence[Path], block_size: int = 8192) -> Optional[str]:
    """Return a SHA256 hash composed from the content of all source files.

    Args:
        source_file_names: A sequence of source file paths

    Returns: A SHA256 hash composed from the content of all source files."""
    # See the PEP-376 RECORD file specification: <https://www.python.org/dev/peps/pep-0376/#record>
    package_record_pattern = re.compile(r'\.dist-info/RECORD$')
    egg_information_pattern = re.compile(r'\.egg-info/PKG-INFO$')
    digest = hashlib.sha256()
    full = set(source_file_names)
    done = set()
    if not full:
        return None
    for source_file_name in sorted(full):
        if package_record_pattern.search(str(source_file_name)):
            package_parent_path = source_file_name.parent.parent
            with open(source_file_name, 'r', buffering=block_size) as record:
                reader = csv.reader(record, delimiter=',', quotechar='"', lineterminator=os.linesep)
                for item in reader:
                    item_name, item_hash, _other = item[:3]
                    source_file_name = package_parent_path / item_name
                    if item_hash and source_file_name in full:
                        digest.update((str(item_name) + item_hash).encode())
                        done.add(source_file_name)
    remaining = full - done
    for source_file_name in sorted(remaining):
        with open(source_file_name, 'rb', buffering=block_size) as source_file:
            if egg_information_pattern.search(str(source_file_name)):
                # Ensure deterministic field order from PKG-INFO files
                # See: https://www.python.org/dev/peps/pep-0314/#including-metadata-in-packages
                parser = email.parser.BytesHeaderParser(policy=email.policy.default)
                source_headers = sorted(parser.parse(source_file).items())
                for header, value in source_headers:
                    digest.update(header.encode())
                    digest.update(value.encode())
            else:
                digest.update(source_file.read())

    return digest.hexdigest()

def get_relative_file_names(source_path: Path, exclude_patterns: Sequence[Pattern] = None) -> Iterable[Path]:
    """Return an unsorted iterable of files recursively beneath the source path

    Args:
        source_path: a filesystem path from which to recursively iterate all files
        exclude_patterns: an optional sequence of regular expressions which will be used to exclude files

    Returns: an unsorted iterable of files recursively beneath the source path"""
    exclude_patterns = exclude_patterns or []
    for root, _directory_names, file_names in os.walk(source_path):
        for file_name in file_names:
            relative_file_name = Path(os.path.join(root, file_name)).relative_to(source_path)
            if not any([pattern.match(str(relative_file_name)) for pattern in exclude_patterns]):
                yield relative_file_name

def write_archive(archive_file_name: Path, archive_mappings: Iterable[ArchiveMapping]) -> None:
    """Write a zip file archive composed of the specified archive file mappings

    Args:
        archive_file_name: a writable file
        archive_mappings: an iterable of mappings of filesystem file names to archive file names"""
    with zipfile.ZipFile(archive_file_name, 'w', compression=zipfile.ZIP_DEFLATED, compresslevel=9) as archive:
        for mapping in archive_mappings:
            archive.write(filename=mapping.source_file_name, arcname=mapping.archive_file_name)


import sys
from cx_Freeze import setup, Executable

target_script = "GUI.py"

# Create an instance of the Executable class
executable = Executable(
    script=target_script,
    base="Win32GUI",  # "Win32GUI" for a GUI application on Windows
    icon=None   # Specify the path to icon file
)

# Define additional options for the setup
options = {
    "build_exe": {
        "includes": ["tkinter", "serial"],
    }
}

# Use the setup() function to create the executable
setup(
    name="Your Application Name",
    version="1.0",
    description="Description of your application",
    executables=[executable],
    options=options
)


# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from openstack.cloud import _utils
from openstack.cloud import exc

class CoeCloudMixin:
    def list_coe_clusters(self):
        """List COE (Container Orchestration Engine) cluster.

        :returns: A list of container infrastructure management ``Cluster``
            objects.
        :raises: ``OpenStackCloudException``: if something goes wrong during
            the OpenStack API call.
        """
        return list(self.container_infrastructure_management.clusters())

    def search_coe_clusters(self, name_or_id=None, filters=None):
        """Search COE cluster.

        :param name_or_id: cluster name or ID.
        :param filters: a dict containing additional filters to use.
        :param detail: a boolean to control if we need summarized or
            detailed output.

        :returns: A list of container infrastructure management ``Cluster``
            objects.
        :raises: ``OpenStackCloudException``: if something goes wrong during
            the OpenStack API call.
        """
        coe_clusters = self.list_coe_clusters()
        return _utils._filter_list(coe_clusters, name_or_id, filters)

    def get_coe_cluster(self, name_or_id, filters=None):
        """Get a COE cluster by name or ID.

        :param name_or_id: Name or ID of the cluster.
        :param filters:
            A dictionary of meta data to use for further filtering. Elements
            of this dictionary may, themselves, be dictionaries. Example::

                {
                    'last_name': 'Smith',
                    'other': {
                        'gender': 'Female'
                    }
                }

            OR
            A string containing a jmespath expression for further filtering.
            Example:: "[?last_name==`Smith`] | [?other.gender]==`Female`]"

        :returns: A container infrastructure management ``Cluster`` object if
            found, else None.
        """
        return _utils._get_entity(self, 'coe_cluster', name_or_id, filters)

    def create_coe_cluster(
        self,
        name,
        cluster_template_id,
        **kwargs,
    ):
        """Create a COE cluster based on given cluster template.

        :param string name: Name of the cluster.
        :param string cluster_template_id: ID of the cluster template to use.
        :param dict kwargs: Any other arguments to pass in.

        :returns: a dict containing the cluster description
        :returns: The created container infrastructure management ``Cluster``
            object.
        :raises: ``OpenStackCloudException`` if something goes wrong during
            the OpenStack API call
        """
        cluster = self.container_infrastructure_management.create_cluster(
            name=name,
            cluster_template_id=cluster_template_id,
            **kwargs,
        )

        return cluster

    def delete_coe_cluster(self, name_or_id):
        """Delete a COE cluster.

        :param name_or_id: Name or unique ID of the cluster.
        :returns: True if the delete succeeded, False if the
            cluster was not found.

        :raises: OpenStackCloudException on operation error.
        """

        cluster = self.get_coe_cluster(name_or_id)

        if not cluster:
            self.log.debug(
                "COE Cluster %(name_or_id)s does not exist",
                {'name_or_id': name_or_id},
                exc_info=True,
            )
            return False

        self.container_infrastructure_management.delete_cluster(cluster)
        return True

    def update_coe_cluster(self, name_or_id, **kwargs):
        """Update a COE cluster.

        :param name_or_id: Name or ID of the COE cluster being updated.
        :param kwargs: Cluster attributes to be updated.

        :returns: The updated cluster ``Cluster`` object.

        :raises: OpenStackCloudException on operation error.
        """
        cluster = self.get_coe_cluster(name_or_id)
        if not cluster:
            raise exc.OpenStackCloudException(
                "COE cluster %s not found." % name_or_id
            )

        cluster = self.container_infrastructure_management.update_cluster(
            cluster, **kwargs
        )

        return cluster

    def get_coe_cluster_certificate(self, cluster_id):
        """Get details about the CA certificate for a cluster by name or ID.

        :param cluster_id: ID of the cluster.

        :returns: Details about the CA certificate for the given cluster.
        """
        return (
            self.container_infrastructure_management.get_cluster_certificate(
                cluster_id
            )
        )

    def sign_coe_cluster_certificate(self, cluster_id, csr):
        """Sign client key and generate the CA certificate for a cluster

        :param cluster_id: UUID of the cluster.
        :param csr: Certificate Signing Request (CSR) for authenticating
            client key.The CSR will be used by Magnum to generate a signed
            certificate that client will use to communicate with the cluster.

        :returns: a dict representing the signed certs.

        :raises: OpenStackCloudException on operation error.
        """
        return self.container_infrastructure_management.create_cluster_certificate(  # noqa: E501
            cluster_uuid=cluster_id, csr=csr
        )

    def list_cluster_templates(self, detail=False):
        """List cluster templates.

        :param bool detail. Ignored. Included for backwards compat.
            ClusterTemplates are always returned with full details.

        :returns: a list of dicts containing the cluster template details.

        :raises: ``OpenStackCloudException``: if something goes wrong during
            the OpenStack API call.
        """
        return list(
            self.container_infrastructure_management.cluster_templates()
        )

    def search_cluster_templates(
        self, name_or_id=None, filters=None, detail=False
    ):
        """Search cluster templates.

        :param name_or_id: cluster template name or ID.
        :param filters: a dict containing additional filters to use.
        :param detail: a boolean to control if we need summarized or
            detailed output.

        :returns: a list of dict containing the cluster templates

        :raises: ``OpenStackCloudException``: if something goes wrong during
            the OpenStack API call.
        """
        cluster_templates = self.list_cluster_templates(detail=detail)
        return _utils._filter_list(cluster_templates, name_or_id, filters)

    def get_cluster_template(self, name_or_id, filters=None, detail=False):
        """Get a cluster template by name or ID.

        :param name_or_id: Name or ID of the cluster template.
        :param filters:
            A dictionary of meta data to use for further filtering. Elements
            of this dictionary may, themselves, be dictionaries. Example::

                {
                    'last_name': 'Smith',
                    'other': {
                        'gender': 'Female'
                    }
                }

            OR
            A string containing a jmespath expression for further filtering.
            Example:: "[?last_name==`Smith`] | [?other.gender]==`Female`]"

        :returns: A cluster template dict or None if no matching
            cluster template is found.
        """
        return _utils._get_entity(
            self,
            'cluster_template',
            name_or_id,
            filters=filters,
            detail=detail,
        )

    def create_cluster_template(
        self, name, image_id=None, keypair_id=None, coe=None, **kwargs
    ):
        """Create a cluster template.

        :param string name: Name of the cluster template.
        :param string image_id: Name or ID of the image to use.
        :param string keypair_id: Name or ID of the keypair to use.
        :param string coe: Name of the coe for the cluster template.
            Other arguments will be passed in kwargs.

        :returns: a dict containing the cluster template description

        :raises: ``OpenStackCloudException`` if something goes wrong during
            the OpenStack API call
        """
        cluster_template = (
            self.container_infrastructure_management.create_cluster_template(
                name=name,
                image_id=image_id,
                keypair_id=keypair_id,
                coe=coe,
                **kwargs,
            )
        )

        return cluster_template

    def delete_cluster_template(self, name_or_id):
        """Delete a cluster template.

        :param name_or_id: Name or unique ID of the cluster template.
        :returns: True if the delete succeeded, False if the
            cluster template was not found.

        :raises: OpenStackCloudException on operation error.
        """

        cluster_template = self.get_cluster_template(name_or_id)

        if not cluster_template:
            self.log.debug(
                "Cluster template %(name_or_id)s does not exist",
                {'name_or_id': name_or_id},
                exc_info=True,
            )
            return False

        self.container_infrastructure_management.delete_cluster_template(
            cluster_template
        )
        return True

    def update_cluster_template(self, name_or_id, **kwargs):
        """Update a cluster template.

        :param name_or_id: Name or ID of the cluster template being updated.

        :returns: an update cluster template.

        :raises: OpenStackCloudException on operation error.
        """
        cluster_template = self.get_cluster_template(name_or_id)
        if not cluster_template:
            raise exc.OpenStackCloudException(
                "Cluster template %s not found." % name_or_id
            )

        cluster_template = (
            self.container_infrastructure_management.update_cluster_template(
                cluster_template, **kwargs
            )
        )

        return cluster_template

    def list_magnum_services(self):
        """List all Magnum services.
        :returns: a list of dicts containing the service details.

        :raises: OpenStackCloudException on operation error.
        """
        return list(self.container_infrastructure_management.services())


"""Configure for ASCAR DRL Evaluation

Copyright (c) 2016, 2017 The Regents of the University of California. All
rights reserved.

Created by Yan Li <yanli@tuneup.ai>, Kenneth Chang <kchang44@ucsc.edu>,
Oceane Bel <obel@ucsc.edu>. Storage Systems Research Center, Baskin School
of Engineering.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the Storage Systems Research Center, the
      University of California, nor the names of its contributors
      may be used to endorse or promote products derived from this
      software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
REGENTS OF THE UNIVERSITY OF CALIFORNIA BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
"""
from ascar import common
from ascar import LustreCommon
import glob
import logging
import os
import socket
from typing import List

__author__ = 'Yan Li'
__copyright__ = 'Copyright (c) 2016, 2017 The Regents of the University of California. All rights reserved.'

"""Maps hostname to MA ID
"""
# 主机名映射
nodeid_map = {
    '192.168.1.11': 1,
    '192.168.1.12': 2,
    '192.168.1.13': 3,
    '192.168.1.14': 4,
}

TICK_LEN = 1
servers = [
    '192.168.1.11',
    '192.168.1.12',
]
# client 观测参数数量
PI_PER_CLIENT_OBD = 11
# clint数量
OBD_PER_CLIENT_MA = len(servers)
clients = [
    '192.168.1.13',
    '192.168.1.14',
]
# todo 控制方法的含义
# 'mrif' max rpcs in flight osc到其ost的rpc的最大并发数
# control_method can be 'mrif', 'rules', or 'mrif_tau'
control_method = 'mrif_tau'
rule_archive_path = '/share/drl/rules'

if control_method == 'mrif':
    cpv_spec = [
        # name, initial value, min, max, step size
        ['mrif', 8, 1, 256, 4],
    ]
elif control_method == 'rules':
    cpv_spec = [
        # name, initial value, min, max, step size
        ['rtt_ratio_changepoint', 237, 37, 437, 10],
        ['ewma_changepoint_a', 41001, 20001, 60001, 1000],
        ['ewma_changepoint_b', 48427, 28427, 68427, 1000],
    ]
    rule_template_file = os.path.join(rule_archive_path, 'iorcp_alpha9999_472_3cpvs.csv')
    with open(rule_template_file, 'r') as f:
        rule_template = f.read()
elif control_method == 'mrif_tau':
    cpv_spec = [
        # name, initial value, min, max, step size
        ['mrif', 8, 1, 256, 4],
        ['tau', 32840, 0, 400000, 1500],
    ]
    rule_template_file = os.path.join(rule_archive_path, 'tau_only.csv')
    with open(rule_template_file, 'r') as f:
        tau_only_rule_template = f.read()
    _tau = 0
else:
    raise RuntimeError('Unknown control method: ' + control_method)

my_hostname = socket.gethostname()

# 收集到服务器的时间
def collect_ping_to_servers() -> List[float]:
    result = []
    for srv in servers:
        # PI 7
        result.append(common.get_ping_time(srv))
    return result

'''
收集了10个环境
max_rpcs_in_flight  最大并行rpc数
min_brw_rpc_gap
指数加权移动平均
ack_ewma
sent_ewma
rtt_ratio100
read_bandwidth
write_bandwidth
tau
cur_dirty_bytes
max_dirty_mb
'''

def collect_osc_pi_cpv(osc_path: str) -> List[float]:
    result = list()
    # PI 0
    result.append(LustreCommon.read_proc_file(os.path.join(osc_path, 'max_rpcs_in_flight')))
    # PI 1
    result.append(LustreCommon.read_proc_file(os.path.join(osc_path, 'min_brw_rpc_gap')))

    with open(os.path.join(osc_path, 'import'), 'r') as importfile:
        # import is a proc file and should be read as a whole, i.e., not using readline()
        import_data = importfile.read()

    # PI 2
    result.append(LustreCommon.extract_ack_ewma_from_import(import_data))
    # PI 3
    result.append(LustreCommon.extract_sent_ewma_from_import(import_data))
    # PI 4
    result.append(LustreCommon.extract_rtt_ratio100_from_import(import_data))
    # PI 5
    result.append(LustreCommon.extract_read_bandwidth_from_import(import_data))
    # PI 6
    result.append(LustreCommon.extract_write_bandwidth_from_import(import_data))
    # PI 7
    result.append(_tau)
    # PI 8
    result.append(LustreCommon.read_proc_file(os.path.join(osc_path, 'cur_dirty_bytes')))
    # PI 9
    result.append(LustreCommon.read_proc_file(os.path.join(osc_path, 'max_dirty_mb')))

    return result

# 从lustre收集所有数据
def lustre_collect_pi() -> List[float]:
    if my_hostname in clients:
        # collect ping time to all servers
        ping_times = collect_ping_to_servers()
        # collect PIs
        oscs = glob.glob('/proc/fs/lustre/osc/*/import')
        """ 收集信息大致如下
import:
    name: temp-OST0004-osc-ffff9068ea7c9000
    target: temp-OST0004_UUID
    state: IDLE
    connect_flags: [ write_grant, server_lock, version, request_portal, truncate_lock, max_byte_per_rpc, early_lock_cancel, adaptive_timeouts, lru_resize, alt_checksum_algorithm, fid_is_enabled, version_recovery, grant_shrink, full20, layout_lock, 64bithash, object_max_bytes, jobstats, einprogress, grant_param, lvb_type, short_io, lfsck, bulk_mbits, second_flags, lockaheadv2 ]
    connect_data:
       flags: 0xa0425af2e3440478
       instance: 1
       target_version: 2.12.7.0
       initial_grant: 8437760
       max_brw_size: 4194304
       grant_block_size: 4096
       grant_inode_size: 32
       grant_max_extent_size: 67108864
       grant_extent_tax: 24576
       cksum_types: 0xf7
       max_object_bytes: 17592186040320
    import_flags: [ replayable, pingable, connect_tried ]
    connection:
       failover_nids: [ 192.168.1.13@tcp ]
       current_connection: 192.168.1.13@tcp
       connection_attempts: 2
       generation: 2
       in-progress_invalidations: 0
       idle: 159490 sec
    rpcs:
       inflight: 0
       unregistering: 0
       timeouts: 0
       avg_waittime: 655647 usec
    service_estimates:
       services: 31 sec
       network: 1 sec
        """
        osc_paths = [os.path.dirname(p) for p in oscs]
        osc_paths.sort()
        osc_pis = list()
        for osc in osc_paths:
            osc_pis.extend(collect_osc_pi_cpv(osc))

        result = osc_pis + ping_times
        assert len(result) == len(servers) * PI_PER_CLIENT_OBD
        return result
    elif my_hostname in servers:
        # todo 服务器
        return list()
    else:
        raise RuntimeError('My hostname is neither client or server')

# lustre 根据不同的 control_method 进行控制
def lustre_controller(cpvs: List[float]):
    """Perform an action

    Applies the CPVs to the system

    :param cpvs: the new values of CPVs
    :return:
    """
    # cpvs[0] is the action id, remove it
    cpvs = cpvs[1:]
    if my_hostname in clients:
        if control_method == 'mrif':
            mrif = int(cpvs[0])
            # 必须位于最大最小值之间
            assert cpv_spec[0][2] <= mrif <= cpv_spec[0][3]
            LustreCommon.set_mrif(mrif, len(servers))
        elif control_method == 'rules':
            # convert cpvs list to a dict
            kv = dict([('cpv{0}'.format(i + 1), int(cpv)) for i, cpv in enumerate(cpvs)])
            rule = LustreCommon.gen_rule(rule_template, kv)
            LustreCommon.set_rule(rule, len(servers))
        elif control_method == 'mrif_tau':
            global _tau
            mrif = int(cpvs[0])
            assert cpv_spec[0][2] <= mrif <= cpv_spec[0][3]
            LustreCommon.set_mrif(mrif, len(servers))

            _tau = int(cpvs[1])
            rule = LustreCommon.gen_rule(tau_only_rule_template, {'tau': _tau})
            LustreCommon.set_rule(rule, len(servers))
        else:
            raise RuntimeError('Unknown control method: ' + control_method)
    elif my_hostname in servers:
        # todo 服务器优化
        pass
    else:
        raise RuntimeError('My hostname is neither client or server')

opt = {
    'loglevel': logging.INFO,
    'log_lazy_flush': True,
    # cProfile 性能分析工具
    # Pympler 内存诊断工具
    # level 1 enables cProfile, level 2 also enables Pympler
    'ma_debugging_level': 0,
    'dqldaemon_debugging_level': 0,
    # database location
    # todo: why not only use ram ?
    'dbfile': '/data/ascar/ascar_replay_db.sqlite',
    # todo 滴答声持续时间
    'tick_len': TICK_LEN,  # duration of a tick in second
    # todo 一次观察中有多少滴答声
    'ticks_per_observation': 10,  # how many ticks are in an observation
    # 节点映射表
    'nodeid_map': nodeid_map,
    # client 节点名
    'clients': clients,
    # server 节点名
    'servers': servers,
    # 可控制参数
    'cpvs': cpv_spec,
    # todo: 加减步长和不动？那也应该是3*len(cpv_spec)
    'num_actions': 2 * len(cpv_spec) + 1,
    'pi_per_client_obd': PI_PER_CLIENT_OBD,
    'obd_per_client_ma': OBD_PER_CLIENT_MA,
    'tick_data_size': PI_PER_CLIENT_OBD * OBD_PER_CLIENT_MA * len(clients),  # only four clients have PI so far

    # Collectors are functions that collect PIs. MA calls them in order and concatenate
    # their returns into a single list before passing them to IntfDaemon
    'collectors': [lustre_collect_pi],
    'controller': lustre_controller,
    # 用于接受，存储和聚合来自mas（monitor agents）的obs
    'intf_daemon_loc': '128.114.59.20:9123',
    # moniter 代理 日志文件
    'ascar.MonitorAgent.MonitorAgent_logfile': '/root/log/ma_log.txt',
    'ascar.IntfDaemon.IntfDaemon_logfile': '/data/ascar/intfdaemon_log.txt',
    'ascar.DQLDaemon.DQLDaemon_logfile': '/data/ascar/dqldaemon_log.txt',
    'pidfile_dir': '/tmp',

    'start_random_rate': 1,
    # How many actions should the exploration period include
    'exploration_period': 1000000,
    'random_action_probability': 0.05,

    'minibatch_size': 32,
    'enable_tuning': True,
}


"""QGIS Unit tests for relations with postgresql provider.

.. note:: This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
"""
__author__ = 'Loïc Bartoletti'
__date__ = '27/07/2020'
__copyright__ = 'Copyright 2020, The QGIS Project'

import os

from qgis.core import QgsProject, QgsVectorLayer
import unittest
from qgis.testing import start_app, QgisTestCase

start_app()

class TestQgsRelationPostgresql(QgisTestCase):

    @classmethod
    def setUpClass(cls):
        super().setUpClass()

        cls.dbconn = 'service=\'qgis_test\''
        if 'QGIS_PGTEST_DB' in os.environ:
            cls.dbconn = os.environ['QGIS_PGTEST_DB']

        cls.relMgr = QgsProject.instance().relationManager()

    def setUp(self):
        """
        Setup the involved layers and relations for a n:m relation
        :return:
        """

        QgsProject.instance().clear()

    def test_discover_relations(self):
        """
        Test the automatic discovery of relations
        """

        # Create test layer
        tables = ['c_amgmt_amgmt_lot', 'c_batiment_bat_lot', 'c_ens_immo_amgmt', 'c_ens_immo_bat', 'c_terrain_ens_immo', 't_actes', 't_adresse', 't_amgmt', 't_amgmt_lot', 't_bat', 't_bat_lot', 't_ens_immo', 't_terrain']  # spellok
        vl_tables = ['vl_c_amgmt_amgmt_lot', 'vl_c_batiment_bat_lot', 'vl_c_ens_immo_amgmt', 'vl_c_ens_immo_bat', 'vl_c_terrain_ens_immo', 'vl_t_actes', 'vl_t_adresse', 'vl_t_amgmt', 'vl_t_amgmt_lot', 'vl_t_bat', 'vl_t_bat_lot', 'vl_t_ens_immo', 'vl_t_terrain']  # spellok

        for i in range(len(tables)):
            vl_tables[i] = QgsVectorLayer(self.dbconn + f' sslmode=disable key=\'pk\' table="relations"."{tables[i]}" sql=', tables[i], 'postgres')
            assert vl_tables[i].isValid()
            QgsProject.instance().addMapLayer(vl_tables[i])

        relations = self.relMgr.discoverRelations([], vl_tables)
        for r in relations:
            self.assertTrue(r.isValid())
        self.assertEqual(len(relations), 18)
        self.assertEqual(sum([len(r.referencingFields()) for r in relations]), 18)
        self.assertEqual(sum([len(r.referencedFields()) for r in relations]), 18)

    def test_discover_relations_spaced(self):
        """Test regression https://github.com/qgis/QGIS/issues/39025 and https://github.com/qgis/QGIS/issues/39036"""

        vl_parent = QgsVectorLayer(self.dbconn + ' sslmode=disable key=\'pk\' table="spaced schema"."spaced parent" sql=', 'parent', 'postgres')
        self.assertTrue(vl_parent.isValid())
        vl_child = QgsVectorLayer(self.dbconn + ' sslmode=disable key=\'pk\' table="spaced schema"."spaced child" sql=', 'child', 'postgres')
        self.assertTrue(vl_child.isValid())

        QgsProject.instance().clear()
        QgsProject.instance().addMapLayers([vl_child, vl_parent])

        relations = self.relMgr.discoverRelations([], [vl_child, vl_parent])
        self.assertEqual(len(relations), 1)

if __name__ == '__main__':
    unittest.main()


# From django
from django.test import TestCase
from django.test import Client

# From drf
from rest_framework.reverse import reverse

# From DDF
from ddf import G, N

# From w
from carts.models import Cart
from products.models import Product

class CartModelTest(TestCase):

    def test_new_cart_is_empty(self):
        cart = N(Cart)
        assert cart.is_empty

    def test_new_cart_is_unlocked(self):
        cart = N(Cart)
        assert not cart.is_locked

    def test_new_cart_has_total_of_zero(self):
        cart = N(Cart)
        assert cart.total == 0

    def test_new_item_to_cart(self):
        cart = G(Cart)
        prod = G(Product, unitary_price=100.0)
        res = cart.add(prod_id=prod.id, qt=1)
        assert res == 1
        assert not cart.is_empty
        assert cart.total == prod.unitary_price

    def test_item_with_qt_0_is_removed(self):
        cart = G(Cart)
        prod = G(Product, unitary_price=100.0)
        cart.add(prod_id=prod.id, qt=1)
        res = cart.add(prod_id=prod.id, qt=0)
        assert res == 1
        assert cart.is_empty

    def test_item_with_new_qt_is_updated(self):
        cart = G(Cart)
        prod = G(Product, unitary_price=10.0)
        cart.add(prod_id=prod.id, qt=1)
        res = cart.add(prod_id=prod.id, qt=2)
        assert res == 1
        assert not cart.is_empty

    def test_locked_cart_cant_add(self):
        cart = G(Cart)
        prod = G(Product, unitary_price=10.0)
        cart.add(prod_id=prod.id, qt=1)
        cart.lock()
        assert cart.is_locked
        res = cart.add(prod_id=prod.id, qt=2)
        assert res == 0

class CartAPITest(TestCase):

    def test_create_new_cart_empty_and_nonlocked(self):
        c = Client()
        url = reverse('cart-list')
        response = c.post(url)
        assert response.status_code == 201
        assert response.json()['total'] == 0
        assert response.json()['id']
        assert response.json()['is_empty']
        assert not response.json()['is_locked']
        assert len(response.json()['products']) == 0

    def test_get_cart(self):
        c = Client()
        cart = G(Cart)
        url = reverse('cart-detail', kwargs={'pk': cart.id})
        response = c.get(url)
        assert response.status_code == 200

    def test_add_product_to_cart(self):
        c = Client()
        cart = G(Cart)
        prod = G(Product)
        body = {
                "product": prod.id,
                "count": 1,
                }
        url = reverse('cart-detail', kwargs={"pk": cart.id})
        response = c.post('{}products/'.format(url), body)
        assert response.status_code == 200
        assert not response.json()['is_empty']
        assert response.json()['products']

    def test_atomic_when_error_updating_cart(self):
        c = Client()
        cart = G(Cart)
        prod = G(Product)
        body = {
                "product": prod.id,
                "count": 1,
                }
        url = reverse('cart-detail', kwargs={"pk": cart.id})
        response = c.post('{}products/'.format(url), body)
        total_before = response.json()['total']
        body['count'] = -2
        response = c.post('{}products/'.format(url), body)
        assert response.status_code == 400
        assert cart.products.count() == 1
        response = c.get(url)
        assert total_before == response.json()['total']

    def test_error_when_body_is_not_correct(self):
        c = Client()
        cart = G(Cart)
        prod = G(Product)
        body = {
                "no_product_key": prod.id,
                "no_count_key": 1,
                }
        url = reverse('cart-detail', kwargs={"pk": cart.id})
        response = c.post('{}products/'.format(url), body)
        assert response.status_code == 400

    def test_add_product_no_exists_error(self):
        c = Client()
        cart = G(Cart)
        prod = G(Product)
        invalid_prod_id = 2323
        body = {
                "product": invalid_prod_id,
                "count": 1,
                }
        url = reverse('cart-detail', kwargs={"pk": cart.id})
        response = c.post('{}products/'.format(url), body)
        assert response.status_code == 400


import pygame as pg
from random import randrange

vec2 = pg.math.Vector2
snake1 = (227, 57, 25)
snake2 = (26, 147, 187)
snake3 = (122, 13, 89)
snake4 = (31, 3, 106)
green = (0, 255, 0)
blue = (0, 0, 255)
yellow = (255, 255, 0)
black = (0, 0, 0)
light_pink = (255, 182, 193)
pink = (255, 105, 180)
dark_pink = (255, 20, 147)
background = (245, 246, 246)

class Snake:
    def __init__(self, game, color, keys, snake_color, image):
        self.color = color
        self.l = keys[0]
        self.r = keys[1]
        self.u = keys[2]
        self.d = keys[3]
        self.game = game
        self.size = game.TILE_SIZE
        self.rect = pg.rect.Rect([0, 0,
                                  game.TILE_SIZE,
                                  game.TILE_SIZE])
        self.rect.center = self.get_random_position()
        self.direction = vec2(self.size, 0)
        self.step_delay = 100
        self.time = 0
        self.lenght = 1
        self.segments = []
        self.directions = {self.l: 1, self.r: 1, self.u: 1, self.d: 1}
        self.snake_color = snake_color
        self.img = import_image(image, 0.2, (0, 0, 0))
        self.head = self.img

    def end_of_snake(self):
        self.game.alive[self.snake_color] = 0
        self.game.table.append(self.snake_color)
        self.segments = []

    def check_collision(self):
        if self.snake_color != 'red':
            for segment in self.game.snake1.segments:
                if segment.center == self.rect.center:
                    print('a')
                    self.end_of_snake()
        if self.game.number_of_players > 1:
            if self.snake_color != 'light blue':
                for segment in self.game.snake2.segments:
                    if segment.center == self.rect.center:
                        print('b')
                        self.end_of_snake()
        if self.game.number_of_players > 2:
            if self.snake_color != 'violete':
                for segment in self.game.snake3.segments:
                    if segment.center == self.rect.center:
                        print('c')
                        self.end_of_snake()
        if self.game.number_of_players > 3:
            if self.snake_color != 'dark blue':
                for segment in self.game.snake4.segments:
                    if segment.center == self.rect.center:
                        print('d')
                        self.end_of_snake()

    def check_food(self):
        if self.rect.center == self.game.food.rect.center:
            self.game.food.rect.center = self.get_random_position()
            self.lenght += 4
        if self.rect.center == self.game.food1.rect.center:
            self.game.food1.rect.center = self.get_random_position()
            self.lenght += 3
        if self.rect.center == self.game.food2.rect.center:
            self.game.food2.rect.center = self.get_random_position()
            self.lenght += 2

    def check_border1(self):
        if self.rect.left < 0 or self.rect.right > self.game.WINDOW_SIZE:
            print('e')
            self.end_of_snake()
        if self.rect.top < 0 or self.rect.bottom > self.game.WINDOW_SIZE:
            print('f')
            self.end_of_snake()

    def check_border(self):
        if self.rect.left < 0:
            self.rect.right = self.game.WINDOW_SIZE
        if self.rect.right > self.game.WINDOW_SIZE:
            self.rect.left = 0
        if self.rect.top < 0:
            self.rect.bottom = self.game.WINDOW_SIZE
        if self.rect.bottom > self.game.WINDOW_SIZE:
            self.rect.top = 0

    def check_if_collision(self):
        if len(self.segments) > len(set([segment.center for segment in self.segments])):
            self.end_of_snake()

    def control(self, event):
        if event.type == pg.KEYDOWN:
            if event.key == self.r and self.directions[self.r]:
                self.direction = vec2(self.size, 0)
                self.directions = {self.l: 0, self.r: 0, self.u: 1, self.d: 1}
                self.head = pg.transform.rotate(self.img, 270)

            if event.key == self.u and self.directions[self.u]:
                self.direction = vec2(0, -self.size)
                self.directions = {self.l: 1, self.r: 1, self.u: 0, self.d: 0}
                self.head = self.img

            if event.key == self.l and self.directions[self.l]:
                self.direction = vec2(-self.size, 0)
                self.directions = {self.l: 0, self.r: 0, self.u: 1, self.d: 1}
                self.head = pg.transform.rotate(self.img, 90)

            if event.key == self.d and self.directions[self.d]:
                self.direction = vec2(0, self.size)
                self.directions = {self.l: 1, self.r: 1, self.u: 0, self.d: 0}
                self.head = pg.transform.rotate(self.img, 180)

    def delta_time(self):
        time_now = pg.time.get_ticks()
        if time_now - self.time > self.step_delay:
            self.time = time_now
            return True
        return False

    def get_random_position(self):
        return [randrange(self.size//2,
                          self.game.WINDOW_SIZE -self.size//2,
                          self.size)] * 2

    def move(self):
        if self.delta_time():
            self.rect.move_ip(self.direction)
            self.segments.append(self.rect.copy())
            self.segments = self.segments[-self.lenght:]

    def update(self):
        self.move()
        self.check_food()
        self.check_border()
        self.check_if_collision()
        self.check_collision()

    def draw(self):
        [pg.draw.rect(self.game.screen, self.color, segment) for segment in self.segments]
        self.game.screen.blit(self.head, (self.rect.left, self.rect.top))

class Food:
    def __init__(self, game):
        self.game = game
        self.size = game.TILE_SIZE
        self.image = import_image('images/fruit1.png', 0.08, (245, 246, 246))
        self.rect = pg.rect.Rect([0, 0,
                                  game.TILE_SIZE - 2,
                                  game.TILE_SIZE - 2])
        self.rect.center = self.get_random_position()

    def get_random_position(self):
        return [randrange(self.size//2,
                          self.game.WINDOW_SIZE -self.size//2,
                          self.size)] * 2

    def draw(self):
        #pg.draw.rect(self.game.screen, (255, 0, 0), self.rect)
        self.game.screen.blit(self.image, (self.rect.left, self.rect.top))

class Button:
    def __init__(self, game, x, y, image, scale):
        self.game = game
        width = image.get_width()
        height = image.get_height()
        self.image = pg.transform.scale(image, (int(width * scale), int(height * scale)))
        self.rect = self.image.get_rect()
        self.rect.topleft = (x, y)
        self.clicked = False

    def draw(self):
        action = False
        pos = pg.mouse.get_pos()
        if self.rect.collidepoint(pos):
            if pg.mouse.get_pressed()[0] == 1 and self.clicked == False:
                self.clicked = True
                action = True

        if pg.mouse.get_pressed()[0] == 0:
            self.clicked = False

        self.game.screen.blit(self.image, (self.rect.x, self.rect.y))
        return action

def draw_text(game, text, font, text_col, x, y):
    img = font.render(text, True, text_col)
    game.screen.blit(img, (x, y))

def import_image(image, scale, colorkey):
    im = pg.image.load(image)
    im.set_colorkey(colorkey)
    im = pg.transform.scale(im, (int(im.get_width() * scale), int(im.get_height() * scale)))
    return im


from PIL import Image
image_file = Image.open('pixil-frame-0.png','r')
convert_RGB_IMAGE = image_file.convert('RGB')
loaded_image = convert_RGB_IMAGE.load()
text_copy = open('menu_copy.txt','a')
def RGB_NUMBER(array_input):
    binary_array = []
    for number in array_input:
        binary_array.append((bin(number)[2:]))
    return binary_array
input_array = (242, 28, 28)
print(RGB_NUMBER(input_array))
test_array = RGB_NUMBER(input_array)

def interate_values(input_image):

    for y in range(input_image.height):
        for x in range(input_image.width):
            values = RGB_NUMBER(input_image.getpixel((x,y)))
            print("13'd{}: PIXEL_INDEX <= [5'b{},6'b{},5'b{}]\n".format((input_image.width*y+x),values[0],values[1],values[2]))
            text_copy.write("13'd{}: PIXEL_DATA <= [5'b{},6'b{},5'b{}];\n".format((input_image.width*y+x),values[0],values[1],values[2]))
    #now we just return the new image
    return 0
interate_values(convert_RGB_IMAGE)