Skip to content

Optimization

pangadfs.optimize

OptimizeDefault()

Bases: OptimizeBase

Source code in pangadfs/base.py
def __init__(self):
    logging.getLogger(__name__).addHandler(logging.NullHandler())

optimize(ga, **kwargs)

Creates initial pool

Parameters:

Name Type Description Default
ga GeneticAlgorithm

the ga instance

required
**kwargs

keyword arguments for plugins

{}

Returns:

Type Description
Dict[str, Any]

Dict

Dict[str, Any]

'population': np.ndarray,

Dict[str, Any]

'fitness': np.ndarray,

Dict[str, Any]

'best_lineup': pd.DataFrame,

Dict[str, Any]

'best_score': float

Source code in pangadfs/optimize.py
def optimize(self, ga: GeneticAlgorithm, **kwargs) -> Dict[str, Any]:
    """Creates initial pool

    Args:
        ga (GeneticAlgorithm): the ga instance
        **kwargs: keyword arguments for plugins

    Returns:
        Dict
        'population': np.ndarray,
        'fitness': np.ndarray,
        'best_lineup': pd.DataFrame,
        'best_score': float

    """
    settings = ga.ctx['ga_settings']
    site = ga.ctx['site_settings']

    # Start profiling
    ga.profiler.start_optimization()
    # create pool and pospool
    # pospool used to generate initial population
    # is a dict of position_name: DataFrame
    pop_size = settings['population_size']
    pool = ga.pool(csvpth=settings['csvpth'])
    cmap = {
        'points': settings['points_column'],
        'position': settings['position_column'],
        'salary': settings['salary_column']
    }
    posfilter = site['posfilter']
    flex_positions = site['flex_positions']
    pospool = ga.pospool(pool=pool, posfilter=posfilter, column_mapping=cmap, flex_positions=flex_positions)

    # create salary and points arrays
    # these match indices of pool
    points = pool[settings['points_column']].to_numpy()
    salaries = pool[settings['salary_column']].to_numpy()

    # create initial population
    initial_population = ga.populate(
        pospool=pospool, 
        posmap=site['posmap'], 
        population_size=pop_size
    )

    # apply validators
    # default is to validate duplicates, salary, and positions
    # can add other validators as desired
    initial_population = ga.validate(
        population=initial_population, 
        salaries=salaries,
        salary_cap=site['salary_cap'],
        pool=pool,
        posmap=site['posmap'],
        position_column=settings['position_column'],
        flex_positions=site['flex_positions']
    )

    # need fitness to determine best lineup
    # and also for selection when loop starts
    population_fitness = ga.fitness(
        population=initial_population, 
        points=points
    )

    # set overall_max based on initial population
    omidx = population_fitness.argmax()
    best_fitness = population_fitness[omidx]
    best_lineup = initial_population[omidx]

    # Mark setup phase complete
    ga.profiler.mark_setup_complete()

    # Mark initial best solution (generation 0)
    ga.profiler.mark_best_solution(0)

    # create new generations
    n_unimproved = 0
    population = initial_population.copy()

    for i in range(1, settings['n_generations'] + 1):
        ga.profiler.start_generation(i)

        if n_unimproved == settings['stop_criteria']:
            break

        if settings.get('verbose'):
            logging.info(f'Starting generation {i}, best={best_fitness}')

        # Evolve population: select → crossover → mutate → validate → fitness
        population, population_fitness = self._evolve_population(
            ga, population, population_fitness, salaries, pool, points, n_unimproved
        )

        # Track best solution
        omidx = population_fitness.argmax()
        generation_max = population_fitness[omidx]

        if generation_max > best_fitness:
            best_fitness = generation_max
            best_lineup = population[omidx]
            n_unimproved = 0
            ga.profiler.mark_best_solution(i)
        else:
            n_unimproved += 1

        ga.profiler.end_generation()

    # End profiling
    ga.profiler.end_optimization()

    # FINALIZE RESULTS
    # will break after n_generations or when stop_criteria reached
    results = {
        'population': population,
        'fitness': population_fitness,
        'best_lineup': pool.loc[best_lineup, :],
        'best_score': best_fitness
    }

    # Add profiling data to results
    if ga.profiler.enabled:
        results['profiling'] = ga.profiler.export_to_dict()

    return results

OptimizeMultilineup()

Bases: OptimizeBase

Source code in pangadfs/optimize.py
def __init__(self):
    super().__init__()
    self._penalty_cache: Dict[Any, np.ndarray] = {}

optimize(ga, **kwargs)

Optimizes for multiple diverse lineups

Parameters:

Name Type Description Default
ga GeneticAlgorithm

the ga instance

required
**kwargs

keyword arguments for plugins

{}

Returns:

Type Description
Dict[str, Any]

Dict containing:

Dict[str, Any]

'population': np.ndarray,

Dict[str, Any]

'fitness': np.ndarray,

Dict[str, Any]

'best_lineup': pd.DataFrame, # For backward compatibility

Dict[str, Any]

'best_score': float, # For backward compatibility

Dict[str, Any]

'lineups': List[pd.DataFrame], # Multiple lineups

Dict[str, Any]

'scores': List[float], # Corresponding scores

Dict[str, Any]

'diversity_metrics': Dict # Diversity statistics

Source code in pangadfs/optimize.py
def optimize(self, ga: GeneticAlgorithm, **kwargs) -> Dict[str, Any]:
    """Optimizes for multiple diverse lineups

    Args:
        ga (GeneticAlgorithm): the ga instance
        **kwargs: keyword arguments for plugins

    Returns:
        Dict containing:
        'population': np.ndarray,
        'fitness': np.ndarray,
        'best_lineup': pd.DataFrame,  # For backward compatibility
        'best_score': float,          # For backward compatibility
        'lineups': List[pd.DataFrame], # Multiple lineups
        'scores': List[float],        # Corresponding scores
        'diversity_metrics': Dict     # Diversity statistics

    """
    settings = ga.ctx['ga_settings']
    site = ga.ctx['site_settings']

    # Get multilineup settings with defaults
    target_lineups = settings.get('target_lineups', 1)
    diversity_weight = settings.get('diversity_weight', 0.2)
    min_overlap_threshold = settings.get('min_overlap_threshold', 0.4)  # More aggressive default
    diversity_method = settings.get('diversity_method', 'jaccard')

    # Start profiling
    ga.profiler.start_optimization()

    # Create pool and pospool (same as OptimizeDefault)
    pop_size = settings['population_size']
    pool = ga.pool(csvpth=settings['csvpth'])
    cmap = {
        'points': settings['points_column'],
        'position': settings['position_column'],
        'salary': settings['salary_column']
    }
    posfilter = site['posfilter']
    flex_positions = site['flex_positions']
    pospool = ga.pospool(pool=pool, posfilter=posfilter, column_mapping=cmap, flex_positions=flex_positions)

    # Create salary and points arrays
    points = pool[settings['points_column']].to_numpy()
    salaries = pool[settings['salary_column']].to_numpy()

    # Create initial population
    initial_population = ga.populate(
        pospool=pospool, 
        posmap=site['posmap'], 
        population_size=pop_size
    )

    # Apply validators
    initial_population = ga.validate(
        population=initial_population, 
        salaries=salaries,
        salary_cap=site['salary_cap'],
        pool=pool,
        posmap=site['posmap'],
        position_column=settings['position_column'],
        flex_positions=site['flex_positions']
    )

    # Calculate fitness
    population_fitness = ga.fitness(
        population=initial_population, 
        points=points
    )

    # Set overall_max based on initial population
    omidx = population_fitness.argmax()
    best_fitness = population_fitness[omidx]
    best_lineup = initial_population[omidx]

    # Mark setup phase complete
    ga.profiler.mark_setup_complete()

    # Mark initial best solution (generation 0)
    ga.profiler.mark_best_solution(0)

    # Create new generations (same GA loop as OptimizeDefault)
    n_unimproved = 0
    population = initial_population.copy()

    evolve_helper = OptimizeDefault()

    for i in range(1, settings['n_generations'] + 1):
        # Start generation timing
        ga.profiler.start_generation(i)

        # End program after n generations if not improving
        if n_unimproved == settings['stop_criteria']:
            break

        # Display progress information with verbose parameter
        if settings.get('verbose'):
            logging.info(f'Starting generation {i}')
            logging.info(f'Best lineup score {best_fitness}')

        # Reuse the same optimized generation evolution path as OptimizeDefault.
        population, population_fitness = evolve_helper._evolve_population(
            ga, population, population_fitness, salaries, pool, points, n_unimproved
        )
        omidx = population_fitness.argmax()
        generation_max = population_fitness[omidx]

        # If new best score, then set n_unimproved to 0
        if generation_max > best_fitness:
            logging.info(f'Lineup improved to {generation_max}')
            best_fitness = generation_max
            best_lineup = population[omidx]
            n_unimproved = 0
            # Mark when best solution was found
            ga.profiler.mark_best_solution(i)
        else:
            n_unimproved += 1
            logging.info(f'Lineup unimproved {n_unimproved} times')

        # End generation timing
        ga.profiler.end_generation()

    # End profiling
    ga.profiler.end_optimization()

    # MULTILINEUP SELECTION
    # Select diverse lineups from final population
    if target_lineups == 1:
        # Single lineup mode - return same structure as OptimizeDefault
        selected_lineups = [population[omidx]]
        selected_scores = [best_fitness]
        diversity_metrics = {'avg_overlap': 0.0, 'min_overlap': 0.0, 'diversity_matrix': np.array([[1.0]])}
    else:
        # Multiple lineup mode - select diverse lineups
        selected_lineups, selected_scores, diversity_metrics = self._select_diverse_lineups(
            population, population_fitness, target_lineups, diversity_weight, min_overlap_threshold, diversity_method
        )

    # FINALIZE RESULTS
    results = {
        'population': population,
        'fitness': population_fitness,
        'best_lineup': pool.loc[best_lineup, :],  # Backward compatibility
        'best_score': best_fitness,               # Backward compatibility
        'lineups': [pool.loc[lineup, :] for lineup in selected_lineups],
        'scores': selected_scores,
        'diversity_metrics': diversity_metrics
    }

    # Add profiling data to results
    if ga.profiler.enabled:
        results['profiling'] = ga.profiler.export_to_dict()

    return results

OptimizeMultiOptimizerFieldOwnership

The OptimizeMultiOptimizerFieldOwnership class is an advanced multi-optimizer that balances three key objectives: - Score optimization: Prioritizes both the best-performing lineups and the overall quality of the entire lineup set. - Diversity: Ensures that the generated lineups are different from each other, reducing overlap and increasing the chances of a unique lineup winning. - Field ownership differentiation: Provides a strategic edge by considering the projected ownership of players in the field. This can be used to create contrarian lineups that avoid popular players or to leverage low-owned players with high upside.

Configuration

The behavior of the optimizer can be controlled through the ga_settings in the context object.

Setting Description Default
target_lineups The number of lineups to generate. 10
score_weight The weight to give to the score component of the fitness function. 0.5
diversity_weight The weight to give to the diversity component of the fitness function. 0.3
field_ownership_weight The weight to give to the field ownership component of the fitness function. 0.2
field_ownership_strategy The strategy to use for field ownership differentiation. Can be contrarian, leverage, or balanced. 'contrarian'
ownership_column The name of the column in the player pool CSV that contains the ownership data. 'ownership'
top_k_focus The number of top lineups to prioritize in the score component. min(10, target_lineups)

Example

ctx = {
    'ga_settings': {
        'target_lineups': 20,
        'score_weight': 0.5,
        'diversity_weight': 0.3,
        'field_ownership_weight': 0.2,
        'field_ownership_strategy': 'contrarian',
        'ownership_column': 'ownership',
        'top_k_focus': 5,
        # ... other GA settings
    }
}
=======
# Optimize Reference

The optimize module provides different optimization strategies for lineup generation.

## Available Optimizers

### OptimizeDefault
The standard single-lineup optimizer that uses a genetic algorithm to find the best possible lineup.

**Use case**: When you need one optimal lineup.

**Key features**:
- Fast convergence to optimal solution
- Proven genetic algorithm implementation
- Suitable for most single-lineup scenarios

### OptimizeMultilineup
Advanced optimizer for generating multiple diverse lineups using a post-processing approach.

**Use case**: When you need multiple diverse lineups (50-150 lineups).

**Key features**:
- Quality-first approach: Runs standard GA first, then selects diverse lineups
- Aggressive diversity selection with configurable thresholds
- Scalable for large lineup sets
- Comprehensive diversity metrics

**Configuration parameters**:
- `target_lineups`: Number of lineups to generate (default: 1)
- `diversity_weight`: Weight for diversity vs quality (default: 0.2)
- `min_overlap_threshold`: Minimum diversity requirement (default: 0.4)
- `diversity_method`: 'jaccard' or 'hamming' similarity (default: 'jaccard')

**Example usage**:
```python
from pangadfs.optimize import OptimizeMultilineup

# Configure for multiple diverse lineups
ga_settings = {
    'target_lineups': 100,
    'diversity_weight': 0.25,
    'min_overlap_threshold': 0.4,
    'diversity_method': 'jaccard',
    'population_size': 1000,
    'n_generations': 150,
    # ... other GA settings
}

optimizer = OptimizeMultilineup()
ga = GeneticAlgorithm(ctx={'ga_settings': ga_settings, ...}, optimize=optimizer)
results = ga.optimize()

# Access multiple lineups
lineups = results['lineups']  # List of DataFrames
scores = results['scores']    # List of scores
diversity_metrics = results['diversity_metrics']  # Diversity statistics

Removed Optimizers

The following optimizers have been removed to streamline the codebase:

  • OptimizeMultilineupSets - Replaced by OptimizeMultilineup's superior post-processing approach
  • OptimizePoolBased - Removed due to complexity without clear benefits
  • OptimizeMultiObjective - Removed as academic approach not suitable for practical use

API Reference

pangadfs.optimize

OptimizeDefault()

Bases: OptimizeBase

Source code in pangadfs/base.py
def __init__(self):
    logging.getLogger(__name__).addHandler(logging.NullHandler())

optimize(ga, **kwargs)

Creates initial pool

Parameters:

Name Type Description Default
ga GeneticAlgorithm

the ga instance

required
**kwargs

keyword arguments for plugins

{}

Returns:

Type Description
Dict[str, Any]

Dict

Dict[str, Any]

'population': np.ndarray,

Dict[str, Any]

'fitness': np.ndarray,

Dict[str, Any]

'best_lineup': pd.DataFrame,

Dict[str, Any]

'best_score': float

Source code in pangadfs/optimize.py
def optimize(self, ga: GeneticAlgorithm, **kwargs) -> Dict[str, Any]:
    """Creates initial pool

    Args:
        ga (GeneticAlgorithm): the ga instance
        **kwargs: keyword arguments for plugins

    Returns:
        Dict
        'population': np.ndarray,
        'fitness': np.ndarray,
        'best_lineup': pd.DataFrame,
        'best_score': float

    """
    settings = ga.ctx['ga_settings']
    site = ga.ctx['site_settings']

    # Start profiling
    ga.profiler.start_optimization()
    # create pool and pospool
    # pospool used to generate initial population
    # is a dict of position_name: DataFrame
    pop_size = settings['population_size']
    pool = ga.pool(csvpth=settings['csvpth'])
    cmap = {
        'points': settings['points_column'],
        'position': settings['position_column'],
        'salary': settings['salary_column']
    }
    posfilter = site['posfilter']
    flex_positions = site['flex_positions']
    pospool = ga.pospool(pool=pool, posfilter=posfilter, column_mapping=cmap, flex_positions=flex_positions)

    # create salary and points arrays
    # these match indices of pool
    points = pool[settings['points_column']].to_numpy()
    salaries = pool[settings['salary_column']].to_numpy()

    # create initial population
    initial_population = ga.populate(
        pospool=pospool, 
        posmap=site['posmap'], 
        population_size=pop_size
    )

    # apply validators
    # default is to validate duplicates, salary, and positions
    # can add other validators as desired
    initial_population = ga.validate(
        population=initial_population, 
        salaries=salaries,
        salary_cap=site['salary_cap'],
        pool=pool,
        posmap=site['posmap'],
        position_column=settings['position_column'],
        flex_positions=site['flex_positions']
    )

    # need fitness to determine best lineup
    # and also for selection when loop starts
    population_fitness = ga.fitness(
        population=initial_population, 
        points=points
    )

    # set overall_max based on initial population
    omidx = population_fitness.argmax()
    best_fitness = population_fitness[omidx]
    best_lineup = initial_population[omidx]

    # Mark setup phase complete
    ga.profiler.mark_setup_complete()

    # Mark initial best solution (generation 0)
    ga.profiler.mark_best_solution(0)

    # create new generations
    n_unimproved = 0
    population = initial_population.copy()

    for i in range(1, settings['n_generations'] + 1):
        ga.profiler.start_generation(i)

        if n_unimproved == settings['stop_criteria']:
            break

        if settings.get('verbose'):
            logging.info(f'Starting generation {i}, best={best_fitness}')

        # Evolve population: select → crossover → mutate → validate → fitness
        population, population_fitness = self._evolve_population(
            ga, population, population_fitness, salaries, pool, points, n_unimproved
        )

        # Track best solution
        omidx = population_fitness.argmax()
        generation_max = population_fitness[omidx]

        if generation_max > best_fitness:
            best_fitness = generation_max
            best_lineup = population[omidx]
            n_unimproved = 0
            ga.profiler.mark_best_solution(i)
        else:
            n_unimproved += 1

        ga.profiler.end_generation()

    # End profiling
    ga.profiler.end_optimization()

    # FINALIZE RESULTS
    # will break after n_generations or when stop_criteria reached
    results = {
        'population': population,
        'fitness': population_fitness,
        'best_lineup': pool.loc[best_lineup, :],
        'best_score': best_fitness
    }

    # Add profiling data to results
    if ga.profiler.enabled:
        results['profiling'] = ga.profiler.export_to_dict()

    return results

OptimizeMultilineup()

Bases: OptimizeBase

Source code in pangadfs/optimize.py
def __init__(self):
    super().__init__()
    self._penalty_cache: Dict[Any, np.ndarray] = {}

optimize(ga, **kwargs)

Optimizes for multiple diverse lineups

Parameters:

Name Type Description Default
ga GeneticAlgorithm

the ga instance

required
**kwargs

keyword arguments for plugins

{}

Returns:

Type Description
Dict[str, Any]

Dict containing:

Dict[str, Any]

'population': np.ndarray,

Dict[str, Any]

'fitness': np.ndarray,

Dict[str, Any]

'best_lineup': pd.DataFrame, # For backward compatibility

Dict[str, Any]

'best_score': float, # For backward compatibility

Dict[str, Any]

'lineups': List[pd.DataFrame], # Multiple lineups

Dict[str, Any]

'scores': List[float], # Corresponding scores

Dict[str, Any]

'diversity_metrics': Dict # Diversity statistics

Source code in pangadfs/optimize.py
def optimize(self, ga: GeneticAlgorithm, **kwargs) -> Dict[str, Any]:
    """Optimizes for multiple diverse lineups

    Args:
        ga (GeneticAlgorithm): the ga instance
        **kwargs: keyword arguments for plugins

    Returns:
        Dict containing:
        'population': np.ndarray,
        'fitness': np.ndarray,
        'best_lineup': pd.DataFrame,  # For backward compatibility
        'best_score': float,          # For backward compatibility
        'lineups': List[pd.DataFrame], # Multiple lineups
        'scores': List[float],        # Corresponding scores
        'diversity_metrics': Dict     # Diversity statistics

    """
    settings = ga.ctx['ga_settings']
    site = ga.ctx['site_settings']

    # Get multilineup settings with defaults
    target_lineups = settings.get('target_lineups', 1)
    diversity_weight = settings.get('diversity_weight', 0.2)
    min_overlap_threshold = settings.get('min_overlap_threshold', 0.4)  # More aggressive default
    diversity_method = settings.get('diversity_method', 'jaccard')

    # Start profiling
    ga.profiler.start_optimization()

    # Create pool and pospool (same as OptimizeDefault)
    pop_size = settings['population_size']
    pool = ga.pool(csvpth=settings['csvpth'])
    cmap = {
        'points': settings['points_column'],
        'position': settings['position_column'],
        'salary': settings['salary_column']
    }
    posfilter = site['posfilter']
    flex_positions = site['flex_positions']
    pospool = ga.pospool(pool=pool, posfilter=posfilter, column_mapping=cmap, flex_positions=flex_positions)

    # Create salary and points arrays
    points = pool[settings['points_column']].to_numpy()
    salaries = pool[settings['salary_column']].to_numpy()

    # Create initial population
    initial_population = ga.populate(
        pospool=pospool, 
        posmap=site['posmap'], 
        population_size=pop_size
    )

    # Apply validators
    initial_population = ga.validate(
        population=initial_population, 
        salaries=salaries,
        salary_cap=site['salary_cap'],
        pool=pool,
        posmap=site['posmap'],
        position_column=settings['position_column'],
        flex_positions=site['flex_positions']
    )

    # Calculate fitness
    population_fitness = ga.fitness(
        population=initial_population, 
        points=points
    )

    # Set overall_max based on initial population
    omidx = population_fitness.argmax()
    best_fitness = population_fitness[omidx]
    best_lineup = initial_population[omidx]

    # Mark setup phase complete
    ga.profiler.mark_setup_complete()

    # Mark initial best solution (generation 0)
    ga.profiler.mark_best_solution(0)

    # Create new generations (same GA loop as OptimizeDefault)
    n_unimproved = 0
    population = initial_population.copy()

    evolve_helper = OptimizeDefault()

    for i in range(1, settings['n_generations'] + 1):
        # Start generation timing
        ga.profiler.start_generation(i)

        # End program after n generations if not improving
        if n_unimproved == settings['stop_criteria']:
            break

        # Display progress information with verbose parameter
        if settings.get('verbose'):
            logging.info(f'Starting generation {i}')
            logging.info(f'Best lineup score {best_fitness}')

        # Reuse the same optimized generation evolution path as OptimizeDefault.
        population, population_fitness = evolve_helper._evolve_population(
            ga, population, population_fitness, salaries, pool, points, n_unimproved
        )
        omidx = population_fitness.argmax()
        generation_max = population_fitness[omidx]

        # If new best score, then set n_unimproved to 0
        if generation_max > best_fitness:
            logging.info(f'Lineup improved to {generation_max}')
            best_fitness = generation_max
            best_lineup = population[omidx]
            n_unimproved = 0
            # Mark when best solution was found
            ga.profiler.mark_best_solution(i)
        else:
            n_unimproved += 1
            logging.info(f'Lineup unimproved {n_unimproved} times')

        # End generation timing
        ga.profiler.end_generation()

    # End profiling
    ga.profiler.end_optimization()

    # MULTILINEUP SELECTION
    # Select diverse lineups from final population
    if target_lineups == 1:
        # Single lineup mode - return same structure as OptimizeDefault
        selected_lineups = [population[omidx]]
        selected_scores = [best_fitness]
        diversity_metrics = {'avg_overlap': 0.0, 'min_overlap': 0.0, 'diversity_matrix': np.array([[1.0]])}
    else:
        # Multiple lineup mode - select diverse lineups
        selected_lineups, selected_scores, diversity_metrics = self._select_diverse_lineups(
            population, population_fitness, target_lineups, diversity_weight, min_overlap_threshold, diversity_method
        )

    # FINALIZE RESULTS
    results = {
        'population': population,
        'fitness': population_fitness,
        'best_lineup': pool.loc[best_lineup, :],  # Backward compatibility
        'best_score': best_fitness,               # Backward compatibility
        'lineups': [pool.loc[lineup, :] for lineup in selected_lineups],
        'scores': selected_scores,
        'diversity_metrics': diversity_metrics
    }

    # Add profiling data to results
    if ga.profiler.enabled:
        results['profiling'] = ga.profiler.export_to_dict()

    return results