Code
import numpy as np
from scipy.integrate import solve_ivpDieter Olson
November 28, 2025
These entries remain visible until evidence-backed adjudication changes their governed status.
crit-double-pendulum-energy-blindness)The double pendulum is the simplest system that exhibits the richness of what the AffineDrift framework studies: it has a "proximal" segment (the arm) and a "distal" segment (the club). When you swing the arm, it pulls on the club. Some of that pull is active (you consciously pushing), and some is passive (the arm's momentum and inertia doing the work). This article breaks down how much of the club's power comes from each source.
Every movement has two sources of power:
We apply this to a double pendulum (representing the arms and club).
For an affine control system, we usually write \[ \dot{x} = f(x) + G(x)u, \] and think of \(f\) as the drift (what the system does “on its own”) and \(G(x)u\) as the control effect.
For mechanical systems, there is a direct analog at the level of forces, torques, and wrenches:
The key point:
For a given motion and environment, the total torques and wrenches can be decomposed into a purely natural mechanical contribution plus an active contribution generated by muscles/actuators. This is not a control design issue; it is a mechanical identity.
In this article:
Consider an \(n\)-DOF mechanical system with generalized coordinates \(q \in \mathbb{R}^n\), velocities \(\dot{q}\), accelerations \(\ddot{q}\). The standard joint-space dynamics (from Lagrange or Newton–Euler) can be written as
\[ M(q)\,\ddot{q} + C(q,\dot{q})\,\dot{q} + g(q) + d(q,\dot{q}) = \tau_{\text{in}} + \tau_{\text{ext}}, \tag{2.1} \]
where
Suppose external wrenches \(\lambda\) (contact forces, etc.) act at some contact frames with Jacobian \(J_c(q)\). Then
\[ \tau_{\text{ext}} = J_c(q)^\top \lambda. \tag{2.2} \]
Substituting into (2.1),
\[ M(q)\,\ddot{q} + C(q,\dot{q})\,\dot{q} + g(q) + d(q,\dot{q}) = \tau_{\text{in}} + J_c(q)^\top \lambda. \tag{2.3} \]
We define the natural (drift) torque field as the part entirely determined by the motion and environment:
\[ \tau_{\text{nat}}(q,\dot{q},\ddot{q},\lambda) := M(q)\ddot{q} + C(q,\dot{q})\,\dot{q} + g(q) + d(q,\dot{q}) - J_c(q)^\top \lambda. \tag{2.4} \]
Interpretation:
From (2.3) and (2.4), we have the identity
\[ \tau_{\text{in}} = \tau_{\text{nat}}(q,\dot{q},\ddot{q},\lambda). \tag{2.5} \]
In words: for whatever motion you actually observe or simulate, the internal active torques equal the natural torque field given by the mass matrix, Coriolis, gravity, damping, and external wrenches.
Now suppose the internal active torques are generated by some input vector \(u \in \mathbb{R}^m\):
\[ \tau_{\text{in}} = B(q)\,u, \tag{2.6} \]
where \(B(q) \in \mathbb{R}^{n \times m}\) is a configuration-dependent actuation matrix (mapping “muscle channels” or actuators into joint space).
Combining (2.5) and (2.6):
\[ B(q)\,u = \tau_{\text{nat}}(q,\dot{q},\ddot{q},\lambda). \tag{2.7} \]
This is a purely mechanical identity:
If \(B(q)\) has full column rank, a typical reconstruction of \(u\) is
\[ u = B(q)^{+}\,\tau_{\text{nat}}, \tag{2.8} \]
where \((\cdot)^{+}\) is a pseudoinverse.
We can also describe the same decomposition at the state level, in the usual first-order form.
Define the state
\[ x = \begin{bmatrix} q \\ \dot{q} \end{bmatrix} \in \mathbb{R}^{2n}. \]
From (2.3) and (2.6),
\[ M(q)\ddot{q} = B(q)u + J_c(q)^\top \lambda - C(q,\dot{q})\dot{q} - g(q) - d(q,\dot{q}). \tag{3.1} \]
Therefore
\[ \ddot{q} = M(q)^{-1}\, \big( J_c(q)^\top \lambda - C(q,\dot{q})\dot{q} - g(q) - d(q,\dot{q}) \big) + M(q)^{-1} B(q)\,u. \tag{3.2} \]
Thus
\[ \dot{x} = \begin{bmatrix} \dot{q} \\ M(q)^{-1}\big( J_c(q)^\top \lambda - C(q,\dot{q})\dot{q} - g(q) - d(q,\dot{q}) \big) \end{bmatrix} + \begin{bmatrix} 0 \\ M(q)^{-1} B(q) \end{bmatrix} u. \tag{3.3} \]
Define the natural drift field
\[ f_{\text{nat}}(x,\lambda) := \begin{bmatrix} \dot{q} \\ M(q)^{-1}\big( J_c(q)^\top \lambda - C(q,\dot{q})\dot{q} - g(q) - d(q,\dot{q}) \big) \end{bmatrix}, \tag{3.4} \]
and the input field
\[ G(x) := \begin{bmatrix} 0 \\ M(q)^{-1} B(q) \end{bmatrix}. \tag{3.5} \]
Then we have the familiar affine structure:
\[ \dot{x} = f_{\text{nat}}(x,\lambda) + G(x)u. \tag{3.6} \]
Here, \(f_{\text{nat}}\) is exactly the evolution the system would have with no active input (\(u = 0\)), subject to gravity, contact, damping, and current velocities.
Consider an end-effector (e.g., a point on the club) with a spatial Jacobian \(J(q)\in\mathbb{R}^{6\times n}\) that maps joint velocities to twist:
\[ v = J(q)\,\dot{q}, \qquad v \in \mathbb{R}^6. \]
The spatial wrench \(w \in \mathbb{R}^6\) (force and moment) maps to joint space as
\[ \tau_{\text{joint}} \approx J(q)^\top\,w + \tau_{\text{int}}, \tag{4.1} \]
where \(\tau_{\text{int}}\) collects internal constraint forces that do not appear as net wrench at that end-effector. If we ignore or separately model those internal contributions, we approximate
\[ \tau_{\text{joint}} \approx J(q)^\top w. \tag{4.2} \]
Given joint torques \(\tau\), we can approximate the corresponding wrench by a pseudoinverse:
\[ w \approx \big(J(q)^\top\big)^{+}\,\tau. \tag{4.3} \]
Using the natural torque field \(\tau_{\text{nat}}\), define the natural wrench at the end-effector:
\[ w_{\text{nat}}(q,\dot{q},\ddot{q},\lambda) := \big(J(q)^\top\big)^{+} \,\tau_{\text{nat}}(q,\dot{q},\ddot{q},\lambda). \tag{4.4} \]
This is the wrench associated with inertia, gravity, velocity-dependent terms, dissipation, and contacts, mapped to the end-effector.
From (2.6), the active torque is \(B(q)u\). The associated active wrench is
\[ w_{\text{act}}(q,u) := \big(J(q)^\top\big)^{+} B(q)\,u. \tag{4.5} \]
Define
\[ W(q) := \big(J(q)^\top\big)^{+} B(q) \in \mathbb{R}^{6 \times m}, \tag{4.6} \]
so that
\[ w_{\text{act}}(q,u) = W(q)\,u. \tag{4.7} \]
Each column of \(W(q)\) is the wrench direction generated by unit activation of one input channel.
The total wrench splits as
\[ w(q,\dot{q},\ddot{q},\lambda,u) = w_{\text{nat}}(q,\dot{q},\ddot{q},\lambda) + w_{\text{act}}(q,u), \tag{4.8} \]
i.e.
\[ w = w_{\text{nat}} + W(q)\,u. \tag{4.9} \]
This is the wrench-level analog of the usual affine decomposition: natural mechanics vs active input.
We now specialize the above to a planar double pendulum (2-link planar manipulator). This gives a concrete model you can drive from Python and plug into Streamlit.
Let
We assume no explicit damping (for simplicity), i.e. \(d(q,\dot{q}) = 0\), and no external contacts (\(J_c^\top\lambda = 0\)).
Then the equations of motion take the canonical form
\[ M(q)\ddot{q} + C(q,\dot{q})\,\dot{q} + g(q) = \tau_{\text{in}}, \tag{5.1} \]
with \(\tau_{\text{in}} = \begin{bmatrix} \tau_1 \\ \tau_2 \end{bmatrix}\) the joint torques.
A standard planar 2-link manipulator model gives:
\[ \begin{aligned} M_{11}(q) &= I_1 + I_2 + m_1 c_1^2 + m_2 \big( l_1^2 + c_2^2 + 2 l_1 c_2 \cos q_2 \big), \\ M_{12}(q) &= I_2 + m_2\big( c_2^2 + l_1 c_2 \cos q_2 \big), \\ M_{21}(q) &= M_{12}(q), \\ M_{22}(q) &= I_2 + m_2 c_2^2. \end{aligned} \tag{5.2} \]
So
\[ M(q) = \begin{bmatrix} M_{11}(q) & M_{12}(q) \\ M_{21}(q) & M_{22}(q) \end{bmatrix}. \]
There are various equivalent parameterizations. A commonly used one defines
\[ h(q) = - m_2 l_1 c_2 \sin q_2. \tag{5.3} \]
Then the product \(C(q,\dot{q})\dot{q}\) can be written as
\[ C(q,\dot{q})\dot{q} = \begin{bmatrix} h(q)\dot{q}_2(2\dot{q}_1 + \dot{q}_2) \\ - h(q)\dot{q}_1^2 \end{bmatrix}. \tag{5.4} \]
You can think of this as \(C(q,\dot{q})\dot{q}\) without ever writing out \(C\) explicitly as a matrix.
Assuming angles are measured from the downward vertical for simplicity, the gravity torques are
\[ \begin{aligned} g_1(q) &= (m_1 c_1 + m_2 l_1)\,g\,\sin q_1 + m_2 c_2 g \sin(q_1 + q_2), \\ g_2(q) &= m_2 c_2 g \sin(q_1 + q_2). \end{aligned} \tag{5.5} \]
So
\[ g(q) = \begin{bmatrix} g_1(q) \\ g_2(q) \end{bmatrix}. \]
With no external contacts and no explicit damping, the natural torque field reduces to
\[ \tau_{\text{nat}}(q,\dot{q},\ddot{q}) = M(q)\ddot{q} + C(q,\dot{q})\dot{q} + g(q). \tag{5.6} \]
For a fully actuated system with inputs directly equal to torques, we can set \(B(q) = I_2\) (the \(2\times 2\) identity), so that
\[ \tau_{\text{in}} = u. \tag{5.7} \]
The mechanical identity (2.7) becomes
\[ u = \tau_{\text{nat}}(q,\dot{q},\ddot{q}). \tag{5.8} \]
In a golf-swing context or in human biomechanics, you might replace \(B(q)\) by a more structured mapping from muscle activations to joint torques; the decomposition still holds.
A critical aspect of the drift–control analysis is understanding power flow: how energy moves between segments and how much of that transfer is due to “natural” dynamics versus “active” input.
For a distal segment (e.g., the club, link 2), the total energy \(E_2\) changes due to work done by the joint constraint force \(\vec{F}_{21}\) and the joint torque \(\tau_{21}\).
\[ \dot{E}_2 = \vec{F}_{21} \cdot \vec{v}_{\text{joint}} + \tau_{21} \, \omega_2. \]
We calculate the joint constraint force \(\vec{F}_{21}\) from Newton’s law applied to the COM of link 2:
\[ \vec{F}_{21} = m_2 (\vec{a}_{c2} - \vec{g}). \]
Using the affine decomposition of acceleration from (3.2), \[ \ddot{q} = \ddot{q}_{\text{nat}} + M(q)^{-1} B(q) u, \] the linear acceleration of the COM \(\vec{a}_{c2}\) decomposes as:
\[ \vec{a}_{c2} = J_{c2} \ddot{q} + \dot{J}_{c2} \dot{q} = \underbrace{(J_{c2} \ddot{q}_{\text{nat}} + \dot{J}_{c2} \dot{q})}_{\text{natural acceleration } \vec{a}_{\text{nat}}} + \underbrace{(J_{c2} M(q)^{-1} B(q) u)}_{\text{active acceleration } \vec{a}_{\text{act}}}. \]
Thus, the constraint force splits into natural and active components:
\[ \vec{F}_{\text{nat}} = m_2 (\vec{a}_{\text{nat}} - \vec{g}), \qquad \vec{F}_{\text{act}} = m_2 \vec{a}_{\text{act}}. \]
Depending on the actuator model, the joint torque \(\tau_{21}\) also splits (e.g., into passive spring/damper \(\tau_{\text{nat}}\) and active motor \(\tau_{\text{act}}\)). For a purely torque-driven joint, \(\tau_{21} = \tau_{\text{act}} = u_2\).
The Power Transfer then decomposes into:
Natural Power Transfer: Energy moved into the link by passive dynamics (e.g., the arm “pulling” on the club due to centripetal acceleration). \[ P_{\text{nat}} = \vec{F}_{\text{nat}} \cdot \vec{v}_{\text{joint}} + \tau_{\text{nat}} \omega_2. \]
Active Power Transfer: Energy moved into the link by the instantaneous action of the control inputs. \[ P_{\text{act}} = \vec{F}_{\text{act}} \cdot \vec{v}_{\text{joint}} + \tau_{\text{act}} \omega_2. \]
This decomposition allows us to answer: “Is the club gaining energy because I am pulling on it (drift-induced transfer — passive from the viewpoint of active input, but mediated by the state history \(q(t), \dot q(t)\)) or because I am twisting it (active transfer)?”
This rigid-body power analysis captures kinetic energy transfer but ignores elastic potential energy. In the full flexible shaft model (Part I), active torque often does work to deform the shaft (increasing \(V_{\text{elastic}}\)), effectively “banking” energy that is later released passively. The rigid model here illustrates the mechanism of transfer but underestimates the capacity for delayed release.
The Zero Torque Counterfactual (ZTCF) sets \(u = 0\), leaving only drift dynamics \(\dot{x} = f(x)\). For a conservative mechanical system (no dissipation), the total mechanical energy is conserved along the ZTCF:
\[ \frac{d}{dt}\left(T(q, \dot{q}) + V(q)\right)\bigg|_{\text{ZTCF}} = 0. \]
This provides a useful sanity check for numerical ZTCF simulations: if the total energy drifts under zero-torque integration, it signals solver error or incorrect constraint enforcement rather than physical dynamics. In practice, dissipation (joint damping, air resistance) breaks strict energy conservation; the ZTCF trajectory then follows the conservative drift augmented by passive dissipative forces.
The energy difference between the full trajectory and the ZTCF trajectory accumulates the net work done by the golfer’s torques over time, providing an energy-based interpretation of the active input contribution.
The decomposition of the total wrench \(w\) into \(w_{\text{nat}}\) and \(w_{\text{act}}\) provides the explicit mechanical realization of the abstract affine control form \(\dot{x} = f(x) + G(x)u\). Specifically, \(w_{\text{nat}}\) corresponds to the “Drift Wrench” generated by the vector field \(f(x)\), encapsulating inertial, gravitational, and centripetal history in the model. In contrast, \(w_{\text{act}}\) represents the instantaneous “Control Authority” \(G(x)u\). This separation is critical for interpreting the Zero Torque Counterfactual (ZTCF): setting \(u=0\) (and thus \(w_{\text{act}}=0\)) isolates the \(w_{\text{nat}}\) component, showing that the clubhead’s acceleration in the ZTCF is generally not zero, but is driven by the model’s passive wrench terms.
The energy transfer decomposition in Section 6 addresses the “Teleological Blindness” often found in kinetic analyses. By distinguishing \(P_{\text{nat}}\) from \(P_{\text{act}}\), we can separate energy transfer via constraint forces \(\vec{F}_{\text{nat}}\) from energy input via joint torques \(\tau_{\text{act}}\) within the double-pendulum model. In late-downswing-style states this framework can produce regimes where \(P_{\text{nat}} \gg P_{\text{act}}\). That is a model-based interpretation of how geometry and timing shape passive energy transfer, not a universal coaching conclusion about all swings.
It is essential to note that this derivation assumes a Rigid Body model for all links. Consequently, the “Active Wrench” \(w_{\text{act}}\) is assumed to transmit instantaneously from the hands to the clubhead. This omits the elastic potential energy storage and release dynamics characteristic of a flexible shaft (the Stretch-Shortening Cycle). In a flexible model, the active input \(u\) would first do work on the shaft stiffness (\(V_{elastic}\)), introducing a phase delay before appearing as kinetic energy at the clubhead. Thus, this rigid-body decomposition represents the “Stiff Shaft Limit” of the true dynamics.
Below is a Python implementation of the double pendulum dynamics, suitable for integration into a Streamlit app. We:
Every theory faces scrutiny. Here's what skeptics and alternative perspectives say:
The "Active" Definition vs. Impedance Control
Biomechanists argue that defining "Active" torque strictly as the joint torque $u$ implies that muscle contraction only generates motion. In reality, muscles also modulate stiffness ($K$) and damping ($D$). These passive parameters appear in the "natural" drift term $\tau_{\text{nat}}$, meaning the golfer can "activate" the drift field by stiffening their joints. The decomposition $\tau_{\text{active}} + \tau_{\text{natural}}$ thus falsely implies that the golfer only contributes via $\tau_{\text{active}}$.
The "Energy Bank" Fallacy
The rigid-body model implies instantaneous power transfer. However, a flexible golf shaft acts as a capacitor. "Active" torque applied early in the swing stores potential energy ($V_{\text{elastic}}$) which is released later as "Natural" kinetic energy. Decomposing forces at the moment of release classifies them as "Natural" (drift), giving the impression the golfer is doing nothing, when in fact they "paid" for that energy earlier.
Planar vs. Spatial Realities
The article relies on a planar double pendulum. Critics argue this misses critical 3D phenomena, specifically the gyroscopic coupling ("Beta axis") where out-of-plane forces induce in-plane torques. In a real swing, the "Natural" wrench includes these 3D cross-terms, which can be significant drivers of clubface closure.