simulate_phylogeny
Simulate phylogenetic trees and sequence evolution for DNA or protein sequences using evolutionary models and substitution patterns.
Instructions
Simulate phylogenetic tree and sequence evolution
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| rootSequence | Yes | Root sequence for phylogenetic simulation | |
| treeStructure | No | Newick format tree or 'random' for random tree | |
| numTaxa | No | Number of taxa for random tree generation | |
| mutationRate | No | Mutation rate per branch length unit | |
| branchLengthVariation | No | Variation in branch lengths (0-1) | |
| molecularClock | No | Use molecular clock (uniform rates) | |
| substitutionModel | No | Substitution model: 'JC69', 'K80', 'HKY85', or 'GTR' | |
| seed | No | Random seed for reproducible results (optional) | |
| outputFormat | No | Output format: 'fasta', 'nexus', or 'phylip' |
Implementation Reference
- src/tools/simulatePhylogeny.ts:58-136 (handler)Main handler function that executes the simulate_phylogeny tool, including tree generation, sequence evolution, and output formattingasync handler({ rootSequence, treeStructure = "random", numTaxa = 5, mutationRate = 0.1, branchLengthVariation = 0.2, molecularClock = true, substitutionModel = "JC69", seed, outputFormat = "fasta" }: { rootSequence: string; treeStructure?: string; numTaxa?: number; mutationRate?: number; branchLengthVariation?: number; molecularClock?: boolean; substitutionModel?: string; seed?: number; outputFormat?: string; }) { const generator = new SequenceGenerator(seed); const isDNA = /^[ATGC]+$/i.test(rootSequence.replace(/\s/g, '')); let tree: PhyloNode; if (treeStructure === "random") { tree = generateRandomTree(numTaxa, generator); } else { tree = parseNewick(treeStructure); } if (!molecularClock) { addBranchLengthVariation(tree, branchLengthVariation, generator); } const sequences = evolveSequencesOnTree(tree, rootSequence, mutationRate, substitutionModel, generator, isDNA); let output = ''; const taxaNames = Object.keys(sequences); switch (outputFormat) { case 'fasta': output = Object.entries(sequences).map(([name, seq]) => `>${name}\n${seq}` ).join('\n\n'); break; case 'nexus': output = generateNexusFormat(sequences, tree); break; case 'phylip': output = generatePhylipFormat(sequences); break; } const treeStats = calculateTreeStats(tree); const sequenceStats = calculateSequenceDivergence(sequences, rootSequence); return { content: [{ type: "text", text: JSON.stringify({ parameters: { numTaxa: taxaNames.length, mutationRate, substitutionModel, molecularClock, seed: seed || "random" }, treeStatistics: treeStats, sequenceStatistics: sequenceStats, newickTree: treeToNewick(tree), sequences: outputFormat === 'fasta' ? sequences : undefined, formattedOutput: output }, null, 2) }] }; }
- src/tools/simulatePhylogeny.ts:4-57 (schema)Tool definition containing input schema with all parameters for phylogenetic simulation including rootSequence, treeStructure, numTaxa, mutationRate, substitutionModel, etc.export const simulatePhylogeny = { definition: { name: "simulate_phylogeny", description: "Simulate phylogenetic tree and sequence evolution", inputSchema: { type: "object", properties: { rootSequence: { type: "string", description: "Root sequence for phylogenetic simulation" }, treeStructure: { type: "string", description: "Newick format tree or 'random' for random tree", }, numTaxa: { type: "number", description: "Number of taxa for random tree generation", minimum: 2 }, mutationRate: { type: "number", description: "Mutation rate per branch length unit", minimum: 0 }, branchLengthVariation: { type: "number", description: "Variation in branch lengths (0-1)", minimum: 0, maximum: 1 }, molecularClock: { type: "boolean", description: "Use molecular clock (uniform rates)", default: true }, substitutionModel: { type: "string", description: "Substitution model: 'JC69', 'K80', 'HKY85', or 'GTR'", enum: ["JC69", "K80", "HKY85", "GTR"] }, seed: { type: "number", description: "Random seed for reproducible results (optional)" }, outputFormat: { type: "string", description: "Output format: 'fasta', 'nexus', or 'phylip'", enum: ["fasta", "nexus", "phylip"] } }, required: ["rootSequence"] }, },
- src/server.ts:26-37 (registration)Tool registration in ListToolsRequestSchema handler that exposes the simulate_phylogeny tool definitionserver.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ generateDNA.definition, generateProtein.definition, mutateSequence.definition, evolveSequence.definition, simulatePhylogeny.definition, simulateFastq.definition, ], }; });
- src/server.ts:94-105 (registration)Handler routing for simulate_phylogeny tool call in CallToolRequestSchemacase "simulate_phylogeny": return await simulatePhylogeny.handler(args as { rootSequence: string; treeStructure?: string; numTaxa?: number; mutationRate?: number; branchLengthVariation?: number; molecularClock?: boolean; substitutionModel?: string; seed?: number; outputFormat?: string; });
- src/utils/sequenceUtils.ts:48-176 (helper)SequenceGenerator utility class used by the handler for random number generation with optional seed, providing methods for DNA/protein generation and mutationexport 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); } }