/******************************************
** Name     : beerlib.c
** Function : tools for beer utilities
** Author   : Domenick Venezia
**/
/* INCLUDE FILES **************************/

#include <local/beer.h>

/* ENTRY *****************************/
/* Corrects the input specific gravity for the input temperature. 
*/

float corrected_SG( float SG, float temp, char temptype )
{
   float tempF,
         correction;

   if ((temptype == 'C') || (temptype == 'c'))
   {  tempF = C_to_F_degrees( temp );
   }
   else
   {  tempF = temp;
   }

   correction = A0 + A1*tempF + A2*tempF*tempF + A3*tempF*tempF*tempF;

   return( SG + (correction * 0.001) );
}  

/* ENTRY *****************************/
/* Corrects the input specific gravity at the input temperature
** to the reference temperature of 15 C (59 F).
*/

float corrected_SG_at_15C( float SG, float temp, char temptype )
{
   float C,
         correction;

   if ((temptype == 'F') || (temptype == 'f'))
   {  C = F_to_C_degrees( temp );
   }
   else
   {  C = temp;
   }

/*
** Use a linear interpolation for 0-3.98 C
*/
   if (C < 3.98)
   {  correction = -0.000032692*C - 0.000740644;
   } 
   else if (C < 50)
   {  correction = -0.0008031922 - 0.0000473773*C + 0.000007231263*C*C
                                 - 0.00000003078278*C*C*C;
   }
   else 
   {  correction = -0.005431719 + 0.0001963596*C + 0.000002661056*C*C;
   }

   return( SG + correction );
}  

/* ENTRY *****************************/
/* Reference: Manning, M.P., Understanding Specific Gravity and Extract,
**            Brewing Techniques, 1,3:30-35 (1993)
**
** Equation is:  P = -676.67 + 1286.4SG - 800.47SG**2 + 190.74SG**3
*/

float SG_to_Plato( float SG )
{
   return( -676.67 + 1286.4*SG - 800.47*SG*SG + 190.74*SG*SG*SG );
}

/* ENTRY *****************************/
/* Reference: Manning, M.P., Understanding Specific Gravity and Extract,
**            Brewing Techniques, 1,3:30-35 (1993)
**
** Equation is:  We = P*SG*R/100 
**       where:  P = degrees Plato @ 60F
**              SG = Specific Gravity @ 60F
**               R = density of water @ 60F = 8.338lb/USgal = 0.999kg/L
*/

float lbs_extract_per_gal_SG( float SG_raw, float temp, char temptype )
{
   float SG,
         Plato;

   SG = corrected_SG( SG_raw, temp, temptype );
   Plato = SG_to_Plato( SG );
 
   return( SG * Plato * 8.338 * 0.01 );
}

/* ENTRY *****************************/
/* Reference: Manning, M.P., Understanding Specific Gravity and Extract,
**            Brewing Techniques, 1,3:30-35 (1993)
**
** Equation is:  SG2pts = SG1pts * (V1/V2)
**       where:  SGpts  = SG - 1
*/

float SG_change_with_vol( float SG0, float vol0, float vol1 )
{
   return( ((SG0 - 1.0) * (vol0 / vol1)) + 1.0 );
}

/* ENTRY *****************************/
/*
** Equation is:  V1 = V0 * (SG0pts/SG1pts)
**       where:  SGpts  = SG - 1
*/

float vol_change_with_SG( float vol0, float SG0, float SG1 )
{
   return( ((SG0 - 1.0)/(SG1 - 1.0)) * vol0 );
}

/* ENTRY *****************************/

float temp_to_F_degrees( float temp, char temptype )
{
   if ((temptype == 'C') || (temptype == 'c'))
   {  return( C_to_F_degrees( temp ) );
   }
   else
   {  return( temp );
   }
}

/* ENTRY *****************************/

float temp_to_C_degrees( float temp, char temptype )
{
   if ((temptype == 'F') || (temptype == 'f'))
   {  return( F_to_C_degrees( temp ) );
   }
   else
   {  return( temp );
   }
}

/* ENTRY *****************************/

float C_to_F_degrees( float temp )
{
   return( (temp * 1.8) + 32.0 );
}

/* ENTRY *****************************/

float F_to_C_degrees( float temp )
{
   return( (temp - 32.0) / 1.8 );
}

/* ENTRY **********************************/

float lbs_to_kgs( float lbs )
{
   return( lbs * 0.453592 );
}

/* ENTRY **********************************/

float kgs_to_lbs( float kgs )
{
   return( kgs * 2.204623 );
}

/* ENTRY **********************************/

float gals_to_liters( float gals )
{
   return( gals * 3.785412 );
}

/* ENTRY **********************************/

float liters_to_gals( float liters )
{
   return( liters * 0.264272 );
}

/* ENTRY **********************************/

float qts_to_liters( float qts )
{
   return( qts * 0.946353 );
}

/* ENTRY **********************************/

float liters_to_qts( float liters )
{
   return( liters * 1.056688 );
}

/* ENTRY **********************************/
/* Calories due to alcohol per 12 ounces of beer */

float alc_calories_per_12_oz( float OSG, float FSG )
{
   return( (1881.22 * FSG) * ((OSG - FSG)/(1.775 - OSG)) );
}

/* ENTRY **********************************/
/* Calories due to extract per 12 ounces of beer */

float ext_calories_per_12_oz( float OSG, float FSG )
{
   return( 3550.0 * FSG * ((0.1808 * OSG) + (0.8192 * FSG) - 1.0004) );
}

/* ENTRY ***********************************/
/* Note temp MUST be fahrenheit 
*/

float CO2_pressure( float vol, float tempF )
{  
   float KP;  /* keg pressure */

   KP = -16.6999 - (0.0101059 * tempF) 
                 + (0.00116512 * tempF * tempF) 
                 + (0.173354 * tempF * vol) 
                 + (4.24267 * vol) 
                 - (0.0684226 * vol * vol);
   return( KP );
}

/* ENTRY ***********************************/
/* Find volume of decoction to raise mash temp from initial temp to target temp
*/

float decoct( float vt, float tt, float t0, float td )

/*       vt,   Total Volume */
/*       tt,   Target temp */
/*       t0,   Initial temp */
/*       td;   decoction temp */

{
   return( (vt * (tt - t0)) / (td - t0) );
}

/* ENTRY ***********************************/
/* Find volume addition at given temp to raise mash temp to target. 
*/

float beer_step_add_vol( float vm, float tm, float ta, float tf )

/*       vm,   Volume mash */
/*       tm,   Temperature mash */
/*       ta,   temperature addition */
/*       tf;   Temperature final */

{
   float va;  /* Volume addition - what we are calculating */

   va = (vm * (tm - tf)) / (tf - ta);
   return( va );
}

/* ENTRY *****************************/
/* Returns the Relative density difference between water at the reference
** temp, t0, and the target temp, t1.
*/

float beer_relative_density_of_water( float t0, float t1 )
{
   int tlow,
       thigh;

   float d0,
         d1,
         dlow,
         dhigh;

   extern float density_water[BEER_NUM_DENS];

/*
** First find the density of water at the reference temperature by interpolation
*/
   if (t0 == 0.0) 
   {  d0 = density_water[0];
   }
   else
   {  tlow  = floor( (double) t0 );
      thigh = ceil( (double) t0 );

      dlow  = density_water[tlow];
      dhigh = density_water[thigh];

      d0  = dlow;
      d0 += (t0 - tlow) * (dhigh - dlow);
   }

/*
** Next find the density of water at the target temperature by interpolation
*/
   if (t1 == 0.0) 
   {  d1 = density_water[0];
   }
   else
   {  tlow  = floor( (double) t1 );
      thigh = ceil( (double) t1 );

      dlow  = density_water[tlow];
      dhigh = density_water[thigh];

      d1  = dlow;
      d1 += (t1 - tlow) * (dhigh - dlow);
   }

   return( d1 / d0 );
}

/* ENTRY *****************************/
/* Returns the Relative density difference between water at the reference
** temp, t0, and the target temp, t1.
*/

float beer_relative_volume_of_water( float t0, float v0, float t1 )
{
   float rd1;   /* relative density of target volume at target temp */

   rd1 = beer_relative_density_of_water( t0, t1 );
   return( v0 / rd1 );
}

/***************************************************************
** The following are Glenn Tinseth's hopping functions
*/

/* ENTRY **********************************/

float tinseth_hop_gravity_adjustment( float SGB )

/* SGB   Specific Gravity of the boil */

{
   return( 1.65 * pow( 0.000125, (double) SGB - 1.0 ) );
}

/* ENTRY **********************************/

float tinseth_hop_boil_time_factor( float boil_time )
{
   return( (1.0 - exp( -0.04 * (double)boil_time )) / 4.15 );
}

/* ENTRY **********************************/

float tinseth_hop_utilization( float SGB, float boil_time )
{
   float time_factor,
         SG_factor;

   time_factor = tinseth_hop_boil_time_factor( boil_time );
   SG_factor   = tinseth_hop_gravity_adjustment( SGB );
   return( SG_factor * time_factor );
}

/* ENTRY **********************************/

float tinseth_hop_ibu_from_weight( float Util, float Acid, 
                       float Wt, float Vol, float GA, float WtFactor )

/*    Util,      Percent Hop utilization as fraction ( Util < 1.0)
      Acid,      Percent Alpha acid content of hops as fraction
      Wt,        Weight of Hops in grams
      Vol,       Volume of batch size in Liters or Gallons
      GA,        Gravity adjustment
      WtFactor;  Weight conversion value for metric or english measures 
*/
{
   return( (Util*Acid*Wt*WtFactor) / Vol );
}

/* ENTRY **********************************/

float tinseth_hop_weight_from_ibu( float Util, float Acid, 
                       float IBU, float Vol, float GA, float WtFactor )

/*       Util,     Percent Hop utilization as fraction ( Util < 1.0) */
/*       Acid,     Percent Alpha acid content of hops as fraction */
/*       IBU,      Desired IBU */
/*       Vol,      Volume of batch size in Liters */
/*       GA,       Gravity adjustment */
/*       WtFactor; Weight conversion value for metric or english measures */

{
   return( (Vol*IBU) / (Util*Acid*WtFactor) );
}

/***************************************************************
** The following are Jackie Rager's hopping functions
*/

/* ENTRY **********************************/

float rager_hop_gravity_adjustment( float SGB )

/* SGB;    Specific Gravity of the boil */
{
   if (SGB <= RAGER_HIGH_SPECIFIC_GAVITY) return( 0.0 );

   return( (SGB - RAGER_HIGH_SPECIFIC_GAVITY) / (0.2) );
}

/* ENTRY **********************************/

float rager_hop_util( int boil_minutes )
{
   if (boil_minutes == 0)
   {  return( 0.0 );
   }
   else if (boil_minutes <= 5)
   {  return( 0.05 );
   }
   else if (boil_minutes <= 10)
   {  return( 0.06 );
   }  
   else if (boil_minutes <= 15)
   {  return( 0.08 );
   }  
   else if (boil_minutes <= 20)
   {  return( 0.101 );
   }  
   else if (boil_minutes <= 25)
   {  return( 0.121 );
   }  
   else if (boil_minutes <= 30)
   {  return( 0.153 );
   }  
   else if (boil_minutes <= 35)
   {  return( 0.188 );
   }  
   else if (boil_minutes <= 40)
   {  return( 0.228 );
   }  
   else if (boil_minutes <= 45)
   {  return( 0.269 );
   }  
   else if (boil_minutes <= 50)
   {  return( 0.281 );
   }  
   else if (boil_minutes <= 55)
   {  return( 0.291 );
   }  
   else
   {  return( 0.30 );
   }  
}

/* ENTRY **********************************/

float rager_hop_ibu_from_weight( float Util, float Acid, 
                       float Wt, float Vol, float GA, float WtFactor )

/*       Util,     Percent Hop utilization as fraction ( Util < 1.0) */
/*       Acid,     Percent Alpha acid content of hops as fraction */
/*       Wt,       Weight of Hops in grams */
/*       Vol,      Volume of batch size in Liters or Gallons*/
/*       GA,       Gravity adjustment */
/*       WtFactor; Weight conversion value for metric or english measures */

{
   return( (Util*Acid*Wt*WtFactor) / (Vol*(1.0 + GA)) );
}

/* ENTRY **********************************/

float rager_hop_weight_from_ibu( float Util, float Acid, 
                       float IBU, float Vol, float GA, float WtFactor )

/*       Util,     Percent Hop utilization as fraction ( Util < 1.0) */
/*       Acid,     Percent Alpha acid content of hops as fraction */
/*       IBU,      Desired IBU */
/*       Vol,      Volume of batch size in Liters */
/*       GA,       Gravity adjustment */
/*       WtFactor; Weight conversion value for metric or english measures */

{
   return( (Vol*IBU*(1.0 + GA)) / (Util*Acid*WtFactor) );
}

/* ENTRY **********************************/
/* .39661 + 0.0017091*Po + (1.0788E-5)*Po^2  AJ DeLange
*/

float ballings_factor( float Plato )
{  return( 0.39661 + (0.0017091*Plato) + (1.0788e-5*Plato*Plato) );
}

/* ENTRY **********************************/

float alcohol_by_weight_fix( float OSG, float FSG )
{
   float Alc,
         OE,
         AE,
         RE;

   OE = SG_to_Plato( OSG );
   AE = SG_to_Plato( FSG );

   RE  = (0.1808 * OE) + (0.8192 * AE);
   Alc = (OE - RE) / (2.0665 - (0.010665 * OE));

   return( Alc );
} 

/* ENTRY **********************************/

float alcohol_by_weight_AJ( float OSG, float FSG )
{
   float PO,    /* Plato original (corrected) */
         PF,    /* Plato finished (corrected) */
         BF,    /* Balling factor */
         ABW;

   PO  = SG_to_Plato( OSG );
   PF  = SG_to_Plato( FSG );
   BF  = ballings_factor( PO );
   ABW = (PO - PF) * BF; 
   return( ABW );
}

/* ENTRY **********************************/
/* Alcohol by weight to alcohol by volume */

float abw_to_abv( float abw )
{
   return( abw/0.793573 );
}

/* ENTRY **********************************/
/*
** RI = 1.33302 + 0.001427193(B) + 0.000005791157(B^2)
**
** where:
**
** B  = measured refractivity in Brix
** RI = calculated Refractive Index
*/

float brix_to_ri( float brix )
{
   double B,
          ri;
   
   B = brix;

   return( (float) 1.33302 + 0.001427193*B + 0.000005791*B*B );
}

/* ENTRY **********************************/
/*
** SG = 1.001843 - 0.002318474(OG) - 0.000007775(OG^2) - 0.000000034(OG^3) +
**      0.00574(AG) + 0.00003344(AG^2) + 0.000000086(AG^3)
**
** where:
**
** SG = estimated specific gravity of the sample
** OG = Original Gravity of the batch (in Brix)
** AG = Apparent Gravity of the sample (in Brix)
*/

float brix_to_fg( float ob, float ab )
{
   double OB,
          AB,
          SG;

   OB = (double) ob;
   AB = (double) ab;

   SG = 1.001843 - (0.002318474*OB) - (0.000007775*OB*OB) 
                 - (0.000000034*OB*OB*OB) + (0.00574*AB) 
                 + (0.00003344*AB*AB) + (0.000000086*AB*AB*AB);

   return( (float) SG );
}

/* ENTRY **********************************/
/* Alcohol by Weight from Specific Gravity and Degrees Brix of finished beer */

float brix_to_abw( float sg, float ab )
{
   double SG,
          RI;

   SG = sg;

   RI = brix_to_ri( ab );

   return( (float)(1017.5596 - (277.4*SG) + RI*((937.8135*RI) - 1805.1228)));
}

/* ENTRY **********************************/
/* Alcohol by Weight from Specific Gravity and Degrees Brix of finished beer */

float brix_sg_to_abw( float ab, float sg )
{
   double SG,
          AB,
          RI;

   SG = sg;
   AB = ab;

   RI = 1.33302 + 0.001427193*AB + 0.000005791*AB*AB;

   return( (float)(1017.5596 - (277.4*SG) + RI*((937.8135*RI) - 1805.1228)));
}

/* ENTRY **********************************/
/* Specific Gravity @ 15C from Degrees Brix @ 20C */

float brix_to_sg( float brix )
{
   double B,
          SG;

   B = brix;
   SG = 1.000898 + 0.003859118*B + 0.00001370735*B*B + 0.00000003742517*B*B*B;

   return( (float) SG );
}
