Skip to main content
Glama
simulation.ts17.3 kB
/** * Simulation Operation * * Runs simulations and models to understand system behavior */ import { BaseOperation, type OperationContext, type OperationResult } from '../base.js'; import type { SimulationResult } from '../../../types/index.js'; export class SimulationOperation extends BaseOperation { name = 'simulation'; category = 'analysis'; async execute(context: OperationContext): Promise<OperationResult> { const { sessionState, prompt, parameters } = context; const simulationType = this.getParam(parameters, 'simulationType', 'system-dynamics') as 'system-dynamics' | 'agent-based' | 'monte-carlo' | 'discrete-event' | 'cellular-automata'; const steps = this.getParam(parameters, 'steps', 100); const initialState = (parameters.initialState as Record<string, number>) || this.parseInitialState(prompt); const rules = (parameters.rules as string[]) || this.extractRules(prompt); const timeStep = this.getParam(parameters, 'timeStep', 1); let result: SimulationResult; switch (simulationType) { case 'system-dynamics': result = this.runSystemDynamicsSimulation(initialState, rules, steps, timeStep); break; case 'agent-based': const agentCount = this.getParam(parameters, 'agentCount', 10); result = this.runAgentBasedSimulation(agentCount, rules, steps); break; case 'monte-carlo': const iterations = this.getParam(parameters, 'iterations', 1000); result = this.runMonteCarloSimulation(initialState, rules, iterations); break; case 'discrete-event': const events = (parameters.events as Array<{time: number, type: string, data: any}>) || []; result = this.runDiscreteEventSimulation(initialState, events, steps); break; case 'cellular-automata': const gridSize = this.getParam(parameters, 'gridSize', 10); result = this.runCellularAutomataSimulation(gridSize, rules, steps); break; default: result = this.runSystemDynamicsSimulation(initialState, rules, steps, timeStep); } return this.createResult({ simulationType, result, parameters: { steps, timeStep, initialState, rules }, analysis: this.analyzeSimulationResults(result), insights: this.generateInsights(result, simulationType), recommendations: this.generateRecommendations(result, simulationType), sessionContext: { sessionId: sessionState.sessionId, stats: sessionState.getStats(), }, }); } private parseInitialState(prompt: string): Record<string, number> { const state: Record<string, number> = {}; // Look for variable assignments in the prompt const assignments = prompt.match(/([a-zA-Z_]\w*)\s*[=:]\s*(\d+(?:\.\d+)?)/g); if (assignments) { for (const assignment of assignments) { const match = assignment.match(/([a-zA-Z_]\w*)\s*[=:]\s*(\d+(?:\.\d+)?)/); if (match) { state[match[1]] = parseFloat(match[2]); } } } // Default state if nothing found if (Object.keys(state).length === 0) { state.population = 100; state.resources = 1000; state.growth_rate = 0.05; } return state; } private extractRules(prompt: string): string[] { const rules: string[] = []; // Look for rule-like statements const sentences = prompt.split(/[.!?]+/); for (const sentence of sentences) { const trimmed = sentence.trim().toLowerCase(); if (trimmed.includes('if') || trimmed.includes('when') || trimmed.includes('increase') || trimmed.includes('decrease')) { rules.push(sentence.trim()); } } // Default rules if none found if (rules.length === 0) { rules.push('population increases by growth_rate * population'); rules.push('resources decrease by population * 0.1'); rules.push('if resources < 100, growth_rate decreases'); } return rules; } private runSystemDynamicsSimulation( initialState: Record<string, number>, rules: string[], steps: number, timeStep: number ): SimulationResult { const trajectory: Array<Record<string, number>> = []; let currentState = { ...initialState }; for (let step = 0; step < steps; step++) { trajectory.push({ ...currentState, time: step * timeStep }); // Apply rules to update state const newState = { ...currentState }; // Simple rule interpreter for (const rule of rules) { this.applySystemDynamicsRule(rule, newState, timeStep); } currentState = newState; } return { steps, trajectory, finalState: currentState }; } private applySystemDynamicsRule(rule: string, state: Record<string, number>, timeStep: number): void { const ruleLower = rule.toLowerCase(); // Growth rule if (ruleLower.includes('population') && ruleLower.includes('growth_rate')) { const growthRate = state.growth_rate || 0.05; const population = state.population || 0; state.population = population + (population * growthRate * timeStep); } // Resource consumption if (ruleLower.includes('resources') && ruleLower.includes('decrease')) { const population = state.population || 0; const consumption = population * 0.1 * timeStep; state.resources = Math.max(0, (state.resources || 1000) - consumption); } // Resource constraint if (ruleLower.includes('resources < 100')) { if ((state.resources || 0) < 100) { state.growth_rate = Math.max(0, (state.growth_rate || 0.05) * 0.9); } } } private runAgentBasedSimulation(agentCount: number, rules: string[], steps: number): SimulationResult { const trajectory: Array<Record<string, number>> = []; // Initialize agents const agents = Array.from({ length: agentCount }, (_, i) => ({ id: i, x: Math.random() * 100, y: Math.random() * 100, energy: 100, state: 'active' })); for (let step = 0; step < steps; step++) { // Update agents for (const agent of agents) { agent.energy -= 1; // Energy decay if (agent.energy <= 0) { agent.state = 'inactive'; } else { // Random movement agent.x += (Math.random() - 0.5) * 10; agent.y += (Math.random() - 0.5) * 10; // Boundary conditions agent.x = Math.max(0, Math.min(100, agent.x)); agent.y = Math.max(0, Math.min(100, agent.y)); } } // Record aggregate statistics const activeAgents = agents.filter(a => a.state === 'active').length; const avgEnergy = agents.reduce((sum, a) => sum + a.energy, 0) / agents.length; trajectory.push({ time: step, active_agents: activeAgents, average_energy: avgEnergy, total_agents: agentCount }); } const finalState = { active_agents: agents.filter(a => a.state === 'active').length, total_agents: agentCount, average_energy: agents.reduce((sum, a) => sum + a.energy, 0) / agents.length }; return { steps, trajectory, finalState }; } private runMonteCarloSimulation( initialState: Record<string, number>, rules: string[], iterations: number ): SimulationResult { const results: Array<Record<string, number>> = []; for (let i = 0; i < iterations; i++) { // Add randomness to initial state const randomizedState: Record<string, number> = {}; for (const [key, value] of Object.entries(initialState)) { // Add ±20% random variation const variation = (Math.random() - 0.5) * 0.4; randomizedState[key] = value * (1 + variation); } // Run a short simulation const shortSim = this.runSystemDynamicsSimulation(randomizedState, rules, 10, 1); results.push({ iteration: i, ...shortSim.finalState }); } // Calculate summary statistics const keys = Object.keys(results[0]).filter(k => k !== 'iteration'); const finalState: Record<string, number> = {}; for (const key of keys) { const values = results.map(r => r[key]).filter(v => typeof v === 'number'); finalState[key] = values.reduce((sum, v) => sum + v, 0) / values.length; finalState[`${key}_std`] = Math.sqrt( values.reduce((sum, v) => sum + Math.pow(v - finalState[key], 2), 0) / values.length ); } return { steps: iterations, trajectory: results, finalState }; } private runDiscreteEventSimulation( initialState: Record<string, number>, events: Array<{time: number, type: string, data: any}>, maxTime: number ): SimulationResult { const trajectory: Array<Record<string, number>> = []; let currentState = { ...initialState }; const sortedEvents = events.sort((a, b) => a.time - b.time); let eventIndex = 0; for (let time = 0; time < maxTime; time++) { // Process events at current time while (eventIndex < sortedEvents.length && sortedEvents[eventIndex].time <= time) { const event = sortedEvents[eventIndex]; this.processEvent(event, currentState); eventIndex++; } trajectory.push({ time, ...currentState }); } return { steps: maxTime, trajectory, finalState: currentState }; } private processEvent(event: {time: number, type: string, data: any}, state: Record<string, number>): void { switch (event.type) { case 'increase': if (event.data.variable && typeof event.data.amount === 'number') { state[event.data.variable] = (state[event.data.variable] || 0) + event.data.amount; } break; case 'decrease': if (event.data.variable && typeof event.data.amount === 'number') { state[event.data.variable] = Math.max(0, (state[event.data.variable] || 0) - event.data.amount); } break; case 'set': if (event.data.variable && typeof event.data.value === 'number') { state[event.data.variable] = event.data.value; } break; } } private runCellularAutomataSimulation(gridSize: number, rules: string[], steps: number): SimulationResult { const trajectory: Array<Record<string, number>> = []; // Initialize grid let grid = Array.from({ length: gridSize }, () => Array.from({ length: gridSize }, () => Math.random() > 0.5 ? 1 : 0) ); for (let step = 0; step < steps; step++) { const newGrid = grid.map(row => [...row]); // Apply rules (simplified Conway's Game of Life) for (let i = 0; i < gridSize; i++) { for (let j = 0; j < gridSize; j++) { const neighbors = this.countNeighbors(grid, i, j); if (grid[i][j] === 1) { // Live cell newGrid[i][j] = (neighbors === 2 || neighbors === 3) ? 1 : 0; } else { // Dead cell newGrid[i][j] = (neighbors === 3) ? 1 : 0; } } } grid = newGrid; // Record statistics const aliveCells = grid.flat().reduce((sum: number, cell: number) => sum + cell, 0); trajectory.push({ time: step, alive_cells: aliveCells, total_cells: gridSize * gridSize, density: aliveCells / (gridSize * gridSize) }); } const finalAliveCells = grid.flat().reduce((sum: number, cell: number) => sum + cell, 0); const finalState = { alive_cells: finalAliveCells, total_cells: gridSize * gridSize, density: finalAliveCells / (gridSize * gridSize) }; return { steps, trajectory, finalState }; } private countNeighbors(grid: number[][], row: number, col: number): number { let count = 0; const size = grid.length; for (let i = -1; i <= 1; i++) { for (let j = -1; j <= 1; j++) { if (i === 0 && j === 0) continue; const newRow = (row + i + size) % size; const newCol = (col + j + size) % size; count += grid[newRow][newCol]; } } return count; } private analyzeSimulationResults(result: SimulationResult): Record<string, any> { const analysis: Record<string, any> = {}; if (result.trajectory.length === 0) { return { error: 'No trajectory data to analyze' }; } // Find trends in key variables const firstState = result.trajectory[0]; const lastState = result.trajectory[result.trajectory.length - 1]; analysis.trends = {}; for (const key of Object.keys(firstState)) { if (typeof firstState[key] === 'number' && key !== 'time') { const initial = firstState[key]; const final = lastState[key]; const change = final - initial; const percentChange = initial !== 0 ? (change / initial) * 100 : 0; analysis.trends[key] = { change, percentChange, direction: change > 0 ? 'increasing' : change < 0 ? 'decreasing' : 'stable' }; } } // Identify equilibrium analysis.equilibrium = this.checkEquilibrium(result.trajectory); // Find peaks and valleys analysis.extremes = this.findExtremes(result.trajectory); return analysis; } private checkEquilibrium(trajectory: Array<Record<string, number>>): Record<string, boolean> { const equilibrium: Record<string, boolean> = {}; if (trajectory.length < 10) return equilibrium; const lastTenStates = trajectory.slice(-10); for (const key of Object.keys(trajectory[0])) { if (typeof trajectory[0][key] === 'number' && key !== 'time') { const values = lastTenStates.map(state => state[key]); const mean = values.reduce((sum, v) => sum + v, 0) / values.length; const variance = values.reduce((sum, v) => sum + Math.pow(v - mean, 2), 0) / values.length; // Consider equilibrium if variance is very small equilibrium[key] = variance < mean * 0.01; } } return equilibrium; } private findExtremes(trajectory: Array<Record<string, number>>): Record<string, any> { const extremes: Record<string, any> = {}; for (const key of Object.keys(trajectory[0])) { if (typeof trajectory[0][key] === 'number' && key !== 'time') { const values = trajectory.map(state => state[key]); extremes[key] = { min: Math.min(...values), max: Math.max(...values), minTime: trajectory[values.indexOf(Math.min(...values))].time, maxTime: trajectory[values.indexOf(Math.max(...values))].time }; } } return extremes; } private generateInsights(result: SimulationResult, simulationType: string): string[] { const insights: string[] = []; if (result.trajectory.length === 0) { insights.push('Simulation produced no data to analyze'); return insights; } const analysis = this.analyzeSimulationResults(result); // General insights if (analysis.equilibrium) { const equilibriumVars = Object.entries(analysis.equilibrium) .filter(([, isEquilibrium]) => isEquilibrium) .map(([key]) => key); if (equilibriumVars.length > 0) { insights.push(`Variables reaching equilibrium: ${equilibriumVars.join(', ')}`); } } // Type-specific insights switch (simulationType) { case 'system-dynamics': insights.push('System dynamics simulation shows long-term behavior patterns'); break; case 'agent-based': insights.push('Agent-based simulation reveals emergent collective behavior'); break; case 'monte-carlo': insights.push('Monte Carlo simulation provides uncertainty estimates'); break; case 'cellular-automata': insights.push('Cellular automata simulation shows spatial pattern evolution'); break; } return insights; } private generateRecommendations(result: SimulationResult, simulationType: string): string[] { const recommendations: string[] = []; recommendations.push('Validate simulation results against real-world data if available'); recommendations.push('Consider sensitivity analysis by varying key parameters'); if (result.steps < 100) { recommendations.push('Consider running longer simulations to observe long-term behavior'); } switch (simulationType) { case 'monte-carlo': recommendations.push('Increase number of iterations for more stable estimates'); break; case 'agent-based': recommendations.push('Experiment with different agent behaviors and interaction rules'); break; case 'system-dynamics': recommendations.push('Examine feedback loops and their stability'); break; } return recommendations; } } export default new SimulationOperation();

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/waldzellai/clearthought-onepointfive'

If you have feedback or need assistance with the MCP directory API, please join our Discord server