src.core.opt_sequential

Sequential multi-period budget optimization.

This module provides a robust alternative to simultaneous multi-period optimization. Instead of optimizing all periods at once (which can fail for numerical reasons), it optimizes one period at a time in sequence.

This approach is: - Guaranteed to converge (each period is a small, easy problem) - Fast (8 small problems << 1 large problem) - Respects ramp constraints naturally (previous period is the baseline) - Numerically stable (avoids high-dimensional optimization issues)

Trade-off: Sequential is greedy (not globally optimal) but robust (always works).

Module Contents

src.core.opt_sequential.optimize_sequential_multiperiod(method: str, total_budget: float, budget_ranges: Dict[str, Tuple[float, float]], parameters: Dict[str, Tuple[float, float]], channels: List[str], n_periods: int, seasonal_effects: Dict[int, Dict[str, float]] | None = None, ramp_abs: Dict[str, float] | None = None, ramp_pct: Dict[str, float] | None = None, ramp_eps: float = 1e-06) Dict[int, Dict[str, float]]

Optimize budget across multiple periods sequentially.

Instead of solving one large optimization problem with n_periods × n_channels variables, this solves n_periods small problems with n_channels variables each.

Each period uses the previous period’s allocation to enforce ramp constraints.

Parameters:
  • method – Response curve method (‘michaelis-menten’ or ‘sigmoid’).

  • total_budget – Total budget across all periods.

  • budget_ranges – Budget ranges (min, max) per channel.

  • parameters – Model parameters per channel.

  • channels – List of channel names.

  • n_periods – Number of periods to optimize.

  • seasonal_effects – Optional seasonal multipliers per period.

  • ramp_abs – Optional absolute ramp limits per channel.

  • ramp_pct – Optional percentage ramp limits per channel.

  • ramp_eps – Epsilon for percentage ramp calculations.

Returns:

Dictionary mapping period index to channel budgets.

Example

>>> result = optimize_sequential_multiperiod(
...     method='sigmoid',
...     total_budget=1_000_000,
...     budget_ranges={'google': (10000, 50000), 'fb': (5000, 30000)},
...     parameters={'google': (0.5, 10000), 'fb': (0.6, 15000)},
...     channels=['google', 'fb'],
...     n_periods=13
... )