r"""
Sampling Random Nondominated Sets
=================================

This example illustrates how to sample random sets with
mutually nondominated points.

First we define a few functions useful for plotting.
"""

import matplotlib.pyplot as plt
import numpy as np
from _utils import plotly_3d, plotly_3d_side_by_side

import moocore


def generate_ndset_plotly_3d(n, method, seed):
    """Generate points with moocore.generate_ndset() and plot them with plotly_3d()."""
    return plotly_3d(
        "simplex",
        moocore.generate_ndset(n, 3, method, seed=seed),
        title=f'method="{method}"',
    )


# %%
#
# Random nondominated sets in 2D
# ------------------------------
#

n = 100
rng = np.random.default_rng(42)

methods = [
    "simplex",
    "concave-sphere",
    "convex-sphere",
    "convex-simplex",
    "inverted-simplex",
    "concave-simplex",
]
colors = ["red", "blue", "green", "purple", "orange", "yellow"]
markers = ["o", "s", "^", "d", "o", "s"]  # circle, square, triangle_up, diamond

plt.figure(figsize=(4, 4))
for i, method in enumerate(methods):
    points = moocore.generate_ndset(n, 2, method, seed=rng)
    plt.scatter(
        points[:, 0],
        points[:, 1],
        color=colors[i],
        marker=markers[i],
        label=method,
    )

plt.xlabel("X")
plt.ylabel("Y")
plt.legend()
plt.tight_layout()
plt.grid(True)
plt.show()

# %%
#
# Random nondominated sets in integer space
# -----------------------------------------
#
# We can also generate points in integer space.

n = 100
rng = np.random.default_rng(42)

maximum = 0
points = []
for method in methods:
    x = moocore.generate_ndset(n, 2, method, seed=rng, integer=True)
    points += [x]
    maximum = max(maximum, x.max())

plt.figure(figsize=(4, 4))
for i, method in enumerate(methods):
    x = points[i]
    # Normalise so we can plot all sets within the same range.
    if x.max() < maximum:
        x = moocore.normalise(x, lower=0, upper=x.max(), to_range=(0, maximum))
    plt.scatter(
        x[:, 0], x[:, 1], color=colors[i], marker=markers[i], label=method
    )
plt.xlabel("X")
plt.ylabel("Y")
plt.legend()
plt.tight_layout()
plt.grid(True)
plt.show()

# %%
#
# Variants of convex nondominated sets
# ------------------------------------
#
# A popular way to generate a convex nondominated set is to translate the
# negative orthant of the hypersphere to the unit hypercube
# (``convex-sphere``).  However, there are other possible convex sets with
# different properties. For example the method ``convex-simplex`` squares the
# points generated by method ``simplex``, resulting in a convex transformation
# of the standard simplex.
#

n = 2000
rng = np.random.default_rng(42)

fig1 = generate_ndset_plotly_3d(n, "convex-sphere", seed=rng)
fig2 = generate_ndset_plotly_3d(n, "convex-simplex", seed=rng)

plotly_3d_side_by_side(fig1, fig2)

# %%
#
# The method ``convex-simplex`` implemented in :func:`~moocore.generate_ndset`
# is different from the `concave` method proposed by :footcite:t:`BriFri2012tcs`.

points = np.abs(rng.normal(size=(n, 3)))
points /= (np.sqrt(points).sum(axis=1, keepdims=True)) ** 2

fig1 = plotly_3d(
    "simplex", points, title="concave (Bringmann & Friedrich, 2012)"
)
plotly_3d_side_by_side(fig1, fig2)


# %%
#
# Inverted shapes
# ---------------
#
# :footcite:t:`IshHeSha2019regular` analyze the differences between `regular` and
# `inverted` shapes, shown below on the left and right figures,
# respectively. The differences between ``simplex`` and ``inverted-simplex``
# are not noticeable in 2D, but are significant in higher dimensions.
#

n = 2000
rng = np.random.default_rng(42)

fig1 = generate_ndset_plotly_3d(n, "simplex", seed=rng)
fig2 = generate_ndset_plotly_3d(n, "inverted-simplex", seed=rng)

plotly_3d_side_by_side(fig1, fig2)

# %%

fig1 = generate_ndset_plotly_3d(n, "concave-sphere", seed=rng)
fig2 = generate_ndset_plotly_3d(n, "convex-sphere", seed=rng)

plotly_3d_side_by_side(fig1, fig2)

# %%

fig1 = generate_ndset_plotly_3d(n, "convex-simplex", seed=rng)
fig2 = generate_ndset_plotly_3d(n, "concave-simplex", seed=rng)

plotly_3d_side_by_side(fig1, fig2)

# %%
#
# Cliff sets (3D)
# ---------------
#
# The 'cliff' type is often used to test the performance of algorithms for
# computing the hypervolume :footcite:p:`EmmFon2011emo,GueFon2017hv4d`, due to
# its particular structure.
#

n = 2000
rng = np.random.default_rng(42)

fig1 = generate_ndset_plotly_3d(n, "cliff-concave", seed=rng)
fig2 = generate_ndset_plotly_3d(n, "cliff-convex", seed=rng)

plotly_3d_side_by_side(fig1, fig2)

# %%
#
# Uniform sampling (moocore) vs projections of uniform samples (naive)
# --------------------------------------------------------------------
#
# Naive methods for sampling such sets usually sample points uniformly in the
# hypercube and project them into a lower dimensional manifold
# :footcite:p:`LacKlaFon2017box`, e.g., the standard simplex or the positive orthant
# of the hypersphere.  However, such projections do not preserve the uniformity
# of the sampling, that is, not all points in the manifold have the same
# probability of being sampled.
#
# The function :func:`~moocore.generate_ndset` produces a uniform sampling
# on the manifold, as shown in the following examples:

n = 2000
rng = np.random.default_rng(42)

points = moocore.generate_ndset(n, 3, "simplex", seed=rng)
fig1 = plotly_3d("simplex", points, title="Simplex (moocore)")

points = rng.uniform(size=(n, 3))
points /= points.sum(axis=1, keepdims=True)
fig2 = plotly_3d("simplex", points, title="Simplex (naive)")

plotly_3d_side_by_side(fig1, fig2)

# %%

points = moocore.generate_ndset(n, 3, "concave-sphere", seed=rng)
fig1 = plotly_3d("concave", points, title="Concave-sphere (moocore)")

points = rng.uniform(size=(n, 3))
points /= np.linalg.norm(points, axis=1, keepdims=True)
fig2 = plotly_3d("concave", points, title="Concave-sphere (naive)")

plotly_3d_side_by_side(fig1, fig2)

# %%

points = moocore.generate_ndset(n, 3, "convex-sphere", seed=rng)
fig1 = plotly_3d("convex", points, title="Convex-sphere (moocore)")

points = rng.uniform(size=(n, 3))
points /= np.linalg.norm(points, axis=1, keepdims=True)
fig2 = plotly_3d("convex", 1.0 - points, title="Convex-sphere (naive)")

plotly_3d_side_by_side(fig1, fig2)

# %%
# References
# ----------
# .. footbibliography::
#
