tycho-solver
Version:
Evolutionary computation and optimization library
139 lines • 5.7 kB
JavaScript
import { SequentialOperator } from '../../../core/pipeline/SequentialOperator';
// Import default implementations
import { GAInitializationOperator } from './InitializationOperator';
import { GAEvaluationOperator } from './EvaluationOperator';
import { GATerminationOperator } from './TerminationOperator';
import { SelectionOperatorImpl } from './SelectionOperator';
import { CrossoverOperatorImpl } from './CrossoverOperator';
import { MutationOperatorImpl } from './MutationOperator';
import { ReplacementOperatorImpl } from './ReplacementOperator';
import { ElitismOperatorImpl } from './ElitismOperator';
export const GALoopOperator = async ({ population, initializationOperator, evaluationOperator, selectionOperator, crossoverOperator, mutationOperator, replacementOperator, elitismOperator, terminationOperator, fitnessFunction, maxGenerations, eliteCount = 0, fitnessLimit, populationSize = 100, // default size if not provided
rng }) => {
// --- InitializationOperator creates the population if not provided ---
const initOp = initializationOperator || new GAInitializationOperator();
let pop = population ? population : (await (initOp.initialize ? initOp.initialize({ populationSize, rng }) : []));
if (!pop || pop.length === 0)
throw new Error('Population could not be initialized.');
// --- EvaluationOperator ---
const isSyncFitness = (fn) => {
try {
const res = fn(pop[0]);
return !(res instanceof Promise);
}
catch {
return true;
}
};
let evalOp;
if (evaluationOperator) {
evalOp = evaluationOperator;
}
else if (isSyncFitness(fitnessFunction)) {
evalOp = new GAEvaluationOperator(fitnessFunction);
}
else {
evalOp = { evaluate: (solution) => fitnessFunction(solution) };
}
// --- Evaluate initial population ---
let fitnesses = await Promise.all(pop.map(ind => evalOp.evaluate(ind)));
let bestSolution = pop[0];
let bestFitness = Math.max(...fitnesses);
let generation = 0;
// --- ElitismOperator ---
const elitOp = elitismOperator || new ElitismOperatorImpl();
// --- ReplacementOperator (now handles elitism internally) ---
const replOp = replacementOperator || new ReplacementOperatorImpl({
elitismOperator: elitOp,
eliteCount,
fitnessFunction
});
// --- SelectionOperator ---
const selectOp = selectionOperator || new SelectionOperatorImpl(rng);
// --- CrossoverOperator ---
let crossOp;
if (crossoverOperator) {
crossOp = crossoverOperator;
}
else {
try {
crossOp = new CrossoverOperatorImpl(rng);
}
catch {
throw new Error('No default crossover operator for this individual type. Please provide one.');
}
}
// --- MutationOperator ---
let mutOp;
if (mutationOperator) {
mutOp = mutationOperator;
}
else {
try {
// MutationOperatorImpl is implemented for array-like individuals.
// Instantiate it as `any` and cast to `MutationOperator<T>` to satisfy callers.
mutOp = new MutationOperatorImpl(undefined, undefined, rng);
}
catch {
throw new Error('No default mutation operator for this individual type. Please provide one.');
}
}
// --- TerminationOperator ---
const termOp = terminationOperator || new GATerminationOperator();
// --- Main Evolutionary Loop ---
// Define pipeline steps as pipeline operators
const selectionStep = {
apply: (currentPop) => selectOp.select(currentPop, fitnesses, currentPop.length)
};
const crossoverMutationStep = {
apply: async (parents) => {
let offspring = [];
for (let i = 0; i < parents.length; i += 2) {
const parent1 = parents[i];
const parent2 = parents[i + 1] || parents[0];
const crossRes = crossOp.crossover(parent1, parent2);
const children = (crossRes && typeof crossRes.then === 'function')
? await crossRes
: crossRes;
const child1 = children[0];
const child2 = children[1] !== undefined ? children[1] : children[0];
offspring.push(mutOp.mutate(child1));
offspring.push(mutOp.mutate(child2));
}
return offspring;
}
};
const replacementStep = {
apply: async (offspring) => await replOp.replace(pop, offspring, fitnesses)
};
const evaluationStep = {
apply: async (newPop) => {
fitnesses = await Promise.all(newPop.map(ind => evalOp.evaluate(ind)));
return newPop;
}
};
// Compose pipeline
const pipeline = new SequentialOperator([
selectionStep,
crossoverMutationStep,
replacementStep,
evaluationStep
]);
while (generation < maxGenerations && !termOp.shouldTerminate(pop)) {
// Apply pipeline steps
pop = await pipeline.apply(pop);
// Update best
const genBestIdx = fitnesses.indexOf(Math.max(...fitnesses));
const genBestFitness = fitnesses[genBestIdx];
const genBestSolution = pop[genBestIdx];
if (genBestFitness > bestFitness) {
bestFitness = genBestFitness;
bestSolution = genBestSolution;
}
if (fitnessLimit !== undefined && bestFitness >= fitnessLimit)
break;
generation++;
}
return { bestSolution, bestFitness, population: pop, generation };
};
//# sourceMappingURL=GALoopOperator.js.map