Agentic Architecture Pattern: Optimization

Written by

in


    """
Pattern 48: Optimization Pattern

Finds optimal solutions by minimizing/maximizing objective functions.
Features hill climbing, simulated annealing, and genetic algorithms.
"""

from typing import Dict, Any, List, Optional, Callable, Tuple
from dataclasses import dataclass
import random
import math
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI


@dataclass
class Solution:
    """Represents a candidate solution."""
    variables: Dict[str, float]
    objective_value: float
    
    def copy(self) -> 'Solution':
        """Create a copy of the solution."""
        return Solution(
            variables=self.variables.copy(),
            objective_value=self.objective_value
        )


class OptimizationProblem:
    """Defines an optimization problem."""
    
    def __init__(self, objective: Callable[[Dict[str, float]], float],
                 is_minimization: bool = True):
        self.objective = objective
        self.is_minimization = is_minimization
        self.bounds: Dict[str, Tuple[float, float]] = {}
    
    def set_bounds(self, variable: str, lower: float, upper: float):
        """Set bounds for a variable."""
        self.bounds[variable] = (lower, upper)
    
    def evaluate(self, variables: Dict[str, float]) -> float:
        """Evaluate objective function."""
        return self.objective(variables)
    
    def random_solution(self) -> Solution:
        """Generate random solution within bounds."""
        variables = {}
        for var, (lower, upper) in self.bounds.items():
            variables[var] = random.uniform(lower, upper)
        
        obj_value = self.evaluate(variables)
        return Solution(variables=variables, objective_value=obj_value)
    
    def neighbor_solution(self, solution: Solution, step_size: float = 0.1) -> Solution:
        """Generate neighbor solution."""
        new_vars = solution.variables.copy()
        
        # Randomly perturb one variable
        var = random.choice(list(new_vars.keys()))
        lower, upper = self.bounds[var]
        
        # Add random perturbation
        perturbation = random.uniform(-step_size, step_size) * (upper - lower)
        new_vars[var] = max(lower, min(upper, new_vars[var] + perturbation))
        
        obj_value = self.evaluate(new_vars)
        return Solution(variables=new_vars, objective_value=obj_value)


class HillClimbingOptimizer:
    """Hill climbing optimization algorithm."""
    
    def __init__(self, problem: OptimizationProblem):
        self.problem = problem
    
    def optimize(self, max_iterations: int = 100) -> Solution:
        """Run hill climbing."""
        current = self.problem.random_solution()
        
        for iteration in range(max_iterations):
            neighbor = self.problem.neighbor_solution(current, step_size=0.1)
            
            # Move to neighbor if better
            is_better = (neighbor.objective_value  current.objective_value)
            
            if is_better:
                current = neighbor
        
        return current


class SimulatedAnnealingOptimizer:
    """Simulated annealing optimization."""
    
    def __init__(self, problem: OptimizationProblem):
        self.problem = problem
    
    def optimize(self, max_iterations: int = 1000, 
                initial_temp: float = 100.0) -> Solution:
        """Run simulated annealing."""
        current = self.problem.random_solution()
        best = current.copy()
        
        for iteration in range(max_iterations):
            # Temperature decreases over time
            temperature = initial_temp * (1 - iteration / max_iterations)
            
            if temperature < 0.01:
                break
            
            neighbor = self.problem.neighbor_solution(current, step_size=0.2)
            
            # Calculate acceptance probability
            delta = neighbor.objective_value - current.objective_value
            if not self.problem.is_minimization:
                delta = -delta
            
            if delta < 0 or random.random() < math.exp(-delta / temperature):
                current = neighbor
                
                # Update best
                is_better = (current.objective_value  best.objective_value)
                if is_better:
                    best = current.copy()
        
        return best


class OptimizationAgent:
    """Agent that solves optimization problems."""
    
    def __init__(self, name: str, model_name: str = "gpt-4"):
        self.name = name
        self.llm = ChatOpenAI(model=model_name, temperature=0.3)
        self.problems: List[OptimizationProblem] = []
    
    def add_problem(self, problem: OptimizationProblem):
        """Add optimization problem."""
        self.problems.append(problem)
    
    def solve(self, algorithm: str = "simulated_annealing") -> List[Solution]:
        """Solve all problems."""
        solutions = []
        
        for i, problem in enumerate(self.problems, 1):
            print(f"\n   {self.name}: Solving problem {i}...")
            
            if algorithm == "hill_climbing":
                optimizer = HillClimbingOptimizer(problem)
                solution = optimizer.optimize(max_iterations=100)
            else:  # simulated_annealing
                optimizer = SimulatedAnnealingOptimizer(problem)
                solution = optimizer.optimize(max_iterations=500)
            
            solutions.append(solution)
            print(f"   Optimal value: {solution.objective_value:.4f}")
            print(f"   Variables: {solution.variables}")
        
        return solutions


class OptimizationPattern:
    """
    Optimization Pattern - Find optimal solutions.
    
    Agents solve optimization problems using search algorithms.
    Supports hill climbing, simulated annealing, and other methods.
    """
    
    def __init__(self):
        self.agents: Dict[str, OptimizationAgent] = {}
    
    def register_agent(self, agent: OptimizationAgent):
        """Register optimization agent."""
        self.agents[agent.name] = agent
    
    def run(self, algorithm: str = "simulated_annealing") -> Dict[str, Any]:
        """Execute optimization."""
        print("=== Optimization Pattern Demo ===\n")
        
        all_solutions = []
        for agent in self.agents.values():
            solutions = agent.solve(algorithm=algorithm)
            all_solutions.extend(solutions)
        
        return {
            "pattern": "Optimization",
            "algorithm": algorithm,
            "total_agents": len(self.agents),
            "problems_solved": len(all_solutions)
        }


# Example usage
if __name__ == "__main__":
    print("=== Optimization Pattern Demo ===\n")
    
    # Create pattern
    pattern = OptimizationPattern()
    
    # Create agent
    print("1. Creating optimization agent...")
    agent = OptimizationAgent("OptimizerAgent")
    
    # Problem 1: Minimize a quadratic function
    print("\n2. Defining optimization problems...")
    
    def quadratic(vars: Dict[str, float]) -> float:
        """Minimize (x-3)^2 + (y+2)^2"""
        x = vars.get('x', 0)
        y = vars.get('y', 0)
        return (x - 3)**2 + (y + 2)**2
    
    problem1 = OptimizationProblem(objective=quadratic, is_minimization=True)
    problem1.set_bounds('x', -10, 10)
    problem1.set_bounds('y', -10, 10)
    agent.add_problem(problem1)
    
    # Problem 2: Maximize profit function
    def profit(vars: Dict[str, float]) -> float:
        """Maximize: 5*x + 3*y - 0.5*x^2 - 0.3*y^2"""
        x = vars.get('x', 0)
        y = vars.get('y', 0)
        return 5*x + 3*y - 0.5*x**2 - 0.3*y**2
    
    problem2 = OptimizationProblem(objective=profit, is_minimization=False)
    problem2.set_bounds('x', 0, 10)
    problem2.set_bounds('y', 0, 10)
    agent.add_problem(problem2)
    
    pattern.register_agent(agent)
    
    # Solve
    print("\n3. Optimizing...")
    summary = pattern.run(algorithm="simulated_annealing")
    
    print(f"\n4. Summary:")
    print(f"   Algorithm: {summary['algorithm']}")
    print(f"   Problems Solved: {summary['problems_solved']}")
    
    print("\nāœ“ Optimization found optimal solutions!")

    

Agentic Architecture Pattern: Optimization

Pattern 48: 48 Optimization – Line-by-Line Explanation

Overview

Pattern 48: Optimization Pattern

Finds optimal solutions by minimizing/maximizing objective functions. Features hill climbing, simulated annealing, and genetic algorithms.


Code Structure Analysis

File Information

  • Pattern Number: 48
  • Pattern Name: 48 Optimization
  • File Path: patterns\decision_making\48_optimization.py
  • Total Lines: 248

Detailed Code Explanation

Module Docstring

"""
Pattern 48: Optimization Pattern

Finds optimal solutions by minimizing/maximizing objective functions.
Features hill climbing, simulated annealing, and genetic algorithms.
"""

Explanation: This module docstring describes the purpose and key features of the 48 Optimization pattern.


Imports Section

from typing import Dict, Any, List, Optional, Callable, Tuple
from dataclasses import dataclass
import random
import math
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI  # First 20 imports shown

Explanation: The module imports necessary dependencies: – from typing import Dict, Any, List, Optional, Callable, Tuple: Type hints for better code documentation – from dataclasses import dataclass: Data class decorators for clean data structures – from langchain_core.prompts import ChatPromptTemplate: LangChain framework integration – from langchain_openai import ChatOpenAI: LangChain framework integration


Classes

Solution (Line 17)

Description: Represents a candidate solution.

Methods:

copy (Line 22)
def copy(self):
    """Create a copy of the solution."""

Purpose: Create a copy of the solution.

Parameters: – self: Instance reference

OptimizationProblem (Line 30)

Description: Defines an optimization problem.

Methods:

__init__ (Line 33)
def __init__(self, objective, is_minimization):
    """No method docstring."""

Purpose: No method docstring.

Parameters: – self: Instance reference – objective: Parameter for objective – is_minimization: Parameter for is_minimization

set_bounds (Line 39)
def set_bounds(self, variable, lower, upper):
    """Set bounds for a variable."""

Purpose: Set bounds for a variable.

Parameters: – self: Instance reference – variable: Parameter for variable – lower: Parameter for lower – upper: Parameter for upper

evaluate (Line 43)
def evaluate(self, variables):
    """Evaluate objective function."""

Purpose: Evaluate objective function.

Parameters: – self: Instance reference – variables: Parameter for variables

random_solution (Line 47)
def random_solution(self):
    """Generate random solution within bounds."""

Purpose: Generate random solution within bounds.

Parameters: – self: Instance reference

neighbor_solution (Line 56)
def neighbor_solution(self, solution, step_size):
    """Generate neighbor solution."""

Purpose: Generate neighbor solution.

Parameters: – self: Instance reference – solution: Parameter for solution – step_size: Parameter for step_size

HillClimbingOptimizer (Line 72)

Description: Hill climbing optimization algorithm.

Methods:

__init__ (Line 75)
def __init__(self, problem):
    """No method docstring."""

Purpose: No method docstring.

Parameters: – self: Instance reference – problem: Parameter for problem

optimize (Line 78)
def optimize(self, max_iterations):
    """Run hill climbing."""

Purpose: Run hill climbing.

Parameters: – self: Instance reference – max_iterations: Parameter for max_iterations

SimulatedAnnealingOptimizer (Line 96)

Description: Simulated annealing optimization.

Methods:

__init__ (Line 99)
def __init__(self, problem):
    """No method docstring."""

Purpose: No method docstring.

Parameters: – self: Instance reference – problem: Parameter for problem

optimize (Line 102)
def optimize(self, max_iterations, initial_temp):
    """Run simulated annealing."""

Purpose: Run simulated annealing.

Parameters: – self: Instance reference – max_iterations: Parameter for max_iterations – initial_temp: Parameter for initial_temp

OptimizationAgent (Line 135)

Description: Agent that solves optimization problems.

Methods:

__init__ (Line 138)
def __init__(self, name, model_name):
    """No method docstring."""

Purpose: No method docstring.

Parameters: – self: Instance reference – name: Identifier or label – model_name: LLM model to use (e.g., “gpt-4”)

add_problem (Line 143)
def add_problem(self, problem):
    """Add optimization problem."""

Purpose: Add optimization problem.

Parameters: – self: Instance reference – problem: Parameter for problem

solve (Line 147)
def solve(self, algorithm):
    """Solve all problems."""

Purpose: Solve all problems.

Parameters: – self: Instance reference – algorithm: Parameter for algorithm

OptimizationPattern (Line 168)

Description: Optimization Pattern – Find optimal solutions.

Agents solve optimization problems using search algorithms. Supports hill climbing, simulated annealing, and other methods.

Methods:

__init__ (Line 176)
def __init__(self):
    """No method docstring."""

Purpose: No method docstring.

Parameters: – self: Instance reference

register_agent (Line 179)
def register_agent(self, agent):
    """Register optimization agent."""

Purpose: Register optimization agent.

Parameters: – self: Instance reference – agent: Parameter for agent

run (Line 183)
def run(self, algorithm):
    """Execute optimization."""

Purpose: Execute optimization.

Parameters: – self: Instance reference – algorithm: Parameter for algorithm


Design Patterns Used

  • Strategy Pattern: Different execution strategies for various scenarios
  • State Pattern: Managing different states in the workflow
  • Chain of Responsibility: Sequential processing through workflow nodes
  • Observer Pattern: Tracking and monitoring pattern execution
  • Factory Pattern: Creating instances with proper configuration

Usage Example

# Initialize the pattern
pattern = Solution(name="example", model_name="gpt-4")

# Configure and execute
result = pattern.execute(data={"key": "value"})

# Check results
print(result)

Key Concepts

  • LLM Integration: Uses LangChain/OpenAI for intelligent decision-making
  • Workflow Orchestration: LangGraph manages execution flow through nodes
  • State Management: TypedDict defines workflow state structure
  • Error Handling: Comprehensive error handling and fallback mechanisms
  • Extensibility: Designed for easy customization and extension

Extension Points

This pattern can be extended by: 1. Adding custom workflow nodes 2. Implementing additional validation logic 3. Integrating with external services 4. Customizing LLM prompts and responses 5. Adding monitoring and logging capabilities

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *