Add AGI module: sofia_reasoning.py
Browse files- sofia_reasoning.py +742 -0
sofia_reasoning.py
ADDED
|
@@ -0,0 +1,742 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
SOFIA Advanced Reasoning Engine
|
| 4 |
+
Implements task decomposition, strategy selection, and logical reasoning
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import re
|
| 8 |
+
import json
|
| 9 |
+
import logging
|
| 10 |
+
from typing import Dict, List, Tuple, Optional, Any, Set
|
| 11 |
+
from datetime import datetime
|
| 12 |
+
from collections import defaultdict, deque
|
| 13 |
+
import heapq
|
| 14 |
+
|
| 15 |
+
logging.basicConfig(level=logging.INFO)
|
| 16 |
+
logger = logging.getLogger(__name__)
|
| 17 |
+
|
| 18 |
+
class Task:
|
| 19 |
+
"""
|
| 20 |
+
Represents a task that can be decomposed and reasoned about
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
def __init__(self, description: str, complexity: int = 1, dependencies: List[str] = None):
|
| 24 |
+
self.description = description
|
| 25 |
+
self.complexity = complexity # 1-10 scale
|
| 26 |
+
self.dependencies = dependencies or []
|
| 27 |
+
self.subtasks = []
|
| 28 |
+
self.completed = False
|
| 29 |
+
self.created_at = datetime.now()
|
| 30 |
+
self.estimated_time = self._estimate_time()
|
| 31 |
+
|
| 32 |
+
def _estimate_time(self) -> float:
|
| 33 |
+
"""Estimate time required based on complexity and description"""
|
| 34 |
+
base_time = self.complexity * 5 # 5 minutes per complexity unit
|
| 35 |
+
|
| 36 |
+
# Adjust based on keywords
|
| 37 |
+
desc_lower = self.description.lower()
|
| 38 |
+
if any(word in desc_lower for word in ['research', 'analyze', 'investigate']):
|
| 39 |
+
base_time *= 1.5
|
| 40 |
+
if any(word in desc_lower for word in ['create', 'build', 'implement']):
|
| 41 |
+
base_time *= 2.0
|
| 42 |
+
if any(word in desc_lower for word in ['simple', 'quick', 'basic']):
|
| 43 |
+
base_time *= 0.5
|
| 44 |
+
|
| 45 |
+
return base_time
|
| 46 |
+
|
| 47 |
+
def add_subtask(self, subtask: 'Task'):
|
| 48 |
+
"""Add a subtask to this task"""
|
| 49 |
+
self.subtasks.append(subtask)
|
| 50 |
+
|
| 51 |
+
def mark_completed(self):
|
| 52 |
+
"""Mark this task as completed"""
|
| 53 |
+
self.completed = True
|
| 54 |
+
|
| 55 |
+
def to_dict(self) -> Dict[str, Any]:
|
| 56 |
+
"""Convert task to dictionary representation"""
|
| 57 |
+
return {
|
| 58 |
+
'description': self.description,
|
| 59 |
+
'complexity': self.complexity,
|
| 60 |
+
'dependencies': self.dependencies,
|
| 61 |
+
'subtasks': [st.to_dict() for st in self.subtasks],
|
| 62 |
+
'completed': self.completed,
|
| 63 |
+
'estimated_time': self.estimated_time,
|
| 64 |
+
'created_at': self.created_at.isoformat()
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
class ReasoningStrategy:
|
| 68 |
+
"""
|
| 69 |
+
Represents a reasoning strategy for solving problems
|
| 70 |
+
"""
|
| 71 |
+
|
| 72 |
+
def __init__(self, name: str, description: str, applicable_domains: List[str],
|
| 73 |
+
success_rate: float = 0.7, avg_time: float = 10.0):
|
| 74 |
+
self.name = name
|
| 75 |
+
self.description = description
|
| 76 |
+
self.applicable_domains = applicable_domains
|
| 77 |
+
self.success_rate = success_rate
|
| 78 |
+
self.avg_time = avg_time
|
| 79 |
+
self.usage_count = 0
|
| 80 |
+
self.success_count = 0
|
| 81 |
+
|
| 82 |
+
def is_applicable(self, problem_domain: str, problem_complexity: int) -> bool:
|
| 83 |
+
"""Check if this strategy is applicable to the given problem"""
|
| 84 |
+
domain_match = problem_domain in self.applicable_domains or 'general' in self.applicable_domains
|
| 85 |
+
|
| 86 |
+
# Some strategies work better for different complexity levels
|
| 87 |
+
if 'simple_problems' in self.applicable_domains and problem_complexity <= 3:
|
| 88 |
+
return True
|
| 89 |
+
if 'complex_problems' in self.applicable_domains and problem_complexity >= 7:
|
| 90 |
+
return True
|
| 91 |
+
|
| 92 |
+
return domain_match
|
| 93 |
+
|
| 94 |
+
def record_usage(self, success: bool):
|
| 95 |
+
"""Record usage of this strategy"""
|
| 96 |
+
self.usage_count += 1
|
| 97 |
+
if success:
|
| 98 |
+
self.success_count += 1
|
| 99 |
+
self.success_rate = self.success_count / self.usage_count
|
| 100 |
+
|
| 101 |
+
def get_effectiveness_score(self) -> float:
|
| 102 |
+
"""Get effectiveness score for strategy selection"""
|
| 103 |
+
# Combine success rate with usage experience
|
| 104 |
+
experience_factor = min(1.0, self.usage_count / 10) # Diminishing returns after 10 uses
|
| 105 |
+
return self.success_rate * (0.5 + 0.5 * experience_factor)
|
| 106 |
+
|
| 107 |
+
class TaskDecomposer:
|
| 108 |
+
"""
|
| 109 |
+
Decomposes complex tasks into manageable subtasks
|
| 110 |
+
"""
|
| 111 |
+
|
| 112 |
+
def __init__(self):
|
| 113 |
+
self.decomposition_patterns = self._load_patterns()
|
| 114 |
+
|
| 115 |
+
def _load_patterns(self) -> Dict[str, List[str]]:
|
| 116 |
+
"""Load task decomposition patterns"""
|
| 117 |
+
return {
|
| 118 |
+
'research': [
|
| 119 |
+
'Define research question',
|
| 120 |
+
'Identify information sources',
|
| 121 |
+
'Gather relevant data',
|
| 122 |
+
'Analyze findings',
|
| 123 |
+
'Synthesize conclusions'
|
| 124 |
+
],
|
| 125 |
+
'implementation': [
|
| 126 |
+
'Analyze requirements',
|
| 127 |
+
'Design solution architecture',
|
| 128 |
+
'Implement core functionality',
|
| 129 |
+
'Add error handling',
|
| 130 |
+
'Test implementation',
|
| 131 |
+
'Document solution'
|
| 132 |
+
],
|
| 133 |
+
'problem_solving': [
|
| 134 |
+
'Understand the problem',
|
| 135 |
+
'Break down into components',
|
| 136 |
+
'Identify potential solutions',
|
| 137 |
+
'Evaluate solution options',
|
| 138 |
+
'Implement chosen solution',
|
| 139 |
+
'Verify solution works'
|
| 140 |
+
],
|
| 141 |
+
'learning': [
|
| 142 |
+
'Assess current knowledge',
|
| 143 |
+
'Identify learning objectives',
|
| 144 |
+
'Find learning resources',
|
| 145 |
+
'Study materials',
|
| 146 |
+
'Practice concepts',
|
| 147 |
+
'Assess understanding'
|
| 148 |
+
]
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
def decompose_task(self, task_description: str, complexity: int = 5) -> Task:
|
| 152 |
+
"""Decompose a task into subtasks"""
|
| 153 |
+
# Identify task type
|
| 154 |
+
task_type = self._classify_task_type(task_description)
|
| 155 |
+
|
| 156 |
+
# Create main task
|
| 157 |
+
main_task = Task(task_description, complexity)
|
| 158 |
+
|
| 159 |
+
# Get decomposition pattern
|
| 160 |
+
if task_type in self.decomposition_patterns:
|
| 161 |
+
pattern = self.decomposition_patterns[task_type]
|
| 162 |
+
|
| 163 |
+
# Adjust pattern based on complexity
|
| 164 |
+
if complexity <= 3:
|
| 165 |
+
# Simplify for simple tasks
|
| 166 |
+
pattern = pattern[:3]
|
| 167 |
+
elif complexity >= 8:
|
| 168 |
+
# Expand for complex tasks
|
| 169 |
+
pattern.extend([
|
| 170 |
+
'Review and refine',
|
| 171 |
+
'Optimize performance',
|
| 172 |
+
'Prepare for deployment'
|
| 173 |
+
])
|
| 174 |
+
|
| 175 |
+
# Create subtasks
|
| 176 |
+
for i, subtask_desc in enumerate(pattern):
|
| 177 |
+
subtask_complexity = max(1, complexity // len(pattern))
|
| 178 |
+
subtask = Task(subtask_desc, subtask_complexity)
|
| 179 |
+
main_task.add_subtask(subtask)
|
| 180 |
+
|
| 181 |
+
else:
|
| 182 |
+
# Generic decomposition for unknown task types
|
| 183 |
+
subtasks = self._generic_decomposition(task_description, complexity)
|
| 184 |
+
for subtask_desc in subtasks:
|
| 185 |
+
subtask = Task(subtask_desc, max(1, complexity // len(subtasks)))
|
| 186 |
+
main_task.add_subtask(subtask)
|
| 187 |
+
|
| 188 |
+
return main_task
|
| 189 |
+
|
| 190 |
+
def _classify_task_type(self, description: str) -> str:
|
| 191 |
+
"""Classify the type of task"""
|
| 192 |
+
desc_lower = description.lower()
|
| 193 |
+
|
| 194 |
+
if any(word in desc_lower for word in ['research', 'investigate', 'analyze', 'study']):
|
| 195 |
+
return 'research'
|
| 196 |
+
elif any(word in desc_lower for word in ['implement', 'build', 'create', 'develop']):
|
| 197 |
+
return 'implementation'
|
| 198 |
+
elif any(word in desc_lower for word in ['solve', 'fix', 'resolve', 'answer']):
|
| 199 |
+
return 'problem_solving'
|
| 200 |
+
elif any(word in desc_lower for word in ['learn', 'understand', 'master', 'study']):
|
| 201 |
+
return 'learning'
|
| 202 |
+
else:
|
| 203 |
+
return 'general'
|
| 204 |
+
|
| 205 |
+
def _generic_decomposition(self, description: str, complexity: int) -> List[str]:
|
| 206 |
+
"""Generic task decomposition when no specific pattern matches"""
|
| 207 |
+
base_steps = [
|
| 208 |
+
'Plan the approach',
|
| 209 |
+
'Gather necessary resources',
|
| 210 |
+
'Execute the main work',
|
| 211 |
+
'Review and verify results'
|
| 212 |
+
]
|
| 213 |
+
|
| 214 |
+
if complexity > 5:
|
| 215 |
+
base_steps.insert(1, 'Break down into smaller steps')
|
| 216 |
+
base_steps.insert(-1, 'Test and validate')
|
| 217 |
+
|
| 218 |
+
return base_steps
|
| 219 |
+
|
| 220 |
+
class StrategySelector:
|
| 221 |
+
"""
|
| 222 |
+
Selects the best reasoning strategy for a given problem
|
| 223 |
+
"""
|
| 224 |
+
|
| 225 |
+
def __init__(self):
|
| 226 |
+
self.strategies = self._initialize_strategies()
|
| 227 |
+
|
| 228 |
+
def _initialize_strategies(self) -> List[ReasoningStrategy]:
|
| 229 |
+
"""Initialize available reasoning strategies"""
|
| 230 |
+
return [
|
| 231 |
+
ReasoningStrategy(
|
| 232 |
+
"analytical_reasoning",
|
| 233 |
+
"Break down problem into components and analyze systematically",
|
| 234 |
+
["mathematics", "science", "engineering", "general"],
|
| 235 |
+
success_rate=0.85,
|
| 236 |
+
avg_time=12.0
|
| 237 |
+
),
|
| 238 |
+
ReasoningStrategy(
|
| 239 |
+
"creative_problem_solving",
|
| 240 |
+
"Use creative thinking and brainstorming for novel solutions",
|
| 241 |
+
["design", "innovation", "art", "general"],
|
| 242 |
+
success_rate=0.75,
|
| 243 |
+
avg_time=15.0
|
| 244 |
+
),
|
| 245 |
+
ReasoningStrategy(
|
| 246 |
+
"deductive_reasoning",
|
| 247 |
+
"Apply logical deduction from general principles to specific cases",
|
| 248 |
+
["logic", "philosophy", "mathematics", "law"],
|
| 249 |
+
success_rate=0.80,
|
| 250 |
+
avg_time=10.0
|
| 251 |
+
),
|
| 252 |
+
ReasoningStrategy(
|
| 253 |
+
"inductive_reasoning",
|
| 254 |
+
"Draw general conclusions from specific observations",
|
| 255 |
+
["science", "research", "data_analysis"],
|
| 256 |
+
success_rate=0.70,
|
| 257 |
+
avg_time=18.0
|
| 258 |
+
),
|
| 259 |
+
ReasoningStrategy(
|
| 260 |
+
"case_based_reasoning",
|
| 261 |
+
"Solve problems by adapting solutions from similar past cases",
|
| 262 |
+
["medicine", "law", "customer_service", "general"],
|
| 263 |
+
success_rate=0.78,
|
| 264 |
+
avg_time=8.0
|
| 265 |
+
),
|
| 266 |
+
ReasoningStrategy(
|
| 267 |
+
"algorithmic_approach",
|
| 268 |
+
"Apply step-by-step algorithmic procedures",
|
| 269 |
+
["programming", "mathematics", "engineering"],
|
| 270 |
+
success_rate=0.90,
|
| 271 |
+
avg_time=6.0
|
| 272 |
+
),
|
| 273 |
+
ReasoningStrategy(
|
| 274 |
+
"intuitive_reasoning",
|
| 275 |
+
"Rely on intuition and experience for quick solutions",
|
| 276 |
+
["simple_problems", "general"],
|
| 277 |
+
success_rate=0.65,
|
| 278 |
+
avg_time=3.0
|
| 279 |
+
)
|
| 280 |
+
]
|
| 281 |
+
|
| 282 |
+
def select_strategy(self, problem_description: str, problem_complexity: int = 5,
|
| 283 |
+
time_constraint: Optional[float] = None) -> ReasoningStrategy:
|
| 284 |
+
"""
|
| 285 |
+
Select the best reasoning strategy for the given problem
|
| 286 |
+
|
| 287 |
+
Args:
|
| 288 |
+
problem_description: Description of the problem
|
| 289 |
+
problem_complexity: Complexity level (1-10)
|
| 290 |
+
time_constraint: Maximum time allowed (minutes)
|
| 291 |
+
|
| 292 |
+
Returns:
|
| 293 |
+
Selected reasoning strategy
|
| 294 |
+
"""
|
| 295 |
+
|
| 296 |
+
# Identify problem domain
|
| 297 |
+
problem_domain = self._identify_domain(problem_description)
|
| 298 |
+
|
| 299 |
+
# Filter applicable strategies
|
| 300 |
+
applicable_strategies = [
|
| 301 |
+
strategy for strategy in self.strategies
|
| 302 |
+
if strategy.is_applicable(problem_domain, problem_complexity)
|
| 303 |
+
]
|
| 304 |
+
|
| 305 |
+
if not applicable_strategies:
|
| 306 |
+
# Fallback to general strategies
|
| 307 |
+
applicable_strategies = [
|
| 308 |
+
strategy for strategy in self.strategies
|
| 309 |
+
if 'general' in strategy.applicable_domains
|
| 310 |
+
]
|
| 311 |
+
|
| 312 |
+
# Apply time constraint if specified
|
| 313 |
+
if time_constraint:
|
| 314 |
+
applicable_strategies = [
|
| 315 |
+
strategy for strategy in applicable_strategies
|
| 316 |
+
if strategy.avg_time <= time_constraint
|
| 317 |
+
]
|
| 318 |
+
|
| 319 |
+
if not applicable_strategies:
|
| 320 |
+
# Ultimate fallback
|
| 321 |
+
return self.strategies[0]
|
| 322 |
+
|
| 323 |
+
# Score strategies based on effectiveness and time
|
| 324 |
+
scored_strategies = []
|
| 325 |
+
for strategy in applicable_strategies:
|
| 326 |
+
effectiveness = strategy.get_effectiveness_score()
|
| 327 |
+
|
| 328 |
+
# Penalize slow strategies for complex problems under time pressure
|
| 329 |
+
time_penalty = 0
|
| 330 |
+
if time_constraint and strategy.avg_time > time_constraint * 0.8:
|
| 331 |
+
time_penalty = 0.2
|
| 332 |
+
|
| 333 |
+
final_score = effectiveness * (1 - time_penalty)
|
| 334 |
+
scored_strategies.append((final_score, strategy))
|
| 335 |
+
|
| 336 |
+
# Select highest scoring strategy
|
| 337 |
+
scored_strategies.sort(reverse=True)
|
| 338 |
+
selected_strategy = scored_strategies[0][1]
|
| 339 |
+
|
| 340 |
+
logger.info(f"Selected strategy: {selected_strategy.name} (score: {scored_strategies[0][0]:.3f})")
|
| 341 |
+
return selected_strategy
|
| 342 |
+
|
| 343 |
+
def _identify_domain(self, description: str) -> str:
|
| 344 |
+
"""Identify the problem domain from description"""
|
| 345 |
+
desc_lower = description.lower()
|
| 346 |
+
|
| 347 |
+
domain_keywords = {
|
| 348 |
+
'mathematics': ['math', 'calculate', 'equation', 'algebra', 'geometry'],
|
| 349 |
+
'programming': ['code', 'program', 'software', 'algorithm', 'debug'],
|
| 350 |
+
'science': ['experiment', 'hypothesis', 'theory', 'research', 'data'],
|
| 351 |
+
'engineering': ['design', 'build', 'construct', 'system', 'architecture'],
|
| 352 |
+
'business': ['profit', 'market', 'customer', 'strategy', 'finance'],
|
| 353 |
+
'medicine': ['patient', 'diagnosis', 'treatment', 'health', 'medical'],
|
| 354 |
+
'law': ['legal', 'contract', 'regulation', 'court', 'justice'],
|
| 355 |
+
'education': ['learn', 'teach', 'student', 'course', 'knowledge']
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
for domain, keywords in domain_keywords.items():
|
| 359 |
+
if any(keyword in desc_lower for keyword in keywords):
|
| 360 |
+
return domain
|
| 361 |
+
|
| 362 |
+
return 'general'
|
| 363 |
+
|
| 364 |
+
class LogicalReasoner:
|
| 365 |
+
"""
|
| 366 |
+
Performs logical reasoning and inference
|
| 367 |
+
"""
|
| 368 |
+
|
| 369 |
+
def __init__(self):
|
| 370 |
+
self.knowledge_base = defaultdict(list)
|
| 371 |
+
self.inference_rules = self._load_inference_rules()
|
| 372 |
+
|
| 373 |
+
def _load_inference_rules(self) -> List[Dict[str, Any]]:
|
| 374 |
+
"""Load basic inference rules"""
|
| 375 |
+
return [
|
| 376 |
+
{
|
| 377 |
+
'name': 'modus_ponens',
|
| 378 |
+
'description': 'If P implies Q and P is true, then Q is true',
|
| 379 |
+
'pattern': lambda premises: self._check_modus_ponens(premises)
|
| 380 |
+
},
|
| 381 |
+
{
|
| 382 |
+
'name': 'transitivity',
|
| 383 |
+
'description': 'If A relates to B and B relates to C, then A relates to C',
|
| 384 |
+
'pattern': lambda premises: self._check_transitivity(premises)
|
| 385 |
+
},
|
| 386 |
+
{
|
| 387 |
+
'name': 'contradiction_detection',
|
| 388 |
+
'description': 'Detect logical contradictions',
|
| 389 |
+
'pattern': lambda premises: self._check_contradiction(premises)
|
| 390 |
+
}
|
| 391 |
+
]
|
| 392 |
+
|
| 393 |
+
def add_knowledge(self, fact: str, category: str = 'general'):
|
| 394 |
+
"""Add a fact to the knowledge base"""
|
| 395 |
+
self.knowledge_base[category].append({
|
| 396 |
+
'fact': fact,
|
| 397 |
+
'added_at': datetime.now(),
|
| 398 |
+
'confidence': 1.0
|
| 399 |
+
})
|
| 400 |
+
|
| 401 |
+
def draw_inference(self, premises: List[str]) -> Dict[str, Any]:
|
| 402 |
+
"""Draw logical inferences from premises"""
|
| 403 |
+
inferences = []
|
| 404 |
+
|
| 405 |
+
# Apply inference rules
|
| 406 |
+
for rule in self.inference_rules:
|
| 407 |
+
result = rule['pattern'](premises)
|
| 408 |
+
if result:
|
| 409 |
+
inferences.append({
|
| 410 |
+
'rule': rule['name'],
|
| 411 |
+
'conclusion': result,
|
| 412 |
+
'confidence': 0.8 # Base confidence
|
| 413 |
+
})
|
| 414 |
+
|
| 415 |
+
# Look for patterns in knowledge base
|
| 416 |
+
kb_inferences = self._knowledge_base_inference(premises)
|
| 417 |
+
inferences.extend(kb_inferences)
|
| 418 |
+
|
| 419 |
+
return {
|
| 420 |
+
'inferences': inferences,
|
| 421 |
+
'premises_used': len(premises),
|
| 422 |
+
'total_inferences': len(inferences)
|
| 423 |
+
}
|
| 424 |
+
|
| 425 |
+
def _check_modus_ponens(self, premises: List[str]) -> Optional[str]:
|
| 426 |
+
"""Check for modus ponens pattern: If P then Q, P -> Q"""
|
| 427 |
+
# Simplified implementation
|
| 428 |
+
for premise in premises:
|
| 429 |
+
if 'if' in premise.lower() and 'then' in premise.lower():
|
| 430 |
+
# This is a conditional premise
|
| 431 |
+
parts = re.split(r'\s+(?:if|then)\s+', premise, flags=re.IGNORECASE)
|
| 432 |
+
if len(parts) >= 2:
|
| 433 |
+
condition = parts[0].strip()
|
| 434 |
+
conclusion = parts[1].strip()
|
| 435 |
+
|
| 436 |
+
# Check if condition is in other premises
|
| 437 |
+
for other_premise in premises:
|
| 438 |
+
if other_premise != premise and condition.lower() in other_premise.lower():
|
| 439 |
+
return conclusion
|
| 440 |
+
|
| 441 |
+
return None
|
| 442 |
+
|
| 443 |
+
def _check_transitivity(self, premises: List[str]) -> Optional[str]:
|
| 444 |
+
"""Check for transitivity pattern"""
|
| 445 |
+
# Simplified implementation - look for chains
|
| 446 |
+
relationships = []
|
| 447 |
+
for premise in premises:
|
| 448 |
+
# Look for patterns like "A is related to B"
|
| 449 |
+
match = re.search(r'(.+?)\s+(?:is|are|relates? to)\s+(.+)', premise, re.IGNORECASE)
|
| 450 |
+
if match:
|
| 451 |
+
relationships.append((match.group(1).strip(), match.group(2).strip()))
|
| 452 |
+
|
| 453 |
+
# Check for transitive chains
|
| 454 |
+
for i, (a1, b1) in enumerate(relationships):
|
| 455 |
+
for j, (a2, b2) in enumerate(relationships):
|
| 456 |
+
if i != j and b1.lower() == a2.lower():
|
| 457 |
+
return f"{a1} relates to {b2}"
|
| 458 |
+
|
| 459 |
+
return None
|
| 460 |
+
|
| 461 |
+
def _check_contradiction(self, premises: List[str]) -> Optional[str]:
|
| 462 |
+
"""Check for contradictions"""
|
| 463 |
+
statements = []
|
| 464 |
+
negations = []
|
| 465 |
+
|
| 466 |
+
for premise in premises:
|
| 467 |
+
premise_lower = premise.lower()
|
| 468 |
+
statements.append(premise_lower)
|
| 469 |
+
|
| 470 |
+
# Simple negation detection
|
| 471 |
+
if premise_lower.startswith(('not ', 'no ', 'never ')):
|
| 472 |
+
negations.append(premise_lower[4:])
|
| 473 |
+
elif 'not' in premise_lower:
|
| 474 |
+
# Split on 'not'
|
| 475 |
+
parts = premise_lower.split('not', 1)
|
| 476 |
+
if len(parts) == 2:
|
| 477 |
+
negations.append(parts[0].strip() + parts[1].strip())
|
| 478 |
+
|
| 479 |
+
# Check for contradictions
|
| 480 |
+
for stmt in statements:
|
| 481 |
+
for neg in negations:
|
| 482 |
+
if self._are_contradictory(stmt, neg):
|
| 483 |
+
return f"Contradiction detected: '{stmt}' vs 'not {neg}'"
|
| 484 |
+
|
| 485 |
+
return None
|
| 486 |
+
|
| 487 |
+
def _are_contradictory(self, stmt1: str, stmt2: str) -> bool:
|
| 488 |
+
"""Check if two statements are contradictory"""
|
| 489 |
+
# Very simplified contradiction detection
|
| 490 |
+
return stmt1.strip() == stmt2.strip()
|
| 491 |
+
|
| 492 |
+
def _knowledge_base_inference(self, premises: List[str]) -> List[Dict[str, Any]]:
|
| 493 |
+
"""Draw inferences using knowledge base"""
|
| 494 |
+
inferences = []
|
| 495 |
+
|
| 496 |
+
for category, facts in self.knowledge_base.items():
|
| 497 |
+
for fact in facts:
|
| 498 |
+
fact_text = fact['fact']
|
| 499 |
+
|
| 500 |
+
# Check if any premise relates to this fact
|
| 501 |
+
for premise in premises:
|
| 502 |
+
if self._texts_related(premise, fact_text):
|
| 503 |
+
inferences.append({
|
| 504 |
+
'rule': 'knowledge_base_similarity',
|
| 505 |
+
'conclusion': f"Related knowledge: {fact_text}",
|
| 506 |
+
'confidence': fact['confidence'] * 0.7,
|
| 507 |
+
'category': category
|
| 508 |
+
})
|
| 509 |
+
|
| 510 |
+
return inferences
|
| 511 |
+
|
| 512 |
+
def _texts_related(self, text1: str, text2: str) -> bool:
|
| 513 |
+
"""Check if two texts are related (simplified)"""
|
| 514 |
+
words1 = set(text1.lower().split())
|
| 515 |
+
words2 = set(text2.lower().split())
|
| 516 |
+
|
| 517 |
+
# Check for word overlap
|
| 518 |
+
overlap = len(words1.intersection(words2))
|
| 519 |
+
return overlap >= 2 # At least 2 words in common
|
| 520 |
+
|
| 521 |
+
class AdvancedReasoningEngine:
|
| 522 |
+
"""
|
| 523 |
+
Main advanced reasoning engine combining all reasoning capabilities
|
| 524 |
+
"""
|
| 525 |
+
|
| 526 |
+
def __init__(self):
|
| 527 |
+
self.task_decomposer = TaskDecomposer()
|
| 528 |
+
self.strategy_selector = StrategySelector()
|
| 529 |
+
self.logical_reasoner = LogicalReasoner()
|
| 530 |
+
|
| 531 |
+
# Reasoning history
|
| 532 |
+
self.reasoning_history = deque(maxlen=1000)
|
| 533 |
+
|
| 534 |
+
def reason_about_task(self, task_description: str,
|
| 535 |
+
complexity: int = 5,
|
| 536 |
+
time_constraint: Optional[float] = None) -> Dict[str, Any]:
|
| 537 |
+
"""
|
| 538 |
+
Perform comprehensive reasoning about a task
|
| 539 |
+
|
| 540 |
+
Args:
|
| 541 |
+
task_description: Description of the task
|
| 542 |
+
complexity: Task complexity (1-10)
|
| 543 |
+
time_constraint: Time limit in minutes
|
| 544 |
+
|
| 545 |
+
Returns:
|
| 546 |
+
Reasoning results
|
| 547 |
+
"""
|
| 548 |
+
|
| 549 |
+
reasoning_start = datetime.now()
|
| 550 |
+
|
| 551 |
+
# 1. Decompose the task
|
| 552 |
+
decomposed_task = self.task_decomposer.decompose_task(task_description, complexity)
|
| 553 |
+
|
| 554 |
+
# 2. Select reasoning strategy
|
| 555 |
+
strategy = self.strategy_selector.select_strategy(
|
| 556 |
+
task_description, complexity, time_constraint
|
| 557 |
+
)
|
| 558 |
+
|
| 559 |
+
# 3. Perform logical reasoning
|
| 560 |
+
task_premises = [task_description]
|
| 561 |
+
if decomposed_task.subtasks:
|
| 562 |
+
task_premises.extend([st.description for st in decomposed_task.subtasks])
|
| 563 |
+
|
| 564 |
+
logical_inferences = self.logical_reasoner.draw_inference(task_premises)
|
| 565 |
+
|
| 566 |
+
# 4. Generate execution plan
|
| 567 |
+
execution_plan = self._generate_execution_plan(decomposed_task, strategy)
|
| 568 |
+
|
| 569 |
+
# 5. Assess feasibility
|
| 570 |
+
feasibility = self._assess_feasibility(decomposed_task, time_constraint)
|
| 571 |
+
|
| 572 |
+
reasoning_result = {
|
| 573 |
+
'task_analysis': {
|
| 574 |
+
'original_task': task_description,
|
| 575 |
+
'complexity': complexity,
|
| 576 |
+
'estimated_total_time': decomposed_task.estimated_time,
|
| 577 |
+
'subtasks_count': len(decomposed_task.subtasks)
|
| 578 |
+
},
|
| 579 |
+
'decomposition': decomposed_task.to_dict(),
|
| 580 |
+
'selected_strategy': {
|
| 581 |
+
'name': strategy.name,
|
| 582 |
+
'description': strategy.description,
|
| 583 |
+
'expected_success_rate': strategy.success_rate,
|
| 584 |
+
'estimated_time': strategy.avg_time
|
| 585 |
+
},
|
| 586 |
+
'logical_inferences': logical_inferences,
|
| 587 |
+
'execution_plan': execution_plan,
|
| 588 |
+
'feasibility_assessment': feasibility,
|
| 589 |
+
'reasoning_time': (datetime.now() - reasoning_start).total_seconds()
|
| 590 |
+
}
|
| 591 |
+
|
| 592 |
+
# Record in history
|
| 593 |
+
self.reasoning_history.append({
|
| 594 |
+
'timestamp': datetime.now().isoformat(),
|
| 595 |
+
'task': task_description,
|
| 596 |
+
'result': reasoning_result
|
| 597 |
+
})
|
| 598 |
+
|
| 599 |
+
return reasoning_result
|
| 600 |
+
|
| 601 |
+
def _generate_execution_plan(self, task: Task, strategy: ReasoningStrategy) -> List[Dict[str, Any]]:
|
| 602 |
+
"""Generate a step-by-step execution plan"""
|
| 603 |
+
plan = []
|
| 604 |
+
|
| 605 |
+
# Add strategy-specific preparation steps
|
| 606 |
+
if strategy.name == 'analytical_reasoning':
|
| 607 |
+
plan.append({
|
| 608 |
+
'step': 'analysis',
|
| 609 |
+
'description': 'Analyze task components systematically',
|
| 610 |
+
'estimated_time': 5.0
|
| 611 |
+
})
|
| 612 |
+
elif strategy.name == 'creative_problem_solving':
|
| 613 |
+
plan.append({
|
| 614 |
+
'step': 'brainstorming',
|
| 615 |
+
'description': 'Generate multiple solution approaches',
|
| 616 |
+
'estimated_time': 10.0
|
| 617 |
+
})
|
| 618 |
+
|
| 619 |
+
# Add task subtasks
|
| 620 |
+
for i, subtask in enumerate(task.subtasks):
|
| 621 |
+
plan.append({
|
| 622 |
+
'step': f'subtask_{i+1}',
|
| 623 |
+
'description': subtask.description,
|
| 624 |
+
'estimated_time': subtask.estimated_time,
|
| 625 |
+
'dependencies': subtask.dependencies
|
| 626 |
+
})
|
| 627 |
+
|
| 628 |
+
# Add verification step
|
| 629 |
+
plan.append({
|
| 630 |
+
'step': 'verification',
|
| 631 |
+
'description': 'Verify solution meets requirements',
|
| 632 |
+
'estimated_time': 3.0
|
| 633 |
+
})
|
| 634 |
+
|
| 635 |
+
return plan
|
| 636 |
+
|
| 637 |
+
def _assess_feasibility(self, task: Task, time_constraint: Optional[float]) -> Dict[str, Any]:
|
| 638 |
+
"""Assess the feasibility of completing the task"""
|
| 639 |
+
total_estimated_time = task.estimated_time
|
| 640 |
+
|
| 641 |
+
if time_constraint and total_estimated_time > time_constraint:
|
| 642 |
+
return {
|
| 643 |
+
'feasible': False,
|
| 644 |
+
'reason': f'Estimated time ({total_estimated_time:.1f} min) exceeds constraint ({time_constraint} min)',
|
| 645 |
+
'recommendation': 'Break task into smaller parts or extend time limit'
|
| 646 |
+
}
|
| 647 |
+
|
| 648 |
+
# Assess based on complexity and subtasks
|
| 649 |
+
complexity_score = task.complexity / 10.0
|
| 650 |
+
subtask_score = len(task.subtasks) / 10.0 # Normalize
|
| 651 |
+
|
| 652 |
+
feasibility_score = 1.0 - (complexity_score * 0.4 + subtask_score * 0.3)
|
| 653 |
+
|
| 654 |
+
if feasibility_score > 0.7:
|
| 655 |
+
assessment = 'highly_feasible'
|
| 656 |
+
elif feasibility_score > 0.4:
|
| 657 |
+
assessment = 'moderately_feasible'
|
| 658 |
+
else:
|
| 659 |
+
assessment = 'challenging'
|
| 660 |
+
|
| 661 |
+
return {
|
| 662 |
+
'feasible': feasibility_score > 0.3,
|
| 663 |
+
'assessment': assessment,
|
| 664 |
+
'feasibility_score': feasibility_score,
|
| 665 |
+
'estimated_time': total_estimated_time
|
| 666 |
+
}
|
| 667 |
+
|
| 668 |
+
def learn_from_experience(self, task_description: str, success: bool, actual_time: float):
|
| 669 |
+
"""Learn from task execution experience"""
|
| 670 |
+
# Find the reasoning result for this task
|
| 671 |
+
for record in reversed(self.reasoning_history):
|
| 672 |
+
if record['task'] == task_description:
|
| 673 |
+
reasoning_result = record['result']
|
| 674 |
+
strategy_name = reasoning_result['selected_strategy']['name']
|
| 675 |
+
|
| 676 |
+
# Find the strategy and update its performance
|
| 677 |
+
for strategy in self.strategy_selector.strategies:
|
| 678 |
+
if strategy.name == strategy_name:
|
| 679 |
+
strategy.record_usage(success)
|
| 680 |
+
|
| 681 |
+
# Update time estimate
|
| 682 |
+
old_time = strategy.avg_time
|
| 683 |
+
strategy.avg_time = (old_time + actual_time) / 2 # Simple averaging
|
| 684 |
+
|
| 685 |
+
logger.info(f"Updated strategy {strategy_name}: success_rate={strategy.success_rate:.3f}, avg_time={strategy.avg_time:.1f}")
|
| 686 |
+
break
|
| 687 |
+
|
| 688 |
+
break
|
| 689 |
+
|
| 690 |
+
def get_reasoning_statistics(self) -> Dict[str, Any]:
|
| 691 |
+
"""Get statistics about reasoning performance"""
|
| 692 |
+
if not self.reasoning_history:
|
| 693 |
+
return {'total_reasoning_sessions': 0}
|
| 694 |
+
|
| 695 |
+
total_sessions = len(self.reasoning_history)
|
| 696 |
+
recent_sessions = list(self.reasoning_history)[-50:] # Last 50 sessions
|
| 697 |
+
|
| 698 |
+
# Calculate average reasoning time
|
| 699 |
+
reasoning_times = [r['result']['reasoning_time'] for r in recent_sessions]
|
| 700 |
+
avg_reasoning_time = sum(reasoning_times) / len(reasoning_times)
|
| 701 |
+
|
| 702 |
+
# Strategy usage statistics
|
| 703 |
+
strategy_usage = defaultdict(int)
|
| 704 |
+
for record in recent_sessions:
|
| 705 |
+
strategy_name = record['result']['selected_strategy']['name']
|
| 706 |
+
strategy_usage[strategy_name] += 1
|
| 707 |
+
|
| 708 |
+
return {
|
| 709 |
+
'total_reasoning_sessions': total_sessions,
|
| 710 |
+
'average_reasoning_time': avg_reasoning_time,
|
| 711 |
+
'strategy_usage': dict(strategy_usage),
|
| 712 |
+
'most_used_strategy': max(strategy_usage.items(), key=lambda x: x[1])[0] if strategy_usage else None
|
| 713 |
+
}
|
| 714 |
+
|
| 715 |
+
|
| 716 |
+
# Example usage
|
| 717 |
+
if __name__ == "__main__":
|
| 718 |
+
print("SOFIA Advanced Reasoning Engine")
|
| 719 |
+
print("This system provides task decomposition, strategy selection, and logical reasoning")
|
| 720 |
+
|
| 721 |
+
# Example usage would be integrated with SOFIA
|
| 722 |
+
"""
|
| 723 |
+
from sofia_reasoning import AdvancedReasoningEngine
|
| 724 |
+
|
| 725 |
+
reasoner = AdvancedReasoningEngine()
|
| 726 |
+
|
| 727 |
+
# Reason about a complex task
|
| 728 |
+
result = reasoner.reason_about_task(
|
| 729 |
+
"Implement a machine learning model for image classification",
|
| 730 |
+
complexity=8,
|
| 731 |
+
time_constraint=120 # 2 hours
|
| 732 |
+
)
|
| 733 |
+
|
| 734 |
+
print("Reasoning result:", result)
|
| 735 |
+
|
| 736 |
+
# Learn from execution
|
| 737 |
+
reasoner.learn_from_experience(
|
| 738 |
+
"Implement a machine learning model for image classification",
|
| 739 |
+
success=True,
|
| 740 |
+
actual_time=95.0
|
| 741 |
+
)
|
| 742 |
+
"""
|