const Species = require('./Species.js');
const Constants = require('./Constants.js');
const getIndex = require('./getIndex.js');
/**
* This class represents a reaction in general, detailing the characteristics
* that do not vary between different instances.
*
* Variables
* species {Array} The species involved in the reaction.
*
* coefficients {Array} The coefficients of the species in the species Vector. Each element
* corresponds directly to an element in the species Vector, and therefore
* these vectors must have exactly the same size.
* The values of the coefficient vector are stored as Numbers. Positive
* values denote Products, while negative coef's denote Reactants values.
*/
/**
* Constructs a new reaction based off of the given species objects as well
* as their list of coefficients.
*
* @param species {Array} The species involved in the reaction.
* @param coefficients {Array} The coefficients of the species in the species Vector.
*
*/
class ReactionKinetics{
constructor(species, coefficients, kinetics=null, orders=null) {
if (species === null || coefficients === null) {
throw "NullPointerException : One of the arguments passed to Reaction is null";
}
if (species.length <= 0) {
throw "IllegalArgumentException : Species passed to Reaction has non-positive length";
}
if (species.length !== coefficients.length) {
throw "IllegalArgumentException : Length of species does not match length of coefficients";
}
this.species = species;
this.coefficients = coefficients;
if (kinetics) {
this.kinetics = true;
this.orders = orders;
this.useEquilibrium = kinetics.useEquilibrium || false;
this.Ea = parseFloat(kinetics.Ea);
this.k298 = parseFloat(kinetics.k298);
}
this.isBetweenSolids = this.evaluateBetweenSolids();
}
/**
* Evaluates if the reactions includes only solid species
*
* @returns isBetweenSolids {Boolean}
*/
evaluateBetweenSolids() {
var isBetweenSolids = true,
s,
i
for (i = 0; i < this.species.length; i++) {
s = this.species[i];
if (!s instanceof Species) {
throw "IllegalTypeException : species in Reaction contains non-Species objects";
}
else {
if (s.state !== "s") {
isBetweenSolids = false;
return isBetweenSolids;
}
}
}
return isBetweenSolids;
};
/**
* Checks the equality between two reactions.
* ASSUMPTION: There cannot exist another reaction with the same species,
* but different coefficients.
*
* @param other {Object}
*
* @returns returnValue {Boolean}
*/
equals(other) {
var returnValue = true,
i,
exist;
if (other === null) {
throw "NullPointerException : other is null in Reaction.equals";
}
if (other instanceof ReactionKinetics) {
if(this.species.length === other.species.length) {
for (i = 0; i < this.species.length; i++) {
if (!(this.species[i].equals(other.species[i]))) {
returnValue = false;
break;
}
}
}
else {
returnValue = false;
}
}
else if (other.archetype) {
returnValue = this.equals(other.archetype);
}
else {
returnValue = false;
}
return returnValue;
};
/**
* Returns a new reaction that represents the reverse of this reaction, as
* computed by converting all reactants to products, and all products to
* reactants.
*
* @returns {Reaction}
*/
reverseReaction() {
const specie = this.cloneSpecies();
const coefficient = this.coefficients.map(x => -x);
return new ReactionKinetics(specie, coefficient);
};
/**
* Returns a copy, not a reference, of the species in this reaction
*
* @returns specie {Array} a copy of species in this reaction
*/
cloneSpecies() {
return this.species.slice();
};
/**
* Returns a copy, not a reference, of the coefficients in this reaction
*
* @returns coefficient {Array} a copy of coefficients in this reaction
*/
cloneCoefficients() {
return this.coefficients.slice();
};
/**
* Adds a Reaction to this reaction. If the sum of these two is a valid
* reaction (there is both a reactant and a product present) then a new
* Reaction will be returned. Else, null will be returned.
*
* ASSUMPTION: All reactions are normalized. I.E., there is never the case
* that the same species is both a reactant and a product.
*
* @param other {Reaction} the other reaction to be added
*
* @returns {Reaction} if valid, else null
*/
add(other) {
var specie = this.cloneSpecies(),
coefficient = this.cloneCoefficients(),
i,
j,
k,
otherSpecie,
otherCoef,
index,
sum,
returnValue,
reactant,
product,
gcd,
a,
b,
temp,
l;
if (other === null) {
throw "NullPointerException : Argument is null in Reaction.add";
}
// Add the species and coefficients of the other species to our
// cloned species/coefficient Vectors.
for (i = 0; i < other.species.length; i++) {
otherSpecie = other.species[i];
otherCoef = other.coefficients[i];
// Find the current species within ourself.
index = getIndex(specie, otherSpecie);
if (index > -1) {
sum = coefficient[index] + otherCoef;
// There might be the possibility that these terms canceled.
if (sum === 0.0) {
specie.splice(index, 1);
coefficient.splice(index, 1);
}
// If they did not cancel, update the coefficient.
else {
coefficient[index] = sum;
}
}
else {
specie.push(otherSpecie);
coefficient.push(otherCoef);
}
}
// If the two species are non-identical, return a new Reaction that
// represents their addition.
returnValue = null;
if (specie.length > 0) {
// Is there both a reactant and a product?
reactant = false;
product = false;
for (k = 0; k < specie.length && (!reactant || !product); k++) {
if (coefficient[k] < 0) {
reactant = true;
}
else {
product = true;
}
}
if (reactant && product) {
// Normalize the coefficients.
// First, we find the gcd;
gcd = Math.abs(coefficient[0]);
for (j = 1; j < coefficient.length; j++) {
a = gcd;
b = Math.abs(coefficient[j]);
while (b !== 0) {
temp = a;
a = b;
b = temp % b;
}
gcd = a;
}
for (l = 0; gcd !== 1.0 && l < coefficient.length; l++) {
coefficient[l] = coefficient[l] / gcd;
}
returnValue = new ReactionKinetics(specie, coefficient);
}
}
return returnValue;
};
/**
* Returns a new reaction, scaled by the given factor.
*
* @param factor {Number}
*
* @returns {Reaction} the new scaled reaction
*/
scale(factor) {
var specie = this.cloneSpecies(),
coefficient = this.cloneCoefficients(),
i;
if (factor === 0.0) {
throw "IllegalArgumentException : Argument is 0 in Reaction.scale";
}
for (i = 0; i < coefficient.length; i++) {
coefficient[i] = coefficient[i] * factor;
}
return new ReactionKinetics(specie, coefficient);
};
/**
* Searches for an overlapping species in the other Reaction, and if it
* finds one, we return the coefficients.
*
* If there is an overlap, we return two Doubles per overlap, the first
* being the coefficient of our overlapping species, and the second being
* the coef of the other reaction.
*
* If there is no overlap, null will be returned.
*
* @param other {Reaction}
*
* @returns returnValue {Array}
*/
getOverlappingCoefficients(other) {
var returnValue = [],
i,
index;
for (i = 0; i < other.species.length; i++) {
index = getIndex(this.species, other.species[i]);
if (index !== -1) {
returnValue.push(this.coefficients[index]);
returnValue.push(other.coefficients[i]);
}
}
return returnValue;
};
/**
* Returns the String representation of this Reaction.
*
* @returns returnValue {String}
*/
toString() {
var returnValue = "",
i;
returnValue = returnValue + "Reaction:";
for (i = 0; i < this.species.length; i++) {
returnValue = returnValue + "\n\t" + this.coefficients[i] + "\t" + this.species[i].toString();
}
return returnValue;
};
/**
* Returns the specific heat for the reaction in J/K/mol.
*
* @returns {Number}
*/
getHeatCapacity() {
var sum = 0,
i;
for (i = 0; i < this.species.length; i++) {
sum += this.getCoefficientAt(i) * this.getSpeciesAt(i).cp *
Constants.CAL_TO_J * this.getSpeciesAt(i).molecularWeight;
}
return sum;
};
/**
* Returns the standard Enthalpy for the reaction.
*
* @returns {Number}
*/
getHo() {
var sum = 0,
i;
for (i = 0; i < this.species.length; i++) {
sum += this.getCoefficientAt(i) * this.getSpeciesAt(i).enthalpy;
}
return sum;
};
/**
* Returns the standard Entropy of the reaction.
*
* @returns {Number}
*/
getSo() {
var sum = 0,
i;
for (i = 0; i < this.species.length; i++) {
sum += this.getCoefficientAt(i) * this.getSpeciesAt(i).entropy / 1000;
}
return sum;
};
/**
* This returns the standard Gibb's Free energy of the reaction, for use in
* calculating K.
*
* @param temp {Number} the temperature
*
* @returns {Number}
*/
getGo(temp) {
if (temp < 0.0) {
throw "IllegalArgumentException : temp is less than zero in Reaction.getGo";
}
return this.getHo() - temp * this.getSo();
};
/**
* Returns the K of the reaction for the given temperature.
*
* @param temperature
*
* @returns {Number}
*/
getK(temperature) {
if (temperature < 0.0) {
throw "IllegalArgumentException : temperature is less than zero in Reaction.getK";
}
return Math.exp(-1.0 * this.getGo(temperature) / (0.00831451 * temperature));
};
/**
* Returns the rate constant for a reaction at a given temperature
*/
getRateConstant(temperature) {
if (temperature < 0.0) {
throw "IllegalArgumentException : temperature is less than zero in Reaction.getK";
}
const Ea_over_R = this.Ea/(0.0083145);
return this.k298 * Math.exp(-Ea_over_R*(1/temperature-1/298.15));
}
/**
* Returns the species specified at the given index.
*
* @param index
*
* @returns {Species}
*/
getSpeciesAt(index) {
return this.species[index];
};
/**
* Returns the number of the species in the Reaction.
*
* @returns {Number}
*/
getSpeciesCount() {
return this.species.length;
};
/**
* Returns the index of the specified Species.
* This method will return -1 if the Species is not
* present in this reaction.
*
* @param specie {Species} the specie we are looking for
*
* @returns {Number}
*/
getSpeciesIndex(specie) {
var i;
for (i = 0; i < this.species.length; i++) {
if (this.species[i].equals(specie)) {
return i;
}
}
return -1;
};
/**
* Stores the referenced specie at the given index. The previous
* specie at that index is discarded.
*
* @param specie {Species}
*
* @param index {Number}
*/
setSpeciesAt(specie, index) {
if (specie === null) {
throw "NullPointerException : specie is null in Reaction.setSpeciesAt";
}
this.species[index] = specie;
};
/**
* Returns the coefficient of the species at the specified index.
*
* @param index {Number}
*
* @returns {Number}
*/
getCoefficientAt(index) {
return this.coefficients[index];
};
/**
* Stores the provided coefficient for the species at the given index.
*
* @param coef {Number}
* @param index {Number}
*/
setCoefficientAt(coef, index) {
this.coefficients[index] = coef;
};
/**
* Returns the products.
* @returns {Array}
*/
getProducts() {
var i;
var products = [];
for (i = 0; i < this.species.length; i++) {
if (this.getCoefficientAt(i) > 0) {
products.push(this.species[i]);
}
}
return products;
};
/**
* Returns the reactants.
* @returns {Array}
*/
getReactants() {
var i;
var reactants = [];
for (i = 0; i < this.species.length; i++) {
if (this.getCoefficientAt(i) < 0) {
reactants.push(this.species[i]);
}
}
return reactants;
};
}
module.exports = ReactionKinetics