Skip to content

TVC applies gimbal moments without the matching lateral thrust force #21

Description

@thc1006

Summary

The TVC path reduces axial thrust and applies the moments of a deflected thrust vector, but the lateral components of that same vector never reach the translational equations.

In rocketpy/simulation/flight.py, u_dot_generalized:

thrust3 = effective_thrust * np.sqrt(
    1 - np.sin(gimbal_angle_x * pi/180) ** 2 - np.sin(gimbal_angle_y * pi/180) ** 2
)
tvc_lever = self.rocket.nozzle_to_cdm
M1 += np.sin(gimbal_angle_x * pi/180) * effective_thrust * tvc_lever
M2 += np.sin(gimbal_angle_y * pi/180) * effective_thrust * tvc_lever

R1 and R2 only ever accumulate aerodynamic contributions (R1 += X, R2 += Y from the surfaces). I could not find T*sin(x) or T*sin(y) being added anywhere, and the body-axis equations use R1 and R2 as they stand while the axial one adds thrust3.

So a gimbal command produces a moment and a reduced axial force, but no lateral force on the centre of mass. The same physical force cannot do one without the other.

Numbers

With a 15 degree limit on each axis, commanding both at once:

omitted lateral force 0.366 * T
thrust3 0.931 * T
actual combined deflection 21.47 degrees

That last row is worth noting separately. Two independent 15 degree axes reach a combined deflection of 21.47 degrees, so a parameter documented as a maximum gimbal angle does not bound what the model actually applies.

There is also a domain problem at larger limits: sin(x)^2 + sin(y)^2 > 1 makes the square root argument negative, which happens once both axes exceed 45 degrees. Nothing validates that when the actuator range is configured.

Is it deliberate?

I considered that TVC might be intended as a pure control-moment actuator. If it were, thrust3 would not be reduced by the deflection. Reducing it is what says the code means to model a real deflected vector, so this reads as an oversight rather than a modelling choice.

Suggestion

Build the body-frame thrust vector once and use it for both sides:

F = effective_thrust * Vector([sin(x), sin(y), sqrt(1 - sin(x)**2 - sin(y)**2)])
# translational: add F
# rotational:    add r x F

That keeps the force and the moment consistent by construction rather than by two formulas that have to be kept in step, and it removes the question of which convention each one follows.

Worth deciding at the same time whether the configured limit is per axis or a total cone angle, and validating the two-axis domain so the square root cannot go negative.

A regression test could assert that the force norm equals the effective thrust, that both lateral components are non-zero for a two-axis command, and that the moment matches r x F.

Context

Found while reviewing the TVC integration in BalloonPoppingChallenge, which pins this code. It does not affect that competition's fairness, since every entrant runs the same model, and I am not proposing to change it there before their release. Raising it here because it is a physics question rather than an integration one.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions