""" Contains the `Graph` class as well its necessary component classes.

A `Graph` object is basically a combination of a list of `Node` and a list of
`Edge`. A `Node` object is constructed based on a list of `Attribute` objects
and contains a dictionary mapping attribute names to values. The preprocessing
of attriubte values are done when construting the `Attribute` objects.

Note:
    For each of the class in the module, their protected attributes are wrapped
    by read-only properties of names without the underscore prefixes. These
    properties' documentations are therefore omitted.
"""

import re
import collections
from typing import (
    Any, List, Tuple, Hashable,
    Iterable, Dict, Callable, Mapping
)

class Attribute:
    """ Store attribute names, types, and values and preprocess values.

    * If ``attr_type`` is ``'text'``, then the value is tokenized as a list
      of strings.
    * If ``attr_type`` is ``'person_entity'``, then the value is split into a
      tuple of (last name, first name).

    Args:
        name: The attribute name.
        attr_type: The attribute type. Currently only implemented
            ``'person_entity'`` and ``'text'``.
        value: The original unprocessed attribute value.

    Attributes:
        _name: (`str`): Same as ``name`` in the parameters section.
        _type: (`str`): Same as ``attr_type`` in the parameters section.
        _value: (`~typing.Any`): Preprocessed value depending on the type.
        _raw_value (`~typing.Any`): Same as ``value`` in the parameters
            section.
    """

    def __init__(self, name: str, attr_type: str, value: Any):
        self._name = name
        self._type = attr_type
        if attr_type == 'text':
            self._value = self._tokenize(value)
            self._raw_value = value
        elif attr_type == 'person_entity':
            self._value = self._clean_person_name(value)
            self._raw_value = value
        else:
            self._value = value
            self._raw_value = value

    @property
    def name(self):
        """ `str`: Omitted."""
        return self._name

    @property
    def type(self):
        """ `str`: Omitted."""
        return self._type

    @property
    def value(self):
        """ `~typing.Any`: Omitted."""
        return self._value

    @property
    def raw_value(self):
        """ `~typing.Any`: Omitted."""
        return self._raw_value

    @staticmethod
    def _tokenize(doc: str) -> List[str]:
        """ tokenize the text string into a list of words.

        Args:
            doc: string of text string.

        Returns:
            A list of tokenized words.
        """
        doc = doc.strip()
        doc = re.sub("[^a-zA-Z0-9]", " ", doc)
        doc = doc.lower().split()
        return doc

    @staticmethod
    def _clean_person_name(name: str) -> Tuple[str, str]:
        """ Retrieve first and last names from normalized person name.

        Args:
            name: A normalized name string. It should consist only of lower
                case letters and underscores of the format:
                <last name>_<first name>_<middle name>, where there should be
                no spaces in the last name, and spaces in first name should all
                be replaced by underscores. Other punctuational marks should be
                removed. For example, ``'W. W. Wang'`` should be transformed to
                ``'wang_w_w'``.

        Returns:
            A tuple of (last name, first name).
        """
        last, *first = name.split('_')
        first = ' '.join(first).strip()
        return last, first

class Node:
    """ Store information about a reference.

    Args:
        node_id: A unique identifier for a reference. Distinct node object must
            have distinct ``node_id``.
        attrs: All attributes of this reference.

    Important:
        In order for the whole entity resolution to function as expected, the
        ``node_id`` should also be **comparable** besides being **hashable**.

    Attributes:
        _id (`~typing.Hashable`): Same as ``node_id`` in the parameters
            section.
        _attr_vals (`~typing.Dict`\ [`str`, `~typing.Any`]): Mapping attriubte
            names to their processed values.
        _raw_attr_vals (`~typing.Dict`\ [`str`, `~typing.Any`]): Mapping
            attribute names to their original unprocessed values.
    """

    def __init__(self, node_id: Hashable, attrs: Iterable[Attribute]):
        self._id = node_id
        attr_vals, raw_attr_vals = dict(), dict()
        for attr in attrs:
            attr_vals[attr.name] = attr.value
            raw_attr_vals[attr.name] = attr.raw_value
        self._attr_vals = attr_vals
        self._raw_attr_vals = raw_attr_vals

    @property
    def id(self):
        """ `~typing.Hashable`: Omitted."""
        return self._id

    @property
    def attr_vals(self):
        """ `~typing.Dict`\ [`str`, `~typing.Any`]: Omitted."""
        return self._attr_vals

    @property
    def raw_attr_vals(self):
        """ `~typing.Dict`\ [`str`, `~typing.Any`]: Omitted."""
        return self._raw_attr_vals

    def __hash__(self) -> int:
        """ Use the ``_id`` attribute as hash value."""
        return self._id

    def __eq__(self, other: 'Node') -> bool:
        """ Two nodes are equal if and only if they have the same ``_id``.

        Args:
            other: Another node to be compared with.
        """
        return self._id == other.id

class Edge:
    """ Store information about a hyper-edge.

    Args:
        edge_id: A unique identifier for a hyper-edge. Distinct edge object
            must have distinct ``edge_id``.

    Attributes:
        _id (`~typing.Hashable`): Same as in the ``edge_id`` in the parameters
            section.
        _nodes (`~typing.List`\ [`Node`]): A list of references connected
            through this hyper-edge.
    """

    def __init__(self, edge_id: Hashable):
        self._id = edge_id
        self._nodes = list()

    @property
    def id(self):
        """ `~typing.Hashable`: Omitted."""
        return self._id

    @property
    def nodes(self):
        """ `~typing.List`\ [`Node`]: Omitted."""
        return self._nodes

    def add_node(self, node: Node) -> None:
        """ Add a reference to this hyper-edge.

        Args:
            node: The reference to be added to the ``_nodes`` attribute.
        """
        self._nodes.append(node)

class Graph:
    """ Store information about the references and their relations.

    Args:
        edges: All hyper-edges this reference graph contains. Since each edge
            object contains all nodes objects, this sufficies to be the only
            input.
        attr_types: Mapping attribute names to the attribute type. Refer to the
            ``attr_type`` attribute of `Attribute` for details on attribute
            types.

    Important:
        The construction of this graph object depends heavily on the assumption
        that each reference only appears in one hyper-edge. Should the
        assumption fail, unexpected errors could occur. Refer to
        :doc:`../advanced_guide` for more details.

    Attributes:
        _nodes (`~typing.List`\ [`Node`]): A list of references contained in
            the graph.
        _edges (`~typing.List`\ [`Edge`]): A list of hyper-edges contained in
            the graph.
        _node_to_edge (`~typing.Dict`\ [`Node`, `Edge`]): Mapping references
            to the hyper-edges they are contained in.
        _attr_types (`~typing.Mapping`\ [`str`, `str`]): Same as ``attr_types``
            in the above parameters section.
        _attr_vals (`~typing.Dict`\ [`str`, `~typing.List`]): Mapping attribute
            names to a list of preprocessed attribute values of all references
            with the corresponding names.
        _raw_attr_vals (`~typing.Dict`\ [`str`, `~typing.List`]): Mapping
            attribute names to a list of original unprocessed attribute
            values of all references with the corresponding names.
    """

    def __init__(self, edges: Iterable[Edge], attr_types: Mapping[str, str]):
        self._edges = list()
        self._node_to_edge = dict()
        for edge in edges:
            self._edges.append(edge)
            for node in edge.nodes:
                self._node_to_edge[node] = edge
        self._nodes = list(self._node_to_edge.keys())
        self._attr_types = attr_types
        attr_vals = collections.defaultdict(list)
        raw_attr_vals = collections.defaultdict(list)
        for name in attr_types:
            attr_vals[name] = []
        for node in self.nodes:
            for name, value in node.attr_vals.items():
                attr_vals[name].append(value)
            for name, raw_value in node.raw_attr_vals.items():
                raw_attr_vals[name].append(raw_value)
        self._attr_vals = attr_vals
        self._raw_attr_vals = raw_attr_vals

    @property
    def nodes(self):
        """ `~typing.List`\ [`Node`]: Omitted."""
        return self._nodes

    @property
    def edges(self):
        """ `~typing.List`\ [`Edge`]: Omitted."""
        return self._edges

    @property
    def attr_types(self):
        """ `~typing.Mapping`\ [`str`, `str`]: Omitted."""
        return self._attr_types

    @property
    def attr_vals(self):
        """ `~typing.Dict`\ [`str`, `~typing.List`]: Omitted."""
        return self._attr_vals

    @property
    def raw_attr_vals(self):
        """ `~typing.Dict`\ [`str`, `~typing.List`]: Omitted."""
        return self._raw_attr_vals

    def get_neighbors(self, node: Node) -> List[Node]:
        """ Retrive the neighboring references of a given reference.

        Args:
            node: To find the neighboring references of this reference.

        Returns:
            A list of neighboring references. Note that this list will contain
            the input ``node`` as well.
        """
        return self._node_to_edge[node].nodes

    def get_ambiguity_adar(
        self,
        f1: Callable[[Dict[str, Any], Dict[str, Any]], Any],
        is_raw1: bool,
        f2: Callable[[Dict[str, Any], Dict[str, Any]], Any],
        is_raw2: bool
    ) -> Dict[Node, float]:
        """ Compute Adar ambiguity score of a reference based on attributes.

        For further details on computation of Adar ambiguity to understand
        the following documentation, please refer to :doc:`../advanced_guide`.

        Args:
            f1: this function should compute the first attribute for ambiguity
                computation.
            is_raw1: Indicate whether the input attribute value dictionaries
                into ``f1`` consist of unprocessed values.
            f2: this function should compute the second attribute for ambiguity
                computation.
            is_raw2: Indicate whether the input attribute value dictionaries
                into ``f2`` consist of unprocessed values.

        Returns:
            Mapping references to their ambiguity scores.
        """
        first_attrs, second_attrs = dict(), dict()
        first_attr2node = collections.defaultdict(list)
        cal_amb_a1_a2 = collections.defaultdict()
        for node in self._nodes:
            if is_raw1:
                a1_val = f1(node.raw_attr_vals)
                a2_val = f2(node.raw_attr_vals)

            else:
                a1_val = f1(node.attr_vals)
                a2_val = f2(node.attr_vals)
            first_attrs[node] = a1_val
            second_attrs[node] = a2_val
            first_attr2node[a1_val].append(node)
        for a1_val, nodes in first_attr2node.items():
            a2_vals = set()
            for node in nodes:
                a2_vals.add(second_attrs[node])
            cal_amb_a1_a2[a1_val] = len(a2_vals)/len(self._nodes)
        node_ambiguity = collections.defaultdict()
        for node, a1_val in first_attrs.items():
            node_ambiguity[node] = cal_amb_a1_a2[a1_val]
        return node_ambiguity


import torch
import torch.nn as nn
import torch.nn.functional as F

torch.backends.cudnn.benchmark = True
print("torch.backends.cudnn.benchmark =", torch.backends.cudnn.benchmark)

def get_network(params):
    if params["network_type"] == "mlp":
        return MLP()
    elif params["network_type"] == "mlp2":
        return MLP2()
    elif params["network_type"] == "conv":
        return Conv()
    else:
        raise ValueError("Incorrect network name.")

input_channels = {"grid": None, "vector": 109}

class Swish(nn.Module):
    def forward(self, x):
        return x * torch.sigmoid(x)

class MLP(nn.Module):
    def __init__(self):
        super().__init__()
        self.num_input_channels = input_channels["vector"]
        self.num_hidden_units = 128

        self.mlp = nn.Sequential(
            nn.Linear(self.num_input_channels, self.num_hidden_units),
            Swish(),
            nn.Linear(self.num_hidden_units, self.num_hidden_units),
            Swish(),
            nn.Linear(self.num_hidden_units, 1),
            nn.Tanh()
        )

    def forward(self, x_grid, x_grid_vector, x_vector):
        return self.mlp(x_vector)

class MLP2(nn.Module):
    def __init__(self):
        super().__init__()
        self.num_input_channels = input_channels["vector"]
        self.num_hidden_units = 256

        self.mlp = nn.Sequential(
            nn.Linear(self.num_input_channels, self.num_hidden_units),
            Swish(),
            nn.Linear(self.num_hidden_units, self.num_hidden_units),
            Swish(),
            nn.Linear(self.num_hidden_units, self.num_hidden_units),
            Swish(),
            nn.Linear(self.num_hidden_units, self.num_hidden_units),
            Swish(),
            nn.Linear(self.num_hidden_units, 1),
            nn.Tanh()
        )

    def forward(self, x_grid, x_grid_vector, x_vector):
        return self.mlp(x_vector)

def get_hexagonal_kernel_mask():
    return torch.tensor([
                [0., 1., 0., 1., 0.],
                [1., 0., 1., 0., 1.],
                [0., 1., 0., 1., 0.],
            ], dtype=torch.float16, requires_grad=False).view(1, 1, 3, 5)

def get_hexagonal_activations_mask():
    return torch.tensor([
                [0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0,],
                [0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0,],
                [0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0,],
                [0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0,],
                [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,],
                [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,],
                [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,],
                [0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0,],
                [0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0,],
                [0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0,],
                [0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0,]
            ], dtype=torch.float16, requires_grad=False).view(1, 1, 11, 21)

def swish(x):
    return x * torch.sigmoid(x)

def hexagonal_2d_global_avg_pool(x, mask):
    return torch.sum(x * mask, dim=(2, 3), keepdim=True) / torch.sum(mask)

class Hexagonal3x3Conv2dNoBias(nn.Conv2d):
    def __init__(self, in_channels, out_channels, kernel_mask):
        super().__init__(in_channels, out_channels, (3, 5), stride=1, padding=(1, 2), bias=False)
        self.kernel_mask = kernel_mask.to(torch.device("cuda:0" if torch.cuda.is_available() else "cpu"))

    def forward(self, x_in):
        return F.conv2d(x_in, self.weight * self.kernel_mask, bias=None, stride=1, padding=(1, 2))

class HexagonalBlock(nn.Module):
    def __init__(self, hidden_channels, activations_mask, kernel_mask):
        super().__init__()
        self.activations_mask = activations_mask
        self.conv_1 = Hexagonal3x3Conv2dNoBias(hidden_channels, hidden_channels, kernel_mask)
        self.conv_2 = Hexagonal3x3Conv2dNoBias(hidden_channels, hidden_channels, kernel_mask)
        self.bn_1 = nn.BatchNorm2d(hidden_channels, affine=True)
        self.bn_2 = nn.BatchNorm2d(hidden_channels, affine=True)

    def forward(self, x_in):
        x = swish(self.bn_1(self.conv_1(x_in))) * self.activations_mask
        x = swish(self.bn_2(self.conv_2(x)) + x_in) * self.activations_mask
        return x

class Conv(nn.Module):
    def __init__(self):
        super().__init__()
        self.num_input_channels = ...
        self.num_blocks = 8
        self.num_hidden_units = 32

        self.activations_mask = get_hexagonal_activations_mask()
        self.activations_mask = self.activations_mask.to(torch.device("cuda:0" if torch.cuda.is_available() else "cpu"))

        self.kernel_mask = get_hexagonal_kernel_mask()
        self.kernel_mask = self.kernel_mask.to(torch.device("cuda:0" if torch.cuda.is_available() else "cpu"))

        self.conv_in = Hexagonal3x3Conv2dNoBias(self.num_input_channels, self.num_hidden_units, self.kernel_mask)
        self.bn_in = nn.BatchNorm2d(self.num_hidden_units, affine=True)

        self.res_stack = nn.Sequential(*[
            HexagonalBlock(
                self.num_hidden_units,
                self.activations_mask,
                self.kernel_mask
                ) for _ in range(self.num_blocks)
            ])

        self.avg_pool = hexagonal_2d_global_avg_pool
        self.max_pool = nn.MaxPool2d((11, 21), stride=1, padding=0)
        self.fc_out = nn.Linear(2 * self.num_hidden_units, 1, bias=True)
        self.tanh = nn.Tanh()

    def forward(self, x_grid, x_grid_vector, x_vector):
        x = swish(self.bn_in(self.conv_in(x_grid))) * self.activations_mask
        x = self.res_stack(x) * self.activations_mask

        x_max = self.max_pool(x)
        x_avg = self.avg_pool(x, self.activations_mask)
        x = torch.cat((x_avg, x_max), dim=1).squeeze()

        x = self.fc_out(x)
        x = self.tanh(x)
        return x


import io
import logging
import os

import pytest

from ansible_galaxy import download
from ansible_galaxy import exceptions

log = logging.getLogger(__name__)

def test_fetch_url_404(tmpdir, requests_mock):
    url = 'http://example.invalid/download/some_ns-some_name-1.2.3.tar.gz'

    some_content = b'''oopsie\n\nHey, couldn't find that.\n\n'''

    requests_mock.get(url,
                      status_code=404,
                      reason='Not Found',
                      content=some_content)

    with pytest.raises(exceptions.GalaxyDownloadError, match='.*download/some_ns-some_name-1.2.3.tar.gz.*') as exc_info:
        download.fetch_url(url)

    log.debug('exc_info: %s', exc_info)

def test_fetch_bytes_200(requests_mock):
    url = 'http://example.invalid/download/some_ns-some_name-1.2.3.tar.gz'
    some_fd = io.BytesIO(b'Some thing like an error page.\nThis is not a tar file\n\n')

    requests_mock.get(url,
                      status_code=200,
                      reason='OK',
                      body=some_fd,
                      )

    # NOTE: have to pass in chunk_size, just to get requests_mock to work, otherwise
    #       it gets stuck waiting on response.body to closer/eof and gets stuck.
    res = download.fetch_url(url, chunk_size=128)

    log.debug('res: %s', res)
    os.unlink(res)


from PIL import Image
import sys

def _main( ):
  if len( sys.argv ) < 2:
    print( 'Error, syntax is display.py <imagefile>' )
    sys.exit(0)
  try:
    img = Image.open( sys.argv[1] )
    img.show( )
  except:
    print( 'Error, could not open %s' % sys.argv[1] )
    sys.exit( 0 )


"""
Calculate CAPM using pandas
"""
import pandas as pd

from data.test import DF_DATA

def returns(prices: pd.DataFrame) -> pd.DataFrame:
    """
    Calculates and returns a DataFrame containing the returns of a ticker
    given the prices
    """
    return pd.DataFrame(prices.pct_change().dropna(how="all"))

def capm_return(
    prices: pd.DataFrame,
    market_ticker: str = "SPY",
    risk_free_rate: float = 0.02,
    compounding: bool = True,
) -> pd.DataFrame:
    """Calculates the CAPM return and prints the results along the way"""
    frequency = len(prices[market_ticker])
    # AAPL, TSLA, SPY
    print("--- PRICES ---")
    print(prices)
    print("--- RETURNS ---")
    df_ret = returns(prices)
    print(df_ret)

    print("--- COVARIANCE MATRIX ---")
    df_cov = df_ret.cov()
    print(df_cov)

    print("--- BETAS ---")
    betas = df_cov["SPY"] / df_cov.loc["SPY", "SPY"]
    print(betas)
    betas = betas.drop("SPY")

    if compounding:
        # Geometric mean returns
        mkt_mean_ret = (1 + df_ret[market_ticker]).prod() ** (
            frequency / df_ret[market_ticker].count()
        ) - 1
    else:
        # Arithmetic mean returns
        mkt_mean_ret = df_ret[market_ticker].mean() * frequency

    return risk_free_rate + betas * (mkt_mean_ret - risk_free_rate)

if __name__ == "__main__":
    capm = capm_return(DF_DATA)
    print("--- CAPM RETURN ---")
    print(capm)


"""
A module containing routines for integer factorization.
"""
import bisect
import functools
import itertools
import math
import random

import numpy as np

from ._math import prod, isqrt, iroot, ilog
from ._overrides import set_module
from ._prime import primes, is_prime

__all__ = [
    "legendre_symbol", "jacobi_symbol", "kronecker_symbol",
    "perfect_power", "trial_division", "pollard_p1", "pollard_rho",
    "divisors", "divisor_sigma",
    "is_prime_power", "is_perfect_power", "is_smooth", "is_powersmooth"
]

###############################################################################
# Legendre, Jacobi, and Kronecker symbols
###############################################################################

@set_module("galois")
def legendre_symbol(a, p):
    r"""
    Computes the Legendre symbol :math:`(\frac{a}{p})`.

    Parameters
    ----------
    a : int
        An integer.
    p : int
        An odd prime :math:`p \ge 3`.

    Returns
    -------
    int
        The Legendre symbol :math:`(\frac{a}{p})` with value in :math:`\{0, 1, -1\}`.

    Notes
    -----
    The Legendre symbol is useful for determining if :math:`a` is a quadratic residue modulo :math:`p`, namely
    :math:`a \in Q_p`. A quadratic residue :math:`a` modulo :math:`p` satisfies :math:`x^2 \equiv a\ (\textrm{mod}\ p)`
    for some :math:`x`.

    .. math::

        \bigg(\frac{a}{p}\bigg) =
            \begin{cases}
                0, & p\ |\ a

                1, & a \in Q_p

                -1, & a \in \overline{Q}_p
            \end{cases}

    References
    ----------
    * Algorithm 2.149 from https://cacr.uwaterloo.ca/hac/about/chap2.pdf

    Examples
    --------
    The quadratic residues modulo :math:`7` are :math:`Q_7 = \{1, 2, 4\}`. The quadratic non-residues
    modulo :math:`7` are :math:`\overline{Q}_7 = \{3, 5, 6\}`.

    .. ipython:: python

        [pow(x, 2, 7) for x in range(7)]
        for a in range(7):
            print(f"({a} / 7) = {galois.legendre_symbol(a, 7)}")
    """
    if not isinstance(a, (int, np.integer)):
        raise TypeError(f"Argument `a` must be an integer, not {type(a)}.")
    if not isinstance(p, (int, np.integer)):
        raise TypeError(f"Argument `p` must be an integer, not {type(p)}.")
    if not (is_prime(p) and p > 2):
        raise ValueError(f"Argument `p` must be an odd prime greater than 2, not {p}.")

    return jacobi_symbol(a, p)

@set_module("galois")
def jacobi_symbol(a, n):
    r"""
    Computes the Jacobi symbol :math:`(\frac{a}{n})`.

    Parameters
    ----------
    a : int
        An integer.
    n : int
        An odd integer :math:`n \ge 3`.

    Returns
    -------
    int
        The Jacobi symbol :math:`(\frac{a}{n})` with value in :math:`\{0, 1, -1\}`.

    Notes
    -----
    The Jacobi symbol extends the Legendre symbol for odd :math:`n \ge 3`. Unlike the Legendre symbol, :math:`(\frac{a}{n}) = 1`
    does not imply :math:`a` is a quadratic residue modulo :math:`n`. However, all :math:`a \in Q_n` have :math:`(\frac{a}{n}) = 1`.

    References
    ----------
    * Algorithm 2.149 from https://cacr.uwaterloo.ca/hac/about/chap2.pdf

    Examples
    --------
    The quadratic residues modulo :math:`9` are :math:`Q_9 = \{1, 4, 7\}` and these all satisfy :math:`(\frac{a}{9}) = 1`.
    The quadratic non-residues modulo :math:`9` are :math:`\overline{Q}_9 = \{2, 3, 5, 6, 8\}`, but notice :math:`\{2, 5, 8\}`
    also satisfy :math:`(\frac{a}{9}) = 1`. The set of integers :math:`\{3, 6\}` not coprime to :math:`9` satisfies
    :math:`(\frac{a}{9}) = 0`.

    .. ipython:: python

        [pow(x, 2, 9) for x in range(9)]
        for a in range(9):
            print(f"({a} / 9) = {galois.jacobi_symbol(a, 9)}")
    """
    if not isinstance(a, (int, np.integer)):
        raise TypeError(f"Argument `a` must be an integer, not {type(a)}.")
    if not isinstance(n, (int, np.integer)):
        raise TypeError(f"Argument `n` must be an integer, not {type(n)}.")
    if not (n > 2 and n % 2 == 1):
        raise ValueError(f"Argument `n` must be an odd integer greater than 2, not {n}.")

    a = a % n
    if a == 0:
        return 0
    if a == 1:
        return 1

    # Write a = 2^e * a1
    a1, e = a, 0
    while a1 % 2 == 0:
        a1, e = a1 // 2, e + 1
    assert 2**e * a1 == a

    if e % 2 == 0:
        s = 1
    else:
        if n % 8 in [1, 7]:
            s = 1
        else:
            s = -1

    if n % 4 == 3 and a1 % 4 == 3:
        s = -s

    n1 = n % a1
    if a1 == 1:
        return s
    else:
        return s * jacobi_symbol(n1, a1)

@set_module("galois")
def kronecker_symbol(a, n):
    r"""
    Computes the Kronecker symbol :math:`(\frac{a}{n})`.

    The Kronecker symbol extends the Jacobi symbol for all :math:`n`.

    Parameters
    ----------
    a : int
        An integer.
    n : int
        An integer.

    Returns
    -------
    int
        The Kronecker symbol :math:`(\frac{a}{n})` with value in :math:`\{0, -1, 1\}`.

    References
    ----------
    * Algorithm 2.149 from https://cacr.uwaterloo.ca/hac/about/chap2.pdf
    """
    # pylint: disable=too-many-return-statements
    if not isinstance(a, (int, np.integer)):
        raise TypeError(f"Argument `a` must be an integer, not {type(a)}.")
    if not isinstance(n, (int, np.integer)):
        raise TypeError(f"Argument `n` must be an integer, not {type(n)}.")

    if n == 0:
        return 1 if a in [1, -1] else 0
    if n == 1:
        return 1
    if n == -1:
        return -1 if a < 0 else 1
    if n == 2:
        if a % 2 == 0:
            return 0
        elif a % 8 in [1, 7]:
            return 1
        else:
            return -1

    # Factor out the unit +/- 1
    u = -1 if n < 0 else 1
    n //= u

    # Factor out the powers of 2 so the resulting n is odd
    e = 0
    while n % 2 == 0:
        n, e = n // 2, e + 1

    if n >=3 :
        # Handle the remaining odd n using the Jacobi symbol
        return kronecker_symbol(a, u) * kronecker_symbol(a, 2)**e * jacobi_symbol(a, n)
    else:
        return kronecker_symbol(a, u) * kronecker_symbol(a, 2)**e

###############################################################################
# Prime factorization
###############################################################################

@functools.lru_cache(maxsize=2048)
def factors(n):
    """
    This function is wrapped and documented in `_polymorphic.factors()`.
    """
    if not n > 1:
        raise ValueError(f"Argument `n` must be greater than 1, not {n}.")
    n = int(n)

    # Step 1
    if is_prime(n):
        return [n], [1]

    # Step 2
    result = perfect_power(n)
    if result is not None:
        base, exponent = result
        p, e = factors(base)
        e = [ei * exponent for ei in e]
        return p, e

    # Step 3
    p, e, n = trial_division(n, 10_000_000)

    # Step 4
    while n > 1 and not is_prime(n):
        f = pollard_rho(n)  # A non-trivial factor
        while f is None:
            # Try again with a different random function f(x)
            f = pollard_rho(n, c=random.randint(2, n // 2))
        if is_prime(f):
            degree = 0
            while n % f == 0:
                degree += 1
                n //= f
            p.append(f)
            e.append(degree)
        else:
            raise RuntimeError(f"Encountered a very large composite {f}. Please report this as a GitHub issue at https://github.com/mhostetter/galois/issues.")

    if n > 1:
        p.append(n)
        e.append(1)

    return p, e

@set_module("galois")
@functools.lru_cache(maxsize=512)
def perfect_power(n):
    r"""
    Returns the integer base :math:`c > 1` and exponent :math:`e > 1` of :math:`n = c^e` if :math:`n` is a perfect power.

    Parameters
    ----------
    n : int
        A positive integer :math:`n > 1`.

    Returns
    -------
    None, tuple
        `None` is :math:`n` is not a perfect power. Otherwise, :math:`(c, e)` such that :math:`n = c^e`. :math:`c`
        may be composite.

    Examples
    --------
    .. ipython:: python

        # Primes are not perfect powers
        galois.perfect_power(5)

        # Products of primes are not perfect powers
        galois.perfect_power(2*3)

        # Products of prime powers were the GCD of the exponents is 1 are not perfect powers
        galois.perfect_power(2 * 3 * 5**3)

        # Products of prime powers were the GCD of the exponents is > 1 are perfect powers
        galois.perfect_power(2**2 * 3**2 * 5**4)
        galois.perfect_power(36)
        galois.perfect_power(125)
    """
    if not isinstance(n, (int, np.integer)):
        raise TypeError(f"Argument `n` must be an integer, not {type(n)}.")
    if not n > 1:
        raise ValueError(f"Argument `n` must be greater than 1, not {n}.")
    n = int(n)

    for k in primes(ilog(n, 2)):
        x = iroot(n, k)
        if x**k == n:
            # Recursively determine if x is a perfect power
            ret = perfect_power(x)
            if ret is None:
                return x, k
            else:
                x, kk = ret
                return x, k * kk

    return None

@set_module("galois")
def trial_division(n, B=None):
    r"""
    Finds all the prime factors :math:`p_i^{e_i}` of :math:`n` for :math:`p_i \le B`.

    The trial division factorization will find all prime factors :math:`p_i \le B` such that :math:`n` factors
    as :math:`n = p_1^{e_1} \dots p_k^{e_k} n_r` where :math:`n_r` is a residual factor (which may be composite).

    Parameters
    ----------
    n : int
        A positive integer.
    B : int, optional
        The max divisor in the trial division. The default is `None` which corresponds to :math:`B = \sqrt{n}`.
        If :math:`B > \sqrt{n}`, the algorithm will only search up to :math:`\sqrt{n}`, since a prime factor of :math:`n`
        cannot be larger than :math:`\sqrt{n}`.

    Returns
    -------
    list
        The discovered prime factors :math:`\{p_1, \dots, p_k\}`.
    list
        The corresponding prime exponents :math:`\{e_1, \dots, e_k\}`.
    int
        The residual factor :math:`n_r`.

    Examples
    --------
    .. ipython:: python

        n = 2**4 * 17**3 * 113 * 15013
        galois.trial_division(n)
        galois.trial_division(n, B=500)
        galois.trial_division(n, B=100)
    """
    if not isinstance(n, (int, np.integer)):
        raise TypeError(f"Argument `n` must be an integer, not {type(n)}.")
    if not isinstance(B, (type(None), int, np.integer)):
        raise TypeError(f"Argument `B` must be an integer, not {type(B)}.")
    B = isqrt(n) if B is None else B
    if not n > 1:
        raise ValueError(f"Argument `n` must be greater than 1, not {n}.")
    if not B > 2:
        raise ValueError(f"Argument `B` must be greater than 2, not {B}.")
    n = int(n)
    B = min(isqrt(n), B)  # There cannot be a prime factor greater than sqrt(n)

    p, e = [], []
    for prime in primes(B):
        degree = 0
        while n % prime == 0:
            degree += 1
            n //= prime
        if degree > 0:
            p.append(prime)
            e.append(degree)

            # Check if we've fully factored n and if so break early
            if n == 1:
                break

    return p, e, n

@set_module("galois")
def pollard_p1(n, B, B2=None):
    r"""
    Attempts to find a non-trivial factor of :math:`n` if it has a prime factor :math:`p` such that
    :math:`p-1` is :math:`B`-smooth.

    For a given odd composite :math:`n` with a prime factor :math:`p`, Pollard's :math:`p-1` algorithm can discover a non-trivial factor
    of :math:`n` if :math:`p-1` is :math:`B`-smooth. Specifically, the prime factorization must satisfy :math:`p-1 = p_1^{e_1} \dots p_k^{e_k}`
    with each :math:`p_i \le B`.

    A extension of Pollard's :math:`p-1` algorithm allows a prime factor :math:`p` to be :math:`B`-smooth with the exception of one
    prime factor :math:`B < p_{k+1} \le B_2`. In this case, the prime factorization is :math:`p-1 = p_1^{e_1} \dots p_k^{e_k} p_{k+1}`.
    Often :math:`B_2` is chosen such that :math:`B_2 \gg B`.

    Parameters
    ----------
    n : int
        An odd composite integer :math:`n > 2` that is not a prime power.
    B : int
        The smoothness bound :math:`B > 2`.
    B2 : int, optional
        The smoothness bound :math:`B_2` for the optional second step of the algorithm. The default is `None`, which
        will not perform the second step.

    Returns
    -------
    None, int
        A non-trivial factor of :math:`n`, if found. `None` if not found.

    References
    ----------
    * Section 3.2.3 from https://cacr.uwaterloo.ca/hac/about/chap3.pdf

    Examples
    --------
    Here, :math:`n = pq` where :math:`p-1` is :math:`1039`-smooth and :math:`q-1` is :math:`17`-smooth.

    .. ipython:: python

        p, q = 1458757, 1326001
        galois.factors(p - 1)
        galois.factors(q - 1)

    Searching with :math:`B=15` will not recover a prime factor.

    .. ipython:: python

        galois.pollard_p1(p*q, 15)

    Searching with :math:`B=17` will recover the prime factor :math:`q`.

    .. ipython:: python

        galois.pollard_p1(p*q, 17)

    Searching :math:`B=15` will not recover a prime factor in the first step, but will find :math:`q` in the second
    step because :math:`p_{k+1} = 17` satisfies :math:`15 < 17 \le 100`.

    .. ipython:: python

        galois.pollard_p1(p*q, 15, B2=100)

    Pollard's :math:`p-1` algorithm may return a composite factor.

    .. ipython:: python

        n = 2133861346249
        galois.factors(n)
        galois.pollard_p1(n, 10)
        37*41
    """
    if not isinstance(n, (int, np.integer)):
        raise TypeError(f"Argument `n` must be an integer, not {type(n)}.")
    if not isinstance(B, (int, np.integer)):
        raise TypeError(f"Argument `B` must be an integer, not {type(B)}.")
    if not isinstance(B2, (type(None), int, np.integer)):
        raise TypeError(f"Argument `B2` must be an integer, not {type(B2)}.")
    if not (n % 2 == 1 and n > 2):
        raise ValueError(f"Argument `n` must be odd and greater than 2, not {n}.")
    if not B > 2:
        raise ValueError(f"Argument `B` must be greater than 2, not {B}.")
    n = int(n)

    a = 2  # A value that is coprime to n (since n is odd)
    check_stride = 10

    for i, p in enumerate(primes(B)):
        e = ilog(n, p)
        a = pow(a, p**e, n)

        # Check the GCD periodically to return early without checking all primes less than the
        # smoothness bound
        if i % check_stride == 0 and math.gcd(a - 1, n) not in [1, n]:
            return math.gcd(a - 1, n)

    d = math.gcd(a - 1, n)

    if d not in [1, n]:
        return d
    if d == n:
        return None

    # Try to find p such that p - 1 has a single prime factor larger than B
    if B2 is not None:
        P = primes(B2)
        P = P[bisect.bisect_right(P, B):bisect.bisect_right(P, B2)]  # Only select primes between B < prime <= B2
        for i, p in enumerate(P):
            a = pow(a, p, n)

            # Check the GCD periodically to return early without checking all primes less than the
            # smoothness bound
            if i % check_stride == 0 and math.gcd(a - 1, n) not in [1, n]:
                return math.gcd(a - 1, n)

        d = math.gcd(a - 1, n)
        if d not in [1, n]:
            return d

    return None

# @functools.lru_cache(maxsize=1024)
def pollard_rho(n, c=1):
    r"""
    Attempts to find a non-trivial factor of :math:`n` using cycle detection.

    Pollard's :math:`\rho` algorithm seeks to find a non-trivial factor of :math:`n` by finding a cycle in a sequence
    of integers :math:`x_0, x_1, \dots` defined by :math:`x_i = f(x_{i-1}) = x_{i-1}^2 + 1\ \textrm{mod}\ p` where :math:`p`
    is an unknown small prime factor of :math:`n`. This happens when :math:`x_{m} \equiv x_{2m}\ (\textrm{mod}\ p)`.
    Because :math:`p` is unknown, this is accomplished by computing the sequence modulo :math:`n` and looking for
    :math:`\textrm{gcd}(x_m - x_{2m}, n) > 1`.

    Parameters
    ----------
    n : int
        An odd composite integer :math:`n > 2` that is not a prime power.
    c : int, optional
        The constant offset in the function :math:`f(x) = x^2 + c\ \textrm{mod}\ n`. The default is 1. A requirement
        of the algorithm is that :math:`c \not\in \{0, -2\}`.

    Returns
    -------
    None, int
        A non-trivial factor :math:`m` of :math:`n`, if found. `None` if not found.

    References
    ----------
    * Section 3.2.2 from https://cacr.uwaterloo.ca/hac/about/chap3.pdf

    Examples
    --------
    Pollard's :math:`\rho` is especially good at finding small factors.

    .. ipython:: python

        n = 503**7 * 10007 * 1000003
        galois.pollard_rho(n)

    It is also efficient for finding relatively small factors.

    .. ipython:: python

        n = 1182640843 * 1716279751
        galois.pollard_rho(n)
    """
    if not isinstance(n, (int, np.integer)):
        raise TypeError(f"Argument `n` must be an integer, not {type(n)}.")
    if not isinstance(c, (type(None), int, np.integer)):
        raise TypeError(f"Argument `c` must be an integer, not {type(c)}.")
    if not n > 1:
        raise ValueError(f"Argument `n` must be greater than 1, not {n}.")
    if not c not in [0, -2]:
        raise ValueError("Argument `c` cannot be -2 or 0.")
    n = abs(int(n))

    f = lambda x: (x**2 + c) % n

    a, b, d = 2, 2, 1
    while d == 1:
        a = f(a)
        b = f(f(b))
        d = math.gcd(a - b, n)

    if d == n:
        return None

    return d

# def fermat_factors(n):
#     a = isqrt(n) + 1
#     b2 = a**2 - n
#     while isqrt(b2)**2 != b2:
#         a += 1
#         b2 = a**2 - n
#     b = isqrt(b2)
#     return a - b, a + b

###############################################################################
# Compsite factorization
###############################################################################

@set_module("galois")
def divisors(n):
    r"""
    Computes all positive integer divisors :math:`d` of the integer :math:`n` such that :math:`d\ |\ n`.

    Parameters
    ----------
    n : int
        Any integer.

    Returns
    -------
    list
        Sorted list of positive integer divisors :math:`d`.

    Notes
    -----
    :func:`galois.divisors` find *all* positive integer divisors or factors of :math:`n`, where :func:`galois.factors` only finds the prime
    factors of :math:`n`.

    Examples
    --------
    .. ipython:: python

        galois.divisors(0)
        galois.divisors(1)
        galois.divisors(24)
        galois.divisors(-24)
        galois.factors(24)
    """
    if not isinstance(n, (int, np.integer)):
        raise TypeError(f"Argument `n` must be an integer, not {type(n)}.")
    n = abs(int(n))

    if n == 0:
        return []
    if n == 1:
        return [1]

    # Factor n into its unique k prime factors and their exponents
    p, e = factors(n)
    k = len(p)

    # Enumerate all the prime powers, i.e. [p1, p1^2, p1^3, p2, p2^2, ...]
    prime_powers = []
    for pi, ei in zip(p, e):
        prime_powers += [pi**j for j in range(1, ei + 1)]

    d = [1, n]
    for ki in range(1, k + 1):
        # For all prime powers choose ki for ki in [1, 2, ..., k]
        for items in itertools.combinations(prime_powers, ki):
            d1 = prod(*items)  # One possible divisor
            if n % d1 == 0:
                d2 = n // d1  # The other divisor
                d += [d1, d2]

    # Reduce the list to unique divisors and sort ascending
    d = sorted(list(set(d)))

    return d

@set_module("galois")
def divisor_sigma(n, k=1):
    r"""
    Returns the sum of :math:`k`-th powers of the positive divisors of :math:`n`.

    Parameters
    ----------
    n : int
        Any integer.
    k : int, optional
        The degree of the positive divisors. The default is 1 which corresponds to :math:`\sigma_1(n)` which is the
        sum of positive divisors.

    Returns
    -------
    int
        The sum of divisors function :math:`\sigma_k(n)`.

    Notes
    -----
    This function implements the :math:`\sigma_k(n)` function. It is defined as:

    .. math:: \sigma_k(n) = \sum_{d\ |\ n} d^k

    Examples
    --------
    .. ipython:: python

        galois.divisors(9)
        galois.divisor_sigma(9, k=0)
        galois.divisor_sigma(9, k=1)
        galois.divisor_sigma(9, k=2)
    """
    if not isinstance(n, (int, np.integer)):
        raise TypeError(f"Argument `n` must be an integer, not {type(n)}.")

    d = divisors(n)

    if n == 0:
        return len(d)
    else:
        return sum([di**k for di in d])

###############################################################################
# Primality tests
###############################################################################

@set_module("galois")
def is_prime_power(n):
    r"""
    Determines if :math:`n` is a prime power :math:`n = p^k` for prime :math:`p` and :math:`k \ge 1`.

    Parameters
    ----------
    n : int
        A positive integer.

    Returns
    -------
    bool:
        `True` if the integer :math:`n` is a prime power.

    Notes
    -----
    There is some controversy over whether :math:`1` is a prime power :math:`p^0`. Since :math:`1` is the :math:`0`-th power
    of all primes, it is often regarded not as a prime power. This function returns `False` for :math:`1`.

    Examples
    --------
    .. ipython:: python

        galois.is_prime_power(8)
        galois.is_prime_power(6)
        galois.is_prime_power(1)
    """
    if not isinstance(n, (int, np.integer)):
        raise TypeError(f"Argument `n` must be an integer, not {type(n)}.")
    if not n > 0:
        raise ValueError(f"Argument `n` must be a positive integer, not {n}.")

    if n == 1:
        return False

    if is_prime(n):
        return True

    # Determine is n is a perfect power and then check is the base is prime or composite
    ret = perfect_power(n)
    if ret is not None and is_prime(ret[0]):
        return True

    return False

@set_module("galois")
def is_perfect_power(n):
    r"""
    Determines if :math:`n` is a perfect power :math:`n = x^k` for :math:`x > 0` and :math:`k \ge 2`.

    Parameters
    ----------
    n : int
        A positive integer.

    Returns
    -------
    bool:
        `True` if the integer :math:`n` is a perfect power.

    Examples
    --------
    .. ipython:: python

        galois.is_perfect_power(8)
        galois.is_perfect_power(16)
        galois.is_perfect_power(20)
    """
    if not isinstance(n, (int, np.integer)):
        raise TypeError(f"Argument `n` must be an integer, not {type(n)}.")
    if not n > 0:
        raise ValueError(f"Argument `n` must be a positive integer, not {n}.")

    if n == 1:
        return True

    if perfect_power(n) is not None:
        return True

    return False

def is_square_free(n):
    if not n > 0:
        raise ValueError(f"Argument `n` must be a positive integer, not {n}.")

    if n == 1:
        return True

    _, e = factors(n)

    return e == [1,]*len(e)

@set_module("galois")
def is_smooth(n, B):
    r"""
    Determines if the positive integer :math:`n` is :math:`B`-smooth.

    Parameters
    ----------
    n : int
        A positive integer.
    B : int
        The smoothness bound :math:`B \ge 2`.

    Returns
    -------
    bool
        `True` if :math:`n` is :math:`B`-smooth.

    Notes
    -----
    An integer :math:`n` with prime factorization :math:`n = p_1^{e_1} \dots p_k^{e_k}` is :math:`B`-smooth
    if :math:`p_k \le B`. The :math:`2`-smooth numbers are the powers of :math:`2`. The :math:`5`-smooth numbers
    are known as *regular numbers*. The :math:`7`-smooth numbers are known as *humble numbers* or *highly composite numbers*.

    Examples
    --------
    .. ipython:: python

        galois.is_smooth(2**10, 2)
        galois.is_smooth(10, 5)
        galois.is_smooth(12, 5)
        galois.is_smooth(60**2, 5)
    """
    if not isinstance(n, (int, np.integer)):
        raise TypeError(f"Argument `n` must be an integer, not {type(n)}.")
    if not isinstance(B, (int, np.integer)):
        raise TypeError(f"Argument `B` must be an integer, not {type(B)}.")
    if not n > 0:
        raise ValueError(f"Argument `n` must be non-negative, not {n}.")
    if not B >= 2:
        raise ValueError(f"Argument `B` must be at least 2, not {B}.")

    if n == 1:
        return True

    for p in primes(B):
        e = ilog(n, p)
        d = math.gcd(p**e, n)
        if d > 1:
            n //= d
        if n == 1:
            # n can be fully factored by the primes already test, therefore it is B-smooth
            return True

    # n has residual prime factors larger than B and therefore is not B-smooth
    return False

@set_module("galois")
def is_powersmooth(n, B):
    r"""
    Determines if the positive integer :math:`n` is :math:`B`-powersmooth.

    Parameters
    ----------
    n : int
        A positive integer.
    B : int
        The smoothness bound :math:`B \ge 2`.

    Returns
    -------
    bool
        `True` if :math:`n` is :math:`B`-powersmooth.

    Notes
    -----
    An integer :math:`n` with prime factorization :math:`n = p_1^{e_1} \dots p_k^{e_k}` is :math:`B`-powersmooth
    if :math:`p_i^{e_i} \le B` for :math:`1 \le i \le k`.

    Examples
    --------
    Comparison of :math:`B`-smooth and :math:`B`-powersmooth. Necessarily, any :math:`n` that is
    :math:`B`-powersmooth must be :math:`B`-smooth.

    .. ipython:: python

        galois.is_smooth(2**4 * 3**2 * 5, 5)
        galois.is_powersmooth(2**4 * 3**2 * 5, 5)
    """
    if not isinstance(n, (int, np.integer)):
        raise TypeError(f"Argument `n` must be an integer, not {type(n)}.")
    if not isinstance(B, (int, np.integer)):
        raise TypeError(f"Argument `B` must be an integer, not {type(B)}.")
    if not n > 0:
        raise ValueError(f"Argument `n` must be non-negative, not {n}.")
    if not B >= 2:
        raise ValueError(f"Argument `B` must be at least 2, not {B}.")

    if n == 1:
        return True

    D = 1  # The product of all GCDs with the prime powers
    for p in primes(B):
        e = ilog(B, p) + 1  # Find the exponent e of p such that p^e > B
        d = math.gcd(p**e, n)
        D *= d

        # If the GCD is p^e, then p^e > B divides n and therefore n cannot be B-powersmooth
        if d == p**e:
            return False

    # If the product of GCDs of n with each prime power is less than n, then n has a prime factor greater than B.
    # Therefore, n cannot be B-powersmooth.
    if D < n:
        return False

    return True


from typing import Any, Dict, Type, TypeVar, Union

import attr

from ..types import UNSET, Unset

T = TypeVar("T", bound="CodatDataIntegrityContractsSummaryMatchCountSummary")

@attr.s(auto_attribs=True)
class CodatDataIntegrityContractsSummaryMatchCountSummary:
    """
    Attributes:
        match_percentage (Union[Unset, float]):
        unmatched (Union[Unset, int]):
        matched (Union[Unset, int]):
        total (Union[Unset, int]):
    """

    match_percentage: Union[Unset, float] = UNSET
    unmatched: Union[Unset, int] = UNSET
    matched: Union[Unset, int] = UNSET
    total: Union[Unset, int] = UNSET

    def to_dict(self) -> Dict[str, Any]:
        match_percentage = self.match_percentage
        unmatched = self.unmatched
        matched = self.matched
        total = self.total

        field_dict: Dict[str, Any] = {}
        field_dict.update({})
        if match_percentage is not UNSET:
            field_dict["matchPercentage"] = match_percentage
        if unmatched is not UNSET:
            field_dict["unmatched"] = unmatched
        if matched is not UNSET:
            field_dict["matched"] = matched
        if total is not UNSET:
            field_dict["total"] = total

        return field_dict

    @classmethod
    def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
        d = src_dict.copy()
        match_percentage = d.pop("matchPercentage", UNSET)

        unmatched = d.pop("unmatched", UNSET)

        matched = d.pop("matched", UNSET)

        total = d.pop("total", UNSET)

        codat_data_integrity_contracts_summary_match_count_summary = cls(
            match_percentage=match_percentage,
            unmatched=unmatched,
            matched=matched,
            total=total,
        )

        return codat_data_integrity_contracts_summary_match_count_summary


from helper import display_task_name, display_example
'''
Darts
URL: https://exercism.org/tracks/python/exercises/darts

Instructions

Write a function that returns the earned points in a single toss of a Darts game.

Darts is a game where players throw darts at a target.

In our particular instance of the game, the target rewards 4 different amounts of points, depending on where the dart lands:

    If the dart lands outside the target, player earns no points (0 points).
    If the dart lands in the outer circle of the target, player earns 1 point.
    If the dart lands in the middle circle of the target, player earns 5 points.
    If the dart lands in the inner circle of the target, player earns 10 points.

The outer circle has a radius of 10 units (this is equivalent to the total radius for the entire target), the middle circle a radius of 5 units, and the inner circle a radius of 1. Of course, they are all centered at the same point (that is, the circles are concentric defined by the coordinates (0, 0).

Write a function that given a point in the target (defined by its Cartesian coordinates x and y, where x and y are real), returns the correct amount earned by a dart landing at that point.
'''

import math
def score(x: float, y: float) -> int:
    # inner circle radius is 1 point
    inner_circle_radius_squared = math.pow(1, 2)

    # middle circle radius is 5 point
    middle_circle_radius_squared = math.pow(5, 2)

    # outer circle radius is 10 point
    outer_circle_radius_squared = math.pow(10, 2)

    # get the distance squared using the two points from Pythagorean theorem
    pythagorean_theorem = math.pow(x - 0, 2) + math.pow(y - 0, 2)

    # if out of the outer circle
    if pythagorean_theorem > outer_circle_radius_squared:
        return 0
    elif pythagorean_theorem <= inner_circle_radius_squared:
        return 10
    elif pythagorean_theorem <= middle_circle_radius_squared:
        return 5
    elif pythagorean_theorem <= outer_circle_radius_squared:
        return 1

# Test 1
display_task_name("I", "Darts > missed target")
display_example('score(-9, 9)', 0)
print(score(-9, 9), "\n")

# Test 2
display_task_name("II", "Darts > on the outer circle")
display_example('score(0, 10)', 1)
print(score(0, 10), "\n")

# Test 3
display_task_name("III", "Darts > on the middle circle")
display_example('score(-5, 0)', 5)
print(score(-5, 0), "\n")

# Test 4
display_task_name("IV", "Darts > on the inner circle")
display_example('score(0, -1)', 10)
print(score(0, -1), "\n")

# Test 5
display_task_name("V", "Darts > exactly on centre")
display_example('score(0, 0)', 10)
print(score(0, 0), "\n")

# Test 6
display_task_name("VI", "Darts > near the centre")
display_example('score(-0.1, -0.1)', 10)
print(score(-0.1, -0.1), "\n")

# Test 7
display_task_name("VII", "Darts > just within the inner circle")
display_example('score(0.7, 0.7)', 10)
print(score(0.7, 0.7), "\n")

# Test 8
display_task_name("VIII", "Darts > just outside the inner circle")
display_example('score(0.8, -0.8)', 5)
print(score(0.8, -0.8), "\n")

# Test 9
display_task_name("IX", "Darts > just within the middle circle")
display_example('score(-3.5, 3.5)', 5)
print(score(-3.5, 3.5), "\n")

# Test 10
display_task_name("X", "Darts > just outside the middle circle")
display_example('score(-3.6, -3.6)', 1)
print(score(-3.6, -3.6), "\n")

# Test 11
display_task_name("XI", "Darts > just within the outer circle")
display_example('score(-7.0, 7.0)', 1)
print(score(-7.0, 7.0), "\n")

# Test 12
display_task_name("XII", "Darts > just outside the outer circle")
display_example('score(7.1, -7.1)', 0)
print(score(7.1, -7.1), "\n")

# Test 13
display_task_name("XIII", "Darts > asymmetric position between the inner and middle circles")
display_example('score(0.5, -4)', 5)
print(score(0.5, -4), "\n")


import math
from datetime import datetime, date
import numpy as np
from dateutil.relativedelta import relativedelta
import pandas as pd
from pandas import Series
from sklearn.preprocessing import StandardScaler
from statsmodels.tsa.stattools import adfuller

from portfolio_optimization.data_lake.mongodb import get_collection, get_feature_selection_collection
from portfolio_optimization.db import get_df_from_table, execute_db_commands, insert_df_into_table
import matplotlib.pyplot as plt

def adjust_date(row, date_header):
    d = row[date_header]
    if d.day != 1:
        d = d.replace(day=1)
        d += relativedelta(months=1)
    return d

def oecd_time_to_datetime(row, date_header):
    if "Q" in row[date_header]:
        return date_from_quarter(row, date_header)
    else:
        return date_from_year(row, date_header)

def date_from_year(row, date_header):
    if "/" in str(row[date_header]):
        return pd.to_datetime(row[date_header])
    if "-" in str(row[date_header]):
        return pd.to_datetime(row[date_header])
    return datetime(int(row[date_header]),1,1)

def date_from_quarter(row, date_header):
    split = row[date_header].split("-")
    if "Q" in split[0]:
        quarter = split[0]
        year = int(split[1])
    else:
        quarter = split[1]
        year = int(split[0])
    if quarter == "Q1":
        return datetime(year,1,1)
    if quarter == "Q2":
        return datetime(year,4,1)
    if quarter == "Q3":
        return datetime(year,7,1)
    if quarter == "Q4":
        return datetime(year,10,1)

def get_last_data_from_df(df):
    df = df.fillna(value=np.nan)
    columns = df.columns
    l = []
    last_data = df.apply(Series.last_valid_index)

    for index, value in last_data.items():
        if value is None or math.isnan(value):
            l.append(None)
        else:
            l.append(df.loc[value, index])

    return pd.DataFrame(np.array([l], dtype=np.float64), columns=columns)

def get_last_data_from_locations_df(df):
    locations = df["location"].unique()

    r = []
    for location in locations:
        df_loc = df[df["location"] == location]
        df_loc = df_loc[df_loc['date'] <= datetime.now().date()]
        df_loc = df_loc.drop(["date", "location"], axis=1)
        last_data = get_last_data_from_df(df_loc)
        last_data["location"] = location
        r.append(last_data)
    df = pd.concat(r)

    return df

def substitute_eur_money_supply(df):

    df2 = df.copy(deep=True)
    df2 = df2[df2["location"] == "EA19"]
    df2 = df2[["date","m1","m3"]]
    df2 = df2.rename(columns={"m1":"m1_EUR", "m3":"m3_EUR"})

    df = pd.merge(df, df2, left_on=["date"], right_on=["date"], how="left")
    df["m1"] = df.apply (lambda row: fix_eur_money_supply(row, "m1_EUR", "m1"), axis=1)
    df["m3"] = df.apply(lambda row: fix_eur_money_supply(row, "m3_EUR", "m3"), axis=1)
    df = df.drop(['m1_EUR', 'm3_EUR'], axis=1)
    return df

def fix_eur_money_supply(row, label_eur, label):
    if row["location"] in ['AUT','BEL','EST','FIN','FRA','DEU','GRC','IRL','ITA','LVA','LTU','LUX','NLD','PRT','SVK','SVN','ESP']:
        return row[label_eur]
    return row[label]

def keep_only_max_date(df, group_cols):

    df = df[df["date"] <= datetime.now().date()]

    max_dates = df.groupby(group_cols)["date"].max().reset_index()
    max_dates = max_dates.rename(columns={"date": "maxDate"})
    df = pd.merge(df, max_dates, left_on=group_cols, right_on=group_cols, how="inner")
    df = df[df["date"] == df["maxDate"]]
    df = df.drop(["maxDate"], axis=1)

    return df

def transform_unavailable_data(row, column, date):
    if row["date"] <= date:
        return row[column]
    return math.nan

def remove_data_not_available_at_the_time(df, created_at):

    # print(df)

    month_columns = ["shprice","emp","ppi","cpi","ltint","m1","m3","bci","cci","stint"]
    quarter_columns = ["gdphrwkdforecast","cpiforecast","realgdpforecast","nomgdpforecast","hhsavforecast",
                       "ltintforecast","stintforecast","pricerent","priceincome","rent","real","nominal"]
    year_columns = ["ggdebt","hhdebt","gdp","gdpltforecast","gdphrwkd","hhexp","fincorp","bankleverage","taxwedge",
                    "avwage","hhsav","hhfa","hhwealth","incomeineq","poverty","povertygap","trustgov","edupubexp",
                    "pisascience","tradegoodserv","ictinvst","gdexprd","ulc"]

    month_last_available_date = created_at - relativedelta(months=2)
    quarter_last_available_date = date(created_at.year, 3 * ((created_at.month - 1) // 3) + 1, 1) \
                                  - relativedelta(months=1)
    if (created_at.month - 1) % 3 == 0:
        quarter_last_available_date -= relativedelta(months=3)
    year_last_available_date = date(created_at.year-1, 12, 1)
    if created_at.month <= 2:
        year_last_available_date -= relativedelta(years=1)

    # print(month_last_available_date)
    # print(quarter_last_available_date)
    # print(year_last_available_date)

    _df = df.copy()

    for col in month_columns:
        if col in _df.columns:
            _df[col] = _df.apply(lambda row: transform_unavailable_data(row, col, month_last_available_date), axis=1)
    for col in quarter_columns:
        if col in _df.columns:
            _df[col] = _df.apply(lambda row: transform_unavailable_data(row, col, quarter_last_available_date), axis=1)
    for col in year_columns:
        if col in _df.columns:
            _df[col] = _df.apply(lambda row: transform_unavailable_data(row, col, year_last_available_date), axis=1)

    # print(_df)

    return _df

def transform_investing_date(row, date_header):
    d = row[date_header]

    if isinstance(d, date):
        return d

    if not "-" in d:
        # print(date)
        return d

    half1 = d.split("-")[0]
    half2 = d.split("-")[1]
    if len(half1) == 2:
        year = half1
        month = half2
    elif len(half1) == 1:
        year = "0"+half1
        month = half2
    else:
        year = half2
        month = half1
    return datetime.strptime(month+" "+year,'%b %y').strftime('%Y-%m-%d')

def get_dfs(remove_outlier=True, remove_corr=True):
    df = get_indicator_df_from_postgresql(f"where date between '{date(1960, 1, 1)}' and '{date(2022, 12, 31)}'",
                                          remove_outlier=remove_outlier, remove_corr=remove_corr)

    df["date"] = pd.to_datetime(df["date"])
    df["value"] = pd.to_numeric(df["value"])

    df = df.pivot_table(index="date", columns="column_name", values="value", aggfunc="sum").reset_index()
    df = df.set_index("date")

    name_df = get_df_from_table("indicator_name")
    name_df = name_df.set_index("id")

    return df, name_df

def get_indicator_df_from_postgresql(where=None, remove_outlier=True, remove_corr=True):

    df = get_df_from_table("indicator", where)
    df = df.drop("id", axis=1)
    id_df = get_df_from_table("indicator_name")
    df = pd.merge(df, id_df, left_on=["name"], right_on=["indicator"], how="inner")
    df = df.drop("indicator", axis=1)

    # print(df.head())
    # print(max(df["date"]))
    if remove_outlier:
        df = df[~df["name"].isin(['TOTBORR', 'BORROW ', 'TOTRESNS', 'NONBORRES ', '^DJUSRE', '^DJCI', 'USCI',
                                  'CL=F', 'GC=F', '^SP500BDT', 'TREASURY', 'DDDM03USA156NWDB', 'M0263AUSM500NNBR',
                                  'M14062USM027NNBR', 'M0264AUSM500NNBR', 'M1490AUSM157SNBR',
                                  'Quarterly GDP | Total | Percentage change', 'Quarterly GDP | Total | Percentage change',
                                  'Q09084USQ507NNBR',
                                  'M0263AUSM500NNBR', 'M0264AUSM500NNBR', 'M1490AUSM157SNBR', 'M14062USM027NNBR',
                                  'M09075USM476NNBR', 'Q09084USQ507NNBR',
                                  'M09086USM156NNBR', 'DDDM03USA156NWDB', 'DDDM01USA156NWDB', 'DDEM01USA156NWDB',
                                  'LABSHPUSA156NRUG', 'RTFPNAUSA632NRUG',
                                  'SIPOVGINIUSA', 'DDDI06USA156NWDB', 'ITNETUSERP2USA',
                                  'Electricity generation | Total | Gigawatt-hours'])]

    df["target_feature"] = "feature"
    df.loc[df["source"].isin(["yahoo_finance", "investing"]), "target_feature"] = "target"
    df.loc[df["name"].isin(['USSTHPI',
                            'Short-term interest rates | Total | % per annum',
                            'Long-term interest rates | Total | % per annum',
                            'Housing prices | Nominal house prices | 2015=100']), "target_feature"] = "target"

    # print(df.head())
    df["column_name"] = df["target_feature"] + df["id"].astype(str)
    # print(df.head())
    # print(max(df["date"]))

    if remove_corr:
        to_remove_corr = get_feature_selection_collection().find({'_id': "feature_to_remove_corr"}).next()['data']
        df = df[~df["column_name"].isin(to_remove_corr)]

    # date, value, column_name + ...

    return df

def shift_targets(df, columns, periods):
    targets = []
    for period in periods:
        for col in columns:
            t = f"{col}p{period}m"
            targets.append(t)
            df[t] = df[col].shift(periods=-period)

    return df, targets

def get_feature_target_shifted(df, shift_periods):
    feature_columns = [col for col in df.columns if 'feature' in col]
    t_columns = [col for col in df.columns if 'target' in col]

    df, target_columns = shift_targets(df, t_columns, shift_periods)
    df = df[[x for x in df.columns if x not in t_columns]]
    return feature_columns, target_columns, df

def standardize_data(df):
    # print(df.head())
    # print(df.tail())
    scaled_features = StandardScaler().fit_transform(df.values)
    scaled_features_df = pd.DataFrame(scaled_features, index=df.index, columns=df.columns)
    # print(scaled_features_df.head())
    # print(scaled_features_df.tail())
    return scaled_features_df

def convert_to_stationary(df, mode="diff", n_diff=1):
    if mode == "log_minus_mean":
        df = np.log(df)
        rolling_mean = df.rolling(window=12).mean()
        df = df - rolling_mean
        return df
    elif mode == "log_shift":
        df = np.log(df)
        df = df.diff()
        return df
    elif mode == "diff":
        for i in range(n_diff):
            df = df.diff()
        return df

def convert_to_non_stationary(original_df, stationary_df, order):

    """Revert back the differencing to get the forecast to original scale."""
    tmp = stationary_df.copy()

    columns = original_df.columns
    for col in columns:

        cumsum = tmp[col].cumsum()
        # print(cumsum)

        for i in range(order):

            # print("\nPASSAGGIO",i,"\n")

            # print("diff n = ", order - i - 1)
            diff_df = original_df.copy()
            for i in range(order - i - 1):
                diff_df = diff_df.diff().dropna()

            first_element = diff_df[col].iloc[0]
            # print("First element", first_element)

            tmp = first_element + cumsum
            # print("tmp")
            # print(tmp)

            tmp = pd.concat([pd.Series(first_element), tmp])
            cumsum = tmp.cumsum()
            # print("cumsum")
            # print(cumsum)

            tmp = pd.DataFrame(tmp, columns=[col])
            # print("final_tmp")
            # print(tmp)

    return tmp

def get_stationarity(df, col, plot=True):

    # rolling statistics
    # if rolling mean and/or std move over time (line is not horizontal) data is NOT stationary. NOT Stationary = value does depend on date
    rolling_mean = df.rolling(window=12).mean()
    rolling_std = df.rolling(window=12).std()

    # Dickey–Fuller test
    # if p-value is > 0.05 we can conclude data is NOT stationary. NOT Stationary = value does depend on date
    result = adfuller(df[col])
    # print('ADF Statistic: {}'.format(result[0]))
    print('Adfuller p-value: {}'.format(result[1]))
    # print('Critical Values:')
    # for key, value in result[4].items():
    #     print('\t{}: {}'.format(key, value))

    # rolling statistics plot
    if plot:
        original = plt.plot(df, color='blue', label='Original')
        mean = plt.plot(rolling_mean, color='red', label='Rolling Mean')
        std = plt.plot(rolling_std, color='black', label='Rolling Std')
        plt.legend(loc='best')
        plt.title('Rolling Mean & Standard Deviation')
        plt.show()

def get_indicator_name(df, column):
    period = ""
    if "p" in column:
        period = column.split("p")[1]
        column = column.split("p")[0]
    id = int(column.replace('target', '').replace('feature', ''))
    return df.loc[id, "indicator"] + period

def get_eda_df(remove_outliers=True, remove_correlation=False, remove_selected=False):
    # We get dataFrame from table "indicator"
    df = get_df_from_table("indicator", f"where date between '{date(1960, 1, 1)}' and '{date(2022, 12, 31)}'")
    df = df.drop("id", axis=1)

    # We get dataFrame from table "indicator_name", we do this to convert features and target names to something more simple to handle
    name_df = get_df_from_table("indicator_name")
    df = pd.merge(df, name_df, left_on=["name"], right_on=["indicator"], how="inner")
    df = df.drop("indicator", axis=1)
    name_df = name_df.set_index("id")

    # This new code remove the outliers we identified previously
    if remove_outliers:
        df = df[~df["name"].isin(['TOTBORR', 'BORROW ', 'TOTRESNS', 'NONBORRES ', '^DJUSRE', '^DJCI', 'USCI',
                                  'CL=F', 'GC=F', '^SP500BDT', 'TREASURY', 'DDDM03USA156NWDB', 'M0263AUSM500NNBR',
                                  'M14062USM027NNBR', 'M0264AUSM500NNBR', 'M1490AUSM157SNBR',
                                  'Quarterly GDP | Total | Percentage change',
                                  'Quarterly GDP | Total | Percentage change',
                                  'Q09084USQ507NNBR',
                                  'M0263AUSM500NNBR', 'M0264AUSM500NNBR', 'M1490AUSM157SNBR', 'M14062USM027NNBR',
                                  'M09075USM476NNBR', 'Q09084USQ507NNBR',
                                  'M09086USM156NNBR', 'DDDM03USA156NWDB', 'DDDM01USA156NWDB', 'DDEM01USA156NWDB',
                                  'LABSHPUSA156NRUG', 'RTFPNAUSA632NRUG',
                                  'SIPOVGINIUSA', 'DDDI06USA156NWDB', 'ITNETUSERP2USA',
                                  'Electricity generation | Total | Gigawatt-hours'])]

    # We select which indicators are feature and which are targets based on these name list
    df["target_feature"] = "feature"
    df.loc[df["source"].isin(["yahoo_finance", "investing"]), "target_feature"] = "target"
    df.loc[df["name"].isin(['USSTHPI',
                            'Short-term interest rates | Total | % per annum',
                            'Long-term interest rates | Total | % per annum',
                            'Housing prices | Nominal house prices | 2015=100']), "target_feature"] = "target"
    df["column_name"] = df["target_feature"] + df["id"].astype(str)

    if remove_correlation:
        to_remove_corr = get_feature_selection_collection().find({'_id': "feature_to_remove_correlation"}).next()['data']
        df = df[~df["column_name"].isin(to_remove_corr)]

    if remove_selected:
        to_remove_corr = get_feature_selection_collection().find({'_id': "selected_features"}).next()['data']
        df = df[~df["column_name"].isin(to_remove_corr)]

    # Converting dataFrame values to datetime and numeric
    df["date"] = pd.to_datetime(df["date"])
    df["value"] = pd.to_numeric(df["value"])

    # In the end we pivot df to obtain a pivot table where columns are feature and targets and rows are dates.
    df = df.pivot_table(index="date", columns="column_name", values="value", aggfunc="sum").reset_index()
    df = df.set_index("date")

    return df, name_df

def forecast_accuracy(forecast, actual):

    mape = np.mean(np.abs(forecast - actual) / np.abs(actual))  # MAPE
    me = np.mean(forecast - actual)  # ME
    mae = np.mean(np.abs(forecast - actual))  # MAE
    mpe = np.mean((forecast - actual) / actual)  # MPE
    rmse = np.mean((forecast - actual) ** 2) ** .5  # RMSE
    corr = np.corrcoef(forecast, actual)[0, 1]  # corr

    return ({'mape': mape, 'me': me, 'mae': mae,
             'mpe': mpe, 'rmse': rmse, 'corr': corr})


from django.conf.urls import url

from . import views

urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'mayosql/$', views.mayosql, name = 'mayosql'),
    url(r'^mayo/', views.mayo, name='mayo'),
    url(r'^insumo/$', views.insumo, name='insumo'),
    url(r'^tablaprueba/$', views.tablaprueba, name='tablaprueba'),
   # url(r'^test_insercion/$', views.testinsert, name='test_insercion')
    url(r'^consulta1/$', views.consulta1, name='consulta1'),
    url(r'^consulta2/$', views.consulta2, name='consulta2'),
    url(r'^consulta3/$', views.consulta3, name='consulta3'),
    url(r'^consulta11/$',views.consulta11,name='consulta11'),
    url(r'^consulta22/$',views.consulta22,name='consulta22'),
    url(r'^consulta33/$',views.consulta33,name='consulta33'),
    #url(r'^formularioprueba/$',views.pruebaformulario,name='pruebaformulario'),
]


from django.urls import path

import authapp.views as authapp

app_name = 'authapp'

urlpatterns = [
    path('login/', authapp.login, name='login'),
    path('logout/', authapp.logout, name='logout'),
    path('user/register/', authapp.user_register, name='user_register'),
    path('user/profile/', authapp.user_profile, name='user_profile'),
    path('user/verify/<str:email>/<str:activation_key>/',
         authapp.user_verify, name='user_verify'),
]


from django.shortcuts import render, get_object_or_404
from .forms import ContactForm
from django.conf import settings
from django.core.mail import send_mail
from profiles.models import UserProfile
from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required

def index(request):
    """ A view to return the index page """
    return render(request, 'home/index.html')

@login_required
def contact(request):
    """ Renders a contact form """
    form = ContactForm(request.POST or None)
    profileUser = get_object_or_404(User, username=request.user)
    if form.is_valid():
        subject = form.cleaned_data.get("subject")
        contact_email = profileUser.email
        content = form.cleaned_data.get("content")
        content = " with the email, " + contact_email + \
            ", sent the following message:\n\n" + content
        # Send email
        send_mail(
            subject, content, settings.DEFAULT_FROM_EMAIL, [
                settings.DEFAULT_FROM_EMAIL])
        context = {'profile': profileUser}
        return render(request, 'home/contact_us_success.html', context)
    else:
        context = {'form': form}
        return render(request, 'home/contact_us.html', context)


from fastapi import APIRouter, UploadFile, Depends, Body

from modern.xor_cipher import XorCipher
from modern.solver.xor import XorDetector, XorSolver
from models import ClassicalCipherInput, FrequencyInput
from classical.alphabet import Alphabet
from fastapi import File

router = APIRouter(
    prefix="/modern",
    tags=["modern"]
)

@router.post("/xor/encrypt")
async def xor_encrypt(text: str, key: str):
    text = text.encode()
    key = key.encode()
    initial_key = key

    key_repeated_times = len(text) // len(initial_key)

    for _ in range(key_repeated_times):
        key += initial_key

    key = key[:len(text)]

    result = XorCipher.encrypt(text, key)

    return {"data": ''.join(format(x, '02x') for x in result)}

@router.post("/xor/solve-single-xor")
async def solve_xor(input: FrequencyInput):
    alphabet = Alphabet(**input.alphabet.dict())

    xor_solver = XorSolver(alphabet)
    result = xor_solver.solve(input.text, "ENGLISH", threshold=40)

    return {"plaintext": result}

@router.post("/xor/detect-single-xor")
async def detect_xor(input: ClassicalCipherInput = Body(...), file: UploadFile = File()):
    alphabet = Alphabet(**input.alphabet.dict())

    xor_detector = XorDetector(alphabet)

    strings = (await file.read()).decode('utf-8').split("\r\n")
    result = xor_detector.detect(strings, "ENGLISH", threshold=50)

    return {"plaintext": result}

@router.post("/xor/solve-multi-xor")
async def solve_multi_key_xor(input: FrequencyInput):
    alphabet = Alphabet(**input.alphabet.dict())

    xor_solver = MultiXorSolver(alphabet)
    result = xor_solver.solve(input.text, "ENGLISH", threshold=40)

    return {"plaintext": result}


import logging
import os

logging.basicConfig(format="%(levelname)s:%(message)s", level=logging.DEBUG)
logging.disable(logging.DEBUG)
tests_enable = True

def get_sample_no(first_samples, sample_no):
    # doesn't contain the last one
    last_seen = {}
    for idx, val in enumerate(first_samples[:-1]):
        last_seen[val] = idx
    samples = first_samples[:]
    while len(samples) < sample_no:
        last_said = samples[-1]
        if last_said in last_seen:
            logging.debug("last said seen, appending dist: %d" % dist)
            last_said_idx = len(samples) - 1
            dist = last_said_idx - last_seen[last_said]
        else:
            logging.debug("last said %d not seen, appending 0" % last_said)
            dist = 0
        samples.append(dist)
        last_seen_not_last = len(samples) - 2
        last_seen[samples[last_seen_not_last]] = last_seen_not_last
        if len(samples) % 1000000 == 0:
            logging.info("{} / {} samples calculated"
            .format(len(samples), sample_no))
    return samples[sample_no - 1]

if tests_enable:
    test_ins = []
    with open("test_inputs", "r") as f:
        for line in f:
            test_ins.append(list(map(int, line.rstrip().split(","))))
    test_outs = []
    with open("test_outputs", "r") as f:
        for line in f:
            test_outs.append(int(line.rstrip()))
    logging.debug("test_ins: %s, test_outs: %s" % (test_ins, test_outs))
    for test in zip(test_ins, test_outs):
        test_in, test_out = test
        assert(get_sample_no(test_in, 2020) == test_out)

actual_in = []
with open("input", "r") as f:
    for line in f:
        actual_in.append(list(map(int, line.rstrip().split(","))))
logging.debug("actual in: %s" % actual_in)

print(get_sample_no(*actual_in, 2020))
print(get_sample_no(*actual_in, 30000000 ))


"""Feature Learning.

Feature Learning with K-means clustering. Nearest Neighbours, and Bag of Words.
"""
import pickle
import gzip
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from sklearn.cluster import KMeans
from sklearn.neighbors import KNeighborsClassifier

# Import the train/test data (raw image pixels).
with gzip.open('../raw_pixels_dataset_5980.pklz', 'rb') as f:
    (train_labels, train_images, test_labels, test_images) = pickle.load(f)

print("train_images shape" + str(train_images.shape))
print("test_images shape" + str(test_images.shape))

# Show a few samples of the positive and negative classes.
num_text = sum(train_labels == 0)
# The dataset is symmetric which means that half of data is positive.
# That means that the following code will also work:
# ** np.random.randint(train_labels.shape[0]/2, train_labels.shape[0]), :],

fig = plt.figure()
for i in range(1, 6):
    ax = fig.add_subplot(2, 5, i)
    ax.imshow(
        np.reshape(
            train_images[
                # np.random.randint(0, train_labels.shape[0]/2), :],
                np.random.randint(0, num_text), :],
            [32, 32]),
        cmap=plt.cm.gray)
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)
    ax = fig.add_subplot(2, 5, i+5)
    ax.imshow(
        np.reshape(
            train_images[
                # can be replaced with **
                np.random.randint(num_text, train_labels.shape[0]), :],
            [32, 32]),
        cmap=plt.cm.gray)
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)
# plt.show()

# -------------------------------------------------
# Extract patches (of 8x8 pixels) from train images

# reshape images to 32x32 and scale values to (0, 1)
images = train_images.reshape((-1, 32, 32)).astype('float32') / 255
print("Images shape " + str(images.shape))

# Collect image patches with sliding window (8x8) in each train image sample
PATCH_SIZE = 8
STEP_SIZE = 8

patches = np.zeros((0, PATCH_SIZE, PATCH_SIZE))

for x in range(0, 32 - PATCH_SIZE + 1, STEP_SIZE):
    for y in range(0, 32 - PATCH_SIZE + 1, STEP_SIZE):
        patches = np.concatenate(
                    (patches, images[:, x: x + PATCH_SIZE, y: y + PATCH_SIZE]),
                    axis=0)

patches = patches.reshape((patches.shape[0], -1))
print("Patches shape " + str(patches.shape))

# Visualize a few patches
fig = plt.figure()
for i in range(1, 6):
    ax = fig.add_subplot(1, 5, i)
    ax.imshow(
        np.reshape(patches[i], [PATCH_SIZE, PATCH_SIZE]),
        cmap=plt.cm.gray, vmin=0, vmax=1)
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)
# plt.show()

# ---------------------------------------------------
# Preprocessing (I) Contrast Normalization of Patches

mu = patches.mean(axis=1)  # mean values

# standard deviation (plus a small value)
sigma = patches.std(axis=1) + 0.0001

# subtract the mean and divide by the standard deviation
patches = (patches - mu.reshape([-1, 1])) / (sigma.reshape([-1, 1]))

# Set NaN (not a number) values (if exist) to 0
w = np.isnan(patches)
patches[w] = 0

# Visualize a few Constrast Normalized patches
fig = plt.figure()
for i in range(1, 6):
    ax = fig.add_subplot(1, 5, i)
    ax.imshow(
        np.reshape(patches[i], [PATCH_SIZE, PATCH_SIZE]),
        cmap=plt.cm.gray)
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)
# plt.show()

# ------------------------------------------------------
# Preprocessing (II) ZCA Whitening of normalized patches

eig_values, eig_vec = np.linalg.eig(np.cov(patches.T))
epsilon = 0.01
pca = eig_vec.dot(np.diag((eig_values + epsilon) ** -0.5).dot(eig_vec.T))

M = patches.mean(axis=0)
patches = patches - M  # substract average value
patches = np.dot(patches, pca)  # perform pca whitening

# Visualize a few preprocessed patches
fig = plt.figure()
for i in range(1, 6):
    ax = fig.add_subplot(1, 5, i)
    ax.imshow(
        np.reshape(patches[i], [PATCH_SIZE, PATCH_SIZE]),
        cmap=plt.cm.gray)
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)
# plt.show()

# ----------------------------------------------------
# K-means clustering to learn K visual_words from data
NUM_VISUAL_WORDS = 64

km = KMeans(n_clusters=NUM_VISUAL_WORDS, max_iter=50, n_init=1, verbose=False)
km.fit(patches)

visual_words = km.cluster_centers_
print("visual_words shape " + str(visual_words.shape))

# Visualize the learned vocabulary of visual words
fig = plt.figure()
num_col = int(np.ceil(float(NUM_VISUAL_WORDS) / 4))
for i in xrange(NUM_VISUAL_WORDS):
    ax = fig.add_subplot(4, num_col, i+1)
    visual_word_ = visual_words[i, :]
    visual_word_ = visual_word_.reshape(PATCH_SIZE, PATCH_SIZE)
    ax.imshow(visual_word_, interpolation='none', cmap=cm.Greys_r)
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)
# plt.show()

# -------------------------------------------------------------
# Learn a KNN classifier, each visual word represents one class
# In this case KNN is used simply as a way to search the nearest neighbor
# visual word in our vocabulary

neig = KNeighborsClassifier(n_neighbors=1)
neig.fit(visual_words, range(0, NUM_VISUAL_WORDS))

# Extract features from train images
train_features = np.zeros((images.shape[0], visual_words.shape[0]))

for i in range(0, images.shape[0]):  # for each image
    # Do sliding window (8x8) in each image to extract patches
    # then normalize, whiten and build the Bag of Words histogram
    for x in range(0, 32 - PATCH_SIZE + 1, STEP_SIZE):
        for y in range(0, 32 - PATCH_SIZE + 1, STEP_SIZE):
            patch = images[i, x: x + PATCH_SIZE, y: y + PATCH_SIZE]
            patch = patch.reshape(1, -1)

            # Preprocessing (I): normalize
            mu = patch.mean(axis=1)  # mean values
            sigma = (
                patch.std(axis=1) +
                max(np.ptp(patch, axis=1) / 20.0, 0.0001))  # std + small value
            patch = (patch-(mu[np.newaxis, :]).T)/(sigma[np.newaxis, :]).T

            # Set NaN values to 0
            w = np.isnan(patch)
            patch[w] = 0

            # Preprocessing (II): ZCA whitening
            patch = patch - M  # substract average value
            patch = np.dot(patch, pca)  # perform pca whitening

            # BoW
            nn = neig.predict(patch)
            train_features[i, nn] = train_features[i, nn] + 1

    # Histogram Normalization
    train_features[i, :] = train_features[i, :] / max(train_features[i, :])

# If you want to save your training features
with gzip.open('./BoW_train_features.pklz', 'wb') as f:
    pickle.dump((train_labels, train_features), f, pickle.HIGHEST_PROTOCOL)

# Extract features from test images
images = test_images.reshape((-1, 32, 32)).astype('float32') / 255
test_features = np.zeros((images.shape[0], visual_words.shape[0]))

for i in range(0, images.shape[0]):  # for each image
    # Do sliding window (8x8) in each image to extract patches
    # then normalize, whiten and build the Bag of Words histogram
    for x in range(0, 32 - PATCH_SIZE + 1, STEP_SIZE):
        for y in range(0, 32 - PATCH_SIZE + 1, STEP_SIZE):
            patch = images[i, x: x + PATCH_SIZE, y: y + PATCH_SIZE]
        patch = patch.reshape(1, -1)

        # PreProcessing (I): Normalize
        mu = patch.mean(axis=1)  # mean values
        sigma = (
            patch.std(axis=1) +
            max(np.ptp(patch, axis=1) / 20.0, 0.0001))  # std + small value
        patch = (patch - (mu[np.newaxis, :]).T) / (sigma[np.newaxis, :]).T

        # Set NaN values to 0
        w = np.isnan(patch)
        patch[w] = 0

        # PreProcessing (II): ZCA whitening
        patch = patch - M  # subtract average value
        patch = np.dot(patch, pca)  # perform pca whitening

        # BoW
        nn = neig.predict(patch)
        test_features[i, nn] = test_features[i, nn] + 1

    # Histogram normalization
    test_features[i, :] = test_features[i, :] / max(test_features[i, :])

# If you want to save your test features
with gzip.open('./BoW_test_features.pklz', 'wb') as f:
    pickle.dump((test_labels, test_features), f, pickle.HIGHEST_PROTOCOL)


# from pudb import set_trace; set_trace()
from typing import List
from collections import Counter
import math

class Solution1:
    def minDominoRotations(self, tops: List[int], bottoms: List[int]) -> int:
        """LeetCode 1007

        We first use counter on tops and bottoms. Then we check tiles 1 to 6.
        For each tile, we first check whether the total number of occurrences
        of the tile is larger or equal to len(tops). If not, this tile cannot
        fill all top or all bottom. If the number is larger or equal to
        len(tops), then we check whether coverage is possible. We iterate
        through all positions and keep count of how many missing spots there
        are on top and bottom. If at any position, neither top or bottom has
        the current tile, the current tile cannot cover. Otherwise, after
        confirming that the current tile can cover top or bottom, we compare
        the number of swaps needed if bottom swaps to top or the other way
        around.

        O(6N), 1858 ms, 21% ranking.
        """
        topc = Counter(tops)
        botc = Counter(bottoms)
        N = len(tops)
        res = math.inf
        for t in range(1, 7):
            if topc[t] + botc[t] < N:
                continue
            mis_top = mis_bot = 0
            for i in range(N):
                if tops[i] != t and bottoms[i] == t:
                    mis_top += 1
                if tops[i] == t and bottoms[i] != t:
                    mis_bot += 1
                if tops[i] != t and bottoms[i] != t:
                    break
            else:
                res = min(res, min(mis_top, topc[t]), min(mis_bot, botc[t]))
        return -1 if res == math.inf else res

class Solution2:
    def minDominoRotations(self, tops: List[int], bottoms: List[int]) -> int:
        """This solution is from the one submitted on 2020-10-19.

        The key observation is that to make the swap work, the tops and bottoms
        must confirm to either tops[0] and bottoms[0]. Thus, these are the only
        two values that need to check.
        """
        topc = Counter(tops)
        botc = Counter(bottoms)
        N = len(tops)
        res = math.inf
        for t in set([tops[0], bottoms[0]]):  # only consider two values
            if topc[t] + botc[t] < N:
                continue
            mis_top = mis_bot = 0
            for i in range(N):
                if tops[i] != t and bottoms[i] == t:
                    mis_top += 1
                if tops[i] == t and bottoms[i] != t:
                    mis_bot += 1
                if tops[i] != t and bottoms[i] != t:
                    break
            else:
                res = min(res, min(mis_top, topc[t]), min(mis_bot, botc[t]))
        return -1 if res == math.inf else res

sol = Solution2()
tests = [
    ([2,1,2,4,2,2], [5,2,6,2,3,2], 2),
    ([3,5,1,2,3], [3,6,3,3,4], -1),
    ([1,2,1,1,1,2,2,2], [2,1,2,2,2,2,2,2], 1),
]

for i, (tops, bottoms, ans) in enumerate(tests):
    res = sol.minDominoRotations(tops, bottoms)
    if res == ans:
        print(f'Test {i}: PASS')
    else:
        print(f'Test {i}; Fail. Ans: {ans}, Res: {res}')


import zmq
import json

# Dummy data

user_manifest = {
    '/': {'type': 'folder'},
    '/foo': {'type': 'folder'},
    '/foo/sub.txt': {'type': 'file', 'id': '001'},
    '/bar.txt': {'type': 'file', 'id': '002'},
}

file_manifests = {
    '001': {
        'blocks': [
            {'id': '001001'},
            {'id': '001002'},
        ]
    },
    '002': {
        'blocks': [
            {'id': '002001'},
            {'id': '002002'},
        ]
    }
}

# Also handle blocks in backend for the sake of simplicity
blocks = {
    # Should be bytes, but use str instead not to bother with json encoding
    '001001': 'hello ',
    '001002': 'world !',
    '002001': 'foo',
    '002002': 'bar',
}

def main(addr):
    global user_manifest
    global file_manifests
    global blocks
    context = zmq.Context()
    socket = context.socket(zmq.ROUTER)
    socket.bind(addr)

    try:
        while True:
            id, _, raw_msg = socket.recv_multipart()
            msg = json.loads(raw_msg.decode())
            if msg['cmd'] == 'user_manifest_read':
                resp = {'status': 'ok', 'content': user_manifest}
            elif msg['cmd'] == 'user_manifest_write':
                user_manifest = msg['content']
                resp = {'status': 'ok'}
            elif msg['cmd'] == 'file_manifest_read':
                try:
                    resp = {'status': 'ok', 'content': file_manifests[msg['id']]}
                except KeyError:
                    resp = {'status': 'not_found'}
            elif msg['cmd'] == 'file_manifest_write':
                try:
                    file_manifests[msg['id']] = msg['content']
                except KeyError:
                    resp = {'status': 'not_found'}
                resp = {'status': 'ok'}
            elif msg['cmd'] == 'block_read':
                try:
                    resp = {'status': 'ok', 'content': blocks[msg['id']]}
                except KeyError:
                    resp = {'status': 'not_found'}
            elif msg['cmd'] == 'block_write':
                try:
                    blocks[msg['id']] = msg['content']
                except KeyError:
                    resp = {'status': 'not_found'}
                resp = {'status': 'ok'}
            socket.send_multipart([id, b'', json.dumps(resp).encode()])
            print('MSG: %s ==> %s' % (msg, resp))
    except KeyboardInterrupt:
        print('bye ;-)')

if __name__ == '__main__':
    import sys
    if len(sys.argv) != 2:
        print("usage: backend.py <address>")
        raise SystemExit(1)
    main(sys.argv[1])


from fastapi.testclient import TestClient

from app.core.config import settings

def test_get_request(client: TestClient, user_token_headers: dict) -> None:
    response = client.get(
        f"{settings.API_V1_STR}/bpmanager/test_get",
        headers=user_token_headers,
    )
    assert response.status_code == 200
    content = response.json()
    assert content["msg"] == "BP Manager GET"

def test_post_request(client: TestClient, user_token_headers: dict) -> None:
    data = {"msg": "Test POST request"}
    response = client.post(
        f"{settings.API_V1_STR}/bpmanager/test_post",
        headers=user_token_headers,
        json=data,
    )
    assert response.status_code == 200
    content = response.json()
    assert content["msg"] == f"BP Manager POST - {data['msg']}"


import importlib
import pkgutil

from langchain_core.load.mapping import SERIALIZABLE_MAPPING

def import_all_modules(package_name: str) -> dict:
    package = importlib.import_module(package_name)
    classes: dict = {}

    for attribute_name in dir(package):
        attribute = getattr(package, attribute_name)
        if hasattr(attribute, "is_lc_serializable") and isinstance(attribute, type):
            if (
                isinstance(attribute.is_lc_serializable(), bool)  # type: ignore
                and attribute.is_lc_serializable()  # type: ignore
            ):
                key = tuple(attribute.lc_id())  # type: ignore
                value = tuple(attribute.__module__.split(".") + [attribute.__name__])
                if key in classes and classes[key] != value:
                    raise ValueError
                classes[key] = value
    if hasattr(package, "__path__"):
        for loader, module_name, is_pkg in pkgutil.walk_packages(
            package.__path__, package_name + "."
        ):
            if module_name not in (
                "langchain.chains.llm_bash",
                "langchain.chains.llm_symbolic_math",
                "langchain.tools.python",
                "langchain.vectorstores._pgvector_data_models",
            ):
                importlib.import_module(module_name)
                new_classes = import_all_modules(module_name)
                for k, v in new_classes.items():
                    if k in classes and classes[k] != v:
                        raise ValueError
                    classes[k] = v
    return classes

def test_serializable_mapping() -> None:
    serializable_modules = import_all_modules("langchain")
    missing = set(SERIALIZABLE_MAPPING).difference(serializable_modules)
    assert missing == set()
    extra = set(serializable_modules).difference(SERIALIZABLE_MAPPING)
    assert extra == set()

    for k, import_path in serializable_modules.items():
        import_dir, import_obj = import_path[:-1], import_path[-1]
        # Import module
        mod = importlib.import_module(".".join(import_dir))
        # Import class
        cls = getattr(mod, import_obj)
        assert list(k) == cls.lc_id()