Skip to content

Commit

Permalink
python/ Convert examples into sphinx galleries.
Browse files Browse the repository at this point in the history
  • Loading branch information
MLopez-Ibanez committed Sep 8, 2024
1 parent 1756087 commit fd55986
Show file tree
Hide file tree
Showing 13 changed files with 203 additions and 36 deletions.
2 changes: 2 additions & 0 deletions python/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ instance/
doc/_build/
doc/jupyter_execute/
doc/source/reference/generated/
doc/source/auto_examples
doc/source/sg_execution_times.rst

# PyBuilder
.pybuilder/
Expand Down
9 changes: 6 additions & 3 deletions python/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This file should be almost identical to
# https://github.com/multi-objective/mooplot/blob/main/python/Makefile
.PHONY : install build test doc clean docdeps pre-commit
.PHONY : install build test docs fastdocs clean docdeps pre-commit

install: build
python3 -m pip install -e . --disable-pip-version-check
Expand All @@ -21,12 +21,15 @@ docdeps:
show:
$(MAKE) -C doc show

doc:
docs:
$(MAKE) -C doc clean html

fastdocs:
$(MAKE) -C doc clean html-noplot

clean:
$(MAKE) -C doc clean
$(MAKE) -C src/moocore/libmoocore/ clean
find . -name '__pycache__' | xargs $(RM) -r
$(RM) -rf .pytest_cache .tox build src/*.egg-info/ doc/source/reference/generated
$(RM) -rf .pytest_cache .tox build src/*.egg-info/ doc/source/reference/generated doc/source/auto_examples
$(RM) -f .coverage coverage.xml c_coverage.xml dist/*
4 changes: 4 additions & 0 deletions python/doc/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ help:
show:
@python3 -c "import webbrowser; webbrowser.open_new_tab('file://$(root_dir)/$(BUILDDIR)/html/index.html')"

html-noplot:
@$(SPHINXBUILD) -D plot_gallery=0 -b html $(ALLSPHINXOPTS) "$(SOURCEDIR)" "$(BUILDDIR)/html"
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
Expand Down
64 changes: 61 additions & 3 deletions python/doc/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,20 @@
import sphinx
import moocore

# Set plotly renderer to capture _repr_html_ for sphinx-gallery
try:
import plotly.io
except ImportError:
pass
else:
plotly.io.renderers.default = "sphinx_gallery"

project = "moocore"
_full_version = moocore.__version__
release = _full_version # _full_version.split("+", 1)[0]
version = _full_version # ".".join(release.split(".")[:2])
year = date.today().year
# Can we get this from pyproject.toml ?
author = "Manuel López-Ibáñez and Fergus Rooney"
copyright = f"2024-{year}, {author}"
html_site_root = f"https://multi-objective.github.io/{project}/python/"
Expand All @@ -40,9 +49,10 @@
"sphinx.ext.autosummary", # Create neat summary tables for modules/classes/methods etc
"sphinx_copybutton", # A small sphinx extension to add a "copy" button to code blocks.
"sphinx.ext.mathjax",
"myst_nb",
"sphinx.ext.autosectionlabel",
# "sphinx.ext.autosectionlabel", DO NOT USE: causes duplicated labels.
"sphinxcontrib.bibtex",
"sphinx_gallery.gen_gallery",
"matplotlib.sphinxext.plot_directive",
]

# -----------------------------------------------------------------------------
Expand Down Expand Up @@ -122,6 +132,13 @@ def setup(app):
# Add light/dark mode and documentation version switcher:
# "navbar_end": ["theme-switcher", "version-switcher", "navbar-icon-links"],
"navbar_end": ["theme-switcher", "navbar-icon-links"],
"icon_links": [
{
"name": "PyPI",
"url": f"https://pypi.org/project/{project}",
"icon": "fa-solid fa-box",
},
],
# "icon_links": [
# {
# # Label for this link
Expand Down Expand Up @@ -155,7 +172,7 @@ def setup(app):
templates_path = ["_templates"]
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path .
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = [
"_build",
"Thumbs.db",
Expand All @@ -164,6 +181,10 @@ def setup(app):
"_templates",
"modules.rst",
"source",
# Exclude .py and .ipynb files in auto_examples generated by sphinx-gallery.
# This is to prevent sphinx from complaining about duplicate source files.
"auto_examples/*.ipynb",
"auto_examples/*.py",
]
suppress_warnings = ["mystnb.unknown_mime_type"]

Expand All @@ -190,3 +211,40 @@ def setup(app):
"sympy": ("https://docs.sympy.org/latest/", None),
"mooplot": ("https://multi-objective.github.io/mooplot/python/", None),
}

# From https://github.com/scikit-learn/scikit-learn/blob/main/doc/conf.py
sphinx_gallery_conf = {
"examples_dirs": "../../examples",
"gallery_dirs": "auto_examples",
# Directory where function/class granular galleries are stored.
"backreferences_dir": "reference/generated/backreferences",
# Modules for which function/class level galleries are created.
"doc_module": (project),
# Regexes to match objects to exclude from implicit backreferences.
# The default option is an empty set, i.e. exclude nothing.
# To exclude everything, use: '.*'
"exclude_implicit_doc": {r"pyplot\.show"},
"show_memory": False,
# "reference_url": {"mooplot": None},
# "subsection_order": SubSectionTitleOrder("../examples"),
# "within_subsection_order": SKExampleTitleSortKey,
# "binder": {
# "org": "scikit-learn",
# "repo": "scikit-learn",
# "binderhub_url": "https://mybinder.org",
# "branch": binder_branch,
# "dependencies": "./binder/requirements.txt",
# "use_jupyter_lab": True,
# },
# avoid generating too many cross links
"inspect_global_variables": False,
"remove_config_comments": True,
"matplotlib_animations": True,
# "plot_gallery": "True",
# "recommender": {"enable": True, "n_examples": 5, "min_df": 12},
# "reset_modules": ("matplotlib", "seaborn"),
}
# if with_jupyterlite:
# sphinx_gallery_conf["jupyterlite"] = {
# "notebook_modification_function": notebook_modification_function
# }
8 changes: 0 additions & 8 deletions python/doc/source/examples/index.rst

This file was deleted.

16 changes: 0 additions & 16 deletions python/doc/source/examples/metrics.md

This file was deleted.

4 changes: 2 additions & 2 deletions python/doc/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ moocore: Core Algorithms for Multi-Objective Optimization
:hidden:

API reference <reference/index>
Examples <examples/index>
Examples <auto_examples/index>

**Version**: |version|

Expand Down Expand Up @@ -53,7 +53,7 @@ performance measures, performance assessment
:img-top: _static/index_getting_started.svg
:class-card: intro-card
:shadow: md
:link: examples
:link: auto_examples
:link-type: ref

Detailed examples and tutorials.
Expand Down
8 changes: 8 additions & 0 deletions python/examples/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.. _auto_examples:

Examples
========

These are longer and more detailed examples than those accompanying the
documentation of each function. These examples may require additional packages
to run.
60 changes: 60 additions & 0 deletions python/examples/plot_hv_approx.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
r"""Comparing methods for approximating the hypervolume
===================================================
This example shows how to approximate the hypervolume metric of the ``CPFs.txt`` dataset using both HypE, :func:`moocore.whv_hype()`, and DZ2019, :func:`moocore.hv_approx()` for several
values of the number of samples between :math:`10^1` and :math:`10^5`. We repeat each
calculation 10 times to account for stochasticity.
"""

import numpy as np
import moocore

ref = 2.1
x = moocore.get_dataset("CPFs.txt")[:, :-1]
x = moocore.filter_dominated(x)
x = moocore.normalise(x, to_range=[1, 2])
true_hv = moocore.hypervolume(x, ref=ref)
rng1 = np.random.default_rng(42)
rng2 = np.random.default_rng(42)

hype = {}
dz = {}
for i in range(1, 6):
hype[i] = []
dz[i] = []
for r in range(15):
res = moocore.whv_hype(x, ref=ref, ideal=0, nsamples=10**i, seed=rng1)
hype[i].append(res)
res = moocore.hv_approx(x, ref=ref, nsamples=10**i, seed=rng2)
dz[i].append(res)
print(
f"True HV : {true_hv:.5f}",
f"Mean HYPE : {np.mean(hype[5]):.5f} [{np.min(hype[5]):.5f}, {np.max(hype[5]):.5f}]",
f"Mean DZ2019: {np.mean(dz[5]):.5f} [{np.min(dz[5]):.5f}, {np.max(dz[5]):.5f}]",
sep="\n",
)


# %%
# Next, we plot the results.

import pandas as pd

hype = pd.DataFrame(hype)
dz = pd.DataFrame(dz)
hype["Method"] = "HypE"
dz["Method"] = "DZ2019"
df = (
pd.concat([hype, dz])
.reset_index(names="rep")
.melt(id_vars=["rep", "Method"], var_name="samples")
)
df["samples"] = 10 ** df["samples"]
df["value"] = np.abs(df["value"] - true_hv) / true_hv

import matplotlib.pyplot as plt
import seaborn as sns

ax = sns.lineplot(x="samples", y="value", hue="Method", data=df, marker="o")
ax.set(xscale="log", yscale="log", ylabel="Relative error")
plt.show()
41 changes: 41 additions & 0 deletions python/examples/plot_metrics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Computing Multi-Objective Quality Metrics
=========================================
TODO: Expand this
"""

import numpy as np
import moocore

# %%
# First, read the datasets.
#

spherical = moocore.get_dataset("spherical-250-10-3d.txt")
uniform = moocore.get_dataset("uniform-250-10-3d.txt")

ref = 1.1
ref_set = moocore.filter_dominated(
np.vstack((spherical[:, :-1], uniform[:, :-1]))
)


def apply_within_sets(x, fun, **kwargs):
"""Apply ``fun`` for each dataset in ``x``."""
_, uniq_index = np.unique(x[:, -1], return_index=True)
x_split = np.vsplit(x[:, :-1], uniq_index[1:])
return [fun(g, **kwargs) for g in x_split]


uniform_igd_plus = apply_within_sets(uniform, moocore.igd_plus, ref=ref_set)
spherical_igd_plus = apply_within_sets(
spherical, moocore.igd_plus, ref=ref_set
)

print(f"""
Uniform Spherical
------- ---------
Mean IGD+: {np.mean(uniform_igd_plus):.5f} {np.mean(spherical_igd_plus):.5f}
""")
8 changes: 8 additions & 0 deletions python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ dependencies = [
"cffi>=1.15.1",
"numpy>=1.22.3",
]

urls.Documentation = "https://multi-objective.github.io/moocore/python/"
urls.Homepage = "https://multi-objective.github.io/moocore/python/"
urls.Source = "https://github.com/multi-objective/moocore/"
Expand Down Expand Up @@ -114,6 +115,13 @@ lint.ignore = [
"D213", # multi-line-summary-second-line
]

lint.per-file-ignores."*examples*/*.py" = [
"D205", # 1 blank line required between summary line and description
"D400", # First line should end with a period
"D415", # First line should end with a period, question mark, or exclamation point
"E402", # Module level import not at top of file
]

[tool.pytest.ini_options]
doctest_optionflags = "NUMBER"
addopts = [
Expand Down
12 changes: 8 additions & 4 deletions python/requirements_dev.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
setuptools>=70.1,<74 # Sync with .pre-commit-config.yaml
cffi >= 1.15.1
numpy >= 1.22.3

pre-commit >= 3.3.2
ruff >= 0.1.4 # Sync with .pre-commit-config.yaml
setuptools >= 65.5.1
cffi >= 1.15.1
ruff >= 0.1.4

tox >= 4.6.2 # Sync with tox.ini
pytest >= 7 # Sync with tox.ini
Expand All @@ -16,11 +16,15 @@ pydata_sphinx_theme
jupyter
ipykernel
kaleido
myst-nb
sphinx-gallery
sphinxcontrib-napoleon
sphinxcontrib-bibtex
sphinx-autodoc-typehints
sphinx-copybutton
sphinx-design
jupyterlab
ipywidgets

# Gallery examples
pandas
seaborn
3 changes: 3 additions & 0 deletions python/src/moocore/_moocore.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,9 @@ def hv_approx(
>>> moocore.hv_approx(x, ref = reference, maximise = True, seed = 42)
1.0563125590974458
.. minigallery:: moocore.hv_approx
:add-heading:
"""
# Convert to numpy.array in case the user provides a list. We use
# np.asarray to convert it to floating-point, otherwise if a user inputs
Expand Down

0 comments on commit fd55986

Please sign in to comment.