Package detail

tycho-solver

lemsy636MPL-2.00.2.3

Evolutionary computation and optimization library

evolutionary computation, genetic algorithm, ga, evolutionary

readme

Tycho Solver

A modular and extensible library for evolutionary computation and optimization problems.

Features

  • Genetic Algorithm implementation
  • Configurable selection, crossover and mutation operators
  • TypeScript support with full type definitions
  • Modular architecture for easy extension

Installation

npm install tycho-solver

Quick Start

import { GeneticAlgorithm } from 'tycho-solver';

// Define a simple problem: find a list of numbers that sum to a target value
const target = 100;
const populationSize = 50;
const numGenes = 10;

// Initialize with random numbers between 0 and 20
const initialPopulation = Array.from({ length: populationSize }, () => 
  Array.from({ length: numGenes }, () => Math.floor(Math.random() * 21))
);

// Fitness function: higher is better when sum is closer to target
const fitnessFunction = (individual: number[]) => {
  const sum = individual.reduce((a, b) => a + b, 0);
  return 1000 - Math.abs(sum - target);
};

// Configure and run the genetic algorithm
const ga = new GeneticAlgorithm(
  initialPopulation,
  fitnessFunction,
  {
    populationSize: populationSize,
    maxGenerations: 100,
    crossoverRate: 0.8,
    mutationRate: 0.2
  }
);

// Run the evolution
const solution = ga.evolve();
console.log('Best solution:', solution);
console.log('Fitness:', ga.getBestFitness());

Examples

Genetic Algorithm

The genetic algorithm is ideal for problems where you need to find solutions in a large search space:

import { GeneticAlgorithm } from 'tycho-solver';

// Example: Binary knapsack problem
// Items with values and weights
const items = [
  { value: 4, weight: 12 },
  { value: 2, weight: 2 },
  { value: 10, weight: 4 },
  { value: 1, weight: 1 },
  { value: 2, weight: 2 }
];
const maxWeight = 15;

// Initialize a random population (0 = item not taken, 1 = item taken)
const populationSize = 50;
const initialPopulation = Array.from({ length: populationSize }, () => 
  Array.from({ length: items.length }, () => Math.round(Math.random()))
);

// Fitness function - evaluate each solution
const fitnessFunction = (individual: number[]) => {
  const totalValue = individual.reduce((sum, gene, index) => 
    sum + (gene * items[index].value), 0);
  const totalWeight = individual.reduce((sum, gene, index) => 
    sum + (gene * items[index].weight), 0);

  // Return 0 fitness if solution exceeds weight constraint
  return totalWeight <= maxWeight ? totalValue : 0;
};

// Configure and run the genetic algorithm
const ga = new GeneticAlgorithm(
  initialPopulation,
  fitnessFunction,
  {
    populationSize: populationSize,
    maxGenerations: 100,
    crossoverRate: 0.8,
    mutationRate: 0.1,
    elitism: 2 // Keep the best 2 individuals
  }
);

// Run the evolution
const solution = ga.evolve();
console.log('Best solution:', solution);
console.log('Fitness:', ga.getBestFitness());

Memetic Algorithm

Memetic algorithms combine global search (genetic algorithm) with local search to refine solutions:

import { MemeticAlgorithm } from 'tycho-solver';

// Example: Traveling Salesman Problem (TSP)
const cities = [
  { x: 60, y: 200 },
  { x: 180, y: 200 },
  { x: 80, y: 180 },
  { x: 140, y: 180 },
  { x: 20, y: 160 },
  { x: 100, y: 160 },
  { x: 200, y: 160 }
];

const distance = (city1, city2) => {
  return Math.sqrt(Math.pow(city1.x - city2.x, 2) + Math.pow(city1.y - city2.y, 2));
};

const memeticOptions = {
  populationSize: 30,
  generations: 50,
  crossoverRate: 0.9,
  mutationRate: 0.2,
  localSearchRate: 0.3,
  initialize: () => {
    const indices = Array.from({ length: cities.length }, (_, i) => i);
    for (let i = indices.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [indices[i], indices[j]] = [indices[j], indices[i]];
    }
    return indices;
  },
  evaluate: (route) => {
    let totalDistance = 0;
    for (let i = 0; i < route.length; i++) {
      const from = cities[route[i]];
      const to = cities[(route[(i + 1) % route.length])];
      totalDistance += distance(from, to);
    }
    return 1000 / totalDistance;
  },
  select: (population) => {
    const tournament = (size = 3) => {
      const contestants = Array.from({ length: size }, () =>
        population[Math.floor(Math.random() * population.length)]
      );
      return contestants.reduce((best, ind) =>
        ind.fitness > best.fitness ? ind : best, contestants[0]
      );
    };
    return [tournament(), tournament()];
  },
  crossover: (parent1, parent2) => {
    const size = parent1.length;
    const start = Math.floor(Math.random() * size);
    const end = start + Math.floor(Math.random() * (size - start));
    const offspring = Array(size).fill(-1);
    for (let i = start; i <= end; i++) {
      offspring[i] = parent1[i];
    }
    let j = (end + 1) % size;
    for (let i = 0; i < size; i++) {
      const nextPos = (end + 1 + i) % size;
      if (offspring[nextPos] === -1) {
        while (offspring.includes(parent2[j])) {
          j = (j + 1) % size;
        }
        offspring[nextPos] = parent2[j];
      }
    }
    return offspring;
  },
  mutate: (genome) => {
    const genomeCopy = [...genome];
    const idx1 = Math.floor(Math.random() * genomeCopy.length);
    let idx2 = Math.floor(Math.random() * genomeCopy.length);
    while (idx1 === idx2) {
      idx2 = Math.floor(Math.random() * genomeCopy.length);
    }
    [genomeCopy[idx1], genomeCopy[idx2]] = [genomeCopy[idx2], genomeCopy[idx1]];
    return genomeCopy;
  },
  objectiveFunction: (route) => {
    let totalDistance = 0;
    for (let i = 0; i < route.length; i++) {
      const from = cities[route[i]];
      const to = cities[(route[(i + 1) % route.length])];
      totalDistance += distance(from, to);
    }
    return 1000 / totalDistance;
  },
  neighborhoodFunction: (route) => {
    const neighbors = [];
    for (let i = 0; i < route.length - 1; i++) {
      for (let j = i + 1; j < route.length; j++) {
        const newRoute = [...route];
        let left = i;
        let right = j;
        while (left < right) {
          [newRoute[left], newRoute[right]] = [newRoute[right], newRoute[left]];
          left++;
          right--;
        }
        neighbors.push(newRoute);
      }
    }
    return neighbors.slice(0, 10);
  },
  localSearchOptions: {
    maxIterations: 20,
    maximize: true
  }
};

// Run the memetic algorithm as a class
const memetic = new MemeticAlgorithm(memeticOptions);
memetic.initializePopulation().then(async () => {
  const bestSolution = await memetic.evolve();
  console.log('Best route:', bestSolution.genome);
  console.log('Fitness:', bestSolution.fitness);
});

Local Search

Local search algorithms are useful for refining solutions or solving optimization problems directly:

import { LocalSearch } from 'tycho-solver';

// Example: Function optimization - find minimum of a 2D function
// Function to optimize: f(x,y) = (x-2)² + (y-3)² + 1
const objectiveFunction = (solution: [number, number]): number => {
  const [x, y] = solution;
  return Math.pow(x - 2, 2) + Math.pow(y - 3, 2) + 1;
};

// Neighborhood function - generate nearby points
const neighborhoodFunction = (solution: [number, number]): [number, number][] => {
  const [x, y] = solution;
  const stepSize = 0.1;

  return [
    [x + stepSize, y],
    [x - stepSize, y],
    [x, y + stepSize],
    [x, y - stepSize],
    [x + stepSize, y + stepSize],
    [x - stepSize, y - stepSize],
    [x + stepSize, y - stepSize],
    [x - stepSize, y + stepSize]
  ];
};

// Create and configure the local search
const localSearch = new LocalSearch<[number, number]>();

// Initial solution [0, 0]
const initialSolution: [number, number] = [0, 0];

// Run the search
const result = localSearch.search(
  initialSolution,
  objectiveFunction,
  neighborhoodFunction,
  {
    maxIterations: 1000,
    maximize: false // We're minimizing the function
  }
);

console.log('Best solution found:', result.solution);
console.log('Objective value:', result.fitness);
console.log('Iterations performed:', result.iterations);

API Documentation

Core Types

// Fitness function that evaluates the quality of a solution
type FitnessFunction<T> = (individual: T) => number;

// Configuration options for evolutionary algorithms
interface EvolutionaryConfig {
  populationSize: number;
  maxGenerations: number;
  selectionPressure?: number;
  mutationRate?: number;
  crossoverRate?: number;
  elitism?: number;
}

GeneticAlgorithm

The main class for creating and running genetic algorithms:

class GeneticAlgorithm<T> implements EvolutionaryAlgorithm<T> {
  constructor(initialPopulation: T[], fitnessFunction: FitnessFunction<T>, config: EvolutionaryConfig);
  evolve(generations?: number): T;
  getBestSolution(): T;
  getBestFitness(): number;
  getPopulation(): T[];
  getGeneration(): number;
}

MemeticAlgorithm

A class for creating and running memetic algorithms that combine genetic algorithms with local search:

class MemeticAlgorithm<T> {
  constructor(config: MemeticOptions<T>);
  initializePopulation(): Promise<void>;
  evolve(): Promise<Individual<T>>;
  getBestIndividual(): Individual<T>;
  getPopulation(): Individual<T>[];
}

LocalSearch

A general-purpose local search algorithm implementation:

interface ObjectiveFunction<T> {
  (solution: T): number;
}

interface NeighborhoodFunction<T> {
  (solution: T): T[];
}

interface LocalSearchOptions {
  maxIterations?: number;  // Default: 1000
  maximize?: boolean;  // Default: true
}

interface LocalSearchResult<T> {
  solution: T;  // Best solution found
  fitness: number;  // Objective value of the best solution
  iterations: number;  // Number of iterations performed
}

class LocalSearch<T> {
  search(
    initialSolution: T,
    objectiveFunction: ObjectiveFunction<T>,
    neighborhoodFunction: NeighborhoodFunction<T>,
    options?: LocalSearchOptions
  ): LocalSearchResult<T>;
}

License

This project is licensed under the Mozilla Public License 2.0 - see the LICENSE file for details.