Source code for torus_solver.sources

from __future__ import annotations

import jax.numpy as jnp

from .poisson import area_mean
from .torus import TorusSurface


[docs] def wrap_angle(x: jnp.ndarray) -> jnp.ndarray: """Wrap angles to (-π, π] in a JAX-differentiable way.""" twopi = 2.0 * jnp.pi return jnp.mod(x + jnp.pi, twopi) - jnp.pi
[docs] def deposit_current_sources( surface: TorusSurface, *, theta_src: jnp.ndarray, # (Ns,) phi_src: jnp.ndarray, # (Ns,) currents: jnp.ndarray, # (Ns,) sigma_theta: float, sigma_phi: float, ) -> jnp.ndarray: """Deposit smooth current source/sink density s(θ,φ) (A/m^2) onto the surface grid. Each source i injects a total current `currents[i]` distributed by a periodic Gaussian kernel in (θ, φ), normalized so that ∫ s dA = Σ currents. For magnetostatics, net injected current must be zero. This routine enforces a zero area-mean source density numerically. """ theta_src = jnp.asarray(theta_src) phi_src = jnp.asarray(phi_src) currents = jnp.asarray(currents) dtheta = wrap_angle(surface.theta[:, None] - theta_src[None, :]) # (Nθ,Ns) dphi = wrap_angle(surface.phi[:, None] - phi_src[None, :]) # (Nφ,Ns) k_theta = jnp.exp(-0.5 * (dtheta / sigma_theta) ** 2) # (Nθ,Ns) k_phi = jnp.exp(-0.5 * (dphi / sigma_phi) ** 2) # (Nφ,Ns) # Normalize each separable kernel so that its integral over the surface is 1: # norm_i = (Σθ kθ(θ,i) √g(θ) dθ) * (Σφ kφ(φ,i) dφ) # Since √g depends only on θ for a circular torus, we avoid materializing # the full (Nθ,Nφ,Ns) kernel. w_theta = (surface.sqrt_g[:, 0] * surface.dtheta)[:, None] # (Nθ,1) norm_theta = jnp.sum(k_theta * w_theta, axis=0) # (Ns,) norm_phi = jnp.sum(k_phi, axis=0) * surface.dphi # (Ns,) norm = norm_theta * norm_phi # (Ns,) # s(θ,φ) = Σ_i currents_i * kθ(θ,i) * kφ(φ,i) / norm_i a_theta = k_theta * (currents / norm)[None, :] # (Nθ,Ns) s = a_theta @ k_phi.T # (Nθ,Nφ) return s - area_mean(surface, s)