import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
from mpl_toolkits.mplot3d import Axes3D
from scipy.stats import norm
# --- 1. Define the Cardoso / Mathematical Theme ---
def set_cardoso_theme():
mpl.rcParams.update({
'font.family': 'serif',
'mathtext.fontset': 'cm', # Computer Modern font (standard LaTeX)
'font.size': 12,
'axes.labelsize': 14,
'axes.grid': False, # No grids for abstract math diagrams
'axes.spines.top': False,
'axes.spines.right': False,
'legend.frameon': False,
'figure.dpi': 300,
'text.color': 'black',
'axes.edgecolor': 'black'
})
set_cardoso_theme()
# =====================================================================
# DIAGRAM 1: Riemannian Gradient on the Stiefel Manifold
# =====================================================================
def plot_manifold_projection():
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')
ax.set_axis_off() # Hide standard 3D axes for a clean geometric sketch
# 1. Draw the Manifold Surface (A curved sphere segment)
u = np.linspace(0, np.pi/2.5, 50)
v = np.linspace(0, np.pi/2.5, 50)
x = np.outer(np.sin(u), np.cos(v))
y = np.outer(np.sin(u), np.sin(v))
z = np.outer(np.cos(u), np.ones_like(v))
# Manifold surface: very faint and transparent
ax.plot_surface(x, y, z, color='#B0B0B0', alpha=0.6, edgecolor='none', shade=True)
# 2. Define our point W on the manifold
W = np.array([np.sin(np.pi/4)*np.cos(np.pi/4), np.sin(np.pi/4)*np.sin(np.pi/4), np.cos(np.pi/4)])
# 3. Draw the Tangent Space (A flat plane touching W)
d = -W.dot(W)
xx, yy = np.meshgrid(np.linspace(W[0]-0.4, W[0]+0.4, 2), np.linspace(W[1]-0.4, W[1]+0.4, 2))
zz = (-W[0]*xx - W[1]*yy - d) / W[2]
# CHANGED: Tangent plane is now stark white, high opacity, with a distinct edge
ax.plot_surface(xx, yy, zz, color='#F8F8F8', alpha=0.3, edgecolor='#B0B0B0', linewidth=0.8)
# 4. Vectors
G = W + np.array([-0.1, 0.3, 0.4])
G_vec = G - W
proj_length = np.dot(G_vec, W)
riemannian_vec = G_vec - proj_length * W
G_riemannian = W + riemannian_vec
# Plot Point W
ax.scatter(*W, color='black', s=50, zorder=5)
ax.text(W[0]+0.2, W[1]-0.1, W[2]-0.05, r'$\mathbf{W}$', fontsize=16)
# Plot Euclidean Gradient G
ax.quiver(W[0], W[1], W[2], G_vec[0], G_vec[1], G_vec[2], color='gray', arrow_length_ratio=0.1, linewidth=1.5, linestyle='--')
ax.text(G[0]+0.02, G[1]+0.02, G[2]+0.02, r'$\mathbf{G} = \nabla_{\mathbf{W}} W_2^2$', fontsize=14, color='gray')
# Plot Riemannian Gradient
ax.quiver(W[0], W[1], W[2], riemannian_vec[0], riemannian_vec[1], riemannian_vec[2], color='black', arrow_length_ratio=0.1, linewidth=2)
ax.text(G_riemannian[0] +0.05, G_riemannian[1]-0.12, G_riemannian[2]-0.05, r'$\nabla_{\mathcal{S}} \mathbf{W}$', fontsize=16, color='black')
# Retraction Step
step_point = W + riemannian_vec * 0.8
retracted_point = step_point / np.linalg.norm(step_point)
ax.plot([step_point[0], retracted_point[0]], [step_point[1], retracted_point[1]], [step_point[2], retracted_point[2]], color='black', linestyle=':', linewidth=2)
ax.scatter(*retracted_point, color='black', s=30, zorder=5)
ax.text(retracted_point[0]+0.05, retracted_point[1]+0.1, retracted_point[2]-0.05, r'$\mathbf{W}_{\text{new}}$', fontsize=14)
# Label the spaces
ax.text(0.1, 0.1, 0.9, r'$\mathcal{S}$ (Stiefel)', fontsize=14)
ax.text(W[0]+0.2, W[1]-0.3, zz[1,1]+0.05, r'$T_{\mathbf{W}}\mathcal{S}$', fontsize=14)
# CHANGED: Adjusted viewing angle for better depth perception
ax.view_init(elev=20, azim=100)
plt.tight_layout()
plt.savefig('stiefel_manifold_projection.png', dpi=300, bbox_inches='tight')
plt.show()
# =====================================================================
# DIAGRAM 2: Discrete CDF Smoothed by Gaussian Dithering
# =====================================================================
def plot_dithering_cdf():
fig, ax = plt.figure(figsize=(8, 5)), plt.gca()
n_points = 150
discrete_data = np.random.choice([0, 1, 2, 3], size=n_points, p=[0.1, 0.4, 0.3, 0.2])
discrete_data = np.sort(discrete_data)
y_discrete = np.arange(1, n_points + 1) / n_points
# REDUCED SIGMA FOR TIGHTER FIT
sigma_dither = 0.05
dithered_data = discrete_data + np.random.normal(0, sigma_dither, size=n_points)
dithered_data = np.sort(dithered_data)
y_dithered = np.arange(1, n_points + 1) / n_points
ax.step(discrete_data, y_discrete, where='post', color='gray', linewidth=2.5, label='Discrete Empirical CDF (Staircase)')
ax.plot(dithered_data, y_dithered, color='black', linewidth=2, label='Dithered CDF (Continuous)')
# NARROWER VISUAL KERNEL
x_kernel = np.linspace(0.8, 1.2, 100)
# Scaled down to fit nicely above the step
y_kernel = 0.5 + 0.035 * norm.pdf(x_kernel, loc=1, scale=sigma_dither)
ax.plot(x_kernel, y_kernel, color='black', linestyle=':', linewidth=1.5)
ax.fill_between(x_kernel, 0.5, y_kernel, color='#E0E0E0', alpha=0.5)
ax.text(1.1, 0.65, r'$\sim \mathcal{N}(0, \sigma_{\text{dither}}^2)$', fontsize=12)
ax.annotate('', xy=(2.0, 0.65), xytext=(2.0, 0.8),
arrowprops=dict(arrowstyle='->', color='black', lw=1.5, ls=':'))
ax.set_xlabel('Projected Value $y = \mathbf{w}^\top \mathbf{x}$', fontsize=14)
ax.set_ylabel('Cumulative Probability $F_{\mathbf{w}}(y)$', fontsize=14)
ax.set_xlim(-0.5, 3.5)
ax.set_ylim(0, 1.05)
ax.set_xticks([0, 1, 2, 3])
ax.legend(loc='lower right', fontsize=12)
plt.tight_layout()
plt.savefig('gaussian_dithering_cdf.png', dpi=300, bbox_inches='tight')
plt.close()
# Run the functions
plot_manifold_projection()
plot_dithering_cdf()
print("Saved 'stiefel_manifold_projection.png' and 'gaussian_dithering_cdf.png'")