www/includes/formulas.php

Thu, 15 Nov 2018 14:27:37 +0100

author
Michiel Broek <mbroek@mbse.eu>
date
Thu, 15 Nov 2018 14:27:37 +0100
changeset 94
295c3af2a421
parent 91
44981507b781
child 96
107c12c3e49d
permissions
-rw-r--r--

Include boil_size field in the recipes table. Calculate pre-boil SG. Use that in the IBU calculation.

<?php

define('EURO', chr(128));

define('MMCa', '40.048');
define('MMMg', '24.305');
define('MMNa', '22.98976928');
define('MMCl', '35.453');
define('MMSO4', '96.0626');
define('MMCO3', '60.01684');
define('MMHCO3', '61.01684');
define('MMCaSO4', '172.171');
define('MMCaCl2', '147.015');
define('MMCaCO3', '100.087');
define('MMMgSO4', '246.475');
define('MMNaHCO3', '84.007');
define('MMNa2CO3', '105.996');
define('MMNaCl', '58.443');
define('MMCaOH2', '74.06268');


function ebc_to_srm($ebc)
{
	// Srm = -1.32303E-12*Ebc4-0.00000000291515*Ebc3+0.00000818515*Ebc2+0.372038*Ebc+0.596351
	return  -1.32303E-12 * pow($ebc, 4) - 0.00000000291515 * pow($ebc, 3) + 0.00000818515 * pow($ebc, 2) + 0.372038 * $ebc + 0.596351;
	//  return $ebc * 0.508;
}



function srm_to_ebc($srm)
{
	// EBC = 0.000000000176506*Srm4+ 0.000000154529*Srm3-0.000159428*Srm2+2.68837*Srm-1.6004
	//
	// Formule van Adrie Otten. brouwhulp.
	$ebc = 0.000000000176506 * pow($srm, 4) + 0.000000154529 * pow($srm, 3) - 0.000159428 * pow($srm, 2) + 2.68837 * $srm - 1.6004;
	return round($ebc);
}



function ebc_to_color($ebc)
{
	return srm_to_color(ebc_to_srm($ebc));
}



function srm_to_color($srm)
{
	$i = abs($srm * 10);
	if ($i < 0) {
		$i = 0;
	}
	if ($i > 299) {
		$i = 299;
	}

	/* Table copied from Brouwhulp/BrewBuddy */
	$R = array( 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250,	// 0
 		    250, 250, 250, 250, 250, 249, 248, 247, 246, 245, 244, 243, 242, 241, 240, 239, 238, 237, 236, 235,	// 2
		    234, 233, 232, 231, 230, 229, 228, 227, 226, 225, 224, 223, 222, 221, 220, 219, 218, 217, 216, 215,	// 4
		    214, 213, 212, 211, 210, 209, 208, 207, 206, 205, 204, 203, 202, 201, 200, 200, 199, 199, 198, 198,	// 6
		    197, 197, 196, 196, 195, 195, 194, 194, 193, 193, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192,	// 8
		    192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192,	// 10
		    192, 192, 192, 192, 192, 192, 192, 192, 191, 190, 189, 188, 187, 186, 185, 184, 183, 182, 181, 180,	// 12
		    179, 178, 177, 175, 174, 172, 171, 169, 168, 167, 195, 164, 162, 161, 159, 158, 157, 155, 154, 152,	// 14
		    151, 149, 148, 147, 145, 144, 142, 141, 139, 138, 137, 135, 134, 132, 131, 129, 128, 127, 125, 124,	// 16
		    122, 121, 119, 118, 117, 115, 114, 112, 111, 109, 108, 107, 105, 104, 102, 101,  99,  98,  97,  95,	// 18
		     94,  92,  91,  89,  88,  87,  85,  84,  82,  81,  79,  78,  77,  75,  74,  72,  71,  69,  68,  67,	// 20
		     65,  64,  62,  61,  59,  58,  57,  55,  54,  52,  51,  49,  48,  47,  45,  44,  43,  41,  39,  38,	// 22
		     37,  37,  36,  36,  35,  35,  34,  34,  33,  33,  32,  32,  31,  31,  30,  30,  29,  29,  28,  28,	// 24
		     27,  27,  26,  26,  25,  25,  24,  24,  23,  23,  22,  22,  21,  21,  20,  20,  19,  19,  18,  18,	// 26
		     17,  17,  16,  16,  15,  15,  14,  14,  13,  13,  12,  12,  11,  11,  10,  10,   9,   9,   8,   8 );

	$G = array( 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250,
		    250, 250, 250, 250, 250, 250, 249, 248, 247, 246, 245, 244, 242, 240, 238, 236, 234, 232, 230, 228,
		    226, 224, 222, 220, 218, 216, 214, 212, 210, 208, 206, 204, 202, 200, 198, 196, 194, 192, 190, 188,
		    186, 184, 182, 180, 178, 176, 174, 172, 170, 168, 166, 164, 162, 160, 158, 156, 154, 152, 150, 148,
		    146, 144, 142, 141, 140, 139, 139, 138, 137, 136, 136, 135, 134, 133, 133, 132, 131, 130, 130, 129,
		    128, 127, 127, 126, 125, 124, 124, 123, 122, 121, 121, 120, 119, 118, 118, 117, 116, 115, 115, 114,
		    113, 112, 112, 111, 110, 109, 109, 108, 107, 106, 106, 105, 104, 103, 103, 102, 101, 100, 100,  99,
		     98,  97,  97,  96,  95,  94,  94,  93,  92,  91,  91,  90,  89,  88,  88,  87,  86,  85,  85,  84,
		     83,  82,  82,  81,  80,  79,  78,  77,  76,  75,  75,  74,  73,  72,  72,  71,  70,  69,  69,  68,
		     67,  66,  66,  65,  64,  63,  63,  62,  61,  60,  60,  59,  58,  57,  57,  56,  55,  54,  54,  53,
		     52,  51,  51,  50,  49,  48,  48,  47,  46,  45,  45,  44,  43,  42,  42,  41,  40,  39,  39,  38,
		     37,  36,  36,  35,  34,  33,  33,  32,  31,  30,  30,  29,  28,  27,  27,  26,  25,  24,  24,  23,
		     22,  22,  22,  21,  21,  21,  20,  20,  20,  19,  19,  19,  18,  18,  18,  17,  17,  17,  16,  16,
		     16,  15,  15,  15,  14,  14,  14,  13,  13,  13,  12,  12,  12,  11,  11,  11,  10,  10,  10,   9,
		      9,   9,   8,   8,   8,   7,   7,   7,   6,   6,   6,   5,   5,   5,   4,   4,   4,   3,   3,   3 );

	$B = array( 210, 204, 199, 193, 188, 182, 177, 171, 166, 160, 155, 149, 144, 138, 133, 127, 122, 116, 111, 105,
		    100,  94,  89,  83,  78,  72,  67,  61,  56,  50,  45,  45,  45,  46,  46,  46,  46,  47,  47,  47,
		     47,  48,  48,  48,  48,  49,  49,  49,  49,  50,  50,  50,  50,  51,  51,  51,  51,  52,  52,  52,
		     52,  53,  53,  53,  53,  54,  54,  54,  54,  55,  55,  55,  55,  56,  56,  56,  56,  56,  56,  56,
		     56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,
		     56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,
		     56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,  56,
		     56,  56,  56,  55,  55,  55,  55,  54,  54,  54,  54,  53,  53,  53,  53,  52,  52,  52,  52,  51,
		     51,  51,  51,  50,  50,  50,  50,  49,  49,  48,  47,  47,  46,  45,  45,  44,  43,  43,  42,  41,
		     41,  40,  39,  39,  38,  37,  37,  36,  35,  34,  33,  32,  31,  29,  28,  27,  26,  25,  24,  23,
		     21,  20,  19,  18,  17,  16,  15,  13,  12,  11,  10,   9,   8,   9,   9,  10,  10,  11,  11,  12,
		     12,  13,  13,  14,  14,  15,  15,  16,  16,  17,  17,  18,  18,  19,  19,  20,  20,  21,  21,  22,
		     21,  21,  21,  20,  20,  20,  19,  19,  19,  18,  18,  18,  17,  17,  17,  17,  16,  16,  15,  15,
		     15,  14,  14,  14,  13,  13,  13,  12,  12,  12,  11,  11,  11,  10,  10,  10,   9,   9,   9,   8,
		      8,   8,   7,   7,   7,   6,   6,   6,   5,   5,   5,   4,   4,   4,   3,   3,   3,   2,   2,   2 );

	return array($R[$i],$G[$i],$B[$i]);
}



function sg_to_plato($sg) {
	if ($sg > 0.5)
		return 259 - 259 / $sg;
	return 0;
}



function plato_to_sg($plato) {
	if ($plato < 259)
		return 259 / (259 - $plato);
	return 1.000;
}



/*
 * sugars is the total extract weight of sugars.
 */
function estimate_og($sugars, $batch_size) {
	$plato = 100 * $sugars / $batch_size;
	$sg = plato_to_sg($plato);

	/* Average loops, HansH 5x. Brouwhulp 20x, about 10x is enough so keep 20. */
	for ($i = 0; $i < 20; $i++) {
		if ($sg > 0)
			$plato = 100 * $sugars / ($batch_size * $sg);
		$sg = plato_to_sg($plato);
	}
	return $sg;
}



function estimate_fg($percSugar, $percCara, $WGratio, $TotTme, $Temp, $attenuation, $og) {
	if ($percSugar > 40)
		$percSugar = 0;
	if ($percCara > 50)
		$percCara = 0;
	if (($WGratio > 0) && ($TotTme > 0)) {
		$BD = $WGratio;
		$BD = max(2, min(5.5, $BD));
		$Temp = max(60, min(72, $Temp));
	} else {
		$BD = 3.5;
		$Temp = 67;
		$TotTme = 75;
	}
	if ($attenuation < 30)
		$attenuation = 77;

	$AttBeer = 0.00825 * $attenuation + 0.00817 * $BD - 0.00684 * $Temp + 0.00026 * $TotTme - 0.00356 * $percCara + 0.00553 * $percSugar + 0.547;
	$fg = 1 + (1 - $AttBeer) * ($og - 1);
	return $fg;
}



function abvol($og, $fg) {
	if ((4.749804 - $fg) <> 0)
		return 486.8693 * ($og - $fg) / (4.749804 - $fg);
	return 0;
}


/*
 * Kleurwerking naar SRM
 */
function kw_to_srm($colormethod, $c) {

	if ($colormethod == "Morey")
		return 1.4922 * pow($c, 0.6859);
	if ($colormethod == "Mosher")
		return 0.3 * $c + 4.7;
	if ($colormethod == "Daniels")
		return 0.2 * $c + 8.4;
}



function kw_to_ebc($colormethod, $c) {
	return srm_to_ebc(kw_to_srm($colormethod, $c));
}



function calc_IBU($useat, $form, $sg, $volume, $mass, $boiltime, $alpha, $method) {

	$fmoment  = 1.0;
	if (($useat == "Dry Hop") || ($useat == "Dry hop") || ($useat == "Whirlpool") || ($useat == "Aroma")) {
		$fmoment = 0.0;
	} else if ($useat == "Mash") {
		$fmoment += /* Settings.MashHopFactor.Value = -30% */ -30 / 100;         // Brouwhulp
	} else if (($useat == "First Wort") || ($useat == "First wort")) {
		$fmoment += /* Settings.FWHFactor.Value = 10% */ 10 / 100;         // Brouwhulp, Louis, Ozzie
	}

	$pfactor  = 1.0;
	if ($form == "Pellet") {
		$pfactor += /* Settings.PelletFactor.Value = 10% */ 10 / 100;
	}
	if ($form == "Plug") {
		$pfactor += /* Settings.PlugFactor.Value = 2% */ 2 / 100;
	}

	$ibu = 0;
	if (($method == "Tinseth") || ($method == "Garetz")) {	// For Garetz, we need the $ibu
		$AddedAlphaAcids = (($alpha / 100) * $mass * 1000) / $volume;
		$Bigness_factor = 1.65 * pow( 0.000125, $sg - 1);
		$BoilTime_factor = ((1 - exp(-0.04 * $boiltime)) / 4.15);
		$utiisation = $Bigness_factor * $BoilTime_factor;
		$ibu = (round($utiisation * $AddedAlphaAcids * $fmoment * $pfactor * 10) / 10.0);
	}
	if ($method == "Daniels") {

		if ($form == "Leaf")
			$boilfactor = -(0.0041*$boiltime*$boiltime)+(0.6162*$boiltime)+1.5779;
		else
			$boilfactor = -(0.0051*$boiltime*$boiltime)+(0.7835*$boiltime)+1.9348;
		if ($sg < 1.050)
			$sgfactor = 0;
		else
			$sgfactor = (($sg * 1000) - 1050) / 200;
		$ibu = $fmoment * (($mass * $alpha * $boilfactor * 0.1) / ($volume * (1 + $sgfactor)));
	}
	if ($method == "Rager") {
		$boilfactor = $fmoment * 18.11 + 13.86 * tanh(($boiltime * 31.32) / 18.27);
		if ($sg < 1.050)
			$sgfactor = 0;
		else
			$sgfactor = (($sg * 1000) - 1050) / 200;
		$ibu = ($mass * $alpha * $boilfactor * 0.1) / ($volume * (1 + $sgfactor));
	}
	if ($method == "Garetz") {
		/* Something is wrong, late hops and dryhops give negative results. */
		$boilfactor = $fmoment * 6.03253 + 16.5289 * tanh(($boiltime - 19.17323) / 26.8013);
		$cfactor = $volume / (1.1 * $volume);	// ConcentratieFactor = (Volume na koelen) / (Volume bij Koken)
		$kookdichtheid = ($cfactor * (($sg * 1000) - 1000) / 1000) + 1;
		$sgfactor = ($kookdichtheid - 1.05) / 0.2 + 1;
		$hopratefactor = (($cfactor * $ibu) / 260) + 1;	// $ibu is Tinseth bitterness. Weird.
		$tempfactor = (32.8/550)*0.02+1;
		$ibu = ($boilfactor * $alpha * $mass * 0.1) / ($volume * $sgfactor * $hopratefactor * $tempfactor);
	}
	/* TODO: Noonan and Mosher */

	return $ibu;
}



/*

Brouwhulp data.pas

Function THop.FlavourContribution : double; //in % * concentration in g/l
var bt, vol : double;
begin
  bt:= FTime.Value;
  vol:= 0;
  if FRecipe <> NIL then vol:= FRecipe.BatchSize.Value;
  if FUse = huFirstWort then Result:= 0.15 * FAmount.Value * 1000 //assume 15% flavourcontribution for fwh
  else if bt > 50 then Result:= 0.10 * FAmount.Value * 1000 //assume 10% flavourcontribution as a minimum
  else
  begin
    Result:= 15.25 / (6 * sqrt(2 * PI)) * Exp(-0.5*Power((bt-21)/6, 2))
             * FAmount.Value * 1000;
    if result < 0.10 * FAmount.Value * 1000 then
      Result:= 0.10 * FAmount.Value * 1000 //assume 10% flavourcontribution as a minimum
  end;
  if vol > 0 then Result:= Result / vol;
end;

Function THop.AromaContribution : double; //in % * concentration in g/l
var bt, vol : double;
begin
  bt:= FTime.Value;
  vol:= 0;
  if FRecipe <> NIL then vol:= FRecipe.BatchSize.Value;
  if bt > 20 then Result:= 0
  else if bt > 7.5 then
    Result:= 10.03 / (4 * sqrt(2 * PI)) * Exp(-0.5*Power((bt-7.5)/4, 2))
             * FAmount.Value * 1000
  else if FUse = huBoil then Result:= FAmount.Value * 1000
  else if FUse = huAroma then Result:= 1.2 * FAmount.Value * 1000
  else if FUse = huWhirlpool then Result:= 1.2 * FAmount.Value * 1000
  else if FUse = huDryHop then Result:= 1.33 * FAmount.Value * 1000;
    if vol > 0 then Result:= Result / vol;
end;


Procedure TFermentable.SetpHParameters(force : boolean);
var x, ebc : double;
begin
  if Between(FDIpH.Value, -0.01, 0.01) or Between(FAcidTo57.Value, -0.1, 0.1) or force then
  begin
    ebc:= SRMtoEBC(FColor.Value);
    case FGrainType of
    gtBase, gtKilned:
    begin
      FDIpH.Value:= -0.0132 * ebc + 5.7605;
      x:= 0.4278 * ebc - 1.8106;
      FAcidTo57.Value:= x;
    end;
    gtRoast:
    begin
      FDIpH.Value:= 0.00018 * ebc + 4.558;
      FAcidTo57.Value:= -0.0176 * ebc + 60.04;
    end;
    gtCrystal:
    begin
      FDIpH.Value:= -0.0019 * ebc + 5.2175;
      FAcidTo57.Value:= 0.132 * ebc + 14.277;
    end;
    gtSour:
    begin
      FDIpH.Value:= 3.44;
      FAcidTo57.Value:= 337;
    end;
    gtSpecial: //this could be anything. Assume for now it is a non-acidulated base or kilned malt
    begin
      FDIpH.Value:= -0.0132 * ebc + 5.7605;
      FAcidTo57.Value:= 0.4278 * ebc - 1.8106;
    end;
    end;
  end;
  //known parameters should be filled in here
  if FSupplier.Value = 'Weyermann' then
  begin
    if FName.Value = 'Vienna mout' then
    begin
      FDIpH.Value:= 5.65;
      FAcidTo57.Value:= 1.6;
    end;
    if FName.Value = 'Münchner I' then
    begin
      FDIpH.Value:= 5.44;
      FAcidTo57.Value:= 8.4;
    end;
    if FName.Value = 'Münchner II' then
    begin
      FDIpH.Value:= 5.54;
      FAcidTo57.Value:= 5.6;
    end;
    if FName.Value = 'Caramunich I' then
    begin
      FDIpH.Value:= 5.1;
      FAcidTo57.Value:= 22.4;
    end;
    if FName.Value = 'Caramunich II' then
    begin
      FDIpH.Value:= 4.71;
      FAcidTo57.Value:= 49;
    end;
    if FName.Value = 'Caramunich III' then
    begin
      FDIpH.Value:= 4.92;
      FAcidTo57.Value:= 31.2;
    end;
    if FName.Value = 'Cara-aroma' then
    begin
      FDIpH.Value:= 4.48;
      FAcidTo57.Value:= 74.4;
    end;
    if FName.Value = 'Carafa I' then
    begin
      FDIpH.Value:= 4.71;
      FAcidTo57.Value:= 42;
    end;
    if FName.Value = 'Carafa III' then
    begin
      FDIpH.Value:= 4.81;
      FAcidTo57.Value:= 35.4;
    end;
    if FName.Value = 'Carafa II' then
    begin
      FDIpH.Value:= 4.76;
      FAcidTo57.Value:= 38.7;
    end;
    if FName.Value = 'Carafa Special I' then
    begin
      FDIpH.Value:= 4.73;
      FAcidTo57.Value:= 46.4;
    end;
    if FName.Value = 'Carafa Special II' then
    begin
      FDIpH.Value:= 4.78;
      FAcidTo57.Value:= 42.9;
    end;
    if FName.Value = 'Carafa Special III' then
    begin
      FDIpH.Value:= 4.83;
      FAcidTo57.Value:= 38.9;
    end;
    if IsInString(FName.Value, 'Zuurmout') then
    begin
      FDIpH.Value:= 3.44;
      FAcidTo57.Value:= 358.2;
    end;
  end;
end;


function TFermentable.GetExtract: double;
begin
  Result := 0;
  if FRecipe <> nil then
  begin
    Result := FAmount.Value * FYield.Value / 100 * (1 - FMoisture.Value / 100);
    if FAdded = atMash then
      Result := Result * FRecipe.Efficiency / 100;
  end;
end;


function TFermentable.GetKolbachIndex: double;
begin
  if FProtein.Value > 0 then
    Result := FDissolvedProtein.Value / FProtein.Value
  else
    Result := 0;
end;


Procedure TWater.AddMinerals(Ca, Mg, Na, HCO3, Cl, SO4 : double);
begin
  FCalcium.Add(Ca);
  FMagnesium.Add(Mg);
  FSodium.Add(Na);
  FBicarbonate.Add(HCO3);
  FChloride.Add(Cl);
  FSulfate.Add(SO4);
end;

function TWater.GetResidualAlkalinity: double;
begin
  //Result in mg/l as CaCO3
  Result:= FTotalAlkalinity.Value - (FCalcium.Value / 1.4 + FMagnesium.Value / 1.7);
end;

const
  Ka1 = 0.0000004445;
  Ka2 = 0.0000000000468;

Function PartCO3(pH : double) : double;
var H : double;
begin
  H:= Power(10, -pH);
  Result:= 100 * Ka1 * Ka2 / (H*H + H * Ka1 + Ka1 * Ka2);
end;

Function PartHCO3(pH : double) : double;
var H : double;
begin
  H:= Power(10, -pH);
  Result:= 100 * Ka1 * H / (H*H + H * Ka1 + Ka1 * Ka2);
end;

Function PartH2CO3(pH : double) : double;
var H : double;
begin
  H:= Power(10, -pH);
  Result:= 100 * H * H / (H*H + H * Ka1 + Ka1 * Ka2);
end;

Function Charge(pH : double) : double;
begin
  Result:= (-2 * PartCO3(pH) - PartHCO3(pH));
end;

//Z alkalinity is the amount of acid (in mEq/l) needed to bring water to the target pH (Z pH)
Function TWater.ZAlkalinity(pHZ : double) : double;  //in mEq/l
var CT, DeltaCNaught, DeltaCZ, C43, Cw, Cz : double;
begin
  C43:= Charge(4.3);
  Cw:= Charge(FpH.Value);
  Cz:= Charge(pHz);
  DeltaCNaught:= -C43+Cw;
  CT:= GetAlkalinity / 50 / DeltaCNaught;
  DeltaCZ:= -Cz+Cw;
  Result:= CT * DeltaCZ;
end;

//Z Residual alkalinity is the amount of acid (in mEq/l) needed to bring the water in the mash to the target pH (Z pH)
Function TWater.ZRA(pHZ : double) : double; //in mEq/l
var Calc, Magn, Z : double;
begin
  Calc:= FCalcium.Value / (MMCa / 2);
  Magn:= FMagnesium.Value / (MMMg / 2);
  Z:= ZAlkalinity(pHZ);
  Result:= Z - (Calc / 3.5 + Magn / 7);
end;

Function TWater.ProtonDeficit(pHZ : double) : double;
var i : integer;
    F : TFermentable;
    x : double;
begin
  Result:= ZRA(pHZ) * FAmount.Value;
  //proton deficit for the added malts
  for i:= 0 to FRecipe.NumFermentables - 1 do
  begin
    F:= FRecipe.Fermentable[i];
    if (F.AddedType = atMash) and (F.GrainType <> gtNone) then
    begin
      x:= F.AcidRequired(pHZ) * F.Amount.Value;
      Result:= Result + x;
    end;
  end;
end;

Function TWater.MashpH : double;
var n : integer;
    pd : double;
    pH, deltapH, deltapd : double;
begin
  Result:= 0;
  n:= 0;
  pH:= 5.4;
  deltapH:= 0.001;
  deltapd:= 0.1;
  pd:= ProtonDeficit(pH);
  while ((pd < -deltapd) or (pd > deltapd)) and (n < 1000) do
  begin
    inc(n);
    if pd < -deltapd then ph:= ph - deltapH
    else if pd > deltapd then pH:= pH + deltapH;
    pd:= ProtonDeficit(pH);
  end;
  Result:= pH;
end;

Function TWater.MashpH2(PrDef : double) : double;
var n : integer;
    pd : double;
    pH, deltapH, deltapd : double;
begin
  Result:= 0;
  n:= 0;
  pH:= 5.4;
  deltapH:= 0.001;
  deltapd:= 0.1;
  pd:= ProtonDeficit(pH);
  while ((pd < PrDef-deltapd) or (pd > PrDef + deltapd)) and (n < 1000) do
  begin
    inc(n);
    if pd < PrDef-deltapd then ph:= ph - deltapH
    else if pd > PrDef+deltapd then pH:= pH + deltapH;
    pd:= ProtonDeficit(pH);
  end;
  Result:= pH;
end;

function TWater.GetAlkalinity: double;
begin
  Result := FBicarbonate.Value / 1.22; //mEq/l
end;

function TWater.GetHardness: double;
begin
  Result := 0.14 * FCalcium.Value - 0.23 * FMagnesium.Value;
end;

function TWater.GetEstPhMash: double;
{var
  pHdemi, S: double;}
begin
  Result:= MashpH;
{  Result := 0;
  if FRecipe <> nil then
  begin
    pHDemi := FRecipe.pHdemi;
    S := 0.013 * FRecipe.MashThickness + 0.013;
    Result := pHDemi + ResidualAlkalinity / 50 * S;
  end;}
end;


function TBeerStyle.GetBUGUMin: double;
var
  B, G: double;
begin
  Result:= 0;
  if ((FOGMax.Value + FOGMin.Value) > 0) and ((FIBUMax.Value + FIBUMin.Value) > 0) then
  begin
    G := (FOGMax.Value - FOGMin.Value) / ((FOGMax.Value + FOGMin.Value) / 2);
    B := (FIBUMax.Value - FIBUMin.Value) / ((FIBUMax.Value + FIBUMin.Value) / 2);
    if G > B then
      Result := ((FIBUMin.Value + FIBUMax.Value) / 2) / (1000 * (FOGMax.Value - 1))
    else
      Result := FIBUMin.Value / (1000 * (((FOGMax.Value + FOGMin.Value) / 2) - 1));
  end;
end;

function TBeerStyle.GetBUGUMax: double;
var
  B, G: double;
begin
  Result:= 0;
  if ((FOGMax.Value + FOGMin.Value) > 0) and ((FIBUMax.Value + FIBUMin.Value) > 0)
  and (FOGMin.Value > 1) then
  begin
    G := (FOGMax.Value - FOGMin.Value) / ((FOGMax.Value + FOGMin.Value) / 2);
    B := (FIBUMax.Value - FIBUMin.Value) / ((FIBUMax.Value + FIBUMin.Value) / 2);
    if G > B then
      Result := ((FIBUMin.Value + FIBUMax.Value) / 2) / (1000 * (FOGMin.Value - 1))
    else
      Result := FIBUMax.Value / (1000 * (((FOGMax.Value + FOGMin.Value) / 2) - 1));
  end;
end;


  CalcBitterness;

        // Get concentration of ions in diluted brewwater (1) and target water (2) in mmol/l
        Ca1 := W.Calcium.Value / MMCa;
        Ca2 := W2.Calcium.Value / MMCa;
        Mg1 := W.Magnesium.Value / MMMg;
        Mg2 := W2.Magnesium.Value / MMMg;
        Na1 := W.Sodium.Value / MMNa;
        Na2 := W2.Sodium.Value / MMNa;

        CO31 := W.Bicarbonate.Value / MMHCO3;
        CO32 := W2.Bicarbonate.Value / MMHCO3;
        SO41 := W.Sulfate.Value / MMSO4;
        SO42 := W2.Sulfate.Value / MMSO4;
        Cl1 := W.Sulfate.Value / MMSO4;
        Cl2 := W2.Sulfate.Value / MMSO4;


procedure MixWater(W1, W2, Wr: TWater);

  function Mix(V1, V2, C1, C2: double): double;
  begin
    if (V1 + V2) > 0 then
      Result := (V1 * C1 + V2 * C2) / (V1 + V2)
    else
      Result := 0;
  end;

var
  vol1, vol2: double;
  phnew: double;
begin
  vol1 := W1.Amount.Value;
  vol2 := W2.Amount.Value;
  if (vol1 + vol2) > 0 then
  begin
    Wr.Amount.Value := vol1 + vol2;
    Wr.Calcium.Value := Mix(vol1, vol2, W1.Calcium.Value, W2.Calcium.Value);
    Wr.Magnesium.Value := Mix(vol1, vol2, W1.Magnesium.Value, W2.Magnesium.Value);
    Wr.Sodium.Value := Mix(vol1, vol2, W1.Sodium.Value, W2.Sodium.Value);
    Wr.Bicarbonate.Value := Mix(vol1, vol2, W1.Bicarbonate.Value, W2.Bicarbonate.Value);
    Wr.Sulfate.Value := Mix(vol1, vol2, W1.Sulfate.Value, W2.Sulfate.Value);
    Wr.Chloride.Value := Mix(vol1, vol2, W1.Chloride.Value, W2.Chloride.Value);
    pHnew := -log10((power(10, -W1.pHWater.Value) * vol1 +
      power(10, -W2.pHWater.Value) * vol2) / (vol1 + vol2));
    Wr.pHwater.Value := pHnew;
  end;
end;



function TRecipe.CalcColorWort : double;
var
  i: integer;
  F: TFermentable;
  c, v: double;
begin
  c := 0;
  v := FBatchSize.Value;
  if (v > 0) and (High(FFermentables) >= 0) then
  begin
    for i := Low(FFermentables) to High(FFermentables) do
    begin
      F := TFermentable(FFermentables[i]);
      c := c + F.Amount.Value * F.Color.Value / v;
    end;
    c := c * 8.34436;
    case FColorMethod of
      cmMorey: c := 1.49 * Power(c, 0.69);
      cmMosher: c := 0.3 * c + 4.7;
      cmDaniels: c := 0.2 * c + 8.4;
    end;
  end;
  Result:= c;
end;


procedure TRecipe.CalcWaterBalance;
var
  i: integer;
  F: TFermentable;
begin
  FAbsorbedByGrain := 0;
  for i := Low(FFermentables) to High(FFermentables) do
  begin
    F := TFermentable(FFermentables[i]);
    if (F.FermentableType = ftGrain) or (F.FermentableType = ftAdjunct) then
      FAbsorbedByGrain := FAbsorbedByGrain + F.Amount.Value;
  end;
  FAbsorbedByGrain := FAbsorbedByGrain * Settings.GrainAbsorption.Value;

  if FEquipment.CalcBoilVolume.Value then
    {FBoilSize.Value := FBatchSize.Value / (1 - (FEquipment.EvapRate.Value / 100) *
      (FBoilTime.Value / 60));}
    FBoilSize.Value:= FBatchSize.Value + FEquipment.BoilSize.Value
                              * FEquipment.EvapRate.Value / 100 *
                              (FBoilTime.Value / 60);
end;





Procedure TRecipe.CalcCalories;
var sug, alc, org, fig : double;
begin
  if FOGFermenter.Value > 1.001 then org:= FOGFermenter.Value
  else if FOG.Value > 1.001 then org:= FOG.Value
  else org:= 0;
  if FFG.Value > 0.999 then fig:= FFG.Value
  else if FEstFG.Value > 1.000 then fig:= FEstFG.Value
  else if FEstFG2.Value > 1.000 then fig:= FEstFG2.Value
  else fig:= 0;
  if (org > 0) and (fig > 0) then
  begin
    alc:= 1881.22 * fig * (org - fig) / (1.775 - org);
    sug:= 3550 * fig * (0.1808 * org + 0.8192 * fig - 1.0004);
    FCalories.Value:= (alc + sug) / (12 * 0.0295735296);
  end
  else FCalories.Value:= 0;
end;




*/

?>

mercurial