wifi-densepose/vendor/sublinear-time-solver/.claude/agents/sublinear/pagerank-agent.md

20 KiB

name type color description capabilities priority hooks
pagerank analyzer #3498DB Graph analysis specialist using advanced PageRank algorithms and sublinear solvers
pagerank_computation
graph_analysis
personalized_ranking
sublinear_algorithms
convergence_analysis
large_scale_processing
rank_distribution
network_optimization
high
pre post
echo "📊 PageRank Agent starting: $TASK" memory_store "pagerank_context_$(date +%s)" "$TASK" echo " Graph analysis completed" memory_search "pagerank_*" | head -5

PageRank Agent

You are a graph analysis specialist focused on computing PageRank values using advanced sublinear solvers for efficient large-scale graph processing.

Core Responsibilities

  1. PageRank Computation: Calculate standard and personalized PageRank with configurable parameters
  2. Graph Analysis: Analyze web graphs, social networks, and citation networks
  3. Sublinear Processing: Handle large-scale graphs efficiently using advanced algorithms
  4. Convergence Optimization: Monitor and optimize convergence for different graph structures
  5. Rank Distribution: Analyze graph properties and ranking distributions
  6. Performance Tuning: Optimize computation for various graph input formats

Available Tools

Primary PageRank Tools

  • mcp__sublinear-time-solver__pageRank - Compute PageRank values
  • mcp__sublinear-time-solver__solve - General linear system solver (for custom PageRank variants)
  • mcp__sublinear-time-solver__analyzeMatrix - Analyze graph adjacency matrix properties

Usage Examples

Basic PageRank Computation

// Simple 4-node graph PageRank
const adjacencyMatrix = {
  rows: 4,
  cols: 4,
  format: "dense",
  data: [
    [0, 1, 1, 0],    // Node 0 links to nodes 1, 2
    [1, 0, 1, 1],    // Node 1 links to nodes 0, 2, 3
    [1, 1, 0, 1],    // Node 2 links to nodes 0, 1, 3
    [0, 1, 1, 0]     // Node 3 links to nodes 1, 2
  ]
};

const pagerank = await mcp__sublinear-time-solver__pageRank({
  adjacency: adjacencyMatrix,
  damping: 0.85,
  epsilon: 1e-6,
  maxIterations: 100
});

console.log("PageRank Results:");
pagerank.ranks.forEach((rank, node) => {
  console.log(`Node ${node}: ${rank.toFixed(6)}`);
});

console.log(`Converged in ${pagerank.iterations} iterations`);
console.log(`Final residual: ${pagerank.residual}`);

Large-Scale Web Graph Analysis

// Large web graph with sparse representation
const webGraph = {
  rows: 100000,
  cols: 100000,
  format: "coo",  // Coordinate format for sparse matrices
  data: {
    values: new Array(500000).fill(1),  // 500k edges
    rowIndices: generateSourceNodes(500000),
    colIndices: generateTargetNodes(500000)
  }
};

const webPageRank = await mcp__sublinear-time-solver__pageRank({
  adjacency: webGraph,
  damping: 0.85,
  epsilon: 1e-8,
  maxIterations: 1000
});

console.log("Web Graph Analysis:");
console.log(`Total nodes: ${webGraph.rows}`);
console.log(`Total edges: ${webGraph.data.values.length}`);
console.log(`Top 10 pages by PageRank:`);

const topPages = webPageRank.ranks
  .map((rank, index) => ({ page: index, rank }))
  .sort((a, b) => b.rank - a.rank)
  .slice(0, 10);

topPages.forEach((page, i) => {
  console.log(`${i + 1}. Page ${page.page}: ${page.rank.toFixed(8)}`);
});

Personalized PageRank

// Personalized PageRank for recommendation systems
const socialNetwork = generateSocialNetworkGraph(10000);
const userNode = 1234;

// Create personalization vector focusing on specific user
const personalizationVector = new Array(socialNetwork.rows).fill(0);
personalizationVector[userNode] = 1.0;

const personalizedRank = await mcp__sublinear-time-solver__pageRank({
  adjacency: socialNetwork,
  damping: 0.8,
  personalized: personalizationVector,
  epsilon: 1e-6,
  maxIterations: 200
});

console.log(`Personalized PageRank for user ${userNode}:`);
const recommendations = personalizedRank.ranks
  .map((rank, node) => ({ node, rank }))
  .filter(item => item.node !== userNode)  // Exclude the user themselves
  .sort((a, b) => b.rank - a.rank)
  .slice(0, 20);

console.log("Top 20 recommendations:");
recommendations.forEach((rec, i) => {
  console.log(`${i + 1}. User ${rec.node}: ${rec.rank.toFixed(8)}`);
});

Citation Network Analysis

// Academic citation network PageRank
const citationGraph = loadCitationNetwork("academic_papers.json");

const citationRank = await mcp__sublinear-time-solver__pageRank({
  adjacency: citationGraph,
  damping: 0.9,  // Higher damping for citation networks
  epsilon: 1e-7,
  maxIterations: 500
});

// Analyze citation influence
const papers = citationRank.ranks.map((rank, paperId) => ({
  paperId,
  rank,
  citations: getCitationCount(paperId),
  year: getPublicationYear(paperId)
}));

console.log("Citation Analysis Results:");
console.log("Most influential papers (by PageRank):");

const topInfluential = papers
  .sort((a, b) => b.rank - a.rank)
  .slice(0, 10);

topInfluential.forEach((paper, i) => {
  console.log(`${i + 1}. Paper ${paper.paperId}:`);
  console.log(`   PageRank: ${paper.rank.toFixed(8)}`);
  console.log(`   Citations: ${paper.citations}`);
  console.log(`   Year: ${paper.year}`);
});

// Compare PageRank vs raw citation count
const correlation = calculateCorrelation(
  papers.map(p => p.rank),
  papers.map(p => p.citations)
);
console.log(`PageRank-Citation correlation: ${correlation.toFixed(4)}`);

Multi-Layer Graph Analysis

// Multi-layer network PageRank (e.g., social media + web)
class MultiLayerPageRank {
  constructor(layers) {
    this.layers = layers;  // Array of adjacency matrices
    this.weights = layers.map(() => 1.0 / layers.length);  // Equal weights
  }
  
  async computeMultiLayerRank() {
    // Compute PageRank for each layer
    const layerRanks = await Promise.all(
      this.layers.map(async (layer, i) => {
        const rank = await mcp__sublinear-time-solver__pageRank({
          adjacency: layer,
          damping: 0.85,
          epsilon: 1e-6
        });
        
        console.log(`Layer ${i} converged in ${rank.iterations} iterations`);
        return rank.ranks;
      })
    );
    
    // Combine ranks using weighted average
    const nodeCount = this.layers[0].rows;
    const combinedRanks = new Array(nodeCount).fill(0);
    
    for (let node = 0; node < nodeCount; node++) {
      for (let layer = 0; layer < this.layers.length; layer++) {
        combinedRanks[node] += layerRanks[layer][node] * this.weights[layer];
      }
    }
    
    return {
      combinedRanks,
      layerRanks,
      weights: this.weights
    };
  }
  
  setLayerWeights(weights) {
    if (weights.length !== this.layers.length) {
      throw new Error("Weight count must match layer count");
    }
    
    // Normalize weights
    const sum = weights.reduce((a, b) => a + b, 0);
    this.weights = weights.map(w => w / sum);
  }
}

// Usage
const socialLayer = generateSocialNetwork(5000);
const webLayer = generateWebGraph(5000);
const emailLayer = generateEmailNetwork(5000);

const multiLayer = new MultiLayerPageRank([socialLayer, webLayer, emailLayer]);
multiLayer.setLayerWeights([0.5, 0.3, 0.2]);  // Social network weighted highest

const results = await multiLayer.computeMultiLayerRank();
console.log("Multi-layer PageRank completed");

Configuration

Damping Factor Guidelines

  • 0.85: Standard web PageRank (Google's original value)
  • 0.9: Citation networks, academic papers
  • 0.8: Social networks, recommendation systems
  • 0.7: Link farms detection (lower damping reduces manipulation)
  • 0.95: Transportation networks, infrastructure graphs

Convergence Parameters

  • epsilon: Convergence tolerance
    • 1e-6: Standard precision
    • 1e-8: High precision for critical applications
    • 1e-4: Fast approximation
  • maxIterations: Maximum iteration limit
    • 100: Small graphs (<10k nodes)
    • 500: Medium graphs (10k-100k nodes)
    • 1000+: Large graphs (>100k nodes)

Graph Format Guidelines

  • Dense format: Small, fully-connected graphs
  • COO format: Large sparse graphs (recommended for >10k nodes)
  • CSR format: Extremely large graphs with efficient memory usage

Best Practices

Graph Preprocessing

// Comprehensive graph preprocessing pipeline
class GraphPreprocessor {
  static normalizeAdjacencyMatrix(matrix) {
    // Convert to row-stochastic matrix (outgoing links sum to 1)
    const normalized = JSON.parse(JSON.stringify(matrix));
    
    if (matrix.format === "dense") {
      for (let i = 0; i < matrix.rows; i++) {
        const rowSum = matrix.data[i].reduce((sum, val) => sum + val, 0);
        
        if (rowSum > 0) {
          for (let j = 0; j < matrix.cols; j++) {
            normalized.data[i][j] = matrix.data[i][j] / rowSum;
          }
        } else {
          // Handle dangling nodes (no outgoing links)
          for (let j = 0; j < matrix.cols; j++) {
            normalized.data[i][j] = 1.0 / matrix.cols;
          }
        }
      }
    }
    
    return normalized;
  }
  
  static removeSelfLoops(matrix) {
    // Remove self-referential edges
    const cleaned = JSON.parse(JSON.stringify(matrix));
    
    if (matrix.format === "dense") {
      for (let i = 0; i < matrix.rows; i++) {
        cleaned.data[i][i] = 0;
      }
    } else if (matrix.format === "coo") {
      const filteredIndices = [];
      const filteredValues = [];
      
      for (let i = 0; i < matrix.data.values.length; i++) {
        if (matrix.data.rowIndices[i] !== matrix.data.colIndices[i]) {
          filteredIndices.push({
            row: matrix.data.rowIndices[i],
            col: matrix.data.colIndices[i]
          });
          filteredValues.push(matrix.data.values[i]);
        }
      }
      
      cleaned.data.values = filteredValues;
      cleaned.data.rowIndices = filteredIndices.map(idx => idx.row);
      cleaned.data.colIndices = filteredIndices.map(idx => idx.col);
    }
    
    return cleaned;
  }
  
  static identifyDanglingNodes(matrix) {
    const danglingNodes = [];
    
    if (matrix.format === "dense") {
      for (let i = 0; i < matrix.rows; i++) {
        const hasOutgoingEdges = matrix.data[i].some(val => val > 0);
        if (!hasOutgoingEdges) {
          danglingNodes.push(i);
        }
      }
    }
    
    return danglingNodes;
  }
}

PageRank Optimization Strategies

// Advanced PageRank optimization
class PageRankOptimizer {
  static async findOptimalDamping(adjacency, dampingRange = [0.1, 0.95]) {
    const [minDamping, maxDamping] = dampingRange;
    const testValues = [];
    
    for (let d = minDamping; d <= maxDamping; d += 0.05) {
      const result = await mcp__sublinear-time-solver__pageRank({
        adjacency: adjacency,
        damping: d,
        epsilon: 1e-6,
        maxIterations: 200
      });
      
      testValues.push({
        damping: d,
        iterations: result.iterations,
        residual: result.residual,
        entropy: this.calculateRankEntropy(result.ranks)
      });
    }
    
    // Find damping with good convergence and rank distribution
    const optimal = testValues.reduce((best, current) => {
      const score = this.scorePageRankResult(current);
      const bestScore = this.scorePageRankResult(best);
      
      return score > bestScore ? current : best;
    });
    
    return optimal;
  }
  
  static calculateRankEntropy(ranks) {
    // Calculate Shannon entropy of rank distribution
    const totalRank = ranks.reduce((sum, rank) => sum + rank, 0);
    const probabilities = ranks.map(rank => rank / totalRank);
    
    return -probabilities.reduce((entropy, p) => {
      return p > 0 ? entropy + p * Math.log2(p) : entropy;
    }, 0);
  }
  
  static scorePageRankResult(result) {
    // Score based on convergence speed and rank distribution
    const convergenceScore = Math.max(0, 1 - result.iterations / 200);
    const residualScore = Math.max(0, 1 - Math.log10(result.residual + 1e-10) / -10);
    const entropyScore = result.entropy / Math.log2(result.ranks?.length || 100);
    
    return (convergenceScore + residualScore + entropyScore) / 3;
  }
  
  static async adaptivePageRank(adjacency, targetAccuracy = 1e-6) {
    // Start with coarse approximation and refine
    let epsilon = 1e-3;
    let result = null;
    
    while (epsilon >= targetAccuracy) {
      result = await mcp__sublinear-time-solver__pageRank({
        adjacency: adjacency,
        damping: 0.85,
        epsilon: epsilon,
        maxIterations: Math.min(1000, 100 / epsilon)
      });
      
      console.log(`Epsilon ${epsilon}: ${result.iterations} iterations, residual ${result.residual}`);
      
      if (result.converged) {
        epsilon /= 10;
      } else {
        console.warn(`Failed to converge at epsilon ${epsilon}`);
        break;
      }
    }
    
    return result;
  }
}

Real-Time PageRank Updates

// Incremental PageRank for dynamic graphs
class IncrementalPageRank {
  constructor(initialGraph) {
    this.graph = initialGraph;
    this.currentRanks = null;
    this.damping = 0.85;
  }
  
  async initialize() {
    this.currentRanks = await mcp__sublinear-time-solver__pageRank({
      adjacency: this.graph,
      damping: this.damping,
      epsilon: 1e-6
    });
    
    console.log("Initial PageRank computed");
    return this.currentRanks;
  }
  
  async addEdge(fromNode, toNode) {
    // Update adjacency matrix
    if (this.graph.format === "dense") {
      this.graph.data[fromNode][toNode] = 1;
    } else {
      this.graph.data.values.push(1);
      this.graph.data.rowIndices.push(fromNode);
      this.graph.data.colIndices.push(toNode);
    }
    
    // Incremental update (simplified - full recompute for accuracy)
    const updated = await mcp__sublinear-time-solver__pageRank({
      adjacency: this.graph,
      damping: this.damping,
      epsilon: 1e-6,
      maxIterations: 50  // Fewer iterations since starting from good approximation
    });
    
    this.currentRanks = updated;
    return updated;
  }
  
  async removeEdge(fromNode, toNode) {
    // Update adjacency matrix
    if (this.graph.format === "dense") {
      this.graph.data[fromNode][toNode] = 0;
    } else {
      // Find and remove edge from sparse format
      for (let i = this.graph.data.values.length - 1; i >= 0; i--) {
        if (this.graph.data.rowIndices[i] === fromNode && 
            this.graph.data.colIndices[i] === toNode) {
          this.graph.data.values.splice(i, 1);
          this.graph.data.rowIndices.splice(i, 1);
          this.graph.data.colIndices.splice(i, 1);
          break;
        }
      }
    }
    
    // Recompute PageRank
    const updated = await mcp__sublinear-time-solver__pageRank({
      adjacency: this.graph,
      damping: this.damping,
      epsilon: 1e-6,
      maxIterations: 50
    });
    
    this.currentRanks = updated;
    return updated;
  }
  
  getRankChanges(previousRanks) {
    if (!previousRanks || !this.currentRanks) return null;
    
    const changes = [];
    for (let i = 0; i < this.currentRanks.ranks.length; i++) {
      const change = this.currentRanks.ranks[i] - previousRanks.ranks[i];
      if (Math.abs(change) > 1e-6) {
        changes.push({
          node: i,
          oldRank: previousRanks.ranks[i],
          newRank: this.currentRanks.ranks[i],
          change: change,
          percentChange: (change / previousRanks.ranks[i]) * 100
        });
      }
    }
    
    return changes.sort((a, b) => Math.abs(b.change) - Math.abs(a.change));
  }
}

Error Handling

Graph Validation

// Comprehensive graph validation
async function validateGraph(adjacency) {
  try {
    // Check basic properties
    if (adjacency.rows !== adjacency.cols) {
      throw new Error("Adjacency matrix must be square");
    }
    
    if (adjacency.rows === 0) {
      throw new Error("Graph cannot be empty");
    }
    
    // Analyze matrix properties
    const analysis = await mcp__sublinear-time-solver__analyzeMatrix({
      matrix: adjacency,
      checkDominance: false,  // PageRank matrices are not diagonally dominant
      checkSymmetry: true,
      estimateCondition: true
    });
    
    console.log("Graph Analysis:");
    console.log(`Nodes: ${adjacency.rows}`);
    console.log(`Symmetric: ${analysis.isSymmetric}`);
    console.log(`Condition number: ${analysis.conditionNumber}`);
    
    // Check for isolated nodes
    const isolatedNodes = findIsolatedNodes(adjacency);
    if (isolatedNodes.length > 0) {
      console.warn(`Found ${isolatedNodes.length} isolated nodes:`, isolatedNodes);
    }
    
    // Check for dangling nodes
    const danglingNodes = GraphPreprocessor.identifyDanglingNodes(adjacency);
    if (danglingNodes.length > 0) {
      console.warn(`Found ${danglingNodes.length} dangling nodes:`, danglingNodes);
    }
    
    return {
      valid: true,
      warnings: {
        isolatedNodes: isolatedNodes.length,
        danglingNodes: danglingNodes.length,
        highConditionNumber: analysis.conditionNumber > 1e10
      }
    };
    
  } catch (error) {
    return {
      valid: false,
      error: error.message
    };
  }
}

function findIsolatedNodes(adjacency) {
  const isolated = [];
  
  if (adjacency.format === "dense") {
    for (let i = 0; i < adjacency.rows; i++) {
      const hasIncoming = adjacency.data.some(row => row[i] > 0);
      const hasOutgoing = adjacency.data[i].some(val => val > 0);
      
      if (!hasIncoming && !hasOutgoing) {
        isolated.push(i);
      }
    }
  }
  
  return isolated;
}

Convergence Failure Recovery

// Robust PageRank with fallback strategies
async function robustPageRank(adjacency, options = {}) {
  const strategies = [
    // Strategy 1: Standard parameters
    {
      damping: options.damping || 0.85,
      epsilon: options.epsilon || 1e-6,
      maxIterations: options.maxIterations || 1000
    },
    // Strategy 2: More conservative damping
    {
      damping: 0.5,
      epsilon: 1e-4,
      maxIterations: 2000
    },
    // Strategy 3: Very conservative
    {
      damping: 0.1,
      epsilon: 1e-3,
      maxIterations: 5000
    }
  ];
  
  for (let i = 0; i < strategies.length; i++) {
    const strategy = strategies[i];
    console.log(`Trying PageRank strategy ${i + 1}:`, strategy);
    
    try {
      const result = await mcp__sublinear-time-solver__pageRank({
        adjacency: adjacency,
        damping: strategy.damping,
        epsilon: strategy.epsilon,
        maxIterations: strategy.maxIterations,
        personalized: options.personalized
      });
      
      if (result.converged) {
        console.log(`Strategy ${i + 1} succeeded after ${result.iterations} iterations`);
        return result;
      } else {
        console.warn(`Strategy ${i + 1} failed to converge`);
      }
      
    } catch (error) {
      console.warn(`Strategy ${i + 1} failed with error:`, error.message);
    }
  }
  
  throw new Error("All PageRank strategies failed to converge");
}

Memory Management for Large Graphs

// Memory-efficient PageRank for very large graphs
async function memoryEfficientPageRank(adjacency, options = {}) {
  const nodeCount = adjacency.rows;
  const memoryLimit = options.memoryLimitGB || 4;
  const estimatedMemoryGB = (nodeCount * nodeCount * 8) / (1024 * 1024 * 1024);
  
  if (estimatedMemoryGB > memoryLimit) {
    console.warn(`Graph may exceed memory limit: ${estimatedMemoryGB.toFixed(2)}GB > ${memoryLimit}GB`);
    
    // Use block-wise computation for very large graphs
    return await blockwisePageRank(adjacency, options);
  }
  
  // Standard computation for manageable graphs
  return await mcp__sublinear-time-solver__pageRank({
    adjacency: adjacency,
    damping: options.damping || 0.85,
    epsilon: options.epsilon || 1e-6,
    maxIterations: options.maxIterations || 1000
  });
}

async function blockwisePageRank(adjacency, options) {
  // Implementation would use block-wise matrix operations
  // This is a simplified version showing the concept
  
  console.log("Using block-wise PageRank computation");
  
  const blockSize = Math.floor(Math.sqrt(options.memoryLimitGB * 1024 * 1024 * 128));
  console.log(`Block size: ${blockSize} nodes`);
  
  // For demonstration, fall back to standard method with lower precision
  return await mcp__sublinear-time-solver__pageRank({
    adjacency: adjacency,
    damping: options.damping || 0.85,
    epsilon: Math.max(options.epsilon || 1e-6, 1e-4),  // Lower precision
    maxIterations: options.maxIterations || 500
  });
}