/*global require*/
// field
var _ = require('./core.utils');
var cloneDeep = _.cloneDeep;
var check = _.check;
var assert = _.assert;
var isVectorOfDimension = check.isVectorOfDimension;
var array2d = _.array2d;
var array1d = _.array1d;
var defineContract = _.defineContract;
var matrixOfDimension = assert.ensureMatrixOfDimension;
var PointSet = require('./geometry.pointset').PointSet;
var FeNodeSet = require('./fens').FeNodeSet;
var _input_contract_field_option_ = defineContract(function(o) {
assert.object(o);
if (check.assigned(o.values)) {
matrixOfDimension('*', '*', 'values is not a valid matrix.')(o.values);
} else if (check.assigned(o.nfens) && check.assigned(o.dim)) {
assert.integer(o.nfens);
assert.integer(o.dim);
if (o.nfens < 0) throw new Error('nfens must > 0.');
if (o.dim < 0) throw new Error('dim must > 0');
} else if (check.assigned(o.fens)) {
assert.instance(o.fens, FeNodeSet);
} else if (check.assigned(o.pointset)) {
assert.instance(o.pointset, PointSet);
} else {
throw new Error('');
}
if (check.assigned(o.ebcs)) {
assert.array(o.ebcs, 'ebcs must be a array of valid EBC object');
}
}, 'Input is not a valid Field option.');
/**
* @module field
*/
/**
* Test
* @param {Number} x
* @returns {Number}
*/
function test() {}
/**
* Field init option.
* @typedef {Object} module:field.FieldInitOption
* @property {Matrix|undefined} values
* @property {PointSet|undefined} pointset
* @property {FeNodeSet|undefined} fens
* @property {Number|undefined} nfens
* @property {Number|undefined} dim
*/
/**
* Field
* @class
* @param {module:field.FieldInitOption}
*/
exports.Field = function Field(options) {
_input_contract_field_option_(options);
this._values = null;
this._prescribed = null;
this._prescribedValues = null;
this._eqnums = null;
this._neqns = -1;
if (check.assigned(options.values)) {
this._values = new PointSet(options.values);
} else if (check.assigned(options.pointset)) {
this._values = options.pointset.clone();
} else if (check.assigned(options.fens)) {
this._values = new PointSet(options.fens.xyz());
} else if (check.assigned(options.nfens) && check.assigned(options.dim)) {
this._values = new PointSet(options.nfens, options.dim);
}
if (check.assigned(options.ebcs)) {
// TODO: merge ebcs
var prescribed = array2d(this.nfens(), this.dim(), false);
var prescribedValues = array2d(this.nfens(), this.dim(), 0);
this._prescribed = prescribed;
this._prescribedValues = prescribedValues;
options.ebcs.forEach(function(ebc) {
// TODO: make sure ebc object is valid;
ebc.applyToField_(this);
}, this);
}
};
var Field = exports.Field;
/**
* Returns number of nodes in the field.
* @returns {}
*/
exports.Field.prototype.nfens = function() {
return this._values.getSize();
};
/**
* Returns number of equations
* @returns {Number}
*/
exports.Field.prototype.neqns = function() {
if (!this._eqnums) this._numberEqnums_();
return this._neqns;
};
/**
* Returns dimension of the field.
* @returns {Number}
*/
exports.Field.prototype.dim = function() {
return this._values.getRn();
};
/**
* Returns the values as 2d js array.
* @returns {Array}
*/
exports.Field.prototype.values = function() {
return this._values.toList();
};
/**
* @callback Field~mapCallback
* @param {Array} vec - the vector value at node.
* @param {Number} i - index of the node.
* @returns {Array} transformed vector value.
*/
/**
* Returns a new transformed field by given mapping. No boudary
* conditions are preserved.
* @param {Field~mapCallback} fn - The mapping function that maps a
* vector to another vector.
* @returns {Field}
*/
exports.Field.prototype.map = function(fn) {
var newPointset = this._values.map(fn);
var newField = new Field({ pointset: newPointset });
return newField;
};
/**
* Returns an identical copy, preserves boundary conditions.
* @returns {Feild}
*/
exports.Field.prototype.clone = function() {
var newPointset = this._values.clone();
var newField = new Field({ pointset: newPointset });
newField._neqns = this._neqns;
newField._eqnums = cloneDeep(this._eqnums);
newField._prescribed = cloneDeep(this._prescribed);
newField._prescribedValues = cloneDeep(this._prescribedValues);
return newField;
};
/**
* @callback Field~bopFn
* @param {Array} vec - the vector value at node.
* @param {Number} i - index of the node.
* @returns {Array} transformed vector value.
*/
/**
* Returns a new field that holds the result of binary operation.
* @param {Number|Field|Array} other - A number, a field of same nfens
and dim or an array of length this.dim().
* @param {Field~bopFn} bopFn - binary operation function.
* @param {String} bopName - binary operation name, which will make
* better error message.
* @returns {Field} Result field.
*/
exports.Field.prototype._bop = function(other, bopFn, bopName) {
if (check.number(other)) {
return this.map(function(vec) {
return vec.map(function(x) {
return bopFn(x, other);
});
});
} else if (check.instance(other, Field) &&
other.dim() === this.dim() &&
other.nfens() === this.nfens()) {
return this.map(function(vec, i) {
return vec.map(function(x, j) {
return bopFn(x, other.at(i)[j]);
});
});
} else if (check.array(other) && other.length === this.dim()) {
return this.map(function(vec) {
return vec.map(function(x, j) {
return bopFn(x, other[j]);
});
});
} else
throw new Error('Field::' + bopName +
'(other, bopFn): other must be a number' +
' , a field of same this.nfens() and this.dim() or' +
' an array of length this.dim().');
};
/**
* Returns a new field that holds the result of binary operation.
* @param {Number|Field|Array} other - A number, a field of same nfens
and dim or an array of length this.dim().
* @param {Field~bopFn} bopFn - binary operation function.
* @returns {Field} Result field.
*/
exports.Field.prototype.bop = function(other, bopFn) {
return this._bop(other, bopFn, bopFn.name || 'custom binary function');
};
/**
* Returns a new field of the point-wise multiplication. No boudary
* conditions are preserved.
* @param {Number|Field|Array} other - A number, a field of same nfens
and dim or an array of length this.dim().
* @returns {Field} - muliplication of this and other.
*/
exports.Field.prototype.mul = function(other) {
return this._bop(other, function(a,b) { return a * b; }, 'mul');
};
exports.Field.prototype.scale = exports.Field.prototype.mul;
/**
* Returns a new field of the point-wise sumation. No boudary
* conditions are preserved.
* @param {Number|Field|Array} other - A number, a field of same nfens
and dim or an array of length this.dim().
* @returns {Field} - Sum of this and other.
*/
exports.Field.prototype.add = function(other) {
return this._bop(other, function(a,b) { return a + b; }, 'add');
};
/**
* Returns a new field of the point-wise substraction. No boudary
* conditions are preserved.
* @param {Number|Field|Array} other - A number, a field of same nfens
and dim or an array of length this.dim().
* @returns {Field} - substraction of this and other.
*/
exports.Field.prototype.sub = function(other) {
return this._bop(other, function(a,b) { return a - b; }, 'sub');
};
/**
* Returns a new field of the point-wise division. No boudary
* conditions are preserved.
* @param {Number|Field|Array} other - A number, a field of same nfens
and dim or an array of length this.dim().
* @returns {Field} - division of this and other.
*/
exports.Field.prototype.div = function(other) {
return this._bop(other, function(a,b) { return a / b; }, 'div');
};
/**
* Get value at index.
* @param {Number} idx - index, 0-based
* @return {Vector:this.dim()}
*/
exports.Field.prototype.at = function(idx) {
return this._values.get(idx);
};
/**
* Returns pointset object for visualization.
* @returns {PointSet}
*/
exports.Field.prototype.pointset = function() {
return this._values;
};
/**
* Returns whether node at given direction is prescribed.
* @param {Number} index - integer index of the node, 0-based.
* @param {Number} direction - dimension index, 0-based.
* @returns {Boolean}
*/
exports.Field.prototype.isPrescribed = function(index, direction) {
if (!this._prescribed) return false;
return this._prescribed[index][direction];
};
/**
* Returns prescribed value of the node at given direction. Return 0
* if the dof is not prescribed.
* @param {Number} index - integer index of the node, 0-based.
* @param {Number} direction - dimension index, 0-based.
* @returns {Number}
*/
exports.Field.prototype.prescribedValue = function(index, direction) {
if (this.isPrescribed(index, direction))
return this._prescribedValues[index][direction];
return 0;
};
/**
* Set prescribed value of the node at given direction.
* if the dof is not prescribed.
* @param {Number} index - integer index of the node, 0-based.
* @param {Number} dir - dimension index. 0-based.
* @param {Number} val - value.
*/
exports.Field.prototype.setPrescribedValue_ = function(index, dir, val) {
// console.log("val = ", val);
// console.log("dir = ", dir);
// console.log("id = ", id);
// console.log("this._prescribed = ", this._prescribed);
this._prescribed[index][dir] = true;
this._prescribedValues[index][dir] = val;
this._values.setAtDir_(index, dir, val);
};
exports.Field.INVALID_EQUATION_NUM = -1;
exports.Field.prototype._numberEqnums_ = function() {
var eqnums = array2d(this.nfens(), this.dim(), exports.Field.INVALID_EQUATION_NUM);
var count = 0, nfens = this.nfens(), dim = this.dim();
var i, j;
for (i = 0; i < nfens; ++i) {
for (j = 0; j < dim; ++j) {
if (!this.isPrescribed(i, j)) {
eqnums[i][j] = count++;
}
}
}
this._eqnums = eqnums;
this._neqns = count;
};
/**
* Returns the eqnum number at node with given direction.
* @param {Number} index - integer index of the node, 0-based.
* @param {Number} direction - dimension index, 0-based.
* @returns {Number} - equation number, 0-based.
*/
exports.Field.prototype.eqnum = function(index, direction) {
if (!this._eqnums) this._numberEqnums_();
if (index < 0 || index >= this.nfens()) throw new Error('Field::eqnum(): index out of range.');
if (direction < 0 || direction >= this.dim()) throw new Error('Field::eqnum(): direction out of range.');
return this._eqnums[index][direction];
};
/**
* Returns gathered eqnum numbers in an js array.
* @param {Array} conn - connectiviy vector.
* @returns {Array} - an array of equation numbers of length
* this.dim()*conn.length.
*/
exports.Field.prototype.gatherEqnumsVector = function(conn) {
var vec = [], dim = this.dim();
conn.forEach(function(fenid) {
var i, eqnum;
for (i = 0; i < dim; ++i) {
vec.push(this.eqnum(fenid, i));
}
}, this);
return vec;
};
/**
* Returns gathered values in an 2d js array.
* @param {Array} conn - connectiviy vector.
* @returns {Array} - a 2d js array of values of dimension conn.length
* by this.dim().
*/
exports.Field.prototype.gatherValuesMatrix = function(conn) {
var len = conn.length, dim = this.dim();
var mat = array1d(len, null);
conn.forEach(function(idx, i) {
mat[i] = this._values.get(idx);
}, this);
return mat;
};
/**
* Returns gathered values in an 2d js array.
* @param {Array} conn - connectiviy vector.
* @returns {Array} - a 2d js array of values of dimension conn.length
* by this.dim().
*/
exports.Field.prototype.gatherPrescirbedValues = function(conn) {
var vec = [], dim = this.dim();
conn.forEach(function(idx) {
var dir;
for (dir = 0; dir < dim; ++dir) {
vec.push(this.prescribedValue(idx, dir));
}
}, this);
return vec;
};
/**
* Scatter values to field. Returns updated field.
* @param {Array} vec - js array of length this.neqns();
* @returns {Field} - updated field.
*/
exports.Field.prototype.scatterSystemVector_ = function(vec) {
var neqns = this.neqns();
if (!isVectorOfDimension(vec, neqns))
throw new Error('Field::scatterSystemVector(): vec is not a vector of ' +
'dimension ' + neqns);
if (!this._eqnums) this._numberEqnums_();
var eqnums = this._eqnums;
var nfens = this.nfens();
var dim = this.dim();
var values = this._values;
var i, j, en, val;
for (i = 0; i < nfens; ++i) {
for (j = 0; j < dim; ++j) {
en = eqnums[i][j];
if (en !== exports.Field.INVALID_EQUATION_NUM) {
val = vec[en];
values.setAtDir_(i, j, val);
}
}
}
};
exports.Field = Field;