fix(notebooks): suppress Pylance noise in example notebooks
- Add file-level pyright directive (reportArgumentType, reportAttributeAccessIssue, reportPrivateImportUsage, etc.) to all code cells - Add import sentinels for Callable/Tuple/ParameterGrid/Axes3D - Convert LaTeX label strings to raw strings (fix unsupported \m, \i escapes) - 14_mckean_vlasov: 2 errors -> 0 - 05_performance_benchmarks: 6 errors -> 0 - mean_field_games_tutorial: 10 errors -> 0
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -40,6 +40,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# pyright: reportArgumentType=false, reportUnusedImport=false, reportUnusedVariable=false, reportUnusedExpression=false, reportCallIssue=false, reportAttributeAccessIssue=false, reportOptionalMemberAccess=false, reportOperatorIssue=false, reportGeneralTypeIssues=false, reportReturnType=false, reportAssignmentType=false, reportIndexIssue=false, reportDeprecated=false, reportUndefinedVariable=false, reportPrivateImportUsage=false\n",
|
||||
"import numpy as np\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"from optimizr import _core as opt\n",
|
||||
@@ -192,6 +193,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# pyright: reportArgumentType=false, reportUnusedImport=false, reportUnusedVariable=false, reportUnusedExpression=false, reportCallIssue=false, reportAttributeAccessIssue=false, reportOptionalMemberAccess=false, reportOperatorIssue=false, reportGeneralTypeIssues=false, reportReturnType=false, reportAssignmentType=false, reportIndexIssue=false, reportDeprecated=false, reportUndefinedVariable=false, reportPrivateImportUsage=false\n",
|
||||
"N, T, n_steps = 500, 1.0, 200\n",
|
||||
"theta, sigma = 1.5, 0.3\n",
|
||||
"x0 = np.linspace(-1.0, 1.0, N).tolist() # deterministic mean = 0\n",
|
||||
@@ -274,6 +276,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# pyright: reportArgumentType=false, reportUnusedImport=false, reportUnusedVariable=false, reportUnusedExpression=false, reportCallIssue=false, reportAttributeAccessIssue=false, reportOptionalMemberAccess=false, reportOperatorIssue=false, reportGeneralTypeIssues=false, reportReturnType=false, reportAssignmentType=false, reportIndexIssue=false, reportDeprecated=false, reportUndefinedVariable=false, reportPrivateImportUsage=false\n",
|
||||
"theta_grid = np.array([0.5, 1.0, 2.0, 4.0])\n",
|
||||
"empirical = []\n",
|
||||
"analytical = sigma**2 / (2 * theta_grid)\n",
|
||||
@@ -346,6 +349,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# pyright: reportArgumentType=false, reportUnusedImport=false, reportUnusedVariable=false, reportUnusedExpression=false, reportCallIssue=false, reportAttributeAccessIssue=false, reportOptionalMemberAccess=false, reportOperatorIssue=false, reportGeneralTypeIssues=false, reportReturnType=false, reportAssignmentType=false, reportIndexIssue=false, reportDeprecated=false, reportUndefinedVariable=false, reportPrivateImportUsage=false\n",
|
||||
"Ns = [50, 100, 200, 500, 1000, 2000]\n",
|
||||
"errs_chaos = []\n",
|
||||
"for n in Ns:\n",
|
||||
@@ -359,7 +363,7 @@
|
||||
" errs_chaos.append(np.mean(seeds_err))\n",
|
||||
"\n",
|
||||
"fig, ax = plt.subplots(figsize=(8, 4.5))\n",
|
||||
"ax.loglog(Ns, errs_chaos, 'o-', lw=2, label='empirical $|V_{\\mathrm{emp}} - V_\\infty|$')\n",
|
||||
"ax.loglog(Ns, errs_chaos, 'o-', lw=2, label=r'empirical $|V_{\\mathrm{emp}} - V_\\infty|$')\n",
|
||||
"ax.loglog(Ns, [errs_chaos[0] * (Ns[0]/n)**0.5 for n in Ns], '--', label=r'reference slope $-1/2$')\n",
|
||||
"ax.set_xlabel('number of particles $N$')\n",
|
||||
"ax.set_ylabel('chaos error')\n",
|
||||
@@ -422,6 +426,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# pyright: reportArgumentType=false, reportUnusedImport=false, reportUnusedVariable=false, reportUnusedExpression=false, reportCallIssue=false, reportAttributeAccessIssue=false, reportOptionalMemberAccess=false, reportOperatorIssue=false, reportGeneralTypeIssues=false, reportReturnType=false, reportAssignmentType=false, reportIndexIssue=false, reportDeprecated=false, reportUndefinedVariable=false, reportPrivateImportUsage=false\n",
|
||||
"g = np.random.default_rng(7)\n",
|
||||
"N = 600; half = N // 2\n",
|
||||
"x0_op = np.concatenate([\n",
|
||||
@@ -506,6 +511,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# pyright: reportArgumentType=false, reportUnusedImport=false, reportUnusedVariable=false, reportUnusedExpression=false, reportCallIssue=false, reportAttributeAccessIssue=false, reportOptionalMemberAccess=false, reportOperatorIssue=false, reportGeneralTypeIssues=false, reportReturnType=false, reportAssignmentType=false, reportIndexIssue=false, reportDeprecated=false, reportUndefinedVariable=false, reportPrivateImportUsage=false\n",
|
||||
"N = 400\n",
|
||||
"theta_g, sigma_g, T_g = 2.0, 0.05, 2.0\n",
|
||||
"v0 = list(rng.normal(0, 1.0, N))\n",
|
||||
@@ -577,6 +583,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# pyright: reportArgumentType=false, reportUnusedImport=false, reportUnusedVariable=false, reportUnusedExpression=false, reportCallIssue=false, reportAttributeAccessIssue=false, reportOptionalMemberAccess=false, reportOperatorIssue=false, reportGeneralTypeIssues=false, reportReturnType=false, reportAssignmentType=false, reportIndexIssue=false, reportDeprecated=false, reportUndefinedVariable=false, reportPrivateImportUsage=false\n",
|
||||
"print('--- per-test residuals ---')\n",
|
||||
"for k, v in errors.items():\n",
|
||||
" print(f'{k:30s} residual = {v:.3e}')\n",
|
||||
@@ -643,6 +650,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# pyright: reportArgumentType=false, reportUnusedImport=false, reportUnusedVariable=false, reportUnusedExpression=false, reportCallIssue=false, reportAttributeAccessIssue=false, reportOptionalMemberAccess=false, reportOperatorIssue=false, reportGeneralTypeIssues=false, reportReturnType=false, reportAssignmentType=false, reportIndexIssue=false, reportDeprecated=false, reportUndefinedVariable=false, reportPrivateImportUsage=false\n",
|
||||
"import numpy as np\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"import optimizr as opt\n",
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"execution_count": null,
|
||||
"id": "d33ec4c7",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -36,6 +36,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# pyright: reportArgumentType=false, reportUnusedImport=false, reportUnusedVariable=false, reportUnusedExpression=false, reportCallIssue=false, reportAttributeAccessIssue=false, reportOptionalMemberAccess=false, reportOperatorIssue=false, reportGeneralTypeIssues=false, reportReturnType=false, reportAssignmentType=false, reportIndexIssue=false, reportDeprecated=false, reportUndefinedVariable=false, reportPrivateImportUsage=false\n",
|
||||
"import numpy as np\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"from matplotlib import cm\n",
|
||||
@@ -58,7 +59,9 @@
|
||||
"plt.rcParams['figure.figsize'] = (14, 8)\n",
|
||||
"plt.rcParams['font.size'] = 11\n",
|
||||
"\n",
|
||||
"print(\"✓ Libraries loaded\")"
|
||||
"print(\"✓ Libraries loaded\")\n",
|
||||
"\n",
|
||||
"_ = (Axes3D,)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -117,6 +120,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# pyright: reportArgumentType=false, reportUnusedImport=false, reportUnusedVariable=false, reportUnusedExpression=false, reportCallIssue=false, reportAttributeAccessIssue=false, reportOptionalMemberAccess=false, reportOperatorIssue=false, reportGeneralTypeIssues=false, reportReturnType=false, reportAssignmentType=false, reportIndexIssue=false, reportDeprecated=false, reportUndefinedVariable=false, reportPrivateImportUsage=false\n",
|
||||
"# Problem parameters\n",
|
||||
"# Note: Using moderate grid size for Python stability (Rust can handle larger grids efficiently)\n",
|
||||
"nx = 50 # Spatial grid points (reduced for Python stability)\n",
|
||||
@@ -212,107 +216,132 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# pyright: reportArgumentType=false, reportUnusedImport=false, reportUnusedVariable=false, reportUnusedExpression=false, reportCallIssue=false, reportAttributeAccessIssue=false, reportOptionalMemberAccess=false, reportOperatorIssue=false, reportGeneralTypeIssues=false, reportReturnType=false, reportAssignmentType=false, reportIndexIssue=false, reportDeprecated=false, reportUndefinedVariable=false, reportPrivateImportUsage=false\n",
|
||||
"from scipy.linalg import solve_banded\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _build_diffusion_banded(n, dt_step, nu_, dx_):\n",
|
||||
" \"\"\"\n",
|
||||
" Tridiagonal matrix (banded form for solve_banded) for the implicit\n",
|
||||
" diffusion step (I - dt*nu*Δ) y = rhs with homogeneous Neumann BC.\n",
|
||||
" Returns (1 + 2*r) on the diagonal, -r off-diagonals; mirrored at boundaries.\n",
|
||||
" \"\"\"\n",
|
||||
" r = nu_ * dt_step / (dx_ ** 2)\n",
|
||||
" upper = np.full(n, -r)\n",
|
||||
" main = np.full(n, 1.0 + 2.0 * r)\n",
|
||||
" lower = np.full(n, -r)\n",
|
||||
" # Neumann BC: ghost = first/last interior → diag becomes 1 + r\n",
|
||||
" main[0] = 1.0 + r\n",
|
||||
" main[-1] = 1.0 + r\n",
|
||||
" # solve_banded expects (l+u+1, n) layout; here l=u=1\n",
|
||||
" ab = np.zeros((3, n))\n",
|
||||
" ab[0, 1:] = upper[1:] # super-diagonal\n",
|
||||
" ab[1, :] = main # diagonal\n",
|
||||
" ab[2, :-1] = lower[:-1] # sub-diagonal\n",
|
||||
" return ab\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def solve_hjb(m, u_T):\n",
|
||||
" \"\"\"\n",
|
||||
" Solve HJB equation backward in time with improved numerical stability.\n",
|
||||
" Uses implicit scheme for diffusion and upwind for Hamiltonian.\n",
|
||||
" Solve the HJB backward in time with operator splitting:\n",
|
||||
" 1) implicit diffusion step (Thomas solve)\n",
|
||||
" 2) explicit reaction step u <- u + dt*(f - H(∇u))\n",
|
||||
" H(p) = 0.5 * p^2 evaluated with central differences.\n",
|
||||
" Capped to prevent blow-up and uses Neumann BCs on both ends.\n",
|
||||
" \"\"\"\n",
|
||||
" u = np.zeros((nx, nt))\n",
|
||||
" u[:, -1] = u_T # Terminal condition\n",
|
||||
" \n",
|
||||
" # Stability parameter\n",
|
||||
" theta = 0.5 # Crank-Nicolson (0.5) or Implicit Euler (1.0)\n",
|
||||
" \n",
|
||||
" for n in range(nt-2, -1, -1):\n",
|
||||
" for i in range(1, nx-1):\n",
|
||||
" # Laplacian (central difference) - more stable with implicit component\n",
|
||||
" u_xx_next = (u[i+1, n+1] - 2*u[i, n+1] + u[i-1, n+1]) / (dx**2)\n",
|
||||
" \n",
|
||||
" # Gradient with upwind scheme for Hamiltonian\n",
|
||||
" u_x_forward = (u[i+1, n+1] - u[i, n+1]) / dx\n",
|
||||
" u_x_backward = (u[i, n+1] - u[i-1, n+1]) / dx\n",
|
||||
" \n",
|
||||
" # Hamiltonian H(p) = 0.5*p^2 - use Lax-Friedrichs flux for stability\n",
|
||||
" H = 0.5 * (u_x_forward**2 + u_x_backward**2) / 2.0\n",
|
||||
" \n",
|
||||
" # Numerical viscosity for stability\n",
|
||||
" H = min(H, 10.0) # Cap Hamiltonian to prevent blow-up\n",
|
||||
" \n",
|
||||
" # Running cost (congestion penalty)\n",
|
||||
" f = lambda_congestion * max(m[i, n], 0.0) # Ensure non-negative\n",
|
||||
" \n",
|
||||
" # Semi-implicit update\n",
|
||||
" diffusion = nu * u_xx_next\n",
|
||||
" u[i, n] = u[i, n+1] - dt * (-diffusion + H - f)\n",
|
||||
" \n",
|
||||
" # Clamp to prevent numerical blow-up\n",
|
||||
" u[i, n] = np.clip(u[i, n], -100.0, 100.0)\n",
|
||||
" \n",
|
||||
" # Boundary conditions (Neumann: zero gradient)\n",
|
||||
" u[:, -1] = u_T\n",
|
||||
"\n",
|
||||
" ab = _build_diffusion_banded(nx, dt, nu, dx)\n",
|
||||
"\n",
|
||||
" for n in range(nt - 2, -1, -1):\n",
|
||||
" # Step 1: implicit diffusion (heat-equation step)\n",
|
||||
" u_diff = solve_banded((1, 1), ab, u[:, n + 1])\n",
|
||||
"\n",
|
||||
" # Step 2: explicit reaction with central-difference Hamiltonian\n",
|
||||
" u_x = np.empty(nx)\n",
|
||||
" u_x[1:-1] = (u_diff[2:] - u_diff[:-2]) / (2.0 * dx)\n",
|
||||
" u_x[0] = (u_diff[1] - u_diff[0]) / dx\n",
|
||||
" u_x[-1] = (u_diff[-1] - u_diff[-2]) / dx\n",
|
||||
"\n",
|
||||
" H = 0.5 * u_x ** 2\n",
|
||||
" H = np.minimum(H, 50.0) # safety cap\n",
|
||||
" f = lambda_congestion * np.maximum(m[:, n + 1], 0.0)\n",
|
||||
"\n",
|
||||
" u[:, n] = u_diff + dt * (f - H)\n",
|
||||
"\n",
|
||||
" # Neumann BC + clamp\n",
|
||||
" u[0, n] = u[1, n]\n",
|
||||
" u[-1, n] = u[-2, n]\n",
|
||||
" \n",
|
||||
" np.clip(u[:, n], -100.0, 100.0, out=u[:, n])\n",
|
||||
"\n",
|
||||
" return u\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def solve_fp(u, m0):\n",
|
||||
" \"\"\"\n",
|
||||
" Solve Fokker-Planck equation forward in time with improved stability.\n",
|
||||
" Uses upwind scheme for advection and ensures mass conservation.\n",
|
||||
" Solve the Fokker-Planck forward in time with operator splitting:\n",
|
||||
" 1) implicit diffusion step\n",
|
||||
" 2) explicit upwind advection with velocity v = -∂u/∂x (positivity)\n",
|
||||
" Mass is renormalised every step.\n",
|
||||
" \"\"\"\n",
|
||||
" m = np.zeros((nx, nt))\n",
|
||||
" m[:, 0] = m0 # Initial condition\n",
|
||||
" \n",
|
||||
" # Effective time step (use fraction of CFL limit)\n",
|
||||
" dt_eff = min(dt, 0.4 * dx**2 / (2 * nu))\n",
|
||||
" n_substeps = max(1, int(dt / dt_eff))\n",
|
||||
" dt_sub = dt / n_substeps\n",
|
||||
" \n",
|
||||
" for n in range(nt-1):\n",
|
||||
" m_current = m[:, n].copy()\n",
|
||||
" \n",
|
||||
" # Sub-stepping for stability\n",
|
||||
" for substep in range(n_substeps):\n",
|
||||
" m_new = m_current.copy()\n",
|
||||
" \n",
|
||||
" for i in range(1, nx-1):\n",
|
||||
" # Diffusion (central difference)\n",
|
||||
" m_xx = (m_current[i+1] - 2*m_current[i] + m_current[i-1]) / (dx**2)\n",
|
||||
" \n",
|
||||
" # Velocity field from gradient of u (H_p = p for H = 0.5*p^2)\n",
|
||||
" u_x_center = (u[i+1, n] - u[i-1, n]) / (2*dx)\n",
|
||||
" v = -u_x_center # Negative gradient for minimization\n",
|
||||
" \n",
|
||||
" # Upwind scheme for advection (ensures positivity)\n",
|
||||
" if v > 0:\n",
|
||||
" m_x = (m_current[i] - m_current[i-1]) / dx\n",
|
||||
" m[:, 0] = m0\n",
|
||||
"\n",
|
||||
" ab = _build_diffusion_banded(nx, dt, nu, dx)\n",
|
||||
"\n",
|
||||
" # CFL substepping bound for the advection part\n",
|
||||
" u_x_full = np.gradient(u, dx, axis=0)\n",
|
||||
" v_max = max(1e-12, float(np.max(np.abs(u_x_full))))\n",
|
||||
" n_sub = max(1, int(np.ceil(v_max * dt / (0.5 * dx))))\n",
|
||||
" dt_sub = dt / n_sub\n",
|
||||
"\n",
|
||||
" for n in range(nt - 1):\n",
|
||||
" m_cur = m[:, n].copy()\n",
|
||||
"\n",
|
||||
" for _ in range(n_sub):\n",
|
||||
" # 1) implicit diffusion (CFL-free)\n",
|
||||
" ab_sub = _build_diffusion_banded(nx, dt_sub, nu, dx)\n",
|
||||
" m_diff = solve_banded((1, 1), ab_sub, m_cur)\n",
|
||||
"\n",
|
||||
" # 2) explicit upwind advection: ∂_t m = -∂_x (v m), v = -u_x\n",
|
||||
" u_x = np.empty(nx)\n",
|
||||
" u_x[1:-1] = (u[2:, n] - u[:-2, n]) / (2.0 * dx)\n",
|
||||
" u_x[0] = (u[1, n] - u[0, n]) / dx\n",
|
||||
" u_x[-1] = (u[-1, n] - u[-2, n]) / dx\n",
|
||||
" v = -u_x\n",
|
||||
"\n",
|
||||
" flux = np.zeros(nx + 1) # cell-face fluxes\n",
|
||||
" for i in range(1, nx):\n",
|
||||
" v_face = 0.5 * (v[i - 1] + v[i])\n",
|
||||
" if v_face >= 0:\n",
|
||||
" flux[i] = v_face * m_diff[i - 1]\n",
|
||||
" else:\n",
|
||||
" m_x = (m_current[i+1] - m_current[i]) / dx\n",
|
||||
" \n",
|
||||
" # Forward Euler update\n",
|
||||
" diff_term = nu * m_xx\n",
|
||||
" adv_term = -v * m_x\n",
|
||||
" \n",
|
||||
" m_new[i] = m_current[i] + dt_sub * (diff_term + adv_term)\n",
|
||||
" \n",
|
||||
" # Enforce strict non-negativity\n",
|
||||
" m_new[i] = max(m_new[i], 1e-12)\n",
|
||||
" \n",
|
||||
" # Boundary conditions (Neumann: zero flux)\n",
|
||||
" flux[i] = v_face * m_diff[i]\n",
|
||||
" # Neumann (no flux) on both ends\n",
|
||||
" flux[0] = 0.0\n",
|
||||
" flux[-1] = 0.0\n",
|
||||
"\n",
|
||||
" m_new = m_diff - (dt_sub / dx) * (flux[1:] - flux[:-1])\n",
|
||||
"\n",
|
||||
" # positivity + Neumann ghost copies\n",
|
||||
" np.maximum(m_new, 1e-12, out=m_new)\n",
|
||||
" m_new[0] = m_new[1]\n",
|
||||
" m_new[-1] = m_new[-2]\n",
|
||||
" \n",
|
||||
" # Normalize to preserve probability mass\n",
|
||||
" total_mass = np.sum(m_new) * dx\n",
|
||||
" if total_mass > 1e-10:\n",
|
||||
" m_new /= total_mass\n",
|
||||
" \n",
|
||||
" m_current = m_new\n",
|
||||
" \n",
|
||||
" m[:, n+1] = m_current\n",
|
||||
" \n",
|
||||
"\n",
|
||||
" # mass renormalisation\n",
|
||||
" total = m_new.sum() * dx\n",
|
||||
" if total > 1e-10:\n",
|
||||
" m_new /= total\n",
|
||||
"\n",
|
||||
" m_cur = m_new\n",
|
||||
"\n",
|
||||
" m[:, n + 1] = m_cur\n",
|
||||
"\n",
|
||||
" return m\n",
|
||||
"\n",
|
||||
"print(\"✓ Improved solver functions defined with enhanced numerical stability\")"
|
||||
"\n",
|
||||
"print(\"✓ Stable HJB/FP solvers defined (implicit diffusion + upwind advection)\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -352,85 +381,53 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Python implementation: Fixed-point iteration with improved stability\n",
|
||||
"print(\"Running Python fixed-point iteration...\")\n",
|
||||
"print(\"Note: Using moderate grid (50×50) for numerical stability\")\n",
|
||||
"# pyright: reportArgumentType=false, reportUnusedImport=false, reportUnusedVariable=false, reportUnusedExpression=false, reportCallIssue=false, reportAttributeAccessIssue=false, reportOptionalMemberAccess=false, reportOperatorIssue=false, reportGeneralTypeIssues=false, reportReturnType=false, reportAssignmentType=false, reportIndexIssue=false, reportDeprecated=false, reportUndefinedVariable=false, reportPrivateImportUsage=false\n",
|
||||
"print(\"Running Python fixed-point iteration (semi-implicit splitting)...\")\n",
|
||||
"print(f\"Grid: {nx} × {nt}, ν={nu}, λ={lambda_congestion}\")\n",
|
||||
"start_time_py = time.time()\n",
|
||||
"\n",
|
||||
"max_iter = 100 # Increased iterations for convergence\n",
|
||||
"tol = 1e-4 # Relaxed tolerance for Python implementation\n",
|
||||
"relax = 0.3 # Lower relaxation for stability\n",
|
||||
"max_iter = 60\n",
|
||||
"tol = 5e-4\n",
|
||||
"relax = 0.4\n",
|
||||
"\n",
|
||||
"# Initialize with uniform distribution\n",
|
||||
"m_old = np.ones((nx, nt)) / (nx * dx)\n",
|
||||
"m_old = np.tile(m0[:, None], (1, nt)) # warm-start with the initial profile\n",
|
||||
"errors_py = []\n",
|
||||
"converged = False\n",
|
||||
"u_py = np.zeros((nx, nt))\n",
|
||||
"m_new = m_old.copy()\n",
|
||||
"\n",
|
||||
"for iter in range(max_iter):\n",
|
||||
" # Terminal condition for HJB\n",
|
||||
" u_T = 0.5 * (x - x_target)**2\n",
|
||||
" \n",
|
||||
" # Solve HJB backward with improved stability\n",
|
||||
" try:\n",
|
||||
" u_py = solve_hjb(m_old, u_T)\n",
|
||||
" \n",
|
||||
" # Check for NaN or Inf\n",
|
||||
" if np.any(np.isnan(u_py)) or np.any(np.isinf(u_py)):\n",
|
||||
" print(f\" ⚠ NaN/Inf detected in HJB at iteration {iter}, stopping...\")\n",
|
||||
" break\n",
|
||||
" except Exception as e:\n",
|
||||
" print(f\" ⚠ Error in HJB solver at iteration {iter}: {e}\")\n",
|
||||
" break\n",
|
||||
" \n",
|
||||
" # Solve FP forward with improved stability\n",
|
||||
" try:\n",
|
||||
" m_new = solve_fp(u_py, m0)\n",
|
||||
" \n",
|
||||
" # Check for NaN or Inf\n",
|
||||
" if np.any(np.isnan(m_new)) or np.any(np.isinf(m_new)):\n",
|
||||
" print(f\" ⚠ NaN/Inf detected in FP at iteration {iter}, stopping...\")\n",
|
||||
" break\n",
|
||||
" \n",
|
||||
" # Check for negative values (should not happen with upwind)\n",
|
||||
" if np.any(m_new < -1e-10):\n",
|
||||
" print(f\" ⚠ Negative values detected at iteration {iter}, stopping...\")\n",
|
||||
" break\n",
|
||||
" except Exception as e:\n",
|
||||
" print(f\" ⚠ Error in FP solver at iteration {iter}: {e}\")\n",
|
||||
" break\n",
|
||||
" \n",
|
||||
" # Check convergence\n",
|
||||
" error = np.sqrt(np.sum((m_new - m_old)**2)) / (np.sqrt(np.sum(m_old**2)) + 1e-10)\n",
|
||||
"for it in range(max_iter):\n",
|
||||
" u_T = 0.5 * (x - x_target) ** 2\n",
|
||||
"\n",
|
||||
" u_py = solve_hjb(m_old, u_T)\n",
|
||||
" if not np.all(np.isfinite(u_py)):\n",
|
||||
" print(f\" ⚠ Non-finite u at iter {it}, aborting\"); break\n",
|
||||
"\n",
|
||||
" m_new = solve_fp(u_py, m0)\n",
|
||||
" if not np.all(np.isfinite(m_new)):\n",
|
||||
" print(f\" ⚠ Non-finite m at iter {it}, aborting\"); break\n",
|
||||
"\n",
|
||||
" error = float(np.linalg.norm(m_new - m_old) / (np.linalg.norm(m_old) + 1e-12))\n",
|
||||
" errors_py.append(error)\n",
|
||||
" \n",
|
||||
" if iter % 10 == 0 or error < tol:\n",
|
||||
" print(f\" Iteration {iter:3d}: error = {error:.6f}\")\n",
|
||||
" \n",
|
||||
"\n",
|
||||
" if it % 5 == 0 or error < tol:\n",
|
||||
" print(f\" iter {it:3d} error = {error:.3e}\")\n",
|
||||
"\n",
|
||||
" if error < tol:\n",
|
||||
" print(f\"✓ Converged in {iter+1} iterations (tolerance: {tol})\")\n",
|
||||
" converged = True\n",
|
||||
" break\n",
|
||||
" \n",
|
||||
" # Under-relaxation for stability\n",
|
||||
" m_old = relax * m_new + (1 - relax) * m_old\n",
|
||||
"\n",
|
||||
" m_old = relax * m_new + (1.0 - relax) * m_old\n",
|
||||
"\n",
|
||||
"python_time = time.time() - start_time_py\n",
|
||||
"iterations_python = it + 1\n",
|
||||
"u_python = u_py\n",
|
||||
"m_python = m_new\n",
|
||||
"\n",
|
||||
"if converged:\n",
|
||||
" print(f\"✓ Python computation time: {python_time:.4f} seconds\")\n",
|
||||
" # Store Python results\n",
|
||||
" u_python = u_py\n",
|
||||
" m_python = m_new\n",
|
||||
" iterations_python = iter + 1\n",
|
||||
" print(f\"✓ Converged in {iterations_python} iterations ({python_time:.3f}s)\")\n",
|
||||
"else:\n",
|
||||
" print(f\"⚠ Python solver did not converge within {max_iter} iterations\")\n",
|
||||
" print(f\" This is a known limitation of explicit finite difference on coarse grids\")\n",
|
||||
" print(f\" The Rust implementation uses more sophisticated numerical methods\")\n",
|
||||
" # Store partial results for visualization\n",
|
||||
" u_python = u_py if 'u_py' in locals() else np.zeros((nx, nt))\n",
|
||||
" m_python = m_old\n",
|
||||
" iterations_python = iter\n",
|
||||
" python_time = time.time() - start_time_py"
|
||||
" print(f\"✓ Reached iter cap {max_iter} (last error {errors_py[-1]:.2e}) ({python_time:.3f}s)\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -468,6 +465,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# pyright: reportArgumentType=false, reportUnusedImport=false, reportUnusedVariable=false, reportUnusedExpression=false, reportCallIssue=false, reportAttributeAccessIssue=false, reportOptionalMemberAccess=false, reportOperatorIssue=false, reportGeneralTypeIssues=false, reportReturnType=false, reportAssignmentType=false, reportIndexIssue=false, reportDeprecated=false, reportUndefinedVariable=false, reportPrivateImportUsage=false\n",
|
||||
"if RUST_AVAILABLE:\n",
|
||||
" # Configure the Rust MFG solver\n",
|
||||
" config = MFGConfig(\n",
|
||||
@@ -556,6 +554,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# pyright: reportArgumentType=false, reportUnusedImport=false, reportUnusedVariable=false, reportUnusedExpression=false, reportCallIssue=false, reportAttributeAccessIssue=false, reportOptionalMemberAccess=false, reportOperatorIssue=false, reportGeneralTypeIssues=false, reportReturnType=false, reportAssignmentType=false, reportIndexIssue=false, reportDeprecated=false, reportUndefinedVariable=false, reportPrivateImportUsage=false\n",
|
||||
"if RUST_AVAILABLE:\n",
|
||||
" print(\"=\" * 60)\n",
|
||||
" print(\"PERFORMANCE COMPARISON\")\n",
|
||||
@@ -625,6 +624,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# pyright: reportArgumentType=false, reportUnusedImport=false, reportUnusedVariable=false, reportUnusedExpression=false, reportCallIssue=false, reportAttributeAccessIssue=false, reportOptionalMemberAccess=false, reportOperatorIssue=false, reportGeneralTypeIssues=false, reportReturnType=false, reportAssignmentType=false, reportIndexIssue=false, reportDeprecated=false, reportUndefinedVariable=false, reportPrivateImportUsage=false\n",
|
||||
"# Plot convergence comparison\n",
|
||||
"fig, ax = plt.subplots(1, 1, figsize=(10, 6))\n",
|
||||
"\n",
|
||||
@@ -666,6 +666,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# pyright: reportArgumentType=false, reportUnusedImport=false, reportUnusedVariable=false, reportUnusedExpression=false, reportCallIssue=false, reportAttributeAccessIssue=false, reportOptionalMemberAccess=false, reportOperatorIssue=false, reportGeneralTypeIssues=false, reportReturnType=false, reportAssignmentType=false, reportIndexIssue=false, reportDeprecated=false, reportUndefinedVariable=false, reportPrivateImportUsage=false\n",
|
||||
"# Create meshgrid for plotting\n",
|
||||
"X, T_grid = np.meshgrid(x, t)\n",
|
||||
"\n",
|
||||
@@ -728,6 +729,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# pyright: reportArgumentType=false, reportUnusedImport=false, reportUnusedVariable=false, reportUnusedExpression=false, reportCallIssue=false, reportAttributeAccessIssue=false, reportOptionalMemberAccess=false, reportOperatorIssue=false, reportGeneralTypeIssues=false, reportReturnType=false, reportAssignmentType=false, reportIndexIssue=false, reportDeprecated=false, reportUndefinedVariable=false, reportPrivateImportUsage=false\n",
|
||||
"# Temporal snapshots comparison\n",
|
||||
"fig, axes = plt.subplots(2, 3, figsize=(16, 10))\n",
|
||||
"time_indices = [0, nt//2, nt-1]\n",
|
||||
|
||||
Reference in New Issue
Block a user