na-error-propagation
Version:
Calculates the error propagation
230 lines (192 loc) • 6.41 kB
JavaScript
/*!
* na-error-propagation
* @see https://github.com/tfoxy/na-error-propagation
* @version 0.1.3
* @author Tomás Fox <tomas.c.fox@gmail.com>
* @license MIT
*/
/* global define */
(function(root, factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], factory);
} else if (typeof exports === 'object') {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
} else {
// Browser globals (root is window)
root.ErrorPropagation = factory();
}
}(this, function() {
'use strict';
function InputError(message) {
this.name = 'InputError';
this.message = message;
}
InputError.prototype = Object.create(Error.prototype);
InputError.prototype.constructor = InputError;
function ErrorPropagation(options) {
options = options || {};
switch (options.correlation) {
case undefined:
case 'correlated':
this._calculateError = calculateCorrelatedError;
break;
case 'uncorrelated':
this._calculateError = calculateUncorrelatedError;
break;
case 'both':
this._calculateError = calculateBothErrors;
break;
default:
throw new InputError(
'Wrong value for correlation: ' + options.correlation +
'. It must be one of correlated, uncorrelated and both'
);
}
}
ErrorPropagation.InputError = InputError;
ErrorPropagation.nerdamer = nerdamerNotSet;
ErrorPropagation.setEventEmitter = setEventEmitter;
ErrorPropagation.setEventEmitter(getEventEmitter());
return ErrorPropagation;
////////////////
function nerdamerNotSet() {
throw new Error('ErrorPropagation.nerdamer must be set');
}
function setEventEmitter(EventEmitter) {
ErrorPropagation.prototype = Object.create(EventEmitter.prototype);
ErrorPropagation.prototype.constructor = ErrorPropagation;
ErrorPropagation.prototype.calculate = calculate;
ErrorPropagation.prototype._calculate = _calculate;
}
function calculate(expression, variables) {
try {
return this._calculate(expression, variables);
} catch (err) {
this.emit('error', err);
}
}
function _calculate(expression, variables) {
this.emit('input', expression, variables);
if (typeof expression !== 'string') {
throw new InputError('expression must be a string. Got ' + typeof expression);
}
variables = variables || {};
var valueMap = getValueMap(variables);
var value = calculateValue(expression, valueMap);
var error = this._calculateError(expression, variables, valueMap, this);
var result = {
value: value,
error: error
};
this.emit('result', result);
return result;
}
function calculateValue(expression, valueMap, expressionStorage) {
var e = ErrorPropagation.nerdamer(expression);
ErrorPropagation.nerdamer.clear('last');
if (expressionStorage) {
expressionStorage.e = e;
}
return e.evaluate(valueMap).valueOf();
}
function calculateErrors(expression, variables, valueMap, emitter) {
var errors = [];
for (var variableName in variables) {
var variable = variables[variableName];
if (variable.error) {
var expressionStorage = {};
var diffExpression = 'diff(' + expression + ',' + variableName + ')';
var evaluatedDiffValue = calculateValue(diffExpression, valueMap, expressionStorage);
var variableError = evaluatedDiffValue * variable.error;
emitter.emit('differential', {
value: evaluatedDiffValue,
variableName: variableName,
variable: variable,
valueWithError: variableError,
expression: expressionStorage.e
});
errors.push(variableError);
}
}
return errors;
}
function calculateCorrelatedError(expression, variables, valueMap, emitter) {
var error = 0;
var errors = calculateErrors(expression, variables, valueMap, emitter);
errors.forEach(function(variableError) {
error += variableError;
});
return error;
}
function calculateUncorrelatedError(expression, variables, valueMap, emitter) {
var error = 0;
var errors = calculateErrors(expression, variables, valueMap, emitter);
errors.forEach(function(variableError) {
error += Math.pow(variableError, 2);
});
return Math.sqrt(error);
}
function calculateBothErrors(expression, variables, valueMap, emitter) {
var correlatedError = 0;
var uncorrelatedError = 0;
var errors = calculateErrors(expression, variables, valueMap, emitter);
errors.forEach(function(variableError) {
correlatedError += variableError;
uncorrelatedError += Math.pow(variableError, 2);
});
return {
correlated: correlatedError,
uncorrelated: Math.sqrt(uncorrelatedError)
};
}
function getValueMap(variables) {
var map = {};
for (var variableName in variables) {
var variable = variables[variableName];
verifyVariable(variable, variableName);
map[variableName] = variable.value;
}
return map;
}
function verifyVariable(variable, variableName) {
if (typeof variable !== 'object') {
throw new InputError('Variable ' + variableName + ' is not an object.' +
' Got ' + typeof variable);
}
if (variable === null) {
throw new InputError('Variable ' + variableName + ' must not be null');
}
if (!('value' in variable)) {
throw new InputError('No value for variable ' + variableName);
}
}
function getEventEmitter() {
var EventEmitter = {
prototype: {
on: function() {
throw new Error('An EventEmitter library needs to be set using' +
' HermiteInterpolation.setEventEmitter(EventEmitter) method.' +
' The /on/ function cannot be used otherwise');
},
emit: function(eventName, param) {
if (eventName === 'error') {
throw param;
}
}
}
};
if (typeof module === 'object' && module.exports) {
try {
EventEmitter = require('events').EventEmitter;
} catch (err) {
// noop
}
}
return EventEmitter;
}
}));