"""Module that provides classes and functions to calculate moments for photoproduction of two-(pseudo)scalar mesons"""
# equation numbers refer to https://halldweb.jlab.org/doc-private/DocDB/ShowDocument?docid=6124&version=4
#TODO update equation numbers

from __future__ import annotations

import bidict as bd
from dataclasses import dataclass, field, fields, InitVar
import dataclasses
from enum import Enum
import functools
import numpy as np
import nptyping as npt
from typing import (
  Dict,
  Generator,
  Iterator,
  List,
  Optional,
  overload,
  Sequence,
  Tuple,
  Union,
)
#TODO switch from int for indices to SupportsIndex; but this requires Python 3.8+

import py3nj
import ROOT

# always flush print() to reduce garbling of log files due to buffering
print = functools.partial(print, flush = True)

@dataclass(frozen = True)  # immutable
class QnWaveIndex:
  """Stores information about quantum-number indices of two-pseudocalar partial-waves"""
  refl: int  # reflectivity
  l:    int  # orbital angular momentum
  m:    int  # projection quantum number of l

@dataclass
class AmplitudeValue:
  """Stores and provides access to single amplitude value"""
  qn:  QnWaveIndex  # quantum numbers
  val: complex      # amplitude value

@dataclass
class AmplitudeSet:
  """Stores partial-wave amplitudes and makes them accessible by quantum numbers"""
  amps: InitVar[Sequence[AmplitudeValue]]
  _amps: Tuple[Dict[Tuple[int, int], complex], Dict[Tuple[int, int], complex]] = field(init = False)  # internal storage for amplitudes split by positive and negative reflectivity

  def __post_init__(
    self,
    amps: Sequence[AmplitudeValue],
  ) -> None:
    """Constructs object from list"""
    self._amps = ({}, {})
    for amp in amps:
      self[amp.qn] = amp.val

  def __getitem__(
    self,
    subscript: QnWaveIndex,
  ) -> AmplitudeValue:
    """Returns partial-wave amplitude for given quantum numbers; returns 0 for non-existing amplitudes"""
    assert abs(subscript.refl) == 1, f"Reflectivity quantum number can only be +-1; got {subscript.refl}."
    reflIndex = 0 if subscript.refl == +1 else 1
    return AmplitudeValue(subscript, self._amps[reflIndex].get((subscript.l, subscript.m), 0))

  def __setitem__(
    self,
    subscript: QnWaveIndex,
    amp:       complex,
  ) -> None:
    """Returns partial-wave amplitude for given quantum numbers"""
    assert abs(subscript.refl) == 1, f"Reflectivity quantum number can only be +-1; got {subscript.refl}."
    reflIndex = 0 if subscript.refl == +1 else 1
    self._amps[reflIndex][(subscript.l, subscript.m)] = amp

  def amplitudes(
    self,
    onlyRefl: Optional[int] = None,  # if set to +-1 only waves with the corresponding reflectivities
  ) -> Generator[AmplitudeValue, None, None]:
    """Returns all amplitude values up maximum spin; optionally filtered by reflectivity"""
    assert onlyRefl is None or abs(onlyRefl) == 1, f"Invalid reflectivity value f{onlyRefl}; expect +1, -1, or None"
    reflIndices: Tuple[int, ...] = (0, 1)
    if onlyRefl == +1:
      reflIndices = (0, )
    elif onlyRefl == -1:
      reflIndices = (1, )
    for reflIndex in reflIndices:
      for l in range(self.maxSpin + 1):
        for m in range(-l, l + 1):
          yield self[QnWaveIndex(+1 if reflIndex == 0 else -1, l, m)]

  @property
  def maxSpin(self) -> int:
    """Returns maximum spin of wave set ignoring 0 amplitudes"""
    maxl = 0
    for reflIndex in (0, 1):
      for (l, _), val in self._amps[reflIndex].items():
        if val != 0:
          maxl = max(l, maxl)
    return maxl

  def photoProdSpinDensElements(
    self,
    refl: int,  # reflectivity
    l1:   int,  # l
    l2:   int,  # l'
    m1:   int,  # m
    m2:   int,  # m'
  ) -> Tuple[complex, complex, complex]:
    """Returns elements of spin-density matrix components (0^rho^ll'_mm', 1^rho^ll'_mm', 2^rho^ll'_mm') with given quantum numbers calculated from partial-wave amplitudes assuming rank 1"""
    qn1     = QnWaveIndex(refl, l1,  m1)
    qn1NegM = QnWaveIndex(refl, l1, -m1)
    qn2     = QnWaveIndex(refl, l2,  m2)
    qn2NegM = QnWaveIndex(refl, l2, -m2)
    rhos: List[complex] = 3 * [0 + 0j]
    rhos[0] =                    (           self[qn1    ].val * self[qn2].val.conjugate() + (-1)**(m1 - m2) * self[qn1NegM].val * self[qn2NegM].val.conjugate())  # Eq. (150)
    rhos[1] =            -refl * ((-1)**m1 * self[qn1NegM].val * self[qn2].val.conjugate() + (-1)**m2        * self[qn1    ].val * self[qn2NegM].val.conjugate())  # Eq. (151)
    rhos[2] = -(0 + 1j) * refl * ((-1)**m1 * self[qn1NegM].val * self[qn2].val.conjugate() - (-1)**m2        * self[qn1    ].val * self[qn2NegM].val.conjugate())  # Eq. (152)
    return (rhos[0], rhos[1], rhos[2])

  def photoProdMoments(
    self,
    L: int,  # angular momentum
    M: int,  # projection quantum number of L
  ) -> Tuple[complex, complex, complex]:
    """Returns moments (H_0, H_1, H_2) with given quantum numbers calculated from partial-wave amplitudes assuming rank 1"""
    # Eqs. (154) to (156) assuming that rank is 1
    moments: List[complex] = 3 * [0 + 0j]
    for refl in (-1, +1):
      for amp1 in self.amplitudes(onlyRefl = refl):
        l1 = amp1.qn.l
        m1 = amp1.qn.m
        for amp2 in self.amplitudes(onlyRefl = refl):
          l2 = amp2.qn.l
          m2 = amp2.qn.m
          term = np.sqrt((2 * l2 + 1) / (2 * l1 + 1)) * (
              py3nj.clebsch_gordan(2 * l2, 2 * L, 2 * l1, 0,      0,     0,      ignore_invalid = True)  # (l_2, 0;    L, 0 | l_1, 0  )
            * py3nj.clebsch_gordan(2 * l2, 2 * L, 2 * l1, 2 * m2, 2 * M, 2 * m1, ignore_invalid = True)  # (l_2, m_2;  L, M | l_1, m_1)
          )
          if term == 0:  # unphysical Clebsch-Gordan
            continue
          rhos: Tuple[complex, complex, complex] = self.photoProdSpinDensElements(refl, l1, l2, m1, m2)
          moments[0] +=  term * rhos[0]  # H_0; Eq. (124)
          moments[1] += -term * rhos[1]  # H_1; Eq. (125)
          moments[2] += -term * rhos[2]  # H_2; Eq. (125)
    return (moments[0], moments[1], moments[2])

  def photoProdMomentSet(
    self,
    maxL: int,  # maximum L quantum number of moments
  ) -> MomentResult:
    """Returns moments calculated from partial-wave amplitudes assuming rank 1; the H_2(L, 0) are omitted"""
    momentIndices = MomentIndices(maxL)
    momentsFlatIndex = np.zeros((len(momentIndices), ), dtype = npt.Complex128)
    norm = 1.0
    for L in range(maxL + 1):
      for M in range(L + 1):
        # get all moments for given (L, M)
        moments: List[complex] = list(self.photoProdMoments(L, M))
        # ensure that moments are real-valued or purely imaginary, respectively
        tolerance = 1e-15
        assert (abs(moments[0].imag) < tolerance) and (abs(moments[1].imag) < tolerance) and (abs(moments[2].real) < tolerance), (
          f"expect (Im[H_0({L} {M})], Im[H_1({L} {M})], and Re[H_2({L} {M})]) < {tolerance} but found ({moments[0].imag}, {moments[1].imag}, {moments[2].real})")
        # set respective real and imaginary parts exactly to zero
        moments[0] = moments[0].real + 0j
        moments[1] = moments[1].real + 0j
        moments[2] = 0 + moments[2].imag * 1j
        # ensure that H_2(L, 0) is zero
        assert M != 0 or (M == 0 and moments[2] == 0), f"expect H_2({L} {M}) == 0 but found {moments[2].imag}"
        # normalize to H_0(0, 0)
        if L == M == 0:
          norm = moments[0].real  # H_0(0, 0)
        for momentIndex, moment in enumerate(moments[:2 if M == 0 else 3]):
          qnIndex   = QnMomentIndex(momentIndex, L, M)
          flatIndex = momentIndices.indexMap.flatIndex_for[qnIndex]
          momentsFlatIndex[flatIndex] = moment / norm
    HTrue = MomentResult(momentIndices, label = "true")
    HTrue._valsFlatIndex = momentsFlatIndex
    return HTrue

@dataclass(frozen = True)  # immutable
class QnMomentIndex:
  """Stores information about quantum-number indices of moments"""
  momentIndex: int  # subscript of photoproduction moments
  L:           int  # angular momentum
  M:           int  # projection quantum number of L

@dataclass
class MomentIndices:
  """Provides mapping between moment index schemes and iterators for moment indices"""
  maxL:      int          # maximum L quantum number of moments
  photoProd: bool = True  # switches between diffraction and photoproduction mode
  indexMap:  bd.BidictBase[int, QnMomentIndex] = field(init = False)  # bidirectional map for flat index <-> quantum-number index conversion

  def __post_init__(self) -> None:
    # create new bidict subclass
    QnIndexByFlatIndexBidict = bd.namedbidict(typename = 'QnIndexByFlatIndexBidict', keyname = 'flatIndex', valname = 'QnIndex')
    # instantiate bidict subclass
    self.indexMap = QnIndexByFlatIndexBidict()
    flatIndex = 0
    for momentIndex in range(3 if self.photoProd else 1):
      for L in range(self.maxL + 1):
        for M in range(L + 1):
          if momentIndex == 2 and M == 0:
            continue  # H_2(L, 0) are always zero and would lead to a singular acceptance integral matrix
          self.indexMap[flatIndex] = QnMomentIndex(momentIndex, L, M)
          flatIndex += 1

  def __len__(self) -> int:
    """Returns total number of moments"""
    return len(self.indexMap)

  def __getitem__(
    self,
    subscript: int,
  ) -> QnMomentIndex:
    """Returns QnIndex that correspond to given flat index"""
    return self.indexMap[subscript]

  def flatIndices(self) -> Generator[int, None, None]:
    """Generates flat indices"""
    for flatIndex in range(len(self)):
      yield flatIndex

  def QnIndices(self) -> Generator[QnMomentIndex, None, None]:
    """Generates quantum-number indices of the form QnIndex(moment index, L, M)"""
    for flatIndex in range(len(self)):
      yield self[flatIndex]

@dataclass
class DataSet:
  """Stores information about a single dataset"""
  polarization:   float            # photon-beam polarization
  data:           ROOT.RDataFrame  # data from which to calculate moments
  phaseSpaceData: ROOT.RDataFrame  # (accepted) phase-space data  #TODO make optional
  nmbGenEvents:   int              # number of generated events

@dataclass(frozen = True)  # immutable
class KinematicBinningVariable:
  """Holds information that define a binning variable"""
  name:      str  # name of variable; used e.g. for filenames
  label:     str  # TLatex expression used for plotting
  unit:      str  # TLatex expression used for plotting
  nmbDigits: Optional[int] = None  # number of digits after decimal point to use when converting value to string

  @property
  def axisTitle(self) -> str:
    """Returns axis title"""
    return f"{self.label} [{self.unit}]"

@dataclass
class AcceptanceIntegralMatrix:
  """Calculates and provides access to acceptance integral matrix"""
  indices:     MomentIndices  # index mapping and iterators
  dataSet:     DataSet        # info on data samples
  _IFlatIndex: Optional[npt.NDArray[npt.Shape["Dim, Dim"], npt.Complex128]] = None  # integral matrix with flat indices; must either be given or set be calling load() or calculate()

  # accessor that guarantees existence of optional field
  @property
  def matrix(self) -> npt.NDArray[npt.Shape["Dim, Dim"], npt.Complex128]:
    """Returns integral matrix"""
    assert self._IFlatIndex is not None, "self._IFlatIndex must not be None"
    return self._IFlatIndex

  @property
  def matrixNormalized(self) -> npt.NDArray[npt.Shape["Dim, Dim"], npt.Complex128]:
    """Returns integral matrix normalized to its diagonal elements"""
    diag = np.diag(np.reciprocal(np.sqrt(np.diag(self.matrix))))
    return diag @ self.matrix @ diag

  @property
  def inverse(self) -> npt.NDArray[npt.Shape["Dim, Dim"], npt.Complex128]:
    """Returns inverse of acceptance integral matrix"""
    return np.linalg.inv(self.matrix)

  @property
  def eigenDecomp(self) -> Tuple[npt.NDArray[npt.Shape["*"], npt.Complex128], npt.NDArray[npt.Shape["Dim, Dim"], npt.Complex128]]:
    """Returns eigenvalues and eigenvectors of acceptance integral matrix"""
    return np.linalg.eig(self.matrix)

  @overload
  def __getitem__(
    self,
    subscript: Tuple[Union[int, QnMomentIndex], Union[int, QnMomentIndex]],
  ) -> Optional[complex]: ...

  @overload
  def __getitem__(
    self,
    subscript: Tuple[slice, Union[int, QnMomentIndex]],
  ) -> Optional[npt.NDArray[npt.Shape["*"], npt.Complex128]]: ...

  @overload
  def __getitem__(
    self,
    subscript: Tuple[Union[int, QnMomentIndex], slice],
  ) -> Optional[npt.NDArray[npt.Shape["*"], npt.Complex128]]: ...

  @overload
  def __getitem__(
    self,
    subscript: Tuple[slice, slice],
  ) -> Optional[npt.NDArray[npt.Shape["*, *"], npt.Complex128]]: ...

  def __getitem__(
    self,
    subscript: Tuple[Union[int, QnMomentIndex, slice], Union[int, QnMomentIndex, slice]],
  ) -> Optional[Union[complex, npt.NDArray[npt.Shape["*"], npt.Complex128], npt.NDArray[npt.Shape["*, *"], npt.Complex128]]]:
    """Returns integral matrix elements for any combination of flat and quantum-number indices"""
    if self._IFlatIndex is None:
      return None
    else:
      # turn quantum-number indices to flat indices
      flatIndexMeas: Union[int, slice] = self.indices.indexMap.flatIndex_for[subscript[0]] if isinstance(subscript[0], QnMomentIndex) else subscript[0]
      flatIndexPhys: Union[int, slice] = self.indices.indexMap.flatIndex_for[subscript[1]] if isinstance(subscript[1], QnMomentIndex) else subscript[1]
      return self._IFlatIndex[flatIndexMeas, flatIndexPhys]

  def __str__(self) -> str:
    if self._IFlatIndex is None:
      return str(None)
    else:
      return np.array2string(self._IFlatIndex, precision = 3, suppress_small = True, max_line_width = 150)

  def calculate(self) -> None:
    """Calculates integral matrix of basis functions from (accepted) phase-space data"""
    # get phase-space data data as NumPy arrays
    thetas = self.dataSet.phaseSpaceData.AsNumpy(columns = ["theta"])["theta"]
    phis   = self.dataSet.phaseSpaceData.AsNumpy(columns = ["phi"]  )["phi"]
    Phis   = self.dataSet.phaseSpaceData.AsNumpy(columns = ["Phi"]  )["Phi"]
    print(f"Phase-space data column: {type(thetas)}; {thetas.shape}; {thetas.dtype}; {thetas.dtype.type}")
    nmbAccEvents = len(thetas)
    assert thetas.shape == (nmbAccEvents,) and thetas.shape == phis.shape == Phis.shape, (
      f"Not all NumPy arrays with input data have the correct shape. Expected ({nmbAccEvents},) but got theta: {thetas.shape}, phi: {phis.shape}, and Phi: {Phis.shape}")
    # calculate basis-function values for physical and measured moments; Eqs. (175) and (176); defined in `wignerD.C`
    nmbMoments = len(self.indices)
    fMeas: npt.NDArray[npt.Shape["*"], npt.Complex128] = np.empty((nmbMoments, nmbAccEvents), dtype = np.complex128)
    fPhys: npt.NDArray[npt.Shape["*"], npt.Complex128] = np.empty((nmbMoments, nmbAccEvents), dtype = np.complex128)
    for flatIndex in self.indices.flatIndices():
      qnIndex = self.indices[flatIndex]
      fMeas[flatIndex] = np.asarray(ROOT.f_meas(qnIndex.momentIndex, qnIndex.L, qnIndex.M, thetas, phis, Phis, self.dataSet.polarization))
      fPhys[flatIndex] = np.asarray(ROOT.f_phys(qnIndex.momentIndex, qnIndex.L, qnIndex.M, thetas, phis, Phis, self.dataSet.polarization))
    # calculate integral-matrix elements; Eq. (178)
    self._IFlatIndex = np.empty((nmbMoments, nmbMoments), dtype = np.complex128)
    for flatIndexMeas in self.indices.flatIndices():
      for flatIndexPhys in self.indices.flatIndices():
        self._IFlatIndex[flatIndexMeas, flatIndexPhys] = (8 * np.pi**2 / self.dataSet.nmbGenEvents) * np.dot(fMeas[flatIndexMeas], fPhys[flatIndexPhys])
    assert self.isValid(), f"Integral matrix data are inconsistent"

  def isValid(self) -> bool:
    return (self._IFlatIndex is not None) and self._IFlatIndex.shape == (len(self.indices), len(self.indices))

  def save(
    self,
    fileName: str = "./integralMatrix.npy",
  ) -> None:
    """Saves NumPy array that holds the integral matrix to file with given name"""
    assert self.isValid(), f"Integral matrix data are inconsistent"
    if self._IFlatIndex is not None:
      print(f"Saving integral matrix to file '{fileName}'.")
      np.save(fileName, self._IFlatIndex)

  def load(
    self,
    fileName: str = "./integralMatrix.npy",
  ) -> None:
    """Loads NumPy array that holds the integral matrix from file with given name"""
    print(f"Loading integral matrix from file '{fileName}'.")
    array = np.load(fileName)
    if not array.shape == (len(self.indices), len(self.indices)):
      raise IndexError(f"Integral loaded from file '{fileName}' has wrong shape. Expected {(len(self.indices), len(self.indices))} but got {array.shape}.")
    self._IFlatIndex = array
    assert self.isValid(), f"Integral matrix data are inconsistent"

  def loadOrCalculate(
    self,
    fileName: str = "./integralMatrix.npy",
  ) -> None:
    """Loads NumPy array that holds the integral matrix from file with given name; and calculates the integral matrix if loading failed"""
    try:
      self.load(fileName)
    except Exception as e:
      print(f"Could not load integral matrix from file '{fileName}': {e} Calculating matrix instead.")
      self.calculate()

@dataclass
class MomentValue:
  """Stores and provides access to single moment value"""
  qn:       QnMomentIndex  # quantum numbers
  val:      complex   # moment value
  uncertRe: float     # uncertainty of real part
  uncertIm: float     # uncertainty of imaginary part
  label:    str = ""  # label used for printing

  def __iter__(self):
    """Returns iterator over shallow copy of fields"""
    return iter(tuple(getattr(self, field.name) for field in fields(self)))

  def __str__(self) -> str:
    momentSymbol = f"H{'^' + self.label if self.label else ''}_{self.qn.momentIndex}(L = {self.qn.L}, M = {self.qn.M})"
    result = (f"Re[{momentSymbol}] = {self.val.real} +- {self.uncertRe}\n"
              f"Im[{momentSymbol}] = {self.val.imag} +- {self.uncertIm}")
    return result

  @property
  def real(self) -> Tuple[float, float]:
    """Returns real part with uncertainty"""
    return (self.val.real, self.uncertRe)

  @property
  def imag(self) -> Tuple[float, float]:
    """Returns imaginary part with uncertainty"""
    return (self.val.imag, self.uncertIm)

  def realOrImag(
    self,
    realPart: bool,  # switched between real part (True) and imaginary part (False)
  ) -> Tuple[float, float]:
    """Returns real or imaginary part with corresponding uncertainty according to given flag"""
    if realPart:
      return self.real
    else:
      return self.imag

@dataclass(eq = False)
class MomentResult:
  """Stores and provides access to moment values"""
  indices:           MomentIndices  # index mapping and iterators
  label:             str = ""       # label used for printing
  _valsFlatIndex:    npt.NDArray[npt.Shape["*"], npt.Complex128]     = field(init = False)  # flat array with moment values
  _covReReFlatIndex: npt.NDArray[npt.Shape["Dim, Dim"], npt.Float64] = field(init = False)  # covariance matrix of real parts of moment values with flat indices
  _covImImFlatIndex: npt.NDArray[npt.Shape["Dim, Dim"], npt.Float64] = field(init = False)  # covariance matrix of imaginary parts of moment values with flat indices
  _covReImFlatIndex: npt.NDArray[npt.Shape["Dim, Dim"], npt.Float64] = field(init = False)  # covariance matrix of real and imaginary parts of moment values with flat indices

  def __post_init__(self) -> None:
    nmbMoments = len(self.indices)
    self._valsFlatIndex    = np.zeros((nmbMoments, ), dtype = npt.Complex128)
    self._covReReFlatIndex = np.zeros((nmbMoments, nmbMoments), dtype = npt.Float64)
    self._covImImFlatIndex = np.zeros((nmbMoments, nmbMoments), dtype = npt.Float64)
    self._covReImFlatIndex = np.zeros((nmbMoments, nmbMoments), dtype = npt.Float64)

  def __eq__(
    self,
    other: object,
  )-> bool:
    # custom equality check needed because of NumPy arrays
    if not isinstance(other, MomentResult):
      return NotImplemented
    return (
      self.indices == other.indices
      and np.array_equal(self._valsFlatIndex,    other._valsFlatIndex)
      and np.array_equal(self._covReReFlatIndex, other._covReReFlatIndex)
      and np.array_equal(self._covImImFlatIndex, other._covImImFlatIndex)
      and np.array_equal(self._covReImFlatIndex, other._covReImFlatIndex)
    )

  @overload
  def __getitem__(
    self,
    subscript: Union[int, QnMomentIndex],
  ) -> MomentValue: ...

  @overload
  def __getitem__(
    self,
    subscript: slice,
  ) -> List[MomentValue]: ...

  def __getitem__(
    self,
    subscript: Union[int, QnMomentIndex, slice],
  ) -> Union[MomentValue, List[MomentValue]]:
    """Returns moment value and corresponding uncertainties at the given flat or quantum-number index/indices"""
    # turn quantum-number index to flat index
    flatIndex: Union[int, slice] = self.indices.indexMap.flatIndex_for[subscript] if isinstance(subscript, QnMomentIndex) else subscript
    if isinstance(flatIndex, slice):
      return [
        MomentValue(
          qn       = self.indices[i],
          val      = self._valsFlatIndex[i],
          uncertRe = np.sqrt(self._covReReFlatIndex[i, i]),
          uncertIm = np.sqrt(self._covImImFlatIndex[i, i]),
          label    = self.label,
        ) for i in range(*flatIndex.indices(len(self.indices)))
      ]
    elif isinstance(flatIndex, int):
      return MomentValue(
        qn       = self.indices[flatIndex],
        val      = self._valsFlatIndex[flatIndex],
        uncertRe = np.sqrt(self._covReReFlatIndex[flatIndex, flatIndex]),
        uncertIm = np.sqrt(self._covImImFlatIndex[flatIndex, flatIndex]),
        label    = self.label,
      )
    else:
      raise TypeError(f"Invalid subscript type {type(flatIndex)}.")

  def __str__(self) -> str:
    result = (str(self[flatIndex]) for flatIndex in self.indices.flatIndices())
    return "\n".join(result)

  # def assignFrom(
  #   self,
  #   other: MomentResult,  # instance from which data are copied
  # ) -> None:
  #   """Assigns all values from given MomentResult instance but leaves `label` untouched"""
  #   self.indices           = other.indices
  #   self._valsFlatIndex    = other._valsFlatIndex
  #   self._covReReFlatIndex = other._covReReFlatIndex
  #   self._covImImFlatIndex = other._covImImFlatIndex
  #   self._covReImFlatIndex = other._covReImFlatIndex

@dataclass
class MomentCalculator:
  """Holds all information to calculate moments for a single kinematic bin"""
  indices:              MomentIndices  # index mapping and iterators
  dataSet:              DataSet  # info on data samples
  integralFileBaseName: str  = "integralMatrix"  # naming scheme for integral files is '<integralFileBaseName>_[<binning var>_<bin center>_...].npy'
  _integralMatrix:      Optional[AcceptanceIntegralMatrix] = None  # if None no acceptance correction is performed; must either be given or calculated by calling calculateIntegralMatrix()
  _HMeas:               Optional[MomentResult] = None  # measured moments; must either be given or calculated by calling calculateMoments()
  _HPhys:               Optional[MomentResult] = None  # physical moments; must either be given or calculated by calling calculateMoments()
  _binCenters:          Optional[Dict[KinematicBinningVariable, float]] = None # dictionary with bin centers

  # accessors that guarantee existence of optional fields
  @property
  def integralMatrix(self) -> AcceptanceIntegralMatrix:
    """Returns acceptance integral matrix"""
    assert self._integralMatrix is not None, "self._integralMatrix must not be None"
    return self._integralMatrix
  @property
  def HMeas(self) -> MomentResult:
    """Returns physical moments"""
    assert self._HMeas is not None, "self._HMeas must not be None"
    return self._HMeas
  @property
  def HPhys(self) -> MomentResult:
    """Returns physical moments"""
    assert self._HPhys is not None, "self._HPhys must not be None"
    return self._HPhys
  @property
  def binCenters(self) -> Dict[KinematicBinningVariable, float]:
    """Returns dictionary with kinematic variables and bin centers"""
    assert self._binCenters is not None, "self._binCenters must not be None"
    return self._binCenters

  # @property
  # def varNames(self) -> List[str]:
  #   """Returns names of kinematic variables that define bin"""
  #   if self._binCenters is None:
  #     return []
  #   return sorted((key.name for key in self._binCenters.keys()))

  @property
  def fileNameBinLabels(self) -> List[str]:
    if self._binCenters is None:
      return []
    return [f"{var.name}_" + (f"{center:.{var.nmbDigits}f}" if var.nmbDigits is not None else f"{center}") for var, center in self.binCenters.items()]

  @property
  def integralFileName(self) -> str:
    """Returns file name used to save acceptance integral matrix; naming scheme is '<integralFileBaseName>_[<binning var>_<bin center>_...].npy'"""
    return "_".join([self.integralFileBaseName, ] + self.fileNameBinLabels) + ".npy"

  def calculateIntegralMatrix(
    self,
    forceCalculation: bool = False,
  ) -> None:
    """Calculates acceptance integral matrix"""
    self._integralMatrix = AcceptanceIntegralMatrix(self.indices, self.dataSet)
    if forceCalculation:
      self._integralMatrix.calculate()
    else:
      self._integralMatrix.loadOrCalculate(self.integralFileName)
    self._integralMatrix.save(self.integralFileName)

  def _calcReImCovMatrices(
    self,
    V_aug: npt.NDArray[npt.Shape["Dim, Dim"], npt.Complex128],  # augmentented covariance matrix
  ) -> Tuple[npt.NDArray[npt.Shape["Dim, Dim"], npt.Float64], npt.NDArray[npt.Shape["Dim, Dim"], npt.Float64], npt.NDArray[npt.Shape["Dim, Dim"], npt.Float64]]:
    """Calculates covariance matrices for real parts, for imaginary parts, and for real and imaginary parts from augmented covariance matrix"""
    nmbMoments = len(self.indices)
    V_Hermit = V_aug[:nmbMoments, :nmbMoments]  # Hermitian covariance matrix; Eq. (88)
    V_pseudo = V_aug[:nmbMoments, nmbMoments:]  # pseudo-covariance matrix; Eq. (88)
    V_ReRe = (np.real(V_Hermit) + np.real(V_pseudo)) / 2  # Eq. (91)
    V_ImIm = (np.real(V_Hermit) - np.real(V_pseudo)) / 2  # Eq. (92)
    V_ReIm = (np.imag(V_pseudo) - np.imag(V_Hermit)) / 2  # Eq. (93)
    return (V_ReRe, V_ImIm, V_ReIm)

  MomentDataSource = Enum("MomentDataSource", ("DATA", "ACCEPTED_PHASE_SPACE", "ACCEPTED_PHASE_SPACE_CORR"))

  def calculateMoments(
    self,
    dataSource: MomentDataSource = MomentDataSource.DATA,
  ) -> None:
    """Calculates photoproduction moments and their covariances using given data source"""
    # define dataset and integral matrix to use for moment calculation
    dataSet = None
    integralMatrix = None
    if dataSource == self.MomentDataSource.DATA:
      # calculate moments of data
      dataSet        = self.dataSet
      integralMatrix = self._integralMatrix
    elif dataSource == self.MomentDataSource.ACCEPTED_PHASE_SPACE:
      # calculate moments of acceptance function
      dataSet        = dataclasses.replace(self.dataSet, data = self.dataSet.phaseSpaceData)
    elif dataSource == self.MomentDataSource.ACCEPTED_PHASE_SPACE_CORR:
      # calculate moments of acceptance-corrected phase space; should all be 0 except H_0(0, 0)
      dataSet        = dataclasses.replace(self.dataSet, data = self.dataSet.phaseSpaceData)
      integralMatrix = self._integralMatrix
    else:
      raise ValueError(f"Unknown data source '{dataSource}'")
    # get input data as NumPy arrays
    thetas = dataSet.data.AsNumpy(columns = ["theta"])["theta"]
    phis   = dataSet.data.AsNumpy(columns = ["phi"]  )["phi"]
    Phis   = dataSet.data.AsNumpy(columns = ["Phi"]  )["Phi"]
    print(f"Input data column: {type(thetas)}; {thetas.shape}; {thetas.dtype}; {thetas.dtype.type}")
    nmbEvents = len(thetas)
    assert thetas.shape == (nmbEvents,) and thetas.shape == phis.shape == Phis.shape, (
      f"Not all NumPy arrays with input data have the correct shape. Expected ({nmbEvents},) but got theta: {thetas.shape}, phi: {phis.shape}, and Phi: {Phis.shape}")
    # read column with event weights if it exists
    # !Note! event weights must be normalized such that sum_i event_i = number of background-subtracted events (see Eq. (63))
    eventWeights = (dataSet.data.AsNumpy(columns = ["eventWeight"]  )["eventWeight"] if "eventWeight" in dataSet.data.GetColumnNames()
                    else np.ones(nmbEvents, dtype = npt.Float64))
    assert eventWeights.shape == (nmbEvents,), f"NumPy arrays with event weights does not have the correct shape. Expected ({nmbEvents},) but got {eventWeights.shape}"
    sumOfWeights        = np.sum(eventWeights)
    sumOfSquaredWeights = np.sum(np.square(eventWeights))
    # calculate basis-function values and values of measured moments
    nmbMoments = len(self.indices)
    fMeas      = np.empty((nmbMoments, nmbEvents), dtype = npt.Complex128)
    fMeasMeans = np.empty((nmbMoments,),           dtype = npt.Complex128)  # weighted means of fMeas values
    self._HMeas = MomentResult(self.indices, label = "meas")
    for flatIndex in self.indices.flatIndices():
      qnIndex = self.indices[flatIndex]
      fMeas[flatIndex] = np.asarray(ROOT.f_meas(qnIndex.momentIndex, qnIndex.L, qnIndex.M, thetas, phis, Phis, dataSet.polarization))  # Eq. (176)
      weightedSum = eventWeights.dot(fMeas[flatIndex])
      self._HMeas._valsFlatIndex[flatIndex] = 2 * np.pi * weightedSum  # Eq. (179)
      fMeasMeans[flatIndex] = weightedSum / sumOfWeights
    # calculate covariance matrices for measured moments; Eqs. (88), (180), and (181)
    # unfortunately, np.cov() does not accept negative weights
    # reimplement code from https://github.com/numpy/numpy/blob/d35cd07ea997f033b2d89d349734c61f5de54b0d/numpy/lib/function_base.py#L2530-L2749
    delta_fMeas = fMeas - fMeasMeans[:, None]
    delta_fMeas_aug = np.concatenate((delta_fMeas, np.conjugate(delta_fMeas)), axis = 0)  # augmented vector, i.e. delta_fMeas stacked on top of delta_fMeas^*
    # see https://juliastats.org/StatsBase.jl/stable/weights/#Implementations and https://juliastats.org/StatsBase.jl/stable/cov/
    # for a sample of ~1000 background-subtracted events the uncertainty estimates using the various Bessel corrections differ only in the 4th decimal place
    besselCorrection = 1 / (sumOfWeights - 1)  # assuming frequency weights, i.e. the sum of weights is the number of background-subtracted events
    # besselCorrection = 1 / (sumOfWeights - sumOfSquaredWeights / sumOfWeights)  # assuming analytic weights that describe importance of each measurement
    # nmbNonZeroWeights = np.count_nonzero(eventWeights)
    # besselCorrection = nmbNonZeroWeights / ((nmbNonZeroWeights - 1) * sumOfWeights)  # assuming probability weights that represent the inverse of the sampling probability for each observation
    V_meas_aug = (2 * np.pi)**2 * sumOfSquaredWeights * besselCorrection * ((eventWeights * delta_fMeas_aug) @ np.asmatrix(delta_fMeas_aug).H)
    self._HMeas._covReReFlatIndex, self._HMeas._covImImFlatIndex, self._HMeas._covReImFlatIndex = self._calcReImCovMatrices(V_meas_aug)
    # calculate physical moments and propagate uncertainty
    self._HPhys = MomentResult(self.indices, label = "phys")
    V_phys_aug = np.empty(V_meas_aug.shape, dtype = npt.Complex128)
    if integralMatrix is None:
      # ideal detector: physical moments are identical to measured moments
      np.copyto(self._HPhys._valsFlatIndex, self._HMeas._valsFlatIndex)
      np.copyto(V_phys_aug, V_meas_aug)
    else:
      # get inverse of acceptance integral matrix
      I_inv = integralMatrix.inverse
      # calculate physical moments, i.e. correct for detection efficiency
      self._HPhys._valsFlatIndex = I_inv @ self._HMeas._valsFlatIndex  # Eq. (83)
      # perform linear uncertainty propagation
      J = I_inv  # Jacobian of efficiency correction; Eq. (101)
      J_conj = np.zeros((nmbMoments, nmbMoments), dtype = npt.Complex128)  # conjugate Jacobian; Eq. (101)
      J_aug = np.block([
        [J,                    J_conj],
        [np.conjugate(J_conj), np.conjugate(J)],
      ])  # augmented Jacobian; Eq. (98)
      V_phys_aug = J_aug @ (V_meas_aug @ np.asmatrix(J_aug).H)  #!Note! @ is left-associative; Eq. (85)
    # normalize moments such that H_0(0, 0) = 1
    norm: complex = self._HPhys[0].val
    self._HPhys._valsFlatIndex /= norm
    V_phys_aug /= norm**2
    self._HPhys._covReReFlatIndex, self._HPhys._covImImFlatIndex, self._HPhys._covReImFlatIndex = self._calcReImCovMatrices(V_phys_aug)

@dataclass
class MomentCalculatorsKinematicBinning:
  """Holds all information to calculate moments for several kinematic bins"""
  moments: List[MomentCalculator]  # data for all bins of the kinematic binning

  def __len__(self) -> int:
    """Returns number of kinematic bins"""
    return len(self.moments)

  def __getitem__(
    self,
    subscript: int,
  ) -> MomentCalculator:
    """Returns MomentCalculator that correspond to given bin index"""
    return self.moments[subscript]

  def __iter__(self) -> Iterator[MomentCalculator]:
    """Iterates over MomentCalculators in kinematic bins"""
    return iter(self.moments)

  # @property
  # def varNames(self) -> List[str]:
  #   """Returns names of kinematic variables used in binning"""
  #   varNames = None
  #   for momentsInBin in self.moments:
  #     varNamesInBin = momentsInBin.varNames
  #     if varNames is None:
  #       varNames = varNamesInBin
  #     else:
  #       assert varNamesInBin == varNames, f"Bins have inconsistent set of binning variables: bin '{momentsInBin.centers}': {varNamesInBin} vs. {varNames} in previous bin"
  #   return varNames or []

  def calculateIntegralMatrices(
    self,
    forceCalculation: bool = False,
  ) -> None:
    """Calculates acceptance integral matrices for all kinematic bins"""
    for momentsInBin in self:
      print(f"Calculating the acceptance integral matrix for kinematic bin {momentsInBin.binCenters}")
      momentsInBin.calculateIntegralMatrix(forceCalculation)

  def calculateMoments(
    self,
    dataSource: MomentCalculator.MomentDataSource = MomentCalculator.MomentDataSource.DATA,
  ) -> None:
    """Calculates moments for all kinematic bins using given data source"""
    for momentsInBin in self:
      momentsInBin.calculateMoments(dataSource)


def main():
    P = int(input())
    sq = [0, 1]
    for i in range(2, 1_000_000):
        sq += [sq[-1] + i**2]
    for _ in range(P):
        I = int(input())
        print(sq[I])

if __name__ == "__main__":
    main()


import os
import sys
import unittest
import tests.flask_tests as flask_tests
import tests.module_tests as module_tests
import tests.inference_tests as inference_tests
from app import app as application
import visualize

# Function to print help messages
def printHelp():
    print("usage: python main.py {test, run, data}")
    print("  options:")
    print("    test: runs unit tests for backend functions")
    print("    run: Runs Flask server on localhost port 5000")
    print("    data: data visualization for training sets")

def main():

    if len(sys.argv)<=1:
        print("ERROR: Missing Arguments")
        printHelp()
    elif sys.argv[1] == "test":
        print("Flask Tests")
        suite = unittest.TestLoader().loadTestsFromModule(flask_tests)
        unittest.TextTestRunner(verbosity=0).run(suite)
        print("Module Tests")
        suite = unittest.TestLoader().loadTestsFromModule(module_tests)
        unittest.TextTestRunner(verbosity=0).run(suite)
        print("Inference Tests")
        suite = unittest.TestLoader().loadTestsFromModule(inference_tests)
        unittest.TextTestRunner(verbosity=0).run(suite)
    elif sys.argv[1] == "run":
        application.run()
    elif sys.argv[1] == "data":
        visualize.main()
    else:
        print("ERROR: Invalid Arguments")
        printHelp()

if __name__ == "__main__":
    main()


from flask import jsonify, request
from . import api
from .. import db
from ..models import Facility, Area
from ..decorators import json, paginate

@api.route('/areas/', methods=['GET'])
@json
@paginate('areas')
def get_areas():
    return Area.query

@api.route('/facilities/<int:id>/areas/', methods=['GET'])
@json
@paginate('areas')
def get_facility_areas(id):
    facility = Facility.query.get_or_404(id)
    return facility.areas

@api.route('/areas/<int:id>', methods=['GET'])
@json
def get_area(id):
    return Area.query.get_or_404(id)

@api.route('/facilities/<int:id>/areas/', methods=['POST'])
@json
def new_facility_area(id):
    facility = Facility.query.get_or_404(id)
    area = Area(facility=facility)
    area.import_data(request.json)
    db.session.add(area)
    db.session.commit()
    return {}, 201, {'Location': area.get_url()}

@api.route('/areas/<int:id>', methods=['PUT'])
@json
def edit_area(id):
    area = Area.query.get_or_404(id)
    area.import_data(request.json)
    db.session.add(area)
    db.session.commit()
    return {}

@api.route('/areas/<int:id>', methods=['DELETE'])
@json
def delete_area(id):
    area = Area.query.get_or_404(id)
    db.session.delete(area)
    db.session.commit()
    return {}


from testing.testcases import TestCase
from rest_framework.test import APIClient
from comments.models import Comment
from django.utils import timezone

COMMENT_URL = '/api/comments/'
TWEET_LIST_URL = '/api/tweets/'
TWEET_DETAIL_URL = '/api/tweets/{}/'
NEWSFEED_LIST_URL = '/api/newsfeeds/'

class CommentApiTests(TestCase):

    def setUp(self):
        self.clear_cache()
        self.user1 = self.create_user('comment_user1')
        self.user1_client = APIClient()
        self.user1_client.force_authenticate(self.user1)
        self.user2 = self.create_user('comment_user2')
        self.user2_client = APIClient()
        self.user2_client.force_authenticate(self.user2)

        self.tweet = self.create_tweet(self.user2)

    def test_list(self):
        # request has to have tweet_id
        response = self.anonymous_client.get(COMMENT_URL)
        self.assertEqual(response.status_code, 400)
        # success retrieve tweet without comments
        response = self.anonymous_client.get(COMMENT_URL, {'tweet_id': self.tweet.id})
        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(response.data['comments']), 0)
        # add some comments
        self.create_comment(self.user1, self.tweet,'1')
        self.create_comment(self.user2, self.tweet, '2')
        self.create_comment(self.user2, self.create_tweet(self.user2), '3')
        response = self.anonymous_client.get(COMMENT_URL, {'tweet_id': self.tweet.id})
        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(response.data['comments']), 2)
        # assert the comments return in order of created_at time
        self.assertEqual(response.data['comments'][0]['content'], '1')
        self.assertEqual(response.data['comments'][1]['content'], '2')
        # if request contains more fields than tweet_id, only tweet_id will be used to
        # filter result
        response = self.anonymous_client.get(COMMENT_URL,
                                             {
                                                 'tweet_id': self.tweet.id,
                                                 'user_id': self.user1.id
                                             })
        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(response.data['comments']), 2)

    def test_create(self):
        # user has to login
        response = self.anonymous_client.post(COMMENT_URL)
        self.assertEqual(response.status_code, 403)
        # has to have enough input
        response = self.user1_client.post(COMMENT_URL)
        self.assertEqual(response.status_code, 400)
        # has to provide tweet id
        response = self.user1_client.post(COMMENT_URL, {'content': 'default content'})
        self.assertEqual(response.status_code, 400)
        # has to provide content
        response = self.user1_client.post(COMMENT_URL, {'tweet_id': self.tweet.id})
        self.assertEqual(response.status_code, 400)
        # content's length must be less than 140
        response = self.user1_client.post(COMMENT_URL, {
            'tweet_id': self.tweet.id,
            'content': '1' * 141,
        })
        self.assertEqual(response.status_code, 400)
        self.assertEqual('content' in response.data['errors'], True)
        # success
        response = self.user1_client.post(COMMENT_URL, {
            'tweet_id': self.tweet.id,
            'content': '1',
        })
        self.assertEqual(response.status_code, 201)
        self.assertEqual(response.data['user']['id'], self.user1.id)
        self.assertEqual(response.data['tweet_id'], self.tweet.id)
        self.assertEqual(response.data['content'], '1')

    def test_destroy(self):
        comment = self.create_comment(self.user1, self.tweet)
        url = '{}{}/'.format(COMMENT_URL, comment.id)

        # user has to login
        response = self.anonymous_client.delete(url)
        self.assertEqual(response.status_code, 403)

        # user has to be the owner
        response = self.user2_client.delete(url)
        self.assertEqual(response.status_code, 403)

        # success
        count = Comment.objects.count()
        response = self.user1_client.delete(url)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(Comment.objects.count(), count - 1)

    def test_update(self):
        comment = self.create_comment(self.user1, self.tweet, 'original')
        another_tweet = self.create_tweet(self.user2)
        url = '{}{}/'.format(COMMENT_URL, comment.id)

        # when method=PUT
        # user has to login
        response = self.anonymous_client.put(url, {'content': 'new'})
        self.assertEqual(response.status_code, 403)
        # only the owner can update
        response = self.user2_client.put(url, {'content': 'new'})
        self.assertEqual(response.status_code, 403)
        comment.refresh_from_db()
        self.assertNotEqual(comment.content, 'new')
        # cannot update other fields other than content
        before_updated_at = comment.updated_at
        before_created_at = comment.created_at
        now = timezone.now()
        response = self.user1_client.put(url, {
            'content': 'new',
            'user_id': self.user2.id,
            'tweet_id': another_tweet.id,
            'created_at': now,
        })
        self.assertEqual(response.status_code, 200)
        comment.refresh_from_db()
        self.assertEqual(comment.content, 'new')
        self.assertEqual(comment.user, self.user1)
        self.assertEqual(comment.tweet, self.tweet)
        self.assertEqual(comment.created_at, before_created_at)
        self.assertNotEqual(comment.created_at, now)
        self.assertNotEqual(comment.updated_at, before_updated_at)

    def test_comments_count(self):
        # test tweet detail api
        tweet = self.create_tweet(self.user1)
        url = TWEET_DETAIL_URL.format(tweet.id)
        response = self.user2_client.get(url)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.data['likes_count'], 0)

        # test tweet list api
        self.create_comment(self.user1, tweet)
        response = self.user1_client.get(TWEET_LIST_URL,{'user_id': self.user1.id})
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.data['results'][0]['comments_count'], 1)

        # test newsfeed list api
        self.create_comment(self.user2, tweet)
        self.create_newsfeed(self.user2, tweet)
        response = self.user2_client.get(NEWSFEED_LIST_URL)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.data['results'][0]['tweet']['comments_count'], 2)

    def test_comments_count_with_cache(self):
        tweet_url = '/api/tweets/{}/'.format(self.tweet.id)
        response = self.user1_client.get(tweet_url)
        self.assertEqual(self.tweet.comments_count, 0)
        self.assertEqual(response.data['comments_count'], 0)

        data = {'tweet_id': self.tweet.id, 'content': 'a comment'}
        for i in range(2):
            _, client = self.create_user_and_client('user{}'.format(i))
            client.post(COMMENT_URL, data)
            response = client.get(tweet_url)
            self.assertEqual(response.data['comments_count'], i + 1)
            self.tweet.refresh_from_db()
            self.assertEqual(self.tweet.comments_count, i + 1)

        comment_data = self.user2_client.post(COMMENT_URL, data).data
        response = self.user2_client.get(tweet_url)
        self.assertEqual(response.data['comments_count'], 3)
        self.tweet.refresh_from_db()
        self.assertEqual(self.tweet.comments_count, 3)

        # uppdate comment shouldn't update comments_count
        comment_url = '{}{}/'.format(COMMENT_URL, comment_data['id'])
        response = self.user2_client.put(comment_url, {'content': 'updated'})
        self.assertEqual(response.status_code, 200)
        response = self.user2_client.get(tweet_url)
        self.assertEqual(response.data['comments_count'], 3)
        self.tweet.refresh_from_db()
        self.assertEqual(self.tweet.comments_count, 3)

        # delete comments will update comments_count
        response = self.user2_client.delete(comment_url)
        self.assertEqual(response.status_code, 200)
        response = self.user1_client.get(tweet_url)
        self.assertEqual(response.data['comments_count'], 2)
        self.tweet.refresh_from_db()
        self.assertEqual(self.tweet.comments_count, 2)


import turtle
t = turtle.Turtle()
b = turtle.Turtle()
c = turtle.Turtle()
ball = turtle.Turtle()
ball.shape("circle")
paddleSpeed = 20
ballSpeed = [3,4]
turtle.tracer(0,0)
t.ht()
b.ht()
c.ht()
ball.ht()
t.pu()
b.pu()
c.pu()
ball.pu()
def pong(x,y):
    ball.setpos(x,y)
    ball.pd()
    ball.clear()
    ball.color('white')
    ball.begin_fill()
    ball.circle(2)
    ball.end_fill()
    ball.pu()
def ballMove():
    pong(ball.xcor()+ballSpeed[0],ball.ycor()+ballSpeed[1])
    if ball.ycor() > 120 or ball.ycor() < -120:
        ballSpeed[1] = ballSpeed[1]*-1
def boundary():
    b.seth(0)
    b.setpos(0,-120)
    b.pd()
    b.color('black')
    b.begin_fill()
    b.fd(200)
    b.lt(90)
    b.fd(240)
    b.lt(90)
    b.fd(400)
    b.lt(90)
    b.fd(240)
    b.lt(90)
    b.fd(200)
    b.end_fill()
    b.pu()
def paddle(x,y):
    t.setpos(-200,y)
    t.color('white')
    t.seth(90)
    t.pd()
    t.begin_fill()
    t.fd(20)
    t.rt(90)
    t.fd(5)
    t.rt(90)
    t.fd(40)
    t.rt(90)
    t.fd(5)
    t.rt(90)
    t.fd(20)
    t.end_fill()
    t.pu()
def move(x,y):
    t.clear()
    paddle(x,y)
def down():
    t.clear()
    paddle(t.xcor(),t.ycor()-paddleSpeed)
    if t.ycor() > 100:
        t.setpos(t.xcor(),100)
        t.clear()
        paddle(t.xcor(),t.ycor())
    elif t.ycor() < -100:
        t.clear()
        t.setpos(t.xcor(),-100)
        paddle(t.xcor(),t.ycor())
    ballMove()
def up():
    t.clear()
    paddle(t.xcor(),t.ycor()+paddleSpeed)
    if t.ycor() > 100:
        t.setpos(t.xcor(),100)
        t.clear()
        paddle(t.xcor(),t.ycor())
    elif t.ycor() < -100:
        t.clear()
        t.setpos(t.xcor(),-100)
        paddle(t.xcor(),t.ycor())
    ballMove()
boundary()
paddle(0,0)
pong(0,0)
turtle.onkey(down,"Down")
turtle.onkey(up,"Up")
turtle.listen()
turtle.done()


"""

    LINE Utilities Senders Python Modules
    Features:
        - Senders text

    @author (TheNongice Wasawat)
    LICENSE: MIT License
    Dates: 13/8/2023 - 0:17

"""

import requests

class LINE:
    def __init__(self, token : str):
        self.token = token
        self.endpoint = "https://notify-api.line.me/api/notify"
        self.headers = {'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': 'Bearer ' + self.token}

    def send(self,text : str, Stop_Nofication: bool = False):
        r = requests.post(
            self.endpoint,
            headers=self.headers,
            data={
                'message': text,
                'notificationDisabled': Stop_Nofication
            }
        )
        return (r.text,r.status_code)

    def change_token(self, token : str):
        self.token = token
        self.headers = {'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': 'Bearer ' + self.token}


import sys
with open(sys.argv[1], "r") as file:
    input = [int(i) for i in file.readlines()]

    def findSum(list, sum):
        for i in list:
            for j in list:
                if i + j == sum:
                    return True
        return False

    for i in range(25, len(input)):
        num = input[i]
        preamble = input[i-25:i]
        x = findSum(preamble, num)
        if not x:
            print(num)
            sys.exit()


'''

'''

t=int(input())
for i in range(t):
    n=int(input())
    a=[]
    for j in range(n):
        a.append(int(input()))
    a.sort()
    c=0
    for k in range(n):
        if k!=n-1:
            if a[k]==a[k+1]:
                k=k+1
            elif a[k]!=a[k+1]:
                print(a[k])
                break
        else:
            print(a[k])
            break


from oo_step_by_step.step5.klass import Klass
from oo_step_by_step.step5.observer import KlassObserver, KlassInfo

class X(KlassObserver):
    def __init__(self, klass: Klass):
        self.klass = klass

    def update(self, klass_info: KlassInfo):
        if klass_info.klass_number == self.klass.klass_number:
            if klass_info.is_leader:
                print('I am the Machine. I know %s become Leader of Class %s.' % (
                    klass_info.name, klass_info.klass_number))
            else:
                print(
                    'I am the Machine. I know %s has joined Class %s.' % (klass_info.name, klass_info.klass_number))


import argparse
import csv
import json
from pathlib import Path
import re
from nltk import word_tokenize
from nltk.tokenize import sent_tokenize
from mrt.viggo.tagging_rules import (
    tag_slots, tags2mrseq, slots2mr, get_specifier_feats)

CATEGORICAL = ["name", "developer", "release_year", "exp_release_date",
               "esrb", "has_multiplayer", "available_on_steam",
               "has_linux_release", "has_mac_release", "rating", "specifier"]
LIST = ["genres", "player_perspective", "platforms"]

DELEX_TOKENS = ['NAME', 'DEVELOPER', 'RELEASE_YEAR', 'EXP_RELEASE_DATE',
                'SPECIFIER']

VA_CORRECT = {
    13: ['developer=PLACEHOLDER', 'genres=sport', 'platforms=Xbox', 'name=PLACEHOLDER'],
    103: ['has_linux_release=no', 'name=PLACEHOLDER', 'has_mac_release=yes', 'platforms=PC', 'available_on_steam=yes', 'genres=real-time strategy'],
    167: ['name=PLACEHOLDER', 'player_perspective=bird view', 'genres=real-time strategy', 'developer=PLACEHOLDER', 'genres=adventure', 'release_year=PLACEHOLDER'],
    250: ['genres=role-playing', 'genres=action-adventure', 'player_perspective=third person', 'genres=puzzle', 'release_year=PLACEHOLDER', 'genres=adventure', 'player_perspective=bird view', 'name=PLACEHOLDER', 'developer=PLACEHOLDER', 'platforms=Nintendo'],
    356: ['has_linux_release=yes', 'name=PLACEHOLDER', 'esrb=M (for Mature)'],
    413: ['player_perspective=first person', 'developer=PLACEHOLDER', 'genres=puzzle', 'name=PLACEHOLDER'],
}

TR_CORRECT = {
    71: ['has_mac_release=no', 'genres=simulation', 'genres=strategy', 'name=PLACEHOLDER', 'has_multiplayer=no'],
    181: ['name=PLACEHOLDER', 'developer=PLACEHOLDER', 'genres=driving/racing'],
    425: ['name=PLACEHOLDER', 'has_multiplayer=yes', 'genres=action', 'genres=shooter', 'platforms=PlayStation'],
    522: ['developer=PLACEHOLDER', 'platforms=PC', 'name=PLACEHOLDER', 'has_mac_release=no'],
    824: ['name=PLACEHOLDER', 'genres=role-playing', 'genres=MMORPG', 'player_perspective=first person', 'platforms=PC'],
    1403: ['has_linux_release=yes', 'has_multiplayer=no', 'name=PLACEHOLDER'],
    1580: ['player_perspective=first person', 'genres=action', 'name=PLACEHOLDER', 'genres=shooter'],
    1585: ['developer=PLACEHOLDER', 'genres=tactical', 'name=PLACEHOLDER', 'player_perspective=first person', 'genres=role-playing', 'genres=shooter'],
    1644: ['release_year=PLACEHOLDER', 'genres=shooter', 'player_perspective=third person', 'genres=adventure', 'name=PLACEHOLDER', 'platforms=PlayStation', 'genres=role-playing', 'genres=action'],
    1739: ['esrb=E (for Everyone)', 'release_year=PLACEHOLDER', 'genres=driving/racing', 'name=PLACEHOLDER', 'has_mac_release=no', 'has_multiplayer=no', 'platforms=PlayStation', 'platforms=Xbox', 'has_linux_release=yes', 'genres=sport', 'genres=simulation', 'platforms=PC'],
    1744: ['player_perspective=bird view', 'has_multiplayer=yes'],
    1750: ['has_mac_release=yes', 'platforms=Nintendo', 'platforms=PC', 'available_on_steam=yes', 'has_multiplayer=no', 'genres=arcade', 'genres=strategy', 'has_linux_release=yes', 'genres=puzzle', 'name=PLACEHOLDER'],
    1830: ['genres=action-adventure', 'genres=shooter', 'player_perspective=first person', 'developer=PLACEHOLDER', 'esrb=M (for Mature)', 'release_year=PLACEHOLDER', 'name=PLACEHOLDER'],
    1896: ['name=PLACEHOLDER', 'genres=simulation', 'genres=turn-based strategy', 'esrb=E 10+ (for Everyone 10 and Older)', 'has_multiplayer=yes', 'platforms=PC'],
    1941: ['name=PLACEHOLDER', 'genres=driving/racing', 'developer=PLACEHOLDER', 'release_year=PLACEHOLDER'],
    2041: ['genres=action-adventure', 'genres=fighting', 'developer=PLACEHOLDER', 'genres=platformer', 'esrb=T (for Teen)', 'player_perspective=first person', 'release_year=PLACEHOLDER', 'name=PLACEHOLDER'],
    2068: ['name=PLACEHOLDER', 'developer=PLACEHOLDER', 'platforms=Nintendo', 'esrb=E (for Everyone)'],
    2276: ['genres=sport', 'genres=vehicular combat', 'has_multiplayer=yes', 'name=PLACEHOLDER'],
    2282: ['esrb=E (for Everyone)', 'genres=turn-based strategy', 'player_perspective=bird view', 'name=PLACEHOLDER', 'genres=strategy', 'has_multiplayer=yes'],
    2323: ['name=PLACEHOLDER', 'developer=PLACEHOLDER', 'has_multiplayer=yes', 'genres=strategy'],
    2486: ['genres=shooter', 'player_perspective=third person', 'release_year=PLACEHOLDER', 'name=PLACEHOLDER'],
    2748: ['has_linux_release=no', 'genres=adventure', 'name=PLACEHOLDER', 'genres=puzzle', 'platforms=PC', 'available_on_steam=yes', 'genres=platformer', 'has_mac_release=no', 'platforms=PlayStation', 'platforms=Xbox'],
    2853: ['name=PLACEHOLDER', 'genres=puzzle', 'player_perspective=first person', 'platforms=Xbox', 'genres=shooter', 'release_year=PLACEHOLDER', 'platforms=PlayStation', 'genres=platformer', 'platforms=PC'],
    3183: ['name=PLACEHOLDER', 'developer=PLACEHOLDER', 'genres=fighting', 'player_perspective=first person', 'genres=action-adventure'],
    3256: ['release_year=PLACEHOLDER', 'name=PLACEHOLDER', 'has_mac_release=yes'],
    3417: ['player_perspective=bird view', 'has_mac_release=no', 'has_linux_release=no', 'esrb=T (for Teen)', 'platforms=PC', 'genres=real-time strategy', 'name=PLACEHOLDER'],
    3490: ['platforms=PlayStation', 'name=PLACEHOLDER', 'release_year=PLACEHOLDER', 'has_mac_release=yes', 'platforms=PC', 'platforms=Nintendo', 'has_linux_release=no', 'genres=simulation', 'genres=sport', 'available_on_steam=yes', 'platforms=Xbox'],
    3895: ['developer=PLACEHOLDER', 'genres=shooter', 'name=PLACEHOLDER', 'player_perspective=first person'],
    4184: ['name=PLACEHOLDER', 'release_year=PLACEHOLDER', 'genres=sport'],
    4337: ['platforms=Nintendo', 'platforms=PlayStation', 'genres=sport', 'platforms=PC', 'available_on_steam=no', 'platforms=Xbox', 'has_mac_release=yes', 'name=PLACEHOLDER', 'has_linux_release=yes'],
    4558: ['developer=PLACEHOLDER', 'has_linux_release=no', 'available_on_steam=yes', 'platforms=Nintendo', 'name=PLACEHOLDER', 'platforms=PlayStation', 'platforms=PC', 'platforms=Xbox', 'has_mac_release=yes', 'genres=sport'],
    4648: ['platforms=PlayStation', 'genres=sport', 'genres=vehicular combat', 'name=PLACEHOLDER'],
    4891: ['available_on_steam=yes', 'name=PLACEHOLDER', 'has_linux_release=yes', 'esrb=M (for Mature)'],
    4943: ['has_linux_release=yes', 'genres=strategy', 'has_mac_release=yes', 'name=PLACEHOLDER'],
    5062: ['name=PLACEHOLDER', 'platforms=Xbox', 'genres=sport'],
}

TE_CORRECT = {
    #22: ['genres=shooter', 'name=PLACEHOLDER', 'has_mac_release=yes', 'genres=role-playing', 'genres=tactical'],
    52: ['genres=action', 'player_perspective=first person', 'has_multiplayer=yes', 'release_year=PLACEHOLDER', 'genres=shooter', 'name=PLACEHOLDER', 'developer=PLACEHOLDER'],
    233: ['release_year=PLACEHOLDER', 'available_on_steam=no', 'genres=vehicular combat', 'name=PLACEHOLDER'],
    235: ['developer=PLACEHOLDER', 'platforms=PlayStation', 'genres=action-adventure', 'name=PLACEHOLDER', 'platforms=PC', 'esrb=M (for Mature)', 'genres=role-playing', 'platforms=Xbox'],
    480: ['release_year=PLACEHOLDER', 'platforms=PlayStation', 'genres=driving/racing', 'player_perspective=third person', 'developer=PLACEHOLDER', 'genres=vehicular combat', 'name=PLACEHOLDER'],
    606: ['developer=PLACEHOLDER', 'player_perspective=side view', 'genres=puzzle', 'name=PLACEHOLDER'],
    875: ['player_perspective=first person', 'platforms=PlayStation', 'genres=shooter', 'platforms=Xbox', 'has_linux_release=no', 'has_mac_release=no', 'platforms=Nintendo', 'platforms=Nintendo Switch', 'genres=role-playing', 'name=PLACEHOLDER', 'available_on_steam=yes', 'platforms=PC'],

}

def preprocess_train(path, output):
    global COUNTS
    COUNTS = 0
    with path.open('r') as in_fp, output.open("w") as out_fp:
        reader = csv.reader(in_fp)
        next(reader)

        for mr_line, utt_line in reader:
            example = preprocess_example(mr_line, utt_line, TR_CORRECT)
            print(json.dumps(example), file=out_fp)

def preprocess_valid(path, output):
    global COUNTS
    COUNTS = 0
    with path.open('r') as in_fp, output.open("w") as out_fp:
        reader = csv.reader(in_fp)
        next(reader)

        for mr_line, utt_line in reader:
            example = preprocess_example(mr_line, utt_line, VA_CORRECT)
            print(json.dumps(example), file=out_fp)

def preprocess_test(path, output):
    global COUNTS
    COUNTS = 0
    with path.open('r') as in_fp, output.open("w") as out_fp:
        reader = csv.reader(in_fp)
        next(reader)

        for mr_line, utt_line in reader:
            example = preprocess_example(mr_line, utt_line, TE_CORRECT)
            print(json.dumps(example), file=out_fp)

COUNTS = 0
SPECS = set()
import copy
def preprocess_example(mr_line, utt_line, CORRECT):

    mr = extract_mr(mr_line)
    orig_mr = copy.copy(mr)
    orig_utt = utt_line
    reference_text, tokens, tokens_delex = extract_utterance(utt_line, mr)

    if 'specifier' in mr['slots']:
        spec = mr['slots']['specifier']
        sup_feat = 'S' if 'worst' in spec or spec[-2:] == 'st' else 'R'
        start_feat = 'V' if spec[0] in 'aeiou' else 'C'
        idx = tokens_delex.index('SPECIFIER')
        tokens_delex[idx] = f'SPECIFIER_{start_feat}_{sup_feat}'
        spec_placeholder = f'SPECIFIER_{start_feat}_{sup_feat}'

    tags = tag_slots(tokens_delex)

    for tok, tag in zip(tokens_delex, tags):
        print(f'{tag:40s} {tok}')
    print()

    pred_mrseq = tags2mrseq(tags)
    pred_mr = set(pred_mrseq)

    gt_mr = slots2mr(mr['slots'])
    gt_mr = set([x for x in gt_mr if not x.startswith('rating')])
    #print(gt_mr)
    #oracle_orig = ['0'] * len(gt_mr
    #input()

    global COUNTS

    COUNTS+=1
    if COUNTS in CORRECT:
        gt_mr = set(CORRECT[COUNTS])

    print(gt_mr)
    print(pred_mr)

    print(COUNTS)
    if COUNTS >= 0:
        if gt_mr != pred_mr:
            input()

    oracle_seq = [
        mr['da'], f"rating={mr['slots'].get('rating', 'N/A')}"
    ] + pred_mrseq
    oracle_slots = oracle_seq[:2] + [x.split('=')[0] for x in oracle_seq[2:]]
    #oracle_slots = [x+f"_{sum([x==y for y in oracle_slots[:i]])}" for i, x in enumerate(oracle_slots)]

    rating = mr['slots'].get('rating', None)
    new_mr = {"da": mr["da"], "slots": {}}
    if rating:
        new_mr['slots']['rating'] = rating

    for sf in pred_mrseq:
        s, f = sf.split('=')
        if s in ['platforms', 'player_perspective', 'genres']:
            if s not in new_mr['slots']:
                new_mr['slots'][s] = []
            new_mr['slots'][s].append(f)
        elif s == 'specifier':
            new_mr['slots']['specifier'] = mr['slots']['specifier']
        else:
            if f == 'PLACEHOLDER':
                f = mr['slots'][s]
            new_mr['slots'][s] = f

    for s, f in new_mr['slots'].items():
        if isinstance(f, list):
            new_mr['slots'][s] = sorted(f)

    example = {
        "original": {
            "mr": mr_line,
            "utt": utt_line,
        },
        "source": {
            "mr_orig": orig_mr,
            "mr": new_mr,
            'sequence': {
                'oracle': oracle_seq,
                'oracle_slots': oracle_slots,
            },
        },
        "target": {
            "sequence": {
                "delex": tokens_delex,
                "lex": tokens,
            },
            "reference_string": reference_text,
        },
    }

    from pprint import pprint
    pprint(example)
#    print(pred_mrseq)
#    print(oracle_seq)
#    #input()
#    ff COUNTS in CORRECT:
#            input()
    return example

def extract_mr(line):
    m = re.match(r'^(.*?)\((.*)\)$', line)
    da = m.group(1)
    da_args = m.group(2)
    mr = {"da": da, "slots": {}}
    for slot, vals in re.findall(r'([\w_]+)\[(.*?)\]', da_args):

        if slot in CATEGORICAL:
            mr["slots"][slot] = vals
        elif slot in LIST:
            mr["slots"][slot] = sorted(vals.split(", "))
        if slot == "specifier":
            mr["slots"]["specifier"] = vals.replace(" ", "_")

    return mr

def extract_utterance(ref, mr):
    ref = re.sub(r"Speed:Payback", r"Speed: Payback", ref)
    ref = re.sub(r"Evolution Studios'", "Evolution Studios '", ref)
    ref = re.sub(r"'09", r"2009", ref)
    ref = re.sub(r"  ", r" ", ref)

    delex_ref = str(ref)

    if "developer" in mr["slots"] and mr["slots"]["developer"] != "":
        assert mr["slots"]["developer"].lower() in delex_ref.lower()
        delex_ref = re.sub(
            re.escape(mr["slots"]["developer"]),
            "DEVELOPER",
            delex_ref,
            flags=re.I)

    if "name" in mr["slots"] and mr["slots"]["name"] != "":
        assert mr["slots"]["name"].lower() in delex_ref.lower()
        delex_ref = re.sub(
            re.escape(mr["slots"]["name"]),
            "NAME",
            delex_ref,
            flags=re.I)

    if "exp_release_date" in mr["slots"] \
            and mr["slots"]["exp_release_date"] != "":
        assert mr["slots"]["exp_release_date"].lower() in delex_ref.lower()
        delex_ref = re.sub(
            re.escape(mr["slots"]["exp_release_date"]),
            "EXP_RELEASE_DATE",
            delex_ref,
            flags=re.I)

    if "release_year" in mr["slots"] and mr["slots"]["release_year"] != "":
        assert mr["slots"]["release_year"].lower() in delex_ref.lower()
        delex_ref = re.sub(
            re.escape(mr["slots"]["release_year"]),
            "RELEASE_YEAR",
            delex_ref,
            flags=re.I)

    if "specifier" in mr["slots"] and mr["slots"]["specifier"] != '':
        spec = mr["slots"]["specifier"]
        if "_" in spec:
            delex_ref = re.sub(spec.replace("_", " "), spec, delex_ref)
        if spec == "fast-paced":
            delex_ref = re.sub("fast paced", spec, delex_ref)

        assert spec in delex_ref
        delex_ref = re.sub(spec, "SPECIFIER", delex_ref)

    delex_ref = re.sub(r"fps\.", r"fp__ __s.", delex_ref)
    delex_ref = re.sub(r"fps", r"fp__ __s", delex_ref)
    delex_ref = re.sub(r"FPS\.", r"FP__ __S.", delex_ref)
    delex_ref = re.sub(r"FPS", r"FP__ __S", delex_ref)
    delex_ref = re.sub(r"(\w)/(\w)", r"\1 / \2", delex_ref)
    delex_ref = re.sub(r"(\w)-(\w)", r"\1 - \2", delex_ref)
    if '/' in delex_ref or '-' in delex_ref or "__" in delex_ref:
        print(delex_ref)
        #input()

    delex_sents = sent_tokenize(delex_ref)
    delex_tokens = " <sent> ".join(
        [" ".join(word_tokenize(delex_sent))
         for delex_sent in delex_sents]).split()

    for i, t in enumerate(delex_tokens):
        if t not in DELEX_TOKENS:
            delex_tokens[i] = t.lower()

    x = " ".join(delex_tokens)
    x = x.replace("adventure-shooters", "adventure - shooters")
    x = x.replace('top-down', 'top - down')
    x = x.replace('third-', 'third -')
    x = x.replace('-person', '- person')
    x = x.replace('puzzle-based', 'puzzle - based')
    x = x.replace("puzzle-oriented", 'puzzle - oriented')
    x = x.replace("adventure-filled", 'adventure - filled')
    x = x.replace("non-availability", "non - availability")
    x = x.replace("bird's-eye", "bird 's - eye")
    x = x.replace("n't not", "not")
    x = x.replace('release_year-released', 'RELEASE_YEAR released')
    x = x.replace('puzzle-platforming','puzzle platforming')
    x = x.replace('shooter-rpg','shooter - rpg')
    x = x.replace("\\name", "NAME")
    x = x.replace('m- rated', 'm - rated')
    x = x.replace('m-rated', 'm - rated')
    x = x.replace('bird- view', 'bird - view')
    x = x.replace('action- adventure', 'action-adventure')
    x = x.replace("role-playing-text", "role-playing - text")
    x = x.replace(" m. ", " m . <sent> ")
    x = x.replace(" t. ", " t . <sent> ")
    x = x.replace(" e. ", " e . <sent> ")
    x = x.replace('third-person', 'third - person')
    x = x.replace('strategy-simulation', 'strategy - simulation')
    x = x.replace('pc-only', 'pc - only')
    x = x.replace('bird- and', 'bird and')
    x = x.replace('music-game', 'music - game')
    x = x.replace("simulation-sports", "simulation - sports")
    x = x.replace('single-player-only', 'single-player - only')
    x = x.replace('action-platformers', 'action - platformers')
    x = x.replace('nfs : payback', 'NAME')
    x = x.replace('simulaton', 'simulation')

    delex_tokens = x.split()

    lex_tokens = list(delex_tokens)
    for slot in DELEX_TOKENS:
        while slot in lex_tokens:
            idx = lex_tokens.index(slot)
            filler_toks = word_tokenize(
                mr['slots'][slot.lower()].replace('_', ' '))
            repl = [tok.lower() for tok in filler_toks]
            lex_tokens = lex_tokens[:idx] + repl + lex_tokens[idx+1:]

    lex_ref = delex_ref
    for slot in DELEX_TOKENS:
        if slot.lower() in mr['slots']:
            lex_ref = re.sub(slot, mr['slots'][slot.lower()], lex_ref)

    lex_ref = re.sub(" / ", "/", lex_ref)
    lex_ref = re.sub(" - ", "-", lex_ref)
    lex_ref = re.sub("__ __", "", lex_ref)

    return lex_ref, lex_tokens, delex_tokens

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("train", type=Path)
    parser.add_argument("valid", type=Path)
    parser.add_argument("test", type=Path)
    parser.add_argument("output", type=Path)
    args = parser.parse_args()

    args.output.mkdir(exist_ok=True, parents=True)
    preprocess_train(args.train, args.output / "viggo.train.jsonl")
    preprocess_valid(args.valid, args.output / "viggo.valid.jsonl")
    preprocess_test(args.test, args.output / "viggo.test.jsonl")

if __name__ == '__main__':
    main()


import numpy as np
import networkx as nx
import matplotlib.pyplot as plt

def maximum_entropy_normalization(y):
    #MaximumEntropyNormalization   Normalize Data using principle of maximum entropy.
    # n=1/(1+exp(-(y-mean(y))/std(y)));
    m=y.mean()
    s=y.std()
    n=1/(1+np.exp(-1*(y-m)/s))
    return n

def get_topk_blink_matrix(D,k=5,score_col=4,query_col=1):
    """
    sort by score_col and query_col
    get top k for each query_col entry

    returns filtered blink matrix
    """

    # Do a quick hand calc to make sure this is working:
    # https://docs.scipy.org/doc/numpy-1.10.0/reference/generated/numpy.ndarray.sort.html
    D = D[np.lexsort((D[:, query_col], -D[:, score_col])),:]
    # idx = np.argsort(D[:,score_col])[::-1] #sort in descending order by blink score
    # D = D[idx,:]
    # idx = np.argsort(D[:,query_col]) # sort in ascending order by query number
    # D = D[idx,:]

    #return indices of first instance of each query number
    u,u_idx = np.unique(D[:,query_col],return_index=True)

    #for each query number get k next best hits
    hits = []
    for i,idx in enumerate(u_idx[:-1]):
        if (u_idx[i+1]-idx)>=k: #check the >=
            hits.append(np.arange(k)+idx)
        else:
            hits.append(np.arange(u_idx[i+1]-idx)+idx)
    hits = np.concatenate(hits)
    D = D[hits,:]
    return D

def filter_component_additive(G, max_component_size,edge_score='score'):
    if max_component_size == 0:
        return

    all_edges = list(G.edges(data=True))
    G.remove_edges_from(list(G.edges))

    all_edges = sorted(all_edges, key=lambda x: x[2][edge_score], reverse=True)
    counter = 0
    for i,edge in enumerate(all_edges):
        G.add_edges_from([edge])
        largest_cc = max(nx.connected_components(G), key=len)
        if len(largest_cc) > max_component_size:
            G.remove_edge(edge[0], edge[1])
        counter +=1
        if counter==1000:
            print(i,len(list(G.edges(data=True))))
            counter=0

def filter_top_k(G, top_k,edge_score='score'):
    print("Filter Top_K", top_k)
    #Keeping only the top K scoring edges per node
    print("Starting Numer of Edges", len(G.edges()))

    node_cutoff_score = {}
    for node in G.nodes():
        node_edges = G.edges((node), data=True)
        node_edges = sorted(node_edges, key=lambda edge: edge[2][edge_score], reverse=True)

        edges_to_delete = node_edges[top_k:]
        edges_to_keep = node_edges[:top_k]

        if len(edges_to_keep) == 0:
            continue

        node_cutoff_score[node] = edges_to_keep[-1][2][edge_score]

        #print("DELETE", edges_to_delete)

        #for edge_to_remove in edges_to_delete:
        #    G.remove_edge(edge_to_remove[0], edge_to_remove[1])

    #print("After Top K", len(G.edges()))
    #Doing this for each pair, makes sure they are in each other's top K
    edge_to_remove = []
    for edge in G.edges(data=True):
        cosine_score = edge[2][edge_score]
        threshold1 = node_cutoff_score[edge[0]]
        threshold2 = node_cutoff_score[edge[1]]

        if cosine_score < threshold1 or cosine_score < threshold2:
            edge_to_remove.append(edge)

    for edge in edge_to_remove:
        G.remove_edge(edge[0], edge[1])

    print("After Top K Mutual", len(G.edges()))

def make_mirror_plot(r,q,figsize=(12,6),mz_tol=0.01,color='black',match_color='red',fontsize=20,grid=True,ax=None,fig=None,vshade=None):
    """
    Make a mirror plot
    You should normalize the spectra before passing to this.
    q = query spectrum np.array([mzs,intensities])
    r = reference spectrum np.array([mzs,intensities])
    returns figure and axes handles
    """
    # return_fig = False
    if ax is None:
        fig,ax = plt.subplots(figsize=figsize)
        # return_fig = True

    dd = abs(np.subtract(r[0][None,:], q[0][:, None],))<mz_tol

    ax.vlines(q[0],q[1]*0,q[1],color,alpha=0.6,linewidth=2)
    idx = dd.any(axis=1)
    ax.vlines(q[0][idx],q[1][idx]*0,q[1][idx],match_color,linewidth=2)

    ax.vlines(r[0],r[1]*0,-1*r[1],color,alpha=0.6,linewidth=2)
    idx = dd.any(axis=0)
    ax.vlines(r[0][idx],r[1][idx]*0,-1*r[1][idx],match_color,linewidth=2)
    ax.set_ylabel('Normalized intensity (au)',fontsize=fontsize)
    ax.set_xlabel('m/z',fontsize=fontsize)
    ax.tick_params(axis='both', which='major', labelsize=(fontsize-2))
    if vshade is not None:
        ax.axvline(vshade[0],linewidth=vshade[1],alpha=vshade[2],color=vshade[3])
    if grid==True:
        ax.grid()
    # if return_fig==True
    # return fig,ax


import pygame

from src.game.GameController import GameController
from src.gui.CommonSurface import CommonSurface
from src.players.opponents.Human import Human

class TrainBoard(CommonSurface):

    def __init__(self, main_surface, board_shape):
        super().__init__(main_surface)
        self.board_shape = board_shape
        self.game_controller = None

    def generate_surface(self, game_controller=None) -> str:
        self.clear_surface()

        self.current_screen = "TRAIN_BOARD"
        self.game_controller = game_controller

        current_episode_text = self.font_big.render("EPISODE : ", False, CommonSurface.BLUE)
        self.main_surface.blit(current_episode_text, (10, 10))
        current_episode_value = self.font_big.render(f"{self.game_controller.episode}", False, CommonSurface.WHITE)
        self.main_surface.blit(current_episode_value, (240, 10))

        stats_text = self.font_big.render("STATS", False, CommonSurface.BLUE)
        self.main_surface.blit(stats_text, (10, 120))

        win_text = self.font_big.render("WIN", False, CommonSurface.WHITE)
        self.main_surface.blit(win_text, (190, 120))

        win_learner_value = self.font_medium.render(
            f"{self.game_controller.stats[self.game_controller.players[0]]['win']}",
            False, CommonSurface.BLUE)
        self.main_surface.blit(win_learner_value, (190, 200))

        win_trainer_value = self.font_medium.render(
            f"{self.game_controller.stats[self.game_controller.players[1]]['win']}", False, CommonSurface.WHITE)
        self.main_surface.blit(win_trainer_value, (190, 260))

        draw_text = self.font_big.render("DRAW", False, CommonSurface.WHITE)
        self.main_surface.blit(draw_text, (310, 120))

        draw_learner_value = self.font_medium.render(
            f"{self.game_controller.stats[self.game_controller.players[0]]['draw']}",
            False, CommonSurface.BLUE)
        self.main_surface.blit(draw_learner_value, (310, 200))

        draw_trainer_value = self.font_medium.render(
            f"{self.game_controller.stats[self.game_controller.players[1]]['draw']}",
            False, CommonSurface.WHITE)
        self.main_surface.blit(draw_trainer_value, (310, 260))

        loss_text = self.font_big.render("LOSS", False, CommonSurface.WHITE)
        self.main_surface.blit(loss_text, (460, 120))

        loss_learner_value = self.font_medium.render(
            f"{self.game_controller.stats[self.game_controller.players[0]]['loss']}", False, CommonSurface.BLUE)
        self.main_surface.blit(loss_learner_value, (460, 200))

        loss_trainer_value = self.font_medium.render(
            f"{self.game_controller.stats[self.game_controller.players[1]]['loss']}", False, CommonSurface.WHITE)
        self.main_surface.blit(loss_trainer_value, (460, 260))

        algorithm_value = self.font_medium.render(f"{self.game_controller.players[0].name}", False,
                                                  CommonSurface.WHITE)
        self.main_surface.blit(algorithm_value, (10, 200))

        algorithm_value = self.font_medium.render(f"{self.game_controller.players[1].name}", False,
                                                  CommonSurface.WHITE)
        self.main_surface.blit(algorithm_value, (10, 260))

        pygame.display.flip()

        # running a training
        self.game_controller.game_loop(update_stats=self.update_stats)

        # generating plots
        game_controller.generate_plot()

        # displaying buttons after training was completed

        test_button_text = self.font_small.render("Test performance", False, CommonSurface.WHITE)
        pygame.draw.rect(self.main_surface, CommonSurface.BLUE, (440, 350, 150, 40))
        self.main_surface.blit(test_button_text, (450, 360))

        menu_button_text = self.font_small.render("Main Menu", False, CommonSurface.BLUE)
        pygame.draw.rect(self.main_surface, CommonSurface.WHITE, (10, 350, 150, 40))
        self.main_surface.blit(menu_button_text, (45, 360))

        return self.current_screen

    def handle_clicks(self, mouse_position, class_to_switch) -> str:

        # test performance button
        if 590 > mouse_position[0] > 440 and 390 > mouse_position[1] > 350:

            learner = self.game_controller.players[0]
            learner.epsilon = 0

            self.game_controller = GameController(players=[learner, Human("Human")], shape=self.game_controller.shape,
                                                  number_of_games=1)
            self.current_screen = class_to_switch[1].generate_surface(self.game_controller)

        # main menu button
        elif 160 > mouse_position[0] > 10 and 390 > mouse_position[1] > 40:
            self.current_screen = class_to_switch[0].generate_surface()

        return self.current_screen

    def update_stats(self):
        self.main_surface.fill(CommonSurface.GREY, (240, 10, 300, 50))
        current_episode_value = self.font_big.render(f"{self.game_controller.episode}", False, CommonSurface.WHITE)
        self.main_surface.blit(current_episode_value, (240, 10))

        self.main_surface.fill(CommonSurface.GREY, (310, 200, 100, 50))
        draw_learner_value = self.font_medium.render(
            f"{self.game_controller.stats[self.game_controller.players[0]]['draw']}",
            False, CommonSurface.BLUE)
        self.main_surface.blit(draw_learner_value, (310, 200))

        self.main_surface.fill(CommonSurface.GREY, (310, 260, 100, 50))
        draw_trainer_value = self.font_medium.render(
            f"{self.game_controller.stats[self.game_controller.players[1]]['draw']}",
            False, CommonSurface.WHITE)
        self.main_surface.blit(draw_trainer_value, (310, 260))

        self.main_surface.fill(CommonSurface.GREY, (190, 200, 100, 50))
        win_learner_value = self.font_medium.render(
            f"{self.game_controller.stats[self.game_controller.players[0]]['win']}",
            False, CommonSurface.BLUE)
        self.main_surface.blit(win_learner_value, (190, 200))

        self.main_surface.fill(CommonSurface.GREY, (190, 260, 100, 50))
        win_trainer_value = self.font_medium.render(
            f"{self.game_controller.stats[self.game_controller.players[1]]['win']}", False, CommonSurface.WHITE)
        self.main_surface.blit(win_trainer_value, (190, 260))

        self.main_surface.fill(CommonSurface.GREY, (460, 200, 100, 50))
        loss_learner_value = self.font_medium.render(
            f"{self.game_controller.stats[self.game_controller.players[0]]['loss']}", False, CommonSurface.BLUE)
        self.main_surface.blit(loss_learner_value, (460, 200))

        self.main_surface.fill(CommonSurface.GREY, (460, 260, 100, 50))
        loss_trainer_value = self.font_medium.render(
            f"{self.game_controller.stats[self.game_controller.players[1]]['loss']}", False, CommonSurface.WHITE)
        self.main_surface.blit(loss_trainer_value, (460, 260))


# Get top ten words from JSON file
import json

def get_news():
    with open('newsafr.json', encoding='utf-8') as news_obj:
        news = json.load(news_obj)
    return news

def get_items_list(news):
    return news['rss']['channel']['items']

def get_words_dict(items):
    words_dict = {}
    for item in items:
        splited_words = item['description'].split()
        for word in splited_words:
            word = word.lower()
            if word.isdigit() or len(word) <= 6:
                continue
            if words_dict.get(word):
                words_dict[word] += 1
            else:
                words_dict[word] = 1
    return words_dict

def get_top_ten_words(dictionary):
    return sorted(dictionary.items(), key=lambda x: x[1], reverse=True)[:10]

def print_top_ten_words(words):
    for word in words:
        print(f'Word: {word[0]} - meets {word[1]} times')

all_news = get_news()
items_list = get_items_list(all_news)
all_words_dict = get_words_dict(items_list)
top_ten_words = get_top_ten_words(all_words_dict)
print_top_ten_words(top_ten_words)


from django.urls import path
from .views import (
    ProveedorListView,
    ProveedorDetailView,
    ProveedorCreateView,
    ProveedorUpdateView,
    ProveedorDeleteView,
)

from .views_autocomplete import ProveedorAutocomplete

app_name = "proveedores"
urlpatterns = [
    path("", ProveedorListView.as_view(), name="list"),
    path("new/", ProveedorCreateView.as_view(), name="create"),
    path("<int:pk>/", ProveedorDetailView.as_view(), name="detail"),
    path("<int:pk>/delete/", ProveedorDeleteView.as_view(), name="delete"),
    path("<int:pk>/update/", ProveedorUpdateView.as_view(), name="update"),
    path("proveedor-autocomplete/", ProveedorAutocomplete.as_view(), name="autocomplete"),
]


import speech_recognition as sr
from datetime import datetime
import webbrowser
import time
from gtts import gTTS
import random
import os
from playsound import playsound
import serial.tools.list_ports
import threading
import urllib.request
import re
from googletrans import Translator
import requests
from bs4 import BeautifulSoup

translater = Translator()

ports=serial.tools.list_ports.comports()

serialInst = serial.Serial()

portlist=[]

for oneport in ports:
    portlist.append(str(oneport))
    print(str(oneport))

val = input("Select port: COM")

for x in range(0,len(portlist)):
    if portlist[x].startswith("COM" + str(val)):
        portVar = "COM" + str(val)
        print(portlist[x])

serialInst.baudrate = 9600
serialInst.port = portVar
serialInst.open()

r = sr.Recognizer()
konuşun = r"C:\\Users\Deniz\Documents\\Python\\oda_serial_ardu\\asistan\\crystal.wav"
alarm = r"C:\\Users\Deniz\Documents\\Python\\oda_serial_ardu\\asistan\\alarm.wav"
bitti = r"C:\\Users\Deniz\Documents\\Python\\oda_serial_ardu\\asistan\\bitti.wav"

def get_video_duration(url):
    page = requests.get(url)
    soup = BeautifulSoup(page.content, "html.parser")
    duration_meta = soup.find("meta", attrs={"itemprop": "duration"})
    if duration_meta:
        duration = duration_meta["content"]
        duration = duration[2:]   #7M7S
        duration = duration.replace("S","")   #7M7
        duration = duration.split("M")
        seconds = (int(duration[0]) * 60) + int(duration[1])
        return seconds
    else:
        return None

def yt(string):
    search_keyword = string
    search_keyword = search_keyword.replace(" ","_")
    search_keyword = search_keyword.replace("ı","i")
    search_keyword = search_keyword.replace("ğ","g")
    search_keyword = search_keyword.replace("ç","c")
    search_keyword = search_keyword.replace("ş","s")
    print(search_keyword)
    html = urllib.request.urlopen("https://www.youtube.com/results?search_query=" + search_keyword)
    video_ids = re.findall(r"watch\?v=(\S{11})", html.read().decode())
    url = "https://www.youtube.com/watch?v=" + video_ids[0]
    return url

def record():
    with sr.Microphone() as source:
        r.adjust_for_ambient_noise(source,duration=5)
        print("Konuşun")
        playsound(konuşun)
        audio = r.listen(source)
        print("Bitti")
        playsound(bitti)
        voice = ""
        try:
            voice = r.recognize_google(audio , language = "tr-TR")
        except sr.UnknownValueError:
            speak("Anlayamadım")
            print("Anlayamadım")
        except sr.requestError:
            speak("Sistem çalışmıyor")
        return voice.capitalize()

def record_eng():
    with sr.Microphone() as source:
        print("Konuşun")
        playsound(konuşun)
        audio = r.listen(source)
        print("Bitti")
        playsound(bitti)
        voice = ""
        try:
            voice = r.recognize_google(audio)
        except sr.UnknownValueError:
            speak("Anlayamadım")
            print("Anlayamadım")
        except sr.requestError:
            speak("Sistem çalışmıyor")
        return voice.capitalize()

def response(voice):
    if "Nasılsın" in voice:
        speak("İyiyim Sen Nasılsın?")
        print("İyiyim sen Nasılsın?")
        mood = record()
        print(mood)
        if "İyiyim" or "Süperim "or "Bomba gibiyim" in mood:
            speak("İyi olmana sevindim!")
            print("İyi olmana sevindim!")
        elif "Kötüyüm" or "Berbatım" or "İyi değilim" or "İyi değilim" in mood:
            speak("Kötü olmana üzüldüm!")
            print("Kötü olmana üzüldüm!")
    if "Saat kaç" in voice:
        speak(datetime.now().strftime("%H:%M"))
        print(datetime.now().strftime("%H:%M"))
    if "Arama yap" in voice:
        speak("Ne aramak istiyorsun ?")
        print("Ne aramak istiyorsun?")
        search = record()
        url = "https://google.com/search?q=" + search
        webbrowser.get().open(url)
        speak(search + " İçin bulduklarım :")
    if "Tamamdır" in voice:
        speak("Görüşürüz")
        exit()
    if "Video ara" in voice:
        speak("Ne aramak istiyorsun ?")
        print("Ne aramak istiyorsun?")
        search = record()
        url = "https://www.youtube.com/results?search_query=" + search
        webbrowser.get().open(url)
        speak(search + "İçin YouTube'da bulduklarım")
    if "Arama motorunu aç" in voice:
        webbrowser.open("https://www.google.com.tr")
    if "Instagram'ı aç" in voice:
        webbrowser.open("https://www.instagram.com/?hl=tr")
    if "İngilizce çeviri yap" in voice:
        speak("Neyi türkçeye çevirmemi istiyorsun?")
        print("Neyi türkçeye çevirmemi istiyorsun?")
        search = record_eng()
        print(search)
        out = translater.translate(search,dest="tr",src="en")
        out = out.text
        speak(f"Söylediğiniz kelime {out} anlamına gelir")
    if "Eş anlamlısını bul" in voice:
        speak("Neyin eş anlamlısını bulmamı istiyorsun?")
        print("Neyin eş anlamlısını bulmamı istiyorsun?")
        url = "https://es-anlam.com/kelime/" + record()
        headerss = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"}
        page = requests.get(url,headers=headerss)
        soup = BeautifulSoup(page.content,"html.parser")
        title = soup.find(id="esanlamlar").get_text().strip()
        speak(title)
    if "Zıt anlamlısını bul" in voice:
        speak("Neyin zıt anlamlısını bulmamı istiyorsun?")
        print("Neyin zıt anlamlısını bulmamı istiyorsun?")
        search = record()
        url = "https://www.es-anlam.com/zit-anlam/kelime/" + search
        headerss = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"}
        page = requests.get(url,headers=headerss)
        soup = BeautifulSoup(page.content,"html.parser")
        title = soup.find(id="esanlamlar").get_text().strip()
        speak(title)
    if "Zamanlayıcı kur" in voice:
        speak("Kaç dakika sonrasına kurmamı istiyorsun ?")
        print("Kaç dakika sonrasına kurmamı istiyorsun ?")
        dakika = record()
        print(dakika)
        saniye = int(dakika) * 5
        geçen = 0
        while geçen < saniye:
            time.sleep(1)
            print(geçen)
            geçen += 1
            if geçen % 5 == 0:
                speak(f"{geçen/5} dakika kaldı")
                geçen += 3
        playsound(alarm)
        time.sleep(4)
        speak("Alarm çaldı!!")
    if "Işığı aç" in voice:
        serialInst.write(str.encode("ac\r\n"))
        speak("Işık açıldı!")
    if "Işığı kapat" in voice:
        serialInst.write(str.encode("kapa\r\n"))
        speak("Işık kapatıldı!")
    if "Ders çalışma müziği aç" in voice:
        webbrowser.open("")
    if "Youtube'da türkçe çal" in voice:
        speak("Ne çalayım?")
        wordd = record()
        link = yt(wordd)
        webbrowser.open(link, new=1, autoraise=True)
        sn = get_video_duration(link)
        threading.Thread(target=sure_bekle,args=(sn,)).start()

    if "Youtube'da i̇ngilizce çal" in voice:
        speak("Ne çalayım?")
        wordd = record_eng()
        webbrowser.open(yt(wordd))

def speak(string):
    tts = gTTS(string,lang="tr")
    rand = random.randint(1,10000)
    file = "audio-" + str(rand) + ".mp3"
    tts.save(file)
    time.sleep(1)
    playsound(file)
    time.sleep(6)
    os.remove(file)

def sure_bekle(sure):
    a = 0
    time.sleep(sure+5)
    print("Betteeee!!")
    os.system("TASKKILL /F /IM chrome.exe")

def speak_eng(string):
    tts = gTTS(string,lang="en")
    rand = random.randint(1,10000)
    file = "audio-" + str(rand) + ".mp3"
    tts.save(file)
    time.sleep(1)
    playsound(file)
    time.sleep(6)
    os.remove(file)

while True:
    if serialInst.in_waiting:
        packet = serialInst.readline()
        cardn = packet.decode("utf").rstrip("\n")
        cardr = cardn.rstrip("\r")
        card = cardr.rstrip(" ")
        print(card)
        if card == "basla":
            speak("Nasıl yardımcı olabilirim?")
            voice = record()
            print(voice)
            response(voice)


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

from __future__ import unicode_literals, print_function, absolute_import

from ..paths import TEST_PATH
from ..classifier import Clf
from ..settings import Settings

import unittest
import os
import pandas

class ClassifierTest(unittest.TestCase):

    def test_training_no_report(self):
        clf = self.clf()
        clf.train(self.dataset(), report=False)
        self.assertEqual(clf.report, None)
        self.assertEqual(clf.misclassified_data, None)

    def test_training_with_report(self):
        clf = self.clf()
        clf.train(self.dataset(), report=True)
        self.assertTrue(len(clf.report) > 100)
        self.assertTrue(len(clf.misclassified_data) > 50)

    def test_classify_fails_model_not_trainer(self):
        clf = self.clf()
        self.assertRaises(ValueError, clf.classify, self.dataset())

    def test_classify(self):
        clf = self.clf()
        clf.train(self.dataset())
        df = self.dataset()
        df = clf.classify(df)
        labels = df['järeldus']
        confidence = df['kindlus']
        self.assertEqual(df.shape[0], len(labels))
        self.assertEqual(df.shape[0], len(confidence))

    def clf(self):
        return Clf(self.settings())

    def settings(self):
        return Settings.read(os.path.join(TEST_PATH, 'weather.def'),
                             os.path.join(TEST_PATH, 'weather.txt'))

    def dataset(self):
        return pandas.read_excel(os.path.join(TEST_PATH, 'weather.xlsx'), sheet_name='Sheet1')


from pyramid.config import Configurator
import os

def main(global_config, **settings):
    """ This function returns a Pyramid WSGI application.
    """
    config = Configurator(settings=settings)
    config.include('pyramid_jinja2')
    config.include('pyramid_fanstatic')
    config.add_jinja2_renderer('.html')
    config.add_static_view('static', 'static', cache_max_age=3600)
    config.add_route('home', '/')
    config.add_route('rdm', '/rdm')
    config.add_route('rdmdocs', '/rdm/docs')
    config.add_route('meta', '/rdm/meta')
    config.add_route('odktools', '/rdm/odktools')

    templatesPath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates')
    config.add_jinja2_search_path(templatesPath)

    config.scan()
    return config.make_wsgi_app()