genetic-search
Version:
Multiprocessing genetic algorithm implementation library
151 lines (122 loc) • 4.19 kB
text/typescript
import {
BaseGenome,
BasePhenomeStrategy,
CrossoverStrategyInterface,
FitnessStrategyInterface,
GenerationFitnessColumn,
GenerationPhenomeMatrix,
PhenomeRow,
PhenomeStrategyConfig,
MutationStrategyInterface,
PopulateStrategyInterface,
Population,
IdGeneratorInterface,
} from "../../src";
export type TravelingGenome = BaseGenome & {
id: number;
path: number[];
}
export type TravelingTaskConfig = [number[], number[][]];
export type TravelingSinglePhenomeStrategyConfig = PhenomeStrategyConfig<TravelingTaskConfig> & {
distanceMatrix: number[][];
}
export function travelingPhenomeTask(data: TravelingTaskConfig): Promise<PhenomeRow> {
const [path, distanceMatrix] = data;
let totalDistance = 0;
for (let i = 0; i < path.length; i++) {
const from = path[i];
const to = path[(i + 1) % path.length];
totalDistance += distanceMatrix[from][to];
}
return Promise.resolve([1 / totalDistance]);
}
export function calcPathDistance(path: number[], distanceMatrix: number[][]): number {
let totalDistance = 0;
for (let i = 0; i < path.length; i++) {
const from = path[i];
const to = path[(i + 1) % path.length];
totalDistance += distanceMatrix[from][to];
}
return totalDistance;
}
export class TravelingPopulateStrategy implements PopulateStrategyInterface<TravelingGenome> {
private pathSize: number;
constructor(pathSize: number) {
this.pathSize = pathSize;
}
populate(size: number, idGenerator: IdGeneratorInterface<TravelingGenome>): Population<TravelingGenome> {
const population = [];
for (let i = 0; i < size; i++) {
const path = Array.from({length: this.pathSize}, (_, index) => index).sort();
population.push({id: idGenerator.nextId(), path});
}
return population;
}
}
export class TravelingMutationStrategy implements MutationStrategyInterface<TravelingGenome> {
mutate(genome: TravelingGenome, newGenomeId: number): TravelingGenome {
const leftIndex = Math.floor(Math.random() * genome.path.length);
const rightIndex = Math.floor(Math.random() * genome.path.length);
const mutatedPath = [...genome.path];
[mutatedPath[leftIndex], mutatedPath[rightIndex]] = [mutatedPath[rightIndex], mutatedPath[leftIndex]];
return {
id: newGenomeId,
path: mutatedPath,
};
}
}
export class TravelingCrossoverStrategy implements CrossoverStrategyInterface<TravelingGenome> {
cross(parents: TravelingGenome[], newGenomeId: number): TravelingGenome {
const [lhs, rhs] = parents;
const length = lhs.path.length;
const start = Math.floor(Math.random() * length);
const end = Math.floor(Math.random() * (length - start)) + start;
const childPath = new Array(length).fill(-1);
for (let i = start; i <= end; i++) {
childPath[i] = lhs.path[i];
}
let currentIndex = 0;
for (let i = 0; i < length; i++) {
if (!childPath.includes(rhs.path[i])) {
while (childPath[currentIndex] !== -1) {
currentIndex++;
}
childPath[currentIndex] = rhs.path[i];
}
}
return {
id: newGenomeId,
path: childPath,
};
}
}
export class TravelingSinglePhenomeStrategy extends BasePhenomeStrategy<
TravelingGenome,
TravelingSinglePhenomeStrategyConfig,
TravelingTaskConfig
> {
protected createTaskInput(genome: TravelingGenome): TravelingTaskConfig {
return [genome.path, this.config.distanceMatrix];
}
}
export class TravelingFitnessStrategy implements FitnessStrategyInterface {
score(results: GenerationPhenomeMatrix): GenerationFitnessColumn {
return results.map((result) => result[0]);
}
}
export function getPermutations(n: number): number[][] {
const results: number[][] = [];
const permute = (current: number[], remaining: number[]) => {
if (remaining.length === 0) {
results.push([...current]);
return;
}
for (let i = 0; i < remaining.length; i++) {
const next = remaining[i];
const newRemaining = remaining.filter((_, index) => index !== i);
permute([...current, next], newRemaining);
}
};
permute([], Array.from({ length: n }, (_, i) => i));
return results;
}