UNPKG

tycho-solver

Version:

Evolutionary computation and optimization library

139 lines 5.7 kB
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