Translated from the author's Korean original. Read the original (한국어)
The Coriolis Force Did Zero Work, Yet the Impeller Added 1800 J/kg — Energy in a Rotating Frame
Coriolis power in a rotating frame is exactly zero. Head still appears because that force is what the blade has to push against.
The man who defined work has a force that does none#
Gaspard-Gustave de Coriolis is the one who put "work" and "kinetic energy" into the form we still use. His 1829 book was titled Du calcul de l'effet des machines — on calculating the effect of machines. It was written by an engineer counting what went into a water wheel and what came out.
The force that carries his name does no work at all. Its magnitude is irrelevant. One centrifugal impeller is enough to rerun the ledger. The Coriolis acceleration reaches 290 g, the ledger still reads J/kg, and the fluid's total enthalpy climbs by 1800 J/kg. Where those 1800 come from, and what disappears when a rotating-frame solver drops the matching term, is what the numbers settle.
1832: from water wheels to rotating coordinates#
The Korean serial Fluid Mechanics in History describes Paris in those years. The July Revolution of 1830 drove out the royalist Cauchy, and the republican Navier took his chair at the École Polytechnique. In 1832, Navier began working with Coriolis.
Coriolis had been chewing on water wheels. How do you count energy and work inside a rotating fluid machine? To do that arithmetic you have to sit on the rotating shaft. His work on rotating coordinates grew out of that, and his 1835 paper introduced what we now call the Coriolis force.
The order matters. The Coriolis force did not come from planetary rotation or meteorology. It came from trying to balance the energy ledger of a rotating machine. Flip the frame button from relative to absolute and only the orange path changes shape. The green line on the right stays flat.
The orange path bends with the camera. The green line does not. Raise omega and only the curve steepens.
A perpendicular force never reaches the ledger#
In a frame rotating at , a fluid particle with relative velocity obeys
The second term on the right is Coriolis, the third is centrifugal. Both are inertial terms, the price of spinning the coordinate system. How they enter the momentum equation was covered in rotating reference frames and MRF. Here only the energy side matters.
To get energy, dot each force with . The Coriolis term dies on the spot.
A cross product is perpendicular to both of its arguments. This is an identity — no approximation, no condition attached. It holds for any and any direction of .
The centrifugal term behaves differently. It equals , so any radial velocity keeps the dot product alive. In exchange, it has a potential.
Having a potential means it can be moved to the left side and folded into a constant.
So the conserved quantity is , not #
Integrating that equation along a streamline under steady, inviscid conditions gives rothalpy — rotational plus enthalpy.
is static enthalpy, the relative speed, and the blade speed. The minus sign on the last term is the centrifugal potential. The Coriolis term never appears at all, because its dot product was zero from the start.
Its link to the absolute total enthalpy follows from :
where is the swirl component of the absolute velocity. So can rise while stays put, and it rises by exactly — the Euler turbomachinery equation.
Three separate ledgers in Python#
Take a centrifugal impeller running from 50 mm to 150 mm in radius, with the channel height tapering from 20 mm to 8 mm. Set rad/s and 30 kg/s of water. Two backsweep angles, and . Continuity fixes the radial relative velocity, constant rothalpy fixes the pressure, and then the work done by the Coriolis, centrifugal, and blade reaction forces is integrated separately in time.
import numpy as np
OMEGA = 300.0 # angular speed [rad/s]
RHO = 1000.0 # density [kg/m^3]
MDOT = 30.0 # mass flow rate [kg/s]
R1, R2 = 0.05, 0.15 # inlet / outlet radius [m]
B1, B2 = 0.020, 0.008 # inlet / outlet channel height [m]
GRID = np.linspace(R1, R2, 4001)
def channel_state(r, beta_deg):
"""At radius r: relative components (w_r, w_t), blade speed U, absolute swirl c_t."""
b = B1 + (B2 - B1) * (r - R1) / (R2 - R1)
w_r = MDOT / (RHO * 2.0 * np.pi * r * b) # radial part set by continuity
w_t = -w_r * np.tan(np.radians(beta_deg)) # backswept blade -> against rotation
u = OMEGA * r
return w_r, w_t, u, u + w_t
def march_channel(beta_deg):
"""Hold rothalpy constant and build p/rho and absolute total enthalpy h0 along the channel."""
w_r, w_t, u, _ = channel_state(R1, beta_deg)
i_const = 0.5 * (w_r**2 + w_t**2) - 0.5 * u**2 # taking p1/rho = 0 as datum
cols = []
for r in GRID:
w_r, w_t, u, c_t = channel_state(r, beta_deg)
p = i_const - 0.5 * (w_r**2 + w_t**2) + 0.5 * u**2
cols.append((u, c_t, p + 0.5 * (w_r**2 + c_t**2),
p + 0.5 * (w_r**2 + w_t**2) - 0.5 * u**2))
return np.array(cols) # U, c_t, h0, I
def power_ledger(beta_deg):
"""Integrate Coriolis, centrifugal and blade-reaction power separately along the path."""
om = np.array([0.0, 0.0, OMEGA])
st = np.array([channel_state(r, beta_deg) for r in GRID])
w_r, w_t = st[:, 0], st[:, 1]
dwt_dr = np.gradient(w_t, GRID)
p_cor = np.zeros_like(GRID)
p_cen = np.zeros_like(GRID)
p_bld = np.zeros_like(GRID)
for k, r in enumerate(GRID):
w = np.array([w_r[k], w_t[k], 0.0]) # local (r, theta, z) orthonormal
f_cor = -2.0 * np.cross(om, w)
f_cen = -np.cross(om, np.cross(om, np.array([r, 0.0, 0.0])))
acc_t = w_r[k] * dwt_dr[k] + w_r[k] * w_t[k] / r # cylindrical curvature term included
f_bld_t = acc_t - f_cor[1] # leftover swirl accel belongs to the blade
p_cor[k] = f_cor @ w
p_cen[k] = f_cen @ w
p_bld[k] = OMEGA * r * f_bld_t # torque x angular speed = lab-frame power
dt = 1.0 / w_r # dt = dr / w_r
ints = [np.trapezoid(p * dt, GRID) for p in (p_cor, p_cen, p_bld)]
return ints + [np.max(np.abs(p_cor))]
def transported_h0(beta_deg, with_source):
"""Integrate the h0 transport equation directly. Source term is Omega * d(r c_theta)/dr."""
st = np.array([channel_state(r, beta_deg) for r in GRID])
src = OMEGA * np.gradient(GRID * st[:, 3], GRID) if with_source else np.zeros_like(GRID)
return np.trapezoid(src, GRID)
for beta in (0.0, 30.0):
tab = march_channel(beta)
d_h0 = tab[-1, 2] - tab[0, 2]
euler = tab[-1, 0] * tab[-1, 1] - tab[0, 0] * tab[0, 1]
drift = np.max(np.abs(tab[:, 3] - tab[0, 3]))
cor, cen, bld, cor_peak = power_ledger(beta)
ok = transported_h0(beta, True)
bad = transported_h0(beta, False)
print(f"beta = {beta:4.1f} deg U1 = {tab[0,0]:4.1f} m/s U2 = {tab[-1,0]:4.1f} m/s")
print(f" delta h0 from rothalpy = {d_h0:9.2f} J/kg")
print(f" U*c_theta (Euler) = {euler:9.2f} J/kg gap {abs(d_h0-euler):.1e}")
print(f" rothalpy max drift = {drift:9.1e} J/kg")
print(f" work by Coriolis = {cor:9.1e} J/kg (peak power {cor_peak:.1e} W/kg)")
print(f" work by centrifugal = {cen:9.2f} J/kg")
print(f" work by blade torque = {bld:9.2f} J/kg")
print(f" h0 transport, source = {ok:9.2f} J/kg head {ok/9.81:5.1f} m")
print(f" h0 transport, dropped = {bad:9.2f} J/kg head {bad/9.81:5.1f} m")beta = 0.0 deg U1 = 15.0 m/s U2 = 45.0 m/s
delta h0 from rothalpy = 1800.00 J/kg
U*c_theta (Euler) = 1800.00 J/kg gap 0.0e+00
rothalpy max drift = 1.1e-13 J/kg
work by Coriolis = 0.0e+00 J/kg (peak power 0.0e+00 W/kg)
work by centrifugal = 900.00 J/kg
work by blade torque = 1800.00 J/kg
h0 transport, source = 1800.00 J/kg head 183.5 m
h0 transport, dropped = 0.00 J/kg head 0.0 m
beta = 30.0 deg U1 = 15.0 m/s U2 = 45.0 m/s
delta h0 from rothalpy = 1737.98 J/kg
U*c_theta (Euler) = 1737.98 J/kg gap 0.0e+00
rothalpy max drift = 7.1e-14 J/kg
work by Coriolis = 2.2e-16 J/kg (peak power 1.4e-12 W/kg)
work by centrifugal = 900.00 J/kg
work by blade torque = 1737.98 J/kg
h0 transport, source = 1737.98 J/kg head 177.2 m
h0 transport, dropped = 0.00 J/kg head 0.0 mDrag backsweep from 0° to 30° and only the purple blade work drops, from 1800.00 to 1737.98 J/kg.
The orange Coriolis arrow is the longest mark on the screen, and its ledger stays at zero. Raise omega and that bar still does not grow.
The rothalpy drift is J/kg, which is double-precision round-off. The Coriolis account reads exactly 0.0 at zero backsweep and at 30°. The second number is floating-point residue, not physics.
900 and 1800 — who paid the other half#
Two numbers stand out. The centrifugal force did 900 J/kg of work, yet total enthalpy rose by 1800 J/kg. Exactly twice.
Those 900 are money circulating inside the relative frame only. They split into pressure and relative kinetic energy. That is a different account from the 1800 the fluid actually received in the lab frame.
Inlet blade speed is 15.0 m/s and outlet speed is 45.0 m/s. Drop those into : 45 squared is 2025, 15 squared is 225, and half the difference is 900. The printed centrifugal work, 900.00 J/kg, matches that expression to the hundredth. At zero backsweep the blade-torque line is 1800.00, exactly twice the centrifugal work. Lab-frame power is torque times , so the 900 in the relative ledger is counted twice. At 30° the blade work falls to 1737.98 J/kg and the Euler term falls to the same 1737.98. The gap prints 0.0e+00. Centrifugal work stays 900.00. Only swirl moved. points against rotation, so exit is smaller than , and sits below 1800. Rothalpy drift is 1.1e-13 J/kg at 0° and 7.1e-14 at 30°. That is holding as a constant inside double precision, not a new loss. Head 183.5 m is 1800.00 divided by 9.81. Head 177.2 m is 1737.98 divided by the same 9.81. Kill the source and both heads print 0.0 m, backsweep or not. The 30° Coriolis residue, 2.2e-16 J/kg, cannot budge that 0.0 m. The 290 g in the opening is an acceleration, not a power. stays an identity at rad/s, even when the acceleration is 290 times gravity. Thirty kilograms per second through a channel that narrows from 20 mm to 8 mm only raises . The arrow gets longer. The work stays 0.0e+00. At 50 mm, m/s. At 150 mm it is 45.0 m/s. The centrifugal potential closes on those two radii.
The blade paid the other 900. On a radial blade the relative velocity has no swirl component. Keeping it that way requires something to cancel the Coriolis force exactly, and that something is the blade's pressure side. By reaction the fluid feels a force of equal magnitude, whose torque is .
Here is the split. In the relative frame that force is perpendicular to , so it does no work. In the absolute frame the blade is turning at , so torque times angular speed is power. Integrate and you get J/kg — the work by blade torque line in the output.
The Coriolis force hands the fluid nothing. It makes the fluid push on the blade, and shaft work enters through that reaction path. The force picks the direction. The shaft pays.
When a rotating-frame solver transports as is#
That was the physics. In code, the accident happens in a predictable place.
It is about which variable you transport in the energy equation over a rotating zone. What is carried by the relative velocity and conserved without a source is . If you want to carry on the relative velocity, a source term has to come with it.
with the streamwise coordinate. Drop that term and you get the last two lines of the output. With the source: 1800 J/kg, a head of 183.5 m. Without it: 0.00 J/kg, a head of 0 m. The fluid crosses the impeller and nothing happens to it.
The symptom is confusing because the residuals look fine. The equation converges nicely — it just converges to the answer with zero head. Refining the grid does not help; zero stays zero.
In OpenFOAM-family solvers, this shape appears when the total-energy definition on the rhothermo side and the MRF correction fall out of step. Drop one term in the coordinate change and the ledger slips. That is why the acc_t line above carries the curvature term . Remove it and the blade ledger at 30° backsweep drifts to 1802.06. Along a streamline is the constant and is not. The non-rotating cousin, where total pressure fails between streamlines, is Bernoulli's constant crossing a streamline.
A printed head of 0.0 m means lost its source#
Inside the rotating zone the constant is . Cross into the stationary zone and it is again. Carrying on the relative velocity needs attached. Turn that source off and 1800.00 J/kg becomes 0.00, while head falls from 183.5 m to 0.0 m. Residuals look clean. They converged to zero. Refining the mesh leaves the zero where it is.
Adding Coriolis as an energy source is the wrong repair. Its power prints 0.0e+00, and at 30° it is still J/kg. The term that belongs there is the swirl source, not Coriolis. He reached rotating coordinates while counting water-wheel efficiency. In the ledger he built, the force with his name is always zero. A hundred and ninety years later, leaving that zero alone is what keeps the shaft work at 1800 J/kg.
Related
Share if you found it helpful.