# -*- coding: utf-8 -*-
"""
Compute nice borders for a print.

The borders are computed so the area of the mat equals the
area of the window times "Golden Ratio".

They are also computed so that the top, left and right borders
are the same, and the bottom border is bigger to compensate for
the optical illusion that the print "sinks" into the mat.

This also allows to use "Golden Ratio"^2 for bigger borders, and
any multiplicator to that.

It's also possible to compute the print size from a desired mat
size.

:copyright: (c) 2019, Serge Émond
:license: BSD 3-Clause, http://opensource.org/licenses/BSD-3-Clause
"""

import re

import click

from mpmath import mp

mp.dps = 15

# Precision to use when printing dimensions
PRECS = {"mm": 1, "cm": 2, "dm": 3, "m": 4, "in": 2, "ft": 4}
POSSIBLE_UNITS = ["mm", "cm", "dm", "m", "in", "ft"]
_punits = "(?P<u>" + "|".join(POSSIBLE_UNITS) + ")?$"
_dim_re = re.compile(r"(?P<v>[0-9.-]+)" + _punits)
_dim_pair_re = re.compile(r"(?P<w>[0-9.]+)(?P<sep>x|X|,|:|-)(?P<h>[0-9.]+)" + _punits)

# The Golden Ratio
PHI = (1 + mp.sqrt(5)) / 2

def _A(b, x, mat):
    bp = -b if mat else b
    return b * b / (x + bp)

def compute_bottom_border(b, dims, mat=False):
    """Compute the bottom border for a given border size."""
    x, y = dims
    return b + _A(b, x, mat)

def compute_border_sizes(dims, phi, mat=False):
    """Compute optimal border sizes for a mat window mesuring dims[0] x dims[1].

    Returns a tuple (top_left_right_border, bottom_border).

    The computed top, left and right borders have the same value.

    The computed bottom border is bigger to compensate for the
    optical illusion of the picture "sinking" in the frame.

    The values are computed so that the mat's surface is the window's
    surface times `phi`.

    Passing `mat=True` causes the computation to be reversed: the borders
    are computed from a mat size.
    """
    x, y = dims
    if mat:
        # dims are mat's dims
        coeffs = [
            -2 * phi,
            5 * x * phi + 2 * y * phi,
            -2 * x * x * phi - 3 * x * y * phi + x * y,
            x * x * y * (phi - 1),
        ]
    else:
        # dims are window's dims
        coeffs = [
            6,
            7 * x + 2 * y,
            x * ((3 - phi) * y + 2 * x),
            (1 - phi) * mp.power(x, 2) * y,
        ]

    roots = mp.polyroots(coeffs, 50)
    roots = [root for root in roots if mp.im(root) == mp.mpf(0)]

    # If multiple solutions, keep the one with smalest borders first
    roots.sort()
    sols = [
        # (root, root + mp.power(root, 2) / (x + root))
        (root, root + _A(root, x, mat))
        for root in roots
    ]

    # Shouldn't happen
    if len(roots) < 1:
        raise Exception("Error, could not find solution for borders")
    elif len(roots) > 1:
        click.echo("*Warning: multiple solutions. Keeping smallest border.", err=True)

    return sols[0]

def to_mm(dim, units=None, inverse=False):
    """Convert to millimeters."""
    dim = mp.mpf(dim)
    if units == "cm":
        mult = mp.mpf(10)
    elif units == "dm":
        mult = mp.mpf(100)
    elif units == "m":
        mult = mp.mpf(1000)
    elif units == "in":
        mult = mp.mpf("25.4")
    elif units == "ft":
        mult = mp.mpf("304.8")
    else:
        mult = mp.mpf("1")

    if inverse:
        # convert *from* mm
        return dim / mult
    return dim * mult

def parse_dim(dstr, default_units=None):
    """Parse a string dimension to mm."""
    m = _dim_re.match(dstr)
    if not m:
        click.echo("ERROR: Can't parse dimension {!r}".format(dstr), err=True)
        return None

    units = m.group("u")
    dim = m.group("v")

    if not units:
        units = default_units

    return to_mm(dim, units)

def parse_dim_pair(dstr, default_units=None, allow_single=False):
    """Parse a string representing a pair of dimensions, to mm."""
    m = _dim_pair_re.match(dstr)
    if not m:
        v = parse_dim(dstr)
        return v, v
        click.echo("ERROR: Can't parse dimension pair {!r}".format(dstr), err=True)
        return None

    units = m.group("u")
    width = m.group("w")
    height = m.group("h")

    if not units:
        units = default_units

    return to_mm(width, units), to_mm(height, units)

def dim_str(dim, units):
    """Format a dimension to a given unit."""
    dim = to_mm(dim, units, True)
    prefix = ""
    if units == "ft":
        if dim >= mp.mpf(1):
            prefix = str(int(mp.floor(dim))) + "'"
        units = "in"
        dim = mp.frac(dim) * 12
    dim = prefix + ("{:." + str(PRECS[units]) + "f}").format(float(mp.nstr(dim)))
    if units == "in":
        return dim + '"'

    return dim + " " + units

def dim_pair_str(dims, units):
    """Generate a dimension pair string."""
    return "{} x {}".format(dim_str(dims[0], units), dim_str(dims[1], units))

@click.command()
@click.argument("print-dims")
@click.option("--units", default="mm", help="Default unit, also used for output")
@click.option(
    "--mat/--print", default=False, help="PRINT_DIMS are mat/print size, default print"
)
@click.option(
    "--paper",
    default="0x0",
    help="Dims of the paper of the print, assumed to be centered",
)
@click.option(
    "--overlap",
    default="0",
    help="Overlap of mat's window over the print (pair or single)",
)
@click.option(
    "--factor", default=None, help="Factor to use (mat area = factor * window area)"
)
@click.option("--exp", default=None, help="Exponent to use for the factor")
@click.option(
    "--border", default=None, help="Use fixed border size of this value, compute bottom"
)
def main(print_dims, overlap, units, paper, factor, exp, mat, border=None):
    """Compute mat/window size so my prints look good.

    Given a print's size, compute the required mat dimensions.
    Given a mat's size, compute the required print dimensions.

    \b
    The final border is computed so that:
        - the bottom border is bigger, to compensate for the illusion
          of the print "sinking in" the border
        - the other three borders are equal
        - the mat's area is a factor of the print's size
          (e.g.  mat surface = factor^exp * window size)

    \b
    The default factor is the (Golden Ratio)^(3/2).
        (e.g. --factor=golden, --exp=1.5)

    The border can also have a fixed dimension, in which case only the
    bottom border is still computed.

    An *overlap* can be given, representing the mat's window overlapping
    the print to make sure the print is completely covered.

    This overlap may be a single dimension, or given as a dimension pair.

    \b
    A single dimension is given as <number><optional unit>.
    A dimension pair is given as <width><sep><height><optional unit>.

    `sep` may be 'x', ':', ','.

    The optional unit may be `mm`, `cm`, `dm`, `in`, `ft`.

    When not specified, the unit is the default unit (parameter --unit).
    The default unit is `mm`.

    A paper size can also be given, which allows for the computation of
    the position of the paper on the mat.

    \b
    +--------------------------------------+  ----------------
    |             Border top (b)           |                ^
    |   +------------------------------+   |  ----------    |
    |   |              Oh              |   |          ^     |
    |   |  +------------------------+  |   |  ---     |     |
    |   |  |                        |  |   |   ^      | P   |
    |   |Ow|         Window         |Ow|   |   |Win-  | r   | M
    |   |  |                        |  |   |   |dow   | i   | a
    | b |  |                        |  | b |   v      | n   | t
    |   |  +------------------------+  |   |  ---     | t   |
    |   |              Oh              |   |          v     |
    |   +------------------------------+   |  ----------    |
    |                                      |                |
    |            Border bottom             |                v
    +--------------------------------------+  ----------------

    \b
    Ow = overlap in width
    Oh = overlap in height
    If Ow = Oh = 0, the window's size equals the print's size

    """
    default_units = units if units in POSSIBLE_UNITS else "mm"
    if units and units not in POSSIBLE_UNITS:
        click.echo(
            "ERROR: Unknown units, {!r}, defaulting to mm".format(units), err=True
        )

    overlap_dims = parse_dim_pair(
        overlap, default_units=default_units, allow_single=True
    )
    print_dims = parse_dim_pair(print_dims, default_units=default_units)
    paper_dims = parse_dim_pair(paper, default_units=default_units)
    if not factor or factor in ("phi", "gold", "golden", "golden-ratio"):
        factor = PHI
        if not exp:
            exp = "1.5"
    else:
        factor = mp.mpf(factor)
    if exp:
        factor = mp.power(factor, mp.mpf(exp))

    if border is not None:
        border_dim = parse_dim(border, default_units=default_units)
    else:
        border_dim = None

    if mat:
        # print is mat, compute window and print sizes
        mat_dims = print_dims

        if border_dim:
            b, b_bottom = (
                border_dim,
                compute_bottom_border(border_dim, mat_dims, mat=True),
            )
        else:
            b, b_bottom = compute_border_sizes(mat_dims, factor, mat=True)

        window_dims = (mat_dims[0] - 2 * b, mat_dims[1] - b - b_bottom)

        print_dims = [dim[0] + dim[1] * 2 for dim in zip(window_dims, overlap_dims)]

    else:
        # print is print, compute mat and window sizes
        window_dims = [dim[0] - dim[1] * 2 for dim in zip(print_dims, overlap_dims)]

        if border_dim:
            b, b_bottom = (
                border_dim,
                compute_bottom_border(border_dim, window_dims, mat=False),
            )
        else:
            b, b_bottom = compute_border_sizes(window_dims, factor, mat=False)

        mat_dims = (window_dims[0] + 2 * b, window_dims[1] + b + b_bottom)

    if paper_dims[0] < print_dims[0] or paper_dims[1] < print_dims[1]:
        paper_dims = [dim for dim in print_dims]

    paper_borders = [(dim[0] - dim[1]) / 2 for dim in zip(paper_dims, print_dims)]

    paper_shift = [b - dim[1] - dim[0] for dim in zip(paper_borders, overlap_dims)]

    print("== Parameters ============================================")
    print(
        "Print size        {print}\n"
        "Paper size        {paper}\n"
        "Window overlap    {overlap}\n"
        "Mat window size * {win}".format(
            print=dim_pair_str(print_dims, units),
            paper=dim_pair_str(paper_dims, units),
            overlap=dim_pair_str(overlap_dims, units),
            win=dim_pair_str(window_dims, units),
        )
    )

    print("\n== Mat Information ========================================")
    print(
        "Mat size        * {mat}\n"
        "Window position\n"
        "  Top           * {top}\n"
        "  Bottom        * {bottom}\n"
        "  Left / Right  * {lr}".format(
            mat=dim_pair_str(mat_dims, units),
            top=dim_str(b, units),
            bottom=dim_str(b_bottom, units),
            lr=dim_str(b, units),
        )
    )

    print("\n== Paper position from sides of mat =======================")
    print(
        "  Top           * {top}\n"
        "  Bottom        * {bottom}\n"
        "  Left          * {left}\n"
        "  Right         * {right}".format(
            top=dim_str(paper_shift[1], units),
            bottom=dim_str(mat_dims[1] - paper_dims[1] - paper_shift[1], units),
            left=dim_str(paper_shift[0], units),
            right=dim_str(mat_dims[0] - paper_dims[0] - paper_shift[0], units),
        )
    )

if __name__ == "__main__":
    main()


import math
import numpy as np

def sample_mask_with_rate(shape, one_rate):
    assert len(shape) == 2, "'shape' must be a list/tuple of format (batch, dim)!"
    assert 0.0 < one_rate < 1.0, "'one_rate' must be in (0, 1)"

    num_ones = int(round(one_rate * shape[1], 0))
    num_zeros = shape[1] - num_ones

    if num_ones == 0:
        return np.zeros(shape, dtype=np.bool)
    elif num_zeros == 0:
        return np.ones(shape, dtype=np.bool)
    else:
        mask = np.concatenate([np.ones([shape[0], num_ones], dtype=np.bool),
                               np.zeros([shape[0], num_zeros], dtype=np.bool)], axis=1)
        mask = np.apply_along_axis(np.random.permutation, axis=1, arr=mask)
        return mask

def normal_density(x, mean, stddev, from_axis=None, eps=1e-8):
    variance = np.maximum(stddev ** 2, eps)
    stddev = np.maximum(stddev, eps)

    density = np.exp(-np.square(x - mean) / (2 * variance)) / (stddev * math.sqrt(2 * math.pi))

    if (from_axis is not None) and (from_axis >= 0):
        shape = tuple(density.shape[:from_axis]) + (np.prod(density.shape[from_axis:]),)
        density = np.reshape(density, shape)
        density = np.prod(density, axis=from_axis)

    return density

def normal_density_cupy(x, mean, stddev, from_axis=None, eps=1e-8, gpu=0):
    import cupy as cp

    with cp.cuda.Device(gpu):
        variance = cp.maximum(stddev ** 2, eps)
        stddev = cp.maximum(stddev, eps)

        density = cp.exp(-cp.square(x - mean) / (2 * variance)) / (stddev * math.sqrt(2 * math.pi))

        if (from_axis is not None) and (from_axis >= 0):
            shape = tuple(density.shape[:from_axis]) + (cp.prod(density.shape[from_axis:]),)
            density = cp.reshape(density, shape)
            density = cp.prod(density, axis=from_axis)

        return density

def normal_log_density(x, mean, stddev, from_axis=None, eps=1e-8):
    variance = np.maximum(stddev ** 2, eps)
    log_stddev = np.log(np.maximum(stddev, eps))

    log_density = -0.5 * (math.log(2 * math.pi) + 2 * log_stddev + ((x - mean) ** 2 / variance))

    if (from_axis is not None) and (from_axis >= 0):
        shape = tuple(log_density.shape[:from_axis]) + (np.prod(log_density.shape[from_axis:]),)
        log_density = np.reshape(log_density, shape)
        log_density = np.sum(log_density, axis=from_axis)

    return log_density

def normal_log_density_cupy(x, mean, stddev, from_axis=None, eps=1e-8, gpu=0):
    import cupy as cp

    with cp.cuda.Device(gpu):
        variance = cp.maximum(stddev ** 2, eps)
        log_stddev = cp.log(cp.maximum(stddev, eps))

        log_density = -0.5 * (math.log(2 * math.pi) + 2 * log_stddev + ((x - mean) ** 2 / variance))

        if (from_axis is not None) and (from_axis >= 0):
            shape = tuple(log_density.shape[:from_axis]) + (cp.prod(log_density.shape[from_axis:]),)
            log_density = cp.reshape(log_density, shape)
            log_density = cp.sum(log_density, axis=from_axis)

        return log_density

def at_bin(x, bin_edges, one_hot=True):
    # x: (batch, )
    # bin_edges: num_bins + 1

    x = np.asarray(x)
    bin_edges = np.asarray(bin_edges)

    assert len(x.shape) == 1 and len(bin_edges.shape) == 1, \
        "x.shape: {}, bin_edges.shape: {}".format(x.shape, bin_edges.shape)

    low_edges = bin_edges[:-1]  # num_bins
    high_edges = bin_edges[1:]  # num_bins

    # (batch, num_bins)
    x_at_bin = np.logical_and(np.greater_equal(np.expand_dims(x, axis=1), np.expand_dims(low_edges, axis=0)),
                              np.less(np.expand_dims(x, axis=1), np.expand_dims(high_edges, axis=0)))

    min_edge = bin_edges[0]
    max_edge = bin_edges[-1]

    for n in range(len(x_at_bin)):
        if x[n] < min_edge:
            assert np.sum(x_at_bin[n].astype(np.int32)) == 0
            x_at_bin[n, 0] = True

        if x[n] >= max_edge:
            assert np.sum(x_at_bin[n].astype(np.int32)) == 0, x_at_bin[n]
            x_at_bin[n, -1] = True

    if not one_hot:
        x_at_bin = np.argmax(x_at_bin, axis=-1).astype(np.int32)

    # (batch, num_bins)
    return x_at_bin

def logsumexp(value, axis=None, keepdims=False, gpu_support=None):
    if gpu_support == "jax":
        print("Use jax to compute 'normal_log_density'!")
        import jax.numpy as np
    elif gpu_support == "cupy":
        print("Use cupy to compute 'normal_log_density'!")
        import cupy as np
    else:
        import numpy as np

    if axis is not None:
        max_val = np.max(value, axis=axis, keepdims=True)
        value0 = value - max_val
        if not keepdims:
            max_val = np.squeeze(max_val, axis)
        return max_val + np.log(np.sum(np.exp(value0), axis=axis, keepdims=keepdims))
    else:
        max_val = np.max(value)
        sum_exp = np.sum(np.exp(value - max_val))
        return max_val + math.log(sum_exp)

def logsumexp_cupy(value, axis=None, keepdims=False, gpu=0):
    import cupy as cp
    with cp.cuda.Device(gpu):
        if axis is not None:
            max_val = cp.max(value, axis=axis, keepdims=True)
            value0 = value - max_val
            if not keepdims:
                max_val = cp.squeeze(max_val, axis)
            return max_val + cp.log(cp.sum(cp.exp(value0), axis=axis, keepdims=keepdims))
        else:
            max_val = cp.max(value)
            sum_exp = cp.sum(cp.exp(value - max_val))
            return max_val + math.log(sum_exp)

def variance_uniform(low, high):
    # Variance of uniform distribution in the range [low, high]
    return (high - low) ** 2 / 12.0

def variance_categorical(prob):
    # Variance of categorical distribution with K classes, each has probability p1, p2,..., pK
    # prob: (batch, K)
    # var: (batch, K)

    # assert np.sum(prob) == 1.0, "'prob' must sum to 1. sum(prob) = {}!".format(np.sum(prob))
    # If prob = [0.6, 0.1, 0.3], variance (for each class) is = [0.6 * 0.4, 0.1 * 0.9, 0.3 * 0.7]
    return prob * (1.0 - prob)


from dataclasses import dataclass, field
from typing import Tuple

import matplotlib.pyplot as plt
import numpy as np
from numpy.typing import NDArray

from .. import RecordBase, to_numpy_array

@dataclass
class PCA:

    autoscaled_data: Tuple[RecordBase]
    np_data: NDArray[np.float64] = field(init=False)
    correlation_matrix: NDArray[np.float64] = field(init=False)
    sorted_eigenval_vector_pairs: Tuple[float, NDArray[np.float64]] = field(
        init=False
    )

    def __post_init__(self):
        self.np_data = to_numpy_array(self.autoscaled_data)
        self.correlation_matrix = (self.np_data.T @ self.np_data) / (
            self.np_data.shape[0]
        )

    def execute(self):
        eigenvalue = np.linalg.eig(self.correlation_matrix)
        self.sorted_eigenval_vector_pairs = sorted(
            zip(*eigenvalue), key=lambda e: e[0], reverse=True
        )

    @property
    def lambdas(self):
        return [l for l, _ in self.sorted_eigenval_vector_pairs]

    def scree_plot(self):
        x = np.arange(1, len(self.all_eigenvalue_vector_pairs()) + 1)
        plt.plot(x, self.lambdas)
        plt.scatter(x, self.lambdas)
        plt.title(
            f"Scree Plot for PCA on {len(self.autoscaled_data)} {self.autoscaled_data[0].class_name()} objects"
        )
        plt.ylabel("λ (eigenvalue)")
        plt.xlabel("Principal components")
        plt.xticks(x, [f"PC{i}" for i in x])
        plt.grid(True)

    def percent_scree_plot(self):
        x = np.arange(1, len(self.all_eigenvalue_vector_pairs()) + 1)
        m = len(self.autoscaled_data[0].numeric())
        prct_lambdas = [v / m for v in self.lambdas]
        label = "% of variance explained"
        plt.plot(x, prct_lambdas)
        plt.scatter(x, prct_lambdas)
        plt.title(
            f"Scree Plot for PCA on {len(self.autoscaled_data)} {self.autoscaled_data[0].class_name()} objects"
        )
        plt.ylabel(label)
        y = np.linspace(
            min(prct_lambdas), max(prct_lambdas), len(prct_lambdas)
        )
        plt.yticks(y, [f"{f*100:.1f}%" for f in y])
        plt.xlabel("Principal components")
        plt.xticks(x, [f"PC{i}" for i in x])
        plt.grid(True)

    def cumulative_percent_scree_plot(self):
        x = np.arange(0, len(self.all_eigenvalue_vector_pairs()) + 1)
        m = len(self.autoscaled_data[0].numeric())
        prct_lambdas = [0.0]
        total = 0
        for v in self.lambdas:
            total += v / m
            prct_lambdas.append(total)
        label = "Cumulative % of variance explained"

        plt.plot(x, prct_lambdas)
        plt.scatter(x, prct_lambdas)
        plt.title(
            f"Scree Plot for PCA on {len(self.autoscaled_data)} {self.autoscaled_data[0].class_name()} objects"
        )
        plt.ylabel(label)
        y = np.linspace(
            min(prct_lambdas), max(prct_lambdas), len(prct_lambdas)
        )
        plt.yticks(y, [f"{f*100:.1f}%" for f in y])
        plt.xlabel("Principal components")
        plt.xticks(x, [""] + [f"PC{i}" for i in x[1:]])
        plt.grid(True)

    def all_eigenvalue_vector_pairs(self):
        return self.sorted_eigenval_vector_pairs

    def from_kaiser_criteria(self, treshold: float = 0.95):
        return filter(
            lambda o: o[0] > treshold, self.all_eigenvalue_vector_pairs()
        )


import math
import numpy

def kvalue2(nx, ny, dx, dy):
    # [kx,ky] = kvalue2 (nx,ny,dx,dy);
    #   Wavenumber coordinates for 2D FFT Spectrum
    # ASSUMES EVEN NUMBERED NX

    # First, need to get the frequency scale right.
    # Remember, for even number of points in spectrum, central frequency
    # is not right at the centre, hence frequency spacing is given as follows
    kxmax = ((2 * math.pi) / (dx * 2))
    kymax = ((2 * math.pi) / (dy * 2))
    kxinc = kxmax / ((nx / 2))
    kyinc = kymax / ((ny / 2))

    # make grid of correctly scaled frequencies
    kx, ky = numpy.meshgrid(numpy.arange(-kxmax, kxmax, kxinc), numpy.arange(-kymax, kymax, kyinc))

    return kx, ky


import numpy as np

from ..constants import Constants as c

__all__ = ["calcHerrickGibbs"]

MU = c.G * c.M_SUN

def calcHerrickGibbs(r1, r2, r3, t1, t2, t3):
    """
    Calculates the velocity vector at the location of the second position vector (r2) using the
    Herrick-Gibbs formula.

    .. math::
        \vec{v}_2 =
            -\Delta t_{32} \left ( \frac{1}{ \Delta t_{21} \Delta t_{31}}
                + \frac{\mu}{12 r_1^3}  \right ) \vec{r}_2
            + ( \Delta t_{32} - \Delta t_{21}) \left ( \frac{1}{ \Delta t_{21} \Delta t_{32}}
                + \frac{\mu}{12 r_2^3}  \right ) \vec{r}_2
            + \Delta t_{21} \left ( \frac{1}{ \Delta t_{32} \Delta t_{31}}
                + \frac{\mu}{12 r_3^3}  \right ) \vec{r}_3

    For more details on theory see Chapter 4 in David A. Vallado's "Fundamentals of Astrodynamics
    and Applications".

    Parameters
    ----------
    r1 : `~numpy.ndarray` (3)
        Heliocentric position vector at time 1 in cartesian coordinates in units
        of AU.
    r2 : `~numpy.ndarray` (3)
        Heliocentric position vector at time 2 in cartesian coordinates in units
        of AU.
    r3 : `~numpy.ndarray` (3)
        Heliocentric position vector at time 3 in cartesian coordinates in units
        of AU.
    t1 : float
        Time at r1. Units of MJD or JD work or any decimal time format (one day is 1.00) as
        long as all times are given in the same format.
    t2 : float
        Time at r2. Units of MJD or JD work or any decimal time format (one day is 1.00) as
        long as all times are given in the same format.
    t3 : float
        Time at r3. Units of MJD or JD work or any decimal time format (one day is 1.00) as
        long as all times are given in the same format.

    Returns
    -------
    v2 : `~numpy.ndarray` (3)
        Velocity of object at position r2 at time t2 in units of AU per day.
    """
    t31 = t3 - t1
    t32 = t3 - t2
    t21 = t2 - t1

    v2 = (-t32 * (1 / (t21 * t31) + MU / (12 * np.linalg.norm(r1)**3)) * r1
          + (t32 - t21) * (1 / (t21 * t32) + MU / (12 * np.linalg.norm(r2)**3)) * r2
          + t21 * (1 / (t32 * t31) + MU / (12 * np.linalg.norm(r3)**3)) * r3)
    return v2


"""Simulates a 2 Poisson Process Distribution."""

import numpy as np
import pandas as pd
import bokeh_catplot
import bokeh.io

rg = np.random.default_rng()

def generate_samples_2pp(beta1, beta2, n_samples=1):
    """
    Generates n_samples samples according to the 2 Poisson Process Distribution.
    Parameters:
    beta1: Rate of the first event.
    beta2: Rate of the second event.
    n_samples: The number of samples to generate.
    """
    times = []
    for i in range(n_samples):
        time = rg.exponential(1 / beta1) + rg.exponential(1 / beta2)
        times.append(time)
    return times

def generate_samples_gamma(a, b, n_samples=1):
    """
    Draws samples from a gamma distribution

    Parameters:
    a: The alpha term.
    b: The beta term (rate).
    size: The number of samples to draw
    """
    times = np.empty(n_samples)
    for i in range(n_samples):
        times[i] = rg.gamma(a, scale=1/b)
    return times

def create_generated_ecdfs(beta_1, beta_2, n_samples=150):
    """
    Creates a plot containing all generated ECDFs from the 2 Poisson Process
    Distribution. It holds beta 1 constant.
    Parameters:
    beta1: An integer representing beta 1.
    beta2: A list of integers representing beta 2.
    n_samples: The number of samples to generate each time.
    """

    times = {'times': [], 'beta 2': []}

    for val in beta_2:
        sampled_times = generate_samples_2pp(beta_1, val, n_samples=n_samples)
        beta_2_array = [val] * n_samples

        times['times'] += sampled_times
        times['beta 2'] += beta_2_array

    df = pd.DataFrame(data=times)
    df.head()

    p = bokeh_catplot.ecdf(
        data=df,
        cats=['beta 2'],
        val='times',
        style='staircase',
        title='Generated ECDFs for Differing Values of β'
    )

    p.legend.location = 'bottom_right'

    return p

def cdf_points(t, beta_1, beta_2):
    '''
    Gets the points for a given analytical CDF.
    Parameters:
    t: An array of t-values,
    beta_1: A beta 1 value
    beta_2: A beta 2 value.
    It returns an array of the CDF values.
    '''
    ratio = (beta_1 * beta_2) / (beta_2 - beta_1)

    term1 = (1 / beta_1) * (1 - np.exp(-beta_1 * t))
    term2 = (1 / beta_2) * (1 - np.exp(-beta_2 * t))

    return ratio * (term1 - term2)

def theo_empirical_cdf(beta_1, beta_2, n_samples=150):
    """
    Overlays empirical and theoretical ECDFs for the 2 Poisson Process Distribution.
    Parameters:
    beta_1: The beta 1 value
    beta_2: The beta 2 value.
    n_samples: The number of samples to generate.
    """
    # Gets our analytical ECDF
    times = np.arange(0, 20, .1)
    prob_times = cdf_points(times, beta_1, beta_2)

    t = {'times': [], 'beta 2': []}

    sampled_times = generate_samples_2pp(beta_1, beta_2, n_samples=n_samples)
    beta_2_array = [beta_2] * n_samples

    t['times'] += sampled_times
    t['beta 2'] += beta_2_array

    df = pd.DataFrame(data=t)
    df['label'] = 'Empirical CDF'

    p = bokeh_catplot.ecdf(
        data=df,
        cats='label',
        val='times',
        style='staircase',
        show_legend=True,
        conf_int=True,
        title='Empirical and Theoretical CDFs'
    )
    p.circle(
        x=times,
        y=prob_times,
        size=2,
        color='orange',
        legend='Theoretical CDF',
    )

    p.yaxis.axis_label = 'CDF value'

    return p


# -*- coding: utf-8 -*-
"""
TRABAJO 2
Nombre Estudiante: Alejandro Palencia Blanco
"""
import numpy as np
import matplotlib.pyplot as plt

# Fijamos la semilla
np.random.seed(1)

def simula_unif(N, dim, rango):
	return np.random.uniform(rango[0],rango[1],(N,dim))

def simula_gauss(N, dim, sigma):
    media = 0
    out = np.zeros((N,dim),np.float64)
    for i in range(N):
        # Para cada columna dim se emplea un sigma determinado. Es decir, para
        # la primera columna (eje X) se usará una N(0,sqrt(sigma[0]))
        # y para la segunda (eje Y) N(0,sqrt(sigma[1]))
        out[i,:] = np.random.normal(loc=media, scale=np.sqrt(sigma), size=dim)

    return out

def simula_recta(intervalo):
    points = np.random.uniform(intervalo[0], intervalo[1], size=(2, 2))
    x1 = points[0,0]
    x2 = points[1,0]
    y1 = points[0,1]
    y2 = points[1,1]
    # y = a*x + b
    a = (y2-y1)/(x2-x1) # Calculo de la pendiente.
    b = y1 - a*x1       # Calculo del termino independiente.

    return a, b

# EJERCICIO 1.1: Dibujar una gráfica con la nube de puntos de salida correspondiente

x_unif = simula_unif(50, 2, [-50,50])

plt.scatter(x_unif[:,0], x_unif[:,1])
plt.title("Nube de puntos uniforme")
plt.show()

input("\n--- Pulsar tecla para continuar ---\n")

x_gauss = simula_gauss(50, 2, np.array([5,7]))

plt.scatter(x_gauss[:,0], x_gauss[:,1])
plt.title("Nube de puntos gaussiana")
plt.show()

input("\n--- Pulsar tecla para continuar ---\n")

###############################################################################
###############################################################################
###############################################################################

# EJERCICIO 1.2: Dibujar una gráfica con la nube de puntos de salida correspondiente

# La funcion np.sign(0) da 0, lo que nos puede dar problemas
def signo(x):
	if x >= 0:
		return 1
	return -1

def f(x, y, a, b):
	return signo(y - a*x - b)

# Función que dibuja una nube de puntos etiquetada junto con una recta en los límites especificados
def dibujar_nube_etiq_con_recta(x, y, a, b, etiq1, etiq1neg, x_inf, x_sup, y_inf, y_sup, title=''):
    x_label1 = x[np.where(y == 1)]
    plt.scatter(x_label1[:,0], x_label1[:,1], c="blue", s=5, label=etiq1)
    x_label1neg = x[np.where(y == -1)]
    plt.scatter(x_label1neg[:,0], x_label1neg[:,1], c="red", s=5, label=etiq1neg)
    t = np.linspace(x_inf,x_sup,2)
    plt.plot(t, a*t+b, c="green")
    plt.ylim(y_inf,y_sup)
    plt.title(title)
    plt.legend()
    plt.show()

a, b = simula_recta([-50,50])
x = simula_unif(100, 2, [-50,50])
y = np.array([f(x[i][0],x[i][1],a,b) for i in range(x.shape[0])])

dibujar_nube_etiq_con_recta(x, y, a, b, 1, -1, -50, 50, -50, 50,
                            "Nube de puntos uniforme etiquetada")

input("\n--- Pulsar tecla para continuar ---\n")

# 1.2.b. Dibujar una gráfica donde los puntos muestren el resultado de su etiqueta, junto con la recta usada para ello
# Array con 10% de indices aleatorios para introducir ruido

# Para cada etiqueta, tomo los índices de los elementos con esa etiqueta,
#   los barajo y cambio el signo de la primera décima parte de ellos
y_ruido = np.copy(y)
for label in (1,-1):
    elem_label = np.where(y_ruido == label)[0]
    for elem in np.random.permutation(elem_label)[0:elem_label.size//10+1]:
        y_ruido[elem] *= -1

dibujar_nube_etiq_con_recta(x, y_ruido, a, b, 1, -1, -50, 50, -50, 50,
                            "Nube de puntos uniforme etiquetada con un 10% de ruido")

input("\n--- Pulsar tecla para continuar ---\n")

###############################################################################
###############################################################################
###############################################################################

# EJERCICIO 1.3: Supongamos ahora que las siguientes funciones definen la frontera de clasificación de los puntos de la muestra en lugar de una recta

def plot_datos_cuad(X, y, fz, title='Point cloud plot', xaxis='x axis', yaxis='y axis'):
    #Preparar datos
    min_xy = X.min(axis=0)
    max_xy = X.max(axis=0)
    border_xy = (max_xy-min_xy)*0.01

    #Generar grid de predicciones
    xx, yy = np.mgrid[min_xy[0]-border_xy[0]:max_xy[0]+border_xy[0]+0.001:border_xy[0],
                      min_xy[1]-border_xy[1]:max_xy[1]+border_xy[1]+0.001:border_xy[1]]
    grid = np.c_[xx.ravel(), yy.ravel(), np.ones_like(xx).ravel()]
    pred_y = fz(grid)
    # pred_y[(pred_y>-1) & (pred_y<1)]
    pred_y = np.clip(pred_y, -1, 1).reshape(xx.shape)

    #Plot
    f, ax = plt.subplots(figsize=(8, 6))
    contour = ax.contourf(xx, yy, pred_y, 50, cmap='RdBu',vmin=-1, vmax=1)
    ax_c = f.colorbar(contour)
    ax_c.set_label('$f(x, y)$')
    ax_c.set_ticks([-1, -0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.75, 1])
    ax.scatter(X[:, 0], X[:, 1], c=y, s=50, linewidth=1,
                cmap="RdYlBu", edgecolor='white')

    XX, YY = np.meshgrid(np.linspace(round(min(min_xy)), round(max(max_xy)),X.shape[0]),np.linspace(round(min(min_xy)), round(max(max_xy)),X.shape[0]))
    positions = np.vstack([XX.ravel(), YY.ravel()])
    ax.contour(XX,YY,fz(positions.T).reshape(X.shape[0],X.shape[0]),[0], colors='black')

    ax.set(
       xlim=(min_xy[0]-border_xy[0], max_xy[0]+border_xy[0]),
       ylim=(min_xy[1]-border_xy[1], max_xy[1]+border_xy[1]),
       xlabel=xaxis, ylabel=yaxis)
    plt.title(title)
    plt.show()

def evaluate_grid(f, grid):
    z = np.zeros(len(grid))
    for i in range(len(grid)):
        z[i] = f(grid[i][0],grid[i][1])
    return z

def f1(x,y):
    return (x-10)**2 + (y-20)**2 - 400

def f2(x,y):
    return 0.5*(x+10)**2 + (y-20)**2 - 400

def f3(x,y):
    return 0.5*(x-10)**2 - (y+20)**2 - 400

def f4(x,y):
    return y - 20*x**2 - 5*x + 3

def f1_grid(grid):
    return evaluate_grid(f1, grid)

def f2_grid(grid):
    return evaluate_grid(f2, grid)

def f3_grid(grid):
    return evaluate_grid(f3, grid)

def f4_grid(grid):
    return evaluate_grid(f4, grid)

# Error de clasificación
def err_accuracy(x, y, f):
    acc = 0.0
    for i in range(y.size):
        if signo(f(x[i][0],x[i][1])) != y[i]:
            acc += 1
    return acc/y.size

plot_datos_cuad(x,y_ruido,f1_grid,'Clasificación realizada por f1 (circunferencia)')
print('Accuracy f1 (circunferencia):', err_accuracy(x,y,f1))

input("\n--- Pulsar tecla para continuar ---\n")

plot_datos_cuad(x,y_ruido,f2_grid,'Clasificación realizada por f2 (elipse)')
print('Accuracy f2 (elipse):', err_accuracy(x,y,f2))

input("\n--- Pulsar tecla para continuar ---\n")

plot_datos_cuad(x,y_ruido,f3_grid,'Clasificación realizada por f3 (hipérbola)')
print('Accuracy f3 (hipérbola):', err_accuracy(x,y,f3))

input("\n--- Pulsar tecla para continuar ---\n")

plot_datos_cuad(x,y_ruido,f4_grid,'Clasificación realizada por f4 (parábola)')
print('Accuracy f4 (parábola):', err_accuracy(x,y,f4))

input("\n--- Pulsar tecla para continuar ---\n")

###############################################################################
###############################################################################
###############################################################################

# EJERCICIO 2.1: ALGORITMO PERCEPTRON

# Algoritmo Perceptrón
def ajusta_PLA(datos, label, max_iter, vini):
    w = np.copy(vini)
    w.astype(np.float64)
    iterations = 0
    change = True
    while change and iterations < max_iter:
        change = False
        i = 0
        while i < label.size and iterations < max_iter:
            if signo(np.dot(w,datos[i])) != label[i]:
                w += label[i]*datos[i]
                change = True
            i += 1
            iterations += 1
    return w, iterations

max_iter = 100000
datos = np.concatenate((np.ones((x.shape[0],1),np.float64),x), axis=1)
label = y

print('PERCEPTRÓN (sobre datos sin ruido)')

# Null vector initialization
w, iterations = ajusta_PLA(datos, label, max_iter, np.zeros(3))

print('Iteraciones necesarias para converger (vector inicial nulo): {}'.format(iterations))

# Random initializations
iterations = []
for i in range(0,10):
    vini = np.random.uniform(0,1,3)
    w, it = ajusta_PLA(datos, label, max_iter, vini)
    iterations.append(it)
    print("vini:",vini,"\titerations:",it)

print('Valor medio de iteraciones necesario para converger (vectores iniciales aleatorios): {}'.format(np.mean(np.asarray(iterations))))

input("\n--- Pulsar tecla para continuar ---\n")

# Ahora con los datos del ejercicio 1.2.b
print('PERCEPTRÓN (sobre datos con ruido)')

max_iter = 100000
datos = np.concatenate((np.ones((x.shape[0],1),np.float64),x), axis=1)
label = y_ruido

# Null vector initialization
w, iterations = ajusta_PLA(datos, label, max_iter, np.zeros(3))

print('Iteraciones necesarias para converger (vector inicial nulo): {}'.format(iterations))

# Random initializations
iterations = []
for i in range(0,10):
    vini = np.random.uniform(0,1,3)
    w, it = ajusta_PLA(datos, label, max_iter, vini)
    iterations.append(it)
    print("vini:",vini,"\titerations:",it)

print('Valor medio de iteraciones necesario para converger (vectores iniciales aleatorios): {}'.format(np.mean(np.asarray(iterations))))

input("\n--- Pulsar tecla para continuar ---\n")

###############################################################################
###############################################################################
###############################################################################

# EJERCICIO 2.2: REGRESIÓN LOGÍSTICA CON STOCHASTIC GRADIENT DESCENT
print('REGRESIÓN LOGÍSTICA')

# Función que clasifica el item xi con la etiqueta 1 si P[yi=1/xi] >= 0.5
#    y con la etiqueta -1 en caso contrario
def clasificadorRL(w, xi):
    exp_wxi = np.exp(np.dot(w,xi))
    return signo(exp_wxi/(1+exp_wxi)-0.5)

# Algoritmo SGD adaptado a Regresión Logística
def sgdRL(x, y, w0, lr, error, batch_size, max_epochs):
    w = np.copy(w0)
    w.astype(np.float64)
    epoch = 0
    diff_w = error
    while diff_w >= error and epoch < max_epochs:
        epoch += 1
        w_prev = w.copy()
        minibatches = np.split(np.random.permutation(np.arange(y.size)),
                               range(batch_size,y.size,batch_size))
        for mb in minibatches:
            grad = np.zeros(w.size, np.float64)
            for i in mb:
                grad = grad + (y[i]/(1+np.exp(y[i]*np.dot(w,x[i]))))*x[i]
            w = w + (lr/mb.size)*grad
        diff_w = np.linalg.norm(w-w_prev)
    return w, epoch

# Error de entropía cruzada
def errRL(x, y, w):
    return np.mean(np.log(1+np.exp(-y*np.matmul(x,w))))

# Error de clasificación aplicado a Regresión Logística
def errAccuracyRL(x, y, w):
    acc = 0
    for i in range(y.size):
        if clasificadorRL(w,x[i]) != y[i]:
            acc += 1
    return acc/y.size

lr = 0.01
error = 0.01
batch_size = 1
max_epochs = 10000

v_epochs = []
v_E_in = []
v_E_test = []
v_E_acc_in = []
v_E_acc_test = []

print('(la ejecución de los 100 experimentos me tarda entre 1 y 2 minutos)')
for rep in range(100):
    # Genero muestra uniforme con tamaño N=100 sobre el cuadrado [0,2]x[0,2]
    #   etiquetada a partir de una recta con ecuación y = a*x + b
    a, b = simula_recta([0,2])
    x = simula_unif(100, 2, [0,2])
    x = np.concatenate((np.ones((x.shape[0],1),np.float64),x), axis=1)
    y = np.array([f(x[i][0],x[i][1],a,b) for i in range(x.shape[0])])

    # Aplico regresión logística
    w0 = np.zeros(x.shape[1])
    w, epochs = sgdRL(x, y, w0, lr, error, batch_size, max_epochs)
    v_epochs.append(epochs)

    # Calculo E_in y Acc_in
    v_E_in.append(errRL(x, y, w))
    v_E_acc_in.append(errAccuracyRL(x, y, w))

    # Genero muestra uniforme con tamaño N=1000 análoga a la anterior
    x = simula_unif(1000, 2, [0,2])
    x = np.concatenate((np.ones((x.shape[0],1),np.float64),x), axis=1)
    y = np.array([f(x[i][0],x[i][1],a,b) for i in range(x.shape[0])])

    # Calculo E_test y Acc_test
    v_E_test.append(errRL(x, y, w))
    v_E_acc_test.append(errAccuracyRL(x, y, w))

# Obtengo promedios
print('Valor medio de E_in: {}'.format(np.mean(np.asarray(v_E_in))))
print('Valor medio de E_test: {}'.format(np.mean(np.asarray(v_E_test))))
print('Valor medio de E_acc_in: {}'.format(np.mean(np.asarray(v_E_acc_in))))
print('Valor medio de E_acc_test: {}'.format(np.mean(np.asarray(v_E_acc_test))))
print('Valor medio de épocas necesarias para converger: {}'.format(np.mean(np.asarray(v_epochs))))

input("\n--- Pulsar tecla para continuar ---\n")

###############################################################################
###############################################################################
###############################################################################

#BONUS: Clasificación de Dígitos
print('BONUS')

# Funcion para leer los datos
def readData(file_x, file_y, digits, labels):
	# Leemos los ficheros
	datax = np.load(file_x)
	datay = np.load(file_y)
	y = []
	x = []
	# Solo guardamos los datos cuya clase sea la digits[0] o la digits[1]
	for i in range(0,datay.size):
		if datay[i] == digits[0] or datay[i] == digits[1]:
			if datay[i] == digits[0]:
				y.append(labels[0])
			else:
				y.append(labels[1])
			x.append(np.array([1, datax[i][0], datax[i][1]]))

	x = np.array(x, np.float64)
	y = np.array(y, np.float64)

	return x, y

# Lectura de los datos de entrenamiento
x, y = readData('datos/X_train.npy', 'datos/y_train.npy', [4,8], [-1,1])
# Lectura de los datos para el test
x_test, y_test = readData('datos/X_test.npy', 'datos/y_test.npy', [4,8], [-1,1])

#mostramos los datos
fig, ax = plt.subplots()
ax.scatter(np.squeeze(x[np.where(y == 1),1]), np.squeeze(x[np.where(y == 1),2]), c='blue', s=5, label='8')
ax.scatter(np.squeeze(x[np.where(y == -1),1]), np.squeeze(x[np.where(y == -1),2]), c='red', s=5, label='4')
ax.set(xlabel='Intensidad promedio', ylabel='Simetria', title='Digitos Manuscritos (TRAINING)')
ax.set_xlim((0, 1))
plt.legend()
plt.show()

input("\n--- Pulsar tecla para continuar ---\n")

fig, ax = plt.subplots()
ax.scatter(np.squeeze(x_test[np.where(y_test == 1),1]), np.squeeze(x_test[np.where(y_test == 1),2]), c='blue', s=5, label='8')
ax.scatter(np.squeeze(x_test[np.where(y_test == -1),1]), np.squeeze(x_test[np.where(y_test == -1),2]), c='red', s=5, label='4')
ax.set(xlabel='Intensidad promedio', ylabel='Simetria', title='Digitos Manuscritos (TEST)')
ax.set_xlim((0, 1))
plt.legend()
plt.show()

input("\n--- Pulsar tecla para continuar ---\n")

# Función de error para Regresión Lineal (error cuadrático medio)
def errLinReg(x,y,w):
    return np.mean((np.matmul(x,w)-y)**2)

# Función de error para Pocket (fracción de elementos mal clasificados)
def errPocket(x, y, w):
    err = 0
    for i in range(y.size):
        if signo(np.dot(w,x[i])) != y[i]:
            err += 1
    return err/y.size

#LINEAR REGRESSION FOR CLASSIFICATION

# Gradiente Descendente Estocástico para Regresión Lineal
def sgdLinReg(x, y, w0, lr, error, batch_size, max_epochs):
    w = np.copy(w0)
    w.astype(np.float64)
    epoch = 0
    while errLinReg(x,y,w) >= error and epoch < max_epochs:
        epoch += 1
        minibatches = np.split(np.random.permutation(np.arange(y.size)),
                               range(batch_size,y.size,batch_size))
        for mb in minibatches:
            w_aux = np.copy(w)
            for k in range(w.size):
                w[k] -= lr*(2/mb.size)*np.dot(np.matmul(x[mb,:],w_aux)-y[mb], x[mb,k])
    return w

w0 = np.zeros(x.shape[1])
lr = 0.01
error = 0.001
batch_size = 50
max_epochs = 1000

w_linreg = sgdLinReg(x, y, w0, lr, error, batch_size, max_epochs)
E_in_reglin = errLinReg(x,y,w_linreg)
E_test_reglin = errLinReg(x_test,y_test,w_linreg)

print('E_in para Regresión Lineal:', E_in_reglin)
print('E_acc_in para Regresión Lineal:', errPocket(x,y,w_linreg))
dibujar_nube_etiq_con_recta(x[:,1:], y, -w_linreg[1]/w_linreg[2], -w_linreg[0]/w_linreg[2], 8, 4, 0, 1, -7, 0,
                            'Solución de Regresión Lineal sobre la nube de puntos de entrenamiento')

input("\n--- Pulsar tecla para continuar ---\n")

print('E_test para Regresión Lineal:', E_test_reglin)
print('E_acc_test para Regresión Lineal:', errPocket(x_test,y_test,w_linreg))
dibujar_nube_etiq_con_recta(x_test[:,1:], y_test, -w_linreg[1]/w_linreg[2], -w_linreg[0]/w_linreg[2], 8, 4, 0, 1, -7, 0,
                            'Solución de Regresión Lineal sobre la nube de puntos de test')

input("\n--- Pulsar tecla para continuar ---\n")

#POCKET ALGORITHM

# Algoritmo Pocket
def pocket(x, y, w0, max_iter):
    w = np.copy(w0)
    w.astype(np.float64)
    w_best = np.copy(w)
    ein_best = errPocket(x,y,w_best)
    iterations = 0
    change = True
    while change and iterations < max_iter:
        change = False
        i = 0
        while i < y.size and iterations < max_iter:
            if signo(np.dot(w,x[i])) != y[i]:
                w += y[i]*x[i]
                change = True
                ein = errPocket(x,y,w)
                if ein < ein_best:
                    w_best = w
                    ein_best = ein
            i += 1
            iterations += 1
    return w_best

w0 = np.zeros(x.shape[1])
max_iter = 10000

w_pocket = pocket(x, y, w0, max_iter)
E_in_pocket = errPocket(x,y,w_pocket)
E_test_pocket = errPocket(x_test,y_test,w_pocket)

print('E_in para Pocket:', E_in_pocket)
dibujar_nube_etiq_con_recta(x[:,1:], y, -w_pocket[1]/w_pocket[2], -w_pocket[0]/w_pocket[2], 8, 4, 0, 1, -7, 0,
                            'Solución de Pocket sobre la nube de puntos de entrenamiento')

input("\n--- Pulsar tecla para continuar ---\n")

print('E_test para Pocket:', E_test_pocket)
dibujar_nube_etiq_con_recta(x_test[:,1:], y_test, -w_pocket[1]/w_pocket[2], -w_pocket[0]/w_pocket[2], 8, 4, 0, 1, -7, 0,
                            'Solución de Pocket sobre la nube de puntos de test')

input("\n--- Pulsar tecla para continuar ---\n")

# LINEAR REGRESSION + POCKET ALGORITHM

max_iter = 1000

w_lr_p = pocket(x, y, w_linreg, max_iter)
E_in_lr_p = errPocket(x,y,w_lr_p)
E_test_lr_p = errPocket(x_test,y_test,w_lr_p)

print('E_in para Reg. Lin. + Pocket:', E_in_lr_p)
dibujar_nube_etiq_con_recta(x[:,1:], y, -w_lr_p[1]/w_lr_p[2], -w_lr_p[0]/w_lr_p[2], 8, 4, 0, 1, -7, 0,
                            'Solución de Reg. Lin. + Pocket sobre la nube de puntos de entrenamiento')

input("\n--- Pulsar tecla para continuar ---\n")

print('E_test para Reg. Lin. + Pocket:', E_test_lr_p)
dibujar_nube_etiq_con_recta(x_test[:,1:], y_test, -w_lr_p[1]/w_lr_p[2], -w_lr_p[0]/w_lr_p[2], 8, 4, 0, 1, -7, 0,
                            'Solución de Reg. Lin. + Pocket sobre la nube de puntos de test')

input("\n--- Pulsar tecla para continuar ---\n")

#COTA SOBRE EL ERROR

d_vc = 3
delta = 0.05

epsilon_in = np.sqrt( (8/y.size)*np.log(4*((2*y.size)**d_vc+1)/delta) )
epsilon_test = np.sqrt( 1/(2*y_test.size)*np.log(2/delta) )

print('Cota de E_out para Regresión Lineal basada en E_in:\n\tE_out <=',E_in_reglin,'+',epsilon_in,'=',E_in_reglin+epsilon_in)
print('Cota de E_out para Regresión Lineal basada en E_test:\n\tE_out <=',E_test_reglin,'+',epsilon_test,'=',E_test_reglin+epsilon_test)
print()

print('Cota de E_out para Pocket basada en E_in:\n\tE_out <=',E_in_pocket,'+',epsilon_in,'=',E_in_pocket+epsilon_in)
print('Cota de E_out para Pocket basada en E_test:\n\tE_out <=',E_test_pocket,'+',epsilon_test,'=',E_test_pocket+epsilon_test)
print()

print('Cota de E_out para Reg. Lin. + Pocket basada en E_in:\n\tE_out <=',E_in_lr_p,'+',epsilon_in,'=',E_in_lr_p+epsilon_in)
print('Cota de E_out para Reg. Lin. + Pocket basada en E_test:\n\tE_out <=',E_test_lr_p,'+',epsilon_test,'=',E_test_lr_p+epsilon_test)
print()


# PDF says how many points exists in particular range

# Using CDF we shall visually see what % of versicolor flowers have petal_length of less than 2 ?
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np

iris = pd.read_csv("iris.csv")

iris_setosa = iris.loc[iris["species"] == "setosa"];
iris_virginica = iris.loc[iris["species"] == "virginica"];
iris_versicolor = iris.loc[iris["species"] == "versicolor"];

counts, bin_edges = np.histogram(iris_setosa['petal_length'], bins=10,
                                 density = True)
pdf = counts/(sum(counts))
print(pdf);
print(bin_edges);
cdf = np.cumsum(pdf)
plt.plot(bin_edges[1:],pdf);
plt.plot(bin_edges[1:], cdf)

counts, bin_edges = np.histogram(iris_setosa['petal_length'], bins=20,
                                 density = True)
pdf = counts/(sum(counts))
plt.plot(bin_edges[1:],pdf);

plt.show();

# CDF --> cummulative sum of histogram
# CDF says, if Petal length x-axis value is 1.6 then
# roughly 82% of setosa flower exists
# CDF scale from 0 to 1, meaning % of the flower
# CDF can be used to compute accuracy in case of if else modelling

# Plots of CDF of petal_length for various types of flowers.

counts, bin_edges = np.histogram(iris_setosa['petal_length'], bins=10,
                                 density = True)
pdf = counts/(sum(counts))
print(pdf);
print(bin_edges)
cdf = np.cumsum(pdf)
plt.plot(bin_edges[1:],pdf)
plt.plot(bin_edges[1:], cdf)

# virginica
counts, bin_edges = np.histogram(iris_virginica['petal_length'], bins=10,
                                 density = True)
pdf = counts/(sum(counts))
print(pdf);
print(bin_edges)
cdf = np.cumsum(pdf)
plt.plot(bin_edges[1:],pdf)
plt.plot(bin_edges[1:], cdf)

#versicolor
counts, bin_edges = np.histogram(iris_versicolor['petal_length'], bins=10,
                                 density = True)
pdf = counts/(sum(counts))
print(pdf);
print(bin_edges)
cdf = np.cumsum(pdf)
plt.plot(bin_edges[1:],pdf)
plt.plot(bin_edges[1:], cdf)

plt.show();

# Misclassification error if you use petal_length only.


#!/usr/bin/env python

import numpy as np

def spiral(n):
    dx, dy = 0, -1
    x, y = n-1, n-1
    array = np.zeros([n, n], int)
    for i in range(1, n ** 2+1)[::-1]:
        array[x][y] = i
        nx, ny = x + dx, y + dy
        if 0 <= nx < n and 0 <= ny < n and array[nx][ny] == 0:
            x, y = nx, ny
        else:
            dx, dy = dy,-dx
            x, y = x + dx, y + dy
    return array

def printspiral(myarray):
    print
    n = range(len(myarray))
    for x in n:
        for y in n:
            print "%2i" % myarray[x][y],
        print

def test_spiral():
    assert printspiral(spiral(7)) == None

def isprime(n):
    if n < 2 or n % 2 == 0:
        return False

    for d in xrange(2, int(np.sqrt(n))+1):
        if n % d == 0:
            return False

    return True

def test_isprime():
    assert isprime(4) == False
    assert isprime(17) == True

def primeratio(array):
    counter = 0
    for d in np.diag(array):
        if isprime(d):
            counter += 1
    for d in np.diag(array[:, ::-1]):
        if isprime(d):
            counter += 1
    return counter * 1.0 / (2 * np.shape(array)[0] - 1)

def test_primeratio():
    assert abs(primeratio(spiral(7)) - 0.62) < 0.01

if __name__ == '__main__':
    n = 7
    while True:
        n += 2
        r = primeratio(spiral(n))
        print n, r
        if r < 0.1:
            print n
            break


#Hash functions and multiplanes

import numpy as np                # library for array and matrix manipulation
import pprint                     # utilities for console printing
from utils_nb import plot_vectors # helper function to plot vectors
import matplotlib.pyplot as plt   # visualization library

pp = pprint.PrettyPrinter(indent=4) # Instantiate a pretty printer
def basic_hash_table(value_l, n_buckets):

    def hash_function(value, n_buckets):
        return int(value) % n_buckets

    hash_table = {i:[] for i in range(n_buckets)} # Initialize all the buckets in the hash table as empty lists

    for value in value_l:
        hash_value = hash_function(value,n_buckets) # Get the hash key for the given value
        hash_table[hash_value].append(value) # Add the element to the corresponding bucket

    return hash_table

value_l = [100, 10, 14, 17, 97] # Set of values to hash
hash_table_example = basic_hash_table(value_l, n_buckets=10)
pp.pprint(hash_table_example)
P = np.array([[1, 1]]) # Define a single plane.
fig, ax1 = plt.subplots(figsize=(8, 8)) # Create a plot

plot_vectors([P], axes=[2, 2], ax=ax1) # Plot the plane P as a vector

# Plot  random points.
for i in range(0, 10):
        v1 = np.array(np.random.uniform(-2, 2, 2)) # Get a pair of random numbers between -4 and 4
        side_of_plane = np.sign(np.dot(P, v1.T))

        # Color the points depending on the sign of the result of np.dot(P, point.T)
        if side_of_plane == 1:
            ax1.plot([v1[0]], [v1[1]], 'bo') # Plot blue points
        else:
            ax1.plot([v1[0]], [v1[1]], 'ro') # Plot red points

plt.show()

P = np.array([[1, 2]])  # Define a single plane. You may change the direction

# Get a new plane perpendicular to P. We use a rotation matrix
PT = np.dot([[0, 1], [-1, 0]], P.T).T

fig, ax1 = plt.subplots(figsize=(8, 8)) # Create a plot with custom size

plot_vectors([P], colors=['b'], axes=[2, 2], ax=ax1) # Plot the plane P as a vector

# Plot the plane P as a 2 vectors.
# We scale by 2 just to get the arrows outside the current box
plot_vectors([PT * 4, PT * -4], colors=['k', 'k'], axes=[4, 4], ax=ax1)

# Plot 20 random points.
for i in range(0, 20):
        v1 = np.array(np.random.uniform(-4, 4, 2)) # Get a pair of random numbers between -4 and 4
        side_of_plane = np.sign(np.dot(P, v1.T)) # Get the sign of the dot product with P
        # Color the points depending on the sign of the result of np.dot(P, point.T)
        if side_of_plane == 1:
            ax1.plot([v1[0]], [v1[1]], 'bo') # Plot a blue point
        else:
            ax1.plot([v1[0]], [v1[1]], 'ro') # Plot a red point

plt.show()

P = np.array([[1, 1]])      # Single plane
v1 = np.array([[1, 2]])     # Sample point 1
v2 = np.array([[-1, 1]])    # Sample point 2
v3 = np.array([[-2, -1]])   # Sample point 3

np.dot(P, v1.T)
np.dot(P, v2.T)
np.dot(P, v3.T)

def side_of_plane(P, v):
    dotproduct = np.dot(P, v.T) # Get the dot product P * v'
    sign_of_dot_product = np.sign(dotproduct) # The sign of the elements of the dotproduct matrix
    sign_of_dot_product_scalar = sign_of_dot_product.item() # The value of the first item
    return sign_of_dot_product_scalar

side_of_plane(P, v1) # In which side is [1, 2]
side_of_plane(P, v2) # In which side is [-1, 1]
side_of_plane(P, v3) # In which side is [-2, -1]

P1 = np.array([[1, 1]])   # First plane 2D
P2 = np.array([[-1, 1]])  # Second plane 2D
P3 = np.array([[-1, -1]]) # Third plane 2D
P_l = [P1, P2, P3]  # List of arrays. It is the multi plane

# Vector to search
v = np.array([[2, 2]])

def hash_multi_plane(P_l, v):
    hash_value = 0
    for i, P in enumerate(P_l):
        sign = side_of_plane(P,v)
        hash_i = 1 if sign >=0 else 0
        hash_value += 2**i * hash_i
    return hash_value

np.random.seed(0)
num_dimensions = 2 # is 300 in assignment
num_planes = 3 # is 10 in assignment
random_planes_matrix = np.random.normal(
                       size=(num_planes,
                             num_dimensions))
print(random_planes_matrix)

v = np.array([[2, 2]])

# Side of the plane function. The result is a matrix
def side_of_plane_matrix(P, v):
    dotproduct = np.dot(P, v.T)
    sign_of_dot_product = np.sign(dotproduct) # Get a boolean value telling if the value in the cell is positive or negative
    return sign_of_dot_product

sides_l = side_of_plane_matrix(
            random_planes_matrix, v)
sides_l

def hash_multi_plane_matrix(P, v, num_planes):
    sides_matrix = side_of_plane_matrix(P, v) # Get the side of planes for P and v
    hash_value = 0
    for i in range(num_planes):
        sign = sides_matrix[i].item() # Get the value inside the matrix cell
        hash_i = 1 if sign >=0 else 0
        hash_value += 2**i * hash_i # sum 2^i * hash_i

    return hash_value

hash_multi_plane_matrix(random_planes_matrix, v, num_planes)

word_embedding = {"I": np.array([1,0,1]),
                   "love": np.array([-1,0,1]),
                   "learning": np.array([1,0,1])
                  }
words_in_document = ['I', 'love', 'learning', 'not_a_word']
document_embedding = np.array([0,0,0])
for word in words_in_document:
    document_embedding += word_embedding.get(word,0)

print(document_embedding)


## Sum of an array
print("==========Answer 1 Sum of an array using recursive========")
#
#This function will accept an array numbers. this function will return sum of array.
# the function will call itself until the array length is zero
def getSum(a):

    if len(a)==0:
        return 0
    else:
        return a[0] + getSum(a[1:])

num=[100,25,26,78 ,34, 45, 27, 57]

SumofArray=getSum(num)
print('           ')

print("Recursive function SumofArray",num, "is",SumofArray)
print('           ')
print("***********************************************************")

print("==========Answer 1 Sum of an array using function ========")

#The reduce() function accepts a function and a sequence and returns a single value calculated as follows:
from functools import reduce
num=[2,34,67,89,34,56,78]

def sum(a,b):
    return a+b
# first method using function

sumarray= reduce(sum,num)
# Second method using lambda
sumarray1= reduce(lambda a,b:a+b ,num)
print('           ')

print("using Lambda value of sum",num,"is",sumarray)
print('           ')
print("***********************************************************")
print("Using function value of sum",num,"is",sumarray1)
print('           ')
print("==========Answer2 Product of an array========")
#This function will accept an array numbers. this function will return Product of an array.
# The function will call itself until the array length is zero
import numpy as np

def mul(a):
    if len(a)==0:
        return 1
    else:
        return  a[0] * mul(np.array(a[1:]))

num=[10,5,25,2,100,200]

MulofArray=mul(num)
print('           ')

print("Recursive function MulofArray value",num, "is ",MulofArray)

print('           ')
print("***********************************************************")
print('           ')
print("==========Answer2 Product of an array using function========")
def mul(a,b):
    return a*b
# first method using function

mularray= reduce(mul,num)
# Second method using lambda
mularray1= reduce(lambda a,b:a*b ,num)
print('           ')
print("using function value of MulofArray value",num, "is" ,mularray)
print('           ')
print('=================================')
print('           ')
print("using lambda value of MulofArray value",num, "is",mularray1)
print('           ')
print('           ')
print("==========Answer3 Remove all odd numbers========")
#This function will accept an array numbers. this function will return an array after elemeneting the odd numbers.
# The function will call itself until the array length is zero,When the lenth of array is not 0 then the first element of the array
#will be inspected is even or not.Odd numbers
def getodd(a):
    if len(a)==0:
        return[]
    elif  a[0]%2==1: #cheching if the number is odd
        return  [a[0]]+ getodd(a[1:])
    else :
        return getodd(a[1:])

num=[10,1,25,2,101,200]

getoddofArray=getodd(num)
print('           ')
print("Recursive function Get_Odd No Of Array",num, "is",getoddofArray)
print('           ')
print("==========Answer3 Remove all odd numbers using function========")

def oddnum(n):
    return n%2!=0

num=[18,22,34,43,59,6,7,85,97,10,11,12,89,39,14]
# in python we have filter function that will take the value from filter
# first method using lambda
odd=list(filter(lambda n:n%2!=0 ,num))
# Second method using function
odd1 =list(filter(oddnum,num))
print('           ')
print("using lambda returns odd numbers value",num, "are",odd)
print('           ')
print('=================================')
print('           ')
print("using function returns odd numbers value",num,  "are",odd1)
print('           ')
print('           ')
print("==========Answer4 Remove all even numbers========")
#This function will accept an array numbers. this function will return Remove all even numbers.
# The function will call itself until the array length is zero
def geteven(a):
    if len(a)==0:
        return[]
    elif  a[0]%2==0:#cheching if the number is even
        #print(a[1:])
        #print("A is ",a[0] )
        #print("value is ",a[0] * getmul(a[1:]))

        return  [a[0]]+ geteven(a[1:])
    else : #a[0]%2==1:
        return geteven(a[1:])

num=[10,1,25,2,101,200]

getevenofArray=geteven(num)
print('           ')
print("Recursive function Get_even No Of Array",num, "is",getevenofArray)
print('           ')
print("=====================================")
print('           ')
print("==========Answer4 Remove all even numbers using function========")

def evennum(n):
    return n%2==0

num=[18,22,34,43,59,6,7,85,97,10,11,12,89,39,14]
# in python we have filter function that will take the value from filter
# first method using lambda
even=list(filter(lambda n:n%2==0 ,num))
# Second method using function
even1 =list(filter(evennum,num))
print('           ')
print("using lambda returns even numbers value",num ,"are",even)
print('=================================')
print('           ')
print("using function returns even numbers value ",num ,"are",even1)
print('           ')

print("==========Answer5 Replace a given character with '*'========")
print('           ')
#This function will accept an array numbers. this function will return Replace a given character with '*'.
# The function will call itself until the array length is zero
name="adarshasachan"
def remove(r, s):
    if not s:
        return ''
    if r == s[0]:# inspecting the first charactor
        return "*"+ remove(r, s[1:])#replacing with *
    else:
        return s[0] + remove(r, s[1:])#not replacing the first charactor
# examle from name Replaceing a  character with '*'
string=remove('a',name)
print('           ')
print("Recursive function Replacing A  character to * from",name,"is" ,string)

print("=====================================")

print('           ')
print("==========Answer6 Find index in array for item.========")

#This function will accept2 parameter an array and counter. this function will return Find index in array for item..
# The function will call itself until not length is zero
def iarray(arr, cha):
  global counter , inputlen
  counter= counter + 1
  if not arr:
    return -1
  if arr[0] == cha:#checking the frist element of array is same as the string that to be found.
    arr.index(cha)
    return(counter-1)
  elif inputlen == counter-1:#if seeach element is not present the return -1
    return -1
  return iarray(arr[1:], cha)
Input1 = ['hjhj','lpls','tali']
Input2 = 'tali'
counter = 0
inputlen = 0
inputlen = len(Input1)
print('           ')
print("======================================================")
print("   String value :" +  str(Input1))
print("    string value :" +  str(Input2))
print(" Recursive function Find index in array for item in a given String :" + str(iarray(Input1, Input2)))

print("=====================================")

print('           ')
print("==========Answer7 Sum of Digits ========")
#This function will accept an  array. this function will return Sum of Digits.
# The function will call itself until the array length is zero
def sum_digit(n):
    if n > 0:# checking if number is greater than 0
        return sum_digit(n // 10) + n % 10 #
    else:
        return 0
number=14567
sumnumber=sum_digit(number)
print('           ')
print("Recursive function Sum of digit",number,"is",sumnumber)

print("=====================================")
print('           ')
print("==========Answer8 Print an Array. ========")
#This function will accept an  array. this function will return Print an array.
# The function will call itself until the the array length is 1
def Printarray(arr):
     if len(arr)== 1:
        return print(arr[0])
     else:
        print(arr[0])
        return Printarray(arr[1:])
num = [1,2,13,8,5,6]
print('           ')
print("Recursive function Print an Array",num,"is")
Printarray(num)

print("=====================================")
print('           ')
print('           ')
print("==========Answer9 Find the minimum element in an array of integers. ========")
#This function will accept an  array. this function will return inimum element in an array of integers.

def Minelement(a):
    if len(a)==2:

        if a[0]<a[1]:# comparing between two number
            return a[0]
        else:
            return a[1]
    else:
        #print("value of a",a[0])
        #print("value of min ",Minelement(a[1:]))
        if a[0]<Minelement(a[1:]):
            return a[0]
        else:
            return Minelement(a[1:])

num=[15,7,9,4,6,7]
i=Minelement(num)
print('           ')
print(" Recursive function minimum element in an array of integer",num,"is",i)

print("=====================================")
print('           ')
print("==========Answer10 Verify the parentheses ========")
#This function will accept an  array. this function will Verify the parentheses.
# The function will call itself until the array length is zero
def index (counter,a):
    if len(a)==0:
        if counter==0:
            return True
        else:
            return False
    else:
        len (a)< 0
        if a[0] =="(":#if open ( found then increasing the counter
            counter=counter +1
        elif a[0]==")":#if close ) found then decreasing the counter
            counter=counter -1
        return index(counter,a[1:])
counter=0
num="((aaaa(sss()))"
#index(counter,num)
print('           ')
print('           ')
print("Recursive function to Verify the parentheses from :",num,"is .."+ str(index(counter,num)))
print("***************************")
print('           ')
print('           ')
print("END")


from sympy import *

def sympy_derivative():
    # 定义表达式的变量名称
    x1, x2 = symbols('x1 x2')
    # 定义表达式内容
    Y = x1 ** 2 + x2 ** 2
    # 计算 x2对应的偏导数
    return diff(Y, x2)

# sympy 带入值计算
func = sympy_derivative()
print(func)  # 输出结果2*x2
print(func.evalf(subs={'x2': 6}))  # 把x2 等于6 带入计算 结果 为12

# 其中 func.evalf(subs={}) 为特征值带入计算的api，需特殊说明的是，该函数运行结果，可能还只是表达式，其主要是求解已经给的特征值


import numpy as np
import scipy.linalg as sla

class Interpolator(object):

	"""Cubic Hermite Spline Interpolation"""

	def __init__(self, x, y):

		"""
		Public variables: locations (x) and values (y) of knots,
		derivatives (d) at knots, and quadratic (c) and cubic (b)
		term coefficients
		"""

		self.x = x
		self.y = y
		self.__h = x[1:] - x[:-1]
		self.__delta = (y[1:] - y[:-1]) / self.__h
		self.d = self.__set_derivatives(self.__h, self.__delta)
		self.c = (3.0 * self.__delta - 2.0 * self.d[0: -1] - \
				  self.d[1:]) / self.__h
		self.b = (self.d[0: -1] - 2.0 * self.__delta + \
				  self.d[1:]) / self.__h ** 2

	def __set_derivatives(self, h, delta):

		"""
		Calculate derivatives at each knot, using not-a-knot
		formalism (see www.mathworks.com/content/dam/mathworks/
		mathworks-dot-com/moler/interp.pdf)
		"""

		# dimension components
		n_diffs = len(h) + 1
		r_vec = np.zeros(n_diffs)
		a_mat = np.zeros((n_diffs, n_diffs))

		# form delta vector
		r_vec[0] = ((h[0] + 2.0 * (h[0] + h[1])) * h[1] * delta[0] + \
					h[0] ** 2 * delta[1]) / (h[0] + h[1])
		r_vec[1: -1] = 3.0 * (h[1:] * delta[0: -1] + \
							  h[0: -1] * delta[1:])
		r_vec[-1] = (h[-1] ** 2 * delta[-2] + \
					 (2.0 * (h[-2] + h[-1]) + h[-1]) * \
					 h[-2] * delta[-1]) / (h[-2] + h[-1])

		# form dense A matrix
		a_mat[0, 0] = h[1]
		a_mat[0, 1] = h[1] + h[0]
		for i in range(1, n_diffs - 1):
			a_mat[i, i-1] = h[i]
			a_mat[i, i] = 2.0 * (h[i-1] + h[i])
			a_mat[i, i+1] = h[i-1]
		a_mat[-1, -2] = h[-1] + h[-2]
		a_mat[-1, -1] = h[-2]

		# solve system and return
		return np.linalg.solve(a_mat, r_vec)

	def __set_derivatives_banded(self, h, delta):

		"""
		Calculate derivatives at each knot, using not-a-knot
		formalism and forming a banded (not dense) matrix
		"""

		# dimension components
		n_diffs = len(h) + 1
		r_vec = np.zeros(n_diffs)
		a_mat = np.zeros((n_diffs, n_diffs))

		# form delta vector
		r_vec[0] = ((h[0] + 2.0 * (h[0] + h[1])) * h[1] * delta[0] + \
					h[0] ** 2 * delta[1]) / (h[0] + h[1])
		r_vec[1: -1] = 3.0 * (h[1:] * delta[0: -1] + \
							  h[0: -1] * delta[1:])
		r_vec[-1] = (h[-1] ** 2 * delta[-2] + \
					 (2.0 * (h[-2] + h[-1]) + h[-1]) * \
					 h[-2] * delta[-1]) / (h[-2] + h[-1])

		# form banded A matrix
		a_mat_ud = np.concatenate(([0], [h[1] + h[0]], h[0: -1]))
		a_mat_d = np.concatenate(([h[1]], 2.0 * (h[0: -1] + h[1:]), [h[-2]]))
		a_mat_ld = np.concatenate((h[1:], [h[-1] + h[-2]], [0]))
		a_mat_band = np.matrix([a_mat_ud, a_mat_d, a_mat_ld])

		# solve system and return
		return sla.solve_banded((1, 1), a_mat_band, r_vec, \
								overwrite_ab=True, \
								overwrite_b=True)

	def __interpolate_scalar(self, x_int, der=False):

		"""Interpolate an individual point"""

		ind = [i for i, check in enumerate(x_int < self.x) if check][0] - 1
		s = x_int - self.x[ind]
		if der:
			y_int = self.d[ind] + \
					s * (2.0 * self.c[ind] + \
						 s * 3.0 * self.b[ind])
		else:
			y_int = self.y[ind] + \
					s * (self.d[ind] + \
						 s * (self.c[ind] + \
							  s * self.b[ind]))
		return y_int

	def interpolate(self, x_int):

		"""Public interpolation method, accepts scalars or 1D arrays"""

		if isinstance(x_int, np.ndarray):
			if (x_int < self.x[0]).any() or \
			   (x_int > self.x[-1]).any():
				raise ValueError('Requested interpolation out of bounds!')
			n = len(x_int)
			y_int = np.zeros(n)
			for i in range(n):
				y_int[i] = self.__interpolate_scalar(x_int[i])
		else:
			if x_int < self.x[0] or x_int > self.x[-1]:
				raise ValueError('Requested interpolation out of bounds!')
			y_int = self.__interpolate_scalar(x_int)
		return y_int


import matplotlib.pyplot as plt
import numpy as np
import math

def feval(funcName, *args):
    return eval(funcName)(*args)

def RK4thOrder(func, yinit, x_range, h):
    m = len(yinit)
    n = int((x_range[-1] - x_range[0]) / h)

    x = x_range[0]
    y = yinit

    # Containers for solutions
    xsol = np.empty(0)
    xsol = np.append(xsol, x)

    ysol = np.empty(0)
    ysol = np.append(ysol, y)

    for i in range(n):
        k1 = feval(func, x, y)

        yp2 = y + k1 * (h / 2)

        k2 = feval(func, x + h / 2, yp2)

        yp3 = y + k2 * (h / 2)

        k3 = feval(func, x + h / 2, yp3)

        yp4 = y + k3 * h

        k4 = feval(func, x + h, yp4)

        for j in range(m):
            y[j] = y[j] + (h / 6) * (k1[j] + 2 * k2[j] + 2 * k3[j] + k4[j])

        x = x + h
        xsol = np.append(xsol, x)

        for r in range(len(y)):
            ysol = np.append(ysol, y[r])

    return [xsol, ysol]

def myFunc(t, vectorY):
    df = np.zeros((len(vectorY)))

    y = vectorY[0]
    z = vectorY[1]

#########################       INPUT GOES HERE

    # y' = z
    df[0] = z
    # z' = ty^2(1+sin(t)y) + yt^3
    df[1] = t*y**2*(1+math.sin(t)*y) + t**3*y
    return df

h = 0.01    # độ dài 1 bước
t0 = 0      # điểm bắt đầu
tn = 1      # điểm kết thúc

y0 = 1      # giá trị đầu y(t0)
z0 = 1      # giá trị đầu z(t0)

# this work with more than 2 equation ...

##########################       \INPUT GOES HERE

y0 = float(y0)
z0 = float(z0)

x = np.array([t0, tn])
yinit = np.array([y0, z0])

[ts, ys] = RK4thOrder('myFunc', yinit, x, h)

node = len(yinit)
ys1 = ys[0::node]
ys2 = ys[1::node]

##########################       PRINT RESULT

for i in range(len(ts)):
    print("t = ", ts[i])
    print("y1 = ", ys1[i], ";   y2 = ", ys2[i])

##########################       \PRINT RESULT

##########################       Plot y, z with respect to t  ( but currently only y, uncomment plt.plot... below to plot z )

plt.plot(ts, ys1, 'r')
#plt.plot(ts, ys2, 'b')
plt.xlim(x[0], x[1])
plt.legend(["y(1)", "y(2)"], loc=2)
plt.xlabel('x', fontsize=17)
plt.ylabel('y', fontsize=17)
plt.tight_layout()
plt.show()

##########################       \Plot y, z with respect to t

##########################       plot y - z as x - y(x)

plt.plot(ys1, ys2, 'r')
plt.xlabel('con mồi', fontsize=17)
plt.ylabel('thú săn mồi', fontsize=17)
plt.show()

##########################       \plot y1 - y2

# Uncomment the following to print the figure:
#plt.savefig('Fig_ex7_RK4th.png', dpi=600)

Topic 5 - Solving Differential Equations/28.Runge_Kutta/R-K system of equation/RK4.py


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

# =============================================================================
# Loading Modules
# =============================================================================
import numpy as np
from envs.GridWorld import GridworldEnv
import matplotlib.pyplot as pl

# =============================================================================
# Create Environment
# =============================================================================
env = GridworldEnv()

# =============================================================================
# Evaluate Policy
# =============================================================================

def policy_eval(policy, env, discount_factor=1.0, theta=0.00001):
    """
    Evaluate a policy given an environment and a full description of the environment's dynamics.

    Args:
        policy: [S, A] shaped matrix representing the policy.
        env: OpenAI env. env.P represents the transition probabilities of the environment.
            env.P[s][a] is a list of transition tuples (prob, next_state, reward, done).
            env.nS is a number of states in the environment.
            env.nA is a number of actions in the environment.
        theta: We stop evaluation once our value function change is less than theta for all states.
        discount_factor: Gamma discount factor.

    Returns:
        Vector of length env.nS representing the value function.
    """
    # Start with a random (all 0) value function
    V = np.zeros(env.nS)
    k=0
    diff=1
    errors=[]
    historicalCheck=[0,10]
    historical=[]
    while (diff>theta):
        newV=np.zeros(env.nS)
        for s in range(env.nS):
            temp1=0
            for a in range(env.nA):
                temp2=0
                for sp in range(len(env.P[s][a])):
                    transition=env.P[s][a][sp]
                    temp2+=transition[0]*(transition[2]+discount_factor*V[transition[1]])
                    #print('action ',a, 'temp ',temp2)
                temp1+=policy[s][a]*temp2
            newV[s]=temp1
        diff=np.abs(np.subtract(V, newV)).mean()
        errors+=[diff]
        V=newV
        if k in historicalCheck:
            historical+=[V]
        k+=1
        print('iteration = ',k,'  delta = %.3e'%diff)

    return (np.array(V),errors,historical)

# =============================================================================
# Evaluate a random policy
# =============================================================================
random_policy = np.ones([env.nS, env.nA]) / env.nA
v,errors,historical = policy_eval(random_policy, env)

# =============================================================================
# Checking the results
# =============================================================================
expected_v = np.array([0, -14, -20, -22, -14, -18, -20, -20, -20, -20, -18, -14, -22, -20, -14, 0])
np.testing.assert_array_almost_equal(v, expected_v, decimal=2)

# =============================================================================
# Display the results
# =============================================================================
print("computed Value Function:")
print(v.reshape(env.shape))
print("")

print("expected Value Function:")
print(expected_v.reshape(env.shape))
print("")

fig=pl.figure()
pl.plot(errors)
pl.xlabel('iteration')
pl.ylabel('L1 difference of deltas')
pl.title('evolution of the Value function deltas at each iteration')
pl.savefig('PolicyEvaluation.png')
pl.show()

fig=pl.figure(figsize=(12,5))
ax=fig.add_subplot(141)
ax.imshow(historical[0].reshape(env.shape))
pl.title('iteration 0')

ax=fig.add_subplot(142)
ax.imshow(historical[1].reshape(env.shape))
pl.title('iteration 10')

ax=fig.add_subplot(143)
ax.imshow(v.reshape(env.shape))
pl.title('final iteration - {}'.format(len(errors)))

ax=fig.add_subplot(144)
ax.imshow(expected_v.reshape(env.shape))
pl.title('expected value function')

pl.savefig('EvolutionPolicyEvaluation.png')

pl.legend()
pl.show()


import logging
import sys
import numpy as np
import matplotlib.pyplot as plt

def load_file(filename):
    """
    Carrega o dataset para a memória
        :param filename: nome do arquivo

        :return: matriz x e y
    """
    x_input = []
    y_input = []
    f = open("data/" + filename + ".txt")
    lines = f.readlines()

    for line in lines:
        if filename == "Books_attend_grade":
            x0, x1, y = line.split(";")
            x_input.append([float(x0), float(x1)])
            y_input.append(float(y))
        else:
            line = line.strip("\n")
            x, y = line.split(";")
            x_input.append(float(x))
            y_input.append(float(y))

    return x_input, y_input

def subtract_mean(x, y):
    """
    Subtrair a média de todo o dataset
        :param x: entrada x
        :param y: entrada y

        :return: matriz ajustada
    """
    if isinstance(x[0], list):
        x_zipped = list(zip(*x))
        x1_mean = sum(x_zipped[0]) / len(x_zipped[0])
        x2_mean = sum(x_zipped[1]) / len(x_zipped[1])
    else:
        x_mean = sum(x) / len(x)

    y_mean = sum(y) / len(y)
    x_adjusted = []
    y_adjusted = []

    for i in range(len(x)):
        if isinstance(x[0], list):
            x_adjusted.append([x[i][0] - x1_mean, x[i][1] - x2_mean])
        else:
            x_adjusted.append(x[i] - x_mean)

        y_adjusted.append(y[i] - y_mean)

    return x_adjusted, y_adjusted

def covariance(a, b):
    """
    Calcula a covariancia entre dois pontos
        :param a: ponto a
        :param b: ponto b

        :return: covariancia entre a e b
    """
    covariance_result = 0

    for i in range(len(a)):
        covariance_result += (a[i] * b[i]) / (len(a) - 1)

    return covariance_result

def covariance_matrix(x, y):
    """
    Calcula matriz de covariancia
        :param x: entrada x
        :param y: entrada y

        :return: matriz de covariancia
    """
    cov_matrix = []

    if not isinstance(x[0], list):
        cov_x_x = covariance(x, x)
        cov_x_y = covariance(x, y)
        cov_matrix.append([cov_x_x, cov_x_y])
        cov_y_x = covariance(y, x)
        cov_y_y = covariance(y, y)
        cov_matrix.append([cov_y_x, cov_y_y])
    else:
        x_zipped = list(zip(*x))
        x1 = x_zipped[0]
        x2 = x_zipped[1]
        cov_x_x = covariance(x1, x1)
        cov_x_y = covariance(x1, y)
        cov_x_x2 = covariance(x1, x2)
        cov_matrix.append([cov_x_x, cov_x_y, cov_x_x2])
        cov_y_x = covariance(y, x1)
        cov_y_y = covariance(y, y)
        cov_y_x2 = covariance(y, x2)
        cov_matrix.append([cov_y_x, cov_y_y, cov_y_x2])
        cov_x2_x = covariance(x2, x1)
        cov_x2_y = covariance(x2, y)
        cov_x2_x2 = covariance(x2, x2)
        cov_matrix.append([cov_x2_x, cov_x2_y, cov_x2_x2])

    return cov_matrix

def calc_pca(dataset):
    x, y = load_file(dataset)
    logging.info("*******INPUT*******")
    logging.info(x)
    logging.info(y)

    logging.info("*******MEAN SUBTRACTED*******")
    x_minus_mean, y_minus_mean = subtract_mean(x, y)
    logging.info(x_minus_mean)
    logging.info(y_minus_mean)

    logging.info("*******COVARIANCE MATRIX*******")
    cov = covariance_matrix(x_minus_mean, y_minus_mean)
    logging.info(cov)

    logging.info("*******AUTOVALOR / AUTOVETOR*******")
    cov_np = np.array(cov)
    w, v = np.linalg.eig(cov_np)
    logging.info(w)
    logging.info(v)

    logging.info("*******COMPONENTES*******")
    feature_vector = v[:, np.argmax(w)]
    gradient_1 = feature_vector[1] / feature_vector[0]
    feature_vector_2 = v[:, np.argmin(w)]
    gradient_2 = feature_vector_2[1] / feature_vector_2[0]
    logging.info(feature_vector)

    logging.info("*******PLOT*******")
    plt.plot(x_minus_mean, y_minus_mean, '+', color='black', label='Dados ajustados')
    plt.plot(0, 0, 'x', color='blue', label='Média')
    min_x = min(x_minus_mean)
    max_x = max(x_minus_mean)
    pca_1_x = [min_x, 0, max_x]
    pca_1_y = [min_x * gradient_1, 0, max_x * gradient_1]
    plt.plot(pca_1_x, pca_1_y, color='red', label='Componente principal')

    pca_2_x = [min_x, 0, max_x]
    pca_2_y = [min_x * gradient_2, 0, max_x * gradient_2]
    plt.plot(pca_2_x, pca_2_y, color='orange', label='Componente secundário')

    if dataset == "US_Census":
        x_mq = 2010 - (sum(x) / len(x))
        y_mq = 286.9128 - (sum(y) / len(y))
        plt.plot([x_minus_mean[0], x_mq], [y_minus_mean[0], y_mq], color='purple', label='Mínimos Quadrados')

    if dataset == "alpswater":
        x_mq = 31.06 - (sum(x) / len(x))
        y_mq = 214.3658 - (sum(y) / len(y))
        plt.plot([x_minus_mean[0], x_mq], [y_minus_mean[0], y_mq], color='purple', label='Mínimos Quadrados')

    plt.legend(loc='upper center', bbox_to_anchor=(0.5, -0.05))
    plt.tight_layout()
    plt.title("Dataset: " + dataset)
    plt.show()

logging.basicConfig(stream=sys.stderr, level=logging.INFO)

calc_pca("alpswater")

calc_pca("US_Census")


"""
A magic square is a square of size NxN with the numbers 1 through n2 put in so that all rows, all columns and both
diagonals sum to the same number. For instance, this is a 3x3 magic square:

8 1 6
3 5 7
4 9 2

As you can see all rows, all columns and both diagonals (8+5+2 and 4+5+6) sum to the same number, 15.

Write a program that draws a magic square of size 18x18.

    Thanks to /u/SwimmingPastaDevil for submitting this problem in /r/dailyprogrammer_ideas! And on behalf of the
    moderators, I'd like to thank everyone who submitted problems the last couple of days, it's been really helpful,
    and there are some great problems there! Keep it up, it really helps us out a lot!

"""

import numpy as np

def magic_square_4(n):
    arr = np.arange(1, n*n + 1)
    arr.resize(n, n)
    n //= 4
    mask = np.array([[0, 1, 1, 0],
                     [1, 0, 0, 1],
                     [1, 0, 0, 1],
                     [0, 1, 1, 0]])
    mask = np.tile(mask, (n, n))
    antimask = 1 - mask

    return (mask * arr) + np.rot90((antimask * arr), 2)

def siamese_square(n, start=1):
    arr = np.zeros((n, n), dtype=int)
    x, y = 0, n // 2
    arr[x, y] = start
    for i in range(1+start, (n**2) + start):
        x, y = (x-1) % n, (y+1) % n
        if arr[x, y]:
            x, y = (x+2) % n, (y-1) % n
        arr[x, y] = i
    return arr

def medjig():
    size = 3
    arr = np.arange(0, size+1)
    np.random.shuffle(arr)
    arr.resize(2, 2)
    return arr

def medjig_creator(n):
    s = n // 2

    target = 3 * s
    retry = False
    while True:
        med = np.array([0])

        for _1 in range(s):
            temp = medjig()
            for _2 in range(s - 1):
                temp = np.concatenate((temp, medjig()), 1)
            for t in temp:
                if sum(t) != target:
                    # print(sum(t), target)
                    retry = True
                    break
            if retry:
                break
            if med.any():
                med = np.concatenate((med, temp), 0)
            else:
                med = temp[:]
        if retry:
            continue
        if validate_square(med):
            break

    return med

def concat_siamese(n):
    return np.repeat(np.repeat(siamese_square(n), 2, 0), 2, 1)

def magic_square_2(n):
    siam = concat_siamese(n//2)
    med = medjig_creator(n)

    res = np.zeros((n, n), dtype=int)
    for a in range(n):
        for b in range(n):
            res[a, b] = siam[a, b] + (9 * med[a, b])
    return res

def magic_square_2nd(n):
    """ http://www.math.wichita.edu/~richardson/mathematics/magic%20squares/even-ordermagicsquares.html """
    size = n // 2
    start = size * size
    arr0 = np.concatenate((siamese_square(size), siamese_square(size, (2 * start) + 1)), 1)
    arr1 = np.concatenate((siamese_square(size, (3 * start) + 1), siamese_square(size, start + 1)), 1)
    res = np.concatenate((arr0, arr1), 0)

    exchanger = (n - 2) // 4
    for x in range(exchanger-1):
        res[:, x] = np.roll(res[:, x], size, 0)
        res[:, n - x - 1] = np.roll(res[:, n - x - 1], size, 0)
    res[:, exchanger-1] = np.roll(res[:, exchanger-1], size, 0)
    res[n//4, exchanger-1], res[(3*n)//4, exchanger-1] = res[(3*n)//4, exchanger-1], res[n//4, exchanger-1]
    res[n//4, exchanger], res[(3*n)//4, exchanger] = res[(3*n)//4, exchanger], res[n//4, exchanger]
    return res

def validate_square(sq):
    s = sum(np.diagonal(sq, axis1=1, axis2=0))
    for column in sq:
        if s != sum(column):
            return False
    for row in np.rot90(sq):
        if s != sum(row):
            return False
    return s

def main():
    # required: n >= 3
    n = 18
    magic = -1
    if n % 4 == 0:
        magic = magic_square_4(n)
    elif n % 2 != 0:
        magic = siamese_square(n)
    elif n % 2 == 0:
        # magic = magic_square_2(n)
        magic = magic_square_2nd(n)
    print(magic, validate_square(magic))

if __name__ == "__main__":
    main()


import numpy as np
from RNA.FuncoesAtivacao import sigmoide, identidade
from RNA.FuncoesCusto import mse

def treinar(X, Y, **kwargs):
  taxa_aprendizado = kwargs.get("taxa_aprendizado", 0.02)
  numero_epocas = kwargs.get("numero_epocas", 100)
  batch = kwargs.get("batch", 100)
  funcao_custo = kwargs.get("funcao_custo", mse)
  funcao_ativacao = kwargs.get("funcao_ativacao", sigmoide)
  inicializacao_media = kwargs.get("inicializacao_media", 0)
  inicializacao_desvio = kwargs.get("inicializacao_desvio", 0.5)

  #nº de variáveis de entrada
  num_in = X.shape[1]

  #nº de variáveis de saída
  num_out = Y.shape[1]

  #nº de instâncias
  num_inst = X.shape[0]

  #Inicialização dos parâmetros
  W = [np.random.normal(inicializacao_media,inicializacao_desvio,num_in) for k in range(num_out)]
  B = [np.random.normal(inicializacao_media,inicializacao_desvio,1) for k in range(num_out)]

  log_erros = []

  for epocas in np.arange(numero_epocas):

    for z in np.arange(batch):

      i = np.random.randint(0,num_inst)

      erros = []

      for saida in range(num_out):

        net = W[saida].dot(X[i,:]) + B[saida]  # WX + B

        estimativa = funcao_ativacao(net)  # Ŷ = sigmoide(net)

        erros.append(funcao_custo(Y[i,saida],estimativa))  # Armazena o erro

        # Regra Delta - O valor de atualização dos pesos
        delta = funcao_custo(Y[i,saida],estimativa, derivada=True) * funcao_ativacao(net, derivada = True)

        #Atualiza os pesos (somo cada peso com seu delta)
        W[saida] += taxa_aprendizado*-delta*-X[i,:]

        #Atualização do viés
        B[saida] += taxa_aprendizado*-delta

    log_erros.append(np.mean(erros))   # Retorna o histórico de erros para visualização

  return W, B, log_erros

def treinar_batch(X, Y, **kwargs):
  taxa_aprendizado = kwargs.get("taxa_aprendizado", 0.02)
  numero_epocas = kwargs.get("numero_epocas", 100)
  batch = kwargs.get("batch", 100)
  funcao_custo = kwargs.get("funcao_custo", mse)
  funcao_ativacao = kwargs.get("funcao_ativacao", sigmoide)
  inicializacao_media = kwargs.get("inicializacao_media", 0)
  inicializacao_desvio = kwargs.get("inicializacao_desvio", 0.5)

  num_in = X.shape[1]
  num_out = Y.shape[1]
  num_inst = X.shape[0]
  W = [np.random.normal(inicializacao_media,inicializacao_desvio,num_in) for k in range(num_out)]
  B = [np.random.normal(inicializacao_media,inicializacao_desvio,1) for k in range(num_out)]
  log_erros = []

  for epocas in np.arange(numero_epocas):
    x_avg = np.zeros(num_in)
    delta_avg = np.zeros(num_out)

    erros = []
    for z in np.arange(batch):
      i = np.random.randint(0,num_inst)
      x_avg += X[i,:]
      for saida in range(num_out):
        net = W[saida].dot(X[i,:]) + B[saida]  # WX + B
        estimativa = sigmoide(net)  # Ŷ = sigmoide(net)
        erros.append(funcao_custo(Y[i,saida],estimativa))  # Armazena o erro

        # Regra Delta - O valor de atualização dos pesos
        delta = funcao_custo(Y[i,saida],estimativa, derivada=True) * funcao_ativacao(net, derivada = True)
        delta_avg[saida] += delta
    x_avg /= batch
    delta_avg /= batch
    for saida in range(num_out):
      W[saida] += taxa_aprendizado*-delta_avg[saida]*-x_avg
      B[saida] += taxa_aprendizado*-delta_avg[saida]

    log_erros.append(np.mean(erros))   # Retorna o histórico de erros para visualização

  return W, B, log_erros

def classificacao(x,W,B, **kwargs):
  funcao_ativacao = kwargs.get("funcao_ativacao", sigmoide)
  num_inst = x.shape[0]
  num_in = x.shape[1]
  num_out = len(W)
  ret = []
  for i in range(num_inst):
    tmp = [funcao_ativacao(W[modelo].dot(x[i,:]) + B[modelo])[0] for modelo in np.arange(num_out)]
    tmp = np.where(tmp == max(tmp), 1, 0).argmax()
    ret.append(tmp)
  return np.array(ret)

def regressao(x,W,B,**kwargs):
  funcao_ativacao = kwargs.get("funcao_ativacao", identidade)
  num_inst = x.shape[0]
  num_in = x.shape[1]
  num_out = len(W)
  ret = []
  for i in range(num_inst):
    tmp = [funcao_ativacao(W[modelo].dot(x[i,:]) + B[modelo])[0] for modelo in np.arange(num_out)]
    tmp = np.where(tmp == max(tmp), 1, 0).argmax()
    ret.append(tmp)
  return np.array(ret)


# ex1_euler
import numpy as np
import matplotlib.pyplot as plt

# initial
t = 0
y_0 = 1
dt = 0.1        # change dt to compare

# euler
t_ary = [t]
y_ary = [y_0]

while t < 5:
    y_1 = y_0 + y_0 * dt
    t += dt

    t_ary.append(t)
    y_ary.append(y_1)

    y_0 = y_1

# analytic
t_ary_analytic = np.arange(0, 5.1, 0.1)
y_ary_analytic = np.exp(t_ary_analytic)

# plot
plt.plot(t_ary, y_ary, label="euler")
plt.plot(t_ary_analytic, y_ary_analytic, label="analytic")
plt.legend()
plt.show()