Real time IBU calculation

Homebrew Talk - Beer, Wine, Mead, & Cider Brewing Discussion Forum

Help Support Homebrew Talk - Beer, Wine, Mead, & Cider Brewing Discussion Forum:

This site may earn a commission from merchant affiliate links, including eBay, Amazon, and others.
Joined
Jul 24, 2006
Messages
14,260
Reaction score
786
Location
Southwest
I mentioned this in the Android brewing software discussion, but I figured it warranted its own thread. IMHO, it'd be swell if there were real time IBU approximation incorporated into the hop addition section of (insert brewing software of choice). For sh*ts and giggles, I whipped up a Java app showing that it can be done. It's a pretty rough hack, but you'll get the idea. It decreases the actual volume and increases the SG based on a user defined boil off rate and pre-boil volume/SG. The IBU calculation is based on actual volume/SG at each update.

[ame=http://www.youtube.com/watch?v=9xhJVWBrnwo]YouTube - Running IBU calculator[/ame]

The actual utility of such a feature may be somewhat limited, but I find it fascinating to see how the wort changes over time.
 
Geek.

Sorry, couldn't help it. I imagine there are plenty of people who are into the science of brewing who would enjoy something like that.
 
I'll see if I can clean up the event handler portion of the code (where all the work is done) and post it this weekend.

The way I see it, this should be a configurable hop/boil timer that just has the added bonus of real time SG/volume/IBU approximation.
 
I have so many ideas for this junk I could go crazy. Wish I wasn't working on like 10 projects at once. Thanks Yuri. Are you also setup for android development? Do you use Eclipse?
 
I messed with the SDK once, but I've since removed it since I don't have an Android phone. I have an iPhone and have downloaded SDK from Apple, but I have so far refused to pay $99 for a license...makes me wish I'd waited for Android to mature just a little more.

I'm using NetBeans at the moment.
 
I have a g1 right now and not a mac, plus Java is my strongest language, so that's why I'm over in droid land. Before android and iphone were around, I did a bit of development for the Sidekick, which is also java. Anyways, I could use some help with this droid app - especially on the calculations and such, as I have been pretty Beersmith dependent and am a relative newbie with brewing. Anyway, a bit off topic for this thread! ;)

I'll PM you my email address.
 
It's not really off topic at all...it's a fine direction for the thread to go. My code was really ugly, and I decided to go with a more structured approach. Here's an example of the direction I've taken (clearly more work to be done...and I should probably read up on naming conventions):

Code:
public class Wort {
    private double sg;      // specific gravity
    private double volume;  // gallons
    private double ibus;    // IBUs

    //constructor
    public Wort (double sg, double volume) {
        this.sg = sg;
        this.volume = volume;
    }

    public double getSG() {
        return sg;
    }
    
    public double getVolume() {
        return volume;
    }
    
    public double getIBUs() {
        return ibus;
    }
    
    public void boil(long seconds, double boil_off_rate) {
        // reduce volume, increase sg, calculate ibus
    }
}

Whaddaya think? Good methodology there, or are you thinking something else?

EDIT: Need to include some more placeholders for hops/grain additions...but you hopefully get the idea.
 
It's not very robust, but this code is working:

Code:
/**
 *
 * @author yuri
 * @version 0.1 11 Sep 09
 *
 * Represents actively boiling (and fermenting?) wort
 *
 * Negative return values indicate variables that have not been initialized
 * or are incalculable.
 */

import java.util.ArrayList;
import java.util.Iterator;

public class Wort {

    private double sg;             // current specific gravity
    private double og;             // original specific gravity
    private double volume;         // gallons
    private double volumePreBoil;  // gallons
    private double ibus;           // IBUs
    private long boilTimeElapsed;  // seconds (since the boil started)
    private ArrayList<HopAddition> hops;

    //constructor
    public Wort (double sg, double volume) {
        this.sg = sg;
        this.og = sg;
        this.volume = volume;
        this.volumePreBoil = volume;
        this.ibus = -1.0;
        this.boilTimeElapsed = 0;
        hops = new ArrayList<HopAddition>();
    }

    public double getSG() {
        return sg;
    }

    public double getOG() {
        return og;
    }

    public double getVolume() {
        return volume;
    }

    public double getVolumePreBoil() {
        return volumePreBoil;
    }

    public double getIBUs() {
        return ibus;
    }

    public long getBoilTimeElapsed() {
        return boilTimeElapsed;
    }

    public ArrayList<HopAddition> getHops() {
        return hops;
    }

    public void addHops(HopAddition h) {
        hops.add(h);
    }

    public void boil(long seconds,      // amount of time to simulate the boil
                     double boilOffRate // (% / 100) per hour
                     ) {
        boilTimeElapsed = boilTimeElapsed + seconds;

        // evaporation
        volume = volumePreBoil - volumePreBoil * (boilOffRate / 3600.0) * (double)boilTimeElapsed;

        // sg after evaporation formula
        // from http://brewingtechniques.com/library/backissues/issue1.3/manning.html
        sg = volumePreBoil / volume * (og - 1.0) + 1.0;

        ibus = 0.0; // always recalculated from start of boil
        for (Iterator it = hops.iterator(); it.hasNext(); ) {
            HopAddition h = (HopAddition)it.next();
            if (h.getTime() * 60.0 < (double)boilTimeElapsed) {
                ibus = ibus + calcIBUs(h.getAmount(),
                                       h.getAA(),
                                       ((double)boilTimeElapsed - h.getTime() * 60.0) / 60.0,
                                       sg,
                                       volume);
            }
        }
    }

    // method using Glenn Tinseth's IBU formula
    private double calcIBUs (double amount, // oz
                             double aa,     // %AA / 100
                             double time,   // minutes
                             double sg,     // specific gravity
                             double volume  // gallons
                             ) {

        /*
         * From http://www.grimmysbeer.com:
         *
         * IBUs = (Boil Time Fact * Bigness Factor) * (mg/l of added alpha acids)
         *
         * mg/l of added alpha acids = (decimal AA rating * oz's hops * 7490) / (volume of finished beer in gallons)
         * Boil Time factor = (1 - e^(-0.04 * time in mins)) / 4.15
         * Bigness factor = 1.65 * 0.000125^(wort gravity - 1)
         */

        final double e = 2.72;          // logarithmic base
        double bignessFactor;           // no unit
        double boilTimeFactor;          // no unit
        double addedAA;                 // mg/L

        addedAA = (aa * amount * 7490) / volume;

        boilTimeFactor = (1 - Math.pow(e, (-0.04 * time))) / 4.15;

        bignessFactor = 1.65 * Math.pow(0.000125, (sg - 1.0));

        return boilTimeFactor * bignessFactor * addedAA;
    }

}

Code:
/**
 *
 * @author yuri
 * @version 0.1 11 Sep 09
 *
 * Represents a single hops addition for homebrewing
 */

public class HopAddition {

    private String name;    // name of hop
    private double amount;  // oz
    private double aa;      // alpha acid (in percent / 100)
    private long time;      // minutes (when to add hops)
                            //   i.e., 0  = start of boil,
                            //         30 = 30 mins into boil
                            //         60 = 60 mins into boil (flameout for a 60 min boil)

    //constructor
    public HopAddition(String name, double amount, double aa, long time) {
        this.name = name;
        this.amount = amount;
        this.aa = aa;
        this.time = time;
    }

    public String getName() {
        return name;
    }

    public double getAmount() {
        return amount;
    }

    public double getAA() {
        return aa;
    }

    public long getTime() {
        return time;
    }
}
 
Example:

Code:
// new wort object: 5.5 gallons at an SG of 1.055
Wort wort = new Wort(1.055, 5.5);

// add 1 oz of 5.5% AA Cascade at the start of the boil
wort.addHops(new HopAddition("Cascade", 1.0, 0.055, 0));

// boil the wort for an hour with a 9% / hour boil off rate
wort.boil(3600, 0.09);

// output the wort's IBU approximation
System.out.println(Double.toString(wort.getIBUs));
 
Looks good yuri. You might want to make the IBU formula pluggable. I am working on getting data for malts/hops. Do you know of any open source data here? I asked Bradley from Beersmith if I can use his data, but haven't heard back yet.
 
I would love people to contribute this type of code to Brewtarget. Dunno if you want it, but here's some IBU code in Brewtarget.

Code:
/*
    Copyright Philip Greggory Lee ([email protected]), 2008-2009.
    
    hoputilization.cpp is part of Brewtarget.
    
    Brewtarget is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Brewtarget is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Brewtarget.  If not, see <http://www.gnu.org/licenses/>.
*/

// Uses a method outlined at http://www.realbeer.com/hops/research.html

#include <cmath>

double BoilTimeFactor( double minutes )
{
   return (1.0 - exp(-0.04 * minutes))/4.15;
}

double BignessFactor( double wort_grav )
{
   return 1.65 * pow(0.000125, (wort_grav - 1));
}

double AlphaAcidUtilization( double wort_grav, double minutes )
{
   return BoilTimeFactor(minutes) * BignessFactor(wort_grav);
}

double MaxAAConcentration_mgPerLiter(double AArating, double hops_grams, double finalVolume_liters)
{
   return (AArating * hops_grams * 1000) / finalVolume_liters;
}

double IBU( double AArating, double hops_grams, double finalVolume_liters, double wort_grav, double minutes)
{
   return MaxAAConcentration_mgPerLiter(AArating, hops_grams, finalVolume_liters) * AlphaAcidUtilization(wort_grav, minutes);
}
 
Rocketman - looks like your implementation of Tinseth's formula is almost identical. However, your methodology is very "traditional" in logic and flow. Do you not use object oriented methodology when using C++?

Pico - while on one hand, I'm suggesting more advanced techniques to Rocketman, I'm going to have to plead ignorance to the term, "pluggable." What do you mean?
 
Rocketman - looks like your implementation of Tinseth's formula is almost identical. However, your methodology is very "traditional" in logic and flow. Do you not use object oriented methodology when using C++?

Pico - while on one hand, I'm suggesting more advanced techniques to Rocketman, I'm going to have to plead ignorance to the term, "pluggable." What do you mean?

I mean you could have a BitternessFormula Interface (a java interface) and then have a few classes (TinsethFormula,RagerFormula,etc) that implement the interface. Then your Wort object would have a BitternessFormula field that could be changed by the user interface. Your interface would just have the calcIbus method on it.
 
Rocketman - looks like your implementation of Tinseth's formula is almost identical. However, your methodology is very "traditional" in logic and flow. Do you not use object oriented methodology when using C++?

Yes, I do. However, I suppose it's just my style that I tend to leave generic functions like math stuff outside of classes.
 
Rocketman - fair enough. Hope you didn't think I was being too critical...it's tough to get a feel for your style with one small code snippet.

Pico - something like this?

Code:
/**
 *
 * @author yuri
 * @version 0.1 11 Sep 09
 *
 * Represents actively boiling (and fermenting?) wort
 *
 * Negative return values indicate variables that have not been initialized
 * or are incalculable.
 */

import java.util.ArrayList;
import java.util.Iterator;

public class Wort {

    private double sg;             // current specific gravity
    private double og;             // original specific gravity
    private double volume;         // gallons
    private double volumePreBoil;  // gallons
    private double ibus;           // IBUs
    private long boilTimeElapsed;  // seconds (since the boil started)
    private ArrayList<HopAddition> hops;
    private IBUFormula ibuFormula; // user selectable algorithm
    
    // constants
    public enum IBUAlgorithm {TINSETH, RAGER};

    //constructor
    public Wort (double sg, double volume) {
        this.sg = sg;
        this.og = sg;
        this.volume = volume;
        this.volumePreBoil = volume;
        this.ibus = -1.0;
        this.boilTimeElapsed = 0;
        hops = new ArrayList<HopAddition>();
        ibuFormula = new TinsethFormula(); // Tinseth is the default
    }

    public double getSG() {
        return sg;
    }

    public double getOG() {
        return og;
    }

    public double getVolume() {
        return volume;
    }

    public double getVolumePreBoil() {
        return volumePreBoil;
    }

    public double getIBUs() {
        return ibus;
    }

    public long getBoilTimeElapsed() {
        return boilTimeElapsed;
    }

    public ArrayList<HopAddition> getHops() {
        return hops;
    }

    public void setIBUAlgorithm(IBUAlgorithm a) {
        switch (a) {
            case RAGER:   ibuFormula = new RagerFormula();
                          break;
            case TINSETH: ibuFormula = new TinsethFormula();
                          break;
        }
    }

    public void addHops(HopAddition h) {
        hops.add(h);
    }

    public void boil(long seconds,      // amount of time to simulate the boil
                     double boilOffRate // (% / 100) per hour
                     ) {
        boilTimeElapsed = boilTimeElapsed + seconds;
        // evaporation
        volume = volumePreBoil - volumePreBoil * (boilOffRate / 3600.0) * (double)boilTimeElapsed;

        // sg after evaporation formula
        // from http://brewingtechniques.com/library/backissues/issue1.3/manning.html
        sg = volumePreBoil / volume * (og - 1.0) + 1.0;

        ibus = 0.0; // always recalculated from start of boil
        for (Iterator it = hops.iterator(); it.hasNext(); ) {
            HopAddition h = (HopAddition)it.next();
            if (h.getTime() * 60.0 < (double)boilTimeElapsed) {
                ibus = ibus + ibuFormula.calcIBUs(h.getAmount(),
                                                  h.getAA(),
                                                  ((double)boilTimeElapsed - h.getTime() * 60.0) / 60.0,
                                                  sg,
                                                  volume);
            }
        }
    }
}

Code:
/**
 *
 * @author yuri
 * @version 0.1 11 Sep 09
 *
 * Uses Glen Tinseth's formula for calculating IBUs in homebrewed beer
 *
 * From http://www.grimmysbeer.com and http://www.realbeer.com/hops/research.html
 *
 * IBUs = (Boil Time Fact * Bigness Factor) * (mg/l of added alpha acids)
 *
 * mg/l of added alpha acids = (decimal AA rating * oz's hops * 7490) / (volume of finished beer in gallons)
 * Boil Time factor = (1 - e^(-0.04 * time in mins)) / 4.15
 * Bigness factor = 1.65 * 0.000125^(wort gravity - 1)
 */
public class TinsethFormula implements IBUFormula {

    public double calcIBUs (double amount, // oz
                            double aa,     // %AA / 100
                            double time,   // minutes
                            double sg,     // specific gravity
                            double volume  // gallons
                            ) {

        final double e = 2.72;          // logarithmic base
        double bignessFactor;           // no unit
        double boilTimeFactor;          // no unit
        double addedAA;                 // mg/L

        addedAA = (aa * amount * 7490) / volume;

        boilTimeFactor = (1 - Math.pow(e, (-0.04 * time))) / 4.15;

        bignessFactor = 1.65 * Math.pow(0.000125, (sg - 1.0));

        return boilTimeFactor * bignessFactor * addedAA;
    }

}
 
Back
Top