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 @@ production phases with detailed execution tracking and rich analytics.
8
8
 
9
9
  Examples:
10
10
  Basic debugging setup:
11
-
11
+
12
12
  >>> debugger = CycleDebugger(
13
13
  ... debug_level="detailed",
14
14
  ... enable_profiling=True
@@ -19,9 +19,9 @@ Examples:
19
19
  ... "workflow_1",
20
20
  ... max_iterations=100
21
21
  ... )
22
-
22
+
23
23
  Iteration tracking:
24
-
24
+
25
25
  >>> # Track each iteration
26
26
  >>> iteration = debugger.start_iteration(trace, input_data)
27
27
  >>> # ... cycle execution ...
@@ -30,9 +30,9 @@ Examples:
30
30
  ... convergence_value=0.05,
31
31
  ... node_executions=["processor", "evaluator"]
32
32
  ... )
33
-
33
+
34
34
  Analysis and reporting:
35
-
35
+
36
36
  >>> # Complete cycle
37
37
  >>> debugger.end_cycle(
38
38
  ... trace, converged=True,
@@ -58,11 +58,11 @@ logger = logging.getLogger(__name__)
58
58
  class CycleIteration:
59
59
  """
60
60
  Represents a single iteration within a cycle execution.
61
-
61
+
62
62
  This class captures all relevant information about a cycle iteration,
63
63
  including input/output data, execution time, memory usage, and any
64
64
  errors that occurred during execution.
65
-
65
+
66
66
  Attributes:
67
67
  iteration_number: The iteration count (starting from 1).
68
68
  start_time: When this iteration began execution.
@@ -76,7 +76,7 @@ class CycleIteration:
76
76
  error: Error message if iteration failed.
77
77
  node_executions: List of nodes executed in this iteration.
78
78
  """
79
-
79
+
80
80
  iteration_number: int
81
81
  start_time: datetime
82
82
  end_time: Optional[datetime] = None
@@ -88,11 +88,13 @@ class CycleIteration:
88
88
  convergence_value: Optional[float] = None
89
89
  error: Optional[str] = None
90
90
  node_executions: List[str] = field(default_factory=list)
91
-
92
- def complete(self, output_data: Dict[str, Any], convergence_value: Optional[float] = None):
91
+
92
+ def complete(
93
+ self, output_data: Dict[str, Any], convergence_value: Optional[float] = None
94
+ ):
93
95
  """
94
96
  Mark iteration as complete with output data.
95
-
97
+
96
98
  Args:
97
99
  output_data: The output data from this iteration.
98
100
  convergence_value: Convergence metric if available.
@@ -101,37 +103,37 @@ class CycleIteration:
101
103
  self.execution_time = (self.end_time - self.start_time).total_seconds()
102
104
  self.output_data = output_data
103
105
  self.convergence_value = convergence_value
104
-
106
+
105
107
  def fail(self, error: str):
106
108
  """
107
109
  Mark iteration as failed with error message.
108
-
110
+
109
111
  Args:
110
112
  error: Error message describing the failure.
111
113
  """
112
114
  self.end_time = datetime.now()
113
115
  self.execution_time = (self.end_time - self.start_time).total_seconds()
114
116
  self.error = error
115
-
117
+
116
118
  def is_completed(self) -> bool:
117
119
  """Check if iteration has completed (successfully or with error).
118
-
120
+
119
121
  Returns:
120
122
  True if iteration has completed.
121
123
  """
122
124
  return self.end_time is not None
123
-
125
+
124
126
  def is_successful(self) -> bool:
125
127
  """Check if iteration completed successfully.
126
-
128
+
127
129
  Returns:
128
130
  True if iteration completed without error.
129
131
  """
130
132
  return self.end_time is not None and self.error is None
131
-
133
+
132
134
  def to_dict(self) -> Dict[str, Any]:
133
135
  """Convert iteration to dictionary for serialization.
134
-
136
+
135
137
  Returns:
136
138
  Dictionary representation of the iteration.
137
139
  """
@@ -146,19 +148,19 @@ class CycleIteration:
146
148
  "error": self.error,
147
149
  "node_executions": self.node_executions,
148
150
  "input_size": len(str(self.input_data)) if self.input_data else 0,
149
- "output_size": len(str(self.output_data)) if self.output_data else 0
151
+ "output_size": len(str(self.output_data)) if self.output_data else 0,
150
152
  }
151
153
 
152
154
 
153
- @dataclass
155
+ @dataclass
154
156
  class CycleExecutionTrace:
155
157
  """
156
158
  Complete execution trace for a cycle, containing all iterations and metadata.
157
-
159
+
158
160
  This class provides a comprehensive record of cycle execution, including
159
161
  all iterations, overall statistics, convergence analysis, and performance
160
162
  metrics. It serves as the primary data structure for cycle debugging.
161
-
163
+
162
164
  Attributes:
163
165
  cycle_id: Unique identifier for the cycle.
164
166
  workflow_id: Parent workflow identifier.
@@ -173,7 +175,7 @@ class CycleExecutionTrace:
173
175
  timeout_configured: Configured timeout limit.
174
176
  convergence_condition: Configured convergence condition.
175
177
  """
176
-
178
+
177
179
  cycle_id: str
178
180
  workflow_id: str
179
181
  start_time: datetime
@@ -188,27 +190,37 @@ class CycleExecutionTrace:
188
190
  convergence_condition: Optional[str] = None
189
191
  memory_peak_mb: Optional[float] = None
190
192
  cpu_peak_percent: Optional[float] = None
191
-
193
+
192
194
  def add_iteration(self, iteration: CycleIteration):
193
195
  """
194
196
  Add an iteration to the trace.
195
-
197
+
196
198
  Args:
197
199
  iteration: The iteration to add.
198
200
  """
199
201
  self.iterations.append(iteration)
200
-
202
+
201
203
  # Update peak metrics
202
- if iteration.memory_usage_mb and (not self.memory_peak_mb or iteration.memory_usage_mb > self.memory_peak_mb):
204
+ if iteration.memory_usage_mb and (
205
+ not self.memory_peak_mb or iteration.memory_usage_mb > self.memory_peak_mb
206
+ ):
203
207
  self.memory_peak_mb = iteration.memory_usage_mb
204
-
205
- if iteration.cpu_usage_percent and (not self.cpu_peak_percent or iteration.cpu_usage_percent > self.cpu_peak_percent):
208
+
209
+ if iteration.cpu_usage_percent and (
210
+ not self.cpu_peak_percent
211
+ or iteration.cpu_usage_percent > self.cpu_peak_percent
212
+ ):
206
213
  self.cpu_peak_percent = iteration.cpu_usage_percent
207
-
208
- def complete(self, converged: bool, termination_reason: str, convergence_iteration: Optional[int] = None):
214
+
215
+ def complete(
216
+ self,
217
+ converged: bool,
218
+ termination_reason: str,
219
+ convergence_iteration: Optional[int] = None,
220
+ ):
209
221
  """
210
222
  Mark cycle execution as complete.
211
-
223
+
212
224
  Args:
213
225
  converged: Whether the cycle converged successfully.
214
226
  termination_reason: Why the cycle terminated.
@@ -219,14 +231,14 @@ class CycleExecutionTrace:
219
231
  self.converged = converged
220
232
  self.termination_reason = termination_reason
221
233
  self.convergence_iteration = convergence_iteration
222
-
234
+
223
235
  def get_statistics(self) -> Dict[str, Any]:
224
236
  """
225
237
  Get comprehensive statistics about the cycle execution.
226
-
238
+
227
239
  Returns:
228
240
  Statistics including timing, convergence, and performance metrics.
229
-
241
+
230
242
  Examples:
231
243
  >>> stats = trace.get_statistics()
232
244
  >>> print(f"Average iteration time: {stats['avg_iteration_time']:.3f}s")
@@ -238,17 +250,27 @@ class CycleExecutionTrace:
238
250
  "min_iteration_time": 0.0,
239
251
  "max_iteration_time": 0.0,
240
252
  "convergence_rate": 0.0,
241
- "efficiency_score": 0.0
253
+ "efficiency_score": 0.0,
242
254
  }
243
-
255
+
244
256
  # Calculate timing statistics
245
- iteration_times = [iter.execution_time for iter in self.iterations if iter.execution_time is not None]
246
-
257
+ iteration_times = [
258
+ iter.execution_time
259
+ for iter in self.iterations
260
+ if iter.execution_time is not None
261
+ ]
262
+
247
263
  stats = {
248
264
  "total_iterations": len(self.iterations),
249
- "successful_iterations": len([iter for iter in self.iterations if iter.is_successful()]),
250
- "failed_iterations": len([iter for iter in self.iterations if iter.error is not None]),
251
- "avg_iteration_time": sum(iteration_times) / len(iteration_times) if iteration_times else 0.0,
265
+ "successful_iterations": len(
266
+ [iter for iter in self.iterations if iter.is_successful()]
267
+ ),
268
+ "failed_iterations": len(
269
+ [iter for iter in self.iterations if iter.error is not None]
270
+ ),
271
+ "avg_iteration_time": (
272
+ sum(iteration_times) / len(iteration_times) if iteration_times else 0.0
273
+ ),
252
274
  "min_iteration_time": min(iteration_times) if iteration_times else 0.0,
253
275
  "max_iteration_time": max(iteration_times) if iteration_times else 0.0,
254
276
  "total_execution_time": self.total_execution_time or 0.0,
@@ -256,43 +278,63 @@ class CycleExecutionTrace:
256
278
  "convergence_iteration": self.convergence_iteration,
257
279
  "termination_reason": self.termination_reason,
258
280
  "memory_peak_mb": self.memory_peak_mb,
259
- "cpu_peak_percent": self.cpu_peak_percent
281
+ "cpu_peak_percent": self.cpu_peak_percent,
260
282
  }
261
-
283
+
262
284
  # Calculate convergence rate (how quickly it converged relative to max_iterations)
263
- if self.converged and self.convergence_iteration and self.max_iterations_configured:
264
- stats["convergence_rate"] = self.convergence_iteration / self.max_iterations_configured
285
+ if (
286
+ self.converged
287
+ and self.convergence_iteration
288
+ and self.max_iterations_configured
289
+ ):
290
+ stats["convergence_rate"] = (
291
+ self.convergence_iteration / self.max_iterations_configured
292
+ )
265
293
  else:
266
294
  stats["convergence_rate"] = 1.0 if self.converged else 0.0
267
-
295
+
268
296
  # Calculate efficiency score (0-1, higher is better)
269
297
  if self.max_iterations_configured and len(self.iterations) > 0:
270
- iteration_efficiency = 1.0 - (len(self.iterations) / self.max_iterations_configured)
271
- time_efficiency = 1.0 if not self.timeout_configured else max(0.0, 1.0 - (self.total_execution_time or 0) / self.timeout_configured)
298
+ iteration_efficiency = 1.0 - (
299
+ len(self.iterations) / self.max_iterations_configured
300
+ )
301
+ time_efficiency = (
302
+ 1.0
303
+ if not self.timeout_configured
304
+ else max(
305
+ 0.0,
306
+ 1.0 - (self.total_execution_time or 0) / self.timeout_configured,
307
+ )
308
+ )
272
309
  convergence_bonus = 0.2 if self.converged else 0.0
273
- stats["efficiency_score"] = min(1.0, iteration_efficiency * 0.5 + time_efficiency * 0.3 + convergence_bonus)
310
+ stats["efficiency_score"] = min(
311
+ 1.0,
312
+ iteration_efficiency * 0.5 + time_efficiency * 0.3 + convergence_bonus,
313
+ )
274
314
  else:
275
315
  stats["efficiency_score"] = 0.5 if self.converged else 0.0
276
-
316
+
277
317
  return stats
278
-
318
+
279
319
  def get_convergence_trend(self) -> List[Tuple[int, Optional[float]]]:
280
320
  """
281
321
  Get convergence values over iterations for trend analysis.
282
-
322
+
283
323
  Returns:
284
324
  List[Tuple[int, Optional[float]]]: List of (iteration_number, convergence_value) pairs
285
-
325
+
286
326
  Side Effects:
287
327
  None - this is a pure calculation method
288
-
328
+
289
329
  Example:
290
330
  >>> trend = trace.get_convergence_trend()
291
331
  >>> for iteration, value in trend:
292
332
  ... print(f"Iteration {iteration}: {value}")
293
333
  """
294
- return [(iter.iteration_number, iter.convergence_value) for iter in self.iterations]
295
-
334
+ return [
335
+ (iter.iteration_number, iter.convergence_value) for iter in self.iterations
336
+ ]
337
+
296
338
  def to_dict(self) -> Dict[str, Any]:
297
339
  """Convert trace to dictionary for serialization."""
298
340
  return {
@@ -310,67 +352,67 @@ class CycleExecutionTrace:
310
352
  "convergence_condition": self.convergence_condition,
311
353
  "memory_peak_mb": self.memory_peak_mb,
312
354
  "cpu_peak_percent": self.cpu_peak_percent,
313
- "statistics": self.get_statistics()
355
+ "statistics": self.get_statistics(),
314
356
  }
315
357
 
316
358
 
317
359
  class CycleDebugger:
318
360
  """
319
361
  Comprehensive debugging tool for cyclic workflow execution.
320
-
362
+
321
363
  This class provides real-time debugging capabilities for cycles, including
322
364
  iteration tracking, performance monitoring, convergence analysis, and
323
365
  detailed execution tracing. It integrates with the cycle execution system
324
366
  to provide insights into cycle behavior and performance.
325
-
367
+
326
368
  Design Philosophy:
327
369
  Provides non-intrusive debugging that doesn't affect cycle performance
328
370
  in production. Offers multiple levels of debugging detail from basic
329
371
  tracking to comprehensive profiling with rich analytics.
330
-
372
+
331
373
  Upstream Dependencies:
332
374
  - Used by CyclicWorkflowExecutor when debug mode is enabled
333
375
  - Integrates with cycle configuration and execution systems
334
-
376
+
335
377
  Downstream Consumers:
336
378
  - Debug reports and analysis tools
337
379
  - Performance optimization recommendations
338
380
  - Cycle visualization and monitoring dashboards
339
-
381
+
340
382
  Usage Patterns:
341
383
  1. Real-time debugging during development
342
384
  2. Performance profiling for optimization
343
385
  3. Production monitoring for cycle health
344
386
  4. Post-execution analysis for troubleshooting
345
-
387
+
346
388
  Example:
347
389
  >>> debugger = CycleDebugger(debug_level="detailed")
348
390
  >>> trace = debugger.start_cycle("optimization", "workflow_1")
349
- >>>
391
+ >>>
350
392
  >>> # During cycle execution
351
393
  >>> iteration = debugger.start_iteration(trace, input_data)
352
394
  >>> debugger.end_iteration(iteration, output_data)
353
- >>>
395
+ >>>
354
396
  >>> # After cycle completion
355
397
  >>> debugger.end_cycle(trace, converged=True, reason="convergence")
356
398
  >>> report = debugger.generate_report(trace)
357
399
  """
358
-
400
+
359
401
  def __init__(self, debug_level: str = "basic", enable_profiling: bool = False):
360
402
  """
361
403
  Initialize cycle debugger.
362
-
404
+
363
405
  Args:
364
406
  debug_level (str): Level of debugging detail ("basic", "detailed", "verbose")
365
407
  enable_profiling (bool): Whether to enable detailed profiling
366
-
408
+
367
409
  Side Effects:
368
410
  Configures logging and profiling settings
369
411
  """
370
412
  self.debug_level = debug_level
371
413
  self.enable_profiling = enable_profiling
372
414
  self.active_traces: Dict[str, CycleExecutionTrace] = {}
373
-
415
+
374
416
  # Configure logging based on debug level
375
417
  if debug_level == "verbose":
376
418
  logger.setLevel(logging.DEBUG)
@@ -378,35 +420,35 @@ class CycleDebugger:
378
420
  logger.setLevel(logging.INFO)
379
421
  else:
380
422
  logger.setLevel(logging.WARNING)
381
-
423
+
382
424
  def start_cycle(
383
425
  self,
384
426
  cycle_id: str,
385
427
  workflow_id: str,
386
428
  max_iterations: Optional[int] = None,
387
429
  timeout: Optional[float] = None,
388
- convergence_condition: Optional[str] = None
430
+ convergence_condition: Optional[str] = None,
389
431
  ) -> CycleExecutionTrace:
390
432
  """
391
433
  Start debugging a new cycle execution.
392
-
434
+
393
435
  Creates a new execution trace and begins tracking cycle execution
394
436
  with all configured debugging features enabled.
395
-
437
+
396
438
  Args:
397
439
  cycle_id (str): Unique identifier for the cycle
398
440
  workflow_id (str): Parent workflow identifier
399
441
  max_iterations (Optional[int]): Configured iteration limit
400
442
  timeout (Optional[float]): Configured timeout limit
401
443
  convergence_condition (Optional[str]): Convergence condition expression
402
-
444
+
403
445
  Returns:
404
446
  CycleExecutionTrace: New trace object for tracking execution
405
-
447
+
406
448
  Side Effects:
407
449
  Creates new trace and adds to active_traces
408
450
  Logs cycle start event
409
-
451
+
410
452
  Example:
411
453
  >>> trace = debugger.start_cycle("opt_cycle", "workflow_1", max_iterations=100)
412
454
  """
@@ -416,197 +458,210 @@ class CycleDebugger:
416
458
  start_time=datetime.now(),
417
459
  max_iterations_configured=max_iterations,
418
460
  timeout_configured=timeout,
419
- convergence_condition=convergence_condition
461
+ convergence_condition=convergence_condition,
420
462
  )
421
-
463
+
422
464
  self.active_traces[cycle_id] = trace
423
-
465
+
424
466
  logger.info(
425
467
  f"Started debugging cycle '{cycle_id}' in workflow '{workflow_id}' "
426
468
  f"with max_iterations={max_iterations}, timeout={timeout}"
427
469
  )
428
-
470
+
429
471
  return trace
430
-
472
+
431
473
  def start_iteration(
432
474
  self,
433
475
  trace: CycleExecutionTrace,
434
476
  input_data: Dict[str, Any],
435
- iteration_number: Optional[int] = None
477
+ iteration_number: Optional[int] = None,
436
478
  ) -> CycleIteration:
437
479
  """
438
480
  Start debugging a new cycle iteration.
439
-
481
+
440
482
  Creates a new iteration object and begins tracking execution time,
441
483
  resource usage, and other iteration-specific metrics.
442
-
484
+
443
485
  Args:
444
486
  trace (CycleExecutionTrace): Parent cycle trace
445
487
  input_data (Dict[str, Any]): Input data for this iteration
446
488
  iteration_number (Optional[int]): Iteration number (auto-calculated if None)
447
-
489
+
448
490
  Returns:
449
491
  CycleIteration: New iteration object for tracking
450
-
492
+
451
493
  Side Effects:
452
494
  Creates new iteration and adds to trace
453
495
  Begins resource monitoring if profiling enabled
454
-
496
+
455
497
  Example:
456
498
  >>> iteration = debugger.start_iteration(trace, {"value": 10})
457
499
  """
458
500
  if iteration_number is None:
459
501
  iteration_number = len(trace.iterations) + 1
460
-
502
+
461
503
  iteration = CycleIteration(
462
504
  iteration_number=iteration_number,
463
505
  start_time=datetime.now(),
464
- input_data=input_data.copy() if self.debug_level in ["detailed", "verbose"] else {}
506
+ input_data=(
507
+ input_data.copy() if self.debug_level in ["detailed", "verbose"] else {}
508
+ ),
465
509
  )
466
-
510
+
467
511
  # Add profiling data if enabled
468
512
  if self.enable_profiling:
469
513
  try:
470
514
  import psutil
515
+
471
516
  process = psutil.Process()
472
517
  iteration.memory_usage_mb = process.memory_info().rss / 1024 / 1024
473
518
  iteration.cpu_usage_percent = process.cpu_percent()
474
519
  except ImportError:
475
- logger.warning("psutil not available for profiling. Install with: pip install psutil")
476
-
520
+ logger.warning(
521
+ "psutil not available for profiling. Install with: pip install psutil"
522
+ )
523
+
477
524
  if self.debug_level == "verbose":
478
525
  logger.debug(
479
526
  f"Started iteration {iteration_number} for cycle '{trace.cycle_id}' "
480
527
  f"with input keys: {list(input_data.keys())}"
481
528
  )
482
-
529
+
483
530
  return iteration
484
-
531
+
485
532
  def end_iteration(
486
533
  self,
487
534
  trace: CycleExecutionTrace,
488
535
  iteration: CycleIteration,
489
536
  output_data: Dict[str, Any],
490
537
  convergence_value: Optional[float] = None,
491
- node_executions: Optional[List[str]] = None
538
+ node_executions: Optional[List[str]] = None,
492
539
  ):
493
540
  """
494
541
  Complete iteration tracking with output data and metrics.
495
-
542
+
496
543
  Finalizes iteration tracking by recording output data, convergence
497
544
  metrics, and final resource usage measurements.
498
-
545
+
499
546
  Args:
500
547
  trace (CycleExecutionTrace): Parent cycle trace
501
548
  iteration (CycleIteration): Iteration object to complete
502
549
  output_data (Dict[str, Any]): Output data from iteration
503
550
  convergence_value (Optional[float]): Convergence metric if available
504
551
  node_executions (Optional[List[str]]): List of executed nodes
505
-
552
+
506
553
  Side Effects:
507
554
  Completes iteration and adds to trace
508
555
  Updates peak resource usage in trace
509
556
  Logs iteration completion
510
-
557
+
511
558
  Example:
512
559
  >>> debugger.end_iteration(trace, iteration, {"result": 20}, convergence_value=0.05)
513
560
  """
514
- iteration.complete(output_data.copy() if self.debug_level in ["detailed", "verbose"] else {}, convergence_value)
515
-
561
+ iteration.complete(
562
+ output_data.copy() if self.debug_level in ["detailed", "verbose"] else {},
563
+ convergence_value,
564
+ )
565
+
516
566
  if node_executions:
517
567
  iteration.node_executions = node_executions
518
-
568
+
519
569
  # Update profiling data if enabled
520
570
  if self.enable_profiling:
521
571
  try:
522
572
  import psutil
573
+
523
574
  process = psutil.Process()
524
575
  end_memory = process.memory_info().rss / 1024 / 1024
525
576
  end_cpu = process.cpu_percent()
526
-
577
+
527
578
  # Use the higher value for this iteration
528
- iteration.memory_usage_mb = max(iteration.memory_usage_mb or 0, end_memory)
529
- iteration.cpu_usage_percent = max(iteration.cpu_usage_percent or 0, end_cpu)
579
+ iteration.memory_usage_mb = max(
580
+ iteration.memory_usage_mb or 0, end_memory
581
+ )
582
+ iteration.cpu_usage_percent = max(
583
+ iteration.cpu_usage_percent or 0, end_cpu
584
+ )
530
585
  except ImportError:
531
586
  pass # Already warned during start_iteration
532
-
587
+
533
588
  trace.add_iteration(iteration)
534
-
589
+
535
590
  if self.debug_level in ["detailed", "verbose"]:
536
591
  logger.info(
537
592
  f"Completed iteration {iteration.iteration_number} for cycle '{trace.cycle_id}' "
538
593
  f"in {iteration.execution_time:.3f}s, convergence={convergence_value}"
539
594
  )
540
-
595
+
541
596
  def end_cycle(
542
597
  self,
543
598
  trace: CycleExecutionTrace,
544
599
  converged: bool,
545
600
  termination_reason: str,
546
- convergence_iteration: Optional[int] = None
601
+ convergence_iteration: Optional[int] = None,
547
602
  ):
548
603
  """
549
604
  Complete cycle tracking with final results and analysis.
550
-
605
+
551
606
  Finalizes cycle execution tracking and generates comprehensive
552
607
  statistics and analysis for the complete cycle execution.
553
-
608
+
554
609
  Args:
555
610
  trace (CycleExecutionTrace): Cycle trace to complete
556
611
  converged (bool): Whether the cycle converged successfully
557
612
  termination_reason (str): Why the cycle terminated
558
613
  convergence_iteration (Optional[int]): Iteration where convergence occurred
559
-
614
+
560
615
  Side Effects:
561
616
  Completes trace and removes from active_traces
562
617
  Logs cycle completion with statistics
563
-
618
+
564
619
  Example:
565
620
  >>> debugger.end_cycle(trace, converged=True, termination_reason="convergence", convergence_iteration=15)
566
621
  """
567
622
  trace.complete(converged, termination_reason, convergence_iteration)
568
-
623
+
569
624
  # Remove from active traces
570
625
  if trace.cycle_id in self.active_traces:
571
626
  del self.active_traces[trace.cycle_id]
572
-
627
+
573
628
  stats = trace.get_statistics()
574
629
  logger.info(
575
630
  f"Completed cycle '{trace.cycle_id}' in {trace.total_execution_time:.3f}s "
576
631
  f"with {stats['total_iterations']} iterations, "
577
632
  f"converged={converged}, efficiency={stats['efficiency_score']:.2f}"
578
633
  )
579
-
634
+
580
635
  def generate_report(self, trace: CycleExecutionTrace) -> Dict[str, Any]:
581
636
  """
582
637
  Generate comprehensive debugging report for a cycle execution.
583
-
638
+
584
639
  Creates a detailed report including execution statistics, performance
585
640
  analysis, convergence trends, and optimization recommendations based
586
641
  on the complete cycle execution trace.
587
-
642
+
588
643
  Args:
589
644
  trace (CycleExecutionTrace): Completed cycle trace to analyze
590
-
645
+
591
646
  Returns:
592
647
  Dict[str, Any]: Comprehensive debugging report
593
-
648
+
594
649
  Side Effects:
595
650
  None - this is a pure analysis method
596
-
651
+
597
652
  Example:
598
653
  >>> report = debugger.generate_report(trace)
599
654
  >>> print(f"Efficiency score: {report['performance']['efficiency_score']}")
600
655
  """
601
656
  stats = trace.get_statistics()
602
657
  convergence_trend = trace.get_convergence_trend()
603
-
658
+
604
659
  # Analyze convergence pattern
605
660
  convergence_analysis = self._analyze_convergence(convergence_trend)
606
-
661
+
607
662
  # Generate optimization recommendations
608
663
  recommendations = self._generate_recommendations(trace, stats)
609
-
664
+
610
665
  # Create performance summary
611
666
  performance = {
612
667
  "efficiency_score": stats["efficiency_score"],
@@ -614,46 +669,59 @@ class CycleDebugger:
614
669
  "convergence_rate": stats["convergence_rate"],
615
670
  "resource_usage": {
616
671
  "memory_peak_mb": trace.memory_peak_mb,
617
- "cpu_peak_percent": trace.cpu_peak_percent
618
- }
672
+ "cpu_peak_percent": trace.cpu_peak_percent,
673
+ },
619
674
  }
620
-
675
+
621
676
  report = {
622
677
  "cycle_info": {
623
678
  "cycle_id": trace.cycle_id,
624
679
  "workflow_id": trace.workflow_id,
625
680
  "execution_time": trace.total_execution_time,
626
681
  "converged": trace.converged,
627
- "termination_reason": trace.termination_reason
682
+ "termination_reason": trace.termination_reason,
628
683
  },
629
684
  "statistics": stats,
630
685
  "performance": performance,
631
686
  "convergence_analysis": convergence_analysis,
632
687
  "recommendations": recommendations,
633
- "trace_data": trace.to_dict() if self.debug_level == "verbose" else None
688
+ "trace_data": trace.to_dict() if self.debug_level == "verbose" else None,
634
689
  }
635
-
690
+
636
691
  return report
637
-
638
- def _analyze_convergence(self, convergence_trend: List[Tuple[int, Optional[float]]]) -> Dict[str, Any]:
692
+
693
+ def _analyze_convergence(
694
+ self, convergence_trend: List[Tuple[int, Optional[float]]]
695
+ ) -> Dict[str, Any]:
639
696
  """Analyze convergence pattern from trend data."""
640
- if not convergence_trend or all(value is None for _, value in convergence_trend):
697
+ if not convergence_trend or all(
698
+ value is None for _, value in convergence_trend
699
+ ):
641
700
  return {"pattern": "no_data", "analysis": "No convergence data available"}
642
-
701
+
643
702
  # Filter out None values
644
- valid_points = [(iter_num, value) for iter_num, value in convergence_trend if value is not None]
645
-
703
+ valid_points = [
704
+ (iter_num, value)
705
+ for iter_num, value in convergence_trend
706
+ if value is not None
707
+ ]
708
+
646
709
  if len(valid_points) < 2:
647
- return {"pattern": "insufficient_data", "analysis": "Insufficient convergence data for analysis"}
648
-
710
+ return {
711
+ "pattern": "insufficient_data",
712
+ "analysis": "Insufficient convergence data for analysis",
713
+ }
714
+
649
715
  # Analyze trend
650
716
  values = [value for _, value in valid_points]
651
-
717
+
652
718
  # Check for improvement pattern
653
719
  if len(values) >= 3:
654
- improving_count = sum(1 for i in range(1, len(values)) if values[i] < values[i-1])
720
+ improving_count = sum(
721
+ 1 for i in range(1, len(values)) if values[i] < values[i - 1]
722
+ )
655
723
  improvement_ratio = improving_count / (len(values) - 1)
656
-
724
+
657
725
  if improvement_ratio > 0.8:
658
726
  pattern = "steady_improvement"
659
727
  analysis = "Convergence is steadily improving"
@@ -669,92 +737,124 @@ class CycleDebugger:
669
737
  else:
670
738
  pattern = "limited_data"
671
739
  analysis = "Limited data for pattern analysis"
672
-
740
+
673
741
  return {
674
742
  "pattern": pattern,
675
743
  "analysis": analysis,
676
744
  "initial_value": values[0] if values else None,
677
745
  "final_value": values[-1] if values else None,
678
746
  "improvement": values[0] - values[-1] if len(values) >= 2 else None,
679
- "data_points": len(valid_points)
747
+ "data_points": len(valid_points),
680
748
  }
681
-
682
- def _generate_recommendations(self, trace: CycleExecutionTrace, stats: Dict[str, Any]) -> List[str]:
749
+
750
+ def _generate_recommendations(
751
+ self, trace: CycleExecutionTrace, stats: Dict[str, Any]
752
+ ) -> List[str]:
683
753
  """Generate optimization recommendations based on execution analysis."""
684
754
  recommendations = []
685
-
755
+
686
756
  # Efficiency recommendations
687
757
  if stats["efficiency_score"] < 0.5:
688
- recommendations.append("Consider reducing max_iterations or improving convergence condition")
689
-
758
+ recommendations.append(
759
+ "Consider reducing max_iterations or improving convergence condition"
760
+ )
761
+
690
762
  # Convergence recommendations
691
763
  if not trace.converged:
692
764
  if stats["total_iterations"] >= (trace.max_iterations_configured or 0):
693
- recommendations.append("Cycle reached max_iterations without converging - increase limit or improve algorithm")
765
+ recommendations.append(
766
+ "Cycle reached max_iterations without converging - increase limit or improve algorithm"
767
+ )
694
768
  else:
695
- recommendations.append("Cycle terminated early - check for errors or timeout issues")
696
-
769
+ recommendations.append(
770
+ "Cycle terminated early - check for errors or timeout issues"
771
+ )
772
+
697
773
  # Performance recommendations
698
774
  if stats["avg_iteration_time"] > 1.0:
699
- recommendations.append("Average iteration time is high - consider optimizing node performance")
700
-
775
+ recommendations.append(
776
+ "Average iteration time is high - consider optimizing node performance"
777
+ )
778
+
701
779
  if trace.memory_peak_mb and trace.memory_peak_mb > 1000:
702
- recommendations.append("High memory usage detected - consider data streaming or chunking")
703
-
780
+ recommendations.append(
781
+ "High memory usage detected - consider data streaming or chunking"
782
+ )
783
+
704
784
  # Convergence pattern recommendations
705
785
  convergence_trend = trace.get_convergence_trend()
706
786
  convergence_analysis = self._analyze_convergence(convergence_trend)
707
-
787
+
708
788
  if convergence_analysis["pattern"] == "plateau":
709
- recommendations.append("Convergence plateaued - try different learning rate or algorithm parameters")
789
+ recommendations.append(
790
+ "Convergence plateaued - try different learning rate or algorithm parameters"
791
+ )
710
792
  elif convergence_analysis["pattern"] == "unstable":
711
- recommendations.append("Unstable convergence - reduce learning rate or add regularization")
712
-
793
+ recommendations.append(
794
+ "Unstable convergence - reduce learning rate or add regularization"
795
+ )
796
+
713
797
  # Success recommendations
714
798
  if trace.converged and stats["efficiency_score"] > 0.8:
715
- recommendations.append("Excellent cycle performance - consider using as template for similar workflows")
716
-
799
+ recommendations.append(
800
+ "Excellent cycle performance - consider using as template for similar workflows"
801
+ )
802
+
717
803
  return recommendations
718
-
719
- def export_trace(self, trace: CycleExecutionTrace, filepath: str, format: str = "json"):
804
+
805
+ def export_trace(
806
+ self, trace: CycleExecutionTrace, filepath: str, format: str = "json"
807
+ ):
720
808
  """
721
809
  Export cycle trace to file for external analysis.
722
-
810
+
723
811
  Args:
724
812
  trace (CycleExecutionTrace): Trace to export
725
813
  filepath (str): Output file path
726
814
  format (str): Export format ("json", "csv")
727
-
815
+
728
816
  Side Effects:
729
817
  Creates file at specified path with trace data
730
-
818
+
731
819
  Example:
732
820
  >>> debugger.export_trace(trace, "cycle_debug.json", "json")
733
821
  """
734
822
  trace_data = trace.to_dict()
735
-
823
+
736
824
  if format == "json":
737
- with open(filepath, 'w') as f:
825
+ with open(filepath, "w") as f:
738
826
  json.dump(trace_data, f, indent=2)
739
827
  elif format == "csv":
740
828
  import csv
741
- with open(filepath, 'w', newline='') as f:
829
+
830
+ with open(filepath, "w", newline="") as f:
742
831
  writer = csv.writer(f)
743
-
832
+
744
833
  # Write header
745
- writer.writerow(["iteration", "execution_time", "memory_mb", "cpu_percent", "convergence", "error"])
746
-
834
+ writer.writerow(
835
+ [
836
+ "iteration",
837
+ "execution_time",
838
+ "memory_mb",
839
+ "cpu_percent",
840
+ "convergence",
841
+ "error",
842
+ ]
843
+ )
844
+
747
845
  # Write iteration data
748
846
  for iteration in trace.iterations:
749
- writer.writerow([
750
- iteration.iteration_number,
751
- iteration.execution_time,
752
- iteration.memory_usage_mb,
753
- iteration.cpu_usage_percent,
754
- iteration.convergence_value,
755
- iteration.error or ""
756
- ])
847
+ writer.writerow(
848
+ [
849
+ iteration.iteration_number,
850
+ iteration.execution_time,
851
+ iteration.memory_usage_mb,
852
+ iteration.cpu_usage_percent,
853
+ iteration.convergence_value,
854
+ iteration.error or "",
855
+ ]
856
+ )
757
857
  else:
758
858
  raise ValueError(f"Unsupported export format: {format}")
759
-
760
- logger.info(f"Exported cycle trace to {filepath} in {format} format")
859
+
860
+ logger.info(f"Exported cycle trace to {filepath} in {format} format")