/// <summary> /// write measured variable at time t in given mySubstrate /// (mySubstrates.get(substrate_id)) /// </summary> /// <param name="t">current simulation time</param> /// <param name="mySensors"></param> /// <param name="mySubstrates">changed in this call</param> /// <param name="substrate_id">ID of substrate to be set</param> public static void set_substrate_params_from_sensor(double t, biogas.sensors mySensors, biogas.substrates mySubstrates, string substrate_id) { biogas.substrate mySubstrate = mySubstrates.get(substrate_id); set_substrate_params_from_sensor(t, mySensors, mySubstrate); }
/// <summary> /// Calculate thermal/electrical power needed by heating to compensate heat loss in digester. /// </summary> /// <param name="digester_id">digester id</param> /// <param name="Q">substrate feed measured in m^3/d</param> /// <param name="mySubstrates"></param> /// <param name="T_ambient">ambient temperature</param> /// <param name="mySensors"></param> /// <returns>thermal/electrical energy needed by heating in kWh/d</returns> /// <exception cref="exception">Unknown digester id</exception> /// <exception cref="exception">Q.Length != mySubstrates.Count</exception> /// <exception cref="exception">efficiency is zero, division by zero</exception> public double calcHeatPower(string digester_id, double[] Q, substrates mySubstrates, physValue T_ambient, sensors mySensors) { digester myDigester = get(digester_id); return(myDigester.calcHeatPower(Q, mySubstrates, T_ambient, mySensors)); }
/// <summary> /// Creates and returns a copy of the template /// </summary> /// <param name="template">template of substrates list</param> public substrates(substrates template) { foreach (substrate mySubstrate in template) { addSubstrate(mySubstrate.copy()); } }
/// <summary> /// Calculates the organic loading rate of the digester. /// Assumption that Q and Qsum is measured in m^3/d. /// /// As a reference see: /// /// Handreichung Biogasgewinnung und -nutzung: Grundlagen der anaeroben /// Fermentation, S. 29. /// /// </summary> /// <param name="x">digester state vector</param> /// <param name="mySubstrates"> /// substrates or sludge but not both!!! Because TS content is calculated /// out of COD in digester. If there is a mixture of substrate and sludge going /// into the digester, then the COD is aleardy decreased by the sludge, so no need /// to account for the lower TS of the sludge here. /// TODO: hier sollten es beide sein! /// </param> /// <param name="Q"> /// Q of the substrates or sludge, but not both! /// TODO: hier sollten es beide sein! /// </param> /// <param name="Qsum"> /// if there is sludge and substrate going into the digester, then Qsum will be /// bigger then math.sum(Q). Qsum is needed to account for full volumeflow /// going into the digester no matter if substrate or sludge. /// TODO: nach Änderung, dass Q alle feed enthält, ist Qsum= sum(Q) /// kann also als Parameter entfernt werden /// </param> /// <returns></returns> public physValue calcOLR(double[] x, substrates mySubstrates, double[] Q, double Qsum) { physValue Vliq = this.Vliq; physValue TS; // TODO // was mache ich hier!!!! ??????????????????? // warum nehme ich nicht einfach VS aus den Substraten??? physValue VS;//= calcVS(x, mySubstrates, Q, out TS); mySubstrates.get_weighted_mean_of(Q, "VS", out VS); mySubstrates.get_weighted_mean_of(Q, "TS", out TS); VS = biogas.substrate.convertFrom_TS_To_FM(VS, TS); VS = VS.convertUnit("100 %"); physValue rho; mySubstrates.get_weighted_mean_of(Q, "rho", out rho); // hier wird die Annahme gemacht, dass rho von schlamm gleich ist // wie rho der substrate, aber egal (TODO) // evtl. wäre es besser gemessenes rho aus density_sensor zu nehmen // das ist dichte des inputs in den fermenter (Gemisch aus substrate und rezischlamm) physValue OLR = new physValue(Qsum, "m^3/d") * rho * VS / Vliq; OLR.Symbol = "OLR"; return(OLR); }
/// <summary> /// type 7 /// /// called in ADMstate_stoichiometry.cs -> measure_type7 /// </summary> /// <param name="x">ADM state vector - not used</param> /// <param name="myPlant"></param> /// <param name="mySubstrates">not yet used</param> /// <param name="mySensors">used to get TS inside digester</param> /// <param name="Q">not used</param> /// <param name="par">not used</param> /// <returns>measured values</returns> override protected physValue[] doMeasurement(double[] x, biogas.plant myPlant, biogas.substrates mySubstrates, biogas.sensors mySensors, double[] Q, params double[] par) { // 1st Pel in kWh/d for mixing digester // 2nd Pdissipated in kWh/d for mixing digester, dissipated in digester physValue[] values = new physValue[dimension]; // digester myDigester = myPlant.getDigesterByID(id_suffix); // calc stirrer(s) power for digester in kWh/d values[0] = myDigester.calcStirrerPower(mySensors); // calc stirrer(s) power dissipated to digester in kWh/d values[1] = myDigester.calcStirrerDissipation(mySensors); values[0].Label = "electrical energy of stirrer"; values[1].Label = "thermal energy dissipated by stirrer"; // return(values); }
// ------------------------------------------------------------------------------------- // !!! CONSTRUCTOR METHODS !!! // ------------------------------------------------------------------------------------- /// <summary> /// constructor /// /// creates a new sludge object, which has parameters the same as the mean /// substrate feed (Rf, RP, RL, ADL, VS), the given TS content and /// a density of 1000 kg/m³ /// /// TS measured in % FM /// /// sludge is created out of the weighted mean of the given substrates, /// which should be the substrates fed on the plant. /// /// </summary> /// <param name="mySubstrates">list of substrates</param> /// <param name="Q">must be measured in m³/d</param> /// <param name="TS">must be measured in % FM</param> public sludge(substrates mySubstrates, double[] Q, double TS) : base() { physValue RF; physValue RP; physValue RL; physValue ADL; physValue VS; try { mySubstrates.get_weighted_mean_of(Q, "RF", out RF); mySubstrates.get_weighted_mean_of(Q, "RP", out RP); mySubstrates.get_weighted_mean_of(Q, "RL", out RL); mySubstrates.get_weighted_mean_of(Q, "ADL", out ADL); mySubstrates.get_weighted_mean_of(Q, "VS", out VS); set_params_of("RF", RF.Value, "RP", RP.Value, "RL", RL.Value, "ADL", ADL.Value, "VS", VS.Value); set_params_of("TS", TS); } catch (exception e) { Console.WriteLine(e.Message); // TODO - maybe do something LogError.Log_Err("sludge constructor1", e); } // TODO: could calculate rho here instead of taking 1000 kg/m^3 //set_params_of("rho", new physValue(1000, "kg/m^3")); }
// ------------------------------------------------------------------------------------- // !!! PUBLIC METHODS !!! // ------------------------------------------------------------------------------------- /// <summary> /// Returns measurement at a given time t of a sensor_array /// </summary> /// <param name="mySubstrates">list of substrates that are included in the sum, mean</param> /// <param name="id_sensor_array">id of sensor_array</param> /// <param name="s_operator">operator: mean or sum</param> /// <param name="t">simulation time in days</param> /// <param name="index">index inside sensor: 0, 1, 2, ...</param> /// <param name="noisy">if true, then noisy measurement values are returned. /// they are only noisy if the parameter apply_real_sensor was true before and during /// the simulation</param> /// <returns></returns> /// <exception cref="exception">Unknown sensor array id</exception> /// <exception cref="exception">Invalid index</exception> public double getArrayMeasurementDAt(substrates mySubstrates, string id_sensor_array, string s_operator, double t, int index, bool noisy) { sensor_array mySensorArray = getArray(id_sensor_array); return(mySensorArray.getMeasurementDAt(mySubstrates, s_operator, t, index, noisy)); }
/// <summary> /// Returns mean or sum of measurements at a given time t for the sensor array /// only for substrates, pumps are in the same sensor_array Q, do not include them /// </summary> /// <param name="mySubstrates">list of substrates that are included in the sum, mean</param> /// <param name="s_operator">operator: mean, sum</param> /// <param name="t">simulation time in days</param> /// <param name="index">index inside sensor: 0, 1, 2, ...</param> /// <param name="noisy">if true, then noisy measurement values are returned. /// they are only noisy if the parameter apply_real_sensor was true before and during /// the simulation</param> /// <returns></returns> /// <exception cref="exception">Invalid index</exception> public double getMeasurementDAt(substrates mySubstrates, string s_operator, double t, int index, bool noisy) { List <double> data = new List <double>(); foreach (sensor mySensor in this) { if (mySubstrates.ids.Contains(mySensor.id_suffix)) { data.Add(mySensor.getMeasurementDAt(index, t, noisy)); } } if (s_operator == "sum") { return(math.sum(data)); } else if (s_operator == "mean") { return(math.mean(data)); } else { // TODO - throw error return(0); } }
/// <summary> /// Calculates the thermal energy balance of the digester. It compares /// thermal sinks (negative) with thermal sources (positive) inside the digester /// thermal sinks are: /// - heat substrates /// - radiation /// thermal sources are: /// - microbiology /// - stirrer dissipation /// </summary> /// <param name="digester_id">digester id</param> /// <param name="Q">substrate feed measured in m^3/d</param> /// <param name="mySubstrates"></param> /// <param name="T_ambient">ambient temperature</param> /// <param name="mySensors"></param> /// <returns>thermal energy balance measured in kWh/d</returns> /// <exception cref="exception">Unknown digester id</exception> /// <exception cref="exception">Q.Length != mySubstrates.Count</exception> public double calcThermalEnergyBalance(string digester_id, double[] Q, substrates mySubstrates, physValue T_ambient, sensors mySensors) { digester myDigester = get(digester_id); return(myDigester.calcThermalEnergyBalance(Q, mySubstrates, T_ambient, mySensors)); }
// ------------------------------------------------------------------------------------- // !!! PUBLIC GET METHODS !!! // ------------------------------------------------------------------------------------- /// <summary> /// Returns the ADM params vector, depending on the current substrate feed. /// The current substrate feed is taken out of the current substrate feed /// measurement in mySensors /// /// Attention!!! Changes the values of the ADM params!!! /// /// the following params depend on the substrate feed: /// - XC fractions (fCH_XC, fLI_XC, ...] /// - disintegration constant: kdis /// - hydrolysis constant: khyd_ch, khyd_pr, khyd_li /// </summary> /// <param name="index">1-based index of digester</param> /// <param name="t">current simulation time measured in days</param> /// <param name="mySensors"></param> /// <param name="mySubstrates"></param> /// <param name="substrate_network_digester"></param> /// <returns></returns> /// <exception cref="exception">Invalid digester index</exception> public double[] getADMparams(int index, double t, sensors mySensors, substrates mySubstrates, double[] substrate_network_digester /*, * double deltatime*/) { return(get(index).getADMparams(t, mySensors, mySubstrates, substrate_network_digester)); }
///// <summary> ///// Calculate power needed to comepnsate thermal loss due to ///// radiation through the digesters surface. ///// </summary> ///// <param name="T_ambient">ambient temperature</param> ///// <param name="P_radiation_loss_kW">electrical or thermal power</param> ///// <param name="P_radiation_loss_kWh_d">electrical or thermal energy per day</param> ///// <exception cref="exception">efficiency is zero, division by zero</exception> //public void compensateHeatLossDueToRadiation(physValue T_ambient, // out physValue P_radiation_loss_kW, // out physValue P_radiation_loss_kWh_d) //{ // physValue P_radiation_loss= calcHeatLossDueToRadiation(T_ambient); // heating.compensateHeatLossDueToRadiation(P_radiation_loss, // out P_radiation_loss_kW, // out P_radiation_loss_kWh_d); //} /// <summary> /// Calculates the thermal energy balance of the digester. It compares /// thermal sinks (negative) with thermal sources (positive) inside the digester /// /// At the moment the following processes are reflected: /// /// thermal sinks are: /// /// 1) heat energy needed to heat the substrates up to the digesters temperature /// (heat substrates) /// 2) heat energy loss due to radiation through the surface of the fermenter /// (radiation) /// /// thermal sources are: /// /// 1) microbiology /// 2) stirrer dissipation /// /// For further effects see /// /// 1) Lübken, M., Wichern, M., Schlattmann, M., Gronauer, A., and Horn, H.: /// Modelling the energy balance of an anaerobic digester fed with cattle manure /// and renewable energy crops, Water Research 41, pp. 4085-4096, 2007 /// 2) Lindorfer, H., Kirchmayr, R., Braun, R.: /// Self-heating of anaerobic digesters using energy crops, 2005 /// /// /// </summary> /// <param name="Q">substrate feed measured in m^3/d</param> /// <param name="mySubstrates"></param> /// <param name="T_ambient">ambient temperature</param> /// <param name="mySensors"></param> /// <returns>thermal energy balance mesasured in kWh/d</returns> /// <exception cref="exception">Q.Length != mySubstrates.Count</exception> public double calcThermalEnergyBalance(double[] Q, substrates mySubstrates, physValue T_ambient, sensors mySensors) { physValue Psubsheat, Pradloss, Pmicros, Pstirdiss; return(calcThermalEnergyBalance(Q, mySubstrates, T_ambient, mySensors, out Psubsheat, out Pradloss, out Pmicros, out Pstirdiss)); }
/// <summary> /// Returns the ADM params vector, but the substrate dependent parameters /// are returned as normal means, not weighted means /// </summary> /// <param name="mySubstrates"></param> /// <returns></returns> public double[] getParams(substrates mySubstrates) { double[] Q = new double[mySubstrates.getNumSubstrates()]; for (int isubstrate = 0; isubstrate < mySubstrates.getNumSubstrates(); isubstrate++) { Q[isubstrate] = 1; } return(getParams(0, Q, math.sum(Q), mySubstrates)); // , true }
/// <summary> /// ... /// /// type 9 /// </summary> /// <param name="mySubstrates"></param> /// <param name="mySensors"></param> /// <param name="par">not used</param> /// <returns></returns> override protected physValue[] doMeasurement(biogas.substrates mySubstrates, biogas.sensors mySensors, params double[] par) { physValue[] values = new physValue[1]; // double udot = calcudot(mySensors, mySubstrates); values[0] = new physValue("udot", udot, "-"); return(values); }
/// <summary> /// ... /// /// type 9 /// </summary> /// <param name="mySubstrates"></param> /// <param name="mySensors"></param> /// <param name="par">not used</param> /// <returns></returns> override protected physValue[] doMeasurement(biogas.substrates mySubstrates, biogas.sensors mySensors, params double[] par) { physValue[] values = new physValue[1]; // bool manurebonus = biogas.eeg2009.check_manurebonus(mySubstrates, mySensors); values[0] = new physValue("manurebonus", Convert.ToDouble(manurebonus), "-"); return(values); }
/// <summary> /// measures mean value of given parameter in mySubstrates depending on Q /// /// type 3 /// </summary> /// <param name="x">not used</param> /// <param name="mySubstrates"></param> /// <param name="Q"> /// substrate feed in m^3/d /// doesn't matter if this is substrate mix for plant or /// substrate mix to digester, depends on what we want to have</param> /// <param name="par">not used</param> /// <returns>mean value of given parameter</returns> override protected physValue[] doMeasurement(double[] x, biogas.substrates mySubstrates, double[] Q, params double[] par) { physValue[] values = new physValue[1]; // id_suffix is here the parameter to be measured, see above mySubstrates.get_weighted_sum_of(Q, id_suffix, out values[0]); values[0].Symbol = id_suffix; return(values); }
/// <summary> /// Returns true if given list contains a substrate with the given /// substrate_id /// </summary> /// <param name="mySubstrates">list of substrates</param> /// <param name="substrate_id">substrate id</param> /// <returns>true, if id can be found in substrate list, else false</returns> public static bool contains(substrates mySubstrates, string substrate_id) { foreach (substrate mySubstrate in mySubstrates) { if (mySubstrate.id == substrate_id) { return(true); } } return(false); }
// ------------------------------------------------------------------------------------- // !!! PUBLIC METHODS !!! // ------------------------------------------------------------------------------------- /// <summary> /// Calculates TS content inside the digester. Therefore we calculate the COD /// inside the digester using the state vector x. Furthermore we calculate out /// of the substrates going into the digester the ash and TS content. If /// no substrate is going into the digester we take the sludge and pass it to /// this method. But never mix substrates and sludge!!! /// /// Basic formula is: /// /// Xc,digester= rho_substrate * TS_digester [100 % FM] * /// VS_digester [% TS] / VS_substrate [% TS] * ( ... ) /// /// /// !!!!!!!!!!!!!!!!!!!! ATTENTION !!!!!!!!!!!!!!!!!!!!! /// this function only calculates the total solids in steady state!!! /// because the ash content in the digester is not taken into account, just the ash of the substrate, which is only /// a small part of volume compared with ash in digester. in steady state we assume that ash content in digester is /// the same as the one of the substrate, but in dynamic simulation this is not true at all /// /// As a reference see: /// /// Koch, K., Lübken, M., Gehring, T., Wichern, M., and Horn, H.: /// Biogas from grass silage – Measurements and modeling with ADM1, /// Bioresource Technology 101, pp. 8158-8165, 2010. /// /// </summary> /// <param name="x">digester state vector</param> /// <param name="mySubstrates"> /// substrates or sludge but not both!!! Because TS content is calculated /// out of COD in digester. If there is a mixture of substrate and sludge going /// into the digester, then the COD is already decreased by the sludge, so no need /// to account for the lower TS of the sludge here. /// </param> /// <param name="Q"> /// array of substrate mix stream or recirculations in m³/d /// wie soll das mit recirculations funktionieren? /// Q welches an get_weighted_mean_of übergeben wird, muss mindestens /// anzahl der substrate beinhalten. das funktioniert, weil /// mySubstrates einmal eine liste von schlamm ist, und einmal /// eine liste der substrate /// </param> /// <returns>TS in % FM</returns> /// <exception cref="exception">Q.Length < mySubstrates.Count</exception> /// <exception cref="exception">TS value out of bounds</exception> public static physValue calcTS(double[] x, substrates mySubstrates, double[] Q) { physValue RF; physValue RP; physValue RL; physValue ADL; physValue VS; physValue rho; physValue ash; //physValue TS_substrate; mySubstrates.get_weighted_mean_of(Q, "RF", out RF); mySubstrates.get_weighted_mean_of(Q, "RP", out RP); mySubstrates.get_weighted_mean_of(Q, "RL", out RL); mySubstrates.get_weighted_mean_of(Q, "ADL", out ADL); mySubstrates.get_weighted_mean_of(Q, "VS", out VS); mySubstrates.get_weighted_mean_of(Q, "rho", out rho); mySubstrates.get_weighted_mean_of(Q, "Ash", out ash); //mySubstrates.get_weighted_mean_of(Q, "TS", out TS_substrate); // particulate COD inside the digester physValue COD = biogas.ADMstate.calcVSOfADMstate(x, "kgCOD/m^3"); VS = VS.convertUnit("% TS"); ash = ash.convertUnit("% FM"); // ash of the substrates physValue TS; // we assume that the COD inside the digester is composed as is the substrate mix TS = biogas.substrate.calcTS(RF, RP, RL, ADL, VS, COD, rho);//.convertUnit("100 %"); //VS= biogas.substrate.convertFrom_TS_To_FM(VS, TS_substrate); VS = VS.convertUnit("100 %"); // VS of substrate mix, weiterhin gemessen in 100 % TS // TS_digester [% FM] * VS_substrates [100 % TS] + ash_substrate [% FM] // we know that the ash content of the substrates does not change inisde the digester // TODO: explain a bit better // wenn COD oben 0 wäre, dann wäre TS ebenfalls 0, durch die nächste Zeile // wird TS auf ash content angehoben. TS = TS * VS + ash; TS.Symbol = "TS"; return(TS); }
/// <summary> /// Calculated volatile solids inside digester /// /// called in ADMstate_stoichiometry /// /// scheint mir typ 3 zu sein, ist auch so /// </summary> /// <param name="x">ADM state vector</param> /// <param name="mySubstrates">list of all substrates of plant</param> /// <param name="Q">substrate feed of total plant in m³/d</param> /// <param name="par">not used</param> /// <returns>volatile solids inside digester in % TS</returns> override protected physValue[] doMeasurement(double[] x, biogas.substrates mySubstrates, double[] Q, params double[] par) { physValue[] values = new physValue[1]; // durch diesen Aufruf werden einige Annahmen gemacht // der TS Gehalt in dem Fermenter wird berechnet aus dem COD Gehalt im Fermenter // mit der Annahme, dass die Verteilung von RF, RP, RL, .. in dem Fermenter // so ist wie die der gesamten Substratzufuhr der Anlage, also unabhängig davon // ob fermenter überhaupt gefüttert wird oder nicht // weiterhin wird angenommen, das Asche Gehalt in jedem Fermenter so groß ist // wie der Asche Gehalt der gesamten Substratzufuhr. das ist nur eine Annahme, // könnte auch völlig anders sein. bspw. bei 2 fermentern müsste in jedem Fermenter // Asche der Substrate halbe sein... values[0] = biogas.digester.calcVS(x, mySubstrates, Q); return(values); }
/// <summary> /// calc electrical or thermal power needed to heat the digester /// using the heating, in kWh/d /// </summary> /// <param name="Q">substrate feed measured in m^3/d</param> /// <param name="mySubstrates"></param> /// <param name="T_ambient">ambient temperature</param> /// <param name="mySensors"></param> /// <returns>thermal/electrical energy needed by heating in kWh/d</returns> /// <exception cref="exception">Q.Length != mySubstrates.Count</exception> /// <exception cref="exception">efficiency is zero, division by zero</exception> public double calcHeatPower(double[] Q, substrates mySubstrates, physValue T_ambient, sensors mySensors) { // thermal energy balance in kWh/d double balance = calcThermalEnergyBalance(Q, mySubstrates, T_ambient, mySensors); physValue P_loss_kW, P_loss_kWh_d; if (balance < 0) { heating.compensateHeatLoss(new physValue(-balance, "kWh/d"), out P_loss_kW, out P_loss_kWh_d); return(P_loss_kWh_d.Value); } else { return(0); } }
/// <summary> /// type 7 /// /// called in ADMstate_stoichiometry.cs /// /// unterschied zu typ 7 ist, dass Q hier nur aus Substraten besteht und nicht /// aus zusätzlichem sludge, da sludge nicht aufgewärmt werden muss, /// nein ist jetzt ein Typ 7 sensor, sludge wird ignoriert /// </summary> /// <param name="x">not used</param> /// <param name="myPlant"></param> /// <param name="mySubstrates"></param> /// <param name="mySensors"></param> /// <param name="Q">zufuhr aller substrate in fermenter (nicht in Anlage), /// da heatConsumption_sensor am fermenter angebracht ist</param> /// <param name="par">not used</param> /// <returns>measured values</returns> override protected physValue[] doMeasurement(double[] x, biogas.plant myPlant, biogas.substrates mySubstrates, biogas.sensors mySensors, double[] Q, params double[] par) { // wegen 4 siehe oben // 1. Ptherm kWh/d for heating substrates // 2. Ptherm kWh/d loss due to radiation // 3. Ptherm kWh/d produced by microbiology // 4. Ptherm kWh/d produced by stirrer dissipation physValue[] values = new physValue[dimension]; // TODO - rufe hier calcThermalEnergyBalance auf von digester_energy.cs // benötigt als weiteren Parameter allerdings mySensors digester myDigester = myPlant.getDigesterByID(id_suffix); // Q ist nur das was in fermenter rein geht // bspw. gäbe es bei einem nicht gefütterten aber beheiztem fermenter // keine substrate welche aufgeheizt werden müssten //myDigester.heatInputSubstrates(Q, mySubstrates, out values[0]); myDigester.calcThermalEnergyBalance(Q, mySubstrates, myPlant.Tout, mySensors, out values[0], out values[1], out values[2], out values[3]); // values[0].Label = "thermal energy to heat substrates"; values[1].Label = "thermal energy loss due to radiation"; values[2].Label = "thermal energy produced by microorganisms"; values[3].Label = "thermal energy dissipated by stirrer"; //physValue P_radiation_loss_kW; // because in heating first power in kW is calculated, it does not // make the code faster to not have power in kW as parameter //myPlant.compensateHeatLossDueToRadiation(id_suffix, // out P_radiation_loss_kW, out values[1]); // return(values); }
/// <summary> /// opens the file for reading /// </summary> /// <param name="fileName">name of xml file</param> /// <returns>true on success, else false</returns> private bool openSubstrateFile(String fileName) { // read config from xml file this.Cursor = Cursors.WaitCursor; setStatusMessage(String.Format("Laden der Datei {0}", fileName)); // create substrate object bool success; try { mySubstrates = new biogas.substrates(fileName); success = true; } catch { MessageBox.Show(String.Format("Kann Datei {0} nicht öffnen!", fileName), "Fehler beim einlesen der Substratdatei", MessageBoxButtons.OK, MessageBoxIcon.Error); success = false; } // read out mySubstrates // before this method is called, new is called always, therefore // the gui is empty at this point, correct to call init here success = success && init_gui_with_substrate(mySubstrates); // resetStatusMessage(); this.Cursor = Cursors.Default; return(success); }
/// <summary> /// Calc u prime for substrate feed of all substrates saved in sensors at the /// current time /// </summary> /// <param name="mySensors"></param> /// <param name="mySubstrates"></param> /// <returns>|| u'(t) ||_2^2, where u is the vector of substrate feeds</returns> private double calcudot(biogas.sensors mySensors, biogas.substrates mySubstrates) { double t2 = mySensors.getCurrentTime(); if (t2 < 0) // no recorded value yet in no sensor { return(0); } double t1 = mySensors.getPreviousTime(); double[] Q1, Q2; // TODO // wenn init substrate feed nicht gegeben ist, dann ist am anfang der simulation // Q2 = Q1 // aktuell berechne ich in MATLAb noch init_substrate feed und addiere das am ende // zu udot hinzu, sollte erstmal ok sein mySensors.getMeasurementsAt("Q", "Q", t1, mySubstrates, out Q1); mySensors.getMeasurementsAt("Q", "Q", t2, mySubstrates, out Q2); // double udot = 0; for (int isubstrate = 0; isubstrate < mySubstrates.getNumSubstrates(); isubstrate++) { double u1 = Q1[isubstrate]; double u2 = Q2[isubstrate]; double udot1 = calcudot(t1, t2, u1, u2); udot += udot1 * udot1; } return(udot); }
/// <summary> /// Returns the ADM params vector, depending on the current substrate feed. /// The current substrate feed is taken out of the current substrate feed /// measurement in mySensors /// /// Attention!!! Changes the values of the ADM params!!! /// /// the following params depend on the substrate feed: /// - XC fractions (fCH_XC, fLI_XC, ...] /// - disintegration constant: kdis /// - hydrolysis constant: khyd_ch, khyd_pr, khyd_li /// </summary> /// <param name="t">current simulation time measured in days</param> /// <param name="mySensors"></param> /// <param name="mySubstrates"></param> /// <param name="substrate_network_digester"></param> /// <param name="digesterID">digester id</param> /// <returns></returns> public double[] getParams(double t, sensors mySensors, substrates mySubstrates, double[] substrate_network_digester, String digesterID /*, * double deltatime*/) { double[] Q; mySensors.getMeasurementsAt("Q", "Q", t, mySubstrates, out Q); // multiply the two vectors with the vector product // two column vector are multiplied therefore a column vector is returned Q = math.times(substrate_network_digester, Q); double QdigesterIn; mySensors.getCurrentMeasurementD(String.Format("Q_{0}_2", digesterID), out QdigesterIn); double[] ADMparams = getParams(t, Q, QdigesterIn, mySubstrates); // , mySensors.isEmpty() // measurement of ADM1 params to sensors mySensors.measure(t, "ADMparams_" + digesterID, ADMparams); return(ADMparams); }
/// <summary> /// Calculates Volatile Solids inside the digester. /// We make the assumption, that the ash content inside the digester /// is equal to the ash content of the mean of the substrates going into it. /// /// As the TS content is decreasing in a digester cascade, and the ash /// content keeps constant the VS content will decrease as well. /// </summary> /// <param name="x">digester state vector</param> /// <param name="mySubstrates"> /// substrates or sludge but not both!!! Because TS content is calculated /// out of COD in digester. If there is a mixture of substrate and sludge going /// into the digester, then the COD is already decreased by the sludge, so no need /// to account for the lower TS of the sludge here. /// </param> /// <param name="Q">array of substrate mix stream or recirculations in m³/d</param> /// <param name="TS">returns TS content as well</param> /// <returns></returns> public static physValue calcVS(double[] x, substrates mySubstrates, double[] Q, out physValue TS) { physValue ash; try { TS = calcTS(x, mySubstrates, Q); mySubstrates.get_weighted_mean_of(Q, "Ash", out ash); } catch (exception e) { Console.WriteLine(e.Message); TS = new physValue("TS", 0, "% FM"); return(new physValue("VS", 0, "% TS")); } if (TS.Value == 0) { return(new physValue("VS", 0, "% TS")); } // ash [% FM] := ( 1 - VS [100 % TS] ) * TS [% FM] // // <=> // // VS [100 % TS] = 1 - ( ash [% FM] )/( TS [% FM] ) // physValue VS = new physValue(1, "100 %") - ash / TS; VS = VS.convertUnit("% TS"); VS.Symbol = "VS"; return(VS); }
/// <summary> /// Calculates the thermal energy balance of the digester. It compares /// thermal sinks (negative) with thermal sources (positive) inside the digester /// thermal sinks are: /// - heat substrates /// - radiation /// thermal sources are: /// - microbiology /// - stirrer dissipation /// </summary> /// <param name="digester_id">digester id</param> /// <param name="Q">substrate feed measured in m^3/d</param> /// <param name="mySubstrates"></param> /// <param name="mySensors"></param> /// <returns>thermal energy balance mesasured in kWh/d</returns> public double calcThermalEnergyBalance(string digester_id, double[] Q, substrates mySubstrates, sensors mySensors) { return(myDigesters.calcThermalEnergyBalance(digester_id, Q, mySubstrates, Tout, mySensors)); }
/// <summary> /// Returns the ADM params vector, depending on the current substrate feed /// /// Attention!!! Changes the values of the ADM params!!! /// /// the following params depend on the substrate feed: /// - XC fractions (fCH_XC, fLI_XC, ...] /// - disintegration constant: kdis /// - hydrolysis constant: khyd_ch, khyd_pr, khyd_li /// </summary> /// <param name="t">current simulation time in days</param> /// <param name="Q">substrate feed measured in m^3/d</param> /// <param name="QdigesterIn">total volumetric flow rate in digester in m^3/d</param> /// <param name="mySubstrates"></param> /// <returns></returns> public double[] getParams(double t, double[] Q, double QdigesterIn, substrates mySubstrates) { double fCH_XC, fLI_XC, fPR_XC, fSI_XC, fXI_XC, fXP_XC; // TODO // das Problem wenn fermenter nicht gefüttert wird, also Q am Anfang nur 0er hat, dann // werden alle Substrate in berechnung einbezogen der Parameter // fFactors, km_... // und nicht nur die Substrate, welche zu dem zeitpunkt gefüttert werden. // kdis und khyd haben nicht das problem, da sie mit gewichtet werden, eine // nicht gefütterte anlage arbeitet dann mit defualt werten s.u. // es gibt das problem, dass wenn man simulation in stücke teilt, dass dann adm1 parameter // am ende der simulation in mat datei gespeichert werden müssen. da hier nur am "start" // der simulation, d.h. bei t= 0, die parameter frei gesetzt werden, sonst werden die nur // verändert wenn unten angegebene bedingungen erfüllt sind wie in einer normalen simulation // wenn das speichern von parametern gewünscht ist, muss in matlab die save_ADMparams_to_mat_file // aufgerufen werden nach der simulation // true if we are at start of simulation else false bool start_of_simulation = (t == 0); // get current XC fractions mySubstrates.calcfFactors(Q, out fCH_XC, out fPR_XC, out fLI_XC, out fXI_XC, out fSI_XC, out fXP_XC); // double fSIN_XC = mySubstrates.calcfSIN_Xc(Q); // double kdis = mySubstrates.calcDisintegrationParam(Q); double khyd_ch, khyd_pr, khyd_li; mySubstrates.calcHydrolysisParams(Q, out khyd_ch, out khyd_pr, out khyd_li); double km_c4, km_pro, km_ac, km_h2; mySubstrates.calcMaxUptakeRateParams(Q, out km_c4, out km_pro, out km_ac, out km_h2); double f_ub = 0.2; // set params in params vector if (math.sum(Q) > 0 || QdigesterIn > 0 || start_of_simulation) // TODO: das bedeutet, dass nur gefütterter Fermenter gesetzt wird // ist das gewollt? mit dem || bedeutet, dass Fermenter gefüttert werden muss // egal ob mit substrat oder schlamm { // TODO: es darf hier nicht passieren, dass ein paar Parameter gesetzt werden // und andere nicht, da sonst die Summe == 1 nicht mehr stimmt // else do not set these parameters, use standard parameters instead if (fSI_XC >= 0 && (Math.Abs(_parameters[ADMparams.pos_fSI_XC - 1] - fSI_XC) < f_ub || start_of_simulation)) { _parameters[ADMparams.pos_fSI_XC - 1] = fSI_XC; } // TODO - es bringt eigentlich nichts diesen wert hier zu setzen, da er in ADM1 // stoichiometry aus anderern Parametern berechnet wird if (fXI_XC >= 0 && (Math.Abs(_parameters[ADMparams.pos_fXI_XC - 1] - fXI_XC) < f_ub || start_of_simulation)) { _parameters[ADMparams.pos_fXI_XC - 1] = fXI_XC; } if (fCH_XC >= 0 && (Math.Abs(_parameters[ADMparams.pos_fCH_XC - 1] - fCH_XC) < f_ub || start_of_simulation)) { _parameters[ADMparams.pos_fCH_XC - 1] = fCH_XC; } if (fPR_XC >= 0 && (Math.Abs(_parameters[ADMparams.pos_fPR_XC - 1] - fPR_XC) < f_ub || start_of_simulation)) { _parameters[ADMparams.pos_fPR_XC - 1] = fPR_XC; } if (fLI_XC >= 0 && (Math.Abs(_parameters[ADMparams.pos_fLI_XC - 1] - fLI_XC) < f_ub || start_of_simulation)) { _parameters[ADMparams.pos_fLI_XC - 1] = fLI_XC; } // TODO: fXP_XC kann auch ganz leicht negativ werden, da er durch substraktion // der anderen Parameter von 1 entsteht if (/*fXP_XC >= 0 && */ (Math.Abs(_parameters[ADMparams.pos_fXP_XC - 1] - fXP_XC) < f_ub || start_of_simulation)) { _parameters[ADMparams.pos_fXP_XC - 1] = Math.Max(fXP_XC, 0.0); } if (fSIN_XC >= 0 && (Math.Abs(_parameters[ADMparams.pos_fSIN_XC - 1] - fSIN_XC) < f_ub || start_of_simulation)) { _parameters[ADMparams.pos_fSIN_XC - 1] = fSIN_XC; } if (km_c4 >= 0 && (Math.Abs(_parameters[ADMparams.pos_km_c4 - 1] - km_c4) < 5 || start_of_simulation)) { _parameters[ADMparams.pos_km_c4 - 1] = km_c4; } if (km_pro >= 0 && (Math.Abs(_parameters[ADMparams.pos_km_pro - 1] - km_pro) < 5 || start_of_simulation)) { _parameters[ADMparams.pos_km_pro - 1] = km_pro; } if (km_ac >= 0 && (Math.Abs(_parameters[ADMparams.pos_km_ac - 1] - km_ac) < 5 || start_of_simulation)) { _parameters[ADMparams.pos_km_ac - 1] = km_ac; } if (km_h2 >= 0 && (Math.Abs(_parameters[ADMparams.pos_km_h2 - 1] - km_h2) < 5 || start_of_simulation)) { _parameters[ADMparams.pos_km_h2 - 1] = km_h2; } } // TODO // was besseres überlegen // 150000 stehen für Schlamm, welcher schon abgebaut wurde // kdis hat im Nachgärer kaum Auswirkungen, Xc scheint schon sehr stark abgebaut zu sein // von hauptfermenter double kdis_default = 10.0;// 1000;// 0.25;// 150000; // _parameters[ADMparams.pos_kdis - 1]; double Qsubstrate = math.sum(Q); if (Qsubstrate > 0) // wenn der Fermenter gefüttert wird, dann kdis default Wert kleiner wählen { kdis_default = Math.Max(0.25, kdis); } // TODO : was ist wenn QdigesterIn == 0 ??? kdis = Qsubstrate / QdigesterIn * kdis + (1 - Qsubstrate / QdigesterIn) * kdis_default; // set disintegration constant if (kdis > 0 && (Math.Abs(_parameters[ADMparams.pos_kdis - 1] - kdis) < 1 || start_of_simulation)) { _parameters[ADMparams.pos_kdis - 1] = kdis; } // TODO // was besseres überlegen // 150000 stehen für Schlamm, welcher schon abgebaut wurde double khyd_ch_default = 10; // 150000; // _parameters[ADMparams.pos_khyd_ch - 1]; double khyd_pr_default = 10; // 150000; // _parameters[ADMparams.pos_khyd_pr - 1]; double khyd_li_default = 10; // 150000; // _parameters[ADMparams.pos_khyd_li - 1]; if (Qsubstrate > 0) // wenn der Fermenter gefüttert wird, dann khyd default Werte kleiner wählen { khyd_ch_default = Math.Max(10, khyd_ch); khyd_pr_default = Math.Max(10, khyd_pr); khyd_li_default = Math.Max(10, khyd_li); } khyd_ch = Qsubstrate / QdigesterIn * khyd_ch + (1 - Qsubstrate / QdigesterIn) * khyd_ch_default; khyd_pr = Qsubstrate / QdigesterIn * khyd_pr + (1 - Qsubstrate / QdigesterIn) * khyd_pr_default; khyd_li = Qsubstrate / QdigesterIn * khyd_li + (1 - Qsubstrate / QdigesterIn) * khyd_li_default; // set hydrolysis constants if (khyd_ch > 0 && (Math.Abs(_parameters[ADMparams.pos_khyd_ch - 1] - khyd_ch) < 1 || start_of_simulation)) { _parameters[ADMparams.pos_khyd_ch - 1] = khyd_ch; } if (khyd_pr > 0 && (Math.Abs(_parameters[ADMparams.pos_khyd_pr - 1] - khyd_pr) < 1 || start_of_simulation)) { _parameters[ADMparams.pos_khyd_pr - 1] = khyd_pr; } if (khyd_li > 0 && (Math.Abs(_parameters[ADMparams.pos_khyd_li - 1] - khyd_li) < 1 || start_of_simulation)) { _parameters[ADMparams.pos_khyd_li - 1] = khyd_li; } // return(_parameters); }
/// <summary> /// Calculates Ash content in digester /// </summary> /// <param name="x"></param> /// <param name="digester_id"></param> /// <param name="mySensors"></param> /// <param name="mySubstrates"></param> /// <param name="Q"></param> /// <param name="myPlant"></param> /// <returns></returns> public static physValue calcAsh(double[] x, string digester_id, biogas.sensors mySensors, substrates mySubstrates, double[] Q, plant myPlant) { physValue ash_digester, ash_substrate; try { ash_digester = mySensors.getCurrentMeasurement("Ash_" + digester_id + "_3"); } catch (exception e) { Console.WriteLine(e.Message); ash_digester = new physValue("ash_digester", 11, "% TS"); } try { mySubstrates.get_weighted_mean_of(Q, "Ash", out ash_substrate); } catch (exception e) { ash_substrate = new physValue("ash_substrate", 0, "% FM"); return(ash_digester); } double volume_substrate = math.sum(Q); // m^3/d double volume_dig = myPlant.get_param_of_d("Vliq"); // m^3 // TODO - achtung mit einheiten, mix von % TS und % FM ash_digester = ash_digester + volume_substrate / volume_dig * ash_substrate; ash_digester = ash_digester.convertUnit("% TS"); ash_digester.Symbol = "Ash"; return(ash_digester); }
/// <summary> /// Calculates Volatile Solids inside the digester. /// We make the assumption, that the ash content inside the digester /// is equal to the ash content of the mean of the substrates going into it. /// /// As the TS content is decreasing in a digester cascade, and the ash /// content keeps constant the VS content will decrease as well. /// </summary> /// <param name="x">digester state vector</param> /// <param name="mySubstrates"> /// substrates or sludge but not both!!! Because TS content is calculated /// out of COD in digester. If there is a mixture of substrate and sludge going /// into the digester, then the COD is already decreased by the sludge, so no need /// to account for the lower TS of the sludge here. /// </param> /// <param name="Q">array of substrate mix stream or recirculations in m³/d</param> /// <returns></returns> public static physValue calcVS(double[] x, substrates mySubstrates, double[] Q) { physValue TS; return(calcVS(x, mySubstrates, Q, out TS)); }
/// <summary> /// Calculates the thermal energy balance of the digester. It compares /// thermal sinks (negative) with thermal sources (positive) inside the digester /// /// At the moment the following processes are reflected: /// /// thermal sinks are: /// /// 1) heat energy needed to heat the substrates up to the digesters temperature /// (heat substrates) /// 2) heat energy loss due to radiation through the surface of the fermenter /// (radiation) /// /// thermal sources are: /// /// 1) microbiology /// 2) stirrer dissipation /// /// For further effects see /// /// 1) Lübken, M., Wichern, M., Schlattmann, M., Gronauer, A., and Horn, H.: /// Modelling the energy balance of an anaerobic digester fed with cattle manure /// and renewable energy crops, Water Research 41, pp. 4085-4096, 2007 /// 2) Lindorfer, H., Kirchmayr, R., Braun, R.: /// Self-heating of anaerobic digesters using energy crops, 2005 /// /// /// </summary> /// <param name="Q">substrate feed measured in m^3/d</param> /// <param name="mySubstrates"></param> /// <param name="T_ambient">ambient temperature</param> /// <param name="mySensors"></param> /// <param name="Psubsheat">thermal energy needed to heat substrates in kWh/d</param> /// <param name="Pradloss">thermal energy loss due to radiation in kWh/d</param> /// <param name="Pmicros">Wärme produziert durch Bakterien in kWh/d</param> /// <param name="Pstirdiss">thermal energy created by stirrer in kWh/d</param> /// <returns>thermal energy balance mesasured in kWh/d</returns> /// <exception cref="exception">Q.Length < mySubstrates.Count</exception> /// <exception cref="exception">energy calculations failed</exception> public double calcThermalEnergyBalance(double[] Q, substrates mySubstrates, physValue T_ambient, sensors mySensors, out physValue Psubsheat, out physValue Pradloss, out physValue Pmicros, out physValue Pstirdiss) { //physValue Pel_kWh_d; //physValue Pel_kW; // thermal energy needed to heat substrates in kWh/d try { Psubsheat = mySubstrates.calcSumQuantityOfHeatPerDay(Q, T).convertUnit("kWh/d"); } catch (exception e) { Console.WriteLine(e.Message); throw new exception("calcThermalEnergyBalance: heat substrates failed!"); } //physValue P_radiation_loss_kW; //physValue P_radiation_loss_kWh_d; // energy needed to compensate loss due to radiation //compensateHeatLossDueToRadiation(T_ambient, out P_radiation_loss_kW, out P_radiation_loss_kWh_d); // thermal energy loss due to radiation in kWh/d try { Pradloss = calcHeatLossDueToRadiation(T_ambient).convertUnit("kWh/d"); } catch (exception e) { Console.WriteLine(e.Message); throw new exception("calcThermalEnergyBalance: heat loss calculation failed!"); } // Wärme produziert durch Bakterien // in kWh/d try { Pmicros = mySensors.getCurrentMeasurement("energyProdMicro_" + id); // wenn noch nichts aufgezeichnet wurde, dann ist der Wert 0 if (Pmicros.Value != 0) { Pmicros = Pmicros.convertUnit("kWh/d"); } else // setzte zu 0 kWh/d, da einheit sonst nicht stimmt { Pmicros = new physValue(0, "kWh/d"); } } catch (exception e) { Console.WriteLine(e.Message); throw new exception("calcThermalEnergyBalance: microorganisms failed!"); } // Wärme welche das Rühwerk erzeugt ist eine Wärmequelle, muss hier addiert werden // In Ganzheitliche stoffliche und energetische Modellierung S. 65 // Dissipation Rührwerk - ist identisch mit der aufgebrachten Leistung des // rührwerks. dafür muss erstmal rührwerksleistung berechnet werden, s. ebenfalls // In Ganzheitliche stoffliche und energetische Modellierung S. 45 ff. // für rührwerksleitung muss auch viskosität berechnet werden s. S. 81 für verschiedene // TS im fermenter // thermal energy created by stirrer in kWh/d try { Pstirdiss = calcStirrerDissipation(mySensors).convertUnit("kWh/d"); } catch (exception e) { Console.WriteLine(e.Message); throw new exception("calcThermalEnergyBalance: stirrer dissipation failed!"); } // sinks are negative, themal sources are positive physValue balance = -Psubsheat - Pradloss + Pstirdiss + Pmicros; // + produzierteWärme im Fermenter return(balance.Value); }
/// <summary> /// Measure TS content inside a digester /// /// type 7 /// </summary> /// <param name="x">ADM state vector</param> /// <param name="myPlant"></param> /// <param name="mySubstrates">list of substrates</param> /// <param name="mySensors"></param> /// <param name="Q"> /// substrate feed and recirculation sludge going into the digester /// first values are Q for substrates, then pumped sludge going into digester /// dimension: always number of substrates + number of digesters /// </param> /// <param name="par">not used</param> /// <returns></returns> override protected physValue[] doMeasurement(double[] x, biogas.plant myPlant, biogas.substrates mySubstrates, biogas.sensors mySensors, double[] Q, params double[] par) { // TODO // so erweitern, dass auch TS in Fermenter Input gemessen werden kann // evtl. substrate_sensor nutzen, kann aber auch direkt über mySubstrates gemessen werden physValue[] values = new physValue[1]; // number of substrates int n_substrate = mySubstrates.getNumSubstrates(); if (Q.Length < n_substrate) { throw new exception(String.Format( "Q.Length < n_substrate: {0} < {1}!", Q.Length, n_substrate)); } double[] Qsubstrates = new double[n_substrate]; // volumeflow for substrates for (int isubstrate = 0; isubstrate < n_substrate; isubstrate++) { Qsubstrates[isubstrate] = Q[isubstrate]; } // biogas.substrates substrates_or_sludge; // List <double> Q_s_or_s = new List <double>(); // if no substrate is going into the fermenter we have to take the // TS from the digesters sludge going into this digester to calculate a // sludge. If there is substrate going into the digester we ignore // recirculation sludge, because the COD content inside the digester is // already influenced by the recirculated COD, so a high recirculation leads // to a reduction of the COD content inside the digester and then also to a // TS content reduction. if (math.sum(Qsubstrates) == 0) { substrates_or_sludge = new biogas.substrates(); int ifermenter = 0; // for (int iflux = n_substrate; iflux < Q.Length; iflux++) { string digester_id = myPlant.getDigesterID(ifermenter + 1); double TS_digester = 0; try { mySensors.getCurrentMeasurementD("TS_" + digester_id + "_3", out TS_digester); if (TS_digester < double.Epsilon) // vermutlich wurde noch nicht gemessen? { TS_digester = 11; } } catch // TODO: wann passiert das??, sollte eigentlich nicht passieren { TS_digester = 11; } substrates_or_sludge.addSubstrate( new biogas.sludge(mySubstrates, math.ones(n_substrate), TS_digester)); ifermenter = ifermenter + 1; } // for (int isubstrate = n_substrate; isubstrate < Q.Length; isubstrate++) { Q_s_or_s.Add(Q[isubstrate]); } } else { substrates_or_sludge = mySubstrates; for (int isubstrate = 0; isubstrate < Qsubstrates.Length; isubstrate++) { Q_s_or_s.Add(Qsubstrates[isubstrate]); } } if (id.EndsWith("3")) // out sensor { // calc TS inside digester due to the substrates or the sludge if digester is not fed values[0] = biogas.digester.calcTS(x, substrates_or_sludge, Q_s_or_s.ToArray()); } else if (id.EndsWith("2")) // in sensor { // TODO // ändern, da momentan entweder substrate oder sludge genommen wird und nicht beides // selbe Problem wie bei OLR_sensor denke ich substrates_or_sludge.get_weighted_mean_of(Q_s_or_s.ToArray(), "TS", out values[0]); values[0].Symbol = "TS"; } else { throw new exception(String.Format("id of TS sensor not valid: {0}", id)); } // values[0].Label = "total solids"; return(values); }