/// <summary> /// Calc mix of substrates for all digesters /// /// TODO: s. auch in funktion unten /// </summary> /// <param name="t">current simulation time in days</param> /// <param name="mySubstrates"></param> /// <param name="myPlant"></param> /// <param name="substrate_network"></param> /// <param name="mySensors"></param> /// <param name="Q">only used if datasource_type == extern</param> /// <param name="dilution_rates">double vector with the wanted dilution rates for each digester. /// Could be given by a dilution rate control. The size of the vector must /// be equal to number of digesters.</param> /// <returns>[34dim stream for digester1; 34dim stream for digester2; ...]</returns> public static double[] calcADMstreamMix(double t, biogas.substrates mySubstrates, biogas.plant myPlant, double[,] substrate_network, biogas.sensors mySensors, double[] Q, //double deltatime, double[] dilution_rates) { double[,] myStreams = new double[biogas.ADMstate._dim_stream, myPlant.getNumDigesters()]; for (int idigester = 0; idigester < myPlant.getNumDigesters(); idigester++) { double Vliq = myPlant.getDigesterParam(idigester + 1, "Vliq"); // D ist < 0, wenn Anlage nicht geregelt wird double D = dilution_rates[idigester]; double Q_new = D * Vliq; myStreams = math.insertColumn(myStreams, calcADMstreamMixForDigester(t, mySubstrates, substrate_network, mySensors, idigester, Q, Q_new), 0, idigester); // measure energy needed to transport substrates biogas.substrate_transport.run(t, mySensors, "substratemix", myPlant.getDigesterID(idigester + 1), myPlant, mySubstrates, substrate_network); } double[] myStream = math.concat(myStreams); return(myStream); }
/// <summary> /// Calculation of total thermal energy consumption /// /// returns thermal energy consumption of /// - heating /// - microbiology (production) /// - mixer (production) /// /// in kWh/d /// </summary> /// <param name="myPlant"></param> /// <param name="mySensors"></param> /// <param name="energyConsumptionHeat">thermal energy consumption of heat losses [kWh/d]</param> /// <param name="energyProdMixer">th. energy prod. of stirrer [kWh/d]</param> /// <param name="energyProdMicro">th. energy prod. by micros [kWh/d]</param> /// <returns></returns> private static double getThermalEnergyConsumption(biogas.plant myPlant, biogas.sensors mySensors, out double energyConsumptionHeat, out double energyProdMixer, out double energyProdMicro) { double energyConsumption = 0; energyConsumptionHeat = 0; energyProdMixer = 0; // heat energy dissipated to digester in kWh/d energyProdMicro = 0; // int n_digester = myPlant.getNumDigesters(); for (int idigester = 0; idigester < n_digester; idigester++) { string digester_id = myPlant.getDigesterID(idigester + 1); biogas.digester myDigester = myPlant.getDigester(idigester + 1); // heating physValue[] heat_v = mySensors.getCurrentMeasurementVector("heatConsumption_" + digester_id); // inflow heating // thermal Energie welche benötigt wird um das Substrat aufzuheizen // \frac{kWh}{d} energyConsumptionHeat += heat_v[0].Value; // radiation loss energy // thermal Energie, welche die Heizungen benötigen um die // Wärmeverluste auszugleichen // \frac{kWh}{d} energyConsumptionHeat += heat_v[1].Value; // produced heat by bacteria in digester [kWh/d] // das ist eine thermische, keine elektrische energie energyProdMicro += heat_v[2].Value; // thermal energy production by stirrer through dissipation // \frac{kWh}{d} energyProdMixer += heat_v[3].Value; } // sum in kWh/d // Vorsicht: energy von bakterien wird als produktion nicht als verbrauch // angesehen, deshalb hier neg. VZ energyConsumption = energyConsumptionHeat - energyProdMicro - energyProdMixer; return(energyConsumption); }
// ------------------------------------------------------------------------------------- // !!! PUBLIC METHODS !!! // ------------------------------------------------------------------------------------- /// <summary> /// get all objectives /// </summary> /// <param name="mySensors"></param> /// <param name="myPlant"></param> /// <param name="mySubstrates"></param> /// <param name="myFitnessParams"></param> /// <param name="Stability_punishment"></param> /// <param name="energyBalance">cost - benefit [1000 €/d]</param> /// <param name="energyProd_fitness"></param> /// <param name="energyConsumption">total el. energy consumption [kWh/d]</param> /// <param name="energyThConsumptionHeat">thermal energy consumption [kWh/d]</param> /// <param name="energyConsumptionPump">el. energy consumption for pumps [kWh/d]</param> /// <param name="energyConsumptionMixer">el. energy consumption for mixer [kWh/d]</param> /// <param name="energyProdMicro">thermal energy prod. microbiology [kWh/d]</param> /// <param name="moneyEnergy"> /// money I get for selling the produced total energy (el. + therm.) in €/d /// </param> /// <param name="fitness_constraints">sum of fitness functions</param> /// <param name="fitness">fitness vector</param> public static void getObjectives(biogas.sensors mySensors, biogas.plant myPlant, biogas.substrates mySubstrates, fitness_params myFitnessParams, /*out double SS_COD_fitness, out double VS_COD_fitness,*/ /*out double SS_COD_degradationRate, out double VS_COD_degradationRate, */ /*out double CH4_fitness, */ out double Stability_punishment, out double energyBalance, out double energyProd_fitness, out double energyConsumption, out double energyThConsumptionHeat, out double energyConsumptionPump, out double energyConsumptionMixer, out double energyProdMicro, /*out double energyThermProduction, * out double energyProduction,*/out double moneyEnergy, out double fitness_constraints, out double[] fitness) { double pHvalue_fitness, VFA_TAC_fitness, TS_fitness, VFA_fitness, AcVsPro_fitness, TAC_fitness, OLR_fitness, HRT_fitness, N_fitness, biogasExcess_fitness, diff_setpoints, CH4_fitness, SS_COD_fitness, VS_COD_fitness; // // normalized between 0 and 1 mySensors.getCurrentMeasurementD("SS_COD_fit", out SS_COD_fitness); // normalized between 0 and 1 mySensors.getCurrentMeasurementD("VS_COD_fit", out VS_COD_fitness); // fitness > 0 if pH value under or over boundaries // normalized between 0 and 1 mySensors.getCurrentMeasurementD("pH_fit", out pHvalue_fitness); // da mit tukey gearbeitet wird, kann der term auch etwas größer als 1 sein mySensors.getCurrentMeasurementD("VFA_TAC_fit", out VFA_TAC_fitness); // this is the fitness of the TS in the digester // da mit tukey gearbeitet wird, kann der term auch etwas größer als 1 sein mySensors.getCurrentMeasurementD("TS_fit", out TS_fitness); // TODO // Calculation of TS concentration in inflow // gibt es auch schon in Individuum Überprüfung: nonlcon_substrate // braucht hier dann eigentlich nicht mehr gemacht werden // verhältnis von propionic acid to acetic acid // max. grenze bei 1.4, s. PhD für Quellen // hier ist der Kehrwert, also min grenze, hier wird mit tukey gearbeitet mySensors.getCurrentMeasurementD("AcVsPro_fit", out AcVsPro_fitness); // da mit tukey gearbeitet wird, kann der term auch etwas größer als 1 sein mySensors.getCurrentMeasurementD("VFA_fit", out VFA_fitness); // tukey mySensors.getCurrentMeasurementD("TAC_fit", out TAC_fitness); // tukey mySensors.getCurrentMeasurementD("OLR_fit", out OLR_fitness); // da mit tukey gearbeitet wird, kann der term auch etwas größer als 1 sein mySensors.getCurrentMeasurementD("HRT_fit", out HRT_fitness); // sum of Snh4 + Snh3, mit tukey mySensors.getCurrentMeasurementD("N_fit", out N_fitness); // CH4 > 50 % als tukey implementiert mySensors.getCurrentMeasurementD("CH4_fit", out CH4_fitness); // biogasExcess_fitness is lossbiogasExcess / 1000 // measured in tausend € / d mySensors.getCurrentMeasurementD("gasexcess_fit", out biogasExcess_fitness); // TODO // // calculate OLR and HRT of plant // TODO - ich könnte auch faecal_fit_sensor schreiben // faecal bacteria removal capacity // intestinal enterococci // faecal coliforms double etaIE = 0, etaFC = 0; for (int idigester = 0; idigester < myPlant.getNumDigesters(); idigester++) { string digesterID = myPlant.getDigesterID(idigester + 1); etaIE += mySensors.getCurrentMeasurementDind("faecal_" + digesterID, 0); etaFC += mySensors.getCurrentMeasurementDind("faecal_" + digesterID, 1); } if (myPlant.getNumDigesters() > 0) { etaIE /= myPlant.getNumDigesters(); etaFC /= myPlant.getNumDigesters(); } // TODO - als ausgabeargumente definieren - nö double fitness_etaIE, fitness_etaFC; // wird in 100 % gemessen fitness_etaIE = 1.0f - etaIE / 100.0f; fitness_etaFC = 1.0f - etaFC / 100.0f; // TODO // stability //stateIsStable(1:n_fermenter,1)= 0; //for ifermenter= 1:n_fermenter // %digester_id= char( plant.getDigesterID(ifermenter) ); // %% TODO !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // stateIsStable(ifermenter,1)= 1;%... // %getStateIsStable(measurements, digester_id, plant); //end //% d.h instabil? //if any(stateIsStable == 0) // Stability_punishment= 1; //else // Stability_punishment= 0; //end Stability_punishment = 0; // TODO // double mbonus; mySensors.getCurrentMeasurementD("manurebonus", out mbonus); myFitnessParams.manurebonus = Convert.ToBoolean(mbonus); // energyConsumption = getElEnergyConsumption(myPlant, mySensors, out energyConsumptionPump, out energyConsumptionMixer); // double energyThProdMixer; double energyThConsumption = getThermalEnergyConsumption(myPlant, mySensors, out energyThConsumptionHeat, out energyThProdMixer, out energyProdMicro); // TODO: einheiten nicht sauber // costs for heating in €/d // // kosten für heizung werden direkt in geld umgerechnet // wenn thermisch produzierte wärme zum heizen des fermenters benutzt wird, // dann werden hier virtuelle kosten berechnet mit kosten revenueTherm, // welche unten bei sellEnergy wieder als erlös mit dem gleichen Wert // berechnet werden, d.h. +/- das gleiche. // double costs_heating = myPlant.calcCostsForHeating_Total( new physValue(energyThConsumption, "kWh/d"), myPlant.myFinances.revenueTherm.Value, myPlant.myFinances.priceElEnergy.Value); // // total el. energy production in kWh/d double energyProduction = mySensors.getCurrentMeasurementDind("energyProdSum", 0); // total thermal energy production in kWh/d double energyThermProduction = mySensors.getCurrentMeasurementDind("energyProdSum", 1); //energyProduction= getEnergyProduction(myPlant, mySensors, out energyThermProduction); // double energyProductionMax = getMaxElEnergyProduction(myPlant); // measured in 100 % energyProd_fitness = (1 - energyProduction / energyProductionMax); // Calculation of costs of substrate inflow // € / d double substrate_costs; mySensors.getCurrentMeasurementD("substrate_cost", out substrate_costs); // double udot; mySensors.getCurrentMeasurementD("udot", out udot); // // TODO // was ist wenn wir weniger thermische energie im BHKW erzeugen als wir verbrauchen? // dann ist costs_heating (virt. kosten) > moneyEnergy thermisch (verkauf von thermischer Energie) // die differenz muss dann elektrisch erzeugt werden, wird allerdings nicht gemacht. // die differenz wird aktuell alas virtuelle Kosten verbucht (Verlust den man hat da man nicht wärme verkauft) // und nicht als reale kosten (erzeugungskosten: thermische energie erzeugt durch heizung) // um das zu lösen, warum ruft man nicht berechnung von costs_heating nach berechnung // von energyThermProduction auf und übergibt dann differenz zw. energyThConsumption und // energyThermProduction? // // TODO - was ist wenn die produzierte elektrische energie von niemanden abgenommen wird // das ist der fall, wenn nach sollwert gefahren wird, dann wird nur so viel energie bezahlt // wie nach sollwert verlangt wurde, das geht so ab eeg 2012 - direktvermarktung // must be in kWh/d double energyElSold = 0; // electrical energy that would be sold // dann gibt es eine referenz kurve welche angibt wieviel energie verkauft würde wenn sie produziert // würde, hier nur elektrische energie if (mySensors.exist("ref_energy_sold")) { // wichtig, dass man sich die messung zur aktuellen zeit holt, da // ref_energy_sold eine referenz vorgibt double time = mySensors.getCurrentTime(); energyElSold = mySensors.getMeasurementDAt("ref_energy_sold", "", time, 0, false); energyElSold = Math.Min(energyElSold, energyProduction); } else { energyElSold = energyProduction; } // moneyEnergy : €/d moneyEnergy = biogas.gasexcess_fit_sensor.sellEnergy(energyElSold, energyThermProduction, myPlant, myFitnessParams); // € / d // is negative when we make more money as we have to pay energyBalance = energyConsumption * myPlant.myFinances.priceElEnergy.Value + costs_heating - moneyEnergy + substrate_costs; // tausend € / d energyBalance = energyBalance / 1000; // // TODO bool noisy = false; // calc setpoint control error //diff_setpoints = calc_setpoint_errors(mySensors, myPlant, myFitnessParams, noisy); mySensors.getCurrentMeasurementD("setpoint_fit", noisy, out diff_setpoints); // calc total fitness of all the constraints fitness_constraints = calcFitnessConstraints(myFitnessParams, SS_COD_fitness, VS_COD_fitness, /*VS_COD_degradationRate,*/ pHvalue_fitness, VFA_TAC_fitness, TS_fitness, VFA_fitness, AcVsPro_fitness, TAC_fitness, OLR_fitness, HRT_fitness, N_fitness, CH4_fitness, biogasExcess_fitness, Stability_punishment, energyProd_fitness, fitness_etaIE, fitness_etaFC, diff_setpoints); // calc fitness vector fitness = calcFitnessVector(myFitnessParams, energyBalance, fitness_constraints, udot); }
// ------------------------------------------------------------------------------------- // !!! PRIVATE METHODS !!! // ------------------------------------------------------------------------------------- /// <summary> /// Calculation of total electrical energy consumption /// /// returns energy consumption of /// - pumps /// - mixer /// /// in kWh/d /// </summary> /// <param name="myPlant"></param> /// <param name="mySensors"></param> /// <param name="energyConsumptionPump">el. energy consumption of pumps [kWh/d]</param> /// <param name="energyConsumptionMixer">el. energy consumption of stirrer [kWh/d]</param> /// <returns></returns> private static double getElEnergyConsumption(biogas.plant myPlant, biogas.sensors mySensors, out double energyConsumptionPump, out double energyConsumptionMixer) { double energyConsumption = 0; energyConsumptionPump = 0; energyConsumptionMixer = 0; // int n_digester = myPlant.getNumDigesters(); for (int idigester = 0; idigester < n_digester; idigester++) { string digester_id = myPlant.getDigesterID(idigester + 1); // Idee: zu energieverbrauch gehören auch Rührwerke, Rührwerksleistung soll // von TS im Fermenter abhängig sein. // folgende Formel: // Energieverbrauch [kWh/d] = V_fermenter * 1 kWh/(d * 100 m³) * TS [%] // // frei nach der Quelle: // Empfehlung für die Auswahl von Rührwerken... // mixer //double V_digester= myPlant.getDigesterParam(digester_id, "Vliq"); //double TS= mySensors.getCurrentMeasurement("TS" + "_" + digester_id + "_3").Value; double Pel_mixer; physValue[] e_mixer = mySensors.getCurrentMeasurementVector("stirrer_" + digester_id); Pel_mixer = e_mixer[0].Value; energyConsumptionMixer += Pel_mixer;//V_digester / 100 * TS; } // pumps for (int ipump = 0; ipump < myPlant.getNumPumps(); ipump++) { string pump_id = myPlant.getPumpID(ipump + 1); double pump_energy; mySensors.getCurrentMeasurementD("pumpEnergy_" + pump_id, out pump_energy); // pump energy per day // P(t) [kWh/d] energyConsumptionPump += pump_energy; } // substrate_transport for (int isubstrate_transport = 0; isubstrate_transport < myPlant.getNumSubstrateTransports(); isubstrate_transport++) { string substrate_transport_id = myPlant.getSubstrateTransportID(isubstrate_transport + 1); double pump_energy; mySensors.getCurrentMeasurementD("pumpEnergy_" + substrate_transport_id, out pump_energy); // pump energy per day // P(t) [kWh/d] energyConsumptionPump += pump_energy; mySensors.getCurrentMeasurementD("transportEnergy_" + substrate_transport_id, out pump_energy); // pump energy per day // P(t) [kWh/d] energyConsumptionPump += pump_energy; } // sum in kWh/d // Vorsicht: energy von bakterien wird als produktion nicht als verbrauch // angesehen, deshalb hier neg. VZ energyConsumption = energyConsumptionPump + energyConsumptionMixer; return(energyConsumption); }
/// <summary> /// Calculate fitness value of digester measurements which must be between min and/or max /// </summary> /// <param name="myPlant"></param> /// <param name="mySensors"></param> /// <param name="var_id">"pH", "TS", ...</param> /// <param name="min_max">"min", "max" or "min_max"</param> /// <param name="myFitnessParams"></param> /// <param name="append_var_id">"_2" or "_3"</param> /// <param name="use_tukey">if true, then tukey function is used, leads to /// that returned value can be > 1. if false, then the fitness value /// is between 0 and 1.</param> /// <returns></returns> public static double calcFitnessDigester_min_max(biogas.plant myPlant, biogas.sensors mySensors, string var_id, string min_max, fitness_params myFitnessParams, string append_var_id, bool use_tukey) { double fitness = 0; int n_digester = myPlant.getNumDigesters(); for (int idigester = 0; idigester < n_digester; idigester++) { string digester_id = myPlant.getDigesterID(idigester + 1); double variable; mySensors.getCurrentMeasurementD(var_id + "_" + digester_id + append_var_id, out variable); // double punish_digester = 0; if ((min_max == "min") || (min_max == "min_max")) { double min_bound = myFitnessParams.get_param_of(var_id + "_min", idigester); if (!use_tukey) { punish_digester = Convert.ToDouble(variable < min_bound); } else { // if a boundary is violated then the penalty is at least 1, and bounded // by tukey biweight rho function // TODO : warum wollte ich das 1 + ... haben??? das macht die sache // nicht stetig, was sehr schlecht ist für skalierung und auch // für kriging punish_digester = Convert.ToDouble(variable < min_bound) * (0 * 1 + math.tukeybiweight(variable - min_bound)); } } if ((min_max == "max") || (min_max == "min_max")) { double max_bound = myFitnessParams.get_param_of(var_id + "_max", idigester); if (!use_tukey) { punish_digester = Math.Max(punish_digester, Convert.ToDouble(variable > max_bound)); } else { // if a boundary is violated then the penalty is at least 1, and bounded // by tukey biweight rho function // TODO : warum wollte ich das 1 + ... haben??? das macht die sache // nicht stetig, was sehr schlecht ist für skalierung und auch // für kriging punish_digester = Math.Max(punish_digester, Convert.ToDouble(variable > max_bound) * (0 * 1 + math.tukeybiweight(variable - max_bound))); } } fitness += punish_digester; } // values between 0 and 1, but if tukey function used, then may also be > 1 fitness = fitness / n_digester; return(fitness); }
/// <summary> /// Calculates the slduge input of the given digester. It checks for each digester /// whether it is connected to the input of the given digester. If so the flow /// coming from the digester is returned in the vector at the corresponding position. /// </summary> /// <param name="t">some simulation time in days</param> /// <param name="mySubstrates"></param> /// <param name="myPlant"></param> /// <param name="mySensors"></param> /// <param name="substrate_network"></param> /// <param name="plant_network"></param> /// <param name="digester_id"> /// digester for which the sludge input is calculated /// </param> /// <returns>dimension: number of digesters</returns> private physValue[] getPumpedInputFlowForFermenter(double t, biogas.substrates mySubstrates, biogas.plant myPlant, sensors mySensors, double[,] substrate_network, double[,] plant_network, string digester_id) { int digester_index = myPlant.getDigesterIndex(digester_id) - 1; // vector tells us what input connections the given fermenter has double[] digester_network = new double[myPlant.getNumDigesters()]; for (int idigester = 0; idigester < plant_network.GetLength(0); idigester++) { digester_network[idigester] = plant_network[idigester, digester_index]; } string digester_id_in = digester_id;// myPlant.getDigesterID(digester_index + 1); int n_digester = myPlant.getNumDigesters(); physValue[] Q = physValue.zeros(n_digester); // for (int idigester = 0; idigester < n_digester; idigester++) { if ((digester_network[idigester] > 0) && (digester_index != idigester)) { string digester_id_out = myPlant.getDigesterID(idigester + 1); string digester_conn = digester_id_out + "_" + digester_id_in; try { // wenn wir eine pumpverbindung mit einem split haben, dann gibt es diese // messung, weil diese aus datei geholt wird: volumeflow_...mat Q[idigester] = mySensors.getMeasurementAt("Q", "Q_" + digester_conn, t); } catch { // macht hier die Annahme, dass das was in fermenter_id_out rein geht // auch mengenmaäßig raus geht und dann in fermenter_id_in rein geht. // diee annahme geht nur, wenn volumen constant ist. // TODO: und biogasstrom vernachlässigt wird //Q[idigester]= //physValue.sum(getInputVolumeflowForFermenter( // t, mySubstrates, myPlant, mySensors, // substrate_network, plant_network, digester_id_out)); // das sollte so viel einfacher möglich sein, und vor allem auch korrekt Q[idigester] = mySensors.getMeasurementAt("Q_" + digester_id_out + "_3", "", t); } } else { Q[idigester] = new physValue(0, "m^3/d"); } } // return(Q); }