Add neat arena
This commit is contained in:
214
src/lib/neatArena/genome.ts
Normal file
214
src/lib/neatArena/genome.ts
Normal file
@@ -0,0 +1,214 @@
|
||||
/**
|
||||
* NEAT Genome Implementation
|
||||
*
|
||||
* Represents a neural network genome with node genes and connection genes.
|
||||
* Implements the core NEAT genome structure as described in the original paper.
|
||||
*/
|
||||
|
||||
export type NodeType = 'input' | 'hidden' | 'output';
|
||||
export type ActivationFunction = 'tanh' | 'sigmoid' | 'relu' | 'linear';
|
||||
|
||||
/**
|
||||
* Node gene - represents a neuron
|
||||
*/
|
||||
export interface NodeGene {
|
||||
id: number;
|
||||
type: NodeType;
|
||||
activation: ActivationFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connection gene - represents a synapse
|
||||
*/
|
||||
export interface ConnectionGene {
|
||||
innovation: number;
|
||||
from: number;
|
||||
to: number;
|
||||
weight: number;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete genome
|
||||
*/
|
||||
export interface Genome {
|
||||
nodes: NodeGene[];
|
||||
connections: ConnectionGene[];
|
||||
fitness: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Global innovation tracker for historical markings
|
||||
*/
|
||||
export class InnovationTracker {
|
||||
private currentInnovation: number = 0;
|
||||
private innovationHistory: Map<string, number> = new Map();
|
||||
|
||||
/**
|
||||
* Get or create innovation number for a connection
|
||||
*/
|
||||
getInnovation(from: number, to: number): number {
|
||||
const key = `${from}->${to}`;
|
||||
|
||||
if (this.innovationHistory.has(key)) {
|
||||
return this.innovationHistory.get(key)!;
|
||||
}
|
||||
|
||||
const innovation = this.currentInnovation++;
|
||||
this.innovationHistory.set(key, innovation);
|
||||
return innovation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset innovation tracking (useful for new experiments)
|
||||
*/
|
||||
reset(): void {
|
||||
this.currentInnovation = 0;
|
||||
this.innovationHistory.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current innovation count
|
||||
*/
|
||||
getCurrentInnovation(): number {
|
||||
return this.currentInnovation;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a minimal genome with only input and output nodes, fully connected
|
||||
*/
|
||||
export function createMinimalGenome(
|
||||
inputCount: number,
|
||||
outputCount: number,
|
||||
innovationTracker: InnovationTracker
|
||||
): Genome {
|
||||
const nodes: NodeGene[] = [];
|
||||
const connections: ConnectionGene[] = [];
|
||||
|
||||
// Create input nodes (IDs 0 to inputCount-1)
|
||||
for (let i = 0; i < inputCount; i++) {
|
||||
nodes.push({
|
||||
id: i,
|
||||
type: 'input',
|
||||
activation: 'linear',
|
||||
});
|
||||
}
|
||||
|
||||
// Create output nodes (IDs starting from inputCount)
|
||||
for (let i = 0; i < outputCount; i++) {
|
||||
nodes.push({
|
||||
id: inputCount + i,
|
||||
type: 'output',
|
||||
activation: 'tanh',
|
||||
});
|
||||
}
|
||||
|
||||
// Create fully connected minimal genome
|
||||
for (let i = 0; i < inputCount; i++) {
|
||||
const inputNode = i; // Assuming inputNode refers to the ID
|
||||
|
||||
for (let o = 0; o < outputCount; o++) {
|
||||
const outputNode = inputCount + o; // Assuming outputNode refers to the ID
|
||||
const innovation = innovationTracker.getInnovation(inputNode, outputNode);
|
||||
|
||||
connections.push({
|
||||
innovation,
|
||||
from: inputNode,
|
||||
to: outputNode,
|
||||
weight: (Math.random() * 4) - 2, // Random weight in [-2, 2] for initial diversity
|
||||
enabled: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
nodes,
|
||||
connections,
|
||||
fitness: 0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone a genome (deep copy)
|
||||
*/
|
||||
export function cloneGenome(genome: Genome): Genome {
|
||||
return {
|
||||
nodes: genome.nodes.map(n => ({ ...n })),
|
||||
connections: genome.connections.map(c => ({ ...c })),
|
||||
fitness: genome.fitness,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get next available node ID
|
||||
*/
|
||||
export function getNextNodeId(genome: Genome): number {
|
||||
return Math.max(...genome.nodes.map(n => n.id)) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a connection already exists
|
||||
*/
|
||||
export function connectionExists(genome: Genome, from: number, to: number): boolean {
|
||||
return genome.connections.some(c => c.from === from && c.to === to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if adding a connection would create a cycle (for feedforward networks)
|
||||
*/
|
||||
export function wouldCreateCycle(genome: Genome, from: number, to: number): boolean {
|
||||
// Build adjacency list
|
||||
const adj = new Map<number, number[]>();
|
||||
for (const node of genome.nodes) {
|
||||
adj.set(node.id, []);
|
||||
}
|
||||
|
||||
for (const conn of genome.connections) {
|
||||
if (!conn.enabled) continue;
|
||||
if (!adj.has(conn.from)) adj.set(conn.from, []);
|
||||
adj.get(conn.from)!.push(conn.to);
|
||||
}
|
||||
|
||||
// Add the proposed connection
|
||||
if (!adj.has(from)) adj.set(from, []);
|
||||
adj.get(from)!.push(to);
|
||||
|
||||
// DFS to detect cycle
|
||||
const visited = new Set<number>();
|
||||
const recStack = new Set<number>();
|
||||
|
||||
const hasCycle = (nodeId: number): boolean => {
|
||||
visited.add(nodeId);
|
||||
recStack.add(nodeId);
|
||||
|
||||
const neighbors = adj.get(nodeId) || [];
|
||||
for (const neighbor of neighbors) {
|
||||
if (!visited.has(neighbor)) {
|
||||
if (hasCycle(neighbor)) return true;
|
||||
} else if (recStack.has(neighbor)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
recStack.delete(nodeId);
|
||||
return false;
|
||||
};
|
||||
|
||||
// Check from the 'from' node
|
||||
return hasCycle(from);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize genome to JSON
|
||||
*/
|
||||
export function serializeGenome(genome: Genome): string {
|
||||
return JSON.stringify(genome, null, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize genome from JSON
|
||||
*/
|
||||
export function deserializeGenome(json: string): Genome {
|
||||
return JSON.parse(json);
|
||||
}
|
||||
Reference in New Issue
Block a user