Skip to content

Commit c4ec8a2

Browse files
author
Andrija Kolic
committed
[GR-71966] Add matplotlib benchmarks
PullRequest: graalpython/4246
2 parents bd4fb44 + 0a9d1ad commit c4ec8a2

10 files changed

Lines changed: 847 additions & 0 deletions

File tree

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
3+
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
#
5+
# The Universal Permissive License (UPL), Version 1.0
6+
#
7+
# Subject to the condition set forth below, permission is hereby granted to any
8+
# person obtaining a copy of this software, associated documentation and/or
9+
# data (collectively the "Software"), free of charge and under any and all
10+
# copyright rights in the Software, and any and all patent rights owned or
11+
# freely licensable by each licensor hereunder covering either (i) the
12+
# unmodified Software as contributed to or provided by such licensor, or (ii)
13+
# the Larger Works (as defined below), to deal in both
14+
#
15+
# (a) the Software, and
16+
#
17+
# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
# one is included with the Software each a "Larger Work" to which the Software
19+
# is contributed by such licensors),
20+
#
21+
# without restriction, including without limitation the rights to copy, create
22+
# derivative works of, display, perform, and distribute the Software and make,
23+
# use, sell, offer for sale, import, export, have made, and have sold the
24+
# Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
# either these or other terms.
26+
#
27+
# This license is subject to the following condition:
28+
#
29+
# The above copyright notice and either this complete permission notice or at a
30+
# minimum a reference to the UPL must be included in all copies or substantial
31+
# portions of the Software.
32+
#
33+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
# SOFTWARE.
40+
41+
"""
42+
3D plotting example: surface, wireframe, and contour projections.
43+
Saves to PDF.
44+
"""
45+
46+
47+
def run():
48+
# Execute all imports inside the `run` method so they're measured
49+
from pathlib import Path
50+
# Use a non-interactive backend to work in headless environments
51+
import matplotlib
52+
matplotlib.use("Agg")
53+
import matplotlib.pyplot as plt
54+
import numpy as np
55+
56+
# Ensure we have version info in the logs
57+
print(f"Using matplotlib version '{matplotlib.__version__}'")
58+
print(f"Using numpy version '{np.__version__}'")
59+
60+
out_path = Path(__file__).parent / "surface_3d.pdf"
61+
62+
# Domain and function
63+
x = np.linspace(-4, 4, 200)
64+
y = np.linspace(-4, 4, 200)
65+
X, Y = np.meshgrid(x, y)
66+
67+
R = np.sqrt(X**2 + Y**2) + 1e-12
68+
Z = np.sin(R) / R + 0.15 * np.cos(3*X) * np.sin(3*Y) / (1 + 0.5 * (X**2 + Y**2))
69+
70+
fig = plt.figure(figsize=(7.5, 5.8), dpi=150)
71+
ax = fig.add_subplot(111, projection="3d")
72+
73+
# Surface with colormap
74+
surf = ax.plot_surface(X, Y, Z, cmap="viridis", linewidth=0, antialiased=True, alpha=0.95)
75+
76+
# Wireframe overlay (sparser grid to avoid clutter)
77+
step = 10
78+
ax.plot_wireframe(X[::step, ::step], Y[::step, ::step], Z[::step, ::step],
79+
rstride=1, cstride=1, color="k", linewidth=0.3, alpha=0.5)
80+
81+
# Contour projections on Z, X, and Y planes
82+
z_offset = Z.min() - 0.4
83+
ax.contour(X, Y, Z, zdir="z", offset=z_offset, cmap="viridis", levels=18, linewidths=0.8)
84+
85+
x_offset = x.min() - 0.6
86+
ax.contour(X, Y, Z, zdir="x", offset=x_offset, cmap="magma", levels=14, linewidths=0.7)
87+
88+
y_offset = y.max() + 0.6
89+
ax.contour(X, Y, Z, zdir="y", offset=y_offset, cmap="plasma", levels=14, linewidths=0.7)
90+
91+
# Axes labels and limits
92+
ax.set_xlabel("X")
93+
ax.set_ylabel("Y")
94+
ax.set_zlabel("Z")
95+
96+
ax.set_xlim(x_offset, x.max())
97+
ax.set_ylim(y.min(), y_offset)
98+
ax.set_zlim(z_offset, Z.max())
99+
100+
# Colorbar
101+
cb = fig.colorbar(surf, ax=ax, shrink=0.6, aspect=12, pad=0.08)
102+
cb.set_label("Z value")
103+
104+
# View angle
105+
ax.view_init(elev=25, azim=-55)
106+
107+
ax.set_title("3D Surface + Wireframe + Contour Projections")
108+
fig.tight_layout()
109+
110+
fig.savefig(out_path, format="pdf")
111+
plt.close(fig)
112+
113+
114+
def warmupIterations():
115+
return 0
116+
117+
118+
def iterations():
119+
return 1
120+
121+
122+
def summary():
123+
return {
124+
"name": "OutlierRemovalAverageSummary",
125+
"lower-threshold": 0.0,
126+
"upper-threshold": 1.0,
127+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[[rule]]
2+
files = "*"
3+
any = [
4+
"francois.farquet@oracle.com",
5+
"andrija.kolic@oracle.com",
6+
]
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
3+
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
#
5+
# The Universal Permissive License (UPL), Version 1.0
6+
#
7+
# Subject to the condition set forth below, permission is hereby granted to any
8+
# person obtaining a copy of this software, associated documentation and/or
9+
# data (collectively the "Software"), free of charge and under any and all
10+
# copyright rights in the Software, and any and all patent rights owned or
11+
# freely licensable by each licensor hereunder covering either (i) the
12+
# unmodified Software as contributed to or provided by such licensor, or (ii)
13+
# the Larger Works (as defined below), to deal in both
14+
#
15+
# (a) the Software, and
16+
#
17+
# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
# one is included with the Software each a "Larger Work" to which the Software
19+
# is contributed by such licensors),
20+
#
21+
# without restriction, including without limitation the rights to copy, create
22+
# derivative works of, display, perform, and distribute the Software and make,
23+
# use, sell, offer for sale, import, export, have made, and have sold the
24+
# Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
# either these or other terms.
26+
#
27+
# This license is subject to the following condition:
28+
#
29+
# The above copyright notice and either this complete permission notice or at a
30+
# minimum a reference to the UPL must be included in all copies or substantial
31+
# portions of the Software.
32+
#
33+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
# SOFTWARE.
40+
41+
"""
42+
Categorical plots: grouped bar chart with error bars and a boxplot.
43+
Saves a multi-page PDF using PdfPages.
44+
"""
45+
46+
47+
def _colorize_boxplot(bp, facecolor="#1f77b4", edgecolor="black", alpha=0.6):
48+
for box in bp["boxes"]:
49+
box.set(facecolor=facecolor, edgecolor=edgecolor, alpha=alpha)
50+
for median in bp["medians"]:
51+
median.set(color="black", linewidth=1.2)
52+
for whisker in bp["whiskers"]:
53+
whisker.set(color=edgecolor, linewidth=1.0)
54+
for cap in bp["caps"]:
55+
cap.set(color=edgecolor, linewidth=1.0)
56+
for flier in bp["fliers"]:
57+
flier.set(marker="o", markersize=3, markerfacecolor="white", markeredgecolor=edgecolor, alpha=0.7)
58+
59+
60+
def run():
61+
# Execute all imports inside the `run` method so they're measured
62+
from pathlib import Path
63+
# Use a non-interactive backend to work in headless environments
64+
import matplotlib
65+
matplotlib.use("Agg")
66+
import matplotlib.pyplot as plt
67+
import numpy as np
68+
from matplotlib.backends.backend_pdf import PdfPages
69+
70+
# Ensure we have version info in the logs
71+
print(f"Using matplotlib version '{matplotlib.__version__}'")
72+
print(f"Using numpy version '{np.__version__}'")
73+
74+
out_path = Path(__file__).parent / "categorical_plots.pdf"
75+
76+
rng = np.random.default_rng(2024)
77+
categories = ["A", "B", "C", "D"]
78+
n = len(categories)
79+
80+
with PdfPages(out_path) as pdf:
81+
# Page 1: Grouped bar chart with error bars
82+
x = np.arange(n)
83+
bar_w = 0.35
84+
85+
means1 = rng.normal(3.0, 0.4, n)
86+
errs1 = rng.uniform(0.1, 0.4, n)
87+
88+
means2 = rng.normal(2.2, 0.5, n)
89+
errs2 = rng.uniform(0.1, 0.4, n)
90+
91+
fig1, ax1 = plt.subplots(figsize=(7, 4), dpi=150)
92+
b1 = ax1.bar(x - bar_w / 2, means1, yerr=errs1, width=bar_w, capsize=3,
93+
label="Series 1", color="#1f77b4", edgecolor="black", alpha=0.85)
94+
b2 = ax1.bar(x + bar_w / 2, means2, yerr=errs2, width=bar_w, capsize=3,
95+
label="Series 2", color="#ff7f0e", edgecolor="black", alpha=0.85)
96+
97+
ax1.set_xticks(x, categories)
98+
ax1.set_ylabel("Value")
99+
ax1.set_title("Grouped Bar Chart with Error Bars")
100+
ax1.grid(axis="y", linestyle="--", alpha=0.35)
101+
ax1.legend(loc="best")
102+
103+
# Annotate bars with heights
104+
for bars in (b1, b2):
105+
for rect in bars:
106+
h = rect.get_height()
107+
ax1.text(rect.get_x() + rect.get_width() / 2.0, h + 0.05,
108+
f"{h:.2f}", ha="center", va="bottom", fontsize=8, rotation=0)
109+
110+
fig1.tight_layout()
111+
pdf.savefig(fig1)
112+
plt.close(fig1)
113+
114+
# Page 2: Boxplot across categories
115+
# Generate some synthetic distributions with varying mean/variance
116+
mus = [2.8, 3.2, 2.5, 3.5]
117+
sigmas = [0.50, 0.60, 0.45, 0.55]
118+
data = [rng.normal(loc=m, scale=s, size=400) for m, s in zip(mus, sigmas)]
119+
120+
fig2, ax2 = plt.subplots(figsize=(7, 4), dpi=150)
121+
bp = ax2.boxplot(
122+
data,
123+
labels=categories,
124+
widths=0.6,
125+
patch_artist=True,
126+
showfliers=True,
127+
whis=(5, 95),
128+
)
129+
# Colorize boxes with a palette
130+
palette = ["#1f77b4", "#ff7f0e", "#2ca02c", "#9467bd"]
131+
for box, color in zip(bp["boxes"], palette):
132+
box.set(facecolor=color, edgecolor="black", alpha=0.6)
133+
134+
# Style the rest
135+
for median in bp["medians"]:
136+
median.set(color="black", linewidth=1.4)
137+
for whisker in bp["whiskers"]:
138+
whisker.set(color="black", linewidth=1.0)
139+
for cap in bp["caps"]:
140+
cap.set(color="black", linewidth=1.0)
141+
for flier in bp["fliers"]:
142+
flier.set(marker="o", markersize=3, markerfacecolor="white", markeredgecolor="black", alpha=0.7)
143+
144+
ax2.set_title("Boxplot by Category")
145+
ax2.set_ylabel("Distribution")
146+
ax2.grid(axis="y", linestyle="--", alpha=0.35)
147+
fig2.tight_layout()
148+
149+
pdf.savefig(fig2)
150+
plt.close(fig2)
151+
152+
153+
def warmupIterations():
154+
return 0
155+
156+
157+
def iterations():
158+
return 1
159+
160+
161+
def summary():
162+
return {
163+
"name": "OutlierRemovalAverageSummary",
164+
"lower-threshold": 0.0,
165+
"upper-threshold": 1.0,
166+
}

0 commit comments

Comments
 (0)