James McCool commited on
Commit
6d510d7
Β·
1 Parent(s): 67963a0

more work on the exposure limiting and adding some prints

Browse files
Files changed (1) hide show
  1. global_func/build_optimal_lineups.py +148 -1
global_func/build_optimal_lineups.py CHANGED
@@ -464,6 +464,66 @@ def _build_active_pool(
464
  return pool[~pool["player_names"].isin(exclude)].reset_index(drop=True)
465
 
466
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
467
  def generate_optimal_lineups(
468
  player_pool: pd.DataFrame,
469
  player_columns: list[str],
@@ -521,9 +581,14 @@ def generate_optimal_lineups(
521
  exposure_cap = None
522
  if max_player_exposure is not None and 0 < max_player_exposure < 100.0:
523
  exposure_cap = max(0.0, min(1.0, float(max_player_exposure) / 100.0))
 
 
 
 
524
 
525
  static_exclude: set[str] = set(exclude_players or [])
526
  stack_in_solver = bool(stack_config and stack_config.get("enabled") and stack_slot_columns)
 
527
 
528
  def _notify_progress() -> None:
529
  if progress_callback is not None:
@@ -531,9 +596,11 @@ def generate_optimal_lineups(
531
 
532
  def try_add_lineup(seed: pd.Series, active_pool: pd.DataFrame) -> pd.Series | None:
533
  if active_pool.empty:
 
 
534
  return None
535
  max_tries = 8 if exposure_cap is not None else (5 if stack_in_solver else 8)
536
- for _ in range(max_tries):
537
  row = _build_one_lineup(
538
  seed,
539
  player_columns,
@@ -548,9 +615,13 @@ def generate_optimal_lineups(
548
  stack_slot_columns,
549
  )
550
  if row is None:
 
 
551
  continue
552
  key = _lineup_key(row, player_columns)
553
  if key in used_keys:
 
 
554
  continue
555
  if exposure_cap is not None and not _lineup_within_exposure_cap(
556
  row,
@@ -559,10 +630,23 @@ def generate_optimal_lineups(
559
  len(rows),
560
  exposure_cap,
561
  ):
 
 
 
 
 
 
 
 
 
 
 
562
  continue
563
  used_keys.add(key)
564
  _record_lineup_usage(row, player_columns, usage_counts)
565
  return row
 
 
566
  return None
567
 
568
  seed = seed_row_with_constraints(
@@ -577,11 +661,35 @@ def generate_optimal_lineups(
577
  optimize_by=optimize_by,
578
  )
579
  first_pool = _build_active_pool(pool, usage_counts, 0, exposure_cap, static_exclude)
 
 
 
 
 
 
 
 
 
 
 
580
  first_row = try_add_lineup(seed, first_pool)
581
  if first_row is None:
 
 
582
  return pd.DataFrame(columns=player_columns)
583
  rows.append(first_row)
584
  _notify_progress()
 
 
 
 
 
 
 
 
 
 
 
585
 
586
  max_attempts = max(num_lineups * 50, 100) if exposure_cap is not None else max(num_lineups * 20, 40)
587
  attempts = 0
@@ -608,13 +716,35 @@ def generate_optimal_lineups(
608
  static_exclude,
609
  temp_exclude,
610
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
611
  if active_pool.empty:
 
 
 
 
 
612
  break
613
 
614
  if stack_in_solver:
615
  next_seed = empty_lineup_row(player_columns)
616
  candidate_row = try_add_lineup(next_seed, active_pool)
617
  if candidate_row is None:
 
 
 
 
618
  continue
619
  rows.append(candidate_row)
620
  _notify_progress()
@@ -634,13 +764,22 @@ def generate_optimal_lineups(
634
  used_keys,
635
  )
636
  if candidate_row is None:
 
 
637
  continue
638
  if not lineup_satisfies_stack(
639
  candidate_row, stack_config, stack_slot_columns, map_dict["team_map"]
640
  ):
 
 
641
  continue
642
  candidate_key = _lineup_key(candidate_row, player_columns)
643
  if candidate_key in used_keys:
 
 
 
 
 
644
  continue
645
  if exposure_cap is not None and not _lineup_within_exposure_cap(
646
  candidate_row,
@@ -649,12 +788,20 @@ def generate_optimal_lineups(
649
  num_built,
650
  exposure_cap,
651
  ):
 
 
652
  continue
653
  used_keys.add(candidate_key)
654
  _record_lineup_usage(candidate_row, player_columns, usage_counts)
655
  rows.append(candidate_row)
656
  _notify_progress()
657
 
 
 
 
 
 
 
658
  result = pd.DataFrame(rows)
659
  objectives = [
660
  calculate_lineup_objective(result.iloc[i], player_columns, pool, metric_col)
 
464
  return pool[~pool["player_names"].isin(exclude)].reset_index(drop=True)
465
 
466
 
467
+ def _print_exposure_debug(
468
+ *,
469
+ label: str,
470
+ num_lineups_built: int,
471
+ num_lineups_target: int,
472
+ usage_counts: dict[str, int],
473
+ full_pool: pd.DataFrame,
474
+ active_pool: pd.DataFrame,
475
+ max_exposure_fraction: float | None,
476
+ static_exclude: set[str],
477
+ temp_exclude: set[str] | None = None,
478
+ attempt: int | None = None,
479
+ ) -> None:
480
+ """Stdout debug: player usage rates and who is in the optimization pool."""
481
+ cap_pct = max_exposure_fraction * 100 if max_exposure_fraction is not None else None
482
+ attempt_str = f" attempt={attempt}" if attempt is not None else ""
483
+ print(
484
+ f"\n[optimizer exposure] {label} | built={num_lineups_built}/{num_lineups_target}"
485
+ f"{attempt_str} | cap={cap_pct}%"
486
+ )
487
+ print(f" full_pool={len(full_pool)} players | active_pool={len(active_pool)} players")
488
+
489
+ if usage_counts and num_lineups_built > 0:
490
+ usage_rows = []
491
+ for pname, count in sorted(usage_counts.items(), key=lambda x: (-x[1], x[0])):
492
+ rate = count / num_lineups_built
493
+ usage_rows.append(f" {pname}: {count}/{num_lineups_built} ({rate:.1%})")
494
+ print(f" usage ({len(usage_counts)} players with appearances):")
495
+ for line in usage_rows[:25]:
496
+ print(line)
497
+ if len(usage_rows) > 25:
498
+ print(f" ... and {len(usage_rows) - 25} more")
499
+ elif num_lineups_built == 0:
500
+ print(" usage: (none yet β€” first lineup)")
501
+
502
+ if max_exposure_fraction is not None and num_lineups_built > 0:
503
+ exposure_blocked = _exposure_excluded_players(
504
+ usage_counts, num_lineups_built, max_exposure_fraction
505
+ )
506
+ print(f" exposure_blocked={len(exposure_blocked)} players")
507
+ if exposure_blocked:
508
+ sample = sorted(exposure_blocked)[:15]
509
+ print(f" sample: {', '.join(sample)}")
510
+ if len(exposure_blocked) > 15:
511
+ print(f" ... and {len(exposure_blocked) - 15} more")
512
+
513
+ if static_exclude:
514
+ print(f" user_excluded={len(static_exclude)} players")
515
+ if temp_exclude:
516
+ print(f" temp_excluded (diversity)={len(temp_exclude)}: {', '.join(sorted(temp_exclude))}")
517
+
518
+ if not active_pool.empty:
519
+ avail = active_pool["player_names"].tolist()
520
+ print(f" pool_available ({len(avail)}): {', '.join(avail[:20])}")
521
+ if len(avail) > 20:
522
+ print(f" ... and {len(avail) - 20} more")
523
+ else:
524
+ print(" pool_available: EMPTY")
525
+
526
+
527
  def generate_optimal_lineups(
528
  player_pool: pd.DataFrame,
529
  player_columns: list[str],
 
581
  exposure_cap = None
582
  if max_player_exposure is not None and 0 < max_player_exposure < 100.0:
583
  exposure_cap = max(0.0, min(1.0, float(max_player_exposure) / 100.0))
584
+ print(
585
+ f"\n[optimizer exposure] START build | target_lineups={num_lineups} "
586
+ f"| cap={max_player_exposure}% | full_pool={len(pool)}"
587
+ )
588
 
589
  static_exclude: set[str] = set(exclude_players or [])
590
  stack_in_solver = bool(stack_config and stack_config.get("enabled") and stack_slot_columns)
591
+ debug_exposure = exposure_cap is not None
592
 
593
  def _notify_progress() -> None:
594
  if progress_callback is not None:
 
596
 
597
  def try_add_lineup(seed: pd.Series, active_pool: pd.DataFrame) -> pd.Series | None:
598
  if active_pool.empty:
599
+ if debug_exposure:
600
+ print(" [try_add_lineup] skipped β€” active_pool is empty")
601
  return None
602
  max_tries = 8 if exposure_cap is not None else (5 if stack_in_solver else 8)
603
+ for try_idx in range(max_tries):
604
  row = _build_one_lineup(
605
  seed,
606
  player_columns,
 
615
  stack_slot_columns,
616
  )
617
  if row is None:
618
+ if debug_exposure:
619
+ print(f" [try_add_lineup] try {try_idx + 1}/{max_tries}: solver returned None")
620
  continue
621
  key = _lineup_key(row, player_columns)
622
  if key in used_keys:
623
+ if debug_exposure:
624
+ print(f" [try_add_lineup] try {try_idx + 1}/{max_tries}: duplicate lineup")
625
  continue
626
  if exposure_cap is not None and not _lineup_within_exposure_cap(
627
  row,
 
630
  len(rows),
631
  exposure_cap,
632
  ):
633
+ if debug_exposure:
634
+ over = [
635
+ p
636
+ for p in _lineup_player_names(row, player_columns)
637
+ if (usage_counts.get(p, 0) + 1) / (len(rows) + 1)
638
+ > exposure_cap + 1e-9
639
+ ]
640
+ print(
641
+ f" [try_add_lineup] try {try_idx + 1}/{max_tries}: "
642
+ f"over exposure cap β€” {over[:8]}"
643
+ )
644
  continue
645
  used_keys.add(key)
646
  _record_lineup_usage(row, player_columns, usage_counts)
647
  return row
648
+ if debug_exposure:
649
+ print(f" [try_add_lineup] failed after {max_tries} tries")
650
  return None
651
 
652
  seed = seed_row_with_constraints(
 
661
  optimize_by=optimize_by,
662
  )
663
  first_pool = _build_active_pool(pool, usage_counts, 0, exposure_cap, static_exclude)
664
+ if debug_exposure:
665
+ _print_exposure_debug(
666
+ label="before lineup 1",
667
+ num_lineups_built=0,
668
+ num_lineups_target=num_lineups,
669
+ usage_counts=usage_counts,
670
+ full_pool=pool,
671
+ active_pool=first_pool,
672
+ max_exposure_fraction=exposure_cap,
673
+ static_exclude=static_exclude,
674
+ )
675
  first_row = try_add_lineup(seed, first_pool)
676
  if first_row is None:
677
+ if debug_exposure:
678
+ print("[optimizer exposure] FAILED β€” could not build first lineup")
679
  return pd.DataFrame(columns=player_columns)
680
  rows.append(first_row)
681
  _notify_progress()
682
+ if debug_exposure:
683
+ _print_exposure_debug(
684
+ label="after lineup 1 accepted",
685
+ num_lineups_built=len(rows),
686
+ num_lineups_target=num_lineups,
687
+ usage_counts=usage_counts,
688
+ full_pool=pool,
689
+ active_pool=first_pool,
690
+ max_exposure_fraction=exposure_cap,
691
+ static_exclude=static_exclude,
692
+ )
693
 
694
  max_attempts = max(num_lineups * 50, 100) if exposure_cap is not None else max(num_lineups * 20, 40)
695
  attempts = 0
 
716
  static_exclude,
717
  temp_exclude,
718
  )
719
+ if debug_exposure and (attempts == 1 or attempts % 10 == 0):
720
+ _print_exposure_debug(
721
+ label=f"before lineup {num_built + 1}",
722
+ num_lineups_built=num_built,
723
+ num_lineups_target=num_lineups,
724
+ usage_counts=usage_counts,
725
+ full_pool=pool,
726
+ active_pool=active_pool,
727
+ max_exposure_fraction=exposure_cap,
728
+ static_exclude=static_exclude,
729
+ temp_exclude=temp_exclude,
730
+ attempt=attempts,
731
+ )
732
  if active_pool.empty:
733
+ if debug_exposure:
734
+ print(
735
+ f"[optimizer exposure] STOP β€” active_pool empty at attempt {attempts} "
736
+ f"(built {num_built}/{num_lineups})"
737
+ )
738
  break
739
 
740
  if stack_in_solver:
741
  next_seed = empty_lineup_row(player_columns)
742
  candidate_row = try_add_lineup(next_seed, active_pool)
743
  if candidate_row is None:
744
+ if debug_exposure and attempts <= 3:
745
+ print(
746
+ f" [stack path] attempt {attempts}: try_add_lineup returned None"
747
+ )
748
  continue
749
  rows.append(candidate_row)
750
  _notify_progress()
 
764
  used_keys,
765
  )
766
  if candidate_row is None:
767
+ if debug_exposure and attempts <= 5:
768
+ print(f" [swap path] attempt {attempts}: _one_swap_below returned None")
769
  continue
770
  if not lineup_satisfies_stack(
771
  candidate_row, stack_config, stack_slot_columns, map_dict["team_map"]
772
  ):
773
+ if debug_exposure and attempts <= 5:
774
+ print(f" [swap path] attempt {attempts}: stack check failed")
775
  continue
776
  candidate_key = _lineup_key(candidate_row, player_columns)
777
  if candidate_key in used_keys:
778
+ if debug_exposure and attempts <= 5:
779
+ print(
780
+ f" [swap path] attempt {attempts}: duplicate lineup "
781
+ f"(same as prior β€” swap found nothing new)"
782
+ )
783
  continue
784
  if exposure_cap is not None and not _lineup_within_exposure_cap(
785
  candidate_row,
 
788
  num_built,
789
  exposure_cap,
790
  ):
791
+ if debug_exposure and attempts <= 5:
792
+ print(f" [swap path] attempt {attempts}: rejected by exposure cap")
793
  continue
794
  used_keys.add(candidate_key)
795
  _record_lineup_usage(candidate_row, player_columns, usage_counts)
796
  rows.append(candidate_row)
797
  _notify_progress()
798
 
799
+ if debug_exposure:
800
+ print(
801
+ f"\n[optimizer exposure] END build | built={len(rows)}/{num_lineups} "
802
+ f"| total_attempts={attempts}"
803
+ )
804
+
805
  result = pd.DataFrame(rows)
806
  objectives = [
807
  calculate_lineup_objective(result.iloc[i], player_columns, pool, metric_col)