from math import sqrt

import dask.array as da
from numba import prange
import numpy as np
import xarray as xr

from xrspatial.utils import ngjit, get_dataarray_resolution

EUCLIDEAN = 0
GREAT_CIRCLE = 1
MANHATTAN = 2

PROXIMITY = 0
ALLOCATION = 1
DIRECTION = 2

def _distance_metric_mapping():
    DISTANCE_METRICS = {}
    DISTANCE_METRICS["EUCLIDEAN"] = EUCLIDEAN
    DISTANCE_METRICS["GREAT_CIRCLE"] = GREAT_CIRCLE
    DISTANCE_METRICS["MANHATTAN"] = MANHATTAN

    return DISTANCE_METRICS

# create dictionary to map distance metric presented by string and the
# corresponding metric presented by integer.
DISTANCE_METRICS = _distance_metric_mapping()

@ngjit
def euclidean_distance(x1: float, x2: float, y1: float, y2: float) -> float:
    """
    Calculates Euclidean (straight line) distance between (x1, y1) and
    (x2, y2).

    Parameters
    ----------
    x1 : float
        x-coordinate of the first point.
    x2 : float
        x-coordinate of the second point.
    y1 : float
        y-coordinate of the first point.
    y2 : float
        y-coordinate of the second point.

    Returns
    -------
    distance : float
        Euclidean distance between two points.

    References
    ----------
        - Wikipedia: https://en.wikipedia.org/wiki/Euclidean_distance#:~:text=In%20mathematics%2C%20the%20Euclidean%20distance,being%20called%20the%20Pythagorean%20distance. # noqa

    Examples
    --------
    .. sourcecode:: python

        >>> # Imports
        >>> from xrspatial import euclidean_distance
        >>> point_a = (142.32, 23.23)
        >>> point_b = (312.54, 432.01)

        >>> # Calculate Euclidean Distance
        >>> dist = euclidean_distance(
                point_a[0],
                point_b[0],
                point_a[1],
                point_b[1],
            )
        >>> print(dist)
        442.80462599209596
    """
    x = x1 - x2
    y = y1 - y2
    return np.sqrt(x * x + y * y)

@ngjit
def manhattan_distance(x1: float, x2: float, y1: float, y2: float) -> float:
    """
    Calculates Manhattan distance (sum of distance in x and y directions)
    between (x1, y1) and (x2, y2).

    Parameters
    ----------
    x1 : float
        x-coordinate of the first point.
    x2 : float
        x-coordinate of the second point.
    y1 : float
        y-coordinate of the first point.
    y2 : float
        y-coordinate of the second point.

    Returns
    -------
    distance : float
        Manhattan distance between two points.

    References
    ----------
        - Wikipedia: https://en.wikipedia.org/wiki/Taxicab_geometry

    Examples
    --------
    .. sourcecode:: python

        >>> # Imports
        >>> from xrspatial import manhattan_distance

        >>> point_a = (142.32, 23.23)
        >>> point_b = (312.54, 432.01)

        >>> # Calculate Euclidean Distance
        >>> dist = manhattan_distance(
                point_a[0],
                point_b[0],
                point_a[1],
                point_b[1],
            )
        >>> print(dist)
        196075.9368
    """
    x = x1 - x2
    y = y1 - y2
    return abs(x) + abs(y)

@ngjit
def great_circle_distance(
    x1: float, x2: float, y1: float, y2: float, radius: float = 6378137
) -> float:
    """
    Calculates great-circle (orthodromic/spherical) distance between
    (x1, y1) and (x2, y2), assuming each point is a longitude,
    latitude pair.

    Parameters
    ----------
    x1 : float
        x-coordinate (latitude) between -180 and 180 of the first point.
    x2: float
        x-coordinate (latitude) between -180 and 180 of the second point.
    y1: float
        y-coordinate (longitude) between -90 and 90 of the first point.
    y2: float
        y-coordinate (longitude) between -90 and 90 of the second point.
    radius: float, default=6378137
        Radius of sphere (earth).

    Returns
    -------
    distance : float
        Great-Circle distance between two points.

    References
    ----------
        - Wikipedia: https://en.wikipedia.org/wiki/Great-circle_distance#:~:text=The%20great%2Dcircle%20distance%2C%20orthodromic,line%20through%20the%20sphere's%20interior). # noqa

    Examples
    --------
    .. sourcecode:: python

        >>> # Imports
        >>> from xrspatial import great_circle_distance

        >>> point_a = (123.2, 82.32)
        >>> point_b = (178.0, 65.09)

        >>> # Calculate Euclidean Distance
        >>> dist = great_circle_distance(
                point_a[0],
                point_b[0],
                point_a[1],
                point_b[1],
            )
        >>> print(dist)
        2378290.489801402
    """
    if x1 > 180 or x1 < -180:
        raise ValueError(
            "Invalid x-coordinate of the first point."
            "Must be in the range [-180, 180]"
        )

    if x2 > 180 or x2 < -180:
        raise ValueError(
            "Invalid x-coordinate of the second point."
            "Must be in the range [-180, 180]"
        )

    if y1 > 90 or y1 < -90:
        raise ValueError(
            "Invalid y-coordinate of the first point."
            "Must be in the range [-90, 90]"
        )

    if y2 > 90 or y2 < -90:
        raise ValueError(
            "Invalid y-coordinate of the second point."
            "Must be in the range [-90, 90]"
        )

    lat1, lon1, lat2, lon2 = (
        np.radians(y1),
        np.radians(x1),
        np.radians(y2),
        np.radians(x2),
    )
    dlon = lon2 - lon1
    dlat = lat2 - lat1
    a = np.sin(dlat / 2.0) ** 2 + \
        np.cos(lat1) * np.cos(lat2) * np.sin(dlon / 2.0) ** 2

    # earth radius: 6378137
    return radius * 2 * np.arcsin(np.sqrt(a))

@ngjit
def _distance(x1, x2, y1, y2, metric):

    if metric == EUCLIDEAN:
        d = euclidean_distance(x1, x2, y1, y2)

    elif metric == GREAT_CIRCLE:
        d = great_circle_distance(x1, x2, y1, y2)

    else:
        # metric == MANHATTAN:
        d = manhattan_distance(x1, x2, y1, y2)

    return np.float32(d)

@ngjit
def _calc_direction(x1, x2, y1, y2):
    # Calculate direction from (x1, y1) to a source cell (x2, y2).
    # The output values are based on compass directions,
    # 90 to the east, 180 to the south, 270 to the west, and 360 to the north,
    # with 0 reserved for the source cell itself

    if x1 == x2 and y1 == y2:
        return 0

    x = x2 - x1
    y = y2 - y1
    d = np.arctan2(-y, x) * 57.29578
    if d < 0:
        d = 90.0 - d
    elif d > 90.0:
        d = 360.0 - d + 90.0
    else:
        d = 90.0 - d

    return np.float32(d)

@ngjit
def _process_proximity_line(
    source_line,
    xs,
    ys,
    pan_near_x,
    pan_near_y,
    is_forward,
    line_id,
    width,
    max_distance,
    line_proximity,
    nearest_xs,
    nearest_ys,
    values,
    distance_metric,
):
    """
    Process proximity for a line of pixels in an image.

    Parameters
    ----------
    source_line : numpy.array
        Input data.
    pan_near_x : numpy.array
    pan_near_y : numpy.array
    is_forward : boolean
        Will we loop forward through pixel.
    line_id : np.int64
        Index of the source_line in the image.
    width : np.int64
        Image width.
        It is the number of pixels in the `source_line`.
    max_distance : np.float32, maximum distance considered.
    line_proximity : numpy.array
        1d numpy array of type np.float32, calculated proximity from
        source_line.
    values : numpy.array
        1d numpy array. A list of target pixel values
        to measure the distance from. If this option is not provided
        proximity will be computed from non-zero pixel values.

    Returns
    -------
    self: numpy.array
        1d numpy array of type np.float32. Corresponding proximity of
        source_line.
    """
    start = width - 1
    end = -1
    step = -1
    if is_forward:
        start = 0
        end = width
        step = 1

    n_values = len(values)
    for pixel in prange(start, end, step):
        is_target = False
        # Is the current pixel a target pixel?
        if n_values == 0:
            if source_line[pixel] != 0 and np.isfinite(source_line[pixel]):
                is_target = True
        else:
            for i in prange(n_values):
                if source_line[pixel] == values[i]:
                    is_target = True

        if is_target:
            line_proximity[pixel] = 0.0
            nearest_xs[pixel] = pixel
            nearest_ys[pixel] = line_id
            pan_near_x[pixel] = pixel
            pan_near_y[pixel] = line_id
            continue

        # Are we near(er) to the closest target to the above (below) pixel?
        near_distance_square = max_distance ** 2 * 2.0
        if pan_near_x[pixel] != -1:
            # distance_square
            x1 = xs[pan_near_y[pixel], pan_near_x[pixel]]
            y1 = ys[pan_near_y[pixel], pan_near_x[pixel]]
            x2 = xs[line_id, pixel]
            y2 = ys[line_id, pixel]

            dist = _distance(x1, x2, y1, y2, distance_metric)
            dist_sqr = dist ** 2
            if dist_sqr < near_distance_square:
                near_distance_square = dist_sqr
            else:
                pan_near_x[pixel] = -1
                pan_near_y[pixel] = -1

        # Are we near(er) to the closest target to the left (right) pixel?
        last = pixel - step
        if pixel != start and pan_near_x[last] != -1:
            x1 = xs[pan_near_y[last], pan_near_x[last]]
            y1 = ys[pan_near_y[last], pan_near_x[last]]
            x2 = xs[line_id, pixel]
            y2 = ys[line_id, pixel]

            dist = _distance(x1, x2, y1, y2, distance_metric)
            dist_sqr = dist ** 2
            if dist_sqr < near_distance_square:
                near_distance_square = dist_sqr
                pan_near_x[pixel] = pan_near_x[last]
                pan_near_y[pixel] = pan_near_y[last]

        #  Are we near(er) to the closest target to the
        #  topright (bottom left) pixel?
        tr = pixel + step
        if tr != end and pan_near_x[tr] != -1:
            x1 = xs[pan_near_y[tr], pan_near_x[tr]]
            y1 = ys[pan_near_y[tr], pan_near_x[tr]]
            x2 = xs[line_id, pixel]
            y2 = ys[line_id, pixel]

            dist = _distance(x1, x2, y1, y2, distance_metric)
            dist_sqr = dist ** 2
            if dist_sqr < near_distance_square:
                near_distance_square = dist_sqr
                pan_near_x[pixel] = pan_near_x[tr]
                pan_near_y[pixel] = pan_near_y[tr]

        # Update our proximity value.
        if (
            pan_near_x[pixel] != -1
            and max_distance * max_distance >= near_distance_square
            and (
                line_proximity[pixel] < 0
                or near_distance_square < line_proximity[pixel]
                * line_proximity[pixel]
            )
        ):
            line_proximity[pixel] = sqrt(near_distance_square)
            nearest_xs[pixel] = pan_near_x[pixel]
            nearest_ys[pixel] = pan_near_y[pixel]
    return

def _process(
    raster,
    x,
    y,
    target_values,
    max_distance,
    distance_metric,
    process_mode
):

    raster_dims = raster.dims
    if raster_dims != (y, x):
        raise ValueError(
            "raster.coords should be named as coordinates:"
            "({0}, {1})".format(y, x)
        )

    distance_metric = DISTANCE_METRICS.get(distance_metric, None)
    if distance_metric is None:
        distance_metric = DISTANCE_METRICS["EUCLIDEAN"]

    target_values = np.asarray(target_values)

    # x-y coordinates of each pixel.
    # flatten the coords of input raster and reshape to 2d
    xs = np.tile(raster[x].data, raster.shape[0]).reshape(raster.shape)
    ys = np.repeat(raster[y].data, raster.shape[1]).reshape(raster.shape)

    if max_distance is None:
        max_distance = np.inf

    max_possible_distance = _distance(
        xs[0][0], xs[-1][-1], ys[0][0], ys[-1][-1], distance_metric
    )

    @ngjit
    def _process_numpy(img, x_coords, y_coords):
        height, width = img.shape
        pan_near_x = np.zeros(width, dtype=np.int64)
        pan_near_y = np.zeros(width, dtype=np.int64)

        # output of the function
        output_img = np.full((height, width), np.nan, dtype=np.float32)
        img_distance = np.zeros(shape=(height, width), dtype=np.float32)

        # Loop from top to bottom of the image.
        for i in prange(width):
            pan_near_x[i] = -1
            pan_near_y[i] = -1

        # a single line of the input image img
        scan_line = np.zeros(width, dtype=img.dtype)

        # indexes of nearest pixels of current line scan_line
        nearest_xs = np.zeros(width, dtype=np.int64)
        nearest_ys = np.zeros(width, dtype=np.int64)

        for line in prange(height):
            # Read for target values.
            for i in prange(width):
                scan_line[i] = img[line][i]

            line_proximity = np.zeros(width, dtype=np.float32)

            for i in prange(width):
                line_proximity[i] = -1.0
                nearest_xs[i] = -1
                nearest_ys[i] = -1

            # left to right
            _process_proximity_line(
                scan_line, x_coords, y_coords,
                pan_near_x, pan_near_y, True,
                line, width, max_distance,
                line_proximity, nearest_xs, nearest_ys,
                target_values, distance_metric,
            )
            for i in prange(width):
                if nearest_xs[i] != -1 and line_proximity[i] >= 0:
                    if process_mode == ALLOCATION:
                        output_img[line][i] = img[nearest_ys[i], nearest_xs[i]]
                    elif process_mode == DIRECTION:
                        output_img[line][i] = _calc_direction(
                            x_coords[line, i],
                            x_coords[nearest_ys[i], nearest_xs[i]],
                            y_coords[line, i],
                            y_coords[nearest_ys[i], nearest_xs[i]],
                        )

            # right to left
            for i in prange(width):
                nearest_xs[i] = -1
                nearest_ys[i] = -1

            _process_proximity_line(
                scan_line, x_coords, y_coords,
                pan_near_x, pan_near_y, False,
                line, width, max_distance,
                line_proximity, nearest_xs, nearest_ys,
                target_values, distance_metric,
            )

            for i in prange(width):
                img_distance[line][i] = line_proximity[i]

                if nearest_xs[i] != -1 and line_proximity[i] >= 0:
                    if process_mode == ALLOCATION:
                        output_img[line][i] = img[nearest_ys[i], nearest_xs[i]]

                    elif process_mode == DIRECTION:
                        output_img[line][i] = _calc_direction(
                            x_coords[line, i],
                            x_coords[nearest_ys[i], nearest_xs[i]],
                            y_coords[line, i],
                            y_coords[nearest_ys[i], nearest_xs[i]],
                        )

        # Loop from bottom to top of the image.
        for i in prange(width):
            pan_near_x[i] = -1
            pan_near_y[i] = -1

        for line in prange(height - 1, -1, -1):
            # Read first pass proximity.
            for i in prange(width):
                line_proximity[i] = img_distance[line][i]

            # Read pixel target_values.
            for i in prange(width):
                scan_line[i] = img[line][i]

            # Right to left
            for i in prange(width):
                nearest_xs[i] = -1
                nearest_ys[i] = -1

            _process_proximity_line(
                scan_line, x_coords, y_coords,
                pan_near_x, pan_near_y, False,
                line, width, max_distance,
                line_proximity, nearest_xs, nearest_ys,
                target_values, distance_metric,
            )

            for i in prange(width):
                if nearest_xs[i] != -1 and line_proximity[i] >= 0:
                    if process_mode == ALLOCATION:
                        output_img[line][i] = img[nearest_ys[i], nearest_xs[i]]

                    elif process_mode == DIRECTION:
                        output_img[line][i] = _calc_direction(
                            x_coords[line, i],
                            x_coords[nearest_ys[i], nearest_xs[i]],
                            y_coords[line, i],
                            y_coords[nearest_ys[i], nearest_xs[i]],
                        )

            # Left to right
            for i in prange(width):
                nearest_xs[i] = -1
                nearest_ys[i] = -1

            _process_proximity_line(
                scan_line, x_coords, y_coords,
                pan_near_x, pan_near_y, True,
                line, width, max_distance,
                line_proximity, nearest_xs, nearest_ys,
                target_values, distance_metric,
            )

            # final post processing of distances
            for i in prange(width):
                if line_proximity[i] < 0:
                    line_proximity[i] = np.nan
                else:
                    if nearest_xs[i] != -1 and line_proximity[i] >= 0:
                        if process_mode == ALLOCATION:
                            output_img[line][i] = img[
                                nearest_ys[i], nearest_xs[i]]

                        elif process_mode == DIRECTION:
                            output_img[line][i] = _calc_direction(
                                x_coords[line, i],
                                x_coords[nearest_ys[i], nearest_xs[i]],
                                y_coords[line, i],
                                y_coords[nearest_ys[i], nearest_xs[i]],
                            )

            for i in prange(width):
                img_distance[line][i] = line_proximity[i]

        if process_mode == PROXIMITY:
            return img_distance
        else:
            return output_img

    def _process_dask(raster, xs, ys):

        if max_distance >= max_possible_distance:
            # consider all targets in the whole raster
            # the data array is computed at once,
            # make sure your data fit your memory
            height, width = raster.shape
            raster.data = raster.data.rechunk({0: height, 1: width})
            xs = xs.rechunk({0: height, 1: width})
            ys = ys.rechunk({0: height, 1: width})
            pad_y = pad_x = 0
        else:
            cellsize_x, cellsize_y = get_dataarray_resolution(raster)
            # calculate padding for each chunk
            pad_y = int(max_distance / cellsize_y + 0.5)
            pad_x = int(max_distance / cellsize_x + 0.5)

        out = da.map_overlap(
            _process_numpy,
            raster.data, xs, ys,
            depth=(pad_y, pad_x),
            boundary=np.nan,
            meta=np.array(()),
        )
        return out

    if isinstance(raster.data, np.ndarray):
        # numpy case
        result = _process_numpy(raster.data, xs, ys)

    elif isinstance(raster.data, da.Array):
        # dask + numpy case
        xs = da.from_array(xs, chunks=(raster.chunks))
        ys = da.from_array(ys, chunks=(raster.chunks))
        result = _process_dask(raster, xs, ys)

    return result

# ported from
# https://github.com/OSGeo/gdal/blob/master/gdal/alg/gdalproximity.cpp
def proximity(
    raster: xr.DataArray,
    x: str = "x",
    y: str = "y",
    target_values: list = [],
    max_distance: float = np.inf,
    distance_metric: str = "EUCLIDEAN",
) -> xr.DataArray:
    """
    Computes the proximity of all pixels in the image to a set of pixels
    in the source image based on a distance metric.

    This function attempts to compute the proximity of all pixels in the
    image to a set of pixels in the source image. The following options
    are used to define the behavior of the function. By default all
    non-zero pixels in `raster.values` will be considered the "target",
    and all proximities will be computed in pixels. Note that target
    pixels are set to the value corresponding to a distance of zero.

    Proximity support NumPy backed, and Dask with NumPy backed
    xarray DataArray. The return values of proximity are of the same type as
    the input type.
    If input raster is a NumPy-backed DataArray, the result is NumPy-backed.
    If input raster is a Dask-backed DataArray, the result is Dask-backed.

    The implementation for NumPy-backed is ported from GDAL, which uses
    a dynamic programming approach to identify nearest target of a pixel from
    its surrounding neighborhood in a 3x3 window.
    The implementation for Dask-backed uses `dask.map_overlap` to compute
    proximity chunk by chunk by expanding the chunk's borders to cover
    the `max_distance`.

    Parameters
    ----------
    raster : xr.DataArray
        2D array image with `raster.shape` = (height, width).

    x : str, default='x'
        Name of x-coordinates.

    y : str, default='y'
        Name of y-coordinates.

    target_values: list
        Target pixel values to measure the distance from. If this option
        is not provided, proximity will be computed from non-zero pixel
        values.

    max_distance: float, default=np.inf
        The maximum distance to search. Proximity distances greater than
        this value will be set to NaN.
        Should be given in the same distance unit as input.
        For example, if input raster is in lat-lon and distances between points
        within the raster is calculated using Euclidean distance metric,
        `max_distance` should also be provided in lat-lon unit.
        If using Great Circle distance metric, and thus all distances is in km,
        `max_distance` should also be provided in kilometer unit.

        When scaling with Dask, whether the function scales well depends on
        the `max_distance` value. If `max_distance` is infinite by default,
        this function only works on a single machine.
        It should scale well, however, if `max_distance` is relatively small
        compared to the maximum possible distance in two arbitrary points
        in the input raster. Note that if `max_distance` is equal or larger
        than the max possible distance between 2 arbitrary points in the input
        raster, the input data array will be rechunked.

    distance_metric: str, default='EUCLIDEAN'
        The metric for calculating distance between 2 points.
        Valid distance metrics are:
        'EUCLIDEAN', 'GREAT_CIRCLE', and 'MANHATTAN'.

    Returns
    -------
    proximity_agg: xr.DataArray of same type as `raster`
        2D array of proximity values.
        All other input attributes are preserved.

    References
    ----------
        - OSGeo: https://github.com/OSGeo/gdal/blob/master/gdal/alg/gdalproximity.cpp # noqa

    Examples
    --------
    .. plot::
       :include-source:

        import numpy as np
        import xarray as xr
        from xrspatial import proximity

        data = np.array([
            [0., 0., 0., 0., 0.],
            [0., 0., 0., 1., 0.],
            [0., 0., 0., 0., 0.],
            [0., 0., 0., 0., 0.],
            [0., 0., 0., 0., 0.]
        ])

        n, m = data.shape
        raster = xr.DataArray(data, dims=['y', 'x'], name='raster')
        raster['y'] = np.arange(n)[::-1]
        raster['x'] = np.arange(m)

        proximity_agg = proximity(raster)

    .. sourcecode:: python

        >>> raster
        <xarray.DataArray 'raster' (y: 5, x: 5)>
        array([[0., 0., 0., 0., 0.],
               [0., 0., 0., 1., 0.],
               [0., 0., 0., 0., 0.],
               [0., 0., 0., 0., 0.],
               [1., 0., 0., 0., 0.]])
        Coordinates:
          * y        (y) int64 4 3 2 1 0
          * x        (x) int64 0 1 2 3 4

        >>> proximity_agg
        <xarray.DataArray (y: 5, x: 5)>
        array([[3.1622777, 2.236068 , 1.4142135, 1.       , 1.4142135],
               [3.       , 2.       , 1.       , 0.       , 1.       ],
               [3.1622777, 2.236068 , 1.4142135, 1.       , 1.4142135],
               [3.6055512, 2.828427 , 2.236068 , 2.       , 2.236068 ],
               [4.2426405, 3.6055512, 3.1622777, 3.       , 3.1622777]],
              dtype=float32)
        Coordinates:
          * y        (y) int64 4 3 2 1 0
          * x        (x) int64 0 1 2 3 4
    """

    proximity_img = _process(
        raster,
        x=x,
        y=y,
        target_values=target_values,
        max_distance=max_distance,
        distance_metric=distance_metric,
        process_mode=PROXIMITY,
    )

    result = xr.DataArray(
        proximity_img,
        coords=raster.coords,
        dims=raster.dims,
        attrs=raster.attrs
    )

    return result

def allocation(
    raster: xr.DataArray,
    x: str = "x",
    y: str = "y",
    target_values: list = [],
    max_distance: float = np.inf,
    distance_metric: str = "EUCLIDEAN",
):
    """
    Calculates, for all pixels in the input raster, the nearest source
    based on a set of target values and a distance metric.

    This function attempts to produce the value of nearest feature of all
    pixels in the image to a set of pixels in the source image. The
    following options are used to define the behavior of the function.
    By default all non-zero pixels in `raster.values` will be considered
    as"target", and all allocation will be computed in pixels.

    Allocation supports NumPy backed, and Dask with NumPy backed
    xarray DataArray. The return values of `allocation` are of the same type as
    the input type.
    If input raster is a NumPy-backed DataArray, the result is NumPy-backed.
    If input raster is a Dask-backed DataArray, the result is Dask-backed.

    `allocation` uses the same approach as `proximity`, which is ported
    from GDAL. A dynamic programming approach is used for identifying nearest
    target of a pixel from its surrounding neighborhood in a 3x3 window.
    The implementation for Dask-backed uses `dask.map_overlap` to compute
    `allocation` chunk by chunk by expanding the chunk's borders to cover
    the `max_distance`.

    Parameters
    ----------
    raster : xr.DataArray
        2D array of target data.

    x : str, default='x'
        Name of x-coordinates.

    y : str, default='y'
        Name of y-coordinates.

    target_values : list
        Target pixel values to measure the distance from. If this option
        is not provided, allocation will be computed from non-zero pixel
        values.

    max_distance: float, default=np.inf
        The maximum distance to search. Proximity distances greater than
        this value will be set to NaN.
        Should be given in the same distance unit as input.
        For example, if input raster is in lat-lon and distances between points
        within the raster is calculated using Euclidean distance metric,
        `max_distance` should also be provided in lat-lon unit.
        If using Great Circle distance metric, and thus all distances is in km,
        `max_distance` should also be provided in kilometer unit.

        When scaling with Dask, whether the function scales well depends on
        the `max_distance` value. If `max_distance` is infinite by default,
        this function only works on a single machine.
        It should scale well, however, if `max_distance` is relatively small
        compared to the maximum possible distance in two arbitrary points
        in the input raster. Note that if `max_distance` is equal or larger
        than the max possible distance between 2 arbitrary points in the input
        raster, the input data array will be rechunked.

    distance_metric : str, default='EUCLIDEAN'
        The metric for calculating distance between 2 points. Valid
        distance metrics are: 'EUCLIDEAN', 'GREAT_CIRCLE', and 'MANHATTAN'.

    Returns
    -------
    allocation_agg: xr.DataArray of same type as `raster`
        2D array of allocation values.
        All other input attributes are preserved.

    References
    ----------
        - OSGeo: https://github.com/OSGeo/gdal/blob/master/gdal/alg/gdalproximity.cpp # noqa

    Examples
    --------
    .. plot::
       :include-source:

        import numpy as np
        import xarray as xr
        from xrspatial import allocation

        data = np.array([
            [0., 0., 0., 0., 0.],
            [0., 1., 0., 2., 0.],
            [0., 0., 3., 0., 0.],
            [0., 0., 0., 0., 0.],
            [0., 0., 0., 0., 0.]
        ])
        n, m = data.shape
        raster = xr.DataArray(data, dims=['y', 'x'], name='raster')
        raster['y'] = np.arange(n)[::-1]
        raster['x'] = np.arange(m)

        allocation_agg = allocation(raster)

    .. sourcecode:: python

        >>> raster
        <xarray.DataArray 'raster' (y: 5, x: 5)>
        array([[0., 0., 0., 0., 0.],
               [0., 1., 0., 2., 0.],
               [0., 0., 3., 0., 0.],
               [0., 0., 0., 0., 0.],
               [1., 0., 0., 0., 0.]])
        Coordinates:
          * y        (y) int64 4 3 2 1 0
          * x        (x) int64 0 1 2 3 4

        >>> allocation_agg
        <xarray.DataArray (y: 5, x: 5)>
        array([[1., 1., 2., 2., 2.],
               [1., 1., 1., 2., 2.],
               [1., 1., 3., 2., 2.],
               [1., 3., 3., 3., 2.],
               [3., 3., 3., 3., 3.]])
        Coordinates:
          * y        (y) int64 4 3 2 1 0
          * x        (x) int64 0 1 2 3 4

    """
    allocation_img = _process(
        raster,
        x=x,
        y=y,
        target_values=target_values,
        max_distance=max_distance,
        distance_metric=distance_metric,
        process_mode=ALLOCATION,
    )

    # convert to have same type as of input @raster
    result = xr.DataArray(
        (allocation_img).astype(raster.dtype),
        coords=raster.coords,
        dims=raster.dims,
        attrs=raster.attrs,
    )
    return result

def direction(
    raster: xr.DataArray,
    x: str = "x",
    y: str = "y",
    target_values: list = [],
    max_distance: float = np.inf,
    distance_metric: str = "EUCLIDEAN",
):
    """
    Calculates, for all cells in the array, the downward slope direction
    Calculates, for all pixels in the input raster, the direction to
    nearest source based on a set of target values and a distance metric.

    This function attempts to calculate for each cell, the the direction,
    in degrees, to the nearest source. The output values are based on
    compass directions, where 90 is for the east, 180 for the south,
    270 for the west, 360 for the north, and 0 for the source cell
    itself. The following options are used to define the behavior of
    the function. By default all non-zero pixels in `raster.values`
    will be considered as "target", and all direction will be computed
    in pixels.

    Direction support NumPy backed, and Dask with NumPy backed
    xarray DataArray. The return values of `direction` are of the same type as
    the input type.
    If input raster is a NumPy-backed DataArray, the result is NumPy-backed.
    If input raster is a Dask-backed DataArray, the result is Dask-backed.

    Similar to `proximity`, the implementation for NumPy-backed is ported
    from GDAL, which uses a dynamic programming approach to identify
    nearest target of a pixel from its surrounding neighborhood in a 3x3 window
    The implementation for Dask-backed uses `dask.map_overlap` to compute
    proximity direction chunk by chunk by expanding the chunk's borders
    to cover the `max_distance`.

    Parameters
    ----------
    raster : xr.DataArray
        2D array image with `raster.shape` = (height, width).

    x : str, default='x'
        Name of x-coordinates.

    y : str, default='y'
        Name of y-coordinates.

    target_values: list
        Target pixel values to measure the distance from. If this
        option is not provided, proximity will be computed from
        non-zero pixel values.

    max_distance: float, default=np.inf
        The maximum distance to search. Proximity distances greater than
        this value will be set to NaN.
        Should be given in the same distance unit as input.
        For example, if input raster is in lat-lon and distances between points
        within the raster is calculated using Euclidean distance metric,
        `max_distance` should also be provided in lat-lon unit.
        If using Great Circle distance metric, and thus all distances is in km,
        `max_distance` should also be provided in kilometer unit.

        When scaling with Dask, whether the function scales well depends on
        the `max_distance` value. If `max_distance` is infinite by default,
        this function only works on a single machine.
        It should scale well, however, if `max_distance` is relatively small
        compared to the maximum possible distance in two arbitrary points
        in the input raster. Note that if `max_distance` is equal or larger
        than the max possible distance between 2 arbitrary points in the input
        raster, the input data array will be rechunked.

    distance_metric: str, default='EUCLIDEAN'
        The metric for calculating distance between 2 points.
        Valid distance_metrics are:
        'EUCLIDEAN', 'GREAT_CIRCLE', and 'MANHATTAN'.

    Returns
    -------
    direction_agg: xr.DataArray of same type as `raster`
        2D array of direction values.
        All other input attributes are preserved.

    References
    ----------
        - OSGeo: https://github.com/OSGeo/gdal/blob/master/gdal/alg/gdalproximity.cpp # noqa

    Examples
    --------
    .. plot::
       :include-source:

        import numpy as np
        import xarray as xr
        from xrspatial import direction

        data = np.array([
            [0., 0., 0., 0., 0.],
            [0., 0., 0., 0., 0.],
            [0., 0., 1., 0., 0.],
            [0., 0., 0., 0., 0.],
            [1., 0., 0., 0., 0.]
        ])
        n, m = data.shape
        raster = xr.DataArray(data, dims=['y', 'x'], name='raster')
        raster['y'] = np.arange(n)[::-1]
        raster['x'] = np.arange(m)

        direction_agg = direction(raster)

    ... sourcecode:: python

        >>> raster
        <xarray.DataArray 'raster' (y: 5, x: 5)>
        array([[0., 0., 0., 0., 0.],
               [0., 0., 0., 0., 0.],
               [0., 0., 1., 0., 0.],
               [0., 0., 0., 0., 0.],
               [1., 0., 0., 0., 0.]])
        Coordinates:
          * y        (y) int64 4 3 2 1 0
          * x        (x) int64 0 1 2 3 4

        >>> direction_agg
        <xarray.DataArray (y: 5, x: 5)>
        array([[ 45.      ,  26.56505 , 360.      , 333.43494 , 315.      ],
               [ 63.434948,  45.      , 360.      , 315.      , 296.56506 ],
               [ 90.      ,  90.      ,   0.      , 270.      , 270.      ],
               [360.      , 135.      , 180.      , 225.      , 243.43495 ],
               [  0.      , 270.      , 180.      , 206.56505 , 225.      ]],
              dtype=float32)
        Coordinates:
          * y        (y) int64 4 3 2 1 0
          * x        (x) int64 0 1 2 3 4
    """

    direction_img = _process(
        raster,
        x=x,
        y=y,
        target_values=target_values,
        max_distance=max_distance,
        distance_metric=distance_metric,
        process_mode=DIRECTION,
    )

    result = xr.DataArray(
        direction_img,
        coords=raster.coords,
        dims=raster.dims,
        attrs=raster.attrs
    )
    return result


# -*- coding: utf-8 -*-
"""
Created on Wed Feb 24 07:01:08 2021

@author: Mahmu
"""

# HYPOTHESIS: h(x) = ax^2 + b

import numpy as np

def problem4():

    RUNS = 1000000
    a_total = 0
    b_total = 0
    N = 2          # size of data set

    for _ in range(RUNS):
        # two random points
        x_rnd = np.random.uniform(-1, 1, N)
        y_rnd = np.sin(np.pi * x_rnd)

        # linear regression for model y = ax^2
        X = np.array([np.ones(N), x_rnd * x_rnd]).T
        w = np.dot(np.dot(np.linalg.inv(np.dot(X.T, X)), X.T), y_rnd)
        b, a = w

        a_total += a
        b_total += b

    a_avg = a_total / RUNS
    b_avg = b_total / RUNS
    return b_avg, a_avg

print("h(x) = b + ax^2")
print("solution problem 7: (b_avg, a_avg) = ", problem4())

#-------------------------------------------------------------------------

def problem5():
    N_test = 1000
    x_test = np.random.uniform(-1,1,N_test)

    y_f = np.sin(np.pi * x_test)
    b_avg, a_avg = problem4()
    y_g_bar = b_avg + a_avg * (x_test * x_test)

    bias = sum((y_f - y_g_bar)**2) / N_test
    return bias

print("\nSolution to problem 7: bias = ", problem5())

#--------------------------------------------------------------------------

def problem6():
    b_avg, a_avg = problem4()
    expectation_over_X = 0

    RUNS_D = 100
    RUNS_X = 1000
    # variance: Compare each g to g_bar

    for i in range(RUNS_X):
        N = 2
        x_test = np.random.uniform(-1,1)
        expectation_over_D = 0

        for _ in range(RUNS_D):
            # two random points as data set D
            x_rnd = np.random.uniform(-1, 1, N)
            y_rnd = np.sin(np.pi * x_rnd)

            # linear regression for model y = ax^2
            # get a particular g^(D)
            X = np.array([np.ones(N), x_rnd * x_rnd]).T
            w = np.dot(np.dot(np.linalg.inv(np.dot(X.T, X)), X.T), y_rnd)
            b, a  = w

            # calculate difference on test point
            y_g = b + a * x_test**2
            y_g_bar = b_avg + a_avg * x_test**2
            expectation_over_D += (y_g - y_g_bar)**2 / RUNS_D

        expectation_over_X += expectation_over_D / RUNS_X

    variance = expectation_over_X
    return variance

print("\nSolution to problem 7, variance = ", problem6())


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Dec  2 14:29:37 2020

Copyright 2020 by Hadrien Montanelli.
"""
# %% Imports.

# Standard library imports:
from math import pi
import numpy as np

# Chebpy imports:
from chebpy.trig import trigpts, quadwts

# %% Integrate f(x) = 1 + sin(4*pi*x) on [-1,1].

# Function:
f = lambda x: 1 + np.sin(4*pi*x)

# Grid and weights:
n = 12
x = trigpts(n)
w = quadwts(n)

# Compute integral with quadrature:
I = w @ f(x)

# Compute error:
Iex = 2
error = np.abs(I - Iex)
print(f'Error: {error:.2e}')

# %% Integrate f(x) = 1 + sin(4*x) on [-pi,pi].

# Function:
f = lambda x: 1 + np.sin(4*x)

# Grid and weights:
n = 12
x = trigpts(n, [-pi, pi])
w = quadwts(n, [-pi, pi])

# Compute integral with quadrature:
I = w @ f(x)

# Compute error:
Iex = 2*pi
error = np.abs(I - Iex)
print(f'Error: {error:.2e}')


import numpy as np

def linealSolverBackwards(A, b):
    """Solves a system of equations given a upper triangular matrix.
    Arguments:
        A: Triangular Matrix
        b: System's solutions
    Raises:
        RuntimeError if A is not upper triangular
    Returns:
        A numpy array with the system's X vector.

        :param A: Lower triangular matrix.
        :param b: System's solutions
        :return: The vector solution for the equation system
    """
    if np.allclose(A, np.triu(A)):
        n = len(b)
        x = np.zeros((n, 1))

        for k in reversed(range(0, n)):

            tempSum = []
            for number in range(k + 1, n):
                tempSum.append(-1 * A[k][number] * x[number])

            tempSum = sum(tempSum)
            x[k] = (b[k] + tempSum) / A[k][k]

        return x

    else:
        raise RuntimeError("Matrix A is not upper triangular.")

def QRdec(A):
    '''
    :param A: matriz a descomponer en Q, R. Puede ser rectangular.
    '''
    numberRowsA = A.shape[0]
    numberColsA = A.shape[1]

    #Q es cuadrada
    Q = np.zeros((numberRowsA, numberRowsA))
    #R tiene la msima forma que la matriz original
    R = np.zeros(A.shape)

    # Aplico Gram-Schmidt como lo hace en el video
    # para ir llenando a Q y voy completando R al
    # mismo tiempo

    for j in range(numberColsA):
        Q[:, j] = A[:, j]
        for col in range(j):
            dotProd = np.dot(A[:, j], Q[:, col])
            R[col][j] = dotProd
            Q[:, j] = Q[:, j] - dotProd * Q[:, col]

        colNorm = np.linalg.norm(Q[:, j])
        Q[:, j] = Q[:, j] / colNorm
        R[j, j] = colNorm

    # LLeno las columnas de Q que faltan con numeros aleatorios
    # y las ortonormalizo respecto de las otras columnas

    for j in range(j+1, numberRowsA):
        Q[:, j] = np.random.rand(numberRowsA)
        for col in range(j):
            dotProd = np.dot(Q[:, j], Q[:, col])
            Q[:, j] = Q[:, j] - dotProd * Q[:, col]
        colNorm = np.linalg.norm(Q[:, j])
        Q[:, j] = Q[:, j] / colNorm

    return Q, R

def leastsqQR(A, b):
    '''
    :param A: matriz de coeficientes del sistema de ecuaciones
    :param b: vector solución (IMPORTANTE: b.shape[1] = 1, es vector columna)
    '''

    Q, R = QRdec(A)
    R_1 = R[:A.shape[1], :]
    Q_T = np.transpose(Q)
    Q1_T = Q_T[:A.shape[1], :]
    Q2_T = Q_T[A.shape[1]:, :]

    errorCuad = np.linalg.norm(np.dot(Q2_T, b))**2

    # Para minimizar la diferencia entre A*x
    # y b se llega al sistema R_1 * x = (Q_1)^T * b
    # error residual (error cuadrático): (norma(Q_2T*b))^2

    # VV tener en cuenta que err siempre va printearse como > 0
    # por redondeo, aunque los valores de entrada sean exactos
    # En ese caso no va a ser error "posta" (o sea residuo de
    # cuadrados mínimos)

    print("Error cuadrático: {}".format(errorCuad))
    x = linealSolverBackwards(R_1, np.dot(Q1_T, b))

    return x

print(leastsqQR(np.array([[1,2],[3,4],[5,6]]), np.array([[5],[11], [17]])))


# Load modules
from __future__ import print_function
import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
import statsmodels.tsa.api as smtsa

# Function to plot signal, ACF and PACF
def plotds(xt, nlag=30, fig_size=(12, 10)):
    if not isinstance(xt, pd.Series):
         xt = pd.Series(xt)
    plt.figure(figsize=fig_size)
    layout = (2, 2)

    # Assign axes
    ax_xt = plt.subplot2grid(layout, (0, 0), colspan=2)
    ax_acf= plt.subplot2grid(layout, (1, 0))
    ax_pacf = plt.subplot2grid(layout, (1, 1))

    # Plot graphs
    xt.plot(ax=ax_xt)
    ax_xt.set_title('Time Series')
    plot_acf(xt, lags=50, ax=ax_acf)
    plot_pacf(xt, lags=50, ax=ax_pacf)
    plt.tight_layout()
    return None

# Number of samples
n = 600

# Generate MA(1) dataset
ar = np.r_[1, -0]
ma = np.r_[1, 0.7]
ma1_data = smtsa.arma_generate_sample(ar=ar, ma=ma, nsample=n)
plotds(ma1_data)

# Generate MA(2) dataset
ar = np.r_[1, -0]
ma = np.r_[1, 0.6, 0.7]
ma2_data = smtsa.arma_generate_sample(ar=ar, ma=ma, nsample=n)
plotds(ma2_data)

# Generate MA(3) dataset
ar = np.r_[1, -0]
ma = np.r_[1, 0.6, 0.7, 0.5]
ma3_data = smtsa.arma_generate_sample(ar=ar, ma=ma, nsample=n)
plotds(ma3_data)

# Build MA(1) model
ma1 = smtsa.ARMA(ma1_data.tolist(), order=(0, 1)).fit(
    maxlag=30, method='mle', trend='nc')
ma1.summary()

# Build MA(3) model
ma3 = smtsa.ARMA(ma3_data.tolist(), order=(0, 3)).fit(
    maxlag=30, method='mle', trend='nc')
ma3.summary()

import numpy,import statsmodels,from statsmodels


import numpy as np

def gaussian(x, mu, sig, amplitude):
	return amplitude * np.exp(-np.power(x - mu, 2.) / (2 * np.power(sig, 2.)))

def exponential(x, decay_length, amplitude, shift):
	return amplitude * np.exp(-1 * (x-shift) / decay_length)

def exponentially_decaying_gaussian(x, mu, sig, amplitude_gaussian, decay_length, amplitude_exponential, shift):
	return gaussian(x + shift, mu, sig, amplitude_gaussian) * exponential(x, decay_length, amplitude_exponential)

def exponentially_decaying_cosine(x, omega, decay_time, amplitude, x_shift):
	return amplitude * np.cos(x * 2 * np.pi / omega) * exponential(x, decay_time, 1, x_shift)

def exponentially_decaying_sine(x, omega, decay_time, amplitude, phase_shift, x_shift):
	return amplitude * np.cos(x * 2 * np.pi / omega + phase_shift) * exponential(x, decay_time, 1, x_shift)

def scale_values_to_unity(y_values, request_max=False):
	reference_value = y_values[0]
	if request_max:
		reference_value = max(y_values)
	normalization = 1 / reference_value
	return normalization

def linear_function(x, slope, interceipt):
	return x * slope + interceipt


"""
quiz 1 checking
"""
import numpy as np
import nashpy as nash

# definition: A pure-strategy Nash equilibrium is an action profile with the property that no single player i can
# obtain a higher payoff by choosing an action different from ai, given every other player j adheres to aj.

# question 1: AA is a pure strategy Nash equilibrium of this game, true or false?
player_1 = np.array([[2, -2], [2, -2]])  # the row player
player_2 = np.array([[2, 2], [-2, -2]])  # the column player
game_1 = nash.Game(player_1, player_2)
print(game_1)

# find the Nash equilibrium with support enumeration
equilibria = game_1.support_enumeration()
for eq in equilibria:
    print(f"the equilibria for q1 are: {eq}")
# # mk: 4x equilibria are found, so the answer is: True

# question 2: AB is a pure strategy Nash equilibrium of this game, true or false?
# player_1 = np.array([[2, -2], [2, -2]])  # the row player
# player_2 = np.array([[2, 2], [-2, -2]])  # the column player
# game_2 = nash.Game(player_1, player_2)
# print(game_2)

# find the Nash equilibrium with support enumeration
# equilibria = game_2.support_enumeration()
# for eq in equilibria:
#     print(f"the equilibria for q2 are: {eq}")
# mk: 4x equilibria are found, so the answer is: True

# question 3: BA is a pure strategy Nash Equilibrium of this game, true or false?
# player_1 = np.array([[2, -2], [2, -2]])  # the row player
# player_2 = np.array([[2, 2], [-2, -2]])  # the column player
# game_3 = nash.Game(player_1, player_2)
# print(game_3)

# find the Nash equilibrium with support enumeration
# equilibria = game_3.support_enumeration()
# for eq in equilibria:
#     print(f"the equilibria for q3 are: {eq}")
# mk: 4x equilibria are found, so the answer is: True

# question 4: BB is a pure strategy Nash equilibrium of this game, true or false?
# player_1 = np.array([[2, -2], [2, -2]])  # the row player
# player_2 = np.array([[2, 2], [-2, -2]])  # the column player
# game_4 = nash.Game(player_1, player_2)
# print(game_4)

# find the Nash equilibrium with support enumeration
# equilibria = game_4.support_enumeration()
# for eq in equilibria:
#     print(f"the equilibria for q4 are: {eq}")
# mk: 4x equilibria are found, so the answer is: True

# question 5: this game is dominance solvable, true or false?
# player_1 = np.array([[2, -2], [2, -2]])  # the row player
# player_2 = np.array([[2, 2], [-2, -2]])  # the column player
# game_5 = nash.Game(player_1, player_2)
# print(game_5)

# find the Nash equilibrium with support enumeration
# equilibria = game_5.support_enumeration()
# for eq in equilibria:
#     print(f"the equilibria for q5 are: {eq}")
# mk: The answer is: True. Both players would never play B as it is dominated. Therefore this game is dominance
#  solvable

# Question 6: This game has at least one inadmissible Nash equilibrium
# check definition of inadmissible Nash equilibrium

# player_1 = np.array([[2, -2], [2, -2]])  # the row player
# player_2 = np.array([[2, 2], [-2, -2]])  # the column player
# game_6 = nash.Game(player_1, player_2)
# print(game_6)
#
# # find the Nash equilibrium with support enumeration
# equilibria = game_6.support_enumeration()
# for eq in equilibria:
#     print(f"the equilibria for q6 are: {eq}")
# mk:answer: True. an inadmissible strategy is one that a player would never play because it is always inferior.
#  There is at least one of those in this game

# question 7: in the unique nash equilibrium of this game, player 1 plays...
# player_1 = np.array([[1, 1, 4, 2], [1, 1, 3, 4], [0, 0, 2, 2], [2, 3, 6, 0]])  # the row player
# player_2 = np.array([[0, 2, 1, 1], [2, 4, 0, 2], [3, 1, 0, 0], [4, 2, 1, 1]])  # the column player
# game_7 = nash.Game(player_1, player_2)
# print(game_7)
#
# # find the Nash equilibrium with support enumeration
# equilibria = game_7.support_enumeration()
# for eq in equilibria:
#     print(f"the equilibria for q7 are: {eq}")
# mk: answer: player 1 plays strategy D

# question 8: in the unique nash equilibrium of this game, player 2 plays...
# player_1 = np.array([[1, 1, 4, 2], [1, 1, 3, 4], [0, 0, 2, 2], [2, 3, 6, 0]])  # the row player
# player_2 = np.array([[0, 2, 1, 1], [2, 4, 0, 2], [3, 1, 0, 0], [4, 2, 1, 1]])  # the column player
# game_8 = nash.Game(player_1, player_2)
# print(game_8)
#
# # find the Nash equilibrium with support enumeration
# equilibria = game_8.support_enumeration()
# for eq in equilibria:
#     print(f"the equilibria for q8 are: {eq}")
# mk: answer: player 2 plays strategy A

# question 9: this game is dominance solvable, true or false?
# player_1 = np.array([[1, 1, 4, 2], [1, 1, 3, 4], [0, 0, 2, 2], [2, 3, 6, 0]])  # the row player
# player_2 = np.array([[0, 2, 1, 1], [2, 4, 0, 2], [3, 1, 0, 0], [4, 2, 1, 1]])  # the column player
# game_9 = nash.Game(player_1, player_2)
# print(game_9)
#
# # find the Nash equilibrium with support enumeration
# equilibria = game_9.support_enumeration()
# for eq in equilibria:
#     print(f"the equilibria for q9 are: {eq}")
# mk: answer: True

# question 10: This game has at least one inadmissible Nash equilibrium, true or false?
# player_1 = np.array([[1, 1, 4, 2], [1, 1, 3, 4], [0, 0, 2, 2], [2, 3, 6, 0]])  # the row player
# player_2 = np.array([[0, 2, 1, 1], [2, 4, 0, 2], [3, 1, 0, 0], [4, 2, 1, 1]])  # the column player
# game_10 = nash.Game(player_1, player_2)
# print(game_10)
#
# # find the Nash equilibrium with support enumeration
# equilibria = game_10.support_enumeration()
# for eq in equilibria:
#     print(f"the equilibria for q10 are: {eq}")
# mk: answer: False. The unique Nash equilibrium is admissible

# # question 11: AA is a pure strategy Nash equilibrium of this game, True or false?
# player_1 = np.array([[3, 4], [2, 4]])  # the row player
# player_2 = np.array([[10, 7], [0, 2]])  # the column player
# game_11 = nash.Game(player_1, player_2)
# print(game_11)
#
# # find the Nash equilibrium with support enumeration
# equilibria= game_11.support_enumeration()
# for eq in equilibria:
#     print(f"the equilibria for q11 are: {eq}")
# mk: answer: true, AA is one of 2 Nash equilibria in this game

# # question 12: BB is a pure Nash equilibrium of this game.
# player_1 = np.array([[3, 4], [2, 4]])  # the row player
# player_2 = np.array([[10, 7], [0, 2]])  # the column player
# game_12 = nash.Game(player_1, player_2)
# print(game_12)
#
# # find the Nash equilibrium with support enumeration
# equilibria = game_12.support_enumeration()
# for eq in equilibria:
#     print(f"the equilibria for q12 are: {eq}")
# # answer: true, BB is one of 2 Nash equilibria in this game

# question 13: AB is a pure strategy nash equilibrium of this game
# player_1 = np.array([[3, 4], [2, 4]])  # the row player
# player_2 = np.array([[10, 7], [0, 2]])  # the column player
# game_13 = nash.Game(player_1, player_2)
# print(game_13)
#
# # find the Nash equilibrium with support enumeration
# equilibria= game_13.support_enumeration()
# for eq in equilibria:
#     print(f"the equilibria for q13 are: {eq}")
# # answer: false, AB is NOT one of 2 Nash equilibria in this game

# # question 14: BA is a pure strategy Nash Equilibrium of this game
# player_1 = np.array([[3, 4], [2, 4]])  # the row player
# player_2 = np.array([[10, 7], [0, 2]])  # the column player
# game_14 = nash.Game(player_1, player_2)
# print(game_14)
#
# # find the Nash equilibrium with support enumeration
# equilibria= game_14.support_enumeration()
# for eq in equilibria:
#     print(f"the equilibria for q14 are: {eq}")
# # answer: False, BA is NOT one of 2 Nash equilibria in this game

# # question 15: this game is dominance solvable, True or false?
# player_1 = np.array([[3, 4], [2, 4]])  # the row player
# player_2 = np.array([[10, 7], [0, 2]])  # the column player
# game_15 = nash.Game(player_1, player_2)
# print(game_15)
#
# # find the Nash equilibrium with support enumeration
# equilibria= game_15.support_enumeration()
# for eq in equilibria:
#     print(f"the equilibria for q15 are: {eq}")
# # answer: True, this game is dominance solvable. strategy B is dominant for player 2, player 2 would never play A as
# # it is always inferior
# wk: it is not dominance solvable

# question 16: this game has at least one inadmissible Nash equilibrium
# player_1 = np.array([[3, 4], [2, 4]])  # the row player
# player_2 = np.array([[10, 7], [0, 2]])  # the column player
# game_16 = nash.Game(player_1, player_2)
# print(game_16)
#
# # find the Nash equilibrium with support enumeration
# equilibria= game_16.support_enumeration()
# for eq in equilibria:
#     print(f"the equilibria for q15 are: {eq}")
# mk: answer: True, AA is inadmissible because player_2 would never play strategy A. Strategy B is dominant for player_2
# wk: correct b is inadmissable because b is weakly dominated by A

# question 17: two firms, firm_1 and firm_2 compete by simultaneously choosing prices. Both firms sell an identical
#  product for which each of the 100 consumers has a maximum willingness to pay of $40. Each consumer will buy at most
#  1 unit, and will buy it from whichever firm charges the lowest price. If firms set the same price, they share the
#  market equally. Costs are given by ci(qi) = 16qi. Because of government regulation, firms can only choose prices
#  which are integer numbers, and they cannot price above $40. Answer the following:

# NB: this is a Bertrand game

# question 17a) If firm_1 chooses p1 = 26, firm_2's best response is to sell at what price?

# answer: 25, because firm_2 has incentive to undercut and take the market. the smallest unit allowed is 1.

# question 17b) if firm_2 chooses the price determined in the previous question, firm_1's best response is to choose
#  what price?=40-16

# answer: 24, because firm_1 has incentive to undercut and take the market

# question 17c) if firm_1 chooses p1 = 10, firm_2's best response is to choose what price?
# mk: answer: 16, because 16 is the marginal cost and firm_2 is unwilling to make a loss. Firm_1 is pricing
#  irrationally.

# question 17d) Now suppose both firms are capacity-constrained: firm_1 can produce at most 13 units, and firm_2 can
#  produce at most 40 units. If firms set different prices, consumers will first buy from the firm charging the lower
#  price. Once that firm's supply is exhausted, consumers will buy from the firm charging the higher price. what is
#  firm 1's equilibrium profit?
# mk: answer: 40. because the supply is capacity constrained,and demand = 100 > supply = 53, therefore both firms will
#  sell all stock no matter what price they set, so they will set a price that maximises profit. The highest allowable
#  price is $40?
# wk: false. you have confused price per unit with total profit. the correct answer is 20(40-16) = 480

# question 18: suppose ice cream vendors are distributed along a straight beach of 1km. Each consumer buys 1 icecrea
#  from the closest ice cream cart. If the carts are equidistant from a consumer, he will buy from each of them with
#  equal probability. You have learned what the Nash equilibrium is if 2 carts simultaneously choose where to position
#  themselves. Now suppose there are 3 ice cream carts simultaneously deciding where to position themselves. It is a
#  Nash equilibrium for all three carts to position themselves where the median customer is located
# mk: answer: False. If there are 3 vendors, there is no equilibrium. If all 3 vendors are in the centre, one of them
#  can move slightly to the right or left adn capture nearly half the market which is more than the 1/3 he had before.
# wk: correct

# question 19: There is a pure strategy Nash equilibrium in this game in which one cart sells to no-one
# mk: answer: False. At any point on the beach a cart may be positioned, there are some customers who will be closest to
#  that cart. Furthermore, as above, there is no equilibrium with 3 players.
# wk: correct

# question 20: There is a pure strategy nash equilibrium in this game in which all three carts share the market equally
# mk: False, there is no equilibrium in this game.
# wk: correct


import numpy as np

def sigmoid(x):
  # f(x) = 1 / (1 + e^(-x))
  return 1 / (1 + np.exp(-x))

def deriv_sigmoid(x):
  # f'(x) = f(x) * (1 - f(x))
  fx = sigmoid(x)
  return fx * (1 - fx)

def mse_loss(y_true, y_pred):
  # y_true and y_pred are numpy arrays of the same length.
  return ((y_true - y_pred) ** 2).mean()

class NeuralNetwork:
  """NeuralNetwork constructor
  A neural network with:
    - Two inputs (x1,x2)
    - A hidden layer with 2 neurons (h1, h2)
    - An output layer with 1 neuron (o1)
  Each neuron has the same weights and bias
  """
  def __init__(self):
    # Weights. Controls the signal strength
    self.w1 = np.random.normal()
    self.w2 = np.random.normal()
    self.w3 = np.random.normal()
    self.w4 = np.random.normal()
    self.w5 = np.random.normal()
    self.w6 = np.random.normal()

    # Biases. Additional set of weights with no required inputs
    self.b1 = np.random.normal()
    self.b2 = np.random.normal()
    self.b3 = np.random.normal()

    self.list_epoch = []
    self.list_loss = []

  """Calculates the output of the neural network
  Returns:
    int: output from the neural network
  """
  def feedforward(self, x):
    # x is a numpy array with 2 elements.
    h1 = sigmoid(self.w1 * x[0] + self.w2 * x[1] + self.b1)
    h2 = sigmoid(self.w3 * x[0] + self.w4 * x[1] + self.b2)
    o1 = sigmoid(self.w5 * h1 + self.w6 * h2 + self.b3)
    return o1

  def train(self, data, all_y_trues):
    """
    - data is a (n x 2) numpy array, n = # of samples in the dataset.
    - all_y_trues is a numpy array with n elements.
      Elements in all_y_trues correspond to those in data.
    """
    learn_rate = 0.01
    epochs = 2000 # number of times to loop through the entire dataset

    for epoch in range(epochs):
      for x, y_true in zip(data, all_y_trues):
        # Do a feedforward
        sum_h1 = self.w1 * x[0] + self.w2 * x[1] + self.b1
        h1 = sigmoid(sum_h1)

        sum_h2 = self.w3 * x[0] + self.w4 * x[1] + self.b2
        h2 = sigmoid(sum_h2)

        sum_o1 = self.w5 * h1 + self.w6 * h2 + self.b3
        o1 = sigmoid(sum_o1)
        y_pred = o1

        # Calculate partial derivatives.
        # Naming: d_L_d_w1 represents "partial L / partial w1"
        derivative_Lypred = -2 * (y_true - y_pred)

        # Neuron o1
        derivative_o1w5 = h1 * deriv_sigmoid(sum_o1)
        derivative_o1w6 = h2 * deriv_sigmoid(sum_o1)
        derivative_o1b3 = deriv_sigmoid(sum_o1)
        derivative_o1h1 = self.w5 * deriv_sigmoid(sum_o1)
        derivative_o1h2 = self.w6 * deriv_sigmoid(sum_o1)

        # Neuron h1
        derivative_h1w1 = x[0] * deriv_sigmoid(sum_h1)
        derivative_h1w2 = x[1] * deriv_sigmoid(sum_h1)
        derivative_h1b1 = deriv_sigmoid(sum_h1)

        # Neuron h2
        derivative_h2w3 = x[0] * deriv_sigmoid(sum_h2)
        derivative_h2w4 = x[1] * deriv_sigmoid(sum_h2)
        derivative_h2b2 = deriv_sigmoid(sum_h2)

        # Update weights and bias
        # Neuron h1
        self.w1 -= learn_rate * derivative_Lypred * derivative_o1h1 * derivative_h1w1
        self.w2 -= learn_rate * derivative_Lypred * derivative_o1h1 * derivative_h1w2
        self.b1 -= learn_rate * derivative_Lypred * derivative_o1h1 * derivative_h1b1

        # Neuron h2
        self.w3 -= learn_rate * derivative_Lypred * derivative_o1h2 * derivative_h2w3
        self.w4 -= learn_rate * derivative_Lypred * derivative_o1h2 * derivative_h2w4
        self.b2 -= learn_rate * derivative_Lypred * derivative_o1h2 * derivative_h2b2

        # Neuron o1
        self.w5 -= learn_rate * derivative_Lypred * derivative_o1w5
        self.w6 -= learn_rate * derivative_Lypred * derivative_o1w6
        self.b3 -= learn_rate * derivative_Lypred * derivative_o1b3

      # Calculate loss
      if epoch % 10 == 0:
        y_preds = np.apply_along_axis(self.feedforward, 1, data)
        loss = mse_loss(all_y_trues, y_preds)
        self.list_loss.append(loss)
        self.list_epoch.append(epoch)
        print("Epoch %d loss: %.3f" % (epoch, loss))


import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from sklearn import svm

plt.close('all')

def plotclasses(x,y,plottitle='Plot classes on feature domain'):
    ## Plot data
    fig=plt.figure()
    if np.shape(x)[1]==2:
        plt.scatter(x[y,0],x[y,1],marker='.', color='r',alpha=0.5,label='Red Class')
        plt.scatter(x[~y,0],x[~y,1],marker='.', color='g',alpha=0.5, label='Green Class')
    elif np.shape(x)[1]==3:
        ax = fig.add_subplot(111, projection='3d')
        ax.scatter(x[y,0],x[y,1],zs=x[y,2],marker='.', color='r',alpha=0.5,label='Red Class')
        ax.scatter(x[~y,0],x[~y,1],zs=x[~y,2],marker='.', color='g',alpha=0.5, label='Green Class')
        ax.set_zlabel('X_3')

    plt.xlabel('X_1')
    plt.ylabel('X_2')
    plt.title(plottitle)
    plt.legend()
    plt.show()

# Generate data
N=2000;
x=np.random.uniform(-1,1,(N,2))
y=np.sqrt(x[:,0]**2+x[:,1]**2)<0.5
plotclasses(x,y)

## SVM classification

clf = svm.LinearSVC()
clf.fit(x, y)
yest=clf.predict(x)
plotclasses(x,yest,'Linear SVM results')

# Manually introduce a kernel
X=np.zeros((N,3))
X[:,0:2]=x
X[:,2]=(x[:,0]**2+x[:,1]**2)**0.5

clf2 = svm.LinearSVC()
clf2.fit(X, y)
Yest=clf2.predict(X)
plotclasses(X,Yest,'Manual kernel results')

# Using RBF kernels

clf3 = svm.SVC()
clf3.fit(x, y)
yrbf=clf3.predict(x)
plotclasses(x,yrbf,'RBF kernel results')

clf4 = svm.SVC(kernel='poly',degree=2)
clf4.fit(x, y)
ypoly=clf4.predict(x)
plotclasses(x,ypoly,'Poly kernel results with degree 2')

# Plotting decision regions
#plt.figure()
#x_min, x_max = x[:, 0].min() - 0.1, x[:, 0].max() + 0.1
#y_min, y_max = x[:, 1].min() - 0.1, x[:, 1].max() + 0.1
#xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.01),
#                     np.arange(y_min, y_max, 0.01))
#
#Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
#Z = Z.reshape(xx.shape)
#
#
#
#
#plt.contourf(xx, yy, Z, alpha=0.4,label='SVM boundaries')


import numpy as np
import matplotlib.pyplot as plt

def FT(x):
    N = len(x)
    X = np.zeros(N, dtype=complex)

    for k in range(N):
        X[k] = 0.0j
        for n in range(N):
            X[k] += x[n] * np.exp(-2.0 * np.pi * 1.0j / N ) ** (k * n)

    return X

def sample(f, N=16, N_T=1):
    T = 2.0 * np.pi
    omega = 2.0 * np.pi / T # i.e. omega=1 en estos ejemplos

    delta_t = T/N

    t = np.arange(N) * delta_t * N_T
    x = f(t)

    t_model = np.linspace(t.min(), t.max(), 1000)
    x_model = func(omega * t_model)
    return t, x, t_model, x_model

def func(t):
    return 3.0*np.cos(t) + 2.0*np.cos(3*t) + np.cos(5*t)

t, x, t_model, x_model = sample(func)
X = FT(x)
N = len(X)

plt.figure(figsize=(8,4.0))
plt.subplot(1,2,1)

plt.scatter(t, x, label='sample')
plt.plot(t_model, x_model, label='x(t)')
plt.xlabel('t')
plt.ylabel('x(t)')
plt.title('x(t)=3.0cos(t) + 2.0cos(3t) + cos(5t) ')
plt.grid()
plt.legend()

plt.subplot(1,2,2)
plt.scatter(np.arange(N), np.abs(X/N))
plt.stem(np.arange(N), np.abs(X/N))
plt.xlabel('k')
plt.ylabel('|$X_k$|/N')
plt.title("N=16")
plt.grid()
plt.savefig('sample_1.png')

def func(t):
    return 3.0*np.cos(t) + 2.0*np.cos(3*t) + np.cos(5*t)

t, x, t_model, x_model = sample(func, N=32)
X = FT(x)
N = len(X)

plt.figure(figsize=(8,4.0))
plt.subplot(1,2,1)

plt.scatter(t, x, label='sample')
plt.plot(t_model, x_model, label='x(t)')
plt.xlabel('t')
plt.ylabel('x(t)')
plt.title('x(t)=3.0cos(t) + 2.0cos(3t) + cos(5t) ')
plt.grid()
plt.legend()

plt.subplot(1,2,2)
plt.scatter(np.arange(N), np.abs(X/N))
plt.stem(np.arange(N), np.abs(X/N))
plt.xlabel('k')
plt.ylabel('|$X_k$|/N')
plt.title("N=32")
plt.grid()
plt.savefig('sample_2_a.png')

def func(t):
    return 3.0*np.cos(t) + 2.0*np.cos(3*t) + np.cos(5*t)

t, x, t_model, x_model = sample(func, N=32, N_T=2)
X = FT(x)
N = len(X)

plt.figure(figsize=(8,4.0))
plt.subplot(1,2,1)

plt.scatter(t, x, label='sample')
plt.plot(t_model, x_model, label='x(t)')
plt.xlabel('t')
plt.ylabel('x(t)')
plt.title('x(t)=3.0cos(t) + 2.0cos(3t) + cos(5t) ')
plt.grid()
plt.legend()

plt.subplot(1,2,2)
plt.scatter(np.arange(N), np.abs(X/N))
plt.stem(np.arange(N), np.abs(X/N))
plt.xlabel('k')
plt.ylabel('|$X_k$|/N')
plt.title("N=32")
plt.grid()
plt.savefig('sample_2_b.png')

def func(t):
    return np.sin(t) + 2.0*np.sin(3*t) + 3*np.sin(5*t)

t, x, t_model, x_model = sample(func)
X = FT(x)
N = len(X)

plt.figure(figsize=(8,4.0))
plt.subplot(1,2,1)

plt.scatter(t, x, label='sample')
plt.plot(t_model, x_model, label='x(t)')
plt.xlabel('t')
plt.ylabel('x(t)')
plt.title('x(t)=sin(t) + 2.0sin(3t) + 3sin(5t) ')
plt.grid()
plt.legend()

plt.subplot(1,2,2)
plt.scatter(np.arange(N), np.abs(X/N))
plt.stem(np.arange(N), np.abs(X/N))
plt.xlabel('k')
plt.ylabel('|$X_k$|/N')
plt.title("N=16")
plt.grid()
plt.savefig('sample_3.png')

def func(t):
    return 5*np.sin(t) + 2.0*np.cos(3*t) + np.sin(5*t)

t, x, t_model, x_model = sample(func)
X = FT(x)
N = len(X)

plt.figure(figsize=(8,4.0))
plt.subplot(1,2,1)

plt.scatter(t, x, label='sample')
plt.plot(t_model, x_model, label='x(t)')
plt.xlabel('t')
plt.ylabel('x(t)')
plt.title('x(t)=5sin(t) + 2cos(3t) + sin(5t) ')
plt.grid()
plt.legend()

plt.subplot(1,2,2)
plt.scatter(np.arange(N), np.abs(X/N))
plt.stem(np.arange(N), np.abs(X/N))
plt.xlabel('k')
plt.ylabel('|$X_k$|/N')
plt.title("N=16")
plt.grid()
plt.savefig('sample_4.png')

def func(t):
    return 5 + 10*np.sin(t+2)

t, x, t_model, x_model = sample(func)
X = FT(x)
N = len(X)

plt.figure(figsize=(10,20))
plt.subplot(4,2,1)

plt.scatter(t, x, label='sample')
plt.plot(t_model, x_model, label='x(t)')
plt.xlabel('t')
plt.ylabel('x(t)')
plt.title('x(t)=5 + 10sin(t+2)')
plt.grid()
plt.legend()

plt.subplot(4,2,2)
plt.scatter(np.arange(N), np.abs(X/N))
plt.stem(np.arange(N), np.abs(X/N))
plt.xlabel('k')
plt.ylabel('|$X_k$|/N')
plt.title("N=16")
plt.grid()

def func(t):
    return 10*np.sin(t+2)

t, x, t_model, x_model = sample(func)
X = FT(x)
N = len(X)

plt.subplot(4,2,3)

plt.scatter(t, x, label='sample')
plt.plot(t_model, x_model, label='x(t)')
plt.xlabel('t')
plt.ylabel('x(t)')
plt.title('x(t)=10sin(t+2)')
plt.grid()
plt.legend()

plt.subplot(4,2,4)
plt.scatter(np.arange(N), np.abs(X/N))
plt.stem(np.arange(N), np.abs(X/N))
plt.xlabel('k')
plt.ylabel('|$X_k$|/N')
plt.title("N=16")
plt.grid()

def func(t):
    return np.sin(t+2)

t, x, t_model, x_model = sample(func)
X = FT(x)
N = len(X)

plt.subplot(4,2,5)

plt.scatter(t, x, label='sample')
plt.plot(t_model, x_model, label='x(t)')
plt.xlabel('t')
plt.ylabel('x(t)')
plt.title('x(t)=sin(t+2)')
plt.grid()
plt.legend()

plt.subplot(4,2,6)
plt.scatter(np.arange(N), np.abs(X/N))
plt.stem(np.arange(N), np.abs(X/N))
plt.xlabel('k')
plt.ylabel('|$X_k$|/N')
plt.title("N=16")
plt.grid()

def func(t):
    return np.sin(t)

t, x, t_model, x_model = sample(func)
X = FT(x)
N = len(X)

plt.subplot(4,2,7)

plt.scatter(t, x, label='sample')
plt.plot(t_model, x_model, label='x(t)')
plt.xlabel('t')
plt.ylabel('x(t)')
plt.title('x(t)=sin(t)')
plt.grid()
plt.legend()

plt.subplot(4,2,8)
plt.scatter(np.arange(N), np.abs(X/N))
plt.stem(np.arange(N), np.abs(X/N))
plt.xlabel('k')
plt.ylabel('|$X_k$|/N')
plt.title("N=16")
plt.grid()

plt.savefig('sample_5.png')

def func(t):
    return np.sin(0.25*t) + 5*np.sin(t)

t, x, t_model, x_model = sample(func, N_T=4, N=16)
X = FT(x)
N = len(X)

plt.figure(figsize=(8,9.0))
plt.subplot(2,2,1)

plt.scatter(t, x, label='sample')
plt.plot(t_model, x_model, label='x(t)')
plt.xlabel('t')
plt.ylabel('x(t)')
plt.title('x(t)=sin(t/4) + 5 sin(t)')
plt.grid()
plt.legend()

plt.subplot(2,2,2)
plt.scatter(np.arange(N), np.abs(X/N))
plt.stem(np.arange(N), np.abs(X/N))
plt.xlabel('k')
plt.ylabel('|$X_k$|/N')
plt.title("N=16")
plt.grid()

t, x, t_model, x_model = sample(func, N_T=4, N=8)
X = FT(x)
N = len(X)

plt.subplot(2,2,3)

plt.scatter(t, x, label='sample')
plt.plot(t_model, x_model, label='x(t)')
plt.xlabel('t')
plt.ylabel('x(t)')
plt.title('x(t)=sin(t/4) + 5 sin(t)')
plt.grid()
plt.legend()

plt.subplot(2,2,4)
plt.scatter(np.arange(N), np.abs(X/N))
plt.stem(np.arange(N), np.abs(X/N))
plt.xlabel('k')
plt.ylabel('|$X_k$|/N')
plt.title("N=8")
plt.grid()

plt.savefig('sample_6.png')


'''
Heap.
'''
import numpy as np

class Heap:
    def __init__(self, capacity=15): # 2^n - 1
        self.array = np.empty(capacity, dtype=np.uint8)
        self.length = capacity
        self.size = 0

    def is_full(self):
        return self.size == self.length

    def is_empty(self):
        return self.size == 0

    def expand(self):
        self.capacity = 2 * self.capacity + 1
        self.array.resize((self.capacity,), refcheck=False)

    def insert(self, value):
        if self.is_full():
            self.expand()
        self.array[self.size] = value
        self.size += 1
        self.bubble_up()

    def bubble_up(self):
        idx = self.size - 1
        while idx > 0 and self.array[idx] > self.array[self.parent(idx)]:
            self.swap_values(idx, self.parent(idx))
            idx = self.parent(idx)

    def remove(self):
        if self.is_empty(): raise ValueError()

        root = self.array[0]
        self.array[0] = self.array[self.size - 1]
        self.size -= 1
        self.bubble_down()
        return root

    def bubble_down(self):
        idx = 0
        while idx <= self.size and not self.is_valid_parent(idx):
            larger_child_idx = self.larger_child_idx(idx)
            self.swap_values(idx, larger_child_idx)
            idx = larger_child_idx

    def larger_child_idx(self, idx):
        if not self.has_left_child(idx):
            return idx

        if not self.has_right_child(idx):
            return self.left_child_idx(idx)

        if self.left_child(idx) > self.right_child(idx):
            return self.left_child_idx(idx)

        return self.left_child_idx(idx) + 1

    def has_left_child(self, idx):
        return self.left_child_idx(idx) <= self.size

    def has_right_child(self, idx):
        return self.left_child_idx(idx) + 1 <= self.size

    def is_valid_parent(self, idx):
        if not self.has_left_child(idx):
            return True

        is_valid = self.array[idx] >= self.left_child(idx)

        if self.has_right_child(idx):
            is_valid &= self.array[idx] >= self.right_child(idx)

        return is_valid

    def left_child(self, idx):
        return self.array[self.left_child_idx(idx)]

    def right_child(self, idx):
        return self.array[self.left_child_idx(idx) + 1]

    def left_child_idx(self, idx):
        return idx * 2 + 1

    def parent(self, idx):
        return (idx - 1) // 2

    def swap_values(self, idx1, idx2):
        arr = self.array
        arr[idx1], arr[idx2] = arr[idx2], arr[idx1]

    def __str__(self):
        return f'{self.array[:self.size]}'

def main():
    heap = Heap()
    nums = [15, 10, 3, 8, 12, 9, 4, 1, 24]
    for num in nums:
        heap.insert(num)
    print('\n\t starting values: ', nums, '\n')
    print('\t the heap: ', heap, '\n')
    print('\t removing largest... \n')
    print('\t removed item: ', heap.remove(), '\n')
    print('\t the heap: ', heap, '\n')

if __name__ == '__main__':
    main()


from typing import Tuple

import numpy as np

class Distance:
    """"Distance class is employed to calculate a distance between two points.

    This class provides a handy set of methods to be used whenever a distance
    needs to be calculated.

    """

    @staticmethod
    def euclidean_distance(point1: Tuple[float, float], point2: Tuple[float, float]):
        """Calculate the Euclidean distance of two points.

        Args:
            point1 (Tuple[float, float]): The first point.
            point2 (Tuple[float, float]): The second point.

        Returns:
            float: The distance.
        """

        x1, y1 = point1
        x2, y2 = point2
        return np.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)

    @staticmethod
    def manhattan_distance(point1: Tuple[float, float], point2: Tuple[float, float]):
        """Calculate the Manhattan distance of two points.

        Args:
            point1 (Tuple[float, float]): The first point.
            point2 (Tuple[float, float]): The second point.

        Returns:
            float: The distance.
        """

        x1, y1 = point1
        x2, y2 = point2
        return np.abs(x2 - x1) + np.abs(y2 - y1)

    @staticmethod
    def map_str_to_function(function_name: str):
        """Map the function name into the function reference.

        Args:
            function_name (str): The name of the distance function.

        Returns:
            function: The distance function object.
        """

        if function_name == Distance.euclidean_distance.__name__:
            return Distance.euclidean_distance
        elif function_name == Distance.manhattan_distance.__name__:
            return Distance.manhattan_distance


import numpy as np
import random
import math
import sys
import time

# This is a single line comment, which starts with `#`in python
# Task:
# Make the following function execute faster/more efficiently.
# make use of numpy as you deem fit.

def convert_to_polar(N):
    """
    Generate a random set of N (x,y) cartesian coordinates,
    convert them to polar coordinates.
    Hints
    tuple (a,b) in python is  a sequence of immutable data.
    """
    cartesian_set = []
    a = 0
    while  a < N :
        cartesian_set.append(   tuple (random.sample(range(1, 100), 2) ) )
        a+=1
    polar_set = []
    index = 0
    for coordinate in cartesian_set:
        x,y = coordinate # coordinate is a tuple, we can split it to x, y
        r = math.sqrt(x**2 + y**2)
        theta = math.atan2(float(y), x)
        polar_set.append (  tuple([r,theta]))

    return polar_set

def convert_to_polar_np(N):
    """
    Generate a random set of N (x,y) cartesian coordinates,
    convert them to polar coordinates.
    Hints
    tuple (a,b) in python is  a sequence of immutable data.
    """
    cartesian_set = np.random.sample((N,2))
    polar_set = np.zeros((N,2))
    polar_set[:,0] = np.sqrt(cartesian_set[:,0]**2 + cartesian_set[:,1]**2)
    polar_set[:,1] = np.arctan2(cartesian_set[:,1], cartesian_set[:,0])

    return polar_set

if __name__ == "__main__":
    N = sys.argv[1]

    start = time.time()
    convert_to_polar(int (N) )
    end = time.time()
    print 'Regular python took: %s' % (end - start)

    start = time.time()
    convert_to_polar_np(int (N) )
    end = time.time()

    print 'Numpy took: %s' % (end - start)


''' mbinary
#########################################################################
# File : interplotion.py
# Author: mbinary
# Mail: zhuheqin1@gmail.com
# Blog: https://mbinary.xyz
# Github: https://github.com/mbinary
# Created Time: 2018-10-02  21:14
# Description:
#########################################################################
'''

#########################################################################
# File : interplotion.py
# Author: mbinary
# Mail: zhuheqin1@gmail.com
# Blog: https://mbinary.xyz
# Github: https://github.com/mbinary
# Created Time: 2018-05-18  09:29
# Description: 插值计算,有牛顿插值,拉格朗日插值,以及通过插值得到的多项式估计新的函数值
#########################################################################

import sympy
from collections import namedtuple
from functools import reduce
from operator import mul

X = sympy.Symbol('x')
point = namedtuple('point', ['x', 'y'])

class interplotion:
    def __init__(self, points):
        self.points = [point(x, y) for x, y in points]
        self.xs = [i for i, j in points]
        self.poly, self.rem = self.newton(self.points, 0, len(self.points)-1)

    def newton(self, li, a, b):
        '''li:[(x,f(x))...]'''

        qs = [li[0].y]

        def quoDiff(begin, end):
            if begin == end:
                return li[begin].y
            q = (quoDiff(begin+1, end)-quoDiff(begin, end-1)) / \
                (li[end].x-li[begin].x)
            if begin == a:
                qs.append(q)
            return q

        quoDiff(a, b)
        poly, base = 0, 1
        for i, q in enumerate(qs):
            poly += q*base
            base *= X-li[i].x
        return poly, base*qs[-1]

    def lagrange(self, points=None):
        xs = None
        if points is None:
            xs = self.xs
            points = self.points
        else:
            xs = [x for x, y in points]
        product = reduce(mul, [X-x for x in xs], 1)
        poly = 0
        for x, y in points:
            tmp = product/(X-x)
            coef = y/(tmp.subs(X, x))
            poly += coef * tmp
        return poly

    def predict(self, val, poly=None):
        if poly is None:
            poly = self.poly
        return poly.subs(X, val)  # note the func  subs

if __name__ == '__main__':
    f = interplotion([(81, 9), (100, 10), (121, 11)])
    p = f.lagrange()
    print(p.subs(X, 105))
    print(p)

    intor = interplotion([(0, 11), (0.02, 9), (0.04, 7), (0.06, 10)])
    p = intor.lagrange()
    print(p)
    res = intor.predict(0.08)
    print(res)


import numpy as np

def __cos_interp(a, b, cnt):
    ts = np.linspace(0, 1, cnt)
    return a + (b - a) * (1 - np.cos(np.pi * ts)) / 2.0

def interpolate(vs, cnt):
    fp = []
    for i in range(len(vs) - 1):
        fp.append(__cos_interp(vs[i], vs[i + 1], cnt))
    fp = np.array(fp).flatten()
    x = np.linspace(1, cnt, cnt)
    xp = np.linspace(1, cnt, len(fp))
    return np.interp(x, xp, fp)


# -*- coding:utf-8 -*-
"""
Project   : numpy
File Name : 23_24_log_normal.py
Author    : Focus
Date      : 8/22/2021 9:43 PM
Keywords  : np.random.lognormal
Abstract  :
Param     :
Usage     : py 23_24_log_normal.py
Reference :
"""
import numpy as np
import matplotlib.pyplot as plt

N = 10000
lognormal_values = np.random.lognormal(size=N)
dummy, bins, dummy = plt.hist(lognormal_values, int(np.sqrt(N)), density=True, lw=1)
sigma = 1
mu = 0
x = np.linspace(min(bins), max(bins), len(bins))
pdf = np.exp(-(np.log(x) - mu) ** 2 / (2 * sigma ** 2))
pdf = pdf / (x * sigma * np.sqrt(2 * np.pi))
plt.plot(x, pdf, lw=3)
plt.show()


import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import multivariate_normal
import seaborn as sns
class GMM():

    def __init__(self, k=3, max_iter=100):

        self._k = k
        self._max_iter = max_iter
        self._gmm_params = []

    def _compute_likelihood(self, X):
        alpha = self._alpha
        mean_all = []
        cov_all = []
        for i in range(3):
            mean = self._gmm_params[i]['mean']
            cov = self._gmm_params[i]['cov']
            mean_all.append(mean)
            cov_all.append(cov)

        loglikelihood = np.sum(np.log(alpha[0] * multivariate_normal(mean=mean_all[0], cov=cov_all[0]).pdf(X) + \
                     alpha[1] * multivariate_normal(mean=mean_all[1], cov=cov_all[1]).pdf(X) + \
                     alpha[2] * multivariate_normal(mean=mean_all[2], cov=cov_all[2]).pdf(X)))

        return loglikelihood

    def fit(self, X):
        self._init_params(X)
        self.ll = []
        for i in range(self._max_iter):
            self._e_step(X)
            self._m_step(X)
            loglikelihood = self._compute_likelihood(X)

            self.ll.append(loglikelihood)
    def _e_step(self, X):

        n_sample = X.shape[0]
        self.wij = np.zeros((n_sample, self._k))
        for i in range(self._k):
            self.wij[:, i] = self._alpha[i] * self._gaussian_pdf(X, self._gmm_params[i])
        self.wij = self.wij / np.sum(self.wij, axis = 1).reshape(-1, 1)

    def _m_step(self, X):
        n_sample = X.shape[0]
        for j in range(self._k):

            self._alpha[j] = np.sum(self.wij[:, j]) / n_sample
            mean = (self.wij[:, j] @ X) / np.sum(self.wij[:, j])
            covar = (self.wij[:, j] * (X - mean).T).dot(X - mean) / np.sum(self.wij[:, j])
            self._gmm_params[j]["mean"] = mean
            self._gmm_params[j]["cov"] = covar

    def _init_params(self, X):

        n_sample = X.shape[0]

        self._alpha = np.ones(self._k) / self._k
        for _ in range(self._k):
            params = dict()
            params["mean"] = X[int(np.random.choice(n_sample, 1))]
            params["cov"] = np.cov(X.T)
            self._gmm_params.append(params)

    def _gaussian_pdf(self, X, params):

        n_sample, n_feature = X.shape
        mean = params["mean"]
        covar = params["cov"]

        determinant = np.linalg.det(covar)
        Gauss = np.zeros(n_sample)
        coeff = 1 / np.sqrt(np.power(2*np.pi, n_feature) * determinant)
        for i, x in enumerate(X):
            exponent = np.exp(-0.5 * (x - mean).T.dot(np.linalg.inv(covar)).dot(x - mean))
            Gauss[i] = coeff * exponent
        return Gauss

if __name__ == "__main__":
    x1 = np.random.multivariate_normal(mean = [-3, -3], cov = [[1, 0.5],[0.5, 1]], size = 100)
    x2 = np.random.multivariate_normal(mean = [3, 0], cov = [[1, 0],[0, 1]], size = 100)
    x3 = np.random.multivariate_normal(mean = [0, 4], cov = [[1, -0.2],[-0.2, 1]], size = 100)
    with sns.axes_style('darkgrid'):
        fig = plt.figure(figsize=(6, 3))
        ax = fig.add_subplot()
        ax.scatter(x = x1[:,0], y = x1[:,1])
        ax.scatter(x = x2[:,0], y = x2[:,1])
        ax.scatter(x = x3[:,0], y = x3[:,1])
        ax.legend(['class1', 'class2', 'class3'])
        ax.set_xlabel('x1')
        ax.set_ylabel('x2')
        ax.grid('True')

    X = np.concatenate([x1, x2, x3], axis = 0)

    model = GMM(k = 3, max_iter = 100)
    model.fit(X)

    alpha = model._alpha
    mean_all = []
    cov_all = []
    for i in range(3):
        mean = model._gmm_params[i]['mean']
        cov = model._gmm_params[i]['cov']
        mean_all.append(mean)
        cov_all.append(cov)

    x1plot = np.linspace(min(X[:, 0]), max(X[:, 0]), 1000)
    x2plot = np.linspace(min(X[:, 1]), max(X[:, 1]), 1000)
    X1, X2 = np.meshgrid(x1plot, x2plot)
    vals = np.zeros(X1.shape)

    for i in range(X1.shape[1]):
        this_X = np.stack((X1[:, i], X2[:, i]), axis=1)

        vals[:, i] = alpha[0] * multivariate_normal(mean=mean_all[0], cov=cov_all[0]).pdf(this_X)+ \
             alpha[1] * multivariate_normal(mean=mean_all[1], cov=cov_all[1]).pdf(this_X)+ \
             alpha[2] * multivariate_normal(mean=mean_all[2], cov=cov_all[2]).pdf(this_X)

    ax.contour(X1, X2, vals)
    plt.savefig('GMM_Contour.png', dpi=250, bbox_inches='tight')
    plt.show()

    print('predicted mean', mean_all)
    print('predicted conv', cov_all)
    print('predicted alpha', alpha)

    with sns.axes_style('darkgrid'):
        fig = plt.figure(figsize=(6, 3))
        ax = fig.add_subplot()
        ax.plot(model.ll)
        ax.set_xlabel('iteration')
        ax.set_ylabel('loglikelihood')
        ax.grid('True')
        plt.savefig('GMM_likelihood.png', dpi=250, bbox_inches='tight')
        plt.show()


import csv
import numpy as np

def slr(X,Y):
    # The role of this function is to
    # estimate or find out the beta_0
    # and beta_1 value of the best
    # fit line.

    # Eruption Duration Values (Input)
    # X = [3.1,3.2,2.6,......]

    # Waiting Time Values (Output)
    # Y = [88,65,75,......]

    X_bar = np.mean(X)
    Y_bar = np.mean(Y)

    # Xi-X_bar, Yi-Y_bar
    Xi_Xbar = []
    Xi_Xbar_2 =[]
    Xi_Xbar_mul_Yi_Ybar = []

    for i in range(0,len(X)):
        Xi_Xbar.append( X[i] - X_bar )
        Xi_Xbar_2.append((X[i] - X_bar)*(X[i] - X_bar))
        Xi_Xbar_mul_Yi_Ybar.append((X[i] - X_bar) * (Y[i] - Y_bar))

    final_numerator = np.sum(Xi_Xbar_mul_Yi_Ybar)

    final_denominator = np.sum(Xi_Xbar_2)

    beta_1 = final_numerator/final_denominator

   # print(beta_1)

    beta_0 = Y_bar - beta_1 * X_bar

    #print(beta_0)
    return [beta_0,beta_1]

def predict(x_predict, beta_values):                    # slr implementation
    y_predict = beta_values[0] + beta_values[1]*x_predict

    return y_predict

def rss(actual_y, predicted_y):                         # calculating RSS value to calculate RSE
    rss_value = 0
    for i in range(len(actual_y)):
        error2 = (actual_y[i]-predicted_y[i])*(actual_y[i]-predicted_y[i])
        rss_value += error2
#     print("\nRss value is coming out to be ", rss_value)
    return rss_value

def rse(actual_y, predicted_y):                             # Calculating RSE value
    rss_val = rss(actual_y,predicted_y)
    n = len(actual_y)
    rse_val = (rss_val/n-2)**0.5

    print("\nRse value is ",rse_val)

def rsquare(actual_y, predicted_y):                                 # Calculating R-Square value
    tss = 0
    m = np.mean(actual_y)
    for k in range(len(actual_y)):
        var = (actual_y[k]-m)*(actual_y[k]-m)
        tss += var
    rss_valu = rss(actual_y, predicted_y)
    r2 = (tss-rss_valu)/tss

    print('\nR Square value is ', r2)

f = open("geyser.csv",'r')

reader = csv.reader(f)

X = []
Y = []

for row in reader:
    if row[1] != "eruptions":
        X.append(float(row[1]))
        Y.append(float(row[2]))

f.close()

betas = slr(X,Y)

ypredicted = []
for xp in X:
    ypredicted.append(predict(xp,betas))
rse(Y,ypredicted)
rsquare(Y,ypredicted)


import numpy as np
x = [1970, 1980, 1991, 1996, 2000, 2010]
y = [132165, 151326, 179323, 203302, 226542, 249633]

ln_y = []

for item in y:
    log_y = np.log(item)
    ln_y.append(log_y)

def somatorio(lista):
    somatorio = 0
    for item in lista:
        somatorio += item
    return somatorio

def x_vezes_y(lista_x, lista_y):
    valores_x_vezes_y = []
    for c in range(0, len(lista_x)):
        elemento = lista_x[c] * lista_y[c]
        valores_x_vezes_y.append(elemento)
    return valores_x_vezes_y

def quadrado(x):
    lista_quadrados = []
    for item in x:
        elemento = item ** 2
        lista_quadrados.append(elemento)
    return lista_quadrados

def a(x, somatorio_x, somatorio_y, somatorio_xy, somatorio_x_quadrado):
    n = len(x)
    resultado = ((n * somatorio_xy) - (somatorio_x * somatorio_y)) / ((n * somatorio_x_quadrado) - (somatorio_x ** 2))
    return resultado

def b(x, somatorio_x, somatorio_y, somatorio_xy, somatorio_x_quadrado):
    n = len(x)
    resultado = ((somatorio_x * somatorio_xy) - (somatorio_y * somatorio_x_quadrado)) / ((somatorio_x ** 2) - (n * somatorio_x_quadrado))
    return resultado

def g(x, a, b):
    resultado = b * np.exp(a * x)
    return resultado

def media(lista):
    total = 0
    for item in lista:
        total += item
    media = total / len(lista)
    return media

def valores_aplicados(valores):
    lista_valores_aplicados = []
    for valor in valores:
        valor_aplicado = g(valor, valor_a, b_real)
        lista_valores_aplicados.append(valor_aplicado)
    return lista_valores_aplicados

def SQreg(gx, y_barra):
    valores_sqreg = []
    for c in gx:
        elemento = (c - y_barra) ** 2
        valores_sqreg.append(elemento)
    return valores_sqreg

def SQtot(yi, y_barra):
    valores_sqtot = []
    for c in yi:
        elemento = (c - y_barra) ** 2
        valores_sqtot.append(elemento)
    return valores_sqtot

def R2(sqreg, sqtot):
    total = sqreg/sqtot
    return total

somatorio_x = somatorio(x)
somatorio_y = somatorio(ln_y)
valores_x_vezes_y = x_vezes_y(x, ln_y)
somatorio_x_vezes_y = somatorio(valores_x_vezes_y)
x_ao_quadrado = quadrado(x)
somatorio_x_quadrado = somatorio(x_ao_quadrado)
valor_a = a(x, somatorio_x, somatorio_y, somatorio_x_vezes_y, somatorio_x_quadrado)
valor_b = b(x, somatorio_x, somatorio_y, somatorio_x_vezes_y, somatorio_x_quadrado)
b_real = np.exp(valor_b)
y_barra = media(ln_y)
g_x_aplicado = valores_aplicados(x)
sqreg = SQreg(g_x_aplicado, y_barra)
somatorio_sqreg = somatorio(sqreg)
sqtot = SQtot(y, y_barra)
somatorio_sqtot = somatorio(sqtot)
r2 = R2(somatorio_sqreg, somatorio_sqtot)

print('ESTATÍSTICA'.center(30))
print('-' * 30)
print(f'Somatório de x = {somatorio_x}')
print(f'Somatório de y = {somatorio_y}')
print(f'Somatório X.Y = {somatorio(valores_x_vezes_y)}')
print(f'Somatório X² = {somatorio_x_quadrado}')
print(f'Valor de a = {valor_a:.3f}')
print(f'Valor de b = {b_real}')
print(f'Valor de y médio = {y_barra:.3f}')
print(f'Valor de R² = {r2:.4f}')
print('-' * 30)
print(f'A equação é: g(x) = {valor_a:.3f}X + {b_real}')
print('-' * 30)
print(f'Estimativa população em 2003 = {g(2003, valor_a, b_real):,.3f}')
print(f'Estimativa população em 2020 = {g(2020, valor_a, b_real):,.3f}')


import numpy as np
import scipy.sparse as spsparse

def objective_p_laplace(u, W, b, p):
    """Minimizers of this objective function solve the p-Laplace equation with `b` as
    RHS.
    $ 1/(2p) \sum_{ij}^n W_{ij} |u_i - u_j|^p - u * b $
    """
    difference_matrix = np.abs(u[:, np.newaxis] - u[np.newaxis, :]) ** p
    Ju = 0.5 / p * W.multiply(difference_matrix).sum() - np.dot(u, b)
    return Ju

def objective_p_laplace_gradient(u, W, b, p):
    """Gradient of the p-Laplace objective function. Critical points satisfy
    $ -div(|\grad u|^{p-2} \grad u) = b $
    For a derivation of the vector notation used here, see [1].

    References
    ----------
    [1] "Algorithms for l^p-based semi-supervised Learning on Graphs" by MF Rios, J Calder
    and G Lerman; Preprint (arXiv); https://arxiv.org/abs/1901.05031
    """
    if p != 2:
        A = W.multiply(np.abs(u[:, np.newaxis] - u[np.newaxis, :]) ** (p - 2))
    else:
        A = W
    # TODO: use get_node_degrees here?
    D = spsparse.diags(A.sum(axis=1).A1)
    grad_Ju = 1.0 * (D - A) @ u - b
    return grad_Ju

def objective_weighted_mean(u, A):
    mean = np.dot(u, A)
    return mean

def objective_weighted_mean_gradient(u, A):
    return A


from sage.all import *
import random as rd
import numpy as np
import sympy as spy
import datetime as dt
import base64

def imply(p,q):
    return int(not p or q)

def iff(p,q):
    return int(imply(p,q) and imply(q,p))

def xor(p,q):
    return int((p or q) and not(p and q))

def tablify2(formula):
    formula=str(formula)
    p,q = var("p,q")
    code="def f(p,q): return %s" % formula
    exec(code)
    tabla = []
    cabecera = ["p", "q", formula]
    tabla.append(cabecera)
    for p in [True, False]:
        for q in [True, False]:
            renglon = [int(p), int(q), int(f(p,q))]
            tabla.append(renglon)
    show(table(tabla))

def tablify3(formula):
    formula=str(formula)
    p,q,r = var("p,q,r")
    code="def f(p,q,r): return %s" % formula
    exec(code)
    tabla = []
    cabecera = ["p", "q", "r", formula]
    tabla.append(cabecera)
    for p in [True, False]:
        for q in [True, False]:
            for r in [True, False]:
                renglon = [int(p), int(q), int(r), int(f(p,q,r))]
                tabla.append(renglon)
    show(table(tabla))

def tablify4(formula, importa=""):
    formula=str(formula)
    p,q,r,s = var("p,q,r,s")
    code="%s" % importa
    exec(code)
    code="def f(p,q,r,s): return %s" % formula
    exec(code)
    tabla = []
    cabecera = ["p", "q", "r", "s", formula]
    tabla.append(cabecera)
    for p in [True, False]:
        for q in [True, False]:
            for r in [True, False]:
                for s in [True, False]:
                    renglon = [int(p), int(q), int(r), int(s), int(f(p,q,r,s))]
                    tabla.append(renglon)
    show(table(tabla))

def urel(B):
    B = FiniteEnumeratedSet(B)
    U = []
    for x in B:
        for y in B:
            U.append([x,y])
    return U

def diag(B):
    B=FiniteEnumeratedSet(B)
    D=[]
    for x in B:
        D.append([x,x])
    return D

def reflexive(R,B):
    D = diag(B)
    for x in D:
        if not(x in R):
            return False
    return True

def symmetric(R):
    for par in R:
        x=par[0]; y=par[1]
        if not([y,x] in R):
            return False
    return True

def antisymmetric(R):
    for par in R:
        x=par[0]; y=par[1]
        if not(x==y) and ([y,x] in R):
            return False
    return True

def transitive(R):
    for par in R:
        for otro in R:
            if par[1]==otro[0]:
                if not([par[0], otro[1]] in R):
                    return False
    return True

def RelGen(A, B=True):
    #print B
    if B==True:
        B=copy(A)
    #print B
    A = FiniteEnumeratedSet(A)#; print A
    A = list(A)
    B = FiniteEnumeratedSet(B)#; print A
    B = list(B)
    AxB = []
    for x in A:
        for y in B:
            AxB.append([x,y])
    return list(powerset(AxB))#; print len(Rels)

def RelClassifier(A):
    equiv=[]
    order=[]
    milista=[]
    Rels = RelGen(A)
    #print Rels
    for R in Rels:
        #print R
        if reflexive(R, A) and transitive(R):
            if symmetric(R):
                equiv.append(R)
                milista.append(R)
            if antisymmetric(R):
                anexo = True
                for par in R:
                    if par[0]>par[1]:
                        anexo=False
                        break
                if anexo:
                    order.append(R)
                    if not(R in milista):
                        milista.append(R)
    return equiv, order, milista

def RelAnalyser(A):
    Rels = RelGen(A)
    mitabla=[["Relation", "Reflexive", "Symmetric", "Antisymmetric", "Transitive"]]
    for R in Rels:
        renglon=[R, reflexive(R, A), symmetric(R), antisymmetric(R), transitive(R)]
        mitabla.append(renglon)
    return mitabla

def SingletonsGen(B):
    lista = []
    for b in B:
        lista.append([b])
    return lista

def ImagesGen(B, N, test=False):
    k=1; l=len(B)
    X = SingletonsGen(B)#; print "X=",X
    #X=[]
    #for i in range(l**N):
    #    X.append([])
    Y = []
    while k < N:
        for x in X:
            for b in B:
                y=copy(x)#; print x
                y.append(b)#; print y
                Y.append(y)
        X=copy(Y)
        Y=[]
        k+=1
        if test: print "ImageGen: step "+str(k)+" X="+str(X)
    return X

def FunGen(A,B, test=False):
    N=len(A)
    Ims=ImagesGen(B,N, test)
    Funs = []
    for im in Ims:
        fun = []
        for k,a in enumerate(A):
            fun.append([a,im[k]])
        if test: print "fun= "+str(fun)
        Funs.append(fun)
    return Funs

def oneone(fun):
    for k, pair in enumerate(fun):
        x=pair[0]
        y=pair[1]
        for i in range(k):
            xx = fun[i][0]
            yy = fun[i][1]
            if y==yy:
                return False
    return True

def image(fun):
    lista=[]
    for pair in fun:
        y = pair[1]
        lista.append(y)
    return list(Set(lista))

def onto(fun, B):
    if Set(image(fun))==Set(B):
        return True
    else:
        return False

def invertible(fun, B):
    if oneone(fun) and onto(fun, B):
        return True
    else:
        return False

def FunAnalyser(Funs, B):
    mitabla=[["Function", "1:1", "Onto", "Invertible"]]
    for fun in Funs:
        renglon=[fun, oneone(fun), onto(fun,B), invertible(fun, B)]
        mitabla.append(renglon)
    return mitabla

def toSet(rel):
    relx=[]
    for x in rel:
        y=(x[0],x[1])
        relx.append(y)
    return Set(relx)

def grafica(formula, autoname=True, name="", foldername="temp", x0=0, y0=0, R=1, micolor="blue", npoints=200):
    #s = str(formula)
    code ="""x=var("x")"""; exec(code)
    code ="""f = (%s).function(x)""" % formula
    exec(code)

    M=max([f(x) for x in np.linspace(x0-R,x0+R, npoints)])
    m=max([f(x) for x in np.linspace(x0-R,x0+R, npoints)])

    titulo="\'$"+latex(f)+"$\'"
    print titulo
    code="""graf = plot(f, (x,x0-R,x0+R), ymin = y0-R, ymax = M, color=micolor,
    title=r%s)""" % titulo
    print code
    exec(code)

    name=str(name)
    if not(name==""):
        autoname=False
    if autoname==True:
        tiempo=str(dt.datetime.now())
        filename="IMG-"+tiempo+".png"
    else:
        filename="IMG-"+str(name)+".png"
    if not os.path.isdir(foldername):
        os.makedirs(foldername)
    filenamext = os.path.join(foldername, filename)
    nombre=str(filename)
    nombrext=str(filenamext)
    graf.save(nombrext)
    #graf.show()
    with open(nombrext, "rb") as image_file:
        encoded_string = base64.b64encode(image_file.read())
        #grafcod.append(str(encoded_string))
    return str(encoded_string)


import numpy as np
import matplotlib.pylab as plt

np.random.seed(8675309)

N = 100
x = np.linspace(0,1,N) + (np.random.random(N)-0.5)
y = np.random.random(N)*x
z = -0.1*np.random.random(N)*x

plt.plot(np.linspace(0,1,N),x,color='r')
plt.plot(np.linspace(0,1,N),y,color='g')
plt.plot(np.linspace(0,1,N),z,color='b')
plt.plot(np.linspace(0,1,N)[::5],x[::5],color='r',marker='o',linestyle='none',label='X')
plt.plot(np.linspace(0,1,N)[::5],y[::5],color='g',marker='s',linestyle='none',label='Y')
plt.plot(np.linspace(0,1,N)[::5],z[::5],color='b',marker='*',linestyle='none',label='Z')
plt.legend(loc="upper left")
plt.tight_layout(pad=0, w_pad=0, h_pad=0)
plt.savefig("correlation_xyz_plot.png", dpi=300)
plt.close()

plt.plot(x,y,marker='o',linestyle='none',color='r',label="X,Y")
plt.plot(x,z,marker='s',linestyle='none',color='g',label="X,Z")
plt.plot(y,z,marker='*',linestyle='none',color='b',label="Y,Z")
plt.legend(loc="upper left")
plt.tight_layout(pad=0, w_pad=0, h_pad=0)
plt.savefig("correlation_scatter_plot.png", dpi=300)
plt.close()

from scipy.stats import pearsonr, spearmanr

print("Pearson(x,y) :", pearsonr(x,y)[0])
print("Spearman(x,y):", spearmanr(x,y)[0])
print()
print("Pearson(x,z) :", pearsonr(x,z)[0])
print("Spearman(x,z):", spearmanr(x,z)[0])
print()
print("Pearson(y,z) :", pearsonr(y,z)[0])
print("Spearman(y,z):", spearmanr(y,z)[0])
print()

def pearson(x,y):
    exy = (x*y).mean()
    ex = x.mean()
    ey = y.mean()
    exx = (x*x).mean()
    ex2 = x.mean()**2
    eyy = (y*y).mean()
    ey2 = y.mean()**2
    return (exy - ex*ey)/(np.sqrt(exx-ex2)*np.sqrt(eyy-ey2))

print("pearson(x,y):", pearson(x,y))
print("pearson(x,z):", pearson(x,z))
print("pearson(y,z):", pearson(y,z))
print()

d = np.vstack((x,y,z))
print(np.corrcoef(d))
print()

from sklearn.datasets import load_sample_image
china = load_sample_image('china.jpg')
a = china[230,:,1].astype("float64")
b = china[231,:,1].astype("float64")
c = china[400,:,1].astype("float64")
d = np.random.random(640)
print("china(a,b): ", pearson(a,b))
print("china(a,c): ", pearson(a,c))
print("china(a,d): ", pearson(a,d))
print()

#  spearman
def spearman(x,y):
    n = len(x)
    t = x[np.argsort(x)]
    rx = []
    for i in range(n):
        rx.append(np.where(x[i] == t)[0][0])
    rx = np.array(rx, dtype="float64")
    t = y[np.argsort(y)]
    ry = []
    for i in range(n):
        ry.append(np.where(y[i] == t)[0][0])
    ry = np.array(ry, dtype="float64")
    d = rx - ry
    return 1.0 - (6.0/(n*(n*n-1)))*(d**2).sum()

print(spearman(x,y), spearmanr(x,y)[0])
print(spearman(x,z), spearmanr(x,z)[0])
print(spearman(y,z), spearmanr(y,z)[0])
print()

a = np.linspace(-20,20,1000)
b = 1.0 / (1.0 + np.exp(-a))
print(pearson(a,b))
print(spearman(a,b))