/* $Id: OCV_corr.cpp,v 1.6 2025/11/27 09:51:32 sqko Exp $ */
#include <iostream>
#include "gas.hpp"
#include <stdio.h>
#include <limits>
#include <utility>
#include <map>
#include <string>
#include <math.h>

using namespace std;
const string VERSION = "2.0.4";

enum mycmd {def, all , par_all, asr_fu, h_wo, h_wi, ny, ou, fu_tot, fu, asr, po2o, po2i, cath, ocv, demf, emfn, emf1, fui, pleak};
map <string, string> arguments;
map <string, bool> arguments_single;
typedef map <string, string>::const_iterator argkey;
typedef map <string, bool>::const_iterator argkey_single;

const double Mincurrent = 0.05;
const double T0 = 273.15;
const double DG0 = -242.55;
const double DG1 = 4.4909E-2;
const double DG2 = 4.8612E-6;
const double DH0 = -238.43;
const double DH1 = 1.2725E-2;
const double DH2 = 3.1588E-6;
const double NaN = numeric_limits<float>::quiet_NaN();

gcomp comp;
double wtemp = T0;
double temp = 850 + T0;
double Tleak = temp;
double P = 1.0;
double emf = NaN;
double emf_theory = emf;
double emf_after = emf;
double emf_av1 = emf;
double U = NaN;
int n = 20;
double A = 1.0;
double I = 0;
bool use_ch4 = false;
bool valid = false;
bool dry = false;
// output variables set default to not a number
double Rsec = NaN;
double ASR = NaN;
double power = NaN;
double leak = NaN;
double leakP = NaN;
double FU = NaN;
double FU_total = NaN;
double OU = NaN;
double Ny = NaN;
double Xt = NaN;
double Xm = NaN;
double Xo = NaN;

int load_arguments(int,char**);
void out_par_all();
void print_help();
void output();
mycmd command = def;
gas initial(Tleak,P,comp);
gas start(initial);
gas endc(start);

int main(int argc, char** argv)
{
  if (load_arguments(argc,argv) == 0)
    return 0;	
  power = -U * I / A;
  if (I != 0)
    Rsec = -(emf - U) * A / I;
  if (command == par_all && temp <= T0 )
    {
      out_par_all();
      return 0;
    }
  try {	
    gas initial_dummy(Tleak,P,comp);
    initial = initial_dummy;
    gcomp FUcomp = initial.composition();
    initial.react_gasses();
    // set emf to theoretical if no emf was specified
    if (isnan(emf))
      {
	emf = initial.emf();
	if (I != 0)
	  Rsec = -(emf - U) * A / I;
      }
    emf_theory = initial.emf();
    // calculate gas leak
    leak = initial.get_leak(emf);
    leakP = leak / (gas::F * 2 * initial.composition().H2);
    Xt = initial.Xgas(H2O);
    // set start gas composition (defined from gas leak)
    start = initial;
    start.newT(temp);
    start.current(leak,skip_cathode);
    gcomp startcomp = start.composition();
    Xm = start.Xgas(H2O);
    // calculate end gas composition
    endc = start;
    // check that enough gas exsist for conversion
    if (I > endc.max_current() || I < endc.min_current())
      {
	if (command != par_all)
	  cerr << "Not enough gas for conversion!" << endl;
	output();
	return 0;	
      }	
    endc.current(I);
    emf_after = endc.emf();
    gcomp endcomp = endc.composition();
    Xo = endc.Xgas(H2O);
    // calculate gas utilisation
    if (I > 0)
      FU = I / (2 * gas::F * (startcomp.H2 + startcomp.CO));
    else if (I < 0)
      FU = -I / (2 * gas::F * (startcomp.CO2 + startcomp.H2O));
    else
      FU = 0;
    OU = (startcomp.O2_cathode - endcomp.O2_cathode) / startcomp.O2_cathode;
    FU_total = (I / (2 * gas::F) + 2 * FUcomp.O2_anode + 2 * leak / gas::F) / (FUcomp.H2 + FUcomp.CO);
    // First order fuel utilisation correction;
    gas segment(start);
    segment.current(I/2);
    emf_av1 = segment.emf();
    if (I != 0)
      ASR = -(emf_av1 - U) * A / I;
    /*
      Real correction
      Based on Solid Oxide fuel cell testing: Results and Interpretation:
      Mogens Mogensen et.al. Electrochemical society Procedings, Vol 99-19
      p 904 - 915
    */
    double sum = 0.0;
    try
      {
	for (int i = 0; i != n; ++i)
	  {
	    double segment_current = (i + 0.5) * I / n;
	    segment = start;
	    segment.current(segment_current);
	    sum -= 1 / (segment.emf() - U);
	  }
      }
    catch (domain_error const &e)
      {
	if (command != par_all)
	  {
	    cerr << "Real fuel utilisation correction algorithm could not be completed!" << endl;
	    cerr << "reason: '" << e.what() << "'" << endl;
	    cerr << "Using 1'st order correction instead!" << endl;
	  }
	// do nothing and use 1's order approximation instead	
      }
    catch (char* string)
      {
	// Something serious happened here!
	cerr << "Aborting program due to unhandled error in fuel utilisation routine: " << string << endl;
	output();
	return 1;	
      }
    if (I != 0)
      ASR = n * A / (I * sum);
    // do efficiency calculation
    double H = DH0 + DH1 * temp + DH2 * temp * temp;
    double G = DG0 + DG1 * temp + DG2 * temp * temp;  
    if (!use_ch4 && (H * emf) != 0)
      {
	Ny = G * U * FU / (H * emf);	
      }
    output();
  }
  catch (domain_error const &e)
    {
      cerr << "Program aborted prematurely!" << endl;
      cerr << "reason: '" << e.what() << "'" << endl;
      output();
      return 1;
    }
  catch (char* string)
    {
      // Something serious happened here!
      cerr << "Aborting program due to unhandled errror" << string << endl;
      output();
      return 1;	
    }
  return 0;
}

void output()
{
  // Output of calculation parameters
  switch(command) 
    {
    case par_all:
      out_par_all();
      break;
    case asr_fu:
      cout << ASR << "  " << FU << "  " << Xt << "  " << Xm << "  " << Xo << endl;
      break;
    case emf1:
      cout << emf1 << endl;
      break;
    case emfn:
      cout << -ASR/A * I + U << endl;
      break;
    case asr:
      cout << ASR << endl;
      break;
    case ocv:
      cout << emf << endl;
      break;
    case po2i:
      cout << start.pressure().O2_anode << endl;
      break;
    case po2o:
      cout << endc.pressure().O2_anode << endl;
      break;
    case cath:
      cout << endc.pressure().O2_cathode << endl;
      break;
    case demf:
      cout << emf - (-ASR/A * I + U) << endl;
      break;
    case fu:
      cout << FU << endl;
      break;
    case fu_tot:
      cout << FU_total << endl;
      break;
    case ou:
      cout << OU << endl;
      break;
    case ny:
      cout << Ny << endl;
      break;
    case h_wi:
      cout << start.composition().H2/start.composition().H2O << endl;
      break;
    case h_wo:
      cout << endc.composition().H2/endc.composition().H2O << endl;
      break;
    case fui:
      cout << (initial.composition().H2 - start.composition().H2) * 100 / initial.composition().H2 << endl;
      break;
    case pleak:
      cout << leak << endl;
      break;
    case all:
      {
	cout << "All calculation based on a cell temperature of " << start.T() << " kelvin." << endl; 
	cout << "and a total gas pressure of " << start.P() << " Atmospheres." << endl;
	gcomp ps = start.pressure();
	if (dry)
	  cout << "Anode gas composition (Dry gas)" << endl;
	else
	  cout << "Anode gas composition in equlibrium with water at " << wtemp-T0 << " C" << endl;
	cout << "Before cell: " << endl << "cell voltage: " << emf << " V" << endl;
	cout << "pH2O = " << ps.H2O << "  pH2 = " << ps.H2 << "  pO2  = " << ps.O2_anode << endl;
	cout << "pCO2 = " << ps.CO2 << "  pCO = " << ps.CO << "  pCH4 = " << ps.CH4 << endl;
	cout << "Oxygen leak: " << leakP*100 << " % of H2 corresponding to " << leak << " A" << endl;
	cout << "After cell (at a current of " << I << " A corresponding to " << I/A << " A/cm^2)" << endl; 
	gcomp pe = endc.pressure();
	cout << "pH2O = " << pe.H2O << "  pH2 = " << pe.H2 << "  pO2  = " << pe.O2_anode << endl;
	cout << "pCO2 = " << pe.CO2 << "  pCO = " << pe.CO << "  pCH4 = " << pe.CH4 << endl;
	cout << "Corresponding OCV: " << endc.emf() << " V" << endl << endl;
	cout << "Emf average (1'st order): " << emf_av1 << " V" << endl;
	cout << "Emf average (" << n << "'th order): " << U - ASR/A * I << " V" << endl;
	cout << "Delta emf: " << emf - (-ASR/A * I + U) << " V" << endl;
	cout << "ASR (" << n << "'th order): " << ASR << "Ohm*cm^2" << endl;
	cout << "Fuel utilisation: " << FU * 100 << "%" << endl;
	cout << "Fuel utilisation (based on total hydrogen): " << FU_total * 100 << "%" << endl;
	cout << "Oxygen utilisation: " << OU * 100 << "%" << endl;
	cout << "X_H2O in(theory):     " << Xt << endl;
	cout << "X_H2O in(measured):   " << Xm << endl;
	cout << "X_H2O out:            " << Xo << endl;
	break;
      }	
    default:
      print_help();
    }
}


void out_par_all()
{
  if (valid && fabs(I) < Mincurrent)
    {
      Rsec = NaN;
      ASR = NaN;	
    }
  printf("R_secant          %.4f\n",Rsec);		
  printf("ASR_corr          %.4f\n",ASR);
  printf("OCV_measure       %.4f\n",emf);
  printf("T_OCV_measure     %.0f\n",Tleak-T0);
  printf("Oxygen_leak       %.3f\n",leakP);
  printf("leak_current      %.4f\n",leak);
  printf("OCV_theory        %.4f\n",emf_theory);
  printf("OCV_after_cell    %.4f\n",emf_after);
  printf("FU_cell           %.3f\n",FU);
  printf("FU_total          %.3f\n",FU_total);
  printf("OxygenUtil        %.3f\n",OU);
  printf("Power             %.3f\n",power);
  printf("Efficiency        %.4f\n",Ny);
  printf("xH2O_in_theory    %.5f\n",Xt);
  printf("xH2O_in_measure   %.5f\n",Xm);
  printf("xH2O_out          %.5f\n",Xo);
  printf("No_values_in_percent\n");
}

int load_arguments(int argc, char** argv)
{
  // set default values for gas flows 
  comp.H2 = 2.0;
  comp.O2_anode = 0.0;
  comp.N2_anode = 0.0;
  comp.CO2 = 0.0;
  comp.CO = 0.0;
  comp.H2O = 0.0;
  comp.CH4 = 0.0;
  comp.N2_cathode = 0.0;
  comp.O2_cathode = 0.0;
  comp.Air = 10.0;
  int i = 1;
  // Run through all arguments
  while (i != argc)
    {
      string str = argv[i];
      if (str.find("--",0) != string::npos)
	{
	  ++i;	
	  if (i == argc)
	    {
	      // we reached the end of the arguments with a single option so stop here
	      arguments_single[str] = true;
	      break;	
	    }
	  string str2 = argv[i];
	  if (str2.find("--",0) == string::npos)
	    {
	      // The argument was a pair of the form '--option value', load that into hash
	      arguments[str] = str2;
	      ++i;
	    }
	  else
	    {
	      // The argument was a single one with a new option after
	      arguments_single[str] = true;					
	    }
	}
      else
	{
	  // The argument was not in the form '--option'!, load in single option hash
	  arguments_single[str] = true;				
	  ++i;
	}
    }
  argkey_single key_single = arguments_single.find("--version");
  if (key_single != arguments_single.end())
    {
      cout << VERSION << endl;
      return 0; 	
    }
  key_single = arguments_single.find("--help");
  if (key_single != arguments_single.end())
    {
      print_help();
      return 0; 	
    }
  // get water temp
  argkey key = arguments.find("--water");
  if (key != arguments.end())
    wtemp = atof(key->second.c_str()) + T0;
  // get oxygen content of air if specified
  key = arguments.find("--O2_air");
  if (key != arguments.end())
    gas::O2_Air = atof(key->second.c_str());	
  // get gas flows
  key = arguments.find("--H2");
  if (key != arguments.end())
    comp.H2 = atof(key->second.c_str());	
  key = arguments.find("--O2_add");
  if (key != arguments.end())
    comp.O2_anode = atof(key->second.c_str());
  key = arguments.find("--O2");
  if (key != arguments.end())
    comp.O2_anode += atof(key->second.c_str());
  key = arguments.find("--N2");
  if (key != arguments.end())
    comp.N2_anode = atof(key->second.c_str());
  key = arguments.find("--H2O");
  if (key != arguments.end())
    comp.H2O = atof(key->second.c_str());
  key = arguments.find("--CO");
  if (key != arguments.end())
    comp.CO = atof(key->second.c_str());
  key = arguments.find("--CO2");
  if (key != arguments.end())
    comp.CO2 = atof(key->second.c_str());
  key = arguments.find("--CH4");
  if (key != arguments.end())
    comp.CH4 = atof(key->second.c_str());
  key = arguments.find("--Air");
  if (key != arguments.end())
    comp.Air = atof(key->second.c_str());
  key = arguments.find("--O2_cathode");
  if (key != arguments.end())
    comp.O2_cathode = atof(key->second.c_str());
  key = arguments.find("--N2_cathode");
  if (key != arguments.end())
    comp.N2_cathode = atof(key->second.c_str());
  key = arguments.find("--emf");
  if (key != arguments.end())
    emf = atof(key->second.c_str());
  // find if hydrogen gas is dry
  key = arguments.find("--dry");
  if (key != arguments.end() && atoi(key->second.c_str()) == 1)
    {
      dry = true;
      comp.H2O = 1E-100;				
    }
  else
    {
      comp.H2O += gas::water(wtemp,comp.H2);	
    }
  // find if H2 gas is pure
  key = arguments.find("--gas");
  if (key != arguments.end())
    {
      double factor = atof(key->second.c_str());
      if (factor < 1)
	{
	  comp.N2_anode += comp.H2 * (1 - factor);
	  comp.H2 = comp.H2 * factor;
	}
    }
  // Other parameters
  key = arguments.find("--P");
  if (key != arguments.end())
    P = atof(key->second.c_str());
  key = arguments.find("--cell");
  if (key != arguments.end())
    {
      temp = atof(key->second.c_str()) + T0;
      Tleak = temp;
    }
  key = arguments.find("--Tleak");
  if (key != arguments.end())
    Tleak = atof(key->second.c_str()) + T0;
  key = arguments.find("--N");
  if (key != arguments.end())
    n = atoi(key->second.c_str());
  key = arguments.find("--A");
  if (key != arguments.end())
    A = atof(key->second.c_str());
  if (A <= 0)
    A = 1; // no negative areas allowed!
  key = arguments.find("--I");
  if (key != arguments.end())
    I = atof(key->second.c_str());
  key = arguments.find("--U");
  if (key != arguments.end())
    U = atof(key->second.c_str());
  key = arguments.find("--use_ch4");
  if (key != arguments.end() && key->second == "YES")
    {
      use_ch4 = true;
    }
  else
    {
      // Set carbon containing gasses to 0 to speed up calculation!
      comp.CO2 = 0;	
      comp.CO = 0;	
      comp.CH4 = 0;	
    }
  key = arguments.find("--valid");
  if (key != arguments.end() && key->second == "yes")
    {
      valid = true;
    }
  key = arguments.find("--command");
  if (key != arguments.end())
    {
      string mystr = key->second;
      std::transform(mystr.begin(),mystr.end(),mystr.begin(),::tolower);
      if (key->second == "par_all")
	{
	  command = par_all;
	  // for par_all, speed matters, so return here
	  return 1;
	}
      if (key->second == "all")
	command = all;
      if (key->second == "emf1")
	command = emf1;
      if (key->second == "emfn")
	command = emfn;
      if (key->second == "demf")
	command = demf;
      if (key->second == "ocv")
	command = ocv;
      if (key->second == "cath")
	command = cath;
      if (key->second == "po2i")
	command = po2i;
      if (key->second == "po2o")
	command = po2o;
      if (key->second == "asr")
	command = asr;
      if (key->second == "fu")
	command = fu;
      if (key->second == "fu_tot")
	command = fu_tot;
      if (key->second == "ou")
	command = ou;
      if (key->second == "ny")
	command = ny;
      if (key->second == "h/wi")
	command = h_wi;
      if (key->second == "h/wo")
	command = h_wo;
      if (key->second == "fui")
	command = fui;
      if (key->second == "leak")
	command = pleak;
    }
  return 1;
}

void print_help()
{
  printf("\n\n               OCV corection algorithm.\n\n");
  printf("This program calculates the corretion of the EMF due to fuel\n");
  printf("utillisation for a fuel cell under co-flow conditions.\n");
  printf("The calculations are based on a total perssure of 1 atm!\n\n");
  printf("Usage: \n\n");
  printf("  OCV_corr [opt: --help --version] --INPUT1 VALUE1 --INPUT2 VALUE2 ... \n\n");
  printf("  INPUT is the command for which the VALUE is the corresponding input value.\n\n");
  printf("     --command   : forces output into the following formats:\n");
  printf("                   If no command is given the output is the same as\n");
  printf("                   with the command argument 'all'\n");
  printf("          all    : All values are displayed (verbose output).\n");
  printf("          emf1   : Outputs only the 1\'st order aproximation.\n");
  printf("          emfn   : Outputs the n\'th order aproximation of the emf.\n"); 
  printf("                   corrected for fuel utilisation.\n");
  printf("          demf   : Outputs only the difference between the open\n");
  printf("                   circuit voltage (OCV) and the corrected OCV.\n");
  printf("                   (equvivalent to no command input).\n");
  printf("          OCV    : Outputs only the output open circuit voltage\n");
  printf("                   (theoretical).\n");
  printf("          cath   : Outputs the output oxygen partial pressure on\n");
  printf("                   the cathode side.\n");
  printf("          pO2i   : Outputs only the input oxygen partia pressure\n");
  printf("                   on the anode side.\n");
  printf("          pO2o   : Outputs only the output oxygen partial pressure\n");
  printf("                   on the anode side.\n");
  printf("          ASR    : Outputs the cell resistance in Ohm*cm^2.\n");
  printf("          FU     : Outputs the fuel utilisation in %%.\n"); 
  printf("          FU_tot : Outputs total fuel utilisation in %% (based on\n");
  printf("                   the total hydrogen supplied).\n");
  printf("          OU     : Outputs the oxygen utilisation in %%.\n");
  printf("          ny     : Outputs the Power density and the efficiency.\n");
  printf("          h/wi   : Outputs the hydrogen/water ratio at the input side.\n");
  printf("          h/wo   : Outputs the hydrogen/water ratio at the output side.\n");
  printf("          ASR_FU : Outputs the Fuel utilisation corrected cell resistance\n");
  printf("                   as well as the mole frction water (theoretical and measured\n");
  printf("                   at the input side of the cell and at the output side.\n");
  printf("                   and the fuel utilisation (in percent).\n");
  printf("          par_all: Outputs all cell parameters one line at a time.\n");
  printf("          leak   : Outputs the leak current.\n");
  printf("\n");    
  printf("     --cell      : Inputs the cell temperature, default is 850 C.\n");
  printf("\n");    
  printf("     --water     : Inputs the water temperature, default is 0 C.\n");
  printf("\n");    
  printf("     --emf       : Inputs the cell emf, default is theoretical \n");
  printf("                   open circuit woltage.\n");
  printf("\n");    
  printf("     --H2        : Inputs the Hydrogen flow in L/h, default is 2.\n");
  printf("\n");    
  printf("     --N2        : Inputs the Nitrogen flow to the anode in L/h, default is 0.\n");
  printf("\n");    
  printf("     --Air       : Inputs the Air flow in L/h, default is 10.\n");
  printf("\n");    
  printf("     --O2_cathode: Inputs the Oxygen flow to the cathode in L/h, default is 0.\n");
  printf("\n");    
  printf("     --N2_cathode: Inputs the Nitrogen flow to the cathode in L/h, default is 0.\n");
  printf("\n"); 
  printf("     --CO        : Inputs the Carbon Monoxide flow in L/h, default is 0.\n");
  printf("\n");    
  printf("     --CO2       : Inputs the Carbon Dioxide flow in L/h, default is 0.\n");
  printf("\n");    
  printf("     --O2_add    : Inputs the amount of Oxygen gas added to the anode gas in L/h,\n");
  printf("\n");    
  printf("     --CH4       : Inputs the amount of methane gas to anode,\n");
  printf("                   default is 0.\n");
  printf("                   Note that all gas flows has to be at 0 degree C\n");
  printf("                   and 1 atm pressure!!\n");
  printf("\n");    
  printf("     --use_ch4   : if this is set to YES the calculations will use ch4.pl to\n");
  printf("                   calculate the cell emf and the leak curent based on the\n");
  printf("                   amount of methane given. Only use this option if carbon,\n");
  printf("                   containing gasses is used as it slows program execution.\n");
  printf("\n");    
  printf("     --O2_air    : Inputs the oxygen partial pressure in the cathode gas\n");
  printf("                   Default is 0.21.  Note that im methane is used only a \n");
  printf("                   value of 0.21 is allowd!\n");
  printf("\n");    
  printf("     --gas       : Inputs the hydrogen gas composition.\n");
  printf("                   Default is 1, coresponding to 100%% hydrogen.\n");
  printf("\n");    
  printf("     --dry       : Inputs if the hydrogen gas is NOT in equlibrium with.\n");
  printf("                   water (default is 0, in eql.) use 1 for dry gas.\n");
  printf("\n");    
  printf("     --N         : Inputs the number of steps in the simulation.\n");
  printf("                   defaults to 20.\n");
  printf("\n");    
  printf("     --I         : Inputs current, defaults to 0.1 A.\n");
  printf("\n");    
  printf("     --U         : Inputs the measured cell potential, default is -0.5 V.\n");
  printf("\n");    
  printf("     --A         : Inputs the cell area, default is 1 cm^2.\n");
  printf("\n");    
  printf("     --P         : Inputs the gas pressure, default is 1 Atm.\n");
  printf("\n");    
  printf("     --valid     : Used for force display of -32768 instead of calculated\n");
  printf("                   resistances if current is below 0.05 A\n");
  printf("                   Default is no, use 'yes' for forced display of -32768.\n");
  printf("\n");    
  printf("If the --version option is given, the version number\n");
  printf("is displayed and the program exits\n");
  printf("The --help option displays this information\n\n");
  printf("OCV_corr is: %s\n\n",VERSION.c_str());
}
