Added dutch translations to the internal acids array. Added slot for calc_acid checkbox. Added more water calculations. The miscs amount fields now have two decimal digits. Show treated waters and good/bad indicators.

Fri, 15 Apr 2022 20:20:22 +0200

author
Michiel Broek <mbroek@mbse.eu>
date
Fri, 15 Apr 2022 20:20:22 +0200
changeset 135
e68b27ad8a40
parent 134
5099df8ba6c6
child 136
17030224d919

Added dutch translations to the internal acids array. Added slot for calc_acid checkbox. Added more water calculations. The miscs amount fields now have two decimal digits. Show treated waters and good/bad indicators.

src/EditRecipe.cpp file | annotate | diff | comparison | revisions
src/EditRecipe.h file | annotate | diff | comparison | revisions
src/EditRecipeTab4.cpp file | annotate | diff | comparison | revisions
src/EditRecipeTab7.cpp file | annotate | diff | comparison | revisions
src/MainWindow.cpp file | annotate | diff | comparison | revisions
src/Utils.cpp file | annotate | diff | comparison | revisions
src/Utils.h file | annotate | diff | comparison | revisions
src/global.h file | annotate | diff | comparison | revisions
ui/EditRecipe.ui file | annotate | diff | comparison | revisions
--- a/src/EditRecipe.cpp	Thu Apr 14 22:47:05 2022 +0200
+++ b/src/EditRecipe.cpp	Fri Apr 15 20:20:22 2022 +0200
@@ -50,9 +50,9 @@
     ui->ibu_methodEdit->addItem("Daniels");
 
     for (int i = 0; i < my_acids.size(); i++) {
-	qDebug() << i << my_acids.at(i).name;
-	ui->mw_acidPick->addItem(my_acids.at(i).name);
-	ui->sp_acidtypeEdit->addItem(my_acids.at(i).name);
+	qDebug() << i << my_acids.at(i).name_en;
+	ui->mw_acidPick->addItem(my_acids.at(i).name_en);
+	ui->sp_acidtypeEdit->addItem(my_acids.at(i).name_en);
     }
 
     ui->sp_sourceEdit->addItem(tr("Source 1"));
@@ -595,6 +595,11 @@
     ui->w2_clEdit->setValue(recipe->w2_chloride);
     ui->w2_so4Edit->setValue(recipe->w2_sulfate);
     ui->w2_phEdit->setValue(recipe->w2_ph);
+    ui->mw_autoEdit->setChecked(recipe->calc_acid);
+    ui->mw_phEdit->setReadOnly(! recipe->calc_acid);
+    ui->mw_phEdit->setButtonSymbols(recipe->calc_acid ? QAbstractSpinBox::UpDownArrows : QAbstractSpinBox::NoButtons);
+    ui->mw_acidvolEdit->setReadOnly(recipe->calc_acid);
+    ui->mw_acidvolEdit->setButtonSymbols(recipe->calc_acid ? QAbstractSpinBox::NoButtons : QAbstractSpinBox::UpDownArrows);
 
     ui->sp_volEdit->setValue(recipe->sparge_volume);
     ui->sp_tempEdit->setValue(recipe->sparge_temp);
@@ -647,6 +652,7 @@
     connect(ui->bs_mgcl2Edit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &EditRecipe::on_mgcl2_changed);
     connect(ui->bs_nahco3Edit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &EditRecipe::on_nahco3_changed);
     connect(ui->bs_caco3Edit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &EditRecipe::on_caco3_changed);
+    connect(ui->mw_autoEdit, &QCheckBox::stateChanged, this, &EditRecipe::on_calc_acid_clicked);
 
     ui->saveButton->setEnabled(false);
     ui->deleteButton->setEnabled((id >= 0) ? true:false);
--- a/src/EditRecipe.h	Thu Apr 14 22:47:05 2022 +0200
+++ b/src/EditRecipe.h	Fri Apr 15 20:20:22 2022 +0200
@@ -303,6 +303,7 @@
     void on_mgcl2_changed(double val);
     void on_nahco3_changed(double val);
     void on_caco3_changed(double val);
+    void on_calc_acid_clicked();
 
     void on_perc_mash_valueChanged(int value);
     void on_perc_sugars_valueChanged(int value);
@@ -352,6 +353,12 @@
     void set_brewing_salt(QString salt, double val);
     void calcFermentables();
     void calcIBUs();
+    double ZAlkalinity(double pHZ);
+    double ZRA(double pHZ);
+    double BufferCapacity(Fermentables F);
+    double AcidRequired(double ZpH, Fermentables F);
+    double ProtonDeficit(double pHZ);
+    double MashpH();
     void calcWater();
 };
 
--- a/src/EditRecipeTab4.cpp	Thu Apr 14 22:47:05 2022 +0200
+++ b/src/EditRecipeTab4.cpp	Fri Apr 15 20:20:22 2022 +0200
@@ -85,9 +85,9 @@
         ui->miscsTable->setItem(i, 3, item);
 
 	if (recipe->miscs.at(i).m_amount_is_weight)
-	    item = new QTableWidgetItem(QString("%1 gr").arg(recipe->miscs.at(i).m_amount * 1000.0, 2, 'f', 1, '0'));
+	    item = new QTableWidgetItem(QString("%1 gr").arg(recipe->miscs.at(i).m_amount * 1000.0, 3, 'f', 2, '0'));
 	else
-	    item = new QTableWidgetItem(QString("%1 ml").arg(recipe->miscs.at(i).m_amount * 1000.0, 2, 'f', 1, '0'));
+	    item = new QTableWidgetItem(QString("%1 ml").arg(recipe->miscs.at(i).m_amount * 1000.0, 3, 'f', 2, '0'));
 	item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
         ui->miscsTable->setItem(i, 4, item);
 
--- a/src/EditRecipeTab7.cpp	Thu Apr 14 22:47:05 2022 +0200
+++ b/src/EditRecipeTab7.cpp	Fri Apr 15 20:20:22 2022 +0200
@@ -29,6 +29,118 @@
 }
 
 
+/*
+ * Z alkalinity is the amount of acid (in mEq/l) needed to bring water to the target pH (Z pH)
+ */
+double EditRecipe::ZAlkalinity(double pHZ)
+{
+    double C43 = Utils::Charge(4.3);
+    double Cw = Utils::Charge(recipe->wg_ph);
+    double Cz = Utils::Charge(pHZ);
+    double DeltaCNaught = -C43 + Cw;
+    double CT = recipe->wg_total_alkalinity / 50 / DeltaCNaught;
+    double DeltaCZ = -Cz + Cw;
+    return CT * DeltaCZ;
+}
+
+
+/*
+ * 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).
+ */
+double EditRecipe::ZRA(double pHZ)
+{
+    double Calc = recipe->wg_calcium / (MMCa / 2);
+    double Magn = recipe->wg_magnesium / (MMMg / 2);
+    double Z = ZAlkalinity(pHZ);
+    return Z - (Calc / 3.5 + Magn / 7);
+}
+
+
+double EditRecipe::BufferCapacity(Fermentables F)
+{
+    double C1 = 0;
+
+    if ((F.f_di_ph != 5.7) && ((F.f_acid_to_ph_57 < - 0.1) || (F.f_acid_to_ph_57 > 0.1))) {
+	C1 = F.f_acid_to_ph_57 / (F.f_di_ph - 5.7);
+     } else {
+	/*
+	 * If the acid_to_ph_5.7 is unknown from the maltster, guess the required acid.
+	 */
+	switch (F.f_graintype) {
+	   case 0:					// Base, Special, Kilned
+	   case 3:
+	   case 5:	C1 = 0.014 * F.f_color - 34.192;
+			break;
+	   case 2:	C1 = -0.0597 * F.f_color - 32.457;	// Crystal
+			break;
+	   case 1:	C1 = 0.0107 * F.f_color - 54.768;	// Roast
+			break;
+	   case 4:	C1 = -149;                      // Sour malt
+			break;
+	}
+    }
+    return C1;
+}
+
+
+double EditRecipe::AcidRequired(double ZpH, Fermentables F)
+{
+    double C1 = BufferCapacity(F);
+    double x = F.f_di_ph;
+    return C1 * (ZpH - x);
+}
+
+
+double EditRecipe::ProtonDeficit(double pHZ)
+{
+    double C1, x;
+    int i, error_count = 0;
+    double Result = ZRA(pHZ) * recipe->wg_amount;
+    Fermentables F;
+
+    /*
+     * proton deficit for the grist
+     */
+    if (recipe->fermentables.size()) {
+	for (i = 0; i < recipe->fermentables.size(); i++) {
+	    F = recipe->fermentables.at(i);
+	    if (F.f_added == 0 && F.f_graintype != 6) { // Added == Mash && graintype != No Malt
+		x = AcidRequired(pHZ, F) * F.f_amount;
+		Result += x;
+	    }
+	}
+    } else {
+	error_count++;
+	if (error_count < 5)
+	   qDebug() << "ProtonDeficit" <<  pHZ << "invalid grist, return" << Result;
+    }
+    return Result;
+}
+
+
+double EditRecipe::MashpH()
+{
+    int n = 0;
+    double pH = 5.4;
+    double deltapH = 0.001;
+    double deltapd = 0.1;
+    double pd = ProtonDeficit(pH);
+
+    while (((pd < -deltapd) || (pd > deltapd)) && (n < 2000)) {
+	n++;
+	if (pd < -deltapd)
+	    pH -= deltapH;
+	else if (pd > deltapd)
+	    pH += deltapH;
+	pd = ProtonDeficit(pH);
+    }
+    pH = round(pH * 1000000) / 1000000.0;
+    qDebug() << "MashpH() n:" << n << "pH:" << pH;
+    return pH;
+}
+
+
 void EditRecipe::calcWater()
 {
     double liters = 0;
@@ -40,6 +152,11 @@
     double chloride = 0;
     double sulfate = 0;
     double ph = 0;
+    double TpH = 0;
+    double frac;
+    double protonDeficit = 0;
+    double Acid = 0, Acidmg = 0;
+    int AT;
 
     qDebug() << "calcWater";
 
@@ -73,6 +190,7 @@
     recipe->wg_chloride = round(chloride * 10.0) / 10.0;
     recipe->wg_sulfate = round(sulfate * 10.0) / 10.0;
     recipe->wg_total_alkalinity = round(total_alkalinity * 10.0) / 10.0;
+    recipe->wg_ph = ph;
 
     ui->wg_volEdit->setValue(liters);
     ui->wg_caEdit->setValue(calcium);
@@ -93,6 +211,122 @@
     double wg_sulfate = sulfate;
     double wg_bicarbonate = bicarbonate;
 
+    double mash_ph = MashpH();
+    qDebug() << "Distilled water mash pH:" << mash_ph;
+
+    /* Calculate Salt additions */
+   if (liters > 0) {
+	calcium += ( ui->bs_cacl2Edit->value() * MMCa / MMCaCl2 * 1000 + ui->bs_caso4Edit->value() * MMCa / MMCaSO4 * 1000 +
+		     ui->bs_caco3Edit->value() * MMCa / MMCaCO3 * 1000) / liters;
+	magnesium += (ui->bs_mgso4Edit->value() * MMMg / MMMgSO4 * 1000 + ui->bs_mgcl2Edit->value() * MMMg / MMMgCl2 * 1000) / liters;
+	sodium += (ui->bs_naclEdit->value() * MMNa / MMNaCl * 1000 + ui->bs_nahco3Edit->value() * MMNa / MMNaHCO3 * 1000) / liters;
+	sulfate += (ui->bs_caso4Edit->value() * MMSO4 / MMCaSO4 * 1000 + ui->bs_mgso4Edit->value() * MMSO4 / MMMgSO4 * 1000) / liters;
+	chloride += (2 * ui->bs_cacl2Edit->value() * MMCl / MMCaCl2 * 1000 + ui->bs_naclEdit->value() * MMCl / MMNaCl * 1000 +
+		     ui->bs_mgcl2Edit->value() * MMCl / MMMgCl2 * 1000) / liters;
+	bicarbonate += (ui->bs_nahco3Edit->value() * MMHCO3 / MMNaHCO3 * 1000 + ui->bs_caco3Edit->value() / 3 * MMHCO3 / MMCaCO3 * 1000) / liters;
+    }
+
+    if (recipe->wa_acid_name < 0 || recipe->wa_acid_name >= my_acids.size()) {
+	recipe->wa_acid_name = 0;
+	recipe->wa_acid_perc = my_acids.at(0).AcidPrc;
+	this->ignoreChanges = true;
+	ui->mw_acidPick->setCurrentIndex(0);
+	ui->mw_acidpercEdit->setValue(my_acids.at(0).AcidPrc);
+	this->ignoreChanges = false;
+    }
+    AT = recipe->wa_acid_name;
+
+    /*
+     * Note that the next calculations do not correct the pH change by the added salts.
+     * This pH change is at most 0.1 pH and is a minor difference in Acid amount.
+     */
+    if (recipe->calc_acid) {
+	/*
+	 * Auto calculate the needed acid.
+	 */
+	TpH = recipe->mash_ph;
+	protonDeficit = ProtonDeficit(TpH);
+	qDebug() << "calc_acid tgt:" << TpH << "protonDeficit:" << protonDeficit;
+	if (protonDeficit > 0) {
+	    qDebug() << "pkn:" << AT << my_acids[AT].pK1 << my_acids[AT].pK2 << my_acids[AT].pK3;
+	    frac = Utils::CalcFrac(TpH, my_acids[AT].pK1, my_acids[AT].pK2, my_acids[AT].pK3);
+	    Acid = protonDeficit / frac;
+	    qDebug() << "1" << frac << Acid << protonDeficit;
+	    Acid *= my_acids[AT].MolWt;	// mg.
+	    Acidmg = Acid;
+	    Acid = Acid / my_acids[AT].AcidSG;
+	    Acid = round((Acid / (recipe->wa_acid_perc / 100.0)) * 100.0) / 100.0;
+	    qDebug() << "Mash auto Acid final ml:" << Acid;
+
+	    for (int i = 0; i < recipe->miscs.size(); i++) {
+		qDebug() << i << recipe->miscs.at(i).m_name << my_acids[AT].name_en;
+		if (recipe->miscs.at(i).m_name == my_acids[AT].name_en || recipe->miscs.at(i).m_name == my_acids[AT].name_nl) {
+		    qDebug() << "found at" << i << recipe->miscs.at(i).m_amount << Acid / 1000.0;
+		    recipe->miscs[i].m_amount = Acid / 1000.0;
+		    QTableWidgetItem *item = new QTableWidgetItem(QString("%1 ml").arg(Acid, 3, 'f', 2, '0'));
+        	    item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+        	    ui->miscsTable->setItem(i, 4, item);
+		    this->ignoreChanges = true;
+		    ui->mw_acidvolEdit->setValue(Acid);
+		    this->ignoreChanges = false;
+		    break;
+		}
+	    }
+	    bicarbonate = bicarbonate - protonDeficit * frac / liters;
+	    total_alkalinity = bicarbonate * 50 / 61;
+	}
+	ph = TpH;
+	ui->wb_phEdit->setValue(ph);
+
+	//recipe->est_mash_ph = ph
+    } else { // Manual
+	/*
+	 * Manual adjust acid, calculate resulting pH.
+	 */
+    }
+
+    ui->wb_caEdit->setValue(calcium);
+    ui->wb_mgEdit->setValue(magnesium);
+    ui->wb_hco3Edit->setValue(bicarbonate);
+    ui->wb_caco3Edit->setValue(total_alkalinity);
+    ui->wb_naEdit->setValue(sodium);
+    ui->wb_clEdit->setValue(chloride);
+    ui->wb_so4Edit->setValue(sulfate);
+
+    ui->wb_caEdit->setStyleSheet((calcium < 40 || calcium > 150) ? "background-color: red":"background-color: green");
+    ui->wb_mgEdit->setStyleSheet((magnesium < 5 || magnesium > 40) ? "background-color: red":"background-color: green");
+    ui->wb_naEdit->setStyleSheet((sodium > 150) ? "background-color: red":"background-color: green");
+    /*
+     * Both chloride and sulfate should be above 50 according to
+     * John Palmer. So the Cl/SO4 ratio calculation will work.
+     */
+    ui->wb_clEdit->setStyleSheet((chloride <= 50 || chloride > 150) ? "background-color: red":"background-color: green");
+    ui->wb_so4Edit->setStyleSheet((sulfate <= 50 || sulfate > 400) ? "background-color: red":"background-color: green");
+    /*
+     * (cloride + sulfate) > 500 is too high
+     */
+    if ((chloride + sulfate) > 500) {
+	ui->wb_clEdit->setStyleSheet("background-color: red");
+	ui->wb_so4Edit->setStyleSheet("background-color: red");
+    }
+    ui->wb_phEdit->setStyleSheet((ph < 5.2 || ph > 5.6) ? "background-color: red":"background-color: green");
+    ui->wb_hco3Edit->setStyleSheet((bicarbonate > 250) ? "background-color: red":"background-color: green");
+    ui->wb_caco3Edit->setStyleSheet((bicarbonate > 250) ? "background-color: red":"background-color: green");
+
+    // calcSparge();
+}
+
+
+void EditRecipe::on_calc_acid_clicked()
+{
+    recipe->calc_acid = ! recipe->calc_acid;
+    ui->mw_autoEdit->setChecked(recipe->calc_acid);
+    ui->mw_phEdit->setReadOnly(! recipe->calc_acid);
+    ui->mw_phEdit->setButtonSymbols(recipe->calc_acid ? QAbstractSpinBox::UpDownArrows : QAbstractSpinBox::NoButtons);
+    ui->mw_acidvolEdit->setReadOnly(recipe->calc_acid);
+    ui->mw_acidvolEdit->setButtonSymbols(recipe->calc_acid ? QAbstractSpinBox::NoButtons : QAbstractSpinBox::UpDownArrows);
+    is_changed();
+    calcWater();
 }
 
 
--- a/src/MainWindow.cpp	Thu Apr 14 22:47:05 2022 +0200
+++ b/src/MainWindow.cpp	Fri Apr 15 20:20:22 2022 +0200
@@ -60,37 +60,41 @@
     openWS(useDevelopOption);
 
     Acid a;
-    a.name = "Lactic";
+    a.name_en = "Lactic";
+    a.name_nl = "Melkzuur";
     a.pK1 = 3.86;
-    a.pK2 = 20;
-    a.pK3 = 20;
+    a.pK2 = 20.0;
+    a.pK3 = 20.0;
     a.MolWt = 90.08;
-    a.AcidSG = 1238;
-    a.AcidPrc = 80;
+    a.AcidSG = 1238.0;
+    a.AcidPrc = 80.0;
     my_acids.append(a);
-    a.name = "Hydrochloric";
-    a.pK1 = -7;
-    a.pK2 = 20;
-    a.pK3 = 20;
+    a.name_en = "Hydrochloric";
+    a.name_nl = "Zoutzuur";
+    a.pK1 = -7.0;
+    a.pK2 = 20.0;
+    a.pK3 = 20.0;
     a.MolWt = 36.46;
-    a.AcidSG = 1497;
-    a.AcidPrc = 28;
+    a.AcidSG = 1497.0;
+    a.AcidPrc = 28.0;
     my_acids.append(a);
-    a.name = "Phosphoric";
+    a.name_en = "Phosphoric";
+    a.name_nl = "Fosforzuur";
     a.pK1 = 2.12;
     a.pK2 = 7.20;
     a.pK3 = 12.44;
     a.MolWt = 98.00;
-    a.AcidSG = 1982;
-    a.AcidPrc = 75;
+    a.AcidSG = 1982.0;
+    a.AcidPrc = 75.0;
     my_acids.append(a);
-    a.name = "Sulfuric";
-    a.pK1 = -1;
+    a.name_en = "Sulfuric";
+    a.name_nl = "Zwavelzuur";
+    a.pK1 = -1.0;
     a.pK2 = 1.92;
-    a.pK3 = 20;
+    a.pK3 = 20.0;
     a.MolWt = 98.07;
-    a.AcidSG = 1884;
-    a.AcidPrc = 93;
+    a.AcidSG = 1884.0;
+    a.AcidPrc = 93.0;
     my_acids.append(a);
 
     qDebug() << "acids" << my_acids.size();
--- a/src/Utils.cpp	Thu Apr 14 22:47:05 2022 +0200
+++ b/src/Utils.cpp	Fri Apr 15 20:20:22 2022 +0200
@@ -481,20 +481,34 @@
 
 double Utils::PartCO3(double pH)
 {
-    double H = pow(10, -pH);
-    return 100 * Ka1 * Ka2 / (H * H + H * Ka1 + Ka1 * Ka2);
+    double H = pow(10.0, -pH);
+    return 100.0 * Ka1 * Ka2 / (H * H + H * Ka1 + Ka1 * Ka2);
 }
 
 
 double Utils::PartHCO3(double pH)
 {
-    double H = pow(10, -pH);
-    return 100 * Ka1 * H / (H * H + H * Ka1 + Ka1 * Ka2);
+    double H = pow(10.0, -pH);
+    return 100.0 * Ka1 * H / (H * H + H * Ka1 + Ka1 * Ka2);
 }
 
 
 double Utils::Charge(double pH)
 {
-    return (-2 * PartCO3(pH) - PartHCO3(pH));
+    return (-2.0 * PartCO3(pH) - PartHCO3(pH));
 }
 
+
+double Utils::CalcFrac(double TpH, double pK1, double pK2, double pK3)
+{
+    double r1d = pow(10.0, TpH - pK1);
+    double r2d = pow(10.0, TpH - pK2);
+    double r3d = pow(10.0, TpH - pK3);
+    double dd = 1.0 / (1.0 + r1d + r1d * r2d + r1d * r2d * r3d);
+    double f2d = r1d * dd;
+    double f3d = r1d * r2d * dd;
+    double f4d = r1d * r2d * r3d * dd;
+    return f2d + 2.0 * f3d + 3.0 * f4d;
+}
+
+
--- a/src/Utils.h	Thu Apr 14 22:47:05 2022 +0200
+++ b/src/Utils.h	Fri Apr 15 20:20:22 2022 +0200
@@ -81,6 +81,8 @@
     double PartHCO3(double pH);
 
     double Charge(double pH);
+
+    double CalcFrac(double TpH, double pK1, double pK2, double pK3);
 }
 
 #endif
--- a/src/global.h	Thu Apr 14 22:47:05 2022 +0200
+++ b/src/global.h	Fri Apr 15 20:20:22 2022 +0200
@@ -34,7 +34,8 @@
 
 struct Acid
 {
-    QString     name;
+    QString     name_en;
+    QString	name_nl;
     double      pK1;
     double      pK2;
     double      pK3;
--- a/ui/EditRecipe.ui	Thu Apr 14 22:47:05 2022 +0200
+++ b/ui/EditRecipe.ui	Fri Apr 15 20:20:22 2022 +0200
@@ -3733,8 +3733,14 @@
          <property name="alignment">
           <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
          </property>
+         <property name="readOnly">
+          <bool>true</bool>
+         </property>
+         <property name="buttonSymbols">
+          <enum>QAbstractSpinBox::NoButtons</enum>
+         </property>
          <property name="accelerated">
-          <bool>true</bool>
+          <bool>false</bool>
          </property>
          <property name="suffix">
           <string>%</string>

mercurial