Summary
Polygon.J() in aerosandbox/geometry/polygon.py is documented as returning the polar moment of inertia "taken about the centroid," but the implementation never applies the parallel-axis correction — it silently returns the origin-referenced value instead. Polygon.Ixx() and Polygon.Iyy(), defined right next to it in the same file, apply the correction correctly, so this looks like a copy/paste that dropped the correction step rather than an intentional design choice.
Minimal reproduction
A 4×3 rectangle, offset away from the origin so the bug is visible (an origin-centered shape would hide it, since the correction term would be zero):
from aerosandbox.geometry.polygon import Polygon
import numpy as np
# By the parallel-axis theorem, for any rectangle:
# Ixx_centroid = b*h^3/12 = 4*3^3/12 = 9
# Iyy_centroid = h*b^3/12 = 3*4^3/12 = 16
# This holds regardless of the rectangle's position, so the correct J = Ixx+Iyy = 25.
coords = np.array([
[10, 20],
[14, 20],
[14, 23],
[10, 23],
])
poly = Polygon(coordinates=coords)
print("area: ", float(poly.area())) # 12.0 -- correct
print("centroid:", poly.centroid()) # [12. 21.5] -- correct
print("Ixx: ", float(poly.Ixx())) # 9.0 -- correct (matches parallel-axis theorem)
print("Iyy: ", float(poly.Iyy())) # 16.0 -- correct (matches parallel-axis theorem)
print("J: ", float(poly.J())) # 7300.0 -- WRONG. Expected 25.0 (= Ixx + Iyy)
Output on aerosandbox==4.2.9:
area: 12.0
centroid: [12. 21.5]
Ixx: 9.0
Iyy: 16.0
J: 7300.0
Root cause
Polygon.Ixx() (lines 167-191) correctly shifts to the centroid:
Ixx = 1 / 12 * np.sum(a * (y**2 + y * y_n + y_n**2))
Iuu = Ixx - A * centroid[1] ** 2 # <-- parallel-axis correction
return Iuu
Polygon.J() (lines 242-266) recomputes the same raw sums but returns them without the correction:
Ixx = 1 / 12 * np.sum(a * (y**2 + y * y_n + y_n**2))
Iyy = 1 / 12 * np.sum(a * (x**2 + x * x_n + x_n**2))
J = Ixx + Iyy
return J # <-- these Ixx/Iyy were never shifted to the centroid
So J() actually returns Ixx_about_origin + Iyy_about_origin, which equals (Ixx_centroid + A*y_c^2) + (Iyy_centroid + A*x_c^2) — correct only for shapes centered exactly on the origin.
Suggested fix
Since Ixx()/Iyy() already do this correctly, the simplest fix is for J() to just reuse them:
def J(self):
"""
Returns the nondimensionalized polar moment of inertia, taken about the centroid.
"""
return self.Ixx() + self.Iyy()
(A more efficient version could compute a, A, and the centroid once and reuse them across Ixx/Iyy/Ixy/J, since right now each method recomputes these independently — happy to open a PR for either the minimal fix or that refactor, whichever is preferred.)
Impact
Any code using Polygon.J() — including anything built on Airfoil/KulfanAirfoil, which inherit from Polygon — silently gets an incorrect value. This isn't just a fixed offset: the missing term (A*(x_c^2+y_c^2)) depends on the shape's centroid location, which changes as a shape is perturbed/optimized. So beyond wrong absolute values, this also biases any comparison or optimization that uses .J() to rank or constrain different shapes.
Environment
aerosandbox 4.2.9 (also present on current main, confirmed same code at the same lines)
- Python 3.10
Summary
Polygon.J()inaerosandbox/geometry/polygon.pyis documented as returning the polar moment of inertia "taken about the centroid," but the implementation never applies the parallel-axis correction — it silently returns the origin-referenced value instead.Polygon.Ixx()andPolygon.Iyy(), defined right next to it in the same file, apply the correction correctly, so this looks like a copy/paste that dropped the correction step rather than an intentional design choice.Minimal reproduction
A 4×3 rectangle, offset away from the origin so the bug is visible (an origin-centered shape would hide it, since the correction term would be zero):
Output on
aerosandbox==4.2.9:Root cause
Polygon.Ixx()(lines 167-191) correctly shifts to the centroid:Polygon.J()(lines 242-266) recomputes the same raw sums but returns them without the correction:So
J()actually returnsIxx_about_origin + Iyy_about_origin, which equals(Ixx_centroid + A*y_c^2) + (Iyy_centroid + A*x_c^2)— correct only for shapes centered exactly on the origin.Suggested fix
Since
Ixx()/Iyy()already do this correctly, the simplest fix is forJ()to just reuse them:(A more efficient version could compute
a,A, and the centroid once and reuse them acrossIxx/Iyy/Ixy/J, since right now each method recomputes these independently — happy to open a PR for either the minimal fix or that refactor, whichever is preferred.)Impact
Any code using
Polygon.J()— including anything built onAirfoil/KulfanAirfoil, which inherit fromPolygon— silently gets an incorrect value. This isn't just a fixed offset: the missing term (A*(x_c^2+y_c^2)) depends on the shape's centroid location, which changes as a shape is perturbed/optimized. So beyond wrong absolute values, this also biases any comparison or optimization that uses.J()to rank or constrain different shapes.Environment
aerosandbox4.2.9 (also present on currentmain, confirmed same code at the same lines)