diff --git a/src/Algorithms.cpp b/src/Algorithms.cpp index c708ab58..657ac45c 100644 --- a/src/Algorithms.cpp +++ b/src/Algorithms.cpp @@ -1,5 +1,5 @@ /*╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - * Algorithms.cpp is part of Brewtarget, and is copyright the following authors 2009-2023: + * Algorithms.cpp is part of Brewtarget, and is copyright the following authors 2009-2025: * • Eric Tamme * • Matt Young * • Philip Greggory Lee @@ -247,7 +247,7 @@ double Polynomial::rootFind( double x0, double x1 ) const { return newGuess; } -//╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ +//====================================================================================================================== bool Algorithms::isNan(double d) { // If using IEEE floating points, all comparisons with a NaN @@ -475,6 +475,12 @@ double Algorithms::abvFromOgAndFg(double og, double fg) { // Assert the parameters were supplied in the right order by checking that FG cannot by higher than OG Q_ASSERT(og >= fg); + // + // Previously, in different places in the code, we either used a very rough rule of thumb: + // + // double calculatedABV_pct = (og - fg) * 130 + // + // or we used the FALLBACK METHOD described below. // // The current calculation method we use comes from the UK Laboratory of the Government Chemist. It is what HM // Revenue and Customs (HMRC) encourage UK microbreweries to use to calculate ABV if they have "no or minimal @@ -526,7 +532,7 @@ double Algorithms::abvFromOgAndFg(double og, double fg) { ); // - // OLD METHOD, which is also the fallback + // FALLBACK METHOD // // From http://www.brewersfriend.com/2011/06/16/alcohol-by-volume-calculator-updated/: // "[This] formula, and variations on it, comes from Ritchie Products Ltd, (Zymurgy, Summer 1995, vol. 18, no. 2) @@ -536,31 +542,31 @@ double Algorithms::abvFromOgAndFg(double og, double fg) { // The relationship between the change in gravity, and the change in ABV is not linear. All these equations are // approximations." // - double abvByOldMethod = (76.08 * (og - fg) / (1.775 - og)) * (fg / 0.794); + double const abvByFallbackMethod = (76.08 * (og - fg) / (1.775 - og)) * (fg / 0.794); if (matchingGravityDifferenceRec == gravityDifferenceFactors.cend()) { qCritical() << Q_FUNC_INFO << "Could not find gravity difference record for difference of " << (excessGravityDiffx10 / 10.0) << "so using fallback method"; - return abvByOldMethod; + return abvByFallbackMethod; } - double abvByNewMethod = excessGravityDiff * matchingGravityDifferenceRec->factorToUse; + double const abvByHmrcMethod = excessGravityDiff * matchingGravityDifferenceRec->factorToUse; qDebug() << - Q_FUNC_INFO << "ABV old method:" << abvByOldMethod << "% , new method:" << abvByNewMethod << "% (used factor" << + Q_FUNC_INFO << "ABV old method:" << abvByFallbackMethod << "% , new method:" << abvByHmrcMethod << "% (used factor" << matchingGravityDifferenceRec->factorToUse << "and should be in range" << matchingGravityDifferenceRec->pctAbv_Min << "% -" << matchingGravityDifferenceRec->pctAbv_Max << "%)"; // The tables from UK HMRC have some sanity-check data, so let's use it! - if (abvByNewMethod < matchingGravityDifferenceRec->pctAbv_Min || - abvByNewMethod > matchingGravityDifferenceRec->pctAbv_Max) { + if (abvByHmrcMethod < matchingGravityDifferenceRec->pctAbv_Min || + abvByHmrcMethod > matchingGravityDifferenceRec->pctAbv_Max) { qWarning() << - Q_FUNC_INFO << "Calculated ABV of" << abvByNewMethod << "% is outside expected range (" << + Q_FUNC_INFO << "Calculated ABV of" << abvByHmrcMethod << "% is outside expected range (" << matchingGravityDifferenceRec->pctAbv_Min << "% -" << matchingGravityDifferenceRec->pctAbv_Max << "%)"; } - return abvByNewMethod; + return abvByHmrcMethod; } double Algorithms::correctSgForTemperature(double measuredSg, double readingTempInC, double calibrationTempInC) { diff --git a/src/model/BrewNote.cpp b/src/model/BrewNote.cpp index 6b8cb948..4870777d 100644 --- a/src/model/BrewNote.cpp +++ b/src/model/BrewNote.cpp @@ -1,5 +1,5 @@ /*╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - * model/BrewNote.cpp is part of Brewtarget, and is copyright the following authors 2009-2024: + * model/BrewNote.cpp is part of Brewtarget, and is copyright the following authors 2009-2025: * • Brian Rower * • Greg Meess * • Jonatan Pålsson @@ -563,16 +563,17 @@ double BrewNote::calculateABV_pct() { // 1 + [(og - 1) * (1.0 - %/100)] double const estFg = 1 + ((m_og-1.0)*(1.0 - atten_pct/100.0)); - double const calculatedABV = (m_og-estFg)*130; - this->setProjABV_pct(calculatedABV); + double const calculatedAbv_pct = Algorithms::abvFromOgAndFg(this->m_og, estFg); - return calculatedABV; + this->setProjABV_pct(calculatedAbv_pct); + + return calculatedAbv_pct; } double BrewNote::calculateActualABV_pct() { - double const abv = (m_og - m_fg) * 130; - this->setABV(abv); - return abv; + double const abv_pct = Algorithms::abvFromOgAndFg(this->m_og, this->m_fg); + this->setABV(abv_pct); + return abv_pct; } double BrewNote::calculateAttenuation_pct() { diff --git a/src/model/Recipe.cpp b/src/model/Recipe.cpp index d0e8e8ca..9de14409 100644 --- a/src/model/Recipe.cpp +++ b/src/model/Recipe.cpp @@ -1,5 +1,5 @@ /*╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - * model/Recipe.cpp is part of Brewtarget, and is copyright the following authors 2009-2024: + * model/Recipe.cpp is part of Brewtarget, and is copyright the following authors 2009-2025: * • Brian Rower * • Greg Greenaae * • Greg Meess @@ -1225,16 +1225,11 @@ class Recipe::impl { return; } - /** * Emits changed(ABV_pct). Depends on: m_og, m_fg */ void recalcABV_pct() { - // The complex formula, and variations comes from Ritchie Products Ltd, (Zymurgy, Summer 1995, vol. 18, no. 2) - // Michael L. Hall’s article Brew by the Numbers: Add Up What’s in Your Beer, and Designing Great Beers by Daniels. - double calculatedABV_pct = - (76.08 * (this->m_og_fermentable - this->m_fg_fermentable) / (1.775 - this->m_og_fermentable)) * - (this->m_fg_fermentable / 0.794); + double const calculatedABV_pct = Algorithms::abvFromOgAndFg(this->m_og_fermentable, this->m_fg_fermentable); if (!qFuzzyCompare(calculatedABV_pct, m_ABV_pct)) { qDebug() << @@ -2657,9 +2652,15 @@ double Recipe::ibuFromHopAddition(RecipeAdditionHop const & hopAddition) { boilTime_mins = static_cast(equipment->boilTime_min().value_or(Equipment::default_boilTime_mins)); } + // Assume 30 min cool time if boil is not set + double coolTime_mins = 30.0; + auto boil = this->boil(); if (boil) { boilTime_mins = boil->boilTime_mins(); + if (boil->coolTime_mins()) { + coolTime_mins = *boil->coolTime_mins(); + } } qDebug() << @@ -2674,7 +2675,7 @@ double Recipe::ibuFromHopAddition(RecipeAdditionHop const & hopAddition) { .postBoilVolume_liters = this->pimpl->m_finalVolumeNoLosses_l, .wortGravity_sg = m_og, .timeInBoil_minutes = boilTime_mins, // Seems unlikely in reality that there would be fractions of a minute - .coolTime_minutes = boil->coolTime_mins(), + .coolTime_minutes = coolTime_mins, }; if (equipment) { parms.kettleInternalDiameter_cm = equipment->kettleInternalDiameter_cm();