www/includes/formulas.php

Sat, 17 Nov 2018 19:44:39 +0100

author
Michiel Broek <mbroek@mbse.eu>
date
Sat, 17 Nov 2018 19:44:39 +0100
changeset 100
08c92cb740b9
parent 96
107c12c3e49d
child 101
5b6bb99bc52a
permissions
-rw-r--r--

Fix for missing coor info on fermentables during recipes import. Log when srm or ebc values are negatie during conversions. Load setup record in global.inc.php and make some variables available for PHP and JS.

<?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 * pow($ebc, 4) - 0.00000000291515 * pow($ebc, 3) + 0.00000818515 * pow($ebc, 2) + 0.372038 * $ebc + 0.596351;
	if (($ebc < 0) || ($srm < 0))
		syslog(LOG_NOTICE, "ebc_to_srm(".$ebc.") = ".$srm);
	return $srm;
}



function srm_to_ebc($srm)
{
	// Formule van Adrie Otten. brouwhulp.
	$ebc = round( 0.000000000176506 * pow($srm, 4) + 0.000000154529 * pow($srm, 3) - 0.000159428 * pow($srm, 2) + 2.68837 * $srm - 1.6004 );
	if (($ebc < 0) || ($srm < 0))
		syslog(LOG_NOTICE, "srm_to_ebc(".$srm.") = ".$ebc);
	return $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_sg($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