Skip to content

Fix floating point exception in FGGasCell when gas contents approach zero#1394

Open
DheerendraTomar wants to merge 1 commit into
JSBSim-Team:masterfrom
DheerendraTomar:fix/gas-cell-fpe-on-balloon-burst
Open

Fix floating point exception in FGGasCell when gas contents approach zero#1394
DheerendraTomar wants to merge 1 commit into
JSBSim-Team:masterfrom
DheerendraTomar:fix/gas-cell-fpe-on-balloon-burst

Conversation

@DheerendraTomar
Copy link
Copy Markdown

When a gas cell is rapidly emptied (e.g. weather balloon burst at high altitude), the gas Contents, Volume, and Pressure can all approach zero. This causes division-by-zero floating point exceptions in three places within FGGasCell::Calculate():

  1. GasDensity = GasMass / GasVolume - when GasVolume reaches zero
  2. Volume = Contents * R * Temperature / Pressure - when Pressure is zero
  3. dVolumeIdeal computation dividing by Pressure and OldPressure

Add guards to handle these zero-denominator cases:

  • Return GasDensity = 0 when GasVolume is zero (no gas = no density)
  • Set Volume to just BallonetsVolume when Pressure is zero (empty cell)
  • Set dVolumeIdeal to 0 when either Pressure is zero (no gas to expand)

Fixes #654

Copy link
Copy Markdown
Collaborator

@andgi andgi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me.

Cheers,
Anders Gidenstam

@bcoconni
Copy link
Copy Markdown
Member

bcoconni commented Mar 2, 2026

I am a bit surprised by this PR:

  • Following the commit 464e591, Contents is already guarded from going below $10^{-8}$ so I am struggling to see a scenario where GasVolume == 0.0 ?
    Contents =
    max(1E-8, Contents - Pressure * VolumeValved / (R * Temperature));
  • Ditto for Pressure which cannot go below the (non-zero) ambient pressure in.Pressure aka AirPressure per the definition of the function max and per the fact that MaxOverpressure is positive.
    const double IdealPressure =
    Contents * R * Temperature / (MaxVolume - BallonetsVolume);
    if (IdealPressure > AirPressure + MaxOverpressure) {
    Pressure = AirPressure + MaxOverpressure;
    } else {
    Pressure = max(IdealPressure, AirPressure);
    }

Could you provide an example that is actually fixed by your PR ? I mean an example which crashes without your patch and works when your patch is committed.

@AirshipSim
Copy link
Copy Markdown
Contributor

AirshipSim commented Mar 10, 2026

When a gas cell is rapidly emptied (e.g. weather balloon burst at high altitude), the gas Contents, Volume, and Pressure can all approach zero.

If a cell burst, pressure may approach zero but is at worse Patmosphere, I believe. Also, Patmosphere only reaches close to zero at the Kármán line (100km). A high altitude balloon usually goes up to 40km.

The concept of a gas cell with zero volume is troubling. That would be an empty envelope, a simple piece of fabric. Basically you can put a T-shirt in the air and call it a gas cell with zero volume.

@DheerendraTomar 's case seems indeed exceptional.

@DheerendraTomar DheerendraTomar force-pushed the fix/gas-cell-fpe-on-balloon-burst branch from d873daa to bdb1991 Compare March 12, 2026 10:18
@DheerendraTomar
Copy link
Copy Markdown
Author

@bcoconni @AirshipSim Thank you both for the thoughtful feedback.

You're absolutely right that with the current max(1E-8, ...) clamp from commit 464e591, the FPE cannot occur on master. My original PR description was misleading — I should have been clearer about the intent.

The goal of this PR is to replace the temporary fix (464e591, which was explicitly titled "Temporary fix for issue #654") with a proper solution. Here's why the temporary clamp is problematic:

  1. It's not physically correct. max(1E-8, ...) means 10⁻⁸ mol of gas can never be vented from the cell, no matter what. The cell can never truly empty. For the gas cell, this is a tiny amount and mostly harmless. But the analogous clamp in FGBallonet is max(1.0, ...) — that's 1 mol of air permanently trapped in the ballonet that can never be removed by any valve operation. That's a noticeable unphysical artifact.

  2. The issue is still open. Issue Weather Balloon script crash #654 was left open precisely because 464e591 was a workaround, not a fix. The underlying problem — that the code has no defined behavior for an empty gas cell — was never addressed.

  3. Our approach: early return for empty cell. Instead of clamping Contents to an artificial minimum or guarding individual divisions, we allow Contents to reach 0.0 naturally via max(0.0, ...), and then handle the empty state explicitly with an early return. This sets physically correct values:

    • Pressure = AirPressure (an empty envelope equalizes to ambient)
    • Volume = BallonetsVolume (only ballonet volume remains)
    • Mass = 0 (no gas)
    • dVolumeIdeal = 0 (nothing to expand)

    This is analogous to the existing if (Contents > 0) guard already present for the temperature computation at line 288.

@AirshipSim You raise a good point that a zero-volume gas cell is "an empty envelope, a simple piece of fabric." That's exactly right — and that's what happens physically when a balloon bursts. Our early return handles this state cleanly rather than pretending there's always some residual gas.

To reproduce the original crash (before 464e591): revert the max(1E-8, ...) clamp back to max(0.0, ...) in FGGasCell::Calculate() and run scripts/weather-balloon.xml — it will SIGFPE. Our early-return block prevents this crash while also removing the artificial clamp.

@AirshipSim
Copy link
Copy Markdown
Contributor

weather balloon burst at high altitude

The thing is, what do you actually mean by that? The term 'burst' feels like the cell is utterly destroyed in an instant. In which case, pressure, volume, etc, none of that makes any sense anymore. It sounds like a "crash event" which should be handled out-of-band. For example, the instance of the class representing the gas cell is removed from the "High Altitude Balloon" object. (Note: I have not read the buoyancy code yet, speculative)

Pressure = AirPressure (an empty envelope equalizes to ambient)
Volume = BallonetsVolume (only ballonet volume remains)
Mass = 0 (no gas)

Well, you write Pressure = AirPressure, but isn't Pressure the Pressure of the moles of lifting gas (as in Pressure * V = n(lifting gas) * RT? In which case, Mass(lifting gas) must be different from 0. An empty envelope does not equalize to ambient, it has no volume so its pressure is undefined (P = nRT / 0). This is what I tried to say with my "simple piece of fabric" image. Again, I have not read the code, so I will stop commenting on this PR. I was just trying to understand from the theoretical point of view.

@bcoconni
Copy link
Copy Markdown
Member

Thanks for the clarification, @DheerendraTomar.

However the root of the problem is that we have a mechanism (the valve opening) that leads to the spontaneous creation of vacuum in the envelope. That is the physical meaning of Contents == 0. And this is in violation of the 2nd law of thermodynamics (just as heat moving from cold to hot) so the underlying equations in FGGasCell.cpp are wrong.

The code in FGGasCell.cpp somehow involuntary hides that inconsistency behind the concomitant reduction of the volume as the content vanishes which, when compared with the intuition of a deflating balloon, seems to make sense. But would the envelope be rigid, the equations for the valve opening would also lead to Content == 0.0 meaning that vacuum would be created by the mere fact that the gas density inside the envelope is lower than the density of the ambient gas:

const double DeltaPressure =
Pressure + CellHeight * g * (AirDensity - GasDensity) - AirPressure;
const double VolumeValved =
ValveOpen * ValveCoefficient * DeltaPressure * dt;

That would be the cheapest vacuum pump in the world! Just heat up the air inside an envelope to reduce its density, open a valve and voilà! perfect vacuum has been created in the envelope.

As far as I am concerned your patch does not address the actual problem and is nothing more than a glorified version of my fix, hence your PR remains a temporary fix and as such I am not willing to merge it.

I will not consider the issue #654 fixed until the physics in FGGasCell.cpp are fixed.

@DheerendraTomar
Copy link
Copy Markdown
Author

@bcoconni Thank you — that's an excellent point about the thermodynamics, and I think you've identified the real bug.

I dug into the valve equations and found something interesting. In the manual valving section, GasDensity is computed as:

GasDensity = GasMass / GasVolume = (Contents * M_gas) / (Contents * R * T / P) = M_gas * P / (R * T)

The Contents term cancels out entirely — GasDensity is independent of how much gas remains in the cell. This means DeltaPressure stays positive for any lighter-than-air gas regardless of how much has been vented. The valve model therefore drives Contents monotonically toward zero with no equilibrium point.

Physically, this is wrong. As the gas cell deflates and the internal pressure drops toward ambient, the pressure differential at the valve should diminish. More importantly, once internal pressure equals ambient, air would begin flowing in through the valve to fill the void — the valve should be bi-directional.

I prototyped a fix: making CellHeight scale with the ratio of current volume to maximum volume (CellHeight *= GasVolume / MaxVolume), so DeltaPressure naturally diminishes to zero as the cell deflates. This addresses the existing FIXME on line 319 (FixMe: CellHeight should depend on current volume).

However, this breaks the weather balloon burst mechanism. The aircraft XML uses valve_coefficient = 100000 to simulate catastrophic burst via instantaneous gas release through the valve. With volume-scaled CellHeight, the venting slows to a crawl before complete emptying (as CellHeight → 0, DeltaPressure → 0), leaving a tiny residual that triggers a table lookup assertion in the aero model.

For the FGBallonet case, the issue is simpler: DeltaPressure = Pressure - AirPressure, and equilibrium is when Pressure = AirPressure. Since IdealPressure = Contents * R * T / MaxVolume, the equilibrium contents is:

Contents_eq = AirPressure * MaxVolume / (R * Temperature)

Clamping to max(Contents_eq, ...) instead of max(1.0, ...) would be physically correct.

What would you suggest as the right scope for this PR? I see two possible paths:

  1. Minimal: Keep the current early-return approach (which handles the burst case correctly) and address the valve physics in a separate PR.

  2. Combined: Fix the valve physics (dynamic CellHeight + ballonet equilibrium clamping) and add a separate burst/destruction mechanism that bypasses the valve entirely.

Happy to go either direction.

@DheerendraTomar
Copy link
Copy Markdown
Author

@bcoconni @AirshipSim Would you please give me a direction so that I can proceed with it?

@bcoconni
Copy link
Copy Markdown
Member

bcoconni commented Apr 21, 2026

@DheerendraTomar, sorry for my late response. I have missed your previous message, my apologies.

Thanks for having investigated the topic in such depth. Your approach looks very interesting and is more in line with what I was expecting to address the topic properly. I will need a bit more time to digest your proposal (sorry not much free time at the moment). I should be able to give you some feedback later this week, no later than the coming week-end.

In the meantime, I would like @andgi to review your suggestion as well since he is the original author of the code.

@bcoconni
Copy link
Copy Markdown
Member

Thanks for the proposal @DheerendraTomar.

. With volume-scaled CellHeight, the venting slows to a crawl before complete emptying (as CellHeight → 0, DeltaPressure → 0), leaving a tiny residual.

Yes, that's really the point.

What would you suggest as the right scope for this PR? I see two possible paths:

  1. Minimal: Keep the current early-return approach (which handles the burst case correctly) and address the valve physics in a separate PR.
  2. Combined: Fix the valve physics (dynamic CellHeight + ballonet equilibrium clamping) and add a separate burst/destruction mechanism that bypasses the valve entirely.

I would vote for option 2. Regarding the burst event, I would force the content to a small value 0.001 rather than opening the valve:

diff --git a/aircraft/weather-balloon/weather-balloon.xml b/aircraft/weather-balloon/weather-balloon.xml
index 3f0557254..9e4790351 100644
--- a/aircraft/weather-balloon/weather-balloon.xml
+++ b/aircraft/weather-balloon/weather-balloon.xml
@@ -190,7 +190,6 @@
       buoyant_forces/gas-cell/volume-ft3 ge buoyant_forces/gas-cell/max_volume-ft3
       buoyant_forces/gas-cell/burst ge 0.5
     </test>
-    <output>buoyant_forces/gas-cell/valve_open</output>
    </switch>
   </channel>
 
diff --git a/scripts/weather-balloon.xml b/scripts/weather-balloon.xml
index 295f94bab..9da4ebbdc 100644
--- a/scripts/weather-balloon.xml
+++ b/scripts/weather-balloon.xml
@@ -31,6 +31,7 @@
         <property>velocities/vt-fps</property>
         <property>metrics/radius-ft</property>
       </notify>
+      <set name="buoyant_forces/gas-cell/contents-mol" value="0.001"/>
       <condition> buoyant_forces/gas-cell/burst ge 0.5 </condition>
     </event>

With these values, the weather balloon hits the ground at t=6752.38s instead of t=6752.8s

@DheerendraTomar DheerendraTomar force-pushed the fix/gas-cell-fpe-on-balloon-burst branch from 6f51448 to 1f1ec9e Compare May 20, 2026 09:30
@DheerendraTomar
Copy link
Copy Markdown
Author

@bcoconni @andgi I think you would need to approve for the pipeline to run.

Replace the temporary clamp on Contents with a physically correct valve
model, add explicit empty-cell handling, and decouple the balloon burst
simulation from the valve. Fixes JSBSim-Team#654.

Physics fixes in FGGasCell:
- Scale CellHeight by GasVolume / MaxVolume so the buoyancy-driven
  DeltaPressure diminishes naturally as the cell deflates, letting the
  valve reach equilibrium.
- Clamp DeltaPressure to >= 0.0 during manual valving to prevent
  unphysical vacuum creation when internal pressure falls to ambient.
- Replace the artificial max(1E-8, ...) clamp on Contents with
  max(0.0, ...), and handle Contents <= 0 explicitly by setting the
  cell to a physically correct empty state (zero mass, ambient
  pressure, ballonet-only volume) and returning early. This avoids the
  division-by-zero FPE that the old clamp was working around.

Physics fix in FGBallonet:
- Replace the temporary max(1.0, ...) clamp with a clamp to the proper
  thermodynamic equilibrium AirPressure * MaxVolume / (R * Temperature),
  plus an empty-ballonet early return as a safety net.

Decouple burst mechanism:
- The previous weather balloon script simulated burst by opening the
  valve with a huge valve_coefficient, which conflicts with the new
  valve physics where venting correctly slows as volume drops.
- Drop the valve_open output from the Burst flight control channel and
  have the burst event set buoyant_forces/gas-cell/contents-mol to
  0.001 directly, simulating catastrophic destruction without relying
  on the valve model.
@DheerendraTomar DheerendraTomar force-pushed the fix/gas-cell-fpe-on-balloon-burst branch from 1f1ec9e to 58593fa Compare May 20, 2026 09:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Weather Balloon script crash

4 participants