Fitting the Solar Wind–Magnetosphere Coupling Function with Gradient-Based Methods
Space weather forecasting hinges on one deceptively simple number: the planetary Kp index. Ranging from 0 to 9, it summarizes how disturbed Earth’s magnetic field is at any given moment, and it drives everything from satellite operators bracing for drag to power grid engineers watching for geomagnetically induced currents. Behind that single number sits a genuinely hard optimization problem — how do you turn noisy, high-dimensional solar wind measurements into an accurate, well-calibrated forecast?
In this article we build a compact but physically grounded version of that problem. We start from the Newell coupling function, a well-established formula linking solar wind speed, interplanetary magnetic field strength, and IMF clock angle to the rate of magnetic reconnection at Earth’s magnetopause. We then treat the Kp response to that coupling function as a nonlinear regression problem, and solve it two ways: a hand-derived, fully vectorized Adam optimizer, and a scipy L-BFGS-B solver used as a cross-check. Along the way we visualize the loss landscape in 3D, watch the optimizer’s trajectory crawl across it, and compare the fitted model surface against the underlying data.
1. The Physical and Mathematical Setup
1.1 The coupling function
The dominant driver of geomagnetic activity is the rate of magnetic flux reconnected at the dayside magnetopause. Newell et al.’s widely used empirical coupling function approximates this rate as:
$$
\frac{d\Phi}{dt} ;=; v^{4/3} , B_t^{2/3} , \sin^{8/3}!\left(\frac{\theta_c}{2}\right)
$$
where:
- $v$ is the solar wind speed (km/s)
- $B_t = \sqrt{B_y^2 + B_z^2}$ is the transverse component of the interplanetary magnetic field (nT)
- $\theta_c = \arctan(B_y, B_z)$ is the IMF clock angle
This single scalar quantity captures most of the physics that matters: faster wind and stronger, more southward-tilted fields drive stronger reconnection, and hence stronger geomagnetic disturbance.
1.2 From coupling function to Kp
We model the Kp response as a nonlinear power-law transformation of the (normalized) coupling function:
$$
\widehat{Kp}(a, b, c) ;=; a \cdot \Phi^{,b} + c, \qquad \Phi = \frac{1}{S}\frac{d\Phi}{dt}
$$
with $S$ a fixed normalization constant that keeps $\Phi$ in a numerically friendly range. The three free parameters $(a, b, c)$ control the amplitude, the nonlinearity/saturation of the response, and the baseline (quiet-time) offset. Fitting these three parameters from observed $(\Phi_i, Kp_i)$ pairs is our optimization problem.
1.3 The loss function
We minimize a regularized mean-squared error:
$$
L(a, b, c) ;=; \frac{1}{N}\sum_{i=1}^{N}\left(a,\Phi_i^{,b} + c - Kp_i\right)^2 ;+; \lambda\left(a^2 + b^2\right)
$$
The regularization term $\lambda(a^2+b^2)$ discourages the optimizer from drifting toward degenerate solutions (e.g., an enormous $a$ paired with a tiny $b$) that fit the training noise rather than the underlying trend.
1.4 Analytic gradients
Because $\widehat{Kp} = a\Phi^b + c$, the partial derivatives are closed-form:
$$
\frac{\partial L}{\partial a} = \frac{2}{N}\sum_i r_i, \Phi_i^{,b} ;+; 2\lambda a
$$
$$
\frac{\partial L}{\partial b} = \frac{2}{N}\sum_i r_i, a, \Phi_i^{,b}\ln \Phi_i ;+; 2\lambda b
$$
$$
\frac{\partial L}{\partial c} = \frac{2}{N}\sum_i r_i
$$
where $r_i = \widehat{Kp}_i - Kp_i$ is the residual. Using these analytic gradients instead of finite-difference or autodiff approximations is what lets the optimizer converge in a few thousand cheap iterations rather than tens of thousands of noisy ones.
2. Why Vectorization Matters Here
A naive implementation of this fit would loop over each of the $N$ observations in pure Python, on every iteration, to accumulate the gradient sums — for a few thousand optimizer steps over a few hundred samples, that’s millions of interpreted Python operations, and the loss-landscape visualization (which evaluates the loss at thousands of parameter combinations) would be even slower if written the same way.
The code below avoids that entirely: every gradient, every loss evaluation, and even the entire 2D loss-landscape grid are computed as single NumPy broadcasted array operations — no Python-level loops over samples or grid points anywhere in the hot path. This is the “pre-optimized” version from the start, so there’s no separate slow/fast pair to show; the fast version is the version below.
3. Full Source Code (Google Colaboratory, single cell)
1 | import numpy as np |
4. Code Walkthrough
Section 1 — synthetic dataset. Rather than pulling live OMNI solar wind data (which would make the article dependent on an external download), we generate 800 physically plausible samples of solar wind speed, transverse IMF magnitude, and clock angle, then compute the true coupling function value for each. A “true” parameter set $(a=0.16, b=0.75, c=0.3)$ generates the corresponding Kp values, with Gaussian noise added and the result clipped to the valid $[0, 9]$ range — this gives us ground truth to check the optimizer against, which is invaluable when validating a fitting pipeline before pointing it at real data.
Section 2 — model and gradients. loss_and_grad computes the regularized MSE loss and all three partial derivatives in one pass, entirely through NumPy array arithmetic. Note the EPS inside np.log(phi + EPS): since $\Phi$ can be exactly (or near) zero when the clock angle is near 0, this avoids a log(0) warning while still multiplying out to zero in the gradient because $\Phi^b \to 0$ faster than $\ln \Phi \to -\infty$ blows up.
Section 3 — the Adam optimizer. This is a from-scratch implementation of Adam (Kingma & Ba, 2015): it keeps running estimates of the first and second moments of the gradient (m, v), bias-corrects them, and takes an adaptive step for each parameter individually. Two guardrails are added after each update: a is clamped to stay strictly positive (since $\Phi^b$ with negative amplitude is not physically meaningful here), and b is clamped to $[0.05, 3.0]$ to keep the power-law exponent in a numerically stable, physically reasonable band. History is recorded every 20 iterations so we can later plot the optimizer’s path.
Section 4 — the cross-check. We hand the same analytic loss/gradient function to scipy.optimize.minimize with method='L-BFGS-B', a quasi-Newton method that typically converges in far fewer iterations than first-order Adam. Comparing the two solutions is a good sanity check: if a hand-rolled optimizer and a well-tested library method agree, you can trust the loss landscape doesn’t have a hidden bug pulling both toward the wrong answer.
Section 5 — visualization. All four panels are described in detail in the next section.
5. Results
Run the cell above in Google Colaboratory. It will print the fitted parameters from both optimizers and display one combined figure with four panels.

=== Kp Coupling-Function Fit: Adam vs L-BFGS-B === True params : a=0.1600, b=0.7500, c=0.3000 Adam estimate : a=0.1705, b=0.7332, c=0.3053 (loss=0.08283) L-BFGS-B estimate : a=0.1629, b=0.7415, c=0.3105 (loss=0.08248)
6. Reading the Figure
Top-left — Loss landscape over $(a, b)$. This 3D surface shows the loss value for every combination of amplitude $a$ and exponent $b$ in a neighborhood around the fitted optimum, with $c$ held fixed at its converged value. The cyan trail is the Adam optimizer’s actual path through this landscape, and the red marker is where it settled. You should see the trail descending from the initial guess, sliding down the steepest visible slope, and curving into the basin — a direct, visual confirmation that the optimizer is doing what the math promises rather than wandering randomly.
Top-right — Convergence curve. Adam’s loss is plotted on a log scale against iteration count, with L-BFGS-B’s final loss drawn as a horizontal reference line. Because L-BFGS-B uses curvature information (an approximate Hessian) rather than only gradient direction, it typically reaches that same loss level in a fraction of the iterations Adam needs — but Adam’s curve should still flatten out and meet that line, confirming both methods converge to essentially the same solution.
Bottom-left — Fitted Kp surface vs. observations. This panel fixes the clock angle at $\theta_c = \pi$ (the geometry that maximizes coupling for a given speed and field strength) and plots the fitted model’s predicted Kp as a function of solar wind speed and transverse IMF magnitude. The red points are actual synthetic observations whose clock angle happened to be close to $\pi$, overlaid directly onto this slice of the surface. Visually, the red points should hug the surface closely — deviations are due to noise and the moderate scatter naturally introduced at other clock angles.
Bottom-right — Predicted vs. observed Kp. Every data point is plotted with observed Kp on the x-axis and the model’s predicted Kp on the y-axis; a perfect model would place every point exactly on the dashed diagonal. Color encodes the absolute residual, so the darkest points are the best fits and the brightest points are the worst. A tight, roughly diagonal cloud with a handful of scattered bright outliers is the expected signature of a well-fit nonlinear model on noisy data.
7. Where This Goes Next
This example deliberately keeps the model to three interpretable parameters so the optimization itself stays visualizable. A production-grade Kp forecasting pipeline would extend this in a few natural directions: fitting separate coupling-function exponents for northward vs. southward IMF (since the magnetosphere responds asymmetrically), adding a short memory/lag term to capture the magnetosphere’s storage-and-release behavior (a NARX-style extension), or replacing the fixed power-law form with a small neural network while keeping the same Adam-based optimization scaffold. The loss-landscape visualization technique used here — projecting a high-dimensional parameter space down to two axes and overlaying the optimizer’s trajectory — scales to those richer models just as well, and is often the fastest way to catch a poorly conditioned loss surface before it costs you days of wasted training time.

















