Mie Performance and Jitting
Scott Prahl
Feb 2025
Unfortunately, switching between jit and non-jit during runtime is too complicated when combined with numba caching.
[1]:
import os
import tempfile
import numpy as np
import matplotlib.pyplot as plt
os.environ["MIEPYTHON_USE_JIT"] = "1" # Set to "0" to disable JIT
os.environ["NUMBA_CACHE_DIR"] = tempfile.gettempdir()
import miepython as mie
Size Parameters
We will use %timeit to see speeds for unjitted code, then jitted code
[2]:
ntests = 6
m = 1.5
N = np.logspace(0, 3, ntests, dtype=int)
result = np.zeros(ntests)
resultj = np.zeros(ntests)
for i in range(ntests):
x = np.linspace(0.1, 20, N[i])
a = %timeit -o qext, qsca, qback, g = mie.mie(m,x)
result[i] = a.best
# mie.use_numba(True) # Ensure the JIT backend is used
# for i in range(ntests):
# x = np.linspace(0.1, 20, N[i])
# a = %timeit -o qext, qsca, qback, g = mie.mie(m,x)
# resultj[i] = a.best
# improvement = result / resultj
# plt.loglog(N, resultj, ":r")
# plt.loglog(N, result, ":b")
# plt.loglog(N, resultj, "or", label="jit")
# plt.loglog(N, result, "ob", label="no jit")
# plt.legend()
# plt.xlabel("Number of sphere sizes calculated")
# plt.ylabel("Execution Time")
# plt.title("Jit improvement is %d to %dX" % (np.min(improvement), np.max(improvement)))
# plt.show()
16 μs ± 259 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
47.2 μs ± 267 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
233 μs ± 763 ns per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
985 μs ± 6 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
3.87 ms ± 20 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
15.7 ms ± 302 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Embedded spheres
[ ]:
ntests = 6
mwater = 4 / 3 # rough approximation
m = 1.0
mm = m / mwater
r = 500 # nm
N = np.logspace(0, 3, ntests, dtype=int)
result = np.zeros(ntests)
resultj = np.zeros(ntests)
for i in range(ntests):
lambda0 = np.linspace(300, 800, N[i]) # also in nm
xx = 2 * np.pi * r * mwater / lambda0
a = %timeit -o qext, qsca, qback, g = mie.mie(mm,xx)
result[i] = a.best
# mie.use_numba(True) # Ensure the JIT backend is used
# for i in range(ntests):
# lambda0 = np.linspace(300, 800, N[i]) # also in nm
# xx = 2 * np.pi * r * mwater / lambda0
# a = %timeit -o qext, qsca, qback, g = mie.mie(mm,xx)
# resultj[i] = a.best
# improvement = result / resultj
# plt.loglog(N, resultj, ":r")
# plt.loglog(N, result, ":b")
# plt.loglog(N, resultj, "or", label="jit")
# plt.loglog(N, result, "ob", label="no jit")
# plt.legend()
# plt.xlabel("Number of Wavelengths Calculated")
# plt.ylabel("Execution Time")
# plt.title("Jit improvement is %d to %dX" % (np.min(improvement), np.max(improvement)))
# plt.show()
Testing ez_mie
Another high level function that should be sped up by jitting.
[ ]:
ntests = 6
m_sphere = 1.0
n_water = 4 / 3
d = 1000 # nm
N = np.logspace(0, 3, ntests, dtype=int)
result = np.zeros(ntests)
resultj = np.zeros(ntests)
for i in range(ntests):
lambda0 = np.linspace(300, 800, N[i]) # also in nm
a = %timeit -o qext, qsca, qback, g = mie.ez_mie(m_sphere, d, lambda0, n_water)
result[i] = a.best
# mie.use_numba(True) # Ensure the JIT backend is used
# for i in range(ntests):
# lambda0 = np.linspace(300, 800, N[i]) # also in nm
# a = %timeit -o qext, qsca, qback, g = mie.ez_mie(m_sphere, d, lambda0, n_water)
# resultj[i] = a.best
# improvement = result / resultj
# plt.loglog(N, resultj, ":r")
# plt.loglog(N, result, ":b")
# plt.loglog(N, resultj, "or", label="jit")
# plt.loglog(N, result, "ob", label="no jit")
# plt.legend()
# plt.xlabel("Number of Wavelengths Calculated")
# plt.ylabel("Execution Time")
# plt.title("Jit improvement is %d to %dX" % (np.min(improvement), np.max(improvement)))
# plt.show()
Scattering Phase Function
[ ]:
ntests = 6
m = 1.5
x = np.pi / 3
N = np.logspace(0, 3, ntests, dtype=int)
result = np.zeros(ntests)
resultj = np.zeros(ntests)
for i in range(ntests):
theta = np.linspace(-180, 180, N[i])
mu = np.cos(theta / 180 * np.pi)
a = %timeit -o s1, s2 = mie.mie_S1_S2(m,x,mu)
result[i] = a.best
# for i in range(ntests):
# theta = np.linspace(-180, 180, N[i])
# mu = np.cos(theta / 180 * np.pi)
# a = %timeit -o s1, s2 = mie.mie_S1_S2(m,x,mu)
# resultj[i] = a.best
# improvement = result / resultj
# plt.loglog(N, resultj, ":r")
# plt.loglog(N, result, ":b")
# plt.loglog(N, resultj, "or", label="jit")
# plt.loglog(N, result, "ob", label="no jit")
# plt.legend()
# plt.xlabel("Number of Angles Calculated")
# plt.ylabel("Execution Time")
# plt.title("Jit improvement is %d to %dX" % (np.min(improvement), np.max(improvement)))
# plt.show()
And finally, as function of sphere size
[ ]:
ntests = 6
m = 1.5 - 0.1j
x = np.logspace(0, 3, ntests)
result = np.zeros(ntests)
resultj = np.zeros(ntests)
theta = np.linspace(-180, 180)
mu = np.cos(theta / 180 * np.pi)
for i in range(ntests):
a = %timeit -o s1, s2 = mie.mie_S1_S2(m,x[i],mu)
result[i] = a.best
# mie.use_numba(True) # Ensure the JIT backend is used
# for i in range(ntests):
# a = %timeit -o s1, s2 = mie.mie_S1_S2(m,x[i],mu)
# resultj[i] = a.best
# improvement = result / resultj
# plt.loglog(N, resultj, ":r")
# plt.loglog(N, result, ":b")
# plt.loglog(N, resultj, "or", label="jit")
# plt.loglog(N, result, "ob", label="no jit")
# plt.legend()
# plt.xlabel("Sphere Size Parameter")
# plt.ylabel("Execution Time")
# plt.title("Jit improvement is %d to %dX" % (np.min(improvement), np.max(improvement)))
# plt.show()
[ ]: