/* thermo.c - Compute thermodynamic properties of individual species and composition of species */ /* $Id: thermo.c,v 1.2 2001/02/22 19:48:44 antoine Exp $ */ /* Copyright (C) 2000 */ /* Antoine Lefebvre */ /* Mark Pinese */ /* */ /* Licensed under the GPLv2 */ #include #include #include #include #include #include "thermo.h" #include "compat.h" #include "conversion.h" /************************************************************** These variables hold the number of records for propellant and thermo data ***************************************************************/ unsigned long num_propellant, num_thermo; /* global variable containing the information about chemical species */ propellant_t *propellant_list; thermo_t *thermo_list; /**************************************************************** VARIABLE: Contain the molar mass of element by atomic number molar_mass[0] contain hydrogen and so on. Data come from Sargent-Welch 1996 *****************************************************************/ const float molar_mass[N_SYMB] = { 1.00794, 4.002602, 6.941, 9.012182, 10.811, 12.0107, 14.00674, 15.9994, 18.9984032, 20.11797, 22.989770, 24.305, 26.981538, 28.0855, 30.973761, 32.066, 35.4527, 39.948, 39.0983, 40.078, 44.95591, 47.88, 50.9415, 51.996, 54.938, 55.847, 58.9332, 58.6934, 63.546, 65.39, 69.723, 72.61, 74.9216, 78.96, 79.904, 83.80, 85.4678, 87.62, 88.9059, 91.224, 92.9064, 95.94, 98.0, 101.07, 102.9055, 106.42, 107.868, 112.41, 114.82, 118.71, 121.757, 127.60, 126.9045, 131.29, 132.9054, 137.33, 138.9055, 140.12, 140.9077, 144.24, 145., 150.36, 151.965, 157.25, 158.9253, 162.50, 164.9303, 167.26, 168.9342, 173.04, 174.967, 178.49, 180.9479, 183.85, 186.207, 190.2, 192.22, 195.08, 196.9665, 200.59, 204.383, 207.2, 208.9804, 209., 210., 222., 223., 226.0254, 227., 232.0381, 231.0359, 238.029, 237.0482, 244., 12.011, 9.01218, 10.811, 24.305, 26.98154, 257.0, 0, 2}; /**************************************************************** VARIABLE: Contain the symbol of the element in the same way as for the molar mass. COMMENTS: It is use in the loading of the data file to recognize the chemical formula. *****************************************************************/ const char symb[N_SYMB][3] = { "H ","HE","LI","BE","B ","C ","N ","O ", "F ","NE","NA","MG","AL","SI","P ","S ","CL","AR","K ","CA", "SC","TI","V ","CR","MN","FE","CO","NI","CU","ZN","GA","GE", "AS","SE","BR","KR","RB","SR","Y ","ZR","NB","MO","TC","RU", "RH","PD","AG","CD","IN","SN","SB","TE","I ","XE","CS","BA", "LA","CE","PR","ND","PM","SM","EU","GD","TB","DY","HO","ER", "TM","YB","LU","HF","TA","W ","RE","OS","IR","PT","AU","HG","TL", "PB","BI","PO","AT","RN","FR","RA","AC","TH","PA","U ","NP", "U6","U5","U1","U2","U3","U4","FM", "E ", "D " }; /* the E stand for electron and D for deuterium*/ /* Enthalpy in the standard state (Dimensionless) */ double enthalpy_0(int sp, float T) { thermo_t *s = (thermo_list + sp); double val; int pos = 0, i; if (T < s->range[0][0]) /* Temperature below the lower range */ { pos = 0; } /*Temperature above the higher range */ else if (T >= s->range[s->nint-1][1]) { pos = s->nint - 1; } else { for (i = 0; i < s->nint; i++) /* Find the range */ { if ((T >= s->range[i][0]) && (T < s->range[i][1])) pos = i; } } /* parametric equation for dimentionless enthalpy */ val = -s->param[pos][0]*pow(T, -2) + s->param[pos][1]*pow(T, -1)*log(T) + s->param[pos][2] + s->param[pos][3]*T/2 + s->param[pos][4]*pow(T, 2)/3 + s->param[pos][5]*pow(T, 3)/4 + s->param[pos][6]*pow(T, 4)/5 + s->param[pos][7]/T; return val; /* dimensionless enthalpy */ } /* Entropy in the standard state (Dimensionless)*/ double entropy_0(int sp, float T) { thermo_t *s = (thermo_list + sp); double val; int pos = 0, i; if (T < s->range[0][0]) { pos = 0; } else if (T >= s->range[s->nint-1][1]) { pos = s->nint - 1; } else { for (i = 0; i < s->nint; i++) { if ((T >= s->range[i][0]) && (T < s->range[i][1])) pos = i; } } /* parametric equation for dimentionless entropy */ val = -s->param[pos][0]*pow(T, -2)/2 - s->param[pos][1]*pow(T, -1) + s->param[pos][2]*log(T) + s->param[pos][3]*T + s->param[pos][4]*pow(T, 2)/2 + s->param[pos][5]*pow(T, 3)/3 + s->param[pos][6]*pow(T, 4)/4 + s->param[pos][8]; return val; } /* Specific heat in the standard state (Dimensionless) */ double specific_heat_0(int sp, float T) { thermo_t *s = (thermo_list + sp); double val; int pos = 0, i; if (T < s->range[0][0]) { pos = 0; } else if (T >= s->range[s->nint-1][1]) { pos = s->nint - 1; } else { for (i = 0; i < s->nint; i++) { if ((T >= s->range[i][0]) && (T < s->range[i][1])) pos = i; } } /* parametric equation for dimentionless specific_heat */ val = s->param[pos][0]*pow(T, -2) + s->param[pos][1]*pow(T, -1) + s->param[pos][2] + s->param[pos][3]*T + s->param[pos][4]*pow(T, 2) + s->param[pos][5]*pow(T, 3) + s->param[pos][6]*pow(T, 4); return val; } /* Dimensionless Gibbs free energy in the standard state */ double gibbs_0(int sp, float T) { return enthalpy_0(sp, T) - entropy_0(sp, T); /* dimensionless */ } /* Check if the species is in its range of definition 0 if out of range, 1 if ok */ int temperature_check(int sp, float T) { thermo_t *s = (thermo_list + sp); if ((T > s->range[s->nint-1][1]) || (T < s->range[0][0])) return 0; return 1; } /* This function return the transition temperature of the species considered which is nearest of the temperature T */ double transition_temperature(int sp, float T) { thermo_t *s = (thermo_list + sp); /* first assume that the lowest temperature is the good one */ double transition_T = s->range[0][0]; /* verify if we did the good bet */ if (fabs(transition_T - T) > fabs(s->range[s->nint-1][1] - T)) { transition_T = s->range[s->nint-1][1]; } return transition_T; } double entropy(int sp, state_t st, double ln_nj_n, float T, float P) { double s; switch (st) { case GAS: /* The thermodynamic data are based on a standard state pressure of 1 bar (10^5 Pa) */ s = entropy_0(sp, T) - ln_nj_n - log(P * ATM_TO_BAR); break; case CONDENSED: s = entropy_0(sp, T); break; default: s = 0; } return s; } /* J/mol T is in K, P is in atm */ double gibbs(int sp, state_t st, double ln_nj_n, float T, float P) { double g; switch (st) { case GAS: g = gibbs_0(sp, T) + ln_nj_n + log(P * ATM_TO_BAR); break; case CONDENSED: g = gibbs_0(sp, T); break; default: g = 0; } return g; } double propellant_molar_mass(int molecule) { int i = 0, coef; double ans = 0; while ((coef = (propellant_list + molecule)->coef[i])) { ans += coef * molar_mass[(propellant_list + molecule)->elem[i]]; i++; } return ans; } /* J/mol */ double heat_of_formation(int molecule) { double hf = (propellant_list + molecule)->heat * propellant_molar_mass(molecule); return hf; } /* should not be in thermo.c */ double propellant_enthalpy(equilibrium_t *e) { int i; double h = 0.0; for (i = 0; i < e->propellant.ncomp; i++) { h += e->propellant.coef[i] * heat_of_formation (e->propellant.molecule[i]) / propellant_mass (e); } return h; } /* should not be in thermo.c */ double product_enthalpy(equilibrium_t *e) { int i; double h = 0.0; for (i = 0; i < e->product.n[GAS]; i++) { h += e->product.coef[GAS][i] * enthalpy_0(e->product.species[GAS][i], e->properties.T); } for (i = 0; i < e->product.n[CONDENSED]; i++) { h += e->product.coef[CONDENSED][i] * enthalpy_0(e->product.species[CONDENSED][i], e->properties.T); } return h; } /* should not be in thermo.c */ double product_entropy(equilibrium_t *e) { int i; double ent = 0.0; for (i = 0; i < e->product.n[GAS]; i++) { ent += e->product.coef[GAS][i]*entropy(e->product.species[GAS][i], GAS, e->itn.ln_nj[i] - e->itn.ln_n, e->properties.T, e->properties.P); } for (i = 0; i < e->product.n[CONDENSED]; i++) { ent += e->product.coef[CONDENSED][i]*entropy(e->product.species[CONDENSED][i], CONDENSED, 0, e->properties.T, e->properties.P); } return ent; } /* should not be in thermo.c */ /* The specific heat of the mixture for frozen performance */ double mixture_specific_heat_0(equilibrium_t *e, double temp) { int i; double cp = 0.0; /* for gases */ for (i = 0; i < e->product.n[GAS]; i++) { cp += e->product.coef[GAS][i]*specific_heat_0(e->product.species[GAS][i], temp); } /* for condensed */ for (i = 0; i < e->product.n[CONDENSED]; i++) { cp += e->product.coef[CONDENSED][i]* specific_heat_0(e->product.species[CONDENSED][i], temp); } return cp; } int thermo_search(char *str) { int i; int last = -1; for (i = 0; i < num_thermo; i++) { if (!(STRNCASECMP(str, (thermo_list + i)->name, strlen(str)))) { last = i; printf("%-5d %s\n", i, (thermo_list + i)->name); } } return last; } int propellant_search(char *str) { int i; int last = -1; for (i = 0; i < num_propellant; i++) { if (!(STRNCASECMP(str, (propellant_list + i)->name, strlen(str)))) { last = i; printf("%-5d %s\n", i, (propellant_list + i)->name); } } return last; } int atomic_number(char *symbole) { int i; int element = -1; /* find the atomic number of the element */ for (i = 0; i < N_SYMB; i++) { if (!STRCASECMP(symbole, symb[i])) { element = i; break; } } return element; } int compute_density(composition_t *c) { short i; double mass = 0; c->density = 0.0; for (i = 0; i < c->ncomp; i++) { mass += c->coef[i] * propellant_molar_mass(c->molecule[i]); } for (i = 0; i < c->ncomp; i++) { if ((propellant_list + c->molecule[i])->density != 0.0) { c->density += c->coef[i] * propellant_molar_mass(c->molecule[i]) / (mass * (propellant_list + c->molecule[i])->density); } } if (c->density != 0.0) { c->density = 1/c->density; } return 0; } /* This fonction return the offset of the molecule in the propellant_list the argument is the chemical formula of the molecule */ int propellant_search_by_formula(char *str) { int i = 0, j ; char tmp[5]; char *ptr; int elem[6] = {0, 0, 0, 0, 0, 1}; int coef[6] = {0, 0, 0, 0, 0, 0}; int molecule = -1; ptr = str; /* beginning of the string */ while ( (i < 6) && ((ptr - str) < strlen(str)) ) { if (isupper(*ptr) && islower(*(ptr+1)) && (isupper(*(ptr+2)) || iscntrl(*(ptr+2))) ) { tmp[0] = *ptr; tmp[1] = toupper(*(ptr+1)); tmp[2] = '\0'; /* find the atomic number of the element */ elem[i] = atomic_number(tmp); coef[i] = 1; i++; ptr += 2; } else if (isupper(*ptr) && (isupper(*(ptr+1)) || iscntrl(*(ptr+1))) ) { tmp[0] = *ptr; tmp[1] = ' '; tmp[2] = '\0'; elem[i] = atomic_number(tmp); coef[i] = 1; i++; ptr++; } else if (isupper(*ptr) && isdigit(*(ptr+1))) { tmp[0] = *ptr; tmp[1] = ' '; tmp[2] = '\0'; elem[i] = atomic_number(tmp); j = 0; do { tmp[j] = *(ptr + 1 + j); j++; } while (isdigit(*(ptr + 1 + j))); tmp[j] = '\0'; coef[i] = atoi(tmp); i++; ptr = ptr + j + 1; } else if (isupper(*ptr) && islower(*(ptr+1)) && isdigit(*(ptr+2))) { tmp[0] = *ptr; tmp[1] = toupper(*(ptr+1)); tmp[2] = '\0'; elem[i] = atomic_number(tmp); j = 0; while (isdigit(*(ptr + 2 + j))) { tmp[j] = *(ptr + 1 + j); j++; } tmp[j] = '\0'; coef[i] = atoi(tmp); i++; ptr = ptr + j + 2; } } /* for (i = 0; i < 6; i++) { if (elem[i] != -1) printf("%s %d\n", symb[elem[i]], coef[i]); } */ for (i = 0; i < num_propellant; i++) { for (j = 0; j < 6; j++) { /* set to the same value as the previous one if the same */ if (!( ((propellant_list+i)->coef[j] == coef[j]) && ((propellant_list+i)->elem[j] == elem[j]) )) break; } /* Now search in propellant list for this molecule */ /* for (j = 0; j < num_propellant; j++) { for (i = 0; i < 6; i++) { if ( (coef[i] != propellant_element_coef(elem[i], j)) && (propellant_list + i) break; } */ if (j == 5) /* we found the molecule ! */ { /* check if the inverse is true */ molecule = i; break; } } return molecule; } /* Mass of propellant in gram */ double propellant_mass(equilibrium_t *e) { int i; double mass = 0.0; for (i = 0; i < e->propellant.ncomp; i++) { mass += e->propellant.coef[i] * propellant_molar_mass(e->propellant.molecule[i]); } return mass; }