Optimization

from scipy.optimize import minimize  
 
f = lambda x: (x-3)**2  
 
res = minimize(f, np.array([2]))  
print(f'argmin: {res.x.item():.5f}, min: {res.fun:.5f}')

Optimization with Constraints and Bounds

f = lambda x: (x[0] - 1) ** 2 + (x[1] - 2.5) ** 2  
  
# inequality constraints should be a form of "eq >= 0".  
constraints = [dict(type='ineq', fun=(lambda x: x[0] - 2 * x[1] + 2)),  
               dict(type='ineq', fun=(lambda x: -x[0] - 2 * x[1] + 6)),  
               dict(type='ineq', fun=(lambda x: -x[0] + 2 * x[1] + 2))]  
bounds = [(0, None), (0, None)]  
res = minimize(f, np.array([1, 2.5]), bounds=bounds, constraints=constraints)  
print(f'argmin: {res.x}, min: {res.fun:.5f}')

Interpolation

['linear', 'quadratic', 'cubic', 'previous', 'nearest', 'next']

from scipy.interpolate import interp1d  
 
f = interp1d(x, y, kind='quadratic')  
x_dense = np.linspace(0, 10, 100)  
y_interp = f(x_dense)  

Curve Fitting

from scipy.optimize import curve_fit  
 
x_data = np.linspace(0, 10, 10)  
y_data = 3 * x_data ** 2 + 2
f = lambda x, a, b: a * x ** 2 + b
 
popt, _ = curve_fit(f, x_data, y_data, p0=[0])  
 
x_dense = np.linspace(0, 10, 100)  
y_dense = f(x_dense, *popt)

Integration

from scipy.integrate import quad  
  
integrand = lambda x: x ** 2 * np.sin(2 * x) * np.exp(-x)  
integral, integral_error = quad(integrand, 0, 1)  

Multiple Integral

from scipy.integrate import dblquad  
  
integrand = lambda y, x: np.sin(x + y ** 2)  
lwr_y, upr_y = lambda x: -x, lambda x: x ** 2  
  
integral, integral_error = dblquad(integrand, 
                                   0, 1, 
                                   lwr_y, upr_y)

Differential Equation

ODE

from scipy.integrate import odeint  
  
alpha, beta = 3, 5  
  
dvdt = lambda v, t: alpha * v ** 2 - beta  
v0 = 0  
  
t_range = np.linspace(0, 1, 100)  
sol = odeint(dvdt, v0, t_range)  
v_range = sol.ravel()

Coupled ODE

def dSdx(S, x):  
    y1, y2 = S  
    dy1dx = y1 + y2 ** 2 + 3 * x  
    dy2dx = 3 * y1 + y2 ** 3 - np.cos(x)  
      
    return dy1dx, dy2dx  
  
  
y1_0, y2_0 = 0, 0  
S_0 = (y1_0, y2_0)  
  
x_range = np.linspace(0, 1, 100)  
sol = odeint(dSdx, S_0, x_range)  
y1, y2 = sol.T  

Second Order ODE

Transform the higher order ODE to the coupled first order ODEs ()

def dSdx(S, t):  
    omega, theta = S  
    d_omega = np.sin(theta)  
    d_theta = omega  
    
    return d_omega, d_theta  
  
omega0 = 0  
theta0 = np.pi / 4  
S0 = (omega0, theta0)  
  
t_range = np.linspace(0, 20, 100)  
sol = odeint(dSdx, S0, t_range)  
omega, theta = sol.T

Fourier Transform

from scipy.fft import fft, fftfreq  
  
  
t = np.linspace(0., 10 * np.pi, 200)    
x = np.sin(2 * np.pi * t) + np.sin(4 * np.pi * t) + 0.5*np.random.randn(len(t))  
  
N = len(x)  
y = fft(x)[:N // 2]  
f = fftfreq(N, t[1] - t[0])[:N // 2]  
  
_, axs = plt.subplots(1, 2, figsize=(8, 3))  
axs[0].plot(f, np.abs(y))  
axs[0].set(title='magnitude plot', xlabel='frequency', ylabel='magnitude')  
  
bin_size = 0.15  
bins = np.arange(0, f.max() + bin_size, bin_size)  
axs[1].hist(f, bins=bins, weights=np.abs(y), align='left', rwidth=0.8)  
axs[1].set(title='magnitude hist', xlabel='frequency bins', ylabel='magnitude')  
plt.show()

Fourier Transform with Zero-Padding

By using zero-padding, we can get smoother spectral plots

from scipy.fft import fft, fftfreq  
  
  
_, axs = plt.subplots(2, 2, figsize=(8, 6))  
  
t = np.linspace(0., 10 * np.pi, 200)  
x = np.sin(2 * np.pi * t) + np.sin(4 * np.pi * t) + 0.5*np.random.randn(len(t))  
N = len(x)  
y = fft(x)[:N // 2]  
f = fftfreq(N, t[1] - t[0])[:N // 2]  
axs[0][0].plot(t, x, lw=1)  
axs[0][0].set(title='original signal')  
axs[0][1].plot(f, np.abs(y),'.-', markersize=5, lw=0.5)  
axs[0][1].set(title='frequencies from original signal')  
  
pad_ratio = 4  
N *= pad_ratio  
y = fft(x, n=N)[:N // 2]  
f = fftfreq(N, t[1] - t[0])[:N // 2]  
  
t = np.linspace(0., t.max()*pad_ratio, N)  
x = np.concat([x, np.zeros(N-len(x))])  
axs[1][0].plot(t, x, lw=1)  
axs[1][0].set(title='4x zero-padded signal')  
axs[1][1].plot(f, np.abs(y),'.-', markersize=5, lw=0.5)  
axs[1][1].set(title='frequencies from zero-padded signal')  
  
plt.show()

Statistics

from scipy.stats import beta  
  
a, b = 2.5, 3.1  
mean, var, skew, kurt = beta.stats(a, b, moments='mvsk') # Get the moments of the distribution
print(f'{mean=:.3f}, {var=:.3f}, {skew=:.3f}, {kurt=:.3f}')  
  
_, ax = plt.subplots(figsize=(6, 3))  
x_range = np.linspace(0, 1, 100)  
ax.plot(x_range, beta.pdf(x_range, a, b)) # Plot the PDF
plt.show()

Custom Distribution

import scipy.stats as st  
 
# Define a custom distribution
class mr_p_solver_dist(st.rv_continuous):  
    def _pdf(self,x, a1, a2, b1, b2):  
        return 1/(2*(a1*b1+a2*b2))*(b1*np.exp(-np.sqrt(x/a1)) + b2*np.exp(-np.sqrt(x/a2)))  
my_rv = mr_p_solver_dist(a=0, b=np.inf)  
 
# Random Sampling  
print(my_rv.rvs(a1, a2, b1, b2, size=10)) 
 
_, ax = plt.subplots(figsize=(5, 3))  
a1, a2, b1, b2 = 2, 3, 1, 2  
x = np.linspace(my_rv.ppf(0.01, a1, a2, b1, b2), my_rv.ppf(0.99, a1, a2, b1, b2), 100)  
y = my_rv.pdf(x, a1, a2, b1, b2)  
ax.plot(x, y)  
ax.set(title='Custom PDF', yscale='log')  
plt.show()