Monopoles, dipoles and quadrupole animations
In this post, we're going to produce a couple of animations related to typical sound sources encountered in the study of acoustics: monopole, dipole and quadrupole sources (using holoviews with the matplotlib backend).
We start by creating an $(x, y)$ grid of space.
import numpy as np
import holoviews as hv
hv.extension('matplotlib', logo=False)
x = np.linspace(-15, 15, num=100)
y = np.linspace(-15, 15, num=100)
X, Y = np.meshgrid(x, y)
Using this grid, we can build a phase map for a monopole source. By phase map, I mean the change in phase, at the selected frequency that a radial wave would incur by propagating from the origin to the point of the grid.
r = np.sqrt(X**2 + Y**2)
phase_map1= np.exp(1j * r)
hv.QuadMesh((X, Y, np.real(phase_map1)))
We can animate this phase map by adding a phase term that varies as a function of time. If we discretize a single period in a given number of frames and loop over the frames, we can generate an "infinite" animation. Let's try this!
N = 15
%%output holomap='scrubber'
hmap1 = hv.HoloMap({i: hv.QuadMesh((X, Y, np.real(phase_map1* np.exp(-1j * i / N * 2 * np.pi))),
label='monopole') for i in range(N)}).opts(colorbar=True)
hmap1
Let's apply the same logic to a dipole, made out of two sources a fraction of a wavelength apart and out of phase.
delta = 2.5
r1 = np.sqrt((X-delta)**2 + Y**2)
r2 = np.sqrt((X+delta)**2 + Y**2)
phase_map2 = .5 * np.exp(1j * r1) - .5 * np.exp(1j * r2)
%%output holomap='scrubber'
hmap2 = hv.HoloMap({i: hv.QuadMesh((X, Y, np.real(phase_map2 * np.exp(-1j * i / N * 2 * np.pi))),
label='dipole') for i in range(N)}).opts(colorbar=True)
hmap2
Finally, the same formulas apply to a quadripole source, which is made out of four sources, two of them being out of phase.
gamma = 4.5
r1 = np.sqrt((X - gamma)**2 + (Y - gamma)**2)
r2 = np.sqrt((X - gamma)**2 + (Y + gamma)**2)
r3 = np.sqrt((X + gamma)**2 + (Y - gamma)**2)
r4 = np.sqrt((X + gamma)**2 + (Y + gamma)**2)
phase_map3 = .25 * np.exp(1j * r1) - .25 * np.exp(1j * r2) + .25 * np.exp(1j * r3) - .25 * np.exp(1j * r4)
%%output holomap='scrubber'
hmap3 = hv.HoloMap({i: hv.QuadMesh((X, Y, np.real(phase_map3 * np.exp(-1j * i / N * 2 * np.pi))),
label='quadrupole') for i in range(N)}).opts(colorbar=True)
hmap3
As a final step, let's add a point cloud of particle moving on top of these monopole source. We first randomly create some points:
M = 400
Xm = x.min() + np.random.rand(M) * (x.max() - x.min())
Ym = y.min() + np.random.rand(M) * (y.max() - y.min())
Rm = np.sqrt(Xm**2 + Ym**2)
Tm = np.arctan2(Ym, Xm)
Build a phase map, using the same formula as before, but inserting the coordinates of the points.
points = np.c_[Rm * np.cos(Tm), Rm * np.sin(Tm)]
phase_map4 = 1j * np.exp(1j * 1 * Rm)
And then we animate it using an oscillation in time.
%%output holomap='scrubber'
hmap4 = hv.HoloMap({i: hv.Points(points + np.c_[np.real(phase_map4 * np.cos(Tm) * np.exp(-1j * i / N * 2 * np.pi)),
np.real(phase_map4 * np.sin(Tm) * np.exp(-1j * i / N * 2 * np.pi))],
label='points') for i in range(N)})
hmap4
The structure of the wave is visualized thanks to the particle displacement.
Using the nice plotting API of holoviews, we can even overlay this animation with our previous monopole field animation:
%%output holomap='scrubber'
hmap1 * hmap4.opts(show_legend=False)
If you like this sort of animations, a fantastic resource for endless variations on the "wavy" theme can be found at Bees and bombs.
This post was entirely written using the IPython notebook. Its content is BSD-licensed. You can see a static view or download this notebook with the help of nbviewer at 20190129_MonopoleDipoleQuadrupole.ipynb.