kailash 0.2.0__py3-none-any.whl → 0.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,7 +8,7 @@ analysis interface for understanding and improving cyclic workflow execution.
8
8
 
9
9
  Examples:
10
10
  Comprehensive cycle analysis:
11
-
11
+
12
12
  >>> analyzer = CycleAnalyzer(
13
13
  ... analysis_level="comprehensive",
14
14
  ... output_directory="./analysis_results"
@@ -22,9 +22,9 @@ Examples:
22
22
  >>> # Generate comprehensive report
23
23
  >>> report = analyzer.generate_session_report()
24
24
  >>> analyzer.export_analysis_data("analysis_results.json")
25
-
25
+
26
26
  Real-time monitoring:
27
-
27
+
28
28
  >>> # Monitor active cycle
29
29
  >>> metrics = analyzer.get_real_time_metrics(trace)
30
30
  >>> if metrics['health_score'] < 0.5:
@@ -47,12 +47,12 @@ logger = logging.getLogger(__name__)
47
47
  class CycleAnalyzer:
48
48
  """
49
49
  Comprehensive analysis tool combining debugging and profiling capabilities.
50
-
50
+
51
51
  This class provides a unified interface for cycle analysis, combining
52
52
  the detailed tracking capabilities of CycleDebugger with the performance
53
53
  insights of CycleProfiler to provide comprehensive cycle optimization
54
54
  guidance and health monitoring.
55
-
55
+
56
56
  Examples:
57
57
  >>> analyzer = CycleAnalyzer(analysis_level="comprehensive")
58
58
  >>> # Start analysis
@@ -64,17 +64,17 @@ class CycleAnalyzer:
64
64
  >>> analyzer.complete_cycle_analysis(trace, converged=True)
65
65
  >>> report = analyzer.generate_comprehensive_report(session)
66
66
  """
67
-
67
+
68
68
  def __init__(
69
69
  self,
70
70
  analysis_level: str = "standard",
71
71
  enable_profiling: bool = True,
72
72
  enable_debugging: bool = True,
73
- output_directory: Optional[str] = None
73
+ output_directory: Optional[str] = None,
74
74
  ):
75
75
  """
76
76
  Initialize cycle analyzer.
77
-
77
+
78
78
  Args:
79
79
  analysis_level: Level of analysis ("basic", "standard", "comprehensive").
80
80
  enable_profiling: Whether to enable performance profiling.
@@ -85,197 +85,204 @@ class CycleAnalyzer:
85
85
  self.enable_profiling = enable_profiling
86
86
  self.enable_debugging = enable_debugging
87
87
  self.output_directory = Path(output_directory) if output_directory else None
88
-
88
+
89
89
  # Initialize components based on configuration
90
90
  debug_level = {
91
91
  "basic": "basic",
92
- "standard": "detailed",
93
- "comprehensive": "verbose"
92
+ "standard": "detailed",
93
+ "comprehensive": "verbose",
94
94
  }.get(analysis_level, "detailed")
95
-
96
- self.debugger = CycleDebugger(
97
- debug_level=debug_level,
98
- enable_profiling=enable_profiling
99
- ) if enable_debugging else None
100
-
101
- self.profiler = CycleProfiler(
102
- enable_advanced_metrics=(analysis_level == "comprehensive")
103
- ) if enable_profiling else None
104
-
95
+
96
+ self.debugger = (
97
+ CycleDebugger(debug_level=debug_level, enable_profiling=enable_profiling)
98
+ if enable_debugging
99
+ else None
100
+ )
101
+
102
+ self.profiler = (
103
+ CycleProfiler(enable_advanced_metrics=(analysis_level == "comprehensive"))
104
+ if enable_profiling
105
+ else None
106
+ )
107
+
105
108
  # Analysis session tracking
106
109
  self.current_session: Optional[str] = None
107
110
  self.session_traces: List[CycleExecutionTrace] = []
108
111
  self.analysis_history: List[Dict[str, Any]] = []
109
-
112
+
110
113
  # Create output directory if specified
111
114
  if self.output_directory:
112
115
  self.output_directory.mkdir(parents=True, exist_ok=True)
113
116
  logger.info(f"Analysis output directory: {self.output_directory}")
114
-
117
+
115
118
  def start_analysis_session(self, session_id: str) -> str:
116
119
  """
117
120
  Start a new analysis session for grouping related cycles.
118
-
121
+
119
122
  Analysis sessions allow grouping multiple cycle executions for
120
123
  comparative analysis, trend identification, and comprehensive
121
124
  reporting across related workflow executions.
122
-
125
+
123
126
  Args:
124
127
  session_id: Unique identifier for the analysis session.
125
-
128
+
126
129
  Returns:
127
130
  Session ID for reference.
128
-
131
+
129
132
  Examples:
130
133
  >>> session = analyzer.start_analysis_session("optimization_experiment_1")
131
134
  """
132
135
  self.current_session = session_id
133
136
  self.session_traces = []
134
-
137
+
135
138
  logger.info(f"Started analysis session: {session_id}")
136
139
  return session_id
137
-
140
+
138
141
  def start_cycle_analysis(
139
142
  self,
140
143
  cycle_id: str,
141
144
  workflow_id: str,
142
145
  max_iterations: Optional[int] = None,
143
146
  timeout: Optional[float] = None,
144
- convergence_condition: Optional[str] = None
147
+ convergence_condition: Optional[str] = None,
145
148
  ) -> Optional[CycleExecutionTrace]:
146
149
  """
147
150
  Start analysis for a new cycle execution.
148
-
151
+
149
152
  Begins comprehensive tracking for a cycle execution, including
150
153
  debugging and profiling as configured. Returns a trace object
151
154
  for tracking iteration progress.
152
-
155
+
153
156
  Args:
154
157
  cycle_id: Unique identifier for the cycle.
155
158
  workflow_id: Parent workflow identifier.
156
159
  max_iterations: Configured iteration limit.
157
160
  timeout: Configured timeout limit.
158
161
  convergence_condition: Convergence condition.
159
-
162
+
160
163
  Returns:
161
164
  Trace object for tracking, or None if debugging disabled.
162
-
165
+
163
166
  Examples:
164
167
  >>> trace = analyzer.start_cycle_analysis("opt_cycle", "workflow_1", max_iterations=100)
165
168
  """
166
169
  if not self.debugger:
167
170
  logger.warning("Debugging not enabled - cannot create trace")
168
171
  return None
169
-
172
+
170
173
  trace = self.debugger.start_cycle(
171
174
  cycle_id=cycle_id,
172
175
  workflow_id=workflow_id,
173
176
  max_iterations=max_iterations,
174
177
  timeout=timeout,
175
- convergence_condition=convergence_condition
178
+ convergence_condition=convergence_condition,
179
+ )
180
+
181
+ logger.info(
182
+ f"Started cycle analysis for '{cycle_id}' in session '{self.current_session}'"
176
183
  )
177
-
178
- logger.info(f"Started cycle analysis for '{cycle_id}' in session '{self.current_session}'")
179
184
  return trace
180
-
185
+
181
186
  def track_iteration(
182
187
  self,
183
188
  trace: CycleExecutionTrace,
184
189
  input_data: Dict[str, Any],
185
190
  output_data: Dict[str, Any],
186
191
  convergence_value: Optional[float] = None,
187
- node_executions: Optional[List[str]] = None
192
+ node_executions: Optional[List[str]] = None,
188
193
  ):
189
194
  """
190
195
  Track a single cycle iteration with input/output data.
191
-
196
+
192
197
  Records detailed information about a cycle iteration including
193
198
  timing, resource usage, convergence metrics, and execution flow
194
199
  for comprehensive analysis.
195
-
200
+
196
201
  Args:
197
202
  trace: Active trace object.
198
203
  input_data: Input data for the iteration.
199
204
  output_data: Output data from the iteration.
200
205
  convergence_value: Convergence metric if available.
201
206
  node_executions: List of executed nodes.
202
-
207
+
203
208
  Examples:
204
209
  >>> analyzer.track_iteration(trace, input_data, output_data, convergence_value=0.05)
205
210
  """
206
211
  if not self.debugger:
207
212
  return
208
-
213
+
209
214
  iteration = self.debugger.start_iteration(trace, input_data)
210
215
  self.debugger.end_iteration(
211
216
  trace, iteration, output_data, convergence_value, node_executions
212
217
  )
213
-
218
+
214
219
  if self.analysis_level == "comprehensive":
215
220
  logger.debug(
216
221
  f"Tracked iteration {iteration.iteration_number} for cycle '{trace.cycle_id}' "
217
222
  f"with convergence={convergence_value}"
218
223
  )
219
-
224
+
220
225
  def complete_cycle_analysis(
221
226
  self,
222
227
  trace: CycleExecutionTrace,
223
228
  converged: bool,
224
229
  termination_reason: str,
225
- convergence_iteration: Optional[int] = None
230
+ convergence_iteration: Optional[int] = None,
226
231
  ):
227
232
  """
228
233
  Complete cycle analysis and generate insights.
229
-
234
+
230
235
  Finalizes cycle tracking and performs comprehensive analysis
231
236
  including performance metrics, optimization recommendations,
232
237
  and comparative insights if multiple cycles are available.
233
-
238
+
234
239
  Args:
235
240
  trace: Cycle trace to complete.
236
241
  converged: Whether the cycle converged successfully.
237
242
  termination_reason: Why the cycle terminated.
238
243
  convergence_iteration: Iteration where convergence occurred.
239
-
244
+
240
245
  Examples:
241
246
  >>> analyzer.complete_cycle_analysis(trace, converged=True, termination_reason="convergence")
242
247
  """
243
248
  if not self.debugger:
244
249
  return
245
-
250
+
246
251
  # Complete debugging
247
- self.debugger.end_cycle(trace, converged, termination_reason, convergence_iteration)
248
-
252
+ self.debugger.end_cycle(
253
+ trace, converged, termination_reason, convergence_iteration
254
+ )
255
+
249
256
  # Add to profiler for performance analysis
250
257
  if self.profiler:
251
258
  self.profiler.add_trace(trace)
252
-
259
+
253
260
  # Add to session traces
254
261
  self.session_traces.append(trace)
255
-
262
+
256
263
  logger.info(
257
264
  f"Completed cycle analysis for '{trace.cycle_id}' - "
258
265
  f"converged={converged}, iterations={len(trace.iterations)}"
259
266
  )
260
-
267
+
261
268
  # Generate immediate insights for comprehensive analysis
262
269
  if self.analysis_level == "comprehensive":
263
270
  self._generate_immediate_insights(trace)
264
-
271
+
265
272
  def generate_cycle_report(self, trace: CycleExecutionTrace) -> Dict[str, Any]:
266
273
  """
267
274
  Generate comprehensive report for a single cycle.
268
-
275
+
269
276
  Creates a detailed analysis report for a specific cycle execution
270
277
  including debugging information, performance metrics, and
271
278
  optimization recommendations.
272
-
279
+
273
280
  Args:
274
281
  trace: Completed cycle trace.
275
-
282
+
276
283
  Returns:
277
284
  Comprehensive cycle analysis report.
278
-
285
+
279
286
  Examples:
280
287
  >>> report = analyzer.generate_cycle_report(trace)
281
288
  >>> print(f"Cycle efficiency: {report['performance']['efficiency_score']}")
@@ -286,119 +293,137 @@ class CycleAnalyzer:
286
293
  "workflow_id": trace.workflow_id,
287
294
  "analysis_level": self.analysis_level,
288
295
  "session_id": self.current_session,
289
- "generated_at": datetime.now().isoformat()
296
+ "generated_at": datetime.now().isoformat(),
290
297
  }
291
298
  }
292
-
299
+
293
300
  # Add debugging information
294
301
  if self.debugger:
295
302
  debug_report = self.debugger.generate_report(trace)
296
303
  report["debugging"] = debug_report
297
-
304
+
298
305
  # Add profiling information
299
306
  if self.profiler:
300
307
  # Create temporary profiler for single trace analysis
301
- single_profiler = CycleProfiler(enable_advanced_metrics=(self.analysis_level == "comprehensive"))
308
+ single_profiler = CycleProfiler(
309
+ enable_advanced_metrics=(self.analysis_level == "comprehensive")
310
+ )
302
311
  single_profiler.add_trace(trace)
303
-
312
+
304
313
  performance_metrics = single_profiler.analyze_performance()
305
314
  recommendations = single_profiler.get_optimization_recommendations(trace)
306
-
315
+
307
316
  report["performance"] = performance_metrics.to_dict()
308
317
  report["recommendations"] = recommendations
309
-
318
+
310
319
  # Add analysis-level specific insights
311
320
  if self.analysis_level == "comprehensive":
312
321
  report["advanced_analysis"] = self._generate_advanced_analysis(trace)
313
-
322
+
314
323
  # Export to file if configured
315
324
  if self.output_directory:
316
325
  self._export_cycle_report(report, trace.cycle_id)
317
-
326
+
318
327
  return report
319
-
320
- def generate_session_report(self, session_id: Optional[str] = None) -> Dict[str, Any]:
328
+
329
+ def generate_session_report(
330
+ self, session_id: Optional[str] = None
331
+ ) -> Dict[str, Any]:
321
332
  """
322
333
  Generate comprehensive report for an analysis session.
323
-
334
+
324
335
  Creates a detailed analysis report covering all cycles in a session,
325
336
  including comparative analysis, trend identification, and overall
326
337
  optimization recommendations.
327
-
338
+
328
339
  Args:
329
340
  session_id: Session to analyze, or current session if None.
330
-
341
+
331
342
  Returns:
332
343
  Comprehensive session analysis report.
333
-
344
+
334
345
  Examples:
335
346
  >>> report = analyzer.generate_session_report()
336
347
  >>> print(f"Best cycle: {report['comparative_analysis']['best_cycle']}")
337
348
  """
338
349
  target_session = session_id or self.current_session
339
350
  traces_to_analyze = self.session_traces if session_id is None else []
340
-
351
+
341
352
  report = {
342
353
  "session_info": {
343
354
  "session_id": target_session,
344
355
  "analysis_level": self.analysis_level,
345
356
  "cycles_analyzed": len(traces_to_analyze),
346
- "generated_at": datetime.now().isoformat()
357
+ "generated_at": datetime.now().isoformat(),
347
358
  },
348
359
  "summary": {
349
360
  "total_cycles": len(traces_to_analyze),
350
- "total_iterations": sum(len(trace.iterations) for trace in traces_to_analyze),
351
- "convergence_rate": len([t for t in traces_to_analyze if t.converged]) / len(traces_to_analyze) if traces_to_analyze else 0,
352
- "avg_cycle_time": sum(t.total_execution_time or 0 for t in traces_to_analyze) / len(traces_to_analyze) if traces_to_analyze else 0
353
- }
361
+ "total_iterations": sum(
362
+ len(trace.iterations) for trace in traces_to_analyze
363
+ ),
364
+ "convergence_rate": (
365
+ len([t for t in traces_to_analyze if t.converged])
366
+ / len(traces_to_analyze)
367
+ if traces_to_analyze
368
+ else 0
369
+ ),
370
+ "avg_cycle_time": (
371
+ sum(t.total_execution_time or 0 for t in traces_to_analyze)
372
+ / len(traces_to_analyze)
373
+ if traces_to_analyze
374
+ else 0
375
+ ),
376
+ },
354
377
  }
355
-
378
+
356
379
  if not traces_to_analyze:
357
380
  report["warning"] = "No traces available for analysis"
358
381
  return report
359
-
382
+
360
383
  # Add profiling analysis
361
384
  if self.profiler and traces_to_analyze:
362
385
  # Ensure all traces are in profiler
363
386
  for trace in traces_to_analyze:
364
387
  if trace not in self.profiler.traces:
365
388
  self.profiler.add_trace(trace)
366
-
389
+
367
390
  performance_report = self.profiler.generate_performance_report()
368
391
  report["performance_analysis"] = performance_report
369
-
392
+
370
393
  # Add comparative analysis
371
394
  if len(traces_to_analyze) >= 2:
372
395
  cycle_ids = [trace.cycle_id for trace in traces_to_analyze]
373
- comparison = self.profiler.compare_cycles(cycle_ids) if self.profiler else {}
396
+ comparison = (
397
+ self.profiler.compare_cycles(cycle_ids) if self.profiler else {}
398
+ )
374
399
  report["comparative_analysis"] = comparison
375
-
400
+
376
401
  # Add session-specific insights
377
402
  report["insights"] = self._generate_session_insights(traces_to_analyze)
378
-
403
+
379
404
  # Export to file if configured
380
405
  if self.output_directory:
381
406
  self._export_session_report(report, target_session)
382
-
407
+
383
408
  return report
384
-
409
+
385
410
  def get_real_time_metrics(self, trace: CycleExecutionTrace) -> Dict[str, Any]:
386
411
  """
387
412
  Get real-time metrics for an active cycle.
388
-
413
+
389
414
  Provides current performance metrics and health indicators
390
415
  for a cycle that is currently executing, enabling real-time
391
416
  monitoring and early intervention if issues are detected.
392
-
417
+
393
418
  Args:
394
419
  trace: Active cycle trace.
395
-
420
+
396
421
  Returns:
397
422
  Dict[str, Any]: Real-time metrics and health indicators
398
-
423
+
399
424
  Side Effects:
400
425
  None - this is a pure analysis method
401
-
426
+
402
427
  Example:
403
428
  >>> metrics = analyzer.get_real_time_metrics(trace)
404
429
  >>> if metrics['health_score'] < 0.5:
@@ -406,23 +431,31 @@ class CycleAnalyzer:
406
431
  """
407
432
  if not trace.iterations:
408
433
  return {"status": "no_iterations", "health_score": 0.5}
409
-
434
+
410
435
  recent_iterations = trace.iterations[-5:] # Last 5 iterations
411
-
436
+
412
437
  # Calculate real-time performance indicators
413
- avg_recent_time = sum(iter.execution_time or 0 for iter in recent_iterations) / len(recent_iterations)
414
-
438
+ avg_recent_time = sum(
439
+ iter.execution_time or 0 for iter in recent_iterations
440
+ ) / len(recent_iterations)
441
+
415
442
  # Memory trend (if available)
416
- memory_values = [iter.memory_usage_mb for iter in recent_iterations if iter.memory_usage_mb]
443
+ memory_values = [
444
+ iter.memory_usage_mb for iter in recent_iterations if iter.memory_usage_mb
445
+ ]
417
446
  memory_trend = "stable"
418
447
  if len(memory_values) >= 2:
419
448
  if memory_values[-1] > memory_values[0] * 1.2:
420
449
  memory_trend = "increasing"
421
450
  elif memory_values[-1] < memory_values[0] * 0.8:
422
451
  memory_trend = "decreasing"
423
-
452
+
424
453
  # Convergence trend
425
- convergence_values = [iter.convergence_value for iter in recent_iterations if iter.convergence_value]
454
+ convergence_values = [
455
+ iter.convergence_value
456
+ for iter in recent_iterations
457
+ if iter.convergence_value
458
+ ]
426
459
  convergence_trend = "unknown"
427
460
  if len(convergence_values) >= 2:
428
461
  if convergence_values[-1] < convergence_values[0]:
@@ -431,10 +464,10 @@ class CycleAnalyzer:
431
464
  convergence_trend = "degrading"
432
465
  else:
433
466
  convergence_trend = "stable"
434
-
467
+
435
468
  # Health score calculation
436
469
  health_score = self._calculate_real_time_health_score(trace, recent_iterations)
437
-
470
+
438
471
  return {
439
472
  "status": "active",
440
473
  "current_iteration": len(trace.iterations),
@@ -442,29 +475,29 @@ class CycleAnalyzer:
442
475
  "memory_trend": memory_trend,
443
476
  "convergence_trend": convergence_trend,
444
477
  "health_score": health_score,
445
- "alerts": self._generate_real_time_alerts(trace, recent_iterations)
478
+ "alerts": self._generate_real_time_alerts(trace, recent_iterations),
446
479
  }
447
-
480
+
448
481
  def export_analysis_data(
449
482
  self,
450
483
  filepath: Optional[str] = None,
451
484
  format: str = "json",
452
- include_traces: bool = True
485
+ include_traces: bool = True,
453
486
  ):
454
487
  """
455
488
  Export comprehensive analysis data.
456
-
489
+
457
490
  Exports all analysis data including traces, performance metrics,
458
491
  and reports for external analysis, archival, or sharing.
459
-
492
+
460
493
  Args:
461
494
  filepath (Optional[str]): Output file path, auto-generated if None
462
495
  format (str): Export format ("json", "csv")
463
496
  include_traces (bool): Whether to include detailed trace data
464
-
497
+
465
498
  Side Effects:
466
499
  Creates export file with analysis data
467
-
500
+
468
501
  Example:
469
502
  >>> analyzer.export_analysis_data("cycle_analysis.json", include_traces=True)
470
503
  """
@@ -473,296 +506,384 @@ class CycleAnalyzer:
473
506
  filepath = f"cycle_analysis_{self.current_session or 'session'}_{timestamp}.{format}"
474
507
  if self.output_directory:
475
508
  filepath = str(self.output_directory / filepath)
476
-
509
+
477
510
  export_data = {
478
511
  "analysis_metadata": {
479
512
  "session_id": self.current_session,
480
513
  "analysis_level": self.analysis_level,
481
514
  "export_timestamp": datetime.now().isoformat(),
482
- "cycles_count": len(self.session_traces)
515
+ "cycles_count": len(self.session_traces),
483
516
  }
484
517
  }
485
-
518
+
486
519
  # Include session report
487
520
  if self.session_traces:
488
521
  export_data["session_report"] = self.generate_session_report()
489
-
522
+
490
523
  # Include individual cycle reports
491
524
  if include_traces:
492
525
  export_data["cycle_reports"] = [
493
526
  self.generate_cycle_report(trace) for trace in self.session_traces
494
527
  ]
495
-
528
+
496
529
  # Include performance history if available
497
530
  if self.profiler:
498
531
  export_data["performance_history"] = [
499
532
  metrics.to_dict() for metrics in self.profiler.performance_history
500
533
  ]
501
-
534
+
502
535
  # Export to file
503
536
  if format == "json":
504
- with open(filepath, 'w') as f:
537
+ with open(filepath, "w") as f:
505
538
  json.dump(export_data, f, indent=2)
506
539
  elif format == "csv":
507
540
  # For CSV, export summary data only
508
541
  import csv
509
- with open(filepath, 'w', newline='') as f:
542
+
543
+ with open(filepath, "w", newline="") as f:
510
544
  writer = csv.writer(f)
511
-
545
+
512
546
  # Write header
513
- writer.writerow(["cycle_id", "workflow_id", "iterations", "execution_time", "converged", "efficiency_score"])
514
-
547
+ writer.writerow(
548
+ [
549
+ "cycle_id",
550
+ "workflow_id",
551
+ "iterations",
552
+ "execution_time",
553
+ "converged",
554
+ "efficiency_score",
555
+ ]
556
+ )
557
+
515
558
  # Write cycle data
516
559
  for trace in self.session_traces:
517
560
  stats = trace.get_statistics()
518
- writer.writerow([
519
- trace.cycle_id,
520
- trace.workflow_id,
521
- len(trace.iterations),
522
- trace.total_execution_time,
523
- trace.converged,
524
- stats.get("efficiency_score", 0.0)
525
- ])
561
+ writer.writerow(
562
+ [
563
+ trace.cycle_id,
564
+ trace.workflow_id,
565
+ len(trace.iterations),
566
+ trace.total_execution_time,
567
+ trace.converged,
568
+ stats.get("efficiency_score", 0.0),
569
+ ]
570
+ )
526
571
  else:
527
572
  raise ValueError(f"Unsupported export format: {format}")
528
-
573
+
529
574
  logger.info(f"Exported analysis data to {filepath} in {format} format")
530
-
575
+
531
576
  def _generate_immediate_insights(self, trace: CycleExecutionTrace):
532
577
  """Generate immediate insights for a completed cycle."""
533
578
  stats = trace.get_statistics()
534
-
579
+
535
580
  # Log key insights
536
581
  if stats["efficiency_score"] > 0.8:
537
- logger.info(f"Excellent performance for cycle '{trace.cycle_id}' - efficiency: {stats['efficiency_score']:.2f}")
582
+ logger.info(
583
+ f"Excellent performance for cycle '{trace.cycle_id}' - efficiency: {stats['efficiency_score']:.2f}"
584
+ )
538
585
  elif stats["efficiency_score"] < 0.3:
539
- logger.warning(f"Poor performance for cycle '{trace.cycle_id}' - efficiency: {stats['efficiency_score']:.2f}")
540
-
586
+ logger.warning(
587
+ f"Poor performance for cycle '{trace.cycle_id}' - efficiency: {stats['efficiency_score']:.2f}"
588
+ )
589
+
541
590
  if not trace.converged:
542
- logger.warning(f"Cycle '{trace.cycle_id}' failed to converge - reason: {trace.termination_reason}")
543
-
591
+ logger.warning(
592
+ f"Cycle '{trace.cycle_id}' failed to converge - reason: {trace.termination_reason}"
593
+ )
594
+
544
595
  # Check for performance issues
545
596
  if stats["avg_iteration_time"] > 1.0:
546
- logger.warning(f"Slow iterations detected for cycle '{trace.cycle_id}' - avg: {stats['avg_iteration_time']:.3f}s")
547
-
597
+ logger.warning(
598
+ f"Slow iterations detected for cycle '{trace.cycle_id}' - avg: {stats['avg_iteration_time']:.3f}s"
599
+ )
600
+
548
601
  def _generate_advanced_analysis(self, trace: CycleExecutionTrace) -> Dict[str, Any]:
549
602
  """Generate advanced analysis insights for comprehensive mode."""
550
603
  convergence_trend = trace.get_convergence_trend()
551
-
604
+
552
605
  # Convergence pattern analysis
553
606
  convergence_analysis = {}
554
607
  if convergence_trend:
555
608
  values = [value for _, value in convergence_trend if value is not None]
556
609
  if len(values) >= 3:
557
610
  # Calculate convergence velocity
558
- velocity = (values[0] - values[-1]) / len(values) if len(values) > 1 else 0
611
+ velocity = (
612
+ (values[0] - values[-1]) / len(values) if len(values) > 1 else 0
613
+ )
559
614
  convergence_analysis = {
560
615
  "convergence_velocity": velocity,
561
- "convergence_pattern": "fast" if velocity > 0.1 else "slow" if velocity > 0.01 else "minimal",
562
- "stability_score": self._calculate_convergence_stability(values)
616
+ "convergence_pattern": (
617
+ "fast"
618
+ if velocity > 0.1
619
+ else "slow" if velocity > 0.01 else "minimal"
620
+ ),
621
+ "stability_score": self._calculate_convergence_stability(values),
563
622
  }
564
-
623
+
565
624
  # Iteration pattern analysis
566
- iteration_times = [iter.execution_time for iter in trace.iterations if iter.execution_time]
625
+ iteration_times = [
626
+ iter.execution_time for iter in trace.iterations if iter.execution_time
627
+ ]
567
628
  iteration_analysis = {}
568
629
  if iteration_times:
569
630
  import statistics
631
+
570
632
  iteration_analysis = {
571
633
  "time_distribution": {
572
634
  "mean": statistics.mean(iteration_times),
573
635
  "median": statistics.median(iteration_times),
574
- "mode": statistics.mode(iteration_times) if len(set(iteration_times)) != len(iteration_times) else None,
575
- "skewness": self._calculate_skewness(iteration_times)
636
+ "mode": (
637
+ statistics.mode(iteration_times)
638
+ if len(set(iteration_times)) != len(iteration_times)
639
+ else None
640
+ ),
641
+ "skewness": self._calculate_skewness(iteration_times),
576
642
  },
577
- "performance_trend": self._analyze_performance_trend(iteration_times)
643
+ "performance_trend": self._analyze_performance_trend(iteration_times),
578
644
  }
579
-
645
+
580
646
  return {
581
647
  "convergence_analysis": convergence_analysis,
582
648
  "iteration_analysis": iteration_analysis,
583
- "resource_efficiency": self._analyze_resource_efficiency(trace)
649
+ "resource_efficiency": self._analyze_resource_efficiency(trace),
584
650
  }
585
-
586
- def _generate_session_insights(self, traces: List[CycleExecutionTrace]) -> Dict[str, Any]:
651
+
652
+ def _generate_session_insights(
653
+ self, traces: List[CycleExecutionTrace]
654
+ ) -> Dict[str, Any]:
587
655
  """Generate insights across multiple cycles in a session."""
588
656
  if not traces:
589
657
  return {}
590
-
658
+
591
659
  # Find best and worst performing cycles
592
- cycle_scores = {trace.cycle_id: trace.get_statistics()["efficiency_score"] for trace in traces}
660
+ cycle_scores = {
661
+ trace.cycle_id: trace.get_statistics()["efficiency_score"]
662
+ for trace in traces
663
+ }
593
664
  best_cycle = max(cycle_scores.items(), key=lambda x: x[1])
594
665
  worst_cycle = min(cycle_scores.items(), key=lambda x: x[1])
595
-
666
+
596
667
  # Identify patterns
597
668
  convergence_rate = len([t for t in traces if t.converged]) / len(traces)
598
669
  avg_iterations = sum(len(t.iterations) for t in traces) / len(traces)
599
-
670
+
600
671
  insights = {
601
672
  "best_performing_cycle": {"id": best_cycle[0], "score": best_cycle[1]},
602
673
  "worst_performing_cycle": {"id": worst_cycle[0], "score": worst_cycle[1]},
603
674
  "overall_convergence_rate": convergence_rate,
604
675
  "avg_iterations_per_cycle": avg_iterations,
605
- "performance_consistency": best_cycle[1] - worst_cycle[1], # Lower is more consistent
606
- "session_quality": "excellent" if convergence_rate > 0.9 and cycle_scores[best_cycle[0]] > 0.8 else
607
- "good" if convergence_rate > 0.7 else
608
- "needs_improvement"
676
+ "performance_consistency": best_cycle[1]
677
+ - worst_cycle[1], # Lower is more consistent
678
+ "session_quality": (
679
+ "excellent"
680
+ if convergence_rate > 0.9 and cycle_scores[best_cycle[0]] > 0.8
681
+ else "good" if convergence_rate > 0.7 else "needs_improvement"
682
+ ),
609
683
  }
610
-
684
+
611
685
  return insights
612
-
613
- def _calculate_real_time_health_score(self, trace: CycleExecutionTrace, recent_iterations: List) -> float:
686
+
687
+ def _calculate_real_time_health_score(
688
+ self, trace: CycleExecutionTrace, recent_iterations: List
689
+ ) -> float:
614
690
  """Calculate real-time health score for an active cycle."""
615
691
  score_components = []
616
-
692
+
617
693
  # Performance component
618
694
  if recent_iterations:
619
- avg_time = sum(iter.execution_time or 0 for iter in recent_iterations) / len(recent_iterations)
620
- time_score = max(0.0, 1.0 - min(1.0, avg_time / 2.0)) # Penalty after 2s per iteration
695
+ avg_time = sum(
696
+ iter.execution_time or 0 for iter in recent_iterations
697
+ ) / len(recent_iterations)
698
+ time_score = max(
699
+ 0.0, 1.0 - min(1.0, avg_time / 2.0)
700
+ ) # Penalty after 2s per iteration
621
701
  score_components.append(time_score)
622
-
702
+
623
703
  # Error rate component
624
704
  error_count = len([iter for iter in recent_iterations if iter.error])
625
- error_score = max(0.0, 1.0 - (error_count / len(recent_iterations))) if recent_iterations else 1.0
705
+ error_score = (
706
+ max(0.0, 1.0 - (error_count / len(recent_iterations)))
707
+ if recent_iterations
708
+ else 1.0
709
+ )
626
710
  score_components.append(error_score)
627
-
711
+
628
712
  # Memory trend component (if available)
629
- memory_values = [iter.memory_usage_mb for iter in recent_iterations if iter.memory_usage_mb]
713
+ memory_values = [
714
+ iter.memory_usage_mb for iter in recent_iterations if iter.memory_usage_mb
715
+ ]
630
716
  if memory_values and len(memory_values) >= 2:
631
717
  memory_growth = (memory_values[-1] - memory_values[0]) / memory_values[0]
632
- memory_score = max(0.0, 1.0 - max(0.0, memory_growth)) # Penalty for memory growth
718
+ memory_score = max(
719
+ 0.0, 1.0 - max(0.0, memory_growth)
720
+ ) # Penalty for memory growth
633
721
  score_components.append(memory_score)
634
-
635
- return sum(score_components) / len(score_components) if score_components else 0.5
636
-
637
- def _generate_real_time_alerts(self, trace: CycleExecutionTrace, recent_iterations: List) -> List[str]:
722
+
723
+ return (
724
+ sum(score_components) / len(score_components) if score_components else 0.5
725
+ )
726
+
727
+ def _generate_real_time_alerts(
728
+ self, trace: CycleExecutionTrace, recent_iterations: List
729
+ ) -> List[str]:
638
730
  """Generate real-time alerts for potential issues."""
639
731
  alerts = []
640
-
732
+
641
733
  # Check for slow iterations
642
734
  if recent_iterations:
643
- avg_time = sum(iter.execution_time or 0 for iter in recent_iterations) / len(recent_iterations)
735
+ avg_time = sum(
736
+ iter.execution_time or 0 for iter in recent_iterations
737
+ ) / len(recent_iterations)
644
738
  if avg_time > 2.0:
645
739
  alerts.append(f"Slow iterations detected: {avg_time:.2f}s average")
646
-
740
+
647
741
  # Check for errors
648
742
  error_count = len([iter for iter in recent_iterations if iter.error])
649
743
  if error_count > 0:
650
- alerts.append(f"Errors detected in {error_count}/{len(recent_iterations)} recent iterations")
651
-
744
+ alerts.append(
745
+ f"Errors detected in {error_count}/{len(recent_iterations)} recent iterations"
746
+ )
747
+
652
748
  # Check for memory growth
653
- memory_values = [iter.memory_usage_mb for iter in recent_iterations if iter.memory_usage_mb]
749
+ memory_values = [
750
+ iter.memory_usage_mb for iter in recent_iterations if iter.memory_usage_mb
751
+ ]
654
752
  if len(memory_values) >= 2:
655
753
  memory_growth = (memory_values[-1] - memory_values[0]) / memory_values[0]
656
754
  if memory_growth > 0.2:
657
- alerts.append(f"Memory usage increasing: {memory_growth*100:.1f}% growth")
658
-
755
+ alerts.append(
756
+ f"Memory usage increasing: {memory_growth*100:.1f}% growth"
757
+ )
758
+
659
759
  # Check for potential non-convergence
660
760
  if len(trace.iterations) > (trace.max_iterations_configured or 100) * 0.8:
661
- alerts.append(f"Approaching max iterations: {len(trace.iterations)}/{trace.max_iterations_configured}")
662
-
761
+ alerts.append(
762
+ f"Approaching max iterations: {len(trace.iterations)}/{trace.max_iterations_configured}"
763
+ )
764
+
663
765
  return alerts
664
-
766
+
665
767
  def _calculate_convergence_stability(self, values: List[float]) -> float:
666
768
  """Calculate stability score for convergence values."""
667
769
  if len(values) < 2:
668
770
  return 1.0
669
-
771
+
670
772
  import statistics
773
+
671
774
  mean_val = statistics.mean(values)
672
775
  if mean_val == 0:
673
776
  return 1.0
674
-
777
+
675
778
  stddev = statistics.stdev(values)
676
779
  cv = stddev / mean_val # Coefficient of variation
677
-
780
+
678
781
  # Lower CV means more stable
679
782
  return max(0.0, 1.0 - min(1.0, cv))
680
-
783
+
681
784
  def _calculate_skewness(self, data: List[float]) -> float:
682
785
  """Calculate skewness of data distribution."""
683
786
  if len(data) < 3:
684
787
  return 0.0
685
-
788
+
686
789
  import statistics
790
+
687
791
  mean_val = statistics.mean(data)
688
792
  n = len(data)
689
793
  variance = sum((x - mean_val) ** 2 for x in data) / n
690
794
  if variance == 0:
691
795
  return 0.0
692
-
693
- std_dev = variance ** 0.5
694
- skewness = sum((x - mean_val) ** 3 for x in data) / (n * std_dev ** 3)
796
+
797
+ std_dev = variance**0.5
798
+ skewness = sum((x - mean_val) ** 3 for x in data) / (n * std_dev**3)
695
799
  return skewness
696
-
800
+
697
801
  def _analyze_performance_trend(self, iteration_times: List[float]) -> str:
698
802
  """Analyze performance trend over iterations."""
699
803
  if len(iteration_times) < 3:
700
804
  return "insufficient_data"
701
-
805
+
702
806
  # Simple trend analysis
703
- first_half = iteration_times[:len(iteration_times)//2]
704
- second_half = iteration_times[len(iteration_times)//2:]
705
-
807
+ first_half = iteration_times[: len(iteration_times) // 2]
808
+ second_half = iteration_times[len(iteration_times) // 2 :]
809
+
706
810
  import statistics
811
+
707
812
  first_avg = statistics.mean(first_half)
708
813
  second_avg = statistics.mean(second_half)
709
-
814
+
710
815
  improvement = (first_avg - second_avg) / first_avg
711
-
816
+
712
817
  if improvement > 0.1:
713
818
  return "improving"
714
819
  elif improvement < -0.1:
715
820
  return "degrading"
716
821
  else:
717
822
  return "stable"
718
-
719
- def _analyze_resource_efficiency(self, trace: CycleExecutionTrace) -> Dict[str, Any]:
823
+
824
+ def _analyze_resource_efficiency(
825
+ self, trace: CycleExecutionTrace
826
+ ) -> Dict[str, Any]:
720
827
  """Analyze resource usage efficiency."""
721
- memory_values = [iter.memory_usage_mb for iter in trace.iterations if iter.memory_usage_mb]
722
- cpu_values = [iter.cpu_usage_percent for iter in trace.iterations if iter.cpu_usage_percent]
723
-
828
+ memory_values = [
829
+ iter.memory_usage_mb for iter in trace.iterations if iter.memory_usage_mb
830
+ ]
831
+ cpu_values = [
832
+ iter.cpu_usage_percent
833
+ for iter in trace.iterations
834
+ if iter.cpu_usage_percent
835
+ ]
836
+
724
837
  efficiency = {}
725
-
838
+
726
839
  if memory_values:
727
840
  import statistics
841
+
728
842
  efficiency["memory_efficiency"] = {
729
843
  "peak_usage": max(memory_values),
730
844
  "avg_usage": statistics.mean(memory_values),
731
- "efficiency_score": max(0.0, 1.0 - (max(memory_values) / 2000)) # Penalty after 2GB
845
+ "efficiency_score": max(
846
+ 0.0, 1.0 - (max(memory_values) / 2000)
847
+ ), # Penalty after 2GB
732
848
  }
733
-
849
+
734
850
  if cpu_values:
735
851
  import statistics
852
+
736
853
  efficiency["cpu_efficiency"] = {
737
854
  "peak_usage": max(cpu_values),
738
855
  "avg_usage": statistics.mean(cpu_values),
739
- "efficiency_score": min(1.0, statistics.mean(cpu_values) / 100) # Higher CPU usage is better utilization
856
+ "efficiency_score": min(
857
+ 1.0, statistics.mean(cpu_values) / 100
858
+ ), # Higher CPU usage is better utilization
740
859
  }
741
-
860
+
742
861
  return efficiency
743
-
862
+
744
863
  def _export_cycle_report(self, report: Dict[str, Any], cycle_id: str):
745
864
  """Export cycle report to file."""
746
865
  if not self.output_directory:
747
866
  return
748
-
749
- filename = f"cycle_report_{cycle_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
867
+
868
+ filename = (
869
+ f"cycle_report_{cycle_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
870
+ )
750
871
  filepath = self.output_directory / filename
751
-
752
- with open(filepath, 'w') as f:
872
+
873
+ with open(filepath, "w") as f:
753
874
  json.dump(report, f, indent=2)
754
-
875
+
755
876
  logger.debug(f"Exported cycle report to {filepath}")
756
-
877
+
757
878
  def _export_session_report(self, report: Dict[str, Any], session_id: str):
758
879
  """Export session report to file."""
759
880
  if not self.output_directory:
760
881
  return
761
-
882
+
762
883
  filename = f"session_report_{session_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
763
884
  filepath = self.output_directory / filename
764
-
765
- with open(filepath, 'w') as f:
885
+
886
+ with open(filepath, "w") as f:
766
887
  json.dump(report, f, indent=2)
767
-
768
- logger.debug(f"Exported session report to {filepath}")
888
+
889
+ logger.debug(f"Exported session report to {filepath}")