Maintainer triage (June 29, 2026)
- Status: Verified bug against the current
master source in this fork.
- Severity: High.
- Area labels: area:drying, area:thermo.
- Tackle batch: P2 - shared thermo, phase, and basis correctness.
The source review confirmed that the cited code path and failure mode are still present. The original audit details are preserved below for reproduction notes and suggested fixes.
Original audit details
Part of the model-correctness audit — see #17.
Severity: high | Category: thermo | Location: PharmaPy/Drying_Model.py lines 402-406
What's wrong
In Drying.energy_balance the evaporative-cooling term for the condensed (solid+liquid) phase is computed as drying_terms = (dry_rate[:, idx_volatiles] * latent_heat * 2).sum(axis=1) and then used in dTcond_dt = (-drying_terms + heat_transf - heat_loss_cond) / denom_cond. The factor of 2 has no comment or physical basis.
Why it's incorrect
Phases.getHeatVaporization (Phases.py:623-657, Watson correlation) already returns the FULL molar/mass latent heat of vaporization. The energy removed from the condensed phase by evaporating a flux 'dry_rate' is exactly dry_rate * deltaHvap (once). Multiplying by 2 doubles the evaporative cooling sink, so the predicted solid/liquid temperature drop during the constant-rate drying period is overstated by ~2x and the condensed-phase energy balance no longer closes against the latent heat that physically leaves with the vapor. The companion gas-phase balance uses 'sensible_heat' and 'heat_transf' with no compensating factor, so total enthalpy is not conserved between the two phases.
Suggested regression test
Remove the '* 2' and run an isolated energy check: with heat_transf and heat_loss set to zero and a constant drying rate, the cumulative enthalpy removed from the condensed phase (integral of denom_conddTcond_dt) must equal dry_ratedeltaHvap*time. With the factor of 2 it will be off by 2x.
Verification: 1/1 adversarial reviewers confirmed (refuted: 0).
Maintainer triage (June 29, 2026)
mastersource in this fork.The source review confirmed that the cited code path and failure mode are still present. The original audit details are preserved below for reproduction notes and suggested fixes.
Original audit details
Part of the model-correctness audit — see #17.
Severity: high | Category: thermo | Location:
PharmaPy/Drying_Model.pylines 402-406What's wrong
In Drying.energy_balance the evaporative-cooling term for the condensed (solid+liquid) phase is computed as drying_terms = (dry_rate[:, idx_volatiles] * latent_heat * 2).sum(axis=1) and then used in dTcond_dt = (-drying_terms + heat_transf - heat_loss_cond) / denom_cond. The factor of 2 has no comment or physical basis.
Why it's incorrect
Phases.getHeatVaporization (Phases.py:623-657, Watson correlation) already returns the FULL molar/mass latent heat of vaporization. The energy removed from the condensed phase by evaporating a flux 'dry_rate' is exactly dry_rate * deltaHvap (once). Multiplying by 2 doubles the evaporative cooling sink, so the predicted solid/liquid temperature drop during the constant-rate drying period is overstated by ~2x and the condensed-phase energy balance no longer closes against the latent heat that physically leaves with the vapor. The companion gas-phase balance uses 'sensible_heat' and 'heat_transf' with no compensating factor, so total enthalpy is not conserved between the two phases.
Suggested regression test
Remove the '* 2' and run an isolated energy check: with heat_transf and heat_loss set to zero and a constant drying rate, the cumulative enthalpy removed from the condensed phase (integral of denom_conddTcond_dt) must equal dry_ratedeltaHvap*time. With the factor of 2 it will be off by 2x.
Verification: 1/1 adversarial reviewers confirmed (refuted: 0).