genetic-search
Version:
Multiprocessing genetic algorithm implementation library
530 lines • 17.8 kB
JavaScript
;
var __values = (this && this.__values) || function(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.fullCopyObject = exports.ArrayManager = exports.IdGenerator = void 0;
exports.round = round;
exports.createFilledArray = createFilledArray;
exports.arraySum = arraySum;
exports.arrayMean = arrayMean;
exports.arrayMedian = arrayMedian;
exports.arrayBinaryOperation = arrayBinaryOperation;
exports.getRandomArrayItem = getRandomArrayItem;
exports.normalizePhenomeRow = normalizePhenomeRow;
exports.normalizePhenomeMatrixColumns = normalizePhenomeMatrixColumns;
exports.normalizePhenomeMatrix = normalizePhenomeMatrix;
exports.createEmptyStatSummary = createEmptyStatSummary;
exports.createEmptyGroupedStatSummary = createEmptyGroupedStatSummary;
exports.createEmptyRangeStatSummary = createEmptyRangeStatSummary;
exports.calcStatSummary = calcStatSummary;
exports.calcRangeStatSummary = calcRangeStatSummary;
exports.roundStatSummary = roundStatSummary;
exports.roundGroupedStatSummary = roundGroupedStatSummary;
exports.roundRangeStatSummary = roundRangeStatSummary;
exports.createEvaluatedPopulation = createEvaluatedPopulation;
exports.extractEvaluatedPopulation = extractEvaluatedPopulation;
var itertools_1 = require("./itertools");
/**
* Generates unique identifiers for genomes.
*
* @template TGenome The type of genome objects in the population.
*
* @category Utils
*/
var IdGenerator = /** @class */ (function () {
function IdGenerator() {
this.id = 1;
}
IdGenerator.prototype.nextId = function () {
return this.id++;
};
IdGenerator.prototype.reset = function (population) {
this.id = population.reduce(function (max, genome) { return Math.max(max, genome.id); }, 0) + 1;
};
return IdGenerator;
}());
exports.IdGenerator = IdGenerator;
/**
* Manages an array of `T` objects.
*
* @template T The type of the objects to manage.
*/
var ArrayManager = /** @class */ (function () {
/**
* Creates a new `ArrayManager` from an array of `T` objects.
*
* @param data The array of `T` objects to manage.
*/
function ArrayManager(data) {
this._data = data;
}
/**
* Updates elements of the managed array that match the given filter.
*
* @param filter A function that returns true if the element should be updated.
* @param update A function that updates the element.
*
* @returns The updated items.
*/
ArrayManager.prototype.update = function (filter, update) {
var e_1, _a;
var updated = [];
try {
for (var _b = __values(this._data), _c = _b.next(); !_c.done; _c = _b.next()) {
var genome = _c.value;
if (filter(genome)) {
update(genome);
updated.push(genome);
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
}
finally { if (e_1) throw e_1.error; }
}
return updated;
};
/**
* Removes elements of the managed array that match the given filter and optionally sorts the rest of the array.
*
* @param filter A function that returns true if the element should be removed.
* @param maxCount The maximum number of elements to remove.
* @param order The order to sort the remaining elements.
*
* @returns The removed items.
*/
ArrayManager.prototype.remove = function (filter, maxCount, order) {
var e_2, _a;
if (maxCount === void 0) { maxCount = Infinity; }
if (order === void 0) { order = 'asc'; }
var buf = __spreadArray([], __read(this._data), false);
if (order === 'desc') {
buf.reverse();
}
this._data.length = 0;
var removed = [];
try {
for (var buf_1 = __values(buf), buf_1_1 = buf_1.next(); !buf_1_1.done; buf_1_1 = buf_1.next()) {
var genome = buf_1_1.value;
if (filter(genome) && removed.length < maxCount) {
removed.push(genome);
}
else {
this._data.push(genome);
}
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (buf_1_1 && !buf_1_1.done && (_a = buf_1.return)) _a.call(buf_1);
}
finally { if (e_2) throw e_2.error; }
}
if (order === 'desc') {
this._data.reverse();
}
return removed;
};
return ArrayManager;
}());
exports.ArrayManager = ArrayManager;
/**
* Creates a deep copy of an object.
*
* @template T The type of the object to copy.
* @param obj The object to copy.
* @returns A deep copy of the object.
*
* @category Utils
*/
var fullCopyObject = function (obj) { return JSON.parse(JSON.stringify(obj)); };
exports.fullCopyObject = fullCopyObject;
/**
* Rounds a number to a given precision.
*
* @param value The number to round.
* @param precision The precision to round to.
* @returns The rounded number.
*
* @category Utils
*/
function round(value, precision) {
return Number(value.toFixed(precision));
}
/**
* Generates an array of a given length, filled with a specified value.
*
* @template T The type of the values in the array.
* @param length The length of the array to generate.
* @param value The value to fill the array with.
* @returns An array of the specified length, filled with the specified value.
*
* @category Utils
*/
function createFilledArray(length, value) {
return Array.from({ length: length }, function () { return value; });
}
/**
* Calculates the sum of an array of numbers.
*
* @param input The array of numbers to sum.
* @returns The sum of the input array.
*
* @category Utils
*/
function arraySum(input) {
return input.reduce(function (acc, val) { return acc + val; }, 0);
}
/**
* Calculates the mean of an array of numbers.
*
* @param input The array of numbers to calculate the mean of.
* @returns The mean of the input array.
*
* @category Utils
*/
function arrayMean(input) {
return arraySum(input) / input.length;
}
/**
* Calculates the median of a sorted array of numbers.
*
* @param sortedInput The sorted array of numbers to find the median of.
* @returns The median value of the input array.
*
* @category Utils
*/
function arrayMedian(sortedInput) {
var middleIndex = Math.floor(sortedInput.length / 2);
if (sortedInput.length % 2 !== 0) {
return sortedInput[middleIndex];
}
return (sortedInput[middleIndex - 1] + sortedInput[middleIndex]) / 2;
}
/**
* Applies a binary operator to two arrays of the same length.
*
* @template T The type of the values in the arrays.
* @param lhs The left-hand side array.
* @param rhs The right-hand side array.
* @param operator The binary operator to apply.
* @returns A new array with the result of applying the operator to each pair of values.
*
* @category Utils
*/
function arrayBinaryOperation(lhs, rhs, operator) {
var result = [];
var len = Math.min(lhs.length, rhs.length);
for (var i = 0; i < len; ++i) {
result.push(operator(lhs[i], rhs[i]));
}
return result;
}
/**
* Returns a random element from the input array.
*
* @template T The type of the values in the array.
* @param input The array to select a random element from.
* @returns A random element from the input array.
*
* @category Utils
*/
function getRandomArrayItem(input) {
return input[Math.floor(Math.random() * input.length)];
}
/**
* Normalizes an array of numbers to the range from -1 to 1, where the `reference` value is mapped to 0.
*
* @param input The array of numbers to normalize.
* @param reference The reference value to map to 0.
* @returns The normalized array of numbers.
*
* @category Utils
*/
function normalizePhenomeRow(input, reference) {
// Find the minimum and maximum values in the array
var minVal = Math.min.apply(Math, __spreadArray([], __read(input), false));
var maxVal = Math.max.apply(Math, __spreadArray([], __read(input), false));
// Calculate the maximum distance relative to the reference
var maxDistance = Math.max(Math.abs(maxVal - reference), Math.abs(minVal - reference));
var denominator = maxDistance || 1;
return input.map(function (num) {
// Normalize each number to the range from -1 to 1, where reference = 0
return (num - reference) / denominator;
});
}
/**
* Normalizes the columns of a matrix of phenome to the range from -1 to 1, where the `reference` value is mapped to 0.
*
* @param input The matrix of phenome to normalize.
* @param reference The reference value to map to 0.
* @returns The normalized matrix of phenome.
*
* @category Utils
*/
function normalizePhenomeMatrixColumns(input, reference) {
var result = (0, exports.fullCopyObject)(input);
if (result.length === 0) {
return result;
}
var _loop_1 = function (i) {
var columnNormalized = normalizePhenomeRow(result.map(function (row) { return row[i]; }), reference[i]);
for (var j = 0; j < result.length; j++) {
result[j][i] = columnNormalized[j];
}
};
for (var i = 0; i < result[0].length; i++) {
_loop_1(i);
}
return result;
}
/**
* Normalizes the columns of a matrix of phenome to the range from -1 to 1, where the `reference` value is mapped to 0.
*
* @param matrix The matrix of phenome to normalize.
* @param reference The reference value to map to 0.
* @param abs Whether to take the absolute value of the normalized values.
* @returns The normalized matrix of phenome.
*
* @category Utils
*/
function normalizePhenomeMatrix(matrix, reference, abs) {
if (abs === void 0) { abs = true; }
var result = normalizePhenomeMatrixColumns(matrix, reference);
if (abs) {
return result.map(function (row) { return row.map(function (x) { return Math.abs(x); }); });
}
return result;
}
/**
* Creates an empty `StatSummary` object.
*
* A `StatSummary` object contains the count of genomes in the population,
* as well as the best, second best, mean, median, and worst values.
* This function initializes a `StatSummary` with all values set to zero.
*
* @returns An initialized `StatSummary` object with all fields set to zero.
*
* @category Utils
*/
function createEmptyStatSummary() {
return {
count: 0,
best: 0,
second: 0,
mean: 0,
median: 0,
worst: 0,
};
}
/**
* Creates an empty `GroupedStatSummary` object.
*
* A `GroupedStatSummary` object contains a summary of the statistics of a population of genomes,
* grouped by origin into three categories: initial, crossover, and mutation.
* This function initializes a `GroupedStatSummary` with all values set to zero.
*
* @returns An initialized `GroupedStatSummary` object with all fields set to zero.
*
* @category Utils
*/
function createEmptyGroupedStatSummary() {
return {
initial: createEmptyStatSummary(),
crossover: createEmptyStatSummary(),
mutation: createEmptyStatSummary(),
};
}
/**
* Creates an empty `RangeStatSummary` object.
*
* A `RangeStatSummary` object contains the minimum, mean, and maximum values
* for a set of numerical data.
* This function initializes a `RangeStatSummary` with all values set to zero.
*
* @returns An initialized `RangeStatSummary` object with all fields set to zero.
*
* @category Utils
*/
function createEmptyRangeStatSummary() {
return {
min: 0,
mean: 0,
max: 0,
};
}
/**
* Calculates a summary of the statistics for a sorted array of numbers.
*
* The summary includes the count of numbers in the array,
* as well as the best, second best, mean, median, and worst values.
*
* @param sortedSource The sorted array of numbers.
* @returns A summary of the statistics for the array of numbers.
*
* @category Utils
*/
function calcStatSummary(sortedSource) {
var _a;
if (sortedSource.length === 0) {
return createEmptyStatSummary();
}
return {
count: sortedSource.length,
best: sortedSource[0],
second: (_a = sortedSource[1]) !== null && _a !== void 0 ? _a : 0,
mean: arrayMean(sortedSource),
median: arrayMedian(sortedSource),
worst: sortedSource[sortedSource.length - 1],
};
}
/**
* Calculates a summary of the statistics for a sorted array of numbers.
*
* The summary includes the minimum, mean, and maximum values
* for a set of numerical data.
*
* @param source The array of numbers.
* @returns A summary of the statistics for the array of numbers.
*
* @category Utils
*/
function calcRangeStatSummary(source) {
if (source.length === 0) {
return createEmptyRangeStatSummary();
}
return {
min: Math.min.apply(Math, __spreadArray([], __read(source), false)),
mean: arrayMean(source),
max: Math.max.apply(Math, __spreadArray([], __read(source), false)),
};
}
/**
* Rounds the fields of a StatSummary object to a given precision.
*
* @param summary The StatSummary object to round.
* @param precision The number of decimal places to round to.
* @returns A new StatSummary object with rounded fields.
*
* @category Utils
*/
function roundStatSummary(summary, precision) {
return {
count: summary.count,
best: round(summary.best, precision),
second: round(summary.second, precision),
mean: round(summary.mean, precision),
median: round(summary.median, precision),
worst: round(summary.worst, precision),
};
}
/**
* Rounds the fields of a GroupedStatSummary object to a given precision.
*
* This function rounds the statistics in each category of the GroupedStatSummary
* (initial, crossover, mutation) to the specified number of decimal places.
*
* @param summary The GroupedStatSummary object to round.
* @param precision The number of decimal places to round to.
* @returns A new GroupedStatSummary object with rounded fields.
*
* @category Utils
*/
function roundGroupedStatSummary(summary, precision) {
return {
initial: roundStatSummary(summary.initial, precision),
crossover: roundStatSummary(summary.crossover, precision),
mutation: roundStatSummary(summary.mutation, precision),
};
}
/**
* Rounds the fields of a RangeStatSummary object to a given precision.
*
* This function rounds the minimum, mean, and maximum values of the RangeStatSummary
* to the specified number of decimal places.
*
* @param summary The RangeStatSummary object to round.
* @param precision The number of decimal places to round to.
* @returns A new RangeStatSummary object with rounded fields.
*
* @category Utils
*/
function roundRangeStatSummary(summary, precision) {
return {
min: round(summary.min, precision),
mean: round(summary.mean, precision),
max: round(summary.max, precision),
};
}
/**
* Creates an array of `EvaluatedGenome` objects from a population and its fitness and phenome.
*
* @param population The population of genomes.
* @param fitnessColumn The fitness column of the population.
* @param phenomeMatrix The phenome matrix of the population.
* @returns An array of EvaluatedGenome objects.
*
* @category Utils
*/
function createEvaluatedPopulation(population, fitnessColumn, phenomeMatrix) {
var zipped = __spreadArray([], __read((0, itertools_1.zip)(population, fitnessColumn, phenomeMatrix)), false);
return zipped.map(function (_a) {
var _b = __read(_a, 3), genome = _b[0], fitness = _b[1], phenome = _b[2];
return ({ genome: genome, fitness: fitness, phenome: phenome });
});
}
/**
* Extracts the population, fitness column, and phenome matrix from an array of
* [[EvaluatedGenome]] objects.
*
* @param input The array of [[EvaluatedGenome]] objects.
* @returns An array of three elements: the population, fitness column, and phenome matrix.
*
* @category Utils
*/
function extractEvaluatedPopulation(input) {
return [
input.map(function (x) { return x.genome; }),
input.map(function (x) { return x.fitness; }),
input.map(function (x) { return x.phenome; }),
];
}
//# sourceMappingURL=utils.js.map