from IPython.core.debugger import Pdb
from typing import Dict, List, TextIO, Optional, Any
import warnings

from overrides import overrides
import torch
from torch.nn.modules import Linear, Dropout
import torch.nn.functional as F

from allennlp.common.checks import check_dimensions_match
from allennlp.data import Vocabulary
from allennlp.modules import Seq2SeqEncoder, TimeDistributed, TextFieldEmbedder
from allennlp.modules.token_embedders import Embedding
from allennlp.models.model import Model
from allennlp.nn import InitializerApplicator, RegularizerApplicator
from allennlp.nn.util import get_text_field_mask, sequence_cross_entropy_with_logits
from allennlp.nn.util import get_lengths_from_binary_sequence_mask, viterbi_decode
from allennlp.training.metrics import SpanBasedF1Measure
from allennlp.training.metrics import DecodeSpanBasedF1Measure
from allennlp.modules.conditional_random_field import allowed_transitions

def get_viterbi_pairwise_potentials(vocab, label_encoding):
    """
    Generate a matrix of pairwise transition potentials for the BIO labels.
    The only constraint implemented here is that I-XXX labels must be preceded
    by either an identical I-XXX tag or a B-XXX tag. In order to achieve this
    constraint, pairs of labels which do not satisfy this constraint have a
    pairwise potential of -inf.

    Returns
    -------
    transition_matrix : torch.Tensor
        A (num_labels, num_labels) matrix of pairwise potentials.
    """
    all_labels = vocab.get_index_to_token_vocabulary("labels")
    num_labels = len(all_labels)
    transition_matrix = torch.zeros([num_labels+2, num_labels+2]).fill_(float("-inf"))

    constraints = allowed_transitions(label_encoding, all_labels)
    # print(constraints)
    for c in constraints:
        #if (c[0] < num_labels) and (c[1] < num_labels):
        transition_matrix[c[0],c[1]] = 0.0
    #
    return transition_matrix
    """
    for i, previous_label in all_labels.items():
        for j, label in all_labels.items():
            # I labels can only be preceded by themselves or
            # their corresponding B tag.
            if i != j and label[0] == 'I' and not previous_label == 'B' + label[1:]:
                transition_matrix[i, j] = float("-inf")

    return transition_matrix
    """

def decode(transition_matrix, vocab, output_dict: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]:
    """
    Does constrained viterbi decoding on class probabilities output in :func:`forward`.  The
    constraint simply specifies that the output tags must be a valid BIO sequence.  We add a
    ``"tags"`` key to the dictionary with the result.
    """
    all_predictions = output_dict['class_probabilities']
    sequence_lengths = get_lengths_from_binary_sequence_mask(output_dict["mask"]).data.tolist()
    max_seq_length = max(sequence_lengths)
    if all_predictions.dim() == 3:
        predictions_list = [all_predictions[i].detach().cpu() for i in range(all_predictions.size(0))]
    else:
        predictions_list = [all_predictions]
    all_tags = []
    all_prob_seq = []
    num_tags  = transition_matrix.shape[0] - 2
    tag_sequence = torch.Tensor(max_seq_length + 2, num_tags + 2)
    for predictions, length in zip(predictions_list, sequence_lengths):
        tag_sequence.fill_(-10000.)
        tag_sequence[0, num_tags] = 0.
        tag_sequence[1:(length + 1), :num_tags] = predictions[:length]
        tag_sequence[(length+1),num_tags+1] = 0.

        max_likelihood_sequence, _  = viterbi_decode(tag_sequence[:(length+2)], transition_matrix)
        max_likelihood_sequence = max_likelihood_sequence[1:-1]
        tags = [vocab.get_token_from_index(x, namespace="labels")
                for x in max_likelihood_sequence]
        all_tags.append(tags)
        all_prob_seq.append(max_likelihood_sequence)
    output_dict['tags'] = all_tags
    output_dict['max_seq'] = all_prob_seq
    return output_dict

def count_violating_spans(decode_semantic_span_list, syntax_span_matrix):
    num_violations = 0
    for this_span in decode_semantic_span_list:
        start_idx = this_span[0]
        end_idx = this_span[1]
        assert (start_idx <= end_idx)
        assert (end_idx <= syntax_span_matrix.shape[0])
        if syntax_span_matrix[start_idx,end_idx] == 0:
            num_violations += 1
    #
    return num_violations

def get_span_list(pred_labels_INFDEC):
    # pred_labels_INFDEC
    cur_tag_type = 'O'
    span_active = False
    decode_semantic_span_list = []
    total_spans = 0
    for this_ind,label in enumerate(pred_labels_INFDEC):
        if(label == 'O'):
            lb_posn = label
            lb_type = label
        else:
            lb_posn = label[0]
            lb_type = label[2:]

        if span_active:
            if lb_posn == 'B':
                cur_tag_type = lb_type
                current_span = [this_ind]
            elif lb_posn == 'I':
                if(cur_tag_type != lb_type):
                    span_active = False
            elif lb_posn == 'U':
                span_active = False
                # QUESTION - increase semantic span count?
                total_spans += 1
            elif lb_posn == 'L':
                span_active = False
                if(cur_tag_type == lb_type):
                    total_spans += 1
                    current_span.append(this_ind)
                    decode_semantic_span_list.append(current_span)
            else:
                span_active = False
        else:
            if lb_posn == 'B':
                span_active = True
                current_span = [this_ind]
                cur_tag_type = lb_type
            elif lb_posn == 'U':
                total_spans += 1
            else: # I-, L-, O
                continue
    return decode_semantic_span_list, total_spans

"""
def write_output_to_file(batch, output_dict, ind2lab, penalties, global_count, fh=None):
    #Pdb().set_trace()
    topk,topk_ind = output_dict['class_probabilities'].detach().topk(k = 5, dim=-1)
    topk = topk.detach().cpu().numpy()
    topk_ind = topk_ind.detach().cpu().numpy()
    all_sentences = batch['metadata']
    vio_keys = [x for x in penalties.keys() if 'violations' in x]
    con_keys = [x.split('_')[0] for x in vio_keys]
    sum_violations = None
    batch_size,num_words = batch['tokens']['tokens'].shape
    for k in vio_keys:
        if sum_violations is None:
            sum_violations = penalties[k][:,:num_words].detach()
        else:
            sum_violations += penalties[k][:,:num_words].detach()
    #
    sum_violations = sum_violations*(output_dict['mask'].detach().float())
    for i,this_sentence in enumerate(all_sentences):
        #check if anything wrong or not
        flag = sum_violations[i].sum() > 0
        tot_incorrect = 0
        for j in range(len(this_sentence['words'])):
            gold_tag = this_sentence['gold_tags'][j]
            pred_tag = ind2lab[topk_ind[i,j,0]]
            if pred_tag != gold_tag:
                tot_incorrect += 1

        if tot_incorrect > 0:
            print('#gc: {} i {} VERB=={}. Total pen: {}. #Incorrect: {}'.format(global_count, i,this_sentence['verb'], round(sum_violations[i].sum().item(),3), tot_incorrect),file=fh)
            for j in range(len(this_sentence['words'])):
                word = this_sentence['words'][j]
                gold_tag = this_sentence['gold_tags'][j]
                top5_pred = ','.join(list(map(lambda x: ind2lab[x], topk_ind[i,j])))
                top5_prob = ','.join(list(map(lambda x: str(round(x,4)), topk[i,j])))
                pen = ','.join([con_key+"_"+str(round(penalties[vio_key][i,j].item(),4)) for vio_key,con_key in zip(vio_keys,con_keys)])
                print('{} ; {} ; {} ; {} ; {}'.format(word,gold_tag,top5_pred,top5_prob, pen),file=fh)
        #
        global_count += 1
    return global_count
"""

def write_decoded_output_to_file(batch, output_dict, ind2lab, penalties, global_count, decoded_output=None, fh=None):
    #Pdb().set_trace()
    topk,topk_ind = output_dict['class_probabilities'].detach().topk(k = 5, dim=-1)
    topk = topk.detach().cpu().numpy()
    topk_ind = topk_ind.detach().cpu().numpy()
    all_sentences = batch['metadata']
    vio_keys = [x for x in penalties.keys() if 'violations' in x]
    con_keys = [x.split('_')[0] for x in vio_keys]
    sum_violations = None
    batch_size,num_words = batch['tokens']['tokens'].shape
    for k in vio_keys:
        if sum_violations is None:
            sum_violations = penalties[k][:,:num_words].clone().detach()
        else:
            sum_violations += penalties[k][:,:num_words].detach()
    #
    sum_violations = sum_violations*(output_dict['mask'].detach().float())
    for i,this_sentence in enumerate(all_sentences):
        #check if anything wrong or not
        flag = sum_violations[i].sum() > 0
        tot_incorrect = 0
        for j in range(len(this_sentence['words'])):
            gold_tag = this_sentence['gold_tags'][j]
            pred_tag = ind2lab[topk_ind[i,j,0]]
            if pred_tag != gold_tag:
                tot_incorrect += 1

        if tot_incorrect > 0:
            tot_pen = ','.join([con_key+"_"+str(round(penalties[vio_key][i].sum().item(),4)) for vio_key,con_key in zip(vio_keys,con_keys)])

            print('#gc: {} i {} VERB=={}. #Words: {}  #Incorrect: {}. Tot. Penalty: {} Penalty: {}'.format(global_count, i,this_sentence['verb'], len(this_sentence['words']), tot_incorrect, round(sum_violations[i].sum().item(),4), tot_pen ),file=fh)
            for j in range(len(this_sentence['words'])):
                word = this_sentence['words'][j]
                gold_tag = this_sentence['gold_tags'][j]
                top5_pred = ','.join(list(map(lambda x: ind2lab[x], topk_ind[i,j])))
                top5_prob = ','.join(list(map(lambda x: str(round(x,4)), topk[i,j])))
                pen = ','.join([con_key+"_"+str(round(penalties[vio_key][i,j].item(),4)) for vio_key,con_key in zip(vio_keys,con_keys)])
                do = 'NA'
                if decoded_output is not None:
                    do = decoded_output[i][j]

                print('{} ; {} ; {} ; {} ; {} ; {}'.format(word,gold_tag, do, top5_pred,top5_prob, pen),file=fh)
        #
        global_count += 1
    return global_count


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

"""
pvalstats.py

VERSION AND LAST UPDATE:
 v1.0  05/24/2022
 v1.1  06/30/2022
 v1.2  11/23/2022
 v1.2  07/17/2023
 v1.3  08/03/2023
 v1.4  08/04/2023

PURPOSE:
 Group of functionalities for visualization of model/observation data
  and validation.
 Users can import as a standard python module, and use it accordingly:
- For example (ModelObsPlot):

  from pvalstats import ModelObsPlot
  mop=ModelObsPlot(model,obs)
  mop.qqplot()
  mop.scatterplot()
  mop.taylordiagram()
  mop.combinerrors()
  mop.pdf()

  mop=ModelObsPlot(model,obs,dtime)
  mop.timeseries()

  fctintervals=np.arange(0,1+np.ceil(np.max(forecastLeadTime)/24)*24,24).astype('int')
  fctxt=np.array(frintervalsd/24).astype('int')[0:-1]+1 # x-axis plot array
  fctxticks=np.array([1,2,3,4,5,7,10,15,20,25,30,35]) # x-axis figure ticks
  outpath="/home/ricardo/validation/"
  mop=ModelObsPlot(np.c_[modelControl,modelEnsMean].T,obs=obs,
    fctime=forecastLeadTime,fctintervals=fctintervals,fctxt=fctxt,
    fctxticks=fctxticks,fctunits="days",
    ftag=outpath+"Results_Hs_",vaxisname="Hs")

  mop.errxfctime()

- For example (GlobalMapPlot):
  from pvalstats import GlobalMapPlot, gsmooth

  gdata = np.random.rand(10, 10)  # Example data
  lonm = np.linspace(0, 360, 10)  # Example longitude values
  latm = np.linspace(-90, 90, 10)  # Example latitude values
  gmp = GlobalMapPlot(gdata=gsmooth(gdata,1),lat=latm,lon=lonm)
  gmp.plot()

  outpath="/home/ricardo/validation/"
  figname=outpath+"GlobalMap_RMSE_Hs.png"
  palette = plt.cm.jet; pextend="max"
  levels = np.array(np.linspace(0.1,2.3,31)).round(2)
  gmp = GlobalMapPlot(gdata=gsmooth(RmseGefsControl,1),latm=lat,lonm=lon,
    latmin=-67.,latmax=67.,palette = palette, levels=levels,ftag=figname,pextend=pextend)
  gmp.plot()

USAGE:
 Class ModelObsPlot
 Functions:
   timeseries
   qqplot
   scatterplot
   taylordiagram
   combinerrors
   pdf
   monthlystats
   errxfctime
   rankhist
   spreadxfctime
   crpsxfctime
   brierxfctime
   rocaucxfctime

 Class GlobalMapPlot
 Functions:
   gsmooth
   plot

 The explanation for each function is contained in the headers, including
  examples,
 help(pvalstats)

OUTPUT:
 png figures saved in the local directory or in the path given through the tag/prefix.

DEPENDENCIES:
 See setup.py and the imports below.

AUTHOR and DATE:
 05/24/2022: Ricardo M. Campos, first version.
 06/30/2022: Ricardo M. Campos, TaylorDiagram and PDF plots corrected
   for arrays containing NaN.
 11/23/2022: Ricardo M. Campos, new functions errXftime and interp_nan,
   plus some format improvements.
 07/17/2023: Ricardo M. Campos, modification to use python Class and OOP.
 08/03/2023: Ricardo M. Campos, new functions included rankhist,monthlystats,
   errxfctime,spreadxfctime,crpsxfctime
 08/04/2023: Ricardo M. Campos, new class GlobalMapPlot and function gsmooth

PERSON OF CONTACT:
 Ricardo M Campos: ricardo.campos@noaa.gov

"""

import matplotlib
# matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
import math
import pandas as pd
from datetime import datetime, timedelta
from scipy.ndimage.filters import gaussian_filter
from scipy.stats import gaussian_kde, linregress
import matplotlib.ticker
from matplotlib.dates import DateFormatter
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import os
from mpl_toolkits.basemap import cm
colormap = cm.GMT_polar
palette = plt.cm.jet
palette.set_bad('aqua', 10.0)
import warnings; warnings.filterwarnings("ignore")
# Font size and style
sl=13
matplotlib.rcParams.update({'font.size': sl}); plt.rc('font', size=sl)
matplotlib.rc('xtick', labelsize=sl); matplotlib.rc('ytick', labelsize=sl); matplotlib.rcParams.update({'font.size': sl})

def interp_nan(data,lmt=10**50):
    '''
    Fill NaN values with linear interpolation.
    User can enter one or two inputs:
      1) time-series containing NaN values to fill in
      2) maximum number of consecutive NaN values to interpolate (to
         avoid interpolating long segments)
    '''
    data=np.array(data)
    lmt=int(lmt)

    if data.ndim>1:
        raise ValueError(' Input array with too many dimensions. Only time-series (1 dimension) allowed.')
    else:
        # using pandas
        A=pd.Series(data)
        # B=A.interpolate(method="polynomial",order=2,limit=lmt)
        B=A.interpolate(method="linear",limit=lmt)
        B=np.array(B.values)

    return B
    del lmt,A,data

class ModelObsPlot:

    def __init__(self,model=None,obs=None,time=None,
        fctime=None,fctintervals=None,fctxt=None,fctxticks=None,fctunits=None,month=None,linreg=None,axisnames=None,
        mlabels=[],vaxisname=None,linestyle=None,linestyle_html=None,marker=None,color=None,ftag='',figformat=None):

        """
        Initialization of the main object used for the plot functions below.
        model and obs are mandatory.
        time is mandatory for the timeseries plot only.
        Optional:
        :param axisnames: adds specific model and observation axis names to qqplot and scatterplot,
            for ex axisnames=["WW3","Buoy"]
        :param vaxisname: adds specific axis names when only one axis is need, for timeseries (y-axis), month (y-axis), and pdf (x-axis),
            for ex vaxisname="Hs(m)" or vaxisname="Hs(m), WW3 and Buoy"
        :param mlabels: adds model labels for the legend, to differenciate simulations,
            for ex mlabels=["WW3T1","WW3T2"]
        :param ftag: is used to include prefix (can include the full path or not),
            for example ftag="/home/ricardo/test/NewRunWW3T2_"

        """

        if model is None or obs is None:
            raise ValueError("Missing required input arguments.")
            return
        else:
            self.model = np.array(np.atleast_2d(model)).astype('float')
            self.obs = np.array(np.atleast_2d(obs)).astype('float')

        self.figformat = figformat
        self.ftag = ftag
        self.mlabels = mlabels
        self.linreg = linreg
        self.vaxisname = vaxisname if vaxisname is not None else "Model and Observations"
        self.axisnames = axisnames if axisnames is not None else ["Model","Observations"]
        self.linestyle = (linestyle if linestyle is not None else
            np.array(['-', '--', '-.', '--', '-.', '--', '-.', '--', '-.', '--', '-.', '--', '-.', '--', '-.']))

        self.linestyle_html = (linestyle_html if linestyle_html is not None else
            np.array(['solid','dashed','dashdot','dashed','dashdot','dashed','dashdot','dashed','dashdot','dashed','dashdot','dashed','dashdot','dashed','dashdot']))

        self.marker = (marker if marker is not None else
            np.array(np.atleast_1d(['.','.','.','.','.','.','.','.','.','.','.','.','.','.','.',])).astype('str'))

        self.color = (color if color is not None else
            np.array(['navy', 'firebrick', 'darkgreen', 'fuchsia', 'gold', 'blue', 'salmon', 'lime', 'darkviolet', 'yellow',
                'cornflowerblue', 'red', 'green', 'violet', 'orange']))

        if self.model.ndim > 2 or self.obs.ndim > 2:
            raise ValueError('Too many dimensions in the model or observation arrays.')

        if self.model.shape[0] > self.model.shape[1]:
            self.model = self.model.T
        if self.obs.shape[0] > self.obs.shape[1]:
            self.obs = self.obs.T

        if self.model.shape[1] != self.obs.shape[1]:
            raise ValueError('Model and observation arrays must have the same index/time size.')

        if np.size(fctime)>1:
            self.fctime = fctime
            if self.fctime.shape[0] != self.model.shape[1]:
                raise ValueError('Forecast time array does not match the model/obs size.')

        if np.size(month)>1:
            self.fmonth = month
            if self.fmonth.shape[0] != self.model.shape[1]:
                raise ValueError('Month array does not match the model/obs size.')

        if np.size(time)>1:
            self.time = time
            if self.time.shape[0] != self.model.shape[1]:
                raise ValueError('Time array does not match the model/obs size.')
            else:
                ind=np.where(self.time>-np.inf)
                if np.size(ind)>1:
                    self.model=self.model[:,ind[0]]
                    self.obs=self.obs[:,ind[0]]
                    self.time=self.time[ind[0]]
                else:
                    raise ValueError('Time array full of NaN.')

                if isinstance(self.time[0], datetime):
                    self.dtime=self.time
                elif isinstance(self.time[0], float):
                    epoch_start = datetime(1970, 1, 1)
                    self.dtime = [epoch_start + timedelta(seconds=int(ts)) for ts in self.time]
                else:
                    raise ValueError('Time array format not recognized.')

        if self.linreg:
            from scipy.stats import linregress

        self.fctintervals = fctintervals
        self.fctxt = fctxt
        self.fctunits = fctunits
        self.fctxticks = fctxticks
        # name of the 8 error metrics
        self.nerrm=np.array(['bias','RMSE','NBias','NRMSE','SCrmse','SI','HH','CC'])

    def timeseries(self):
        '''
        Time-series plots of model (lines) and observations (discrete points).
        The x-axis is time and y-axis is the variable and unit of the array provided.
        Inputs:
          Mandatory: model(s); observations; time array (datetime).
           the model array can include one or more model results, through the number of columns, while
           the observation array must be one-dimensional, with the same number of lines as the model and
           the time array.
          Optional: see object construction above.
        Output: png figure saved in the local directory where python is running or in the path given through ftag.
        Example:
          from pvalstats import ModelObsPlot

          mop=ModelObsPlot(ww3gfs,obs,dtime)
          mop.timeseries()

          mop=ModelObsPlot(model=np.c_[model1[:],model2[:]],obs=buoydata[:],time=t[:],
              vaxisname="Hs (m)",mlabels=["WW3T1","WW3T2"],ftag="NewRun_")
          mop.timeseries()

        '''

        if self.dtime is None:
            raise ValueError("Missing time array input argument.")
            return

        fig1, ax = plt.subplots(figsize=(9, 4))
        if np.size(self.mlabels) > 0 and np.size(np.where(self.obs[0, :] > -999)) > 1:
            ax.plot(self.dtime, self.obs[0, :], color='dimgray', marker='.', linestyle='', linewidth=2., label='Obs', zorder=2)
        else:
            ax.plot(self.dtime, self.obs[0, :], color='dimgray', marker='.', linestyle='', linewidth=2., zorder=2)

        if np.size(np.where(self.obs[0, :] > -999)) > 1:
            ax.fill_between(self.dtime, 0., self.obs[0, :], color='silver', alpha=0.5, zorder=1)

        for i in range(self.model.shape[0]):
            if np.size(self.mlabels) > 0:
                if np.size(np.where(self.model[i, :] > -999)) > 1:
                    ax.plot(self.dtime, interp_nan(self.model[i, :], 3), color=self.color[i], linestyle=self.linestyle[i],
                            linewidth=2., label=self.mlabels[i], alpha=0.8, zorder=3)
            else:
                if np.size(np.where(self.model[i, :] > -999)) > 1:
                    ax.plot(self.dtime, interp_nan(self.model[i, :], 3), color=self.color[i], linestyle=self.linestyle[i],
                            linewidth=2., alpha=0.8, zorder=3)

            if np.size(np.where(self.model[i, :] > -999)) > 1:
                ax.plot(self.dtime, self.model[i, :], marker='.', markersize=5, markerfacecolor=self.color[i],
                        markeredgewidth=0.5, markeredgecolor='black', linestyle='', linewidth=0., zorder=3)

        ax.set_xlim(self.dtime[0], self.dtime[-1])
        ax.xaxis.set_major_formatter(DateFormatter('%b%d'))
        ax.fmt_xdata = DateFormatter('%b%d')

        for label in ax.get_xticklabels() + ax.get_yticklabels():
            label.set_fontsize(sl - 2)

        ax.set_xlabel("Time")
        if np.size(self.vaxisname) > 0:
            ax.set_ylabel(self.vaxisname)
        else:
            ax.set_ylabel("Model and Observations")

        if np.size(self.mlabels) > 0:
            if np.size(self.mlabels) < 7:
                ax.legend(fontsize=sl - 3)
            else:
                ax.legend(fontsize=sl - 5)

        # plt.tight_layout()
        plt.grid(c='grey', ls='--', alpha=0.3, zorder=1)
        plt.savefig(self.ftag + 'TimeSeries.png', dpi=200, facecolor='w', edgecolor='w', orientation='portrait',
                    format='png', bbox_inches='tight', pad_inches=0.1)
        plt.close(fig1)

        # Html plot with bokeh
        if self.figformat == 'html':
            from bokeh.plotting import figure, output_file, show
            plt.close('all')
            p = figure(x_axis_type="datetime", plot_width=1400, plot_height=600)
            p.title.text = 'Click on legend entries to hide the corresponding lines'
            p.line(self.dtime,self.obs[0, :],line_width=3, color='black', alpha=0.8, legend_label='Obs')
            for i in range(self.model.shape[0]):
                if np.size(self.mlabels) > 0:
                    if np.size(np.where(self.model[i, :] > -999)) > 1:
                        p.line(self.dtime,interp_nan(self.model[i, :], 3), line_width=2, color=self.color[i], line_dash=self.linestyle_html[i], alpha=0.8, legend_label=self.mlabels[i])

                else:
                    if np.size(np.where(self.model[i, :] > -999)) > 1:
                        p.line(self.dtime,interp_nan(self.model[i, :], 3), line_width=2, color=self.color[i], line_dash=self.linestyle_html[i], alpha=0.8)

            if np.size(self.mlabels) > 0:
                p.legend.location = "top_left" ; p.legend.click_policy = "hide"

            p.xaxis.axis_label = "Time"

            if np.size(self.vaxisname) > 0:
                p.yaxis.axis_label = self.vaxisname
            else:
                p.yaxis.axis_label = "Model and Observations"

            output_file(self.ftag + 'TimeSeries.html'); show(p); del p

    def qqplot(self):
        '''
        Quantile-Quantile plot.
        Inputs:
          Mandatory: model(s); observations (or any reference).
           the model array can include one or more model results, through the number of columns, while
           the observation array must be one-dimensional, with the same number of lines as the model.
          Optional: see object construction above.
        Output: png figure saved in the local directory where python is running or in the path given through ftag.
        Example:
          from pvalstats import ModelObsPlot
          mop=ModelObsPlot(ww3gfs,obs)
          mop.qqplot()

          mop=ModelObsPlot(model=np.c_[model1[:],model2[:]],obs=buoydata[:],axisnames=["WW3","Buoy"],
              mlabels=["WW3T1","WW3T2"],ftag="/home/ricardo/testWW3/NewRun_")
          mop.qqplot()
        '''

        # percentiles
        p = np.arange(1,96,1); p=np.append(p,[95.5,96.,96.5,97.,97.3,97.6,97.9,98.2,98.5,98.7,98.9,99.1,99.3,99.5,99.7])
        qm = np.zeros((self.model.shape[0],p.shape[0]),'f')*np.nan
        qobs = np.zeros((self.model.shape[0],p.shape[0]),'f')*np.nan
        for i in range(0,p.shape[0]):
            for j in range(0,self.model.shape[0]):
                qobs[j,i] = np.nanpercentile(self.obs[:],p[i])
                qm[j,i] = np.nanpercentile(self.model[j,:],p[i])

        a=math.floor(np.nanmin([qobs,qm])*100.)/100. ; b=math.ceil(np.nanmax([qobs,qm])*100.)/100.
        aux=np.linspace(a-0.2*a,b+0.2*a,p.shape[0])
        # plot
        fig1 = plt.figure(1,figsize=(5,4.5)); ax = fig1.add_subplot(111)
        ax.plot(aux,aux,'k', linewidth=2.,alpha=0.4,zorder=2); del a,b
        for i in range(0,self.model.shape[0]):
            if np.size(self.mlabels)>0:
                ax.plot(gaussian_filter(qobs[i,:],1),gaussian_filter(qm[i,:],1), color=self.color[i], marker=self.marker[i], linestyle='--',label=self.mlabels[i],linewidth=1.,alpha=0.8,zorder=3)
            else:
                ax.plot(gaussian_filter(qobs[i,:],1),gaussian_filter(qm[i,:],1), color=self.color[i], marker=self.marker[i], linestyle='--',linewidth=1.,alpha=0.8,zorder=3)

            if self.linreg:
                a=np.array(qm[i,:]); b=np.array(qobs[i,:]); ind=np.where((a*b)>-999.)
                if np.size(ind)>2:
                    a=np.copy(a[ind]); b=np.copy(b[ind]);
                    r = linregress(b,a)
                    aregr = np.array(r.slope*aux + r.intercept )
                    ax.plot(aux,aregr,color=self.color[i],ls='-',linewidth=1.,alpha=0.8,zorder=4)
                    ax.plot(aux,aregr,color='k',ls=':',linewidth=0.7,alpha=0.7,zorder=4)
                    if np.size(self.mlabels)>0:
                        print(self.ftag+"QQplot "+self.mlabels[i]+": Slope "+np.str(np.round(float(r.slope),5))+", Intercept "+np.str(np.round(float(r.intercept),5)))
                    else:
                        print(self.ftag+"QQplot: Slope "+np.str(np.round(float(r.slope),5))+", Intercept "+np.str(np.round(float(r.intercept),5)))

                    del a,b,r,aregr

                del ind

        plt.grid(c='grey', ls=':', alpha=0.5,zorder=1)
        for i in np.array([50,80,90,95,99]):
            plt.axvline(x= np.nanpercentile(self.obs,int(i)),ls='--',color='grey',linewidth=1.,alpha=0.9,zorder=1)
            plt.text(np.nanpercentile(self.obs,int(i)),(aux.max()-aux.min())/15 + aux.min(),str(int(i))+'th',color='dimgrey',fontsize=sl-7,zorder=4)
            plt.text(np.nanpercentile(self.obs,int(i)),(aux.max()-aux.min())/1.05 + aux.min(),str(int(i))+'th',color='dimgrey',fontsize=sl-7)

        plt.ylim(ymax = aux.max(), ymin = aux.min())
        plt.xlim(xmax = aux.max(), xmin = aux.min())
        plt.locator_params(axis='y', nbins=7) ; plt.locator_params(axis='x', nbins=7)

        ax.set_xlabel(self.axisnames[1]); ax.set_ylabel(self.axisnames[0])

        if np.size(self.mlabels)>0:
            plt.legend(loc="upper left",fontsize=sl-2)

        plt.tight_layout()
        plt.savefig(self.ftag+'QQplot.png', dpi=200, facecolor='w', edgecolor='w',orientation='portrait', format='png',transparent=False, bbox_inches='tight', pad_inches=0.1)
        plt.close(fig1); del fig1, ax

    def scatterplot(self):
        '''
        Scatter plot.
        Inputs:
          Mandatory: model(s); observations (or any reference).
           the model array can include one or more model results, through the number of columns, while
           the observation array must be one-dimensional, with the same number of lines as the model.
          Optional: see object construction above.
        Output: png figure saved in the local directory where python is running or in the path given through ftag.
        Example:
          from pvalstats import ModelObsPlot
          mop=ModelObsPlot(ww3gfs,obs)
          mop.scatterplot()

          mop=ModelObsPlot(model=np.c_[model1[:],model2[:]],obs=buoydata[:],axisnames=["WW3","Buoy"],
              mlabels=["WW3T1","WW3T2"],ftag="/home/ricardo/testWW3/NewRun_")
          mop.scatterplot()
        '''

        if self.obs[0,:].shape[0]>50000:
            sk=int(np.round(np.float(self.obs[0,:].shape[0])/30000.,0))
        else:
            sk=1

        a=math.floor(np.nanmin(np.append(self.obs[:,::sk],self.model[:,::sk]))*100.)/100. ; b=math.ceil(np.nanmax(np.append(self.obs[:,::sk],self.model[:,::sk]))*100.)/100.
        famin=a-0.1*a; famax=b+0.1*a
        aux=np.linspace(famin,famax,100); del a,b

        # plot
        fig1 = plt.figure(1,figsize=(5,4.5)); ax = fig1.add_subplot(111)

        for i in range(0,self.model.shape[0]):
            b=np.array(self.obs[0,::sk]); a=np.array(self.model[i,::sk])
            ind=np.where((a*b)>-999.)[0]; a=np.copy(a[ind]); b=np.copy(b[ind]); del ind

            if (a.shape[0]<30) | (self.model.shape[0]>1):
                if np.size(self.mlabels)>0:
                    if self.mlabels[0] != '':
                        ax.scatter(b,a,color=self.color[i],marker=self.marker[i],label=self.mlabels[i],zorder=2)
                    else:
                        ax.scatter(b,a,color=self.color[i],marker=self.marker[i],zorder=2)

                else:
                    ax.scatter(b,a,color=self.color[i],marker=self.marker[i],zorder=2)

            elif (np.size(self.color)==1) & (self.model.shape[0]==1):
                ax.scatter(b,a,color=self.color[i],marker=self.marker[i],zorder=2)

            else:
                xy = np.vstack([a,b]); z = gaussian_kde(xy)(xy)
                if np.size(self.mlabels)>0:
                    if self.mlabels[0] != '':
                        ax.scatter(b,a, c=z, s=5,cmap=plt.cm.jet,label=self.mlabels[i],zorder=2)
                    else:
                        ax.scatter(b,a, c=z, s=5,cmap=plt.cm.jet,zorder=2)

                else:
                    ax.scatter(b,a, c=z, s=5,cmap=plt.cm.jet,zorder=2)

            if self.linreg:
                r = linregress(b,a)
                aregr = np.array(r.slope*aux + r.intercept )
                ax.plot(aux,aregr,color=self.color[i],ls='-',linewidth=1.,alpha=0.8,zorder=4)
                ax.plot(aux,aregr,color='k',ls=':',linewidth=0.7,alpha=0.7,zorder=4)
                if np.size(self.mlabels)>0:
                    print(self.ftag+"ScatterPlot "+self.mlabels[i]+": Slope "+np.str(np.round(float(r.slope),5))+", Intercept "+np.str(np.round(float(r.intercept),5)))
                else:
                    print(self.ftag+"ScatterPlot: Slope "+np.str(np.round(float(r.slope),5))+", Intercept "+np.str(np.round(float(r.intercept),5)))

                del r,aregr

            del a,b

        ax.plot(aux,aux,'k--', linewidth=1.,alpha=0.9,zorder=3)  # main diagonal
        plt.locator_params(axis='y', nbins=7) ; plt.locator_params(axis='x', nbins=7)

        ax.set_xlabel(self.axisnames[1]); ax.set_ylabel(self.axisnames[0])

        plt.grid(c='grey', ls=':', alpha=0.5,zorder=1)
        for i in np.array([50,80,95,99]):
            plt.axvline(x= np.nanpercentile(self.obs,int(i)),ls='--',color='grey',linewidth=1.,alpha=0.7,zorder=1)
            plt.text(np.nanpercentile(self.obs,int(i)),((famax-famin)/15)+famin,str(int(i))+'th',color='dimgrey',fontsize=sl-7,zorder=4)
            plt.text(np.nanpercentile(self.obs,int(i)),((famax-famin)/1.05)+famin,str(int(i))+'th',color='dimgrey',fontsize=sl-7,zorder=4)

        plt.gca().set_xlim(left=famin, right=famax); plt.gca().set_ylim(ymin=famin,ymax=famax)

        if np.size(self.mlabels)>0:
            if self.mlabels[0] != '':
                plt.legend(loc="upper left",fontsize=sl-2)

        plt.tight_layout()
        plt.savefig(self.ftag+'ScatterPlot.png', dpi=200, facecolor='w', edgecolor='w',orientation='portrait', format='png',transparent=False, bbox_inches='tight', pad_inches=0.1)
        plt.close(fig1); del fig1, ax

    def taylordiagram(self):
        '''
        Taylor Diagram using skill_metrics.
        https://github.com/PeterRochford/SkillMetrics
        Inputs:
          Mandatory: model(s); observations (or any reference).
           the model array can include one or more model results, through the number of columns, while
           the observation array must be one-dimensional, with the same number of lines as the model.
          Optional: see object construction above.
        Output: png figure saved in the local directory where python is running or in the path given through ftag.
        Example:
          from pvalstats import ModelObsPlot
          mop=ModelObsPlot(ww3gfs,obs)
          mop.taylordiagram()

          mop=ModelObsPlot(model=np.c_[model1[:],model2[:]],obs=buoydata[:],
              mlabels=["WW3T1","WW3T2"],ftag="/home/ricardo/testWW3/NewRun_")
          mop.taylordiagram()
        '''

        import skill_metrics as sm

        # Organizing labels for the Taylor Diagram
        if np.size(self.mlabels)>0:
            label = np.append(['Obs'],self.mlabels)
        else:
            if self.model.shape[0] > 1:
                label = np.array(['Obs'])
                for j in range(0,self.model.shape[0]):
                    label = np.append(label,['Model'+repr(j+1)])
            else:
                label = np.array(['Obs','Model'])

        # remove NaN
        ind=np.where( (np.mean(self.model,axis=0)>-999.) & (np.mean(self.obs,axis=0)>-999.) )
        if np.size(ind)>0:
            model=np.copy(self.model[:,ind[0]]); obs=np.copy(self.obs[:,ind[0]]); del ind
        else:
            raise ValueError(' No quality data available.')

        # plot
        fig1 = plt.figure(1,figsize=(7,6)); ax = fig1.add_subplot(111)
        # initial statistics
        ts = sm.taylor_statistics(model[0,:],obs[0,:])
        sdev=np.array(ts['sdev'][0]); crmsd=np.array(ts['crmsd'][0]); ccoef=np.array(ts['ccoef'][0])
        for i in range(0,model.shape[0]):
            ts = sm.taylor_statistics(model[i,:],obs[0,:])
            sdev=np.append(sdev,ts['sdev'][1])
            crmsd=np.append(crmsd,ts['crmsd'][1])
            ccoef=np.append(ccoef,ts['ccoef'][1])

        # formatting
        if crmsd.max()>0.8: npr=int(2)
        else: npr=int(3)
        tRMS = np.array(np.linspace(0,np.round(crmsd.max()+0.1*crmsd.max(),npr),5)).round(npr)

        if sdev.max()>0.8: npr=int(2)
        else: npr=int(3)
        axmax = np.round(sdev.max()+0.5*sdev.max(),npr)

        if (sdev.max()+0.4*sdev.max())>0.8: npr=int(2)
        else: npr=int(3)
        tSTD = np.array(np.linspace(sdev.min()-0.3*sdev.max(),np.round(sdev.max()+0.4*sdev.max(),npr),4)).round(npr)
        del npr

        fp1=1.07; fp2=1.03; fp3=0.055; fp4=13
        if np.size(label)>8:
            fp2=1.07; fp3=0.040; fp4=12
        elif np.size(label)>5:
            fp2=1.06; fp3=0.045

        slmax=0
        for i in range(0,np.size(label)):
            if len(label[i])>slmax:
                slmax=int(len(label[i]))

        if slmax>11:
            fp1=1.04; fp4=12
        elif slmax>8:
            fp1=1.04

        # loop through models
        for i in range(0,model.shape[0]):
            if i==0:
                sm.taylor_diagram(sdev.take([0,i+1]), crmsd.take([0,i+1]), ccoef.take([0,i+1]), locationColorBar = 'EastOutside',  markerColor = self.color[i],
                    # markerLegend = 'on', markerLabel = list(label.take([0,i+1])),
                    styleOBS = '-', colOBS = 'k', markerobs = 'o', titleOBS = 'Obs',
                    markerSize = 17, tickRMS = tRMS, axismax = axmax, alpha=0.7, tickSTD = tSTD,
                    tickRMSangle = 87, showlabelsRMS = 'on',
                    colRMS='dimgrey', styleRMS=':', widthRMS=1.0, titleRMS='off',
                    colSTD='k', styleSTD='-.', widthSTD=1.0, titleSTD ='on',
                    colCOR='k', styleCOR='--', widthCOR=1.0, titleCOR='on')

            else:
                sm.taylor_diagram(sdev.take([0,i+1]), crmsd.take([0,i+1]), ccoef.take([0,i+1]), overlay = 'on', locationColorBar = 'EastOutside',  markerColor = self.color[i],
                    # markerLegend = 'on', markerLabel = list(label.take([0,i+1])),
                    styleOBS = '-', colOBS = 'k', markerobs = 'o', titleOBS = 'Obs',
                    markerSize = 17, tickRMS = tRMS, axismax = axmax, alpha=0.5, tickSTD = tSTD,
                    tickRMSangle = 87, showlabelsRMS = 'on',
                    colRMS='dimgrey', styleRMS=':', widthRMS=1.0, titleRMS='off',
                    colSTD='k', styleSTD='-.', widthSTD=1.0, titleSTD ='on',
                    colCOR='k', styleCOR='--', widthCOR=1.0, titleCOR='on')

            if np.size(self.mlabels)>0:
                ax.text(fp1, fp2-(fp3*i), label[i+1], verticalalignment='bottom', horizontalalignment='right',
                    transform=ax.transAxes,color=self.color[i], fontsize=fp4)

        plt.tight_layout()
        plt.savefig(self.ftag+'TaylorDiagram.png', dpi=250, facecolor='w', edgecolor='w',orientation='portrait', format='png',transparent=False, bbox_inches='tight', pad_inches=0.1)
        plt.close(fig1); del fig1, ax

    def combinerrors(self):
        '''
        Combined Errors, Scatter Index and Normalized Bias
        Inputs:
          Mandatory: model(s); observations (or any reference).
           the model array can include one or more model results, through the number of columns, while
           the observation array must be one-dimensional, with the same number of lines as the model.
          Optional: see object construction above.
        Output: png figure saved in the local directory where python is running or in the path given through ftag.
        Example:
          from pvalstats import ModelObsPlot
          mop=ModelObsPlot(ww3gfs,obs)
          mop.combinerrors()

          mop=ModelObsPlot(model=np.c_[model1[:],model2[:]],obs=buoydata[:],axisnames=["WW3","Buoy"],
              mlabels=["WW3T1","WW3T2"],ftag="/home/ricardo/testWW3/NewRun_")
          mop.combinerrors()
        '''

        import mvalstats

        fig1 = plt.figure(1,figsize=(5,4.5)); ax = fig1.add_subplot(111)
        fauxnb=[];fauxsi=[];
        for i in range(0,self.model.shape[0]):
            aux = mvalstats.metrics(self.model[i,:],self.obs[0,:])
            if np.size(self.mlabels)>0:
                ax.scatter(aux[2],aux[5], s=40+150*aux[7]**5,color=self.color[i],edgecolors='k',linewidth=1,alpha=0.7,label=self.mlabels[i],zorder=3)
            else:
                ax.scatter(aux[2],aux[5], s=40+150*aux[7]**5,color=self.color[i],edgecolors='k',linewidth=1,alpha=0.7,zorder=3)

            fauxnb=np.append(fauxnb,np.abs(aux[2]))
            fauxsi=np.append(fauxsi,np.abs(aux[5]))
            del aux

        plt.grid(c='grey', ls=':', alpha=0.5,zorder=1)
        plt.axvline(x=0,ls='--',color='dimgrey',linewidth=1.,alpha=0.9,zorder=2)
        plt.locator_params(axis='y', nbins=5) ; plt.locator_params(axis='x', nbins=5)
        ax.set_xlabel("Normalized Bias"); ax.set_ylabel("Scatter Index")
        if (np.nanmax(fauxnb)-np.nanmin(fauxnb))<0.001:
            plt.ticklabel_format(axis="x", style="sci", scilimits=(0,0))
        if (np.nanmax(fauxsi)-np.nanmin(fauxsi))<0.001:
            plt.ticklabel_format(axis="y", style="sci", scilimits=(0,0))
        if np.size(self.mlabels)>0:
            plt.legend(fontsize=sl-2)

        plt.tight_layout()
        plt.savefig(self.ftag+'NBiasSI.png', dpi=200, facecolor='w', edgecolor='w',orientation='portrait', format='png',transparent=False, bbox_inches='tight', pad_inches=0.1)
        plt.close(fig1); del fig1, ax

    def pdf(self,result="no"):
        '''
        Probability Density Plot. Observations (shaded grey) and model(s)
        Inputs:
          Mandatory: model(s); observations (or any reference).
           the model array can include one or more model results, through the number of columns, while
           the observation array must be one-dimensional, with the same number of lines as the model.
          Optional: see object construction above.

        Output: two png figure saved in the local directory where python is running or in the path given through ftag.
        Example:
          from pvalstats import ModelObsPlot
          mop=ModelObsPlot(ww3gfs,obs)
          mop.pdf()

          mop=ModelObsPlot(model=np.c_[model1[:],model2[:]],obs=buoydata[:],vaxisname=["Hs(m), WW3 and Buoy"],
              mlabels=["WW3T1","WW3T2"],ftag="/home/ricardo/testWW3/NewRun_")
          mop.pdf()
        '''

        from scipy.stats import gaussian_kde

        # remove NaN
        ind=np.where( (np.mean(self.model,axis=0)>-999.) & (np.mean(self.obs,axis=0)>-999.) )
        if np.size(ind)>0:
            model=np.copy(self.model[:,ind[0]]); obs=np.copy(self.obs[:,ind[0]]); del ind
        else:
            raise ValueError(' No quality data available.')

        # plot
        px=np.linspace(np.nanmin([np.nanmin(obs),np.nanmin(model)]),np.nanmax([np.nanmax(obs),np.nanmax(model)])*0.99,100)

        # plt.close('all')
        fig1 = plt.figure(1,figsize=(5,5)); ax = fig1.add_subplot(111)
        dx = gaussian_kde(obs[0,:])
        ax.fill_between(px, 0., gaussian_filter(dx(px), 1.), color='grey', alpha=0.7,zorder=1)
        ax.plot(px,gaussian_filter(dx(px), 1.),color='dimgrey',linestyle='-',linewidth=0.5,alpha=0.7,zorder=2)
        mmax=np.array([1.])
        for i in range(0,model.shape[0]):
            de = gaussian_kde(model[i,:])
            if np.size(self.mlabels)>0:
                ax.plot(px,gaussian_filter(de(px), 1.),color=self.color[i],marker=self.marker[i],linestyle='-',linewidth=1.,alpha=0.7,label=self.mlabels[i],zorder=3)
            else:
                ax.plot(px,gaussian_filter(de(px), 1.),color=self.color[i],marker=self.marker[i],linestyle='-',linewidth=1.,alpha=0.7,zorder=3)

            mmax=np.append(mmax,np.nanmax(gaussian_filter(de(px), 1.)))
            del de

        plt.grid(c='grey', ls=':', alpha=0.5,zorder=1)
        plt.locator_params(axis='y', nbins=7) ; plt.locator_params(axis='x', nbins=7)
        ax.set_ylabel("Probability Density")
        ax.set_xlabel(self.vaxisname)

        if np.size(self.mlabels)>0:
            plt.legend(fontsize=sl-2)

        plt.tight_layout(); plt.axis('tight'); plt.ylim(ymin = -0.005)
        plt.savefig(self.ftag+'PDF.png', dpi=200, facecolor='w', edgecolor='w',orientation='portrait', format='png',transparent=False, bbox_inches='tight', pad_inches=0.1)
        ax.set_yscale( "log" ); plt.axis('tight');plt.ylim(ymin = 0.001);plt.ylim(ymax = np.nanmax(mmax))
        plt.savefig(self.ftag+'PDFlogscale.png', dpi=200, facecolor='w', edgecolor='w',orientation='portrait', format='png',transparent=False, bbox_inches='tight', pad_inches=0.1)
        plt.close(fig1); del fig1, ax, mmax

    def monthlystats(self,result="no"):

        if (np.size(self.fmonth)!=self.obs.shape[1]):
            raise ValueError(' Array with months should have the same length as the array of observations and model(s).')

        self.fmonth=np.array(self.fmonth).astype('int')
        import mvalstats
        months=np.arange(1,13,1)
        monthlyval=np.zeros((months.shape[0],self.model.shape[0],np.size(self.nerrm)),'f')*np.nan
        for i in range(0,months.shape[0]):
            indm=np.where((self.fmonth==months[i]) & (np.mean(self.model[:,:],axis=0)>-999) & (np.mean(self.obs[:,:],axis=0)>-999))
            if np.size(indm)>1:
                indm=indm[0]
                for j in range(0,self.model.shape[0]):
                    monthlyval[i,j,:] = mvalstats.metrics(self.model[j,indm],self.obs[0,indm])

        for i in range(0,len(self.nerrm)):

            fig1 = plt.figure(1,figsize=(9,4)); ax = fig1.add_subplot(111)
            for j in range(0,self.model.shape[0]):
                if np.size(self.mlabels)>0:
                    if self.mlabels[0] != '':
                        ax.plot(months,monthlyval[:,j,i],self.color[j],marker=self.marker[j],label=self.mlabels[j], linewidth=2.,zorder=2)
                    else:
                        ax.plot(months,monthlyval[:,j,i],self.color[j],marker=self.marker[j],linewidth=2.,zorder=2)

                else:
                    ax.plot(months,monthlyval[:,j,i],self.color[j],marker=self.marker[j],linewidth=2.,zorder=2)

            ax.set_xticks(months)
            ax.set_xlabel('Month',size=sl)

            if np.size(self.mlabels)>0:
                plt.legend(fontsize=sl-2)

            if self.vaxisname != "Model and Observations":
                ax.set_ylabel(self.nerrm[i]+" "+self.vaxisname)
            else:
                ax.set_ylabel(self.nerrm[i])

            plt.tight_layout();plt.axis('tight')
            ax.grid(c='dimgrey', ls='--', alpha=0.3)
            plt.xlim(xmin = 0.9, xmax = 12.1)
            plt.savefig(self.ftag+"MonthlyError_"+self.nerrm[i]+".png", dpi=200, facecolor='w', edgecolor='w',orientation='portrait', format='png',transparent=False, bbox_inches='tight', pad_inches=0.1)
            plt.close(fig1); del fig1, ax

        if result!="no":
            print(" Returning two arrays with error_metrics and metrics_names, from ModelObsPlot.monthlystats")
            print(" The error_metrics array has dimensions [month,models,metrics_names]")
            return monthlyval,self.nerrm

    def errxfctime(self,result="no"):

        if np.size(self.fctime)<=1:
            raise ValueError('Forecast time array must be informed.')

        import mvalstats

        if np.size(self.fctintervals)<=1:
            self.fctintervals = np.unique(self.fctime)

        if np.size(self.fctxt)<=1:
            self.fctxt=[]
            for i in range(len(self.fctintervals) - 1):
                self.fctxt.append(f"{self.fctintervals[i]}-{self.fctintervals[i+1]}")

            self.fctxt=np.array(self.fctxt)

        if self.fctxt.shape[0] == (self.fctintervals.shape[0]):
            valarr=0
        elif self.fctxt.shape[0] == (self.fctintervals.shape[0]-1):
            valarr=1
        else:
            raise ValueError('The size of fctxt must be equal to np.size(fctintervals) or np.size(fctintervals-1) ')

        errm=np.zeros((self.fctxt.shape[0],self.model.shape[0],np.size(self.nerrm)),'f')*np.nan
        for i in range(0,self.fctxt.shape[0]):
            if valarr==1:
                ind=np.where( (self.fctime>=self.fctintervals[i]) & (self.fctime<=self.fctintervals[i+1]) )
            else:
                ind=np.where(self.fctime==self.fctintervals[i])

            if np.size(ind)>1:
                ind=ind[0]
                obs=np.array(self.obs[0,ind])
                for j in range(0,self.model.shape[0]):
                    errm[i,j,:] = mvalstats.metrics(self.model[j,ind],obs)

                del obs

            del ind

        # plot
        for i in range(0,len(self.nerrm)):

            fig1 = plt.figure(1,figsize=(9,4)); ax = fig1.add_subplot(111)
            for j in range(0,self.model.shape[0]):
                if np.size(self.mlabels)>0:
                    if self.mlabels[0] != '':
                        ax.plot(self.fctxt,errm[:,j,i],self.color[j],marker=self.marker[j],label=self.mlabels[j], linewidth=2.,zorder=2)
                    else:
                        ax.plot(self.fctxt,errm[:,j,i],self.color[j],marker=self.marker[j],linewidth=2.,zorder=2)

                else:
                    ax.plot(self.fctxt,errm[:,j,i],self.color[j],marker=self.marker[j],linewidth=2.,zorder=2)

            ax.set_xticks(self.fctxt)

            if self.fctunits:
                ax.set_xlabel('Forecast Lead Time ('+self.fctunits+')',size=sl)
            else:
                ax.set_xlabel('Forecast Lead Time ',size=sl)

            if self.vaxisname != "Model and Observations":
                ax.set_ylabel(self.nerrm[i]+" "+self.vaxisname)
            else:
                ax.set_ylabel(self.nerrm[i])

            if np.size(self.mlabels)>0:
                plt.legend(loc='best',fontsize=sl-2)

            ax.grid(c='dimgrey', ls='--', alpha=0.3)
            plt.tight_layout();plt.axis('tight')

            if np.size(self.fctxticks)>1:
                ax.set_xticks(self.fctxticks)
                plt.xlim(xmin = np.min(self.fctxticks)*0.9, xmax = np.max(self.fctxticks)*1.01)

            plt.savefig(self.ftag+"ErrXForecastTime_"+self.nerrm[i]+".png", dpi=200, facecolor='w', edgecolor='w',orientation='portrait', format='png',transparent=False, bbox_inches='tight', pad_inches=0.1)
            plt.close(fig1); del fig1, ax

        if result!="no":
            print(" Returning two arrays with error_metrics and metrics_names, from ModelObsPlot.errxfctime")
            print(" The error_metrics array has dimensions [forecastLeadTime,models,metrics_names]")
            return errm,self.nerrm

    # --- Ensemble Validation ---

    # Rank Histogram (Talagrand diagram)
    # https://journals.ametsoc.org/view/journals/mwre/129/3/1520-0493_2001_129_0550_iorhfv_2.0.co_2.xml
    def rankhist(self,result="no"):

        from scipy.stats import rankdata
        # remove NaN
        ind=np.where( (np.mean(self.model,axis=0)>-999.) & (np.mean(self.obs,axis=0)>-999.) )
        if np.size(ind)>0:
            model=np.copy(self.model[:,ind[0]]); obs=np.copy(self.obs[0,ind[0]]); del ind
        else:
            raise ValueError(' No quality data available.')

        combined=np.vstack((obs[np.newaxis],model))
        ranks=np.apply_along_axis(lambda x: rankdata(x,method='min'),0,combined)
        ties=np.sum(ranks[0]==ranks[1:], axis=0)
        ranks=ranks[0]
        tie=np.unique(ties)
        for i in range(1,len(tie)):
            index=ranks[ties==tie[i]]
            ranks[ties==tie[i]]=[np.random.randint(index[j],index[j]+tie[i]+1,tie[i])[0] for j in range(len(index))]

        result = np.histogram(ranks, bins=np.linspace(0.5, combined.shape[0]+0.5, combined.shape[0]+1))
        # plot
        fig1 = plt.figure(1,figsize=(6,5)); ax1 = fig1.add_subplot(111)
        fresult=result[0]/np.sum(result[0])
        rxaxis=range(1,len(result[0])+1)
        plt.bar(rxaxis,fresult,color='grey', edgecolor='k')
        # plt.ylim(ymax = 1.01, ymin = -0.01)
        ax1.set_xticks(np.arange(1,13,1))
        ax1.set_xlabel('Rank',size=sl); ax1.set_ylabel('Probability',size=sl)
        plt.tight_layout(); # plt.axis('tight')
        plt.grid(c='grey', ls='--', alpha=0.3)
        plt.savefig(self.ftag+'RankHistogram.png', dpi=200, facecolor='w', edgecolor='w',orientation='portrait', format='png',transparent=False, bbox_inches='tight', pad_inches=0.1)
        plt.close(fig1); del fig1, ax1, result

        if result!="no":
            print(" Returning two arrays with probabilities (y-axis) and ensemble_members+1 (x-axis), from ModelObsPlot.rankhist")
            return fresult,rxaxis

    def spreadxfctime(self,result="no"):

        if np.size(self.fctime)<=1:
            raise ValueError('Forecast time array must be informed.')

        if self.model.shape[0]<=1:
            raise ValueError('Forecast must have at least 2 members.')

        if np.size(self.fctintervals)<=1:
            self.fctintervals = np.unique(self.fctime)

        if np.size(self.fctxt)<=1:
            self.fctxt=[]
            for i in range(len(self.fctintervals) - 1):
                self.fctxt.append(f"{self.fctintervals[i]}-{self.fctintervals[i+1]}")

            self.fctxt=np.array(self.fctxt)

        if self.fctxt.shape[0] == (self.fctintervals.shape[0]):
            valarr=0
        elif self.fctxt.shape[0] == (self.fctintervals.shape[0]-1):
            valarr=1
        else:
            raise ValueError('The size of fctxt must be equal to np.size(fctintervals) or np.size(fctintervals-1) ')

        mspread=np.zeros((self.fctxt.shape[0]),'f')*np.nan
        for i in range(0,self.fctxt.shape[0]):
            if valarr==1:
                ind=np.where( (self.fctime>=self.fctintervals[i]) & (self.fctime<=self.fctintervals[i+1]) )
            else:
                ind=np.where(self.fctime==self.fctintervals[i])

            if np.size(ind)>1:
                ind=ind[0]
                # unbiased std using Bessel's correction (ddof=1)
                mspread[i]=np.nanmean(np.nanstd(self.model[:,ind],axis=0,ddof=1))

        # plot Spread
        fig1 = plt.figure(1,figsize=(9,4)); ax = fig1.add_subplot(111)
        ax.plot(self.fctxt,mspread,'k',marker='.',linewidth=2.,zorder=2)
        ax.set_xticks(self.fctxt)
        if self.fctunits:
            ax.set_xlabel('Forecast Lead Time ('+self.fctunits+')',size=sl)
        else:
            ax.set_xlabel('Forecast Lead Time ',size=sl)

        if self.vaxisname != "Model and Observations":
            ax.set_ylabel("Spread "+self.vaxisname)
        else:
            ax.set_ylabel("Spread")

        ax.grid(c='dimgrey', ls='--', alpha=0.3)
        plt.tight_layout();plt.axis('tight')

        if np.size(self.fctxticks)>1:
            ax.set_xticks(self.fctxticks)
            plt.xlim(xmin = np.min(self.fctxticks)*0.9, xmax = np.max(self.fctxticks)*1.01)

        plt.savefig(self.ftag+"SpreadXForecastTime.png", dpi=200, facecolor='w', edgecolor='w',orientation='portrait', format='png',transparent=False, bbox_inches='tight', pad_inches=0.1)
        plt.close(fig1); del fig1, ax

        if result!="no":
            print(" Returning SPREAD array, from ModelObsPlot.spreadxfctime")
            return mspread

    def crpsxfctime(self,result="no"):

        import properscoring as ps

        if np.size(self.fctime)<=1:
            raise ValueError('Forecast time array must be informed.')

        if self.model.shape[0]<=1:
            raise ValueError('Forecast must have at least 2 members.')

        if np.size(self.fctintervals)<=1:
            self.fctintervals = np.unique(self.fctime)

        if np.size(self.fctxt)<=1:
            self.fctxt=[]
            for i in range(len(self.fctintervals) - 1):
                self.fctxt.append(f"{self.fctintervals[i]}-{self.fctintervals[i+1]}")

            self.fctxt=np.array(self.fctxt)

        if self.fctxt.shape[0] == (self.fctintervals.shape[0]):
            valarr=0
        elif self.fctxt.shape[0] == (self.fctintervals.shape[0]-1):
            valarr=1
        else:
            raise ValueError('The size of fctxt must be equal to np.size(fctintervals) or np.size(fctintervals-1) ')

        crps=np.zeros((self.fctxt.shape[0]),'f')*np.nan
        for i in range(0,self.fctxt.shape[0]):
            if valarr==1:
                ind=np.where( (self.fctime>=self.fctintervals[i]) & (self.fctime<=self.fctintervals[i+1]) )
            else:
                ind=np.where(self.fctime==self.fctintervals[i])

            if np.size(ind)>1:
                ind=ind[0]
                crps[i]=np.nanmean(ps.crps_ensemble( np.array(self.obs[0,ind]),self.model[:,ind].T))

        # plot CRPS
        fig1 = plt.figure(1,figsize=(9,4)); ax = fig1.add_subplot(111)
        ax.plot(self.fctxt,crps,'k',marker='.',linewidth=2.,zorder=2)
        ax.set_xticks(self.fctxt)
        if self.fctunits:
            ax.set_xlabel('Forecast Lead Time ('+self.fctunits+')',size=sl)
        else:
            ax.set_xlabel('Forecast Lead Time ',size=sl)

        if self.vaxisname != "Model and Observations":
            ax.set_ylabel("CRPS "+self.vaxisname)
        else:
            ax.set_ylabel("CRPS")

        ax.grid(c='dimgrey', ls='--', alpha=0.3)
        plt.tight_layout();plt.axis('tight')

        if np.size(self.fctxticks)>1:
            ax.set_xticks(self.fctxticks)
            plt.xlim(xmin = np.min(self.fctxticks)*0.9, xmax = np.max(self.fctxticks)*1.01)

        plt.savefig(self.ftag+"CRPSXForecastTime.png", dpi=200, facecolor='w', edgecolor='w',orientation='portrait', format='png',transparent=False, bbox_inches='tight', pad_inches=0.1)
        plt.close(fig1); del fig1, ax

        if result!="no":
            print(" Returning CRPS array, from ModelObsPlot.crpsxfctime")
            return crps

    # https://www.psl.noaa.gov/people/tom.hamill/ROC_problems.pdf
    def brierxfctime(self,threshold=None,result="no"):

        from sklearn.metrics import brier_score_loss

        if np.size(self.fctime)<=1:
            raise ValueError('Forecast time array must be informed.')

        if self.model.shape[0]<=1:
            raise ValueError('Forecast must have at least 2 members.')

        if np.size(self.fctintervals)<=1:
            self.fctintervals = np.unique(self.fctime)

        if np.size(self.fctxt)<=1:
            self.fctxt=[]
            for i in range(len(self.fctintervals) - 1):
                self.fctxt.append(f"{self.fctintervals[i]}-{self.fctintervals[i+1]}")

            self.fctxt=np.array(self.fctxt)

        if self.fctxt.shape[0] == (self.fctintervals.shape[0]):
            valarr=0
        elif self.fctxt.shape[0] == (self.fctintervals.shape[0]-1):
            valarr=1
        else:
            raise ValueError('The size of fctxt must be equal to np.size(fctintervals) or np.size(fctintervals-1) ')

        if threshold==None:
            threshold=np.nanmean(self.obs[0,:])

        num_ensemble_members = int(self.model.shape[0])
        prob_forecasts = [1/num_ensemble_members] * num_ensemble_members

        brier=np.zeros((self.fctxt.shape[0]),'f')*np.nan
        for i in range(0,self.fctxt.shape[0]):
            if valarr==1:
                ind=np.where( (self.fctime>=self.fctintervals[i]) & (self.fctime<=self.fctintervals[i+1]) )
            else:
                ind=np.where(self.fctime==self.fctintervals[i])

            if np.size(ind)>1:
                ind=ind[0]
                if self.obs[0,ind].shape[0]>50000:
                    sk=int(np.round(np.float(self.obs[0,ind].shape[0])/30000.,0))
                else:
                    sk=1

                if i==0 and sk>1:
                    print(" Calculating Brier Score for a large array, this may take some time ...")

                model = self.model[:,ind][:,::sk]
                obs = self.obs[0,ind][::sk]

                true_binary=[]; binary_forecasts=[]; fprob_forecasts=[]
                for j in range(0,obs.shape[0]):
                    atrue_binary = 1 if obs[j] >= threshold else 0
                    abinary_forecasts = [1 if auxv >= threshold else 0 for auxv in model[:,j]]
                    atrue_binary = [atrue_binary]*len(abinary_forecasts)
                    true_binary=np.append(true_binary,atrue_binary)
                    binary_forecasts=np.append(binary_forecasts,abinary_forecasts)
                    fprob_forecasts=np.append(fprob_forecasts,prob_forecasts)
                    del atrue_binary,abinary_forecasts

                brier[i] = brier_score_loss(true_binary, binary_forecasts, sample_weight=fprob_forecasts)
                del model,obs,true_binary,binary_forecasts,fprob_forecasts

                if sk>1:
                    print("   - Brier Score, Ok: "+repr(self.fctxt[i])+"  of "+repr(self.fctxt.shape[0]))

            del ind

        # plot brier_score
        fig1 = plt.figure(1,figsize=(9,4)); ax = fig1.add_subplot(111)
        ax.plot(self.fctxt,brier,'k',marker='.',linewidth=2.,zorder=2)
        ax.set_xticks(self.fctxt)
        if self.fctunits:
            ax.set_xlabel('Forecast Lead Time ('+self.fctunits+')',size=sl)
        else:
            ax.set_xlabel('Forecast Lead Time ',size=sl)

        if self.vaxisname != "Model and Observations":
            ax.set_ylabel("Brier Score "+self.vaxisname)
        else:
            ax.set_ylabel("Brier Score")

        ax.grid(c='dimgrey', ls='--', alpha=0.3)
        plt.tight_layout();plt.axis('tight')

        if np.size(self.fctxticks)>1:
            ax.set_xticks(self.fctxticks)
            plt.xlim(xmin = np.min(self.fctxticks)*0.9, xmax = np.max(self.fctxticks)*1.01)

        plt.savefig(self.ftag+"BrierScoreXForecastTime.png", dpi=200, facecolor='w', edgecolor='w',orientation='portrait', format='png',transparent=False, bbox_inches='tight', pad_inches=0.1)
        plt.close(fig1); del fig1, ax

        if result!="no":
            print(" Returning brier_score array, from ModelObsPlot.brierxfctime")
            return brier

#    def rocaucxfctime(self,result="no"):

# --------------------------------
# ------ Global Map --------------

def gsmooth(data,gflev=1):
    """
    Gaussian filter for spatial smoothness.
    data is a 2-dimensional field. data.shape must be equal to 2.
    gflev is the level of smoothing.
    """
    from scipy.ndimage.filters import gaussian_filter
    data=np.array(data)
    gflev=float(gflev)

    fdata1=gaussian_filter(data,gflev); fdata2=gaussian_filter(data,gflev/2)
    fdata=np.nanmean(np.append(np.append(np.array([data]),np.array([fdata1]),axis=0),np.array([fdata2]),axis=0),axis=0)

    return fdata
    del fdata1,fdata

class GlobalMapPlot:
    def __init__(self,lat=None,lon=None,gdata=None,latmin=-85.,latmax=85.,
        ftag=None,levels=None,palette=None,pextend="max", contcolor="lightgrey",sl=13):

        self.lat = lat
        self.lon = lon
        self.latmin = latmin
        self.latmax = latmax
        self.gdata = gdata
        self.levels = levels
        self.pextend = pextend
        self.contcolor = contcolor
        self.sl = sl

        self.ftag = ftag if ftag is not None else str(os.getcwd()) + '/GlobalMap.png'
        self.palette = palette if palette is not None else plt.cm.jet

        if self.gdata is None or self.lon is None or self.lat is None:
            raise ValueError("Missing required input arguments.")
            return

        matplotlib.rcParams.update({'font.size': sl}); plt.rc('font', size=sl)
        matplotlib.rc('xtick', labelsize=sl); matplotlib.rc('ytick', labelsize=sl); matplotlib.rcParams.update({'font.size': sl})

    def plot(self):

        if self.levels is None:
            if self.pextend == "both":
                aux=np.nanpercentile(np.abs(self.gdata), 99.)
                self.levels = np.linspace(-aux,aux, 101); del aux
            else:
                self.levels = np.linspace(0, np.nanpercentile(self.gdata, 99.), 101)

        plt.figure(figsize=(7, 4))
        # ax = plt.axes(projection=ccrs.PlateCarree(central_longitude=-90))
        ax = plt.axes(projection=ccrs.PlateCarree(central_longitude=180))
        ax.set_extent([0., 360., self.latmin, self.latmax], crs=ccrs.PlateCarree())
        gl = ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True, linewidth=0.5, color='grey', alpha=0.5, linestyle='--')
        gl.xlabel_style = {'size': self.sl-4, 'color': 'k', 'rotation': 0}
        gl.ylabel_style = {'size': self.sl-4, 'color': 'k', 'rotation': 0}
        cs = ax.contourf(self.lon, self.lat, self.gdata, self.levels, cmap=self.palette, zorder=1, extend=self.pextend, transform=ccrs.PlateCarree())
        ax.add_feature(cfeature.OCEAN, facecolor=("white"))
        ax.add_feature(cfeature.LAND, facecolor=(self.contcolor), edgecolor='grey', linewidth=0.2, zorder=2)
        ax.add_feature(cfeature.BORDERS, edgecolor='dimgrey', linestyle='-', linewidth=0.2, alpha=1, zorder=3)
        ax.coastlines(resolution='110m', color='dimgrey', linewidth=0.2, linestyle='-', alpha=1, zorder=4)
        plt.tight_layout()
        ax = plt.gca()
        pos = ax.get_position()
        l, b, w, h = pos.bounds
        cax = plt.axes([l + 0.07, b - 0.075, w - 0.12, 0.025])
        cbar = plt.colorbar(cs, cax=cax, orientation='horizontal')
        cbar.ax.tick_params(labelsize=self.sl-3)
        plt.axes(ax)
        plt.savefig(self.ftag, dpi=200, facecolor='w', edgecolor='w',
                    orientation='portrait', format='png', transparent=False, bbox_inches='tight', pad_inches=0.1)
        plt.close('all')
        del ax


import numpy as np
import cv2
import os
import json
import collections
from tqdm import tqdm
def delCenter(center, imgShape, minStr=8):
	centerF = np.flip(np.copy(center))[:]
	tempF = centerF[1:]-centerF[0:-1] < -minStr
	tempF = np.append(tempF, [True])

	tempNum = 0
	for i in range(len(tempF)-1, -1, -1):
		if(tempF[i] == True):
			tempNum = centerF[i]
		else:
			centerF[i] = tempNum

	centerN = np.copy(center)
	temp = centerN[1:]-centerN[0:-1] > minStr
	temp = np.append(temp, [True])
	tempNum = 0
	for i in range(len(temp)-1, -1, -1):
		if(temp[i] == True):
			tempNum = centerN[i]
		else:
			centerN[i] = tempNum
	centerM = (centerN*2//3 + np.flip(centerF)//3)

	return centerM
def getCenterList(rects, imgShape):
	centerX = []
	centerY = []

	sumX, sumY = 0, 0
	for rect in rects:
		centerX.append((rect[2]+rect[0])//2)
		centerY.append((rect[3]+rect[1])//2)

		sumX += rect[2]-rect[0]
		sumY += rect[3]-rect[1]
	averageX = sumX/len(centerX)
	averageY = sumY/len(centerY)
	# print(centerX, centerY)
	centerX = np.array(centerX)
	centerY = np.array(centerY)
	centerX.sort()
	centerY.sort()
	# print(averageX, averageY)
	centerX = delCenter(centerX, imgShape, minStr=averageX/5)
	centerY = delCenter(centerY, imgShape, minStr=averageY/5)
	# print("after", centerX, centerY)

	# temp = np.bincount(centerX)
	# mask = temp>3
	# temp = temp[mask]
	# centerX = np.where(mask == True)[0]
	# mean, std = np.mean(temp), np.std(temp)
	# print(temp, np.mean(temp), np.std(temp))
	# mask = temp > mean/2
	# temp = temp[mask]
	# centerX = centerX[mask]
	# print(temp)
	# print(centerX)

	temp = np.bincount(centerX) > 2
	# print(np.bincount(centerX)[temp])
	centerX = np.where(temp == True)[0]

	temp = np.bincount(centerY) > 2
	# print(np.bincount(centerY)[temp])
	centerY = np.where(temp == True)[0]

	# print("cut", centerX, centerY)
	return centerX, centerY
def findGrid(rects, imgShape):
	centerX, centerY = getCenterList(rects, imgShape)
	# print(centerX, centerY)
	return centerX, centerY


import os

from absl.testing import parameterized
import tensorflow as tf
from tfx.orchestration import metadata
from tfx.orchestration.experimental.core import async_pipeline_task_gen as asptg
from tfx.orchestration.experimental.core import task as task_lib
from tfx.orchestration.experimental.core import task_queue as tq
from tfx.orchestration.experimental.core import test_utils as otu
from tfx.orchestration.portable import test_utils as tu
from tfx.proto.orchestration import pipeline_pb2

from ml_metadata.proto import metadata_store_pb2

class AsyncPipelineTaskGeneratorTest(tu.TfxTest, parameterized.TestCase):

  def setUp(self):
    super(AsyncPipelineTaskGeneratorTest, self).setUp()
    pipeline_root = os.path.join(
        os.environ.get('TEST_UNDECLARED_OUTPUTS_DIR', self.get_temp_dir()),
        self.id())

    # Makes sure multiple connections within a test always connect to the same
    # MLMD instance.
    metadata_path = os.path.join(pipeline_root, 'metadata', 'metadata.db')
    self._metadata_path = metadata_path
    connection_config = metadata.sqlite_metadata_connection_config(
        metadata_path)
    connection_config.sqlite.SetInParent()
    self._mlmd_connection = metadata.Metadata(
        connection_config=connection_config)

    # Sets up the pipeline.
    pipeline = pipeline_pb2.Pipeline()
    self.load_proto_from_text(
        os.path.join(
            os.path.dirname(__file__), 'testdata', 'async_pipeline.pbtxt'),
        pipeline)
    self._pipeline = pipeline
    self._pipeline_info = pipeline.pipeline_info
    self._pipeline_runtime_spec = pipeline.runtime_spec
    self._pipeline_runtime_spec.pipeline_root.field_value.string_value = (
        pipeline_root)

    # Extracts components.
    self._example_gen = pipeline.nodes[0].pipeline_node
    self._transform = pipeline.nodes[1].pipeline_node
    self._trainer = pipeline.nodes[2].pipeline_node

    self._task_queue = tq.TaskQueue()

  def _verify_node_execution_task(self, node, execution_id, task):
    self.assertEqual(
        task_lib.ExecNodeTask.create(self._pipeline, node, execution_id), task)

  def _dequeue_and_test(self, use_task_queue, node, execution_id):
    if use_task_queue:
      task = self._task_queue.dequeue()
      self._task_queue.task_done(task)
      self._verify_node_execution_task(node, execution_id, task)

  def _generate_and_test(self, use_task_queue, num_initial_executions,
                         num_tasks_generated, num_new_executions,
                         num_active_executions):
    """Generates tasks and tests the effects."""
    with self._mlmd_connection as m:
      executions = m.store.get_executions()
    self.assertLen(
        executions, num_initial_executions,
        'Expected {} execution(s) in MLMD.'.format(num_initial_executions))
    task_gen = asptg.AsyncPipelineTaskGenerator(
        self._mlmd_connection, self._pipeline,
        self._task_queue.contains_task_id)
    tasks = task_gen.generate()
    self.assertLen(
        tasks, num_tasks_generated,
        'Expected {} task(s) to be generated.'.format(num_tasks_generated))
    with self._mlmd_connection as m:
      executions = m.store.get_executions()
    num_total_executions = num_initial_executions + num_new_executions
    self.assertLen(
        executions, num_total_executions,
        'Expected {} execution(s) in MLMD.'.format(num_total_executions))
    active_executions = [
        e for e in executions
        if e.last_known_state == metadata_store_pb2.Execution.RUNNING
    ]
    self.assertLen(
        active_executions, num_active_executions,
        'Expected {} active execution(s) in MLMD.'.format(
            num_active_executions))
    if use_task_queue:
      for task in tasks:
        self._task_queue.enqueue(task)
    return tasks, active_executions

  def test_no_tasks_generated_when_new(self):
    task_gen = asptg.AsyncPipelineTaskGenerator(self._mlmd_connection,
                                                self._pipeline,
                                                lambda _: False)
    tasks = task_gen.generate()
    self.assertEmpty(tasks, 'Expected no task generation when no inputs.')
    with self._mlmd_connection as m:
      self.assertEmpty(
          m.store.get_executions(),
          'There must not be any registered executions since no tasks were '
          'geerated.')

  @parameterized.parameters(False, True)
  def test_task_generation(self, use_task_queue):
    """Tests async pipeline task generation.

    Args:
      use_task_queue: If task queue is enabled, new tasks are only generated
        if a task with the same task_id does not already exist in the queue.
        `use_task_queue=False` is useful to test the case of task generation
        when task queue is empty (for eg: due to orchestrator restart).
    """
    # Simulate that ExampleGen has already completed successfully.
    otu.fake_example_gen_run(self._mlmd_connection, self._example_gen, 1, 1)

    # Before generation, there's 1 execution in MLMD.
    with self._mlmd_connection as m:
      executions = m.store.get_executions()
    self.assertLen(executions, 1)

    # Generate once.
    with self.subTest(generate=1):
      tasks, active_executions = self._generate_and_test(
          use_task_queue,
          num_initial_executions=1,
          num_tasks_generated=1,
          num_new_executions=1,
          num_active_executions=1)
      self._verify_node_execution_task(self._transform, active_executions[0].id,
                                       tasks[0])

    # No new effects if generate called again.
    with self.subTest(generate=2):
      tasks, active_executions = self._generate_and_test(
          use_task_queue,
          num_initial_executions=2,
          num_tasks_generated=0 if use_task_queue else 1,
          num_new_executions=0,
          num_active_executions=1)
      execution_id = active_executions[0].id
      if not use_task_queue:
        self._verify_node_execution_task(self._transform, execution_id,
                                         tasks[0])

    # Mark transform execution complete.
    otu.fake_transform_output(self._mlmd_connection, self._transform,
                              active_executions[0])
    # Dequeue the corresponding task if task queue is enabled.
    self._dequeue_and_test(use_task_queue, self._transform,
                           active_executions[0].id)

    # Trainer execution task should be generated next.
    with self.subTest(generate=3):
      tasks, active_executions = self._generate_and_test(
          use_task_queue,
          num_initial_executions=2,
          num_tasks_generated=1,
          num_new_executions=1,
          num_active_executions=1)
      execution_id = active_executions[0].id
      self._verify_node_execution_task(self._trainer, execution_id, tasks[0])

    # Mark the trainer execution complete.
    otu.fake_trainer_output(self._mlmd_connection, self._trainer,
                            active_executions[0])
    # Dequeue the corresponding task if task queue is enabled.
    self._dequeue_and_test(use_task_queue, self._trainer, execution_id)

    # No more tasks should be generated as there are no new inputs.
    with self.subTest(generate=4):
      self._generate_and_test(
          use_task_queue,
          num_initial_executions=3,
          num_tasks_generated=0,
          num_new_executions=0,
          num_active_executions=0)

    # Fake another ExampleGen run.
    otu.fake_example_gen_run(self._mlmd_connection, self._example_gen, 1, 1)

    # Both transform and trainer tasks should be generated as they both find
    # new inputs.
    with self.subTest(generate=4):
      tasks, active_executions = self._generate_and_test(
          use_task_queue,
          num_initial_executions=4,
          num_tasks_generated=2,
          num_new_executions=2,
          num_active_executions=2)
      self._verify_node_execution_task(self._transform, active_executions[0].id,
                                       tasks[0])
      self._verify_node_execution_task(self._trainer, active_executions[1].id,
                                       tasks[1])

    # Re-generation will produce the same tasks when task queue enabled.
    with self.subTest(generate=5):
      tasks, active_executions = self._generate_and_test(
          use_task_queue,
          num_initial_executions=6,
          num_tasks_generated=0 if use_task_queue else 2,
          num_new_executions=0,
          num_active_executions=2)
      if not use_task_queue:
        self._verify_node_execution_task(self._transform,
                                         active_executions[0].id, tasks[0])
        self._verify_node_execution_task(self._trainer, active_executions[1].id,
                                         tasks[1])

    # Mark transform execution complete.
    otu.fake_transform_output(self._mlmd_connection, self._transform,
                              active_executions[0])
    # Dequeue the corresponding task.
    self._dequeue_and_test(use_task_queue, self._transform,
                           active_executions[0].id)

    # Mark the trainer execution complete.
    otu.fake_trainer_output(self._mlmd_connection, self._trainer,
                            active_executions[1])
    self._dequeue_and_test(use_task_queue, self._trainer,
                           active_executions[1].id)

    # Trainer should be triggered again due to transform producing new output.
    with self.subTest(generate=6):
      tasks, active_executions = self._generate_and_test(
          use_task_queue,
          num_initial_executions=6,
          num_tasks_generated=1,
          num_new_executions=1,
          num_active_executions=1)
      self._verify_node_execution_task(self._trainer, active_executions[0].id,
                                       tasks[0])

    # Finally, no new tasks once trainer completes.
    otu.fake_trainer_output(self._mlmd_connection, self._trainer,
                            active_executions[0])
    # Dequeue corresponding task.
    self._dequeue_and_test(use_task_queue, self._trainer,
                           active_executions[0].id)
    with self.subTest(generate=7):
      self._generate_and_test(
          use_task_queue,
          num_initial_executions=7,
          num_tasks_generated=0,
          num_new_executions=0,
          num_active_executions=0)
    if use_task_queue:
      self.assertTrue(self._task_queue.is_empty())

if __name__ == '__main__':
  tf.test.main()


#!/usr/bin/env python

"""
Name:    simple_sv_annotation.py

Purpose: Simplify SV annotations from snpEff to highlight exon impact.

Input:   vcf file with SVs annotated with snpEff 4.3 or higher, text file with known fusion pairs, text file with genes of interest

Output:  vcf file with SV annotation field with simplified annotation

Usage:   simple_sv_annotation.py [OPTIONS]

Authors:  David Jenkins (david.jenkins1@astrazeneca.com/dfj@bu.edu), Miika Ahdesmaki (miika.ahdesmaki @ astrazeneca.com / live.fi)

Notes:
    - Simplified with SnpEff 4.3 updates that add better annotation for fusion events resulting from all BND/INV/DUP/DEL
    - No simplified annotation for within-exon deletions (frameshifts, in-frame dels) as not classified as SVs
    - Soft filters intergenic events that are not involved with genes directly

Current scheme with priority 1(high)-3(low)
- exon loss
 . on prioritisation gene list (2)
 . not on gene list (3)
- gene_fusion
. paired (hits two genes)
.. on list of known pairs (1)
.. not on list of known pairs
... one or two genes on gene list (2)
... neither gene on gene list (3)
. unpaired (hits one gene)
.. on gene list (2)
.. not on gene list (3)
- upstream or downstream of gene list genes (3)
- other variant: "Intergenic" in FILTER
- missing ANN or SVTYPE: "Intergenic" in FILTER

See the provided README.md file for more information
"""

from __future__ import print_function
import argparse, os, vcf, sys
try:
    import pysam
except ImportError:
    pysam = None

def main(vcf_in, outfile, exon_nums, args):
    """Adds additional header information, opens the infile for reading, opens
    the outfile for writing, and iterates through the records to identify
    records that are candidates for adding the simple annotation
    """
    vcf_reader = vcf.Reader(filename=vcf_in) if vcf_in != "-" else vcf.Reader(sys.stdin)
    #
    # add a new info field into the header of the output file
    #
    vcf_reader.infos['SIMPLE_ANN'] = vcf.parser._Info(id="SIMPLE_ANN", num=".", type="String", desc="Simplified human readable structural variant annotation: 'SVTYPE | ANNOTATION | GENE(s) | TRANSCRIPT | DETAIL (exon losses, KNOWN_FUSION, ON_PRIORITY_LIST, NOT_PRIORITISED) | PRIORITY (1-3) '", source=None, version=None)
    #
    # Add an info field for highest priority (given multiple annotations per entry)
    vcf_reader.infos['SV_HIGHEST_TIER'] = vcf.parser._Info(id="SV_HIGHEST_TIER", num=1, type="Integer", desc="Highest priority tier for the effects of a variant entry", source=None, version=None)
    #

    # Add filters
    #
    vcf_reader.filters["MissingAnn"] = vcf.parser._Filter(id="MissingAnn", desc="Rejected in SV-prioritize (missing ANN/BND)")
    vcf_reader.filters["Intergenic"] = vcf.parser._Filter(id="Intergenic", desc="Rejected in SV-prioritize (purely intergenic, or a small event)")
    vcf_writer = vcf.Writer(open(outfile, 'w'), vcf_reader) if outfile != "-" else vcf.Writer(sys.stdout, vcf_reader)
    #
    # Read in gene lists
    #
    known_fusions, known_promiscuous, prioritised_genes = read_gene_lists(args.known_fusion_pairs, args.known_fusion_promiscuous, args.gene_list)
    #
    #
    #
    for record in vcf_reader:
        if record.FILTER is None:
            record.FILTER = []

        if 'SVTYPE' in record.INFO and 'ANN' in record.INFO:
            #any(["gene_fusion" in x for x in record.INFO['ANN']])
            vcf_writer.write_record(simplify_ann(record, exon_nums, known_fusions, known_promiscuous, prioritised_genes))
        else:
            record.FILTER.append("MissingAnn")
            vcf_writer.write_record(record)
    vcf_writer.close()

def read_gene_lists(known_fusion_pairs, known_fusion_promiscuous, gene_list):
    known_pairs = []
    known_promiscuous = []
    gl = []
    if known_fusion_pairs and os.path.isfile(known_fusion_pairs):
        with open(known_fusion_pairs, 'r') as myfhandle:
            for line in myfhandle:
                genes = line.strip().split(",")
                if len(genes) == 2 and len(genes[0]) > 0 and len(genes[1]) > 0:
                    known_pairs.append([genes[0].strip(), genes[1].strip()])
    if known_fusion_promiscuous and os.path.isfile(known_fusion_promiscuous):
        with open(known_fusion_promiscuous, 'r') as myfhandle:
            for line in myfhandle:
                gene = line.strip()
                known_promiscuous.append(gene)

    if gene_list and os.path.isfile(gene_list):
        with open(gene_list, 'r') as myghandle:
            for line in myghandle:
                gene = line.strip()
                if len(gene) > 0:
                    gl.append(gene)
    return known_pairs, known_promiscuous, gl

def simplify_ann(record, exon_nums, known_fusions, known_promiscuous, prioritised_genes):
    """Find any annotations that can be simplified and call the method
    to annotate it.
    """
    # marching order is: 'exon_loss_variant', fusions, others (reject)

    # to-do: CNV and INS?

    exon_losses = {}
    annotated = False
    is_intergenic = True # is intergenic or otherwise likely rubbish?
    record.INFO['SV_HIGHEST_TIER'] = 3
    for i in record.INFO['ANN']:
        ann_a = i.split('|')
        if "exon_loss_variant" in ann_a[1]:
            is_intergenic = False
            try:
                exon_losses[ann_a[6]].append(i)
            except KeyError:
                exon_losses[ann_a[6]] = [i]
        elif "gene_fusion" in ann_a[1]:
            # This could be 'gene_fusion', 'bidirectional_gene_fusion' but not 'feature_fusion'
            # 'gene_fusion' could lead to a coding fusion whereas
            # 'bidirectional_gene_fusion' is likely non-coding (opposing frames, _if_ inference correct)
            annotate_other_var(record, ann_a, known_fusions, known_promiscuous, prioritised_genes)
            annotated = True
            is_intergenic = False
        elif "downstream" in ann_a[1] or "upstream" in ann_a[1]:
            # get SVs affecting up/downstream regions of prioritised genes
            if len(ann_a[3]) > 0 and ann_a[3] in prioritised_genes:
                var_priority = "3"
                var_detail = "ON_PRIORITY_LIST"
                simple_ann = "%s|%s|%s|%s|%s|%s" % (record.INFO['SVTYPE'], ann_a[1].upper(), ann_a[3], ann_a[6], var_detail, var_priority)
                try:
                    if simple_ann not in record.INFO['SIMPLE_ANN']: # avoid duplicate entries that bloat the output
                        record.INFO['SIMPLE_ANN'].append(simple_ann)
                except KeyError:
                    record.INFO['SIMPLE_ANN'] = [simple_ann]
                annotated = True
                is_intergenic = False
    if len(exon_losses) > 0:
        annotate_exon_loss(record, exon_losses, exon_nums, prioritised_genes)
        annotated = True
    # Add filter for purely intergenic events and other nuisance variants
    if is_intergenic:
        record.FILTER.append("Intergenic")
        del record.INFO["SV_HIGHEST_TIER"]
    return record

def uniq_list(inlist):
    """Remove unique elements from a list"""
    inset = set(inlist)
    return list(inset)

def find_deleted_exons(annotations):
    """Take the annotations for a particular transcript
    and parse them to find the numbers of the exons that have
    been deleted
    """
    exons = []
    gene = ''
    for i in annotations:
        ann_a = i.split('|')
        if gene == '':
            gene = ann_a[3]
        try:
            exons.append(int(ann_a[8].split('/')[0]))
        except ValueError:
            pass
    return exons, gene

def find_alt_deleted_exons(record, exon_nums, annotations):
    """In the case where the user has provided a file of alternate
    transcript numbers, use those numbers to find the exons that have been
    deleted
    """
    exons = []
    gene = ''
    for i in annotations:
        ann_a = i.split('|')
        if gene == '':
            gene = ann_a[3]
        else:
            break
    for i in exon_nums:
        if int(i[0]) > record.POS and int(i[1]) <= record.INFO['END']:
            exons.append(int(i[2]))
    return exons, gene

def annotate_exon_loss(record, exon_losses, exon_nums, prioritised_genes):
    """Create the exon loss simple annotation from the exon dict created
    in simplify_ann

    For each transcript with exon losses, find the numbers for each exon
    and create the annotation
    Example: DEL|EXON_DEL|BLM|NM_001287247.1|Exon2-12del
    """
    for transcript in exon_losses:
        #Remove version number if it exists
        if transcript.split('.')[0] in exon_nums:
            #use alternate exon numbers for transcript
            exons, gene = find_alt_deleted_exons(record, exon_nums[transcript.split('.')[0]], exon_losses[transcript])
        else:
            #use snpEff numbers for transcript
            exons, gene = find_deleted_exons(exon_losses[transcript])
        exons = uniq_list(exons)
        if len(exons) == 0:
            return None
        if max(exons)-min(exons)+1 == len(exons):
            if len(exons) == 1:
                deleted_exons = "Exon"+str(exons[0])+"del"
            else:
                deleted_exons = "Exon"+str(min(exons))+"-"+str(max(exons))+"del"
        else:
            deleted_exons = "Exon"+str(min(exons))+"-"+str(max(exons))+"del"
        var_priority = "2" if gene in prioritised_genes else "3"
        if record.INFO['SV_HIGHEST_TIER'] > int(var_priority):
            record.INFO['SV_HIGHEST_TIER'] = int(var_priority)
        try:
            record.INFO['SIMPLE_ANN'].append("DEL|EXON_DEL|%s|%s|%s|%s" % (gene,transcript,deleted_exons,var_priority))
        except KeyError:
            record.INFO['SIMPLE_ANN'] = ["DEL|EXON_DEL|%s|%s|%s|%s" % (gene,transcript,deleted_exons,var_priority)]

def annotate_other_var(record, ann_a, known_fusions, known_promiscuous, prioritised_genes):
    """Create a simplified version of the annotation field for non-whole exon loss events
    that are likely to lead to fusions
    Regardless of sv type, the simple annotation for an intronic variant
    looks like: SIMPLE_ANN=INV|GENE_FUSION|ALK&EML4|NM_...&NM_...|KNOWN_FUSION|1
    """
    var_priority = "3"
    var_detail = "NOT_PRIORITISED"
    genes = ann_a[3].strip().split("&")

    if len(genes) > 1 and ([genes[0], genes[1]] in known_fusions or [genes[1], genes[0]] in known_fusions
                           or genes[0] in known_promiscuous or genes[1] in known_promiscuous):
        var_priority = "1"
        var_detail = "KNOWN_FUSION"
    elif (len(genes[0]) > 0 and genes[0] in prioritised_genes) or (len(genes) > 1 and len(genes[1])>0 and genes[1] in prioritised_genes):
        var_detail = "ON_PRIORITY_LIST"
        var_priority = "2"
    if record.INFO['SV_HIGHEST_TIER'] > int(var_priority):
        record.INFO['SV_HIGHEST_TIER'] = int(var_priority)
    simple_ann = "%s|%s|%s|%s|%s|%s" % (record.INFO['SVTYPE'], ann_a[1].upper(), ann_a[3], ann_a[6], var_detail, var_priority)
    try:
        if simple_ann not in record.INFO['SIMPLE_ANN']: # avoid duplicate entries that bloat the output
            record.INFO['SIMPLE_ANN'].append(simple_ann)
    except KeyError:
        record.INFO['SIMPLE_ANN'] = [simple_ann]

# UNUSED FUNCTION FOR NOW
#def annotate_intergenic_var(record, ann_a):
#    """Create a simplified version of the annotation field for an intergenic var
#
#    Regardless of sv type, the simple annotation for an intergenic variant
#    looks like: SIMPLE_ANN=INV|INTERGENIC|LETM2-FGFR1||
#    """
#    simple_ann = "%s|INTERGENIC|%s||" % (record.INFO['SVTYPE'],ann_a[4])
#    try:
#        if simple_ann not in record.INFO['SIMPLE_ANN']: # avoid duplicate entries that bloat the output
#            record.INFO['SIMPLE_ANN'].append(simple_ann)
#    except KeyError:
#        record.INFO['SIMPLE_ANN'] = [simple_ann]

def create_exon_numDict(infile):
    """Create a dictionary of exon numbers based on an input file of alternate
    chromosome numberings.

    example: BRCA1 has no exon 4 (http://www.medscape.com/viewarticle/567639_2),
    making exon numbers from snpEff incorrect.
    """
    exons = {}
    f = open(infile)
    for line in f:
        la = line.rstrip("\n").split("\t")
        name = la[3].split("|")
        transcript = name[0]
        if transcript in exons:
            exons[transcript].append((la[1],la[2],name[1]))
        else:
            exons[transcript] = [(la[1],la[2],name[1])]
    return exons

if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description = "Simplify SV annotations from snpEff to highlight exon impact. Requires the pyvcf module.")
    parser.add_argument('vcf', help='VCF file with snpEff annotations')
    parser.add_argument('--gene_list', '-g', help='File with names of genes (one per line) for prioritisation', required=False, default=None)
    parser.add_argument('--known_fusion_pairs', '-k', help='File with known fusion gene pairs, one pair per line delimited by comma', required=False, default=None)
    parser.add_argument('--known_fusion_promiscuous', '-p', help='File with known promiscuous fusion genes, one gene name per line', required=False, default=None)
    parser.add_argument('--output', '-o', help='Output file name (must not exist). Does not support bgzipped output. Use "-" for stdout. [<invcf>.simpleann.vcf]', required=False)
    parser.add_argument('--exonNums', '-e', help='List of custom exon numbers. A transcript listed in this file will be annotated with the numbers found in this file, not the numbers found in the snpEff result')
    #parser_excl = parser.add_mutually_exclusive_group(required=False)
    args = parser.parse_args()
    if args.output:
        outfile = args.output
    else:
        outfile = args.vcf.replace("vcf.gz","vcf").replace(".vcf", ".simpleann.vcf")
    if os.path.exists(outfile):
        raise IOError("Output file %s exists" % outfile)
    exonNumDict = {}
    if args.exonNums:
        exonNumDict = create_exon_numDict(args.exonNums)
    main(args.vcf, outfile, exonNumDict, args)


class Solution:
    def findJudge(self, n: int, trust: List[List[int]]) -> int:
        if n == 1 and len(trust) == 0: return 1
        freq = {}
        for item in trust:
            freq[item[0]] = freq.get(item[0], 0) - 1
            freq[item[1]] = freq.get(item[1], 0) + 1

        for item in freq:
            if freq[item] == n - 1:
                return item

        return -1


import json
import os

DB_FOLDER_NAME = "db"
PRODUCTS_FILE_NAME = "products.txt"

def get_prod():
    products = []
    with open (os.path.join('db','products.txt'), 'r') as file:
        for lines in file:
            products.append(json.loads(lines.strip()))
        return products


'''
Desafio 71

Problema: Crie um programa que simule o funcionamento de um caixa eletrônico.
          No início, pergunte ao usuário qual será o valor a ser sacado (número inteiro)
          e o programa vai informar quantas cédulas de cada valor serão entregues.

          OBS:
              considere que o caixa possui cédulas de R$50, R$20, R$10 e R$1.

Resolucao do problema:
'''
print('='*34)
print('\t BANCO RACHADINHA')
print('='*34)

n50 = n20 = n10 = n1 = caixa = 0  #defino as variaveis
valor = int(input('Qual o Valor do Saque?: R$'))
caixa = valor

while True:

    if caixa >= 50:
        caixa -= 50
        n50 += 1

    elif caixa >= 20:
        caixa -= 20
        n20 += 1

    elif caixa >= 10:
        caixa -= 10
        n10 += 1

    elif caixa >= 1:
        caixa -= 1
        n1 += 1

    else:
        break

if n50 > 0:
    print(f'Total de {n50} cedulas de R$50')

if n20 > 0:
    print(f'Total de {n20} cedulas de R$20')

if n10 > 0:
    print(f'Total de {n10} cedulas de R$10')

if n1 > 0:
    print(f'Total de {n1} cedulas de R$1')

print('='*34)


__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
__license__ = "AGPL v3"

from django.contrib import admin

from .models import Preprint

class PreprintAdmin(admin.ModelAdmin):
    search_fields = [
        "identifier_w_vn_nr",
    ]

admin.site.register(Preprint, PreprintAdmin)


#Taking Inputs
n = (input("Enter a Number: "))
#Creating empty variables
l = []
#Processing
for i in range(0,len(n)+1):
    n = int(n)
    s = n % 10
    l.append(int(s))
    n = (n-s)/10
b = sum(l)
#Giving Outputs
print("The sum of digits is %d."%(b))


from typing import Union

from fastapi import FastAPI
from facebook_scraper import get_posts

app = FastAPI()

@app.get("/")
def read_root():
    return {"Hello": "World"}

@app.get("/{page_id}")
def scrap_item(page_id: str):
    for post in get_posts(page_id, pages=2):
        return {"posts": post}


# Container for nodes to track path
class NodeContainer():
    def __init__(self, edges, node, path=None):
        self.node = node
        self.edges = edges
        if not path:
            self.path = []
        else:
            self.path = path

# DFS search on matrix from source to destination
def dfs_search(matrix, src, dest):
    """
    Input:
        matrix ([[int]]) = Matrix representing adjacency graph
        src (int) = Node index in matrix to start search from
        dest (int) = Node index in matrix to search for

    Output:
        paths ([[int]]) = A list of paths from src to dest in DFS order
    """
    if len(matrix) < 2:
        raise AttributeError

    start_edges = matrix[src]
    start_node = NodeContainer(start_edges, src)

    end_edges = matrix[dest]
    dest_node = NodeContainer(end_edges, dest)

    return dfs_search_recursive(matrix, start_node, dest_node, [])

def dfs_search_recursive(matrix, src_node, dest_node, success_paths):
    """
    Input:
        matrix ([[int]]) = Matrix representing adjacency graph
        src (NodeContainer) = Node in matrix to start search from
        dest (NodeContainer) = Node in matrix to search for

    Output:
        paths ([[int]]) = A list of paths from src to dest in DFS order
    """
    if src_node.node == dest_node.node:
        success_paths.append(src_node.path + [dest_node.node])

    for edge_i in range(len(src_node.edges)):
        if edge_i not in src_node.path and src_node.edges[edge_i]:
            new_node = NodeContainer(matrix[edge_i], edge_i, src_node.path + [src_node.node])
            dfs_search_recursive(matrix, new_node, dest_node, success_paths)

    return success_paths


from sympy import *

x = symbols('x')

with open("inp21.txt") as f:
    c = [i.strip().split() for i in f.readlines()]
d={}
commands = []
for i in c:
    if len(i) == 2:
        if i[0][:4] == "humn":
            d[i[0][:4]] = x
        else:
            d[i[0][:4]] = int(i[1])
    else:
        commands.append(i)
while len(commands) > 0:
    if "root" in d:
        print (d["root"])
    for i in commands:
        if i[1] in d and i[3] in d:
            #print (i)
            #print (commands)
            if i[2] == "+":
                d[i[0][:4]] = d[i[1]]+d[i[3]]
            elif i[2] == "-":
                d[i[0][:4]] = d[i[1]]-d[i[3]]
            elif i[2] == "*":
                d[i[0][:4]] = d[i[1]]*d[i[3]]
            elif i[2] == "/":
                d[i[0][:4]] = d[i[1]]/d[i[3]]
            commands.remove(i)
            break
print (round(solve(d["root"], x)[0]))