Skip to content

Commit

Permalink
Merge pull request #18 from swright87/pgfplots
Browse files Browse the repository at this point in the history
Adds a new backend for generating plots using TeX/PGFPlots
  • Loading branch information
Pennycook committed May 2, 2024
2 parents 4f84770 + 4ca146b commit d63902b
Show file tree
Hide file tree
Showing 8 changed files with 744 additions and 128 deletions.
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
include p3/data/coverage-0.1.0.schema
include p3/data/coverage-0.2.0.schema
include p3/data/coverage-0.3.0.schema
include p3/plot/backend/templates/cascade.tex
include p3/plot/backend/templates/navchart.tex
8 changes: 8 additions & 0 deletions docs/source/p3.plot.backend.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ p3.plot.backend.matplotlib module
:undoc-members:
:show-inheritance:

p3.plot.backend.pgfplots module
-------------------------------

.. automodule:: p3.plot.backend.pgfplots
:members:
:undoc-members:
:show-inheritance:

Module contents
---------------

Expand Down
17 changes: 11 additions & 6 deletions p3/plot/_cascade.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,12 @@
from p3._utils import _require_columns


def cascade(df, eff=None, size=(6, 5), **kwargs):
def cascade(df, eff=None, size=None, **kwargs):
"""
Plot a `cascade`_ summarizing the efficiency and performance
portability of each application in a DataFrame, highlighting
differences in platform support across the applications.
The cascade is plotted using the current pyplot figure,
if one exists.
.. _cascade: https://doi.org/10.1109/P3HPC51967.2020.00007
Parameters
Expand All @@ -33,8 +30,11 @@ def cascade(df, eff=None, size=(6, 5), **kwargs):
"app" or "arch". If no value is provided, the efficiency is selected
automatically based on the data available in `df`.
size: 2-tuple of floats, default: (6, 5)
size: 2-tuple of floats, optional
The size of the plot, in backend-specific units.
In the matplotlib backend, the default is (6, 5), in the pgfplots
backend the size controls only the top left plot, where the default
is ("200pt", "200pt")
**kwargs: properties, optional
`kwargs` are used to specify properties that control various styling
Expand Down Expand Up @@ -91,8 +91,13 @@ def cascade(df, eff=None, size=(6, 5), **kwargs):
if backend == "matplotlib":
from p3.plot.backend.matplotlib import CascadePlot

return CascadePlot(df, eff, size, **kwargs)
elif backend == "pgfplots":
from p3.plot.backend.pgfplots import CascadePlot

return CascadePlot(df, eff, size, **kwargs)
else:
raise ValueError(
"'backend' must be one of the supported backends: 'matplotlib'",
"'backend' must be one of the supported backends: ",
"'matplotlib', 'pgfplots'",
)
15 changes: 10 additions & 5 deletions p3/plot/_navchart.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,14 @@
from p3._utils import _require_columns, _require_numeric


def navchart(pp, cd, eff=None, size=(5, 5), goal=None, **kwargs):
def navchart(pp, cd, eff=None, size=None, goal=None, **kwargs):
"""
Plot a `navigation chart`_ showing the performance portability and code
convergence of each application in a DataFrame. The chart highlights the
tradeoff between performance (portability) and programmer productivity,
assisting in navigation of the P3 space and reasoning about how to reach
development goals.
The chart is plotted using the current pyplot figure, if one exists.
.. _navigation chart: https://doi.org/10.1109/MCSE.2021.3097276
Parameters
Expand All @@ -34,8 +32,10 @@ def navchart(pp, cd, eff=None, size=(5, 5), goal=None, **kwargs):
"app" or "arch". If no value is provided, the efficiency is selected
automatically based on the data available in `pp`.
size: 2-tuple of floats, default: (5, 5)
size: 2-tuple of floats, optional
The size of the plot, in backend-specific units.
In the matplotlib backend, the default is (5, 5), in the pgfplots
backend, the default is ("200pt", "200pt")
goal: tuple, optional
User-defined goal, expressed as (convergence, portability).
Expand Down Expand Up @@ -92,8 +92,13 @@ def navchart(pp, cd, eff=None, size=(5, 5), goal=None, **kwargs):
if backend == "matplotlib":
from p3.plot.backend.matplotlib import NavChart

return NavChart(pp, cd, eff, size, goal, **kwargs)
elif backend == "pgfplots":
from p3.plot.backend.pgfplots import NavChart

return NavChart(pp, cd, eff, size, goal, **kwargs)
else:
raise ValueError(
"'backend' must be one of the supported backends: 'matplotlib'",
"'backend' must be one of the supported backends: ",
"'matplotlib', 'pgfplots'",
)
148 changes: 31 additions & 117 deletions p3/plot/backend/matplotlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,24 @@
from p3.plot.backend import CascadePlot, NavChart


def _get_colors(applications, kwarg):
"""
Assign a color to each application based on supplied kwarg.
"""
if isinstance(kwarg, str):
cmap = getattr(plt.cm, kwarg)
elif isinstance(kwarg, list):
cmap = matplotlib.colors.ListedColormap(kwarg)
elif isinstance(kwarg, matplotlib.colors.Colormap):
cmap = kwarg
else:
raise ValueError("Unsupported type provided for colormap.")

cmap = cmap.resampled(len(applications))
colors = cmap(np.linspace(0, 1, len(applications)))
return {app: color for app, color in zip(applications, colors)}


class _PlatformLegendHandler(matplotlib.legend_handler.HandlerBase):
def __init__(self, colors, labels):
self.colors = colors
Expand Down Expand Up @@ -74,85 +92,7 @@ class CascadePlot(CascadePlot):
Cascade plot object for :py:mod:`matplotlib`.
"""

def __init__(
self,
df,
eff=None,
size=(6, 5),
fig=None,
axes=None,
**kwargs,
):
"""
Plot a `cascade`_ summarizing the efficiency and performance
portability of each application in a DataFrame, highlighting
differences in platform support across the applications.
The cascade is plotted using the current pyplot figure,
if one exists.
.. _cascade: https://doi.org/10.1109/P3HPC51967.2020.00007
Parameters
----------
df: DataFrame
A pandas DataFrame storing performance efficiency data.
The following columns are always required: "problem", "platform",
"application". At least one of the following columns is required:
"app eff" or "arch eff".
eff: string, optional
The efficiency value to use when plotting the cascade. Must be
either "app" or "arch". If no value is provided, the efficiency is
selected automatically based on the data available in `df`.
size: 2-tuple of floats, default: (6, 5)
The size of the plot, in backend-specific units.
**kwargs: properties, optional
`kwargs` are used to specify properties that control various
styling options (e.g. colors and markers).
.. list-table:: Properties
:widths: 10, 20, 18
:header-rows: 1
* - Property
- Type
- Description
* - `platform_legend`
- p3.plot.Legend
- Styling options for platform legend.
* - `application_legend`
- p3.plot.Legend
- Styling options for application legend.
* - `platform_style`
- p3.plot.PlatformStyle
- Styling options for platforms.
* - `application_style`
- p3.plot.ApplicationStyle
- Styling options for applications.
Returns
-------
~p3.plot.backend.CascadePlot
An object providing direct access to backend-specific components
of the cascade plot.
Raises
------
ValueError
If any of the required columns are missing from `df`.
If `eff` is set to any value other than "app" or "arch".
TypeError
If any of the values in the efficiency column(s) is non-numeric.
"""

def __init__(self, df, eff=None, size=None, fig=None, axes=None, **kwargs):
super().__init__("matplotlib")

kwargs.setdefault("platform_legend", Legend())
Expand Down Expand Up @@ -204,6 +144,10 @@ def __init__(
raise ValueError(msg % (eff_column))
_require_numeric(df, [eff_column])

# If the size is unset, default to 6 x 5
if not size:
size = (6, 5)

# Keep only the most efficient (application, platform) results.
key = ["problem", "platform", "application"]
groups = df[key + [eff_column]].groupby(key)
Expand All @@ -229,7 +173,7 @@ def __init__(
)

# Choose colors for each application
app_colors = self.__get_colors(applications, app_style.colors)
app_colors = _get_colors(applications, app_style.colors)

# Choose markers for each application
markers = app_style.markers
Expand All @@ -244,7 +188,7 @@ def __init__(
app_markers = {app: color for app, color in zip(applications, markers)}

# Choose colors for each platform
plat_colors = self.__get_colors(platforms, plat_style.colors)
plat_colors = _get_colors(platforms, plat_style.colors)

# Choose labels for each platform
if len(platforms) > len(string.ascii_uppercase):
Expand Down Expand Up @@ -488,23 +432,6 @@ def __pp_bars(self, ax, df, pp_column, colors, markers):
zorder=4,
)

def __get_colors(self, applications, kwarg):
"""
Assign a color to each application based on supplied kwarg.
"""
if isinstance(kwarg, str):
cmap = getattr(plt.cm, kwarg)
elif isinstance(kwarg, list):
cmap = matplotlib.colors.ListedColormap(kwarg)
elif isinstance(kwarg, matplotlib.colors.Colormap):
cmap = kwarg
else:
raise ValueError("Unsupported type provided for colormap.")

cmap = cmap.resampled(len(applications))
colors = cmap(np.linspace(0, 1, len(applications)))
return {app: color for app, color in zip(applications, colors)}

def save(self, filename):
"""
Save the plot to the specified file.
Expand All @@ -526,7 +453,7 @@ def __init__(
pp,
cd,
eff=None,
size=(5, 5),
size=None,
goal=None,
fig=None,
axes=None,
Expand Down Expand Up @@ -569,10 +496,14 @@ def __init__(
raise ValueError(msg % (pp_column))
_require_numeric(pp, [pp_column])

# If the size is unset, default to 5 x 5
if not size:
size = (5, 5)

ppcd = pd.merge(pp, cd, on=["problem", "application"], how="inner")

applications = ppcd["application"].unique()
app_colors = self.__get_colors(applications, style.colors)
app_colors = _get_colors(applications, style.colors)

markers = style.markers
if not isinstance(markers, (list, tuple)):
Expand Down Expand Up @@ -749,23 +680,6 @@ def __init__(
self.fig = fig
self.axes = axes

def __get_colors(self, applications, kwarg):
"""
Assign a color to each application based on supplied kwarg.
"""
if isinstance(kwarg, str):
cmap = getattr(plt.cm, kwarg)
elif isinstance(kwarg, list):
cmap = matplotlib.colors.ListedColormap(kwarg)
elif isinstance(kwarg, matplotlib.colors.Colormap):
cmap = kwarg
else:
raise ValueError("Unsupported type provided for colormap.")

cmap = cmap.resampled(len(applications))
colors = cmap(np.linspace(0, 1, len(applications)))
return {app: color for app, color in zip(applications, colors)}

def get_figure(self):
"""
Returns
Expand Down
Loading

0 comments on commit d63902b

Please sign in to comment.