Missing post boil gravity every time

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.

brewmeister13

Well-Known Member
Joined
Feb 11, 2013
Messages
568
Reaction score
59
Location
Tucson
As the title suggests, my post boil gravity is always 2-5 points off, depending on the grain bill, from my BeerSmith calculation. I've just learned to except it and adjust my recipes accordingly. However, today I was doing some digging and it seems to me that BeerSmith doesn't follow the most basic rule of brewing science, sugars in the wort stay in the wort and no sugars miraculously find their way in. Let me run through the numbers and see if I'm missing something from my most recent batch.

Preboil SG: 1.044
Preboil Vol: 8.46 Gal

Post Boil SG: 1.054
Post Boil Vol: 7.21 Gal (note, this is in the kettle, not batch size)

8.46X44= 372.24
7.21X54= 389.34

All numbers are taken strait from BeerSmith and apparently BeerSmith thinks 17.1 Gravity points worth of sugar are going to appear in my wort after boiling. Am I looking at this wrong?
 
You have a boil off of 1.25 gallons or so?

If the 7.21 gallons of finished wort after the boil isn't your batch size, what is? Where does the rest of the wort go?
 
You have a boil off of 1.25 gallons or so?

If the 7.21 gallons of finished wort after the boil isn't your batch size, what is? Where does the rest of the wort go?

Yup, boil off is about 1.25 gallons an hour. My batch size is 6 gallons. the rest of the wort is losses to the hosing and trub and SHRINKAGE.

Yooper, you're amazing! Just figured it out as I was looking at my specs. Once you take the 4% shrinkage from the 7.21 gallons you get 6.92 gallons. 6.92X54=373.68, which is close enough to 372.24 for me. I've been measuring preboil at a higher temp, I usually turn the burner on under my boil kettle during lautering and am close to a boil by the time I'm at the end. Thus, when I think I have 8.5 gallons of 1.044 wort, I really would only have 8.16 gallons (if cooled like beersmith uses) of wort at 1.044.
 
Preboil SG: 1.044
Preboil Volume: 8.46 * 3.785 = 32.0211 L
Preboil Mass: 8.46 * 3.785 * 1.044 * 0.998203 = 33.37 kg
Preboil °P: 10.9561
Preboil Extract: = .109561 * 8.46 * 3.785 * 1.044 * 0.998203 = 3.65605 kg
Preboil Water: = ( 1 - .109561) * 8.46 * 3.785 * 1.044 * 0.998203 = 29.7139 kg

Postboil SG: 1.054
Postboil Volume: 7.21 gal
Postboil Mass: 1.054 * .998203 * 3.785 * 7.21 = 28.7118 kg
Postboil °P : 13.334
Postboil Extract: 0.13334 * 1.054 * .998203 * 3.785 * 7.21 = 3.82843 kg !!!

This says that in your boil you created 172 grams of extract. As that can't happen clearly either your volume or specific gravity measurements are in error. This is the probable cause of your inconsistency.
 
Preboil SG: 1.044
Preboil Volume: 8.46 * 3.785 = 32.0211 L
Preboil Mass: 8.46 * 3.785 * 1.044 * 0.998203 = 33.37 kg
Preboil °P: 10.9561
Preboil Extract: = .109561 * 8.46 * 3.785 * 1.044 * 0.998203 = 3.65605 kg
Preboil Water: = ( 1 - .109561) * 8.46 * 3.785 * 1.044 * 0.998203 = 29.7139 kg

Postboil SG: 1.054
Postboil Volume: 7.21 gal
Postboil Mass: 1.054 * .998203 * 3.785 * 7.21 = 28.7118 kg
Postboil °P : 13.334
Postboil Extract: 0.13334 * 1.054 * .998203 * 3.785 * 7.21 = 3.82843 kg !!!

This says that in your boil you created 172 grams of extract. As that can't happen clearly either your volume or specific gravity measurements are in error. This is the probable cause of your inconsistency.

Those actually aren't my numbers, those are the numbers that BeerSmith gives. Which is what my problem has been, but I did figure out why. The numbers are calculated at two different temperatures. Once expansion is taken into consideration the Postboil volume would effectively be 6.92 gal. That will make the numbers match up much better.
 
I do have a question now though. If I take a gallon of water at 70F and heat it up to 150F what would its volume be?

I'm assuming the thermal expansion of 4% is from room temperature to boiling, right?
 
I do have a question now though. If I take a gallon of water at 70F and heat it up to 150F what would its volume be?
The density of water at 70 °F is 997.967 g/L and at 150 °F it is 980.243 i.e. it is 997.967/980.243 = 1.01808 9 (1.8 %) denser. One gallon at 70 would expand to 1.01808 gal at 150 °F.

I'm assuming the thermal expansion of 4% is from room temperature to boiling, right?
Yes.
 
Thanks AJ. This helps a bunch to get more accurate results. Now I just need to start doing the calculations myself and I'd be golden. Too bad I don't have the resolve.
 
i think the batch size on beersmith takes into account all loss through the entire process. i think. maybe? :) i have never really understood that. and as a homebrewer, i don't care if i leave 1 or 3 gallons in my boil kettle. i care that i get the X amount of wort into my fermenter with the correct gravity, ibus and color and none of those are effected by loss due to trub, deadspace or just making more wort than you need. or am i missing something? i got so tired of overcomplicated and seemingly inaccurate tools that i built my own. you are welcome to try them out and i'm happy to share my code if your into that sort of thing. its all html + bootstrap, php and a little javascript. it's up to almost 30K lines of code so starting from scratch would likely take a while :)

http://www.hommelhomebrew.com/brewdesign/

that said, i do like the way beertools pro accounts for temp. with each affected field you pick at want temp you are taking the measurement. i haven't gotten that in my tool yet but i plan to do something similar.

i did notice that your numbers seemed to be off by more than 4%. in your gravity range that should just be a couple of points or half a degree plato. or am i reading that wrong?

also, i built a tool so that if you over or under shoot your gravity it will tell you how much fermentables of your choice or water to add to fix it. i mostly do new recipes with new grains so i almost never nail my gravity so i use this tool a lot :)

http://www.hommelhomebrew.com/brewdesign/gravity.php
 
i think the batch size on beersmith takes into account all loss through the entire process. i think. maybe? :) i have never really understood that. and as a homebrewer, i don't care if i leave 1 or 3 gallons in my boil kettle. i care that i get the X amount of wort into my fermenter with the correct gravity, ibus and color and none of those are effected by loss due to trub, deadspace or just making more wort than you need. or am i missing something? i got so tired of overcomplicated and seemingly inaccurate tools that i built my own. you are welcome to try them out and i'm happy to share my code if your into that sort of thing. its all html + bootstrap, php and a little javascript. it's up to almost 30K lines of code so starting from scratch would likely take a while :)

http://www.hommelhomebrew.com/brewdesign/

that said, i do like the way beertools pro accounts for temp. with each affected field you pick at want temp you are taking the measurement. i haven't gotten that in my tool yet but i plan to do something similar.

i did notice that your numbers seemed to be off by more than 4%. in your gravity range that should just be a couple of points or half a degree plato. or am i reading that wrong?

also, i built a tool so that if you over or under shoot your gravity it will tell you how much fermentables of your choice or water to add to fix it. i mostly do new recipes with new grains so i almost never nail my gravity so i use this tool a lot :)

http://www.hommelhomebrew.com/brewdesign/gravity.php

Thanks for the links.
 
Here is something I came up with several months back and stuck in spreadsheet, might help you

Water Temp to Volume Equation:
Vf = Vi * (a*Ti^2 + b*Ti + c) / (a*Tf^2 + b*Tf + c)
Vf = Final volume after temperature change
Vi = Initial volume before temperature change
Ti = Inital temperature
Tf = Final temperature
a, b, c = Quadratic equation factors, from 2nd order polynomial regression analysis of water temperature to density (http://www.ncsu.edu/chemistry/resource/H2Odensity_vp.html)
a= -0.0000129267346018307
b= 0.000804432262364695
c= 8.33549164715676

Units are in fahrenheit and gallons. *This is just for water, I haven't crunched the numbers to see how much sugars and altitude would effect this. It ought to get you close.
 
I used the ICUMSA polynomial reataining only terms that do not depend on sugar content (the result being the behaviour of water alone). This gives, for density

(((((-281.03006e-12*t +105.84601e-9)*t-46.241757e-6)*t-7.9905127e-3)*t+16.952577)*t +999.83952)/(1+16.887236e-3*t)

where t is the temperature in °C. Note that this give 0.998203 for the density at 20 °C on the ITS 90 temperature scale (the current one) which is the same number as in the pure water table that Anton Paar puts in the back of its densitometer manual.

The density change with temperature for weak solutions differs only slightly from that for pure water. For example, where DensA(°P,°C,0~calculate density,0~sucrose) returns the density:

DensA(0,(70 - 32)/1.8,0,0)/DensA(0,(150 - 32)/1.8,0,0) = 1.01808; 1.808% change

DensA(10,(70 - 32)/1.8,0,0)/DensA(10,(150 - 32)/1.8,0,0) = 1.01858; 1.858% change

DensA(20,(70 - 32)/1.8,0,0)/DensA(20,(150 - 32)/1.8,0,0) = 1.01903; 1.903% change

Clearly you can use ratios returned by this polynomial to correct hydrometer readings or you can use the ASBC 'official' corrections from

function ASBCcor(P,T)
variable P,T
//Curve fit to MOA Beer-3. Correction to be added to hydrometer reading.
//T = temperature in °C. P = hydrometer reading in °P.
T -=20
return P + 0.000383644+0.0496253*T-6.35525e-05*P+0.00110774*T*T+0.00091996*T*P+1.36646e-05*P*P
end
 
Looks like my formula was pretty darn close. Adjusting mine to use the ICUMSA equation.

Thanks aj.

[Edit]
Hey, do you have a source for that equation? I'd like to see the formula with the sugar dependent variables.
 
The source is ICUMSA's Sugar Analysis Handbook (or something close to that). I don't have it in Canada but can, I think, give you the data you want based on code I do have here. The density at T (in °C) depends on mixed powers of (T-20) up to the fifth and of c, the concentration in grams of sugar per cc with a different set of 20 coefficients for each of sucrose, glucose, fructose and invert sugar. I arrange the 20 coefficients in a 5 x 4 matrix then post multiply by the powers of (T-20) and then dot that with the powers of c. Given that you should, if you do stuff like this, be able to get the details from the following code fragment even though you may never have heard of IGOR (the programming language being used):

function suc_c(c,t,m,sugar)
variable c,t,m,sugar

//density of sucrose solutions by mass concentration (grams/cc) in % at temperature t in ¡C. air is density of air in kg/m3 ~ 1.2
//It is very important to note that c is at 20 °C. IOW, the calculated density at temperature T is for a solution whose strength is
//c gramss per cc at 20 °C.
//Use densA for solutions whose strength is specified in grams sugar per gram of solution
// m = mode
// 0 density
// 1 apparent SG t/t
// 2 true SG t/t
// 3 wt 1 cc in air
//
// sugar = the type of sugar
// 0 sucrose
// 1 glucose
// 2 fructose
// 3 invert sugar


variable wts = 8000, air = 1.201

switch (sugar)
case 0:
make/o/d/n=20 c_suc = {385.85074,-45.9244,60.198,-51.1,19.86,-13.03435,7.5699,-13.008,15.8,0,-3.6663,6.2667,-4.907,0.0,0,0,0,0}
break
case 1:
make/o/d/n=20 c_suc = {382.9716,-55.1502,75.5366,-44.059,0,-23.0838,19.0140,-30.9314,0,0,0.1218,4.2741,0,0,0,-0.7704,0,0,0,4}
break
case 2:
make/o/d/n=20 c_suc = {390.5399,-74.4141,81.7245,-77.138,32.396,-22.1864,15.5436,-44.6412,42.824,0,-0.0829,5.3744,4.9248,0,0,-1.7937,0,0,0,0}
break
case 3:
make/o/d/n=20 c_suc = {386.7294,-64.7839,78.6570,-60.676,16.241,-22.9485,17.6392,-37.8841,21.588,0,1.7684,4.2073,2.5197,0,0,-2.3475,0,0,0,0}
endswitch
redimension/n=(5,4) c_suc
wave M_product
variable acc = water(t)
variable tau = (t-20)/100
variable i, j
make/d/o/n=4 mf
mf[0] = c
for( j = 1;j<4;j+=1)
mf[j] = mf[j-1]*c
endfor
make/o/d/n=5 ta
ta[0] = 1
for( j=1;j<5;j+=1)
ta[j] = ta[j-1]*tau
endfor
MatrixMultiply ta/T,c_suc,mf
acc += M_product
switch (m)
case 0:
return acc/1000
break
case 1:
return (acc -air)/(water(t) -air)
break
case 2:
return acc/water(t)
break
case 3:
return acc/((1 + (air/wts)*(wts -acc)/(acc-air)))/1000
endswitch

end


The coefficients go into the matrix in columns so that, e.g. for sucrose, c_suc(0,0) = 385.851, c_suc(1,0) = -45.9244, c_suc(0,1) = -13.0343 etc.

The most annoying part is that you have to calculate grams/cc from grams per 100 grams (°P). To do this note that since w (grams_sugar/gram_solution) is invariant with temperature and because grams_sugar/cc_solution = (grams_solution/cc_solution)*(grams_sugar/grams_solution) we have c = suc_c(c,20,0,sugar)*w and so solve suc_c(c,20,0,sugar)*(°P/100) - c = 0 for c by root bisection and
//then insert c into suc_c(c,T,0,sugar) to calculate density (3rd parameter 0, or from it, specific gravity, true or apparent or weight per cc in air).
This is the method used to calculate ICUMSA Table A.

I'm sure all this is going to be a bit confusing at first.
 
I didn't think I had the Bettin & Spieweck data (what Anton Paar uses) here but I found it. The polynomial below returns the density of water as a function of temperature (ITS 1990) in °C. Based on a fit to data from Bettin, H.; Spieweck,F.: "Die Dichte des Wassers als Funktion der Temperatur nach Einführung der Internationalen Temperaturskala von 1990. PTB-Mitt. 100 (1990) pg 195-196 The referenced data set lists densities for each 0.1°C over the range (0,100). The data in determining this polynomial are the subset on integer degree values. Sporadic checking shows that the fit appears to be accurate to 1 count in the 6th decimal place. The rms residual wrt the fit points is 3.1E-7 and the peak residual 5.5E-7. The residuals appear noiselike.

0.99984+T*(6.7715e-05-T*(+9.0735e-06-T*(1.015e-07-T*(+1.3356e-09-T*(1.4421e-11-T*(+1.0896e-13-T*(4.9038e-16-9.7531e-19*T)))))))

This is what I'd suggest using if you want to be really, really accurate (5+ decimal places).
 
I do have a question now though. If I take a gallon of water at 70F and heat it up to 150F what would its volume be?

I'm assuming the thermal expansion of 4% is from room temperature to boiling, right?
According to a interpolation of data I found in the engineers toolbox, 1 gallon at 70F becomes 1.0187 gallons at 150F, assuming wort follows the same thermal expansion coefficients as water.

Threw in the coefficients and second power polynomial above, and got 1.01998.

Sorry didn't see AJ was participating. He wins, I'm out!

Using the formula from AJs post #16, setting T to 65.55555C (150F), I get a density of 0.980244475, and a volume of 1.020154.
Setting T to 21.1111111 I get a density of 0.997967076 and a volume of 1.002037.
So 1 gallon at 70F becomes 1.01808 gallons at 150F?
 
Yep, at least according to ICUMSA (#13) or Bettin and Spieweck (#16). As I mentioned somewhere here Anton Paar uses the Bettin and Spieweck data to calibrate their instruments. That's a good enough endorsement for me!
 
Yep, at least according to ICUMSA (#13) or Bettin and Spieweck (#16). As I mentioned somewhere here Anton Paar uses the Bettin and Spieweck data to calibrate their instruments. That's a good enough endorsement for me!

Good enough for me to have to update my calculator again :ban:.

Now the sugar content stuff is too much for me to look at now. How much do the different sugars affect the outcome? 1%, 5%?

Probably just wishful thinking...

Example: Could you do a calculation at 1.060 comprised of all invert, and compare it to 100% glucose vs typical homebrew composition. What would the absolute value of the difference of the two densities at 150F?

Is it marginal enough to simplify to a single equation and not a dot product of 20 variables and not lose too much accuracy?
 
The differences between sugars are very small. Here are densities for 10 °P at 170 °F for all four sugars ICUMSA covers (in order sucrose, glucose, fructose and invert):
&#8226;print DensA(10,(150 - 32)/1.8,0,0)
1.01889
&#8226;print DensA(10,(150 - 32)/1.8,0,1)
1.01835
&#8226;print DensA(10,(150 - 32)/1.8,0,2)
1.01821
&#8226;print DensA(10,(150 - 32)/1.8,0,3)
1.01828

Experiments with maltose (difficult because it is very hygroscopic) and even soluble starch show very similar results.
 
Code:
//density of sucrose solutions by mass concentration (grams/cc) in % at temperature t in ¡C. air is density of air in kg/m3 ~ 1.2
//It is very important to note that c is at 20 °C. IOW, the calculated density at temperature T is for a solution whose strength is
//c gramss per cc at 20 °C.
//Use densA for solutions whose strength is specified in grams sugar per gram of solution
// m = mode
//   0	    density
//   1        apparent SG t/t
//   2        true SG t/t
//   3        wt 1 cc in air
//
// sugar = the type of sugar
//   0        sucrose
//   1        glucose
//   2        fructose
//   3        invert sugar

Aj, any chance I can see the function for densA?
 
No problem - it's short:

function DensA(w,t,m,sugar)
variable w,t,m,sugar
//finds density of sugar solutions from ICUMSA mass concentration polynomial, p(c,t) i.e. function suc_c(c,t,mode,sugar)
//as a function of w, the grams of sugar per gram of solution. suc_c requires strength expressed as grams sugar per
//cc of solution at 20 °C so first step is to find the value of c corresponding to the desired w. Since w is invariant with
//temperature and because grams_sugar/cc_solution = (grams_solution/cc_solution)*(grams_sugar/grams_solution) we have
// c = p(c,20)*w we solve p(c,20)*w - c = 0 for c by root bisection and
//then insert c into p(c,t) to calculate density (or specific gravity).
//This is method used to calculate ICUMSA Table A. ICUMSA
// mode - m
// 0 density
// 1 apparent SG t/t
// 2 true SG t/t
// 3 wt 1 cc in air

// sugar = the type of sugar
// 0 sucrose
// 1 glucose
// 2 fructose
// 3 invert sugar


variable c1 = 0
variable c2 = 1.44009
variable cb,acc
variable f1,f2,fb
variable j = 0
variable dc
variable tolerance = 1.e-9
w += 1E-15

//Check that there is a solution between sg1 and sg2
f1 = suc_c(c1,20,0,sugar)*w/100 -c1
f2 = suc_c(c2,20,0,sugar)*w/100 -c2
if (f1*f2 >0 ) // There is no valid solution. Return signal for exception handler
return -1
else
dc = c2 - c1
do
cb = c1 +dc/2
fb = suc_c(cb,20,0,sugar)*w/100 -cb
if(f1*fb < 0) //root is between sg1 and sgb
c2 = cb
else //root is betweeb sgb abd sg2
c1 = cb
endif
dc = c2 - c1
j += 1

while ( (abs(dc) > tolerance)*(j < 35) )
// print cb

acc = suc_c(cb,t,m,sugar)
return acc
endif
end function
 
Thank you, sir. The IGOR language is a new one on me but I think I can get through this bit of code.
 
Code:
function suc_c(c,t,m,sugar)
  ...
  wave M_product

  variable acc = water(t)
  variable tau = (t-20)/100 
  variable i, j

  make/d/o/n=4 mf
  mf[0] = c
  for( j = 1;j<4;j+=1)
      mf[j] = mf[j-1]*c
  endfor 
  
  make/o/d/n=5 ta
  ta[0] = 1
  for( j=1;j<5;j+=1)
    ta[j] = ta[j-1]*tau
  endfor
  
  MatrixMultiply ta/T,c_suc,mf
  acc += M_product
  ...

I'm trying to port this to my system, I pretty much have a handle on it but I have a couple questions:
  1. At line variable acc = water(t), is water(t) a function that returns water density (at temp t celsius)? If not what is this call?
  2. At the second to last line "MatrixMultiply ta/T" what is capital 'T'? I don't see its definition elsewhere in the function. Also I assume this is dot product?
  3. Finally, what is m_product? The summation of the previous call to MatrixMultiply?
 
I'm trying to port this to my system, I pretty much have a handle on it but I have a couple questions:

IGOR started as a visualization program many years ago and has been adding capability (like the ability to write compilable functions) ever since. The guys that did the compiler obviously borrowed heavily from C but there are many IGOR unique things.

At line variable acc = water(t), is water(t) a function that returns water density (at temp t celsius)?
Yes, that is exactly what it is.

At the second to last line "MatrixMultiply ta/T" what is capital 'T'? I don't see its definition elsewhere in the function. Also I assume this is dot product?
/T means transpose. You are forming the quadratic ta^T*suc_c*mf in which ta is a column vector of 5 powers of (T - 20°C), suc_c is a 5 x 4 matrix of polynomial coefficients and mf is a column vector of 4 powers of the concentration. The result of the calculation is thus a scalar (1 x 5) * (5 x 4) * (4 x 1) --> (1 x 1)
Finally, what is m_product? The summation of the previous call to MatrixMultiply?
Yes. Stands for matrix product.
 
Okay, I think I have it ported to my language of choice. I think we just have rounding/floating point discrepancies. *But* can you confirm that your DensA function returns g/ml/1000? for example from your post #20 you have
Code:
print DensA(10,(150 - 32)/1.8,0,0)
1.01889

where as mine comes out to
Code:
puts DensA(10,(150 - 32)/1.8)
0.0010190180214628489

puts (DensA(10,(150 - 32)/1.8)*1000).round(5)
1.01902

Comparing to your post #13 my *ratios* are in agreement with yours:
DensA(0,(70 - 32)/1.8,0,0)/DensA(0,(150 - 32)/1.8,0,0) = 1.01808; 1.808% change

DensA(10,(70 - 32)/1.8,0,0)/DensA(10,(150 - 32)/1.8,0,0) = 1.01858; 1.858% change

DensA(20,(70 - 32)/1.8,0,0)/DensA(20,(150 - 32)/1.8,0,0) = 1.01903; 1.903% change
Code:
puts (DensA(0,(70-32)/1.8)/DensA(0,(150-32)/1.8)).round(5)
puts (DensA(10,(70-32)/1.8)/DensA(10,(150-32)/1.8)).round(5)
puts (DensA(20,(70-32)/1.8)/DensA(20,(150-32)/1.8)).round(5)
1.01808
1.0186
1.01913


(My functions assume mode=0 and sugar=sucrose at the moment)

[EDIT]
Nevermind, I was using the Bettin & Spieweck polynomial for water density that you posted in #16 rather than the ICUMSA polynomial that you posted in #13 because you mentioned it was a little more accurate. All is good. Swapping in the ICUMSA formula gets me the exact same results as all of the examples you have posted.

Thank you for providing us this Aj.
 
Okay, I think I have it ported to my language of choice. I think we just have rounding/floating point discrepancies. *But* can you confirm that your DensA function returns g/ml/1000? for example from your post #20 you have
Code:
print DensA(10,(150 - 32)/1.8,0,0)
1.01889
No, DensA returns g/cc.
print/d densa(10,(150-32)/1.8,0,0)
1.01889250295621

where as mine comes out to
Code:
puts DensA(10,(150 - 32)/1.8)
0.0010190180214628489[/quote]

It looks as if you are off by a factor of 1000 plus a bit. In looking over the code I gave you I can't find any obvious factor that might explain that but I'd go after that first. A value if 0.001 doesn't make sense in terms of practical unit which are grams/cc or grams/L or kg/L.

•print/d densa(10,(150-32)/1.8,0,0)
  0.103811426676367
  1.01889250295621

In this block I have printed both the value returned from the root bisector and the result which is returned by suc_c when then value is passed to it. If suc_c doesn't return 1.01889250295621 when passed  0.103811426676367 at (150-32)/1.8 °C then there is a problem with suc_c

•print/d suc_c(0.103811426676367,(150-32)/1.8,0,0)
  1.01889250295621

It seems to me there are ample opportunities to screw up suc_c as you have to be sure to put the coefficients in the right places in the matrix, make sure you are consistent in dimensioning (IGOR indexes the first element in a vector 0), be sure your eyes don't cross when you are transcribing the coefficient vectors and, finally, don't mishandle the factor of 1000 in the density calculation (the basic ICUMSA formula returns grams/L).


A good starting test is 0 °P and 20 °C at which the density of water is 0.998203
•print/d densA(0,20,0,0)
  6.70594163239002e-10
  0.998203122996104


[quote="thekraken, post: 6958252"]Comparing to your post #13 my *ratios* are in agreement with yours:[/quote]
That's encouraging (I guess).

Happy debugging!
 
and, finally, don't mishandle the factor of 1000 in the density calculation (the basic ICUMSA formula returns grams/L).
...
Happy debugging!

Got 'em ironed out! I edited my last post but I guess it was a little too late, sorry. My bug was in my water_density function. I got it working now in total agreement with all the sample's you have posted:

Code:
def water_density(t)
  #Bettin & Spieweck equation (*1000):
  return (0.99984+t*(6.7715e-05-t*(+9.0735e-06-t*(1.015e-07-t*(+1.3356e-09-t*(1.4421e-11-t*(+1.0896e-13-t*(4.9038e-16-9.7531e-19*t))))))))*1000.0

  #ICUMSA equation:
  #return (((((-281.03006e-12*t +105.84601e-9)*t-46.241757e-6)*t-7.9905127e-3)*t+16.952577)*t +999.83952)/(1+16.887236e-3*t)
end
 
I'll stop hijacking this thread now. I apologize to op, I got excited...
Here is the ported code if anyone wants this in ruby:
Code:
require 'matrix'

#Functions to calculate water/sugar solution density.
#Code ported from IGOR to Ruby
#Original code provided by ajdelange: https://www.homebrewtalk.com/showthread.php?t=531216&page=2

def water(t)
  #Bettin & Spieweck equation (*1000):
  return (0.99984+t*(6.7715e-05-t*(+9.0735e-06-t*(1.015e-07-t*(+1.3356e-09-t*(1.4421e-11-t*(+1.0896e-13-t*(4.9038e-16-9.7531e-19*t))))))))*1000.0

  #ICUMSA equation:
  #return (((((-281.03006e-12*t +105.84601e-9)*t-46.241757e-6)*t-7.9905127e-3)*t+16.952577)*t +999.83952)/(1+16.887236e-3*t)
end

=begin
################################################################################
#          Generate the sugar matrices from ajdelange's code                   #
################################################################################
m = [0,0,0,0]
m[0] = [385.85074,-45.9244,60.198,-51.1,19.86,-13.03435,7.5699,-13.008,15.8,0,-3.6663,6.2667,-4.907,0.0,0,0,0,0,0,0]
m[1] = [382.9716,-55.1502,75.5366,-44.059,0,-23.0838,19.0140,-30.9314,0,0,0.1218,4.2741,0,0,0,-0.7704,0,0,0,4]
m[2] = [390.5399,-74.4141,81.7245,-77.138,32.396,-22.1864,15.5436,-44.6412,42.824,0,-0.0829,5.3744,4.9248,0,0,-1.7937,0,0,0,0]
m[3] = [386.7294,-64.7839,78.6570,-60.676,16.241,-22.9485,17.6392,-37.8841,21.588,0,1.7684,4.2073,2.5197,0,0,-2.3475,0,0,0,0]

for i in 0..3
  m[i] = [m[i][ 0..4 ],
          m[i][ 5..9 ],
          m[i][10..14],
          m[i][15..19]].transpose
  print "c_suc = "
  print m[i]
  puts
end

## RESULTS:

c_suc = [[385.85074, -13.03435, -3.6663, 0], [-45.9244, 7.5699, 6.2667, 0], [60.198, -13.008, -4.907, 0], [-51.1, 15.8, 0.0, 0], [19.86, 0, 0, 0]]
c_suc = [[382.9716, -23.0838, 0.1218, -0.7704], [-55.1502, 19.014, 4.2741, 0], [75.5366, -30.9314, 0, 0], [-44.059, 0, 0, 0], [0, 0, 0, 4]]
c_suc = [[390.5399, -22.1864, -0.0829, -1.7937], [-74.4141, 15.5436, 5.3744, 0], [81.7245, -44.6412, 4.9248, 0], [-77.138, 42.824, 0, 0], [32.396, 0, 0, 0]]
c_suc = [[386.7294, -22.9485, 1.7684, -2.3475], [-64.7839, 17.6392, 4.2073, 0], [78.657, -37.8841, 2.5197, 0], [-60.676, 21.588, 0, 0], [16.241, 0, 0, 0]]

################################################################################
=end

def suc_c(c, t, mode=0, sugar=0) #default mode is density and sugar is sucrose
  # m = mode
  # 0	density
  # 1 apparent SG t/t
  # 2 true SG t/t
  # 3 wt 1 cc in air
  #
  # sugar = the type of sugar
  # 0 sucrose
  # 1 glucose
  # 2 fructose
  # 3 invert sugar

  wts = 8000
  air = 1.201

  case sugar
  when 0
    c_suc = Matrix[[385.85074, -13.03435, -3.6663, 0],
                   [-45.9244, 7.5699, 6.2667, 0],
                   [60.198, -13.008, -4.907, 0],
                   [-51.1, 15.8, 0.0, 0],
                   [19.86, 0, 0, 0]]
  when 1
    c_suc = Matrix[[382.9716, -23.0838, 0.1218, -0.7704],
                   [-55.1502, 19.014, 4.2741, 0],
                   [75.5366, -30.9314, 0, 0],
                   [-44.059, 0, 0, 0],
                   [0, 0, 0, 4]]
  when 2
    c_suc = Matrix[[390.5399, -22.1864, -0.0829, -1.7937],
                   [-74.4141, 15.5436, 5.3744, 0],
                   [81.7245, -44.6412, 4.9248, 0],
                   [-77.138, 42.824, 0, 0],
                   [32.396, 0, 0, 0]]
  when 3
    c_suc = Matrix[[386.7294, -22.9485, 1.7684, -2.3475],
                   [-64.7839, 17.6392, 4.2073, 0],
                   [78.657, -37.8841, 2.5197, 0],
                   [-60.676, 21.588, 0, 0],
                   [16.241, 0, 0, 0]]
  end

  mf = [c.to_f, 0.0, 0.0, 0.0]
  for j in 1...4
    mf[j] = mf[j-1]*c.to_f
  end
  mf = Matrix[mf].t

  tau = (t.to_f-20.0)/100.0
  ta = [1.0, 0.0, 0.0, 0.0, 0.0]
  for j in 1...5
    ta[j] = ta[j-1]*tau
  end
  ta = Matrix[ta]

  m_product = (ta * c_suc * mf)[0,0]
  acc = water(t) + m_product

  case mode
  when 0
    return acc/1000.0
  when 1
    return (acc -air)/(water(t) -air)
  when 2
    return acc/water(t)
  when 3
    return acc/((1.0 + (air/wts)*(wts -acc)/(acc-air)))/1000.0
  end
end

#w = percent sugar
#t = temp celsius (F-32)/1.8
def DensA(w, t, mode=0, sugar=0) #default mode density and sugar is sucrose
  c1 = 0.0
  c2 = 1.44009
  cb = acc = 0.0
  f1 = f2 = fb = dc = 0.0
  tolerance = 1E-9
  w += 1E-15
  j = 0

  #check that there is a solution between sg1 and sg2
  f1 = suc_c(c1,20.0,0,sugar)*w/100.0-c1
  f2 = suc_c(c2,20.0,0,sugar)*w/100.0-c2

  if f1*f2 > 0
    return -1
  else
    dc = c2 - c1

    begin
      cb = c1 +dc/2.0
      fb = suc_c(cb,20.0,0,sugar)*w/100.0 -cb

      if (f1*fb < 0) #root is between sg1 and sgb
        c2 = cb
      else #root is betweeb sgb abd sg2
        c1 = cb
      end

      dc = c2 - c1
      j += 1
    end while ( (dc.abs > tolerance) or (j < 35) )

    acc = suc_c(cb,t,mode,sugar)
    return acc
  end
end


puts
puts

puts (DensA(0,(70-32)/1.8)/DensA(0,(150-32)/1.8)).round(5)
puts (DensA(10,(70-32)/1.8)/DensA(10,(150-32)/1.8)).round(5)
puts (DensA(20,(70-32)/1.8)/DensA(20,(150-32)/1.8)).round(5)

puts
puts

puts (DensA(10,(150-32.0)/1.8,0,0)).round(5)
puts (DensA(10,(150-32.0)/1.8,0,1)).round(5)
puts (DensA(10,(150-32.0)/1.8,0,2)).round(5)
puts (DensA(10,(150-32.0)/1.8,0,3)).round(5)

puts
puts
 
function ASBCcor(P,T)
variable P,T
//Curve fit to MOA Beer-3. Correction to be added to hydrometer reading.
//T = temperature in °C. P = hydrometer reading in °P.
T -=20
return P + 0.000383644+0.0496253*T-6.35525e-05*P+0.00110774*T*T+0.00091996*T*P+1.36646e-05*P*P
end

AJ, I assume this function is for calibration at 68F/20C. Do you have an *official* formula for hydrometers calibrated at different temps?
 
AJ, I assume this function is for calibration at 68F/20C.
I assume it is too as the ASBC tables derived from the Plato work are for 20/20.

Do you have an *official* formula for hydrometers calibrated at different temps?
I don't have an official formula for 20°! I took the table in the MOA and did a two dimensional fit to it to get the poiynomial. It should be a relatively simple matter to convert to other hydrometer calibrations using the ICUMSA density formula now that you have that at hand.
 
I assume it is too as the ASBC tables derived from the Plato work are for 20/20.

I don't have an official formula for 20°! I took the table in the MOA and did a two dimensional fit to it to get the polynomial. It should be a relatively simple matter to convert to other hydrometer calibrations using the ICUMSA density formula now that you have that at hand.

Ooh, so thats what you meant by "official".

Here is what I am thinking, please tell me if the logic is sound:

sg = &#961;_sample / &#961;_water
hydrometer reading = densa(plato, sample temperature, 0, 0) / densa(0, calibration temperature, 0, 0)

Simply program a loop to solve for plato? Is that all there is to it?

The little voice in the back of my head is telling me it's probably not that straight forward.
 
It shouldn't be that bad. It's a matter of figuring out what the relationship between wort density and the markings on the hydrometer are. For example a hydrometer calibrated in °P at 20 °C is going to read 10 when placed in wort of density 1.03998*.998203, 11 when placed in wort of density 1.04414*.998203 and so on at 20 °C. A hydrometer calibrated for 25 °C would read 10 when placed in wort of density 1.03981*0.997043 and 11 when placed in wort of density 1.04395*0.997043. From a set of similar data over the whole range of the hydrometer a curve fit can be constructed. For 25 °C the curve (independent variable the density)
K0 =-949.92
K1 =2292.7
K2 =-2191
K3 =1047.1
K4 =-198.04
with K0 being the constant, K1 the linear coefficient etc gives a noisy residual.

One can then pick another temperature, say 40 °C and calculate densities at those temperatures. For example if one had wort of 12 °P it would have density, at 40 °C, of 0.992212* 1.04773 = 1.03957. Inserting this into the polynomial we find that a hydrometer calibrated at 25 °C would read 10.6884 from which we conclude that the correction for a 25 °C hydrometer at 40 °C is +1.3116 °P. Building up a matrix of corrections as a function of strength and temperature allows us to do a two dimensional curve fit and get a polynomial similar to the one for 20 °C given a couple of posts back.
 
Last edited:
I'm trying to keep up, I promise! I really appreciate your patience and explanations.

I'm trying to back into your numbers and I've added some color coded comments to your post, green = I got it, orange = hmmm, and red = huh wha?!?:
It shouldn't be that bad. It's a matter of figuring out what the relationship between wort density and the markings on the hydrometer are. For example a hydrometer calibrated in °P at 20 °C is going to read
10 when placed in wort of density 1.03811*.998203 [=densa(10,20,0,0)*densa(0,20,0,0) but why are we multiplying densities?],
11 when placed in wort of density 1.04226*.998203 [=densa(11,20,0,0)*densa(0,20,0,0)]
and so on at 20 °C. A hydrometer calibrated for 25 °C would
read 10 when placed in wort of density 1.03675*0.998203 [=densa(10,25,0,0)*densa(0,20,0,0)]
and 11 when placed in wort of density 1.040862*0.998203 [=densa(11,25,0,0)*densa(0,20,0,0)] .
From a set of data over the whole range of the hydrometer a curve fit can be constructed. One can then pick another temperature, say 40 °C and calculate densities at those temperatures. For example (and this is contrived) if one had wort of 11.2946 °P it would have density 0.998203*1.033675 [did you mean 1.03675? Where did you get 11.2946 °P?] and thus read 10 on a hydrometer calibrated at 25 °C from which we conclude that the correction for a 25 °C hydrometer at 40 °C is +1.2946 °P. Building up a matrix of corrections as a function of strength and temperature allows us to do a two dimensional curve fit [okay, I can do curve fitting] and get a polynomial similar to the one for 20 °C given a couple of posts back.

I'm not following on how to calculate that 11.2946°P at 40°C = 10°P at 25°C.
I get densa(11.2946, 39.81 ...) OR densa(11.312, 40 ...) = 1.03675, do we just have rounding discrepancies? (that would be a pretty big discrepancy, I'd have to revisit my port of your code.. I ported it again to excel vba)

Also, when you say 'a hydrometer calibrated in °P at 20 °C is going to read 10 when placed in wort of density 1.03811*.998203' why are we multiplying two densities? Is multiplication what you meant to imply there? Then we'd have units (g/ml)^2.
 
I'm trying to keep up, I promise! I really appreciate your patience and explanations.

Well, I'm not helping you much with a post like last night's. We should not indeed be multiplying two densities but rather a specific gravity and a density. Thus for 25 °C we want

densa(12,25,0,0) = densa(12,25,2,0)*densa(0,25,0,0) + small error.

The reason for breaking things into two factors was to emphasize the role of specific gravity which, with a Plato hydrometer is not at issue but with a specific gravity hydrometer is. Note that the argument '2' indicates that we really want the true specific gravity here but for actual work with hydrometers apparent SG should be good enough.

I edited the post to fix the errors and also went ahead and calculated the Plato vs density curve for 25 °C and illustrated how to use that to calculate one correction for a 25 °C hydrometer in 40 ° wort. Hope it's clearer now!
 
Well, I'm not helping you much with a post like last night's. We should not indeed be multiplying two densities but rather a specific gravity and a density. Thus for 25 °C we want

densa(12,25,0,0) = densa(12,25,2,0)*densa(0,25,0,0) + small error.

The reason for breaking things into two factors was to emphasize the role of specific gravity which, with a Plato hydrometer is not at issue but with a specific gravity hydrometer is. Note that the argument '2' indicates that we really want the true specific gravity here but for actual work with hydrometers apparent SG should be good enough.

I edited the post to fix the errors and also went ahead and calculated the Plato vs density curve for 25 °C and illustrated how to use that to calculate one correction for a 25 °C hydrometer in 40 ° wort. Hope it's clearer now!

I have a feeling I'm over thinking this but now I gotta ask: Are the Plato and Specific gravity equations (source) dependent on hydrometer calibration temperature?
Code:
SG = 1+ (plato / (258.6 &#8211; ( (plato/258.2) *227.1) ) )
plato = (-1 * 616.868) + (1111.14 * sg) &#8211; (630.272 * sg^2) + (135.997 * sg^3)

IE does an SG reading calibrated at 60F, placed in 60F wort, have a different plato reading/conversion than a 68F calibrated hydrometer in 68F wort (same wort)?

I know plato is percent 'extract' by mass and as such is independent of temperature, but obviously SG isn't and that's what is giving me pause here.
 
Plato depends only on the grams of sugar per 100 grams of solution and is not temperature dependent. Density depends on the temperature of the solution and specific gravity depends on the temperature of the solution and on the temperature of the water whose density is being used to normalize the density of the solution. Thus a specific gravity value is labeled 20/20 or 20/4 meaning that in both cases the density is measured at 20 °C and normalized, in the first case, by the density of water at 20 °C and in the second by the density of water at 4 °C. To complicate things even more specific gravity is specified as being 'apparent' meaning that it is based on weighings made in air or 'true' based on measurements made in a vacuum. As measuring liquids is a vacuum is really tricky true measurements are based on air weighings adjusted for buoyancy (by subtracting off the calculated weight of air displaced by the sample.

The Plato commission took readings at, I think 25/4. The ASBC and EBC tables are derived from the Plato tables and converted to 20/20 in air. Thus they are apparent specific gravities.
 
The Plato commission took readings at, I think 25/4. The ASBC and EBC tables are derived from the Plato tables and converted to 20/20 in air. Thus they are apparent specific gravities.

So then yes? Plato and SG conversion equations will be dependent on hydrometer's calibration temps?

For example my hydrometer is calibrated to 60F/60F. I've been to two different homebrew shops in the area and they both only carry 60/60 hydrometers! Looking around at online stores they mostly seem to carry 60F hydrometers too.
 
Back
Top