/**
* @module SpeciesNode
* @description Represents a species node in a chemical solution, handling the logical representation
* of a Species including its quantity, state, and behavior within a solution.
*
* @requires ./SpecieState
* @requires ./Species
*
* @property {Species} archetype - Species of which this is a quantity
* @property {Solution} solvent - Reference to the solution containing this SpeciesNode
* @property {number} moles - Number of moles of this SpeciesNode
* @property {boolean} unitActivity - Indicates if this species node is a solid (has unit activity)
* @property {boolean} infinite - Indicates if there's a functionally infinite quantity available
*
* @example
* const speciesNode = new SpeciesNode(speciesTemplate, 0.5);
*/
/**
* Creates a new Node based off of the the Species provided. As no quantity
* has been provided for the number of moles, this constructor will assume
* both infinite amount and unitActivity.
*
* @param template {Species}
* The Species for the SpeciesNode
* @param noMoles {Number}
* The amount of species in moles
*
* @constructor
*/
define([
'./SpecieState',
'./Species'
], function (SpecieState, Species) {
function SpeciesNode(template, noMoles) {
if (template === null || !(template instanceof Species)) {
throw "RunTimeException : Pass a valid Species argument to the SpeciesNode constructor";
}
this.archetype = template;
this.solvent = null; // Type solution! At some point, this must be added...
if (noMoles === null) {
this.unitActivity = true;
this.infinite = true;
this.moles = 0.0;
} else {
this.moles = noMoles;
this.infinite = template.infinite;
this.unitActivity = template.unitActivity;
}
}
/**
* Takes as an argument the fraction of the current number of moles that it
* should removed from this node. Returns the moles removed.
*
* @param fractionToRemove Must be between 0 and 1
*
* @returns {Number}
*/
SpeciesNode.prototype.removeFraction = function (fractionToRemove) {
if (fractionToRemove < 0.0 || fractionToRemove > 1.0) {
throw "RunTimeException : fractionToRemove must be between 0 and 1";
}
var removed = this.moles * fractionToRemove;
this.moles = (1.0 - fractionToRemove) * this.moles;
return removed;
};
/**
* Merge two SpeciesNode
*
* @param other {SpeciesNode}
*/
SpeciesNode.prototype.merge = function (other) {
if ((other instanceof SpeciesNode) && other !== null && this.equals(other)) {
this.moles = this.moles + other.moles;
}
};
/**
* Returns the name of the Species that this node represents in addition to
* the number of moles, and if the solvent is non-null, the concentration.
*/
SpeciesNode.prototype.toString = function () {
var returnValue = "";
returnValue = returnValue + this.archetype.name + "\t" + this.moles;
if (this.solvent !== null) {
returnValue = returnValue + "\t" + this.getConcentration();
}
return returnValue;
};
/**
* Checks the equality of two SpeciesNodes. This will return true if the two
* Species that these nodes represent are equal.
*
* @param other
*
* @returns {Boolean}
*/
SpeciesNode.prototype.equals = function (other) {
if (other === null) {
throw "NullPointerException : Can't compare SpeciesNode to null";
}
var returnValue = false;
if (other instanceof SpeciesNode) {
returnValue = this.archetype.equals(other.archetype);
}
else if (other instanceof Species) {
returnValue = this.archetype.equals(other);
}
return returnValue;
};
/**
* Deep copy, but the reference to the node's solvent is not copied.
*
* @returns other {SpeciesNode}
*/
SpeciesNode.prototype.clone = function () {
var other = new SpeciesNode(this.archetype, this.moles);
other.infinite = this.infinite;
other.unitActivity = this.unitActivity;
return other;
};
/**
* Changes the number of moles of this species within it's solution.
*
* @param moles {Number}
*/
SpeciesNode.prototype.setMoles = function (moles) {
if (moles < 0.0) {
throw "IllegalArgumentException : moles in SpeciesNode must be positive";
}
this.moles = moles;
};
/**
* Returns the concentration of this species with respect to it's solvent.
* This should be different for solids, liquids, and gases...
* @returns {Number}
*/
SpeciesNode.prototype.getConcentration = function () {
if (this.archetype.state === 'g') {
return this.moles * 0.083145 * this.solvent.getTemperature() / this.solvent.getGasVolume();
}
// else if (this.archetype.state === 'l') {
// return 1;
// }
else {
return this.moles / this.solvent.liquidVolume;
}
};
/**
* Calculates the derivative of concentration with respect to moles (dC/dn).
* This represents how the concentration changes when the number of moles changes.
*
* For gases, this uses the ideal gas law where dC/dn = RT/V
* For liquids and aqueous species, dC/dn = 1/V
*
* @returns {Number} The derivative of concentration with respect to moles in mol/L per mol
*/
SpeciesNode.prototype.dConc_dmoles = function () {
if (this.archetype.state === 'g') {
return 0.083145 * this.solvent.getTemperature() / this.solvent.getGasVolume();
} else {
return 1.0 / this.solvent.liquidVolume;
}
}
/**
* Sets the concentration of this species with respect to it's solvent.
* NOTE : This has the effect of changing the number of moles.
*
* @param concentration {Number}
*/
SpeciesNode.prototype.setConcentration = function (concentration) {
if (this.solvent === null) {
throw "NullPointerException : Solvent is null in SpeciesNode";
}
if (concentration < 0.0) {
throw "IllegalArgumentException : concentration in SpeciesNode must be positive";
}
if (this.archetype.state === 'g') {
this.setMoles(concentration * this.solvent.getGasVolume() / (0.083145 * this.solvent.getTemperature()));
} else {
this.setMoles(concentration * this.solvent.liquidVolume);
}
};
/**
* Returns the weight of this SpeciesNode in grams.
*
* @returns {Number}
*/
SpeciesNode.prototype.getWeight = function () {
return this.moles * this.archetype.molecularWeight;
};
/**
* Returns the number of moles of water replaced.
*
* @returns {Number}
*/
SpeciesNode.prototype.getWaterReplaced = function () {
// 0 for solids, calculated for liquids and aqueous species
return (this.archetype.state === SpecieState.SOLID) ? 0.0 :
this.moles * this.archetype.waterReplacement;
};
/**
* Returns the amount of volume taken up by this SpeciesNode in Liters.
* Again, mostly useful for solids (real volume).
*
* @returns {Number}
*/
SpeciesNode.prototype.getVolume = function () {
var volume = 0.0; // for liquids or aqueous species, confounded with water
if (this.archetype.state === SpecieState.SOLID) {
// for solids, real volume
volume = (this.getWeight() / this.archetype.getDensity()) / 1000.0;
}
return volume;
};
/**
*
* Currently assumes that the solvent moles are calculated correctly (solventFinite = true)
*
* @returns {Number}
*/
SpeciesNode.prototype.getMoleFraction = function (amountObj={}) {
const moles = amountObj[this.archetype.id] || this.moles;
let totalMoles = 0;
for (const species of this.solvent.speciesInSolution) {
const state = species.archetype.state;
if (state === 'aq' || state === 'l') {
const id = species.archetype.id;
if (amountObj.hasOwnProperty(id)) {
totalMoles += amountObj[id];
} else {
totalMoles += species.moles;
}
}
}
return moles / totalMoles;
}
/**
* Get the density of this SpeciesNode's Species.
*
* @returns {Number}
*/
SpeciesNode.prototype.getDensity = function () {
return this.archetype.getDensity();
};
/**
* Get the molecular weight of this SpeciesNode's Species.
*
* @returns {Number}
*/
SpeciesNode.prototype.getMolecularWeight = function () {
return this.archetype.molecularWeight;
};
return SpeciesNode;
});