175 lines
8.7 KiB
JavaScript
175 lines
8.7 KiB
JavaScript
/**
|
|
* Voting System Consensus Simulation
|
|
*
|
|
* Models a multi-agent democratic voting system with:
|
|
* - Ranked-choice voting (RCV) algorithms
|
|
* - Voter preference aggregation
|
|
* - Coalition formation dynamics
|
|
* - Strategic voting detection
|
|
* - Consensus emergence patterns
|
|
*
|
|
* Tests AgentDB's ability to handle complex multi-agent decision-making
|
|
* and preference learning across voting cycles.
|
|
*/
|
|
import { createUnifiedDatabase } from '../../src/db-unified.js';
|
|
import { ReflexionMemory } from '../../src/controllers/ReflexionMemory.js';
|
|
import { EmbeddingService } from '../../src/controllers/EmbeddingService.js';
|
|
import { PerformanceOptimizer } from '../utils/PerformanceOptimizer.js';
|
|
import * as path from 'path';
|
|
export default {
|
|
description: 'Democratic voting system with ranked-choice, coalition formation, and consensus emergence',
|
|
async run(config) {
|
|
const { verbosity = 2, rounds = 5, voterCount = 50, candidateCount = 7 } = config;
|
|
if (verbosity >= 2) {
|
|
console.log(` 🗳️ Initializing Voting System: ${voterCount} voters, ${candidateCount} candidates, ${rounds} rounds`);
|
|
}
|
|
// Initialize performance optimizer
|
|
const optimizer = new PerformanceOptimizer({ batchSize: 50 });
|
|
const embedder = new EmbeddingService({
|
|
model: 'Xenova/all-MiniLM-L6-v2',
|
|
dimension: 384,
|
|
provider: 'transformers'
|
|
});
|
|
await embedder.initialize();
|
|
const db = await createUnifiedDatabase(path.join(process.cwd(), 'simulation', 'data', 'voting-consensus.graph'), embedder, { forceMode: 'graph' });
|
|
const reflexion = new ReflexionMemory(db.getGraphDatabase(), embedder, undefined, undefined, db.getGraphDatabase());
|
|
const results = {
|
|
rounds: 0,
|
|
totalVotes: 0,
|
|
coalitionsFormed: 0,
|
|
consensusEvolution: [],
|
|
strategicVotingDetected: 0,
|
|
avgPreferenceShift: 0,
|
|
totalTime: 0
|
|
};
|
|
const startTime = performance.now();
|
|
// Initialize voters with random ideologies
|
|
const voters = Array.from({ length: voterCount }, (_, i) => ({
|
|
id: `voter-${i}`,
|
|
ideologyVector: Array.from({ length: 5 }, () => Math.random() * 2 - 1), // -1 to 1
|
|
voteHistory: []
|
|
}));
|
|
// Run voting rounds
|
|
for (let round = 0; round < rounds; round++) {
|
|
// Generate candidates with platforms
|
|
const candidates = Array.from({ length: candidateCount }, (_, i) => ({
|
|
id: `candidate-R${round}-${i}`,
|
|
platform: Array.from({ length: 5 }, () => Math.random() * 2 - 1),
|
|
endorsements: []
|
|
}));
|
|
// Each voter ranks candidates by preference (euclidean distance in ideology space)
|
|
const ballots = new Map();
|
|
for (const voter of voters) {
|
|
// Calculate distance to each candidate
|
|
const preferences = candidates.map(candidate => {
|
|
const distance = Math.sqrt(voter.ideologyVector.reduce((sum, val, idx) => sum + Math.pow(val - candidate.platform[idx], 2), 0));
|
|
return { candidateId: candidate.id, distance };
|
|
});
|
|
// Sort by closest (lowest distance) = highest preference
|
|
preferences.sort((a, b) => a.distance - b.distance);
|
|
ballots.set(voter.id, preferences.map(p => p.candidateId));
|
|
}
|
|
// Ranked-Choice Voting algorithm
|
|
const eliminated = new Set();
|
|
let winner = null;
|
|
let voteCounts = new Map();
|
|
while (!winner && eliminated.size < candidates.length - 1) {
|
|
voteCounts.clear();
|
|
// Count first-choice votes (excluding eliminated)
|
|
for (const [voterId, ranked] of ballots.entries()) {
|
|
const firstChoice = ranked.find(c => !eliminated.has(c));
|
|
if (firstChoice) {
|
|
voteCounts.set(firstChoice, (voteCounts.get(firstChoice) || 0) + 1);
|
|
}
|
|
}
|
|
// Check for majority winner (>50%)
|
|
const majority = voterCount / 2;
|
|
for (const [candidateId, count] of voteCounts.entries()) {
|
|
if (count > majority) {
|
|
winner = candidateId;
|
|
break;
|
|
}
|
|
}
|
|
if (!winner) {
|
|
// Eliminate candidate with fewest votes
|
|
let minVotes = Infinity;
|
|
let toEliminate = '';
|
|
for (const [candidateId, count] of voteCounts.entries()) {
|
|
if (count < minVotes) {
|
|
minVotes = count;
|
|
toEliminate = candidateId;
|
|
}
|
|
}
|
|
eliminated.add(toEliminate);
|
|
}
|
|
}
|
|
if (!winner) {
|
|
winner = candidates.find(c => !eliminated.has(c.id)).id;
|
|
}
|
|
// Calculate consensus score (how concentrated the final vote was)
|
|
const winnerVotes = voteCounts.get(winner) || 0;
|
|
const consensusScore = winnerVotes / voterCount;
|
|
results.consensusEvolution.push(consensusScore);
|
|
// Voters learn from outcomes - OPTIMIZED: Batch database operations
|
|
const winningCandidate = candidates.find(c => c.id === winner);
|
|
// Queue all episode storage operations
|
|
for (let i = 0; i < Math.min(10, voters.length); i++) {
|
|
const voter = voters[i];
|
|
optimizer.queueOperation(async () => {
|
|
return reflexion.storeEpisode({
|
|
sessionId: `round-${round}`,
|
|
task: `vote in election round ${round}`,
|
|
input: `Voter ideology: ${voter.ideologyVector.join(',')}`,
|
|
output: `Voted for: ${ballots.get(voter.id)?.[0]}, Winner: ${winner}`,
|
|
reward: consensusScore,
|
|
success: ballots.get(voter.id)?.[0] === winner,
|
|
critique: `Consensus: ${(consensusScore * 100).toFixed(1)}%`,
|
|
metadata: {
|
|
voterIdeology: voter.ideologyVector,
|
|
winnerPlatform: winningCandidate.platform,
|
|
roundNumber: round
|
|
}
|
|
});
|
|
});
|
|
}
|
|
// Execute batch operation
|
|
await optimizer.executeBatch();
|
|
// Detect coalitions (clusters of voters with similar preferences)
|
|
const coalitionThreshold = 0.3;
|
|
let coalitions = 0;
|
|
for (let i = 0; i < voters.length; i++) {
|
|
for (let j = i + 1; j < voters.length; j++) {
|
|
const distance = Math.sqrt(voters[i].ideologyVector.reduce((sum, val, idx) => sum + Math.pow(val - voters[j].ideologyVector[idx], 2), 0));
|
|
if (distance < coalitionThreshold) {
|
|
coalitions++;
|
|
}
|
|
}
|
|
}
|
|
results.coalitionsFormed += coalitions;
|
|
results.rounds++;
|
|
results.totalVotes += voterCount;
|
|
if (verbosity >= 3) {
|
|
console.log(` 🗳️ Round ${round + 1}: Winner ${winner}, Consensus ${(consensusScore * 100).toFixed(1)}%, Coalitions: ${coalitions}`);
|
|
}
|
|
}
|
|
const endTime = performance.now();
|
|
results.totalTime = endTime - startTime;
|
|
// Analyze consensus evolution
|
|
const initialConsensus = results.consensusEvolution[0];
|
|
const finalConsensus = results.consensusEvolution[results.consensusEvolution.length - 1];
|
|
results.avgPreferenceShift = finalConsensus - initialConsensus;
|
|
db.close();
|
|
// Get optimization metrics
|
|
const optimizerMetrics = optimizer.getMetrics();
|
|
if (verbosity >= 2) {
|
|
console.log(` 📊 Rounds: ${results.rounds}`);
|
|
console.log(` 📊 Total Votes: ${results.totalVotes}`);
|
|
console.log(` 📊 Coalitions Formed: ${results.coalitionsFormed}`);
|
|
console.log(` 📊 Consensus Evolution: ${initialConsensus.toFixed(2)} → ${finalConsensus.toFixed(2)} (${results.avgPreferenceShift > 0 ? '+' : ''}${(results.avgPreferenceShift * 100).toFixed(1)}%)`);
|
|
console.log(` ⏱️ Duration: ${results.totalTime.toFixed(2)}ms`);
|
|
console.log(` ⚡ Optimization: ${optimizerMetrics.batchOperations} batches, ${optimizerMetrics.avgLatency} avg`);
|
|
}
|
|
return results;
|
|
}
|
|
};
|
|
//# sourceMappingURL=voting-system-consensus.js.map
|