Sat, 17 Nov 2018 19:44:39 +0100
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; */ ?>