Logo Search packages:      
Sourcecode: qbrew version File versions

calc.cpp

/***************************************************************************
  calc.cpp
  -------------------
  Calculations for brewing model
  -------------------
  Copyright (c) 2004 David Johnson
  Please see the header file for copyright and license information.
***************************************************************************/

#include <math.h>

#include "calc.h"
#include "recipe.h"

using namespace CalcResource;

QValueList<UEntry> Calc::utable_;
double Calc::efficiency_ = 0.75;
bool Calc::tinseth_ = true;
bool Calc::morey_ = true;

//////////////////////////////////////////////////////////////////////////////
// recalc()
// --------
// Recalculate major values

void Calc::recalc(Recipe *r)
{
    r->og_ = calcOG(r);
    r->ibu_ = calcIBU(r);
    r->srm_ = calcSRM(r);
}

//////////////////////////////////////////////////////////////////////////////
// calcOG()
// --------
// Calculate the original gravity

double Calc::calcOG(Recipe *r)
{
    const double STEEP_YIELD = 0.33;

    GrainList *list = r->grains();
    double est = 0.0;
    GrainIterator it;
    for (it=list->begin(); it!=list->end(); ++it) {
        double yield = (*it).yield();
        switch ((*it).use()) {
            case GRAIN_MASHED:
                // adjust for mash efficiency
                yield *= efficiency_;
                break;
            case GRAIN_STEEPED:
                // steeped grains don't yield nearly as much as mashed grains
                yield *= STEEP_YIELD;
                break;
            case GRAIN_EXTRACT:
                break;  // no modifications necessary
            default:
                break;
        }
        est += yield;
    }
    est /= r->size().amount(Volume::gallon);
    return est + 1.0;
}

//////////////////////////////////////////////////////////////////////////////
// calcIBU()
// ---------
// Calculate the bitterness

double Calc::calcIBU(Recipe *r)
{
    // switch between two possible calculations
    if (tinseth_)
        return calcTinsethIBU(r);
    else
        return calcRagerIBU(r);
}

//////////////////////////////////////////////////////////////////////////////
// calcRagerIBU()
// --------------
// Calculate the bitterness based on Rager's method (table method)

double Calc::calcRagerIBU(Recipe *r)
{
    HopsList *list = r->hops();
    double bitterness = 0.0;
    HopIterator it;
    for (it=list->begin(); it!=list->end(); ++it) {
        bitterness += (*it).HBU() * utilization((*it).time());
        // TODO: we should also correct for hop form
    }
    bitterness /= r->size().amount(Volume::gallon);
    // correct for boil gravity
    if (r->og_ > 1.050) bitterness /= 1.0 + ((r->og_ - 1.050) / 0.2);
    return bitterness;
}

//////////////////////////////////////////////////////////////////////////////
// calcTinsethIBU()
// ----------------
// Calculate the bitterness based on Tinseth's method (formula method)
// The formula used is:
// (1.65*0.000125^(gravity-1))*(1-EXP(-0.04*time))*alpha*mass*1000
// ---------------------------------------------------------------
// (volume*4.15)

// TODO: check and recheck this formula

double Calc::calcTinsethIBU(Recipe *r)
{
    const double GPO = 28.3495; // grams per ounce
    const double LPG = 3.785;   // liters per gallon

    const double COEFF1 = 1.65;
    const double COEFF2 = 0.000125;
    const double COEFF3 = 0.04;
    const double COEFF4 = 4.15;

    HopsList *list = r->hops();
    double bitterness = 0.0;
    HopIterator it;
    for (it=list->begin(); it!=list->end(); ++it) {
        double ibu = (COEFF1 * pow(COEFF2, (r->og_ - 1.0))) *
            (1.0 - exp(-COEFF3 * (*it).time())) *
            ((*it).alpha()) * (*it).weight().amount(Weight::ounce) * 1000.0;
        ibu /= (r->size().amount(Volume::gallon)) * COEFF4;
        bitterness += ibu;
    }
    bitterness *= (GPO / LPG) / 100.0;
    return bitterness;
}

//////////////////////////////////////////////////////////////////////////////
// utilization
// -----------
// Look up the utilization for the given time

double Calc::utilization(unsigned time)
{
    QValueList<UEntry>::Iterator it;
    for (it=utable_.begin(); it!=utable_.end(); ++it) {
        if (time >= (*it).time) return (double((*it).utilization));
    }
    return 0.0;
}

//////////////////////////////////////////////////////////////////////////////
// calcSRM()
// ---------
// Calculate the color

double Calc::calcSRM(Recipe *r)
{
    GrainList *list = r->grains();
    double srm = 0.0;
    GrainIterator it;
    for (it=list->begin(); it!=list->end(); ++it) {
        srm += (*it).HCU();
    }
    srm /= r->size().amount(Volume::gallon);

    // switch between two possible calculations
    if (morey_) {
        // power model (morey) [courtesy Rob Hudson <rob@tastybrew.com>]
        srm = (pow(srm, 0.6859)) * 1.4922;
        if (srm > 50) srm = 50;
    } else {
        // linear model (daniels)
        if (srm > 8.0) {
            srm *= 0.2;
            srm += 8.4;
        }
    }
    return srm;
}

// TODO: following formulas need to use constants

//////////////////////////////////////////////////////////////////////////////
// FGEstimate()
// ------------
// Return estimatated final gravity

double Calc::FGEstimate(Recipe *r)
{
    if (r->og_ <= 0.0) return 0.0;
    return (((r->og_ - 1.0) * 0.25) + 1.0);
}

//////////////////////////////////////////////////////////////////////////////
// SgToP()
// -------
// Convert specific gravity to degrees plato

double Calc::SgToP(double sg) {
    return ((-463.37) + (668.72*sg) - (205.35 * sg * sg));
}

//////////////////////////////////////////////////////////////////////////////
// ABV()
// -----
// Calculate alcohol by volume

double Calc::ABV(Recipe *r)
{
    double fg = FGEstimate(r);
    return ABW(r->og_, fg) * fg / 0.79;
}

double Calc::ABV(double og, double fg)
{ 
    return ABW(og, fg) * fg / 0.79;
}

//////////////////////////////////////////////////////////////////////////////
// ABW()
// -----
// Calculate alcohol by weight
// NOTE: Calculations were taken from http://hbd.org/ensmingr/

double Calc::ABW(Recipe *r)
{
    return (ABW(r->og_, FGEstimate(r)));
}
 
double Calc::ABW(double og, double fg) { 
    double oe, ae, re;
    oe = SgToP(og);
    ae = SgToP(fg);
    re = (0.1808 * oe) + (0.8192 * ae); // real extract
    return ((oe - re) / (2.0665 - 0.010665 * oe) / 100.0);
}

//////////////////////////////////////////////////////////////////////////////
// addUEntry()
// -----------
// Add an entry to the utilization table

void Calc::addUEntry(const UEntry &u)
{
    // keep the list sorted from highest time to lowest
    QValueList<UEntry>::Iterator it;
    if (utable_.isEmpty()) {
        utable_.append(u);
    } else {
        for (it=utable_.begin(); it != utable_.end(); ++it) {
            if ((*it).time < u.time)
                break;
        }
        utable_.insert(it, u);
    }
}

Generated by  Doxygen 1.6.0   Back to index