@@ -115,10 +115,50 @@ def _evaluate_propellant_inertia(self):
115115 def I_22 (self ):
116116 """Assembled (dry + propellant) transverse inertia about the e_2 axis.
117117
118- Overrides :meth:`Motor.I_22`, which assumes ``I_22 == I_11`` by
119- axisymmetry. A ring cluster is not axisymmetric for small ``number``
120- (notably ``number == 2``), so ``I_22`` differs from ``I_11`` and must be
121- computed from the separately-evaluated ``_22`` components.
118+ Overrides :meth:`Motor.I_22`, which returns ``I_11`` directly on the
119+ assumption that the motor is axisymmetric. That assumption does not hold
120+ for every ring cluster, so ``I_22`` is computed here from the
121+ separately-evaluated ``_22`` components (see
122+ :meth:`_evaluate_propellant_inertia` and :meth:`_calculate_dry_inertia`).
123+
124+ When ``I_22`` equals ``I_11``
125+ -----------------------------
126+ The relevant property is not continuous axisymmetry (which a discrete
127+ cluster of ``number`` motors never has for finite ``number``) but
128+ *transverse isotropy* of the inertia tensor: ``I_11 == I_22`` and
129+ ``I_12 == 0``, i.e. every transverse axis is a principal axis with the
130+ same moment. A rigid body has this whenever it possesses a discrete
131+ rotational-symmetry axis of order ``n >= 3`` -- geometric axisymmetry is
132+ sufficient but not necessary.
133+
134+ For a ring cluster the motors sit at angles ``theta_k = 2*pi*k/number``,
135+ ``k = 0 .. number-1``, all at radius ``radius``. The transverse
136+ anisotropy is driven by
137+
138+ I_22 - I_11 proportional to sum_k (x_k**2 - y_k**2)
139+ = radius**2 * sum_k cos(2*theta_k)
140+ = radius**2 * Re( sum_k exp(i * 4*pi*k / number) ).
141+
142+ That geometric series vanishes unless ``exp(i*4*pi/number) == 1``, i.e.
143+ unless ``number`` divides 2. Hence:
144+
145+ * ``number == 2`` -- the ``m = 2`` angular term does not cancel
146+ (``sum cos(2*theta_k) == 2``); the cluster is transversely anisotropic
147+ and ``I_22 != I_11``. This is the case this override exists for.
148+ * ``number >= 3`` -- the term cancels exactly for *every* such value
149+ (odd, even, prime alike); ``I_22 == I_11`` analytically, and this
150+ method returns the same value as :meth:`I_11` up to floating-point
151+ round-off.
152+
153+ Note that parity or primality of ``number`` is irrelevant: three or more
154+ equally-spaced motors already annihilate the ``m = 2`` harmonic, so e.g.
155+ ``number == 3`` and ``number == 5`` are both transversely isotropic. The
156+ sole non-trivial anisotropic configuration (given the ``number >= 2``
157+ constraint enforced in ``__init__``) is ``number == 2``.
158+
159+ The implementation nonetheless sums every motor's contribution
160+ explicitly rather than special-casing ``number == 2``, so the result is
161+ exact for all configurations.
122162 """
123163 prop_I_22 = parallel_axis_theorem_from_com (
124164 self .propellant_I_22 ,
0 commit comments