Skip to content

Commit 4507195

Browse files
authored
Merge pull request #329 from python-adaptive/feat/animated-logo
Add an animated logo that shows the working of Adaptive
2 parents 0d27717 + 82d72dd commit 4507195

File tree

5 files changed

+130
-4
lines changed

5 files changed

+130
-4
lines changed

README.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88

99
*Adaptive*: parallel active learning of mathematical functions.
1010

11+
.. animated-logo::
12+
1113
``adaptive`` is an open-source Python library designed to
1214
make adaptive parallel function evaluation simple. With ``adaptive`` you
1315
just supply a function with its bounds, and it will be evaluated at the
14-
“best” points in parameter space, rather than unecessarily computing *all* points on a dense grid.
16+
“best” points in parameter space, rather than unnecessarily computing *all* points on a dense grid.
1517
With just a few lines of code you can evaluate functions on a computing cluster,
1618
live-plot the data as it returns, and fine-tune the adaptive sampling algorithm.
1719

docs/environment.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ dependencies:
2020
- sphinx=4.2.0
2121
- m2r2=0.3.1
2222
- sphinx_rtd_theme=1.0.0
23+
- ffmpeg=4.3.2

docs/logo.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,13 @@ def add_rounded_corners(fname, rad):
5252
return im
5353

5454

55-
if __name__ == "__main__":
55+
def main(fname="source/_static/logo_docs.png"):
5656
learner = create_and_run_learner()
57-
fname = "source/_static/logo_docs.png"
5857
plot_learner_and_save(learner, fname)
5958
im = add_rounded_corners(fname, rad=200)
6059
im.thumbnail((200, 200), Image.ANTIALIAS) # resize
6160
im.save(fname)
61+
62+
63+
if __name__ == "__main__":
64+
main()

docs/logo_animated.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import matplotlib.tri as mtri
2+
import numpy as np
3+
from matplotlib import animation
4+
from matplotlib import pyplot as plt
5+
from matplotlib.animation import FFMpegWriter
6+
from PIL import Image, ImageDraw
7+
from tqdm.auto import tqdm
8+
9+
import adaptive
10+
11+
12+
def add_rounded_corners(size, rad):
13+
# Make new images
14+
circle = Image.new("L", (rad * 2, rad * 2), color=1)
15+
draw = ImageDraw.Draw(circle)
16+
draw.ellipse((0, 0, rad * 2, rad * 2), fill=0)
17+
alpha = Image.new("L", size, 0)
18+
19+
# Crop circles
20+
w, h = size
21+
alpha.paste(circle.crop((0, 0, rad, rad)), (0, 0))
22+
alpha.paste(circle.crop((0, rad, rad, rad * 2)), (0, h - rad))
23+
alpha.paste(circle.crop((rad, 0, rad * 2, rad)), (w - rad, 0))
24+
alpha.paste(circle.crop((rad, rad, rad * 2, rad * 2)), (w - rad, h - rad))
25+
26+
# To array
27+
cut = np.array(alpha)
28+
cut = cut.reshape((*cut.shape, 1)).repeat(4, axis=2)
29+
30+
# Set the corners to (252, 252, 252, 255) to match the RTD background #FCFCFC
31+
cut[:, :, -1] *= 255
32+
cut[:, :, :-1] *= 252
33+
return cut
34+
35+
36+
def learner_till(till, learner, data):
37+
new_learner = adaptive.Learner2D(None, bounds=learner.bounds)
38+
new_learner.data = {k: v for k, v in data[:till]}
39+
for x, y in learner._bounds_points:
40+
# always include the bounds
41+
new_learner.tell((x, y), learner.data[x, y])
42+
return new_learner
43+
44+
45+
def plot_tri(learner, ax):
46+
tri = learner.ip().tri
47+
triang = mtri.Triangulation(*tri.points.T, triangles=tri.vertices)
48+
return ax.triplot(triang, c="k", lw=0.8, alpha=0.8)
49+
50+
51+
def get_new_artists(npoints, learner, data, rounded_corners, ax):
52+
new_learner = learner_till(npoints, learner, data)
53+
line1, line2 = plot_tri(new_learner, ax)
54+
data = np.rot90(new_learner.interpolated_on_grid()[-1])
55+
im = ax.imshow(data, extent=(-0.5, 0.5, -0.5, 0.5), cmap="viridis")
56+
im2 = ax.imshow(rounded_corners, extent=(-0.5, 0.5, -0.5, 0.5), zorder=10)
57+
return im, line1, line2, im2
58+
59+
60+
def create_and_run_learner():
61+
def ring(xy):
62+
import numpy as np
63+
64+
x, y = xy
65+
a = 0.2
66+
return x + np.exp(-((x ** 2 + y ** 2 - 0.75 ** 2) ** 2) / a ** 4)
67+
68+
learner = adaptive.Learner2D(ring, bounds=[(-1, 1), (-1, 1)])
69+
adaptive.runner.simple(learner, goal=lambda l: l.loss() < 0.005)
70+
return learner
71+
72+
73+
def main(fname="source/_static/logo_docs.mp4"):
74+
learner = create_and_run_learner()
75+
76+
data = list(learner.data.items())
77+
78+
fig, ax = plt.subplots(figsize=(5, 5))
79+
fig.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=None, hspace=None)
80+
ax.set_xticks([])
81+
ax.set_yticks([])
82+
ax.spines["top"].set_visible(False)
83+
ax.spines["right"].set_visible(False)
84+
ax.spines["bottom"].set_visible(False)
85+
ax.spines["left"].set_visible(False)
86+
87+
nseconds = 15
88+
npoints = (len(data) * np.linspace(0, 1, 24 * nseconds) ** 2).astype(int)
89+
rounded_corners = add_rounded_corners(size=(1000, 1000), rad=300)
90+
artists = [
91+
get_new_artists(n, learner, data, rounded_corners, ax) for n in tqdm(npoints)
92+
]
93+
94+
ani = animation.ArtistAnimation(fig, artists, blit=True)
95+
ani.save(fname, writer=FFMpegWriter(fps=24))
96+
97+
98+
if __name__ == "__main__":
99+
main()

docs/source/conf.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,17 @@
1414
import os
1515
import sys
1616

17+
from docutils import nodes
18+
from docutils.parsers.rst import Directive
19+
1720
package_path = os.path.abspath("../..")
1821
# Insert into sys.path so that we can import adaptive here
1922
sys.path.insert(0, package_path)
2023
# Insert into PYTHONPATH so that jupyter-sphinx will pick it up
2124
os.environ["PYTHONPATH"] = ":".join((package_path, os.environ.get("PYTHONPATH", "")))
25+
# Insert `docs/` such that we can run the logo scripts
26+
docs_path = os.path.abspath("..")
27+
sys.path.insert(1, docs_path)
2228

2329
import adaptive # noqa: E402, isort:skip
2430

@@ -151,8 +157,23 @@
151157
]
152158

153159

154-
html_logo = "logo_docs.png"
160+
html_logo = "_static/logo_docs.png"
161+
162+
163+
class RunLogoAnimated(Directive):
164+
def run(self):
165+
fname = "_static/logo_docs.mp4"
166+
if not os.path.exists(fname):
167+
import logo_animated
168+
169+
print(f"{fname} does not exist.")
170+
logo_animated.main(fname)
171+
style = "width: 400px; max-width: 100%; margin: 0 auto; display:block;"
172+
opts = f'autoplay loop muted playsinline webkit-playsinline style="{style}"'
173+
html = f'<video {opts}><source src="{fname}" type="video/mp4"></video><br>'
174+
return [nodes.raw(text=html, format="html")]
155175

156176

157177
def setup(app):
158178
app.add_css_file("custom.css") # For the `live_info` widget
179+
app.add_directive("animated-logo", RunLogoAnimated)

0 commit comments

Comments
 (0)