export interface SimulationParameters {
length: number;
gcContent?: number;
model?: string;
seed?: number;
}
export interface MutationParameters {
substitutionRate?: number;
insertionRate?: number;
deletionRate?: number;
transitionBias?: number;
}
export interface EvolutionParameters {
generations: number;
populationSize: number;
mutationRate: number;
selectionPressure?: number;
}
export const DNA_BASES = ['A', 'T', 'G', 'C'] as const;
export const RNA_BASES = ['A', 'U', 'G', 'C'] as const;
export const AMINO_ACIDS = [
'A', 'R', 'N', 'D', 'C', 'Q', 'E', 'G', 'H', 'I',
'L', 'K', 'M', 'F', 'P', 'S', 'T', 'W', 'Y', 'V'
] as const;
export const GENETIC_CODE: Record<string, string> = {
'TTT': 'F', 'TTC': 'F', 'TTA': 'L', 'TTG': 'L',
'TCT': 'S', 'TCC': 'S', 'TCA': 'S', 'TCG': 'S',
'TAT': 'Y', 'TAC': 'Y', 'TAA': '*', 'TAG': '*',
'TGT': 'C', 'TGC': 'C', 'TGA': '*', 'TGG': 'W',
'CTT': 'L', 'CTC': 'L', 'CTA': 'L', 'CTG': 'L',
'CCT': 'P', 'CCC': 'P', 'CCA': 'P', 'CCG': 'P',
'CAT': 'H', 'CAC': 'H', 'CAA': 'Q', 'CAG': 'Q',
'CGT': 'R', 'CGC': 'R', 'CGA': 'R', 'CGG': 'R',
'ATT': 'I', 'ATC': 'I', 'ATA': 'I', 'ATG': 'M',
'ACT': 'T', 'ACC': 'T', 'ACA': 'T', 'ACG': 'T',
'AAT': 'N', 'AAC': 'N', 'AAA': 'K', 'AAG': 'K',
'AGT': 'S', 'AGC': 'S', 'AGA': 'R', 'AGG': 'R',
'GTT': 'V', 'GTC': 'V', 'GTA': 'V', 'GTG': 'V',
'GCT': 'A', 'GCC': 'A', 'GCA': 'A', 'GCG': 'A',
'GAT': 'D', 'GAC': 'D', 'GAA': 'E', 'GAG': 'E',
'GGT': 'G', 'GGC': 'G', 'GGA': 'G', 'GGG': 'G'
};
export class SequenceGenerator {
public rng: () => number;
constructor(seed?: number) {
if (seed !== undefined) {
let s = seed;
this.rng = () => {
s = Math.sin(s) * 10000;
return s - Math.floor(s);
};
} else {
this.rng = Math.random;
}
}
generateRandomDNA(length: number, gcContent: number = 0.5): string {
const gcProb = gcContent / 2;
const atProb = (1 - gcContent) / 2;
let sequence = '';
for (let i = 0; i < length; i++) {
const rand = this.rng();
if (rand < atProb) {
sequence += 'A';
} else if (rand < atProb * 2) {
sequence += 'T';
} else if (rand < atProb * 2 + gcProb) {
sequence += 'G';
} else {
sequence += 'C';
}
}
return sequence;
}
generateRandomProtein(length: number): string {
let sequence = '';
for (let i = 0; i < length; i++) {
const index = Math.floor(this.rng() * AMINO_ACIDS.length);
sequence += AMINO_ACIDS[index];
}
return sequence;
}
mutateDNA(sequence: string, params: MutationParameters): string {
const {
substitutionRate = 0.01,
insertionRate = 0.001,
deletionRate = 0.001,
transitionBias = 2.0
} = params;
let mutated = sequence.split('');
for (let i = 0; i < mutated.length; i++) {
if (this.rng() < substitutionRate) {
const currentBase = mutated[i];
let newBase: string;
if (this.rng() < transitionBias / (transitionBias + 1)) {
newBase = this.getTransition(currentBase);
} else {
newBase = this.getTransversion(currentBase);
}
mutated[i] = newBase;
}
if (this.rng() < insertionRate) {
const randomBase = DNA_BASES[Math.floor(this.rng() * DNA_BASES.length)];
mutated.splice(i, 0, randomBase);
i++;
}
if (this.rng() < deletionRate && mutated.length > 1) {
mutated.splice(i, 1);
i--;
}
}
return mutated.join('');
}
private getTransition(base: string): string {
switch (base) {
case 'A': return 'G';
case 'G': return 'A';
case 'T': return 'C';
case 'C': return 'T';
default: return base;
}
}
private getTransversion(base: string): string {
const transversions: Record<string, string[]> = {
'A': ['T', 'C'],
'T': ['A', 'G'],
'G': ['T', 'C'],
'C': ['A', 'G']
};
const options = transversions[base] || [base];
return options[Math.floor(this.rng() * options.length)];
}
evolveSequence(sequence: string, params: EvolutionParameters): string[] {
let population = Array(params.populationSize).fill(sequence);
const generations = [sequence];
for (let gen = 0; gen < params.generations; gen++) {
population = population.map(seq =>
this.mutateDNA(seq, { substitutionRate: params.mutationRate })
);
if (params.selectionPressure) {
population.sort((a, b) => this.calculateFitness(b) - this.calculateFitness(a));
population = population.slice(0, Math.floor(params.populationSize / 2));
population = population.concat(population);
}
generations.push(population[0]);
}
return generations;
}
private calculateFitness(sequence: string): number {
const gcContent = (sequence.match(/[GC]/g) || []).length / sequence.length;
return Math.abs(gcContent - 0.5);
}
}