Multi-Period Budget Optimization - User Guide
Overview
Section titled “Overview”Multi-period budget optimization extends AMMM’s budget allocation capabilities to plan across multiple time periods (e.g., 13 weeks, 6 months) rather than optimizing for a single aggregated period. This enables:
- Time-varying budget allocation - Allocate different budgets to different periods based on expected effectiveness
- Seasonality integration - Automatically adjust for seasonal variations in channel effectiveness
- Realistic planning - Align with business planning cycles (quarterly, annual)
- Constraint flexibility - Set per-period budget limits while optimizing total allocation
How Multi-Period Optimisation Works (Implementation)
Section titled “How Multi-Period Optimisation Works (Implementation)”Multi-period optimisation allocates budgets S_{p,i} across periods p=1..P and channels i=1..n to maximise
total expected contribution:
\max_{\{S_{p,i}\}} \sum_{p=1}^{P}\sum_{i=1}^{n} w_{p,i}\,C_i(S_{p,i})Where:
C_i(·)is a per-channel response curve fitted from the MMM (sigmoid saturation by default),w_{p,i}is an optional seasonality multiplier (defaults to 1 when seasonality is disabled).
Constraints
Section titled “Constraints”The optimiser enforces:
- Total budget across all periods and channels:
Σ_{p,i} S_{p,i} = B - Per-channel bounds per period:
S_{p,i}^{min} ≤ S_{p,i} ≤ S_{p,i}^{max} - Optional per-period budget limits:
B_p^{min} ≤ Σ_i S_{p,i} ≤ B_p^{max} - Optional ramp constraints between consecutive periods (absolute and/or percentage)
Solver selection (sequential vs simultaneous)
Section titled “Solver selection (sequential vs simultaneous)”AMMM automatically chooses a more robust strategy for larger problems:
- Sequential optimisation (default for larger problems): optimises one period at a time (greedy) and naturally enforces ramp constraints against the previous period. This is much more stable for larger horizons.
- Simultaneous optimisation (smaller problems): solves all
P×nvariables at once using SciPy’s constrained optimiser (SLSQPby default; may switch totrust-constrfor very large problems).
Adstock-aware multi-period optimisation (advanced)
Section titled “Adstock-aware multi-period optimisation (advanced)”The codebase includes an adstock-aware objective (carryover state dynamics), but it is not exposed via runme.py
multi-period mode today. Treat it as an advanced/experimental option requiring careful constraint tuning.
When to Use Multi-Period Optimization
Section titled “When to Use Multi-Period Optimization”Use multi-period optimization when:
- You need to plan budgets for multiple weeks/months ahead
- Your business has significant seasonality (e.g., retail, travel)
- You want to optimize across a planning horizon (e.g., Q1, full year)
- You need to respect time-varying budget constraints
Use single-period optimization when:
- You’re optimizing for a single aggregated time period
- You want quick “what-if” scenarios with fixed total budgets
- Seasonality is not a major concern
Quick Start
Section titled “Quick Start”Basic Multi-Period Optimization (13 weeks)
Section titled “Basic Multi-Period Optimization (13 weeks)”import src as ammmfrom src.driver import MMMBaseDriverV2
# Fit model (standard pipeline)driver = MMMBaseDriverV2('config.yml', 'data.csv', 'holidays.xlsx', 'results')driver.main()
# Run 13-week multi-period optimizationammm.optimize_marketing_budget( model=driver.model, data=driver.processed_data, config=driver.config, results_dir=driver.results_dir, n_time_periods=13, # 13 weeks multiperiod_mode=True, # Enable multi-period use_seasonality=True, # Apply seasonal adjustments frequency='W' # Weekly planning)Output:
- CSV file:
results/multiperiod_optimization_results.csv - Console summary with total budget, contribution, and ROI
Usage Examples
Section titled “Usage Examples”Example 1: Quarterly Planning (13 weeks)
Section titled “Example 1: Quarterly Planning (13 weeks)”ammm.optimize_marketing_budget( model=driver.model, data=driver.processed_data, config=driver.config, results_dir='results', n_time_periods=13, multiperiod_mode=True, use_seasonality=True, frequency='W', start_date='2025-01-06' # Q1 start)Example 2: Custom Budget with Seasonality
Section titled “Example 2: Custom Budget with Seasonality”ammm.optimize_marketing_budget( model=driver.model, data=driver.processed_data, config=driver.config, results_dir='results', n_time_periods=13, total_budget=12_500_000, # £12.5M for Q1 multiperiod_mode=True, use_seasonality=True, frequency='W', start_date='2025-01-06')Example 3: Monthly Planning (12 months)
Section titled “Example 3: Monthly Planning (12 months)”ammm.optimize_marketing_budget( model=driver.model, data=driver.processed_data, config=driver.config, results_dir='results', n_time_periods=12, multiperiod_mode=True, use_seasonality=True, frequency='M', # Monthly frequency start_date='2025-01-01')Example 4: With Per-Period Budget Constraints
Section titled “Example 4: With Per-Period Budget Constraints”# Direct call to multi-period function for advanced optionsfrom src.driver.opt import optimize_multiperiod_budget
results_df = optimize_multiperiod_budget( model=driver.model, data=driver.processed_data, config=driver.config, results_dir='results', n_periods=13, total_budget=12_500_000, period_budget_limits=(800_000, 1_200_000), # £800K-£1.2M per week use_seasonality=True, frequency='W', start_date='2025-01-06')Parameters Reference
Section titled “Parameters Reference”Main Function: optimize_marketing_budget()
Section titled “Main Function: optimize_marketing_budget()”| Parameter | Type | Default | Description |
|---|---|---|---|
model | BaseMMM | Required | Fitted MMM model |
data | DataFrame | Required | Historical data |
config | Dict | Required | Configuration dictionary |
results_dir | str | Required | Output directory |
n_time_periods | int | 1 | Number of periods to optimize |
multiperiod_mode | bool | False | Enable multi-period optimization |
use_seasonality | bool | False | Apply seasonal adjustments |
frequency | str | ’M’ | Time frequency (‘W’, ‘M’, ‘D’) |
start_date | str | None | Planning start date (YYYY-MM-DD) |
total_budget | float | None | Total budget (auto-calculated if None) |
Advanced Function: optimize_multiperiod_budget()
Section titled “Advanced Function: optimize_multiperiod_budget()”Additional parameters for fine-grained control:
| Parameter | Type | Default | Description |
|---|---|---|---|
period_budget_limits | Tuple[float, float] | None | (min, max) budget per period |
lower_bound_pct | float | 0.20 | % below mean for channel lower bounds |
upper_bound_pct | float | 0.20 | % above mean for channel upper bounds |
Output Format
Section titled “Output Format”CSV File: multiperiod_optimization_results.csv
Section titled “CSV File: multiperiod_optimization_results.csv”| Column | Description |
|---|---|
period | Period index (0-indexed) |
period_date | Actual date for period start |
channel | Channel name (or ‘TOTAL’) |
budget | Allocated budget for this period/channel |
contribution | Expected contribution (with seasonal adjustment) |
seasonal_multiplier | Seasonal effectiveness multiplier (1.0 = baseline) |
roi | Return on investment for this period/channel |
Example Output
Section titled “Example Output”period,period_date,channel,budget,contribution,seasonal_multiplier,roi0,2025-01-06,google,12500,60000,0.92,4.80,2025-01-06,facebook,28000,420000,0.88,15.00,2025-01-06,tv,95000,550000,1.15,5.80,2025-01-06,TOTAL,965000,1306000,0.98,1.351,2025-01-13,google,13200,63800,0.94,4.8...Console Summary
Section titled “Console Summary”Multi-Period Optimization Summary (13 periods)Total Budget: 12,500,000.00Total Contribution: 95,750,000.00Overall ROI: 7.66×Understanding Multi-Period Visualizations
Section titled “Understanding Multi-Period Visualizations”When you run multi-period optimization with use_seasonality=True, AMMM generates several visualizations to help you understand the results. These are saved in results/png/:
Budget vs Contribution Trends (multiperiod_budget_vs_contribution.png)
Section titled “Budget vs Contribution Trends (multiperiod_budget_vs_contribution.png)”What you’re looking at:
This graph shows two metrics over time:
- Blue bars (Budget): The optimal recommended budget for each specific period
- Orange line (Contribution): The expected contribution (revenue/conversions) generated in each period
Critical understanding:
The “Budget” shown is NOT your historical or average spending. This is the output of the optimization algorithm - it represents the model’s recommendation for how to allocate your total budget across time to maximize total contribution.
Key features:
-
Time-Varying Allocation: The budget is not flat across periods. The optimizer allocates more to periods where it expects higher return on investment (ROI). For example, if you see higher budget in periods 4-6, that’s when the model predicts the best effectiveness.
-
Seasonal Response: The allocation pattern reflects Prophet’s seasonal effectiveness multipliers. Periods with higher seasonal multipliers (e.g., holiday weeks) receive more budget because each £ spent generates more contribution.
-
Total Per Period: Each bar represents the total recommended marketing spend across all channels for that single period (e.g., week 0, week 1, etc.). This is the sum of all channel budgets for that time period.
-
Contribution Follows Budget: You’ll typically see the contribution line (orange) peak around the same periods as the budget bars. This shows the optimization is working - allocating more budget where it generates more output.
Example interpretation:
If you see:
- Period 5: £1.2M budget → £8.5M contribution
- Period 0: £800K budget → £5.2M contribution
This means:
- The optimizer recommends spending 50% more in Period 5 (£1.2M vs £800K)
- Because Period 5 has higher effectiveness (likely higher seasonal multiplier)
- Result: Period 5 generates 63% more contribution despite only 50% more spend
- This is the optimization at work - allocating budget to high-ROI periods
Other Multi-Period Visualizations
Section titled “Other Multi-Period Visualizations”Seasonal Patterns (multiperiod_seasonal_patterns.png):
- Shows how seasonal multipliers vary over time for each channel
- Multipliers >1.0 indicate periods of above-average effectiveness
- Helps explain why budget allocation varies across periods
Budget Allocation Heatmap (multiperiod_budget_heatmap.png):
- Channel-by-period heatmap showing recommended spend
- Identify which channels get more budget during specific periods
- Useful for spotting channel-specific seasonal patterns
Total Budget: 12,500,000.00 Total Contribution: 95,750,000.00 Overall ROI: 7.66×
## Prophet Seasonality Integration**Period Comparison (`multiperiod_period_comparison.png`):**- Side-by-side comparison of budget, contribution, and ROI by period- Quickly identify the most efficient periods
**Contribution Over Time (`multiperiod_contribution_over_time.png`):**- Stacked area chart showing each channel's contribution by period- See which channels dominate during different time periods
**Using these visualizations:**
1. **Validate the optimization** - Does the budget allocation make business sense given your seasonality?2. **Identify peak periods** - When should you concentrate spend?3. **Channel timing** - Which channels work best in which periods?4. **Explain to stakeholders** - These visuals make the \"why\" behind budget allocation clear
## Prophet Seasonality Integration============================================================Total Budget: 12,500,000.00Total Contribution: 95,750,000.00Overall ROI: 7.66×Prophet Seasonality Integration
Section titled “Prophet Seasonality Integration”How It Works
Section titled “How It Works”Multi-period optimization uses Prophet’s seasonal decomposition from your fitted MMM model to adjust channel effectiveness over time.
Seasonal Components Used:
- Yearly seasonality - Annual patterns (e.g., holiday peaks, summer lulls)
- Weekly seasonality - Day-of-week patterns (e.g., weekend vs weekday)
- Both combined - Additive effect of yearly + weekly
Example:
- Week of December 15 (pre-Christmas Friday):
- Yearly: +0.5 (holiday shopping season)
- Weekly: +0.2 (Friday/weekend effect)
- Combined: +0.7
- Multiplier: ~1.07 (7% effectiveness boost)
Configuring Prophet Seasonality in Your Model
Section titled “Configuring Prophet Seasonality in Your Model”Prophet seasonality is configured when fitting your MMM model. Check your model config:
# In your config.ymlprophet: yearly_seasonality: true # Enable yearly patterns weekly_seasonality: true # Enable weekly patterns daily_seasonality: false # Usually false for weekly/monthly data
# Optional: Custom seasonality seasonalities: - name: 'monthly' period: 30.5 fourier_order: 5Interpreting Seasonal Multipliers
Section titled “Interpreting Seasonal Multipliers”- 1.0 - Baseline effectiveness (no seasonal adjustment)
- >1.0 - Channel more effective (e.g., 1.08 = 8% boost)
- <1.0 - Channel less effective (e.g., 0.92 = 8% reduction)
Example: If Google Ads normally generates £5 contribution per £1 spend:
- Week with multiplier 1.08: £5.40 contribution per £1 spend
- Week with multiplier 0.92: £4.60 contribution per £1 spend
Checking Seasonal Patterns
Section titled “Checking Seasonal Patterns”Inspect what seasonality your model has:
# After model fittingprophet_model = driver.model.prophet_modelforecast = prophet_model.predict(prophet_model.history)
print(forecast.columns)# Output: ['ds', 'trend', 'yhat', 'yearly', 'weekly', 'holidays', ...]
# Plot seasonal componentsprophet_model.plot_components(forecast)Holiday Event Planning & Marketing Theory
Section titled “Holiday Event Planning & Marketing Theory”Overview
Section titled “Overview”Multi-period optimization with seasonality is particularly powerful for planning around major retail events like Black Friday, Christmas, Prime Day, or other promotional periods. This section explains how AMMM handles holiday events and provides guidance based on marketing science research.
How Holidays Are Captured
Section titled “How Holidays Are Captured”Holiday effects appear in Prophet’s holidays component when:
- Holidays file is configured - Your
holidays.xlsxcontains the event dates - Events fall within planning window - The holiday date is within your optimization horizon
- Historical data shows patterns - Prophet learns the effect from your training data
Example:
Planning window: 13 weeks starting Oct 17, 2025Black Friday: Nov 28, 2025 (6 weeks ahead)Result: Black Friday IS captured (within window)
Planning window: 13 weeks starting Oct 17, 2025Christmas: Dec 25, 2025 (10 weeks ahead)Result: Christmas IS captured (within window)
Planning window: 13 weeks starting Oct 17, 2025Easter 2026: April 20, 2026 (26 weeks ahead)Result: Easter NOT captured (beyond window) ⚠️To include more holidays: Extend your planning window
# 26 weeks captures Q4 holidays + Q1n_time_periods=26Configuring Holidays
Section titled “Configuring Holidays”Add events to your holidays.xlsx file:
ds,holiday,country,year2024-11-29,Black_Friday,US,20242025-11-28,Black_Friday,US,20252026-11-27,Black_Friday,US,20262024-12-25,Christmas,US,20242025-12-25,Christmas,US,2025Prophet will learn the average effect of each holiday from your historical data and project it forward.
Checking Holiday Effects
Section titled “Checking Holiday Effects”Verify Prophet has captured your holidays:
# After model fittingprophet_model = driver.model.prophet_modelforecast = prophet_model.predict(prophet_model.history)
# Check if holidays component exists and has variationif 'holidays' in forecast.columns: print(f"Holiday effect range: {forecast['holidays'].min():.2f} to {forecast['holidays'].max():.2f}")
# Show which dates have holiday effects holiday_dates = forecast[forecast['holidays'] != 0][['ds', 'holidays']] print(holiday_dates)The Black Friday Planning Problem: Buildup vs Event Allocation
Section titled “The Black Friday Planning Problem: Buildup vs Event Allocation”The Strategic Question: Should you allocate more budget to the weeks before Black Friday or during Black Friday week itself?
The Short Answer: Marketing science suggests weighted toward the buildup (2-3 weeks before), but the optimal split depends on your brand. Let your MMM discover the answer empirically.
Marketing Theory & Research
Section titled “Marketing Theory & Research”1. The Carryover Effect (Adstock)
Section titled “1. The Carryover Effect (Adstock)”Principle: Advertising has lagged effects. Spend in weeks 1-3 before Black Friday continues to influence purchases during the event.
Research:
-
Naik, Mantrala & Sawyer (1998) - “Planning Media Schedules in the Presence of Dynamic Advertising Quality”
- Pulsing strategies (concentrated bursts before events) outperform continuous spending
- Pre-event concentration builds awareness that converts during the event
-
Sethuraman, Tellis & Briesch (2011) - Meta-analysis of advertising elasticity
- Advertising effects peak 0-1 weeks after exposure but persist for several weeks
- Carryover effects mean early spending has compounding impact
Implication for AMMM: If your MMM shows strong carryover parameters (high adstock), the optimization will naturally shift budget earlier to capture these compounding effects.
2. The Customer Journey
Section titled “2. The Customer Journey”Consumer behaviour research shows:
3-4 weeks before Black Friday:
- Research phase - Consumers browse deals, create wish lists
- Ad effectiveness: Brand awareness advertising has high ROI
- Channel focus: Upper-funnel (display, video, social brand)
1-2 weeks before:
- Consideration phase - Comparing options, checking reviews
- Ad effectiveness: Persuasion advertising critical
- Channel focus: Mid-funnel (search, comparison shopping, retargeting)
Black Friday week:
- Conversion phase - Executing planned purchases
- Ad effectiveness: Reminder advertising, but decisions largely made
- Channel focus: Lower-funnel (branded search, remarketing)
Research:
- Google/Ipsos (2019) - “Black Friday Shopping behaviour”
- 72% of Black Friday shoppers research 2+ weeks in advance
- 54% create shopping lists 1-2 weeks before
- Only 28% make spontaneous same-day decisions
Implication: The majority of purchasing decisions are made before Black Friday, suggesting buildup spending is critical.
3. Competitive Saturation
Section titled “3. Competitive Saturation”During Black Friday itself:
- Extreme competition for attention (every brand advertising heavily)
- Diminishing returns kick in faster due to saturation
- Cost inflation (higher CPMs/CPCs during peak periods)
Research:
- Bruce, Foutz & Kolsarici (2012) - “Dynamic Effectiveness of Advertising and Word of Mouth in Sequential Distribution of Short Lifecycle Products”, Journal of Marketing Research
- Advertising effectiveness varies significantly based on competitive intensity
- Early timing in product lifecycle (or event window) can establish stronger market position
- Word-of-mouth effects amplify early advertising investments
Implication: Your advertising may face less competition and achieve stronger effectiveness in the buildup weeks compared to the highly saturated Black Friday period itself.
Typical Optimal Allocation Pattern
Section titled “Typical Optimal Allocation Pattern”Based on marketing literature and industry practice:
Week -4 to -3: 15-20% of total budget (awareness building)Week -2 to -1: 30-35% of total budget (peak consideration)Week 0 (BF): 25-30% of total budget (conversion + share of voice)Week +1: 10-15% of total budget (Cyber Monday + stragglers)However: This is a starting point. Your MMM will discover the optimal pattern for YOUR brand from YOUR data.
How AMMM Discovers Optimal Timing
Section titled “How AMMM Discovers Optimal Timing”Your multi-period optimization will empirically determine the best allocation by:
-
Prophet learns lag structures from historical data:
- If you historically spent more in buildup weeks and saw strong Black Friday sales
- Prophet encodes that lag relationship in its forecast
-
Response curves capture dynamics:
- The saturation curves show how spending in week T affects outcomes in weeks T, T+1, T+2
- Multi-period optimization accounts for these cross-period effects
-
Seasonal multipliers show when channels are most effective:
- If buildup weeks have higher multipliers than Black Friday week
- Optimization allocates more budget to those higher-ROI weeks
Validating Your Black Friday Strategy
Section titled “Validating Your Black Friday Strategy”After running your multi-period optimization, validate the results:
import pandas as pdimport matplotlib.pyplot as plt
# Load resultsdf = pd.read_csv('results/csv/multiperiod_optimization_results.csv')
# Extract Black Friday window (adjust period numbers to your dates)bf_window = df[(df['period'] >= 4) & (df['period'] <= 8)]
# Check budget allocation patternbudget_by_period = bf_window.groupby('period')['budget'].sum()print("\nBudget allocation around Black Friday:")print(budget_by_period)
# Check seasonal multipliersmultipliers = bf_window[bf_window['channel']=='google'][['period', 'period_date', 'seasonal_multiplier']]print("\nSeasonal effectiveness:")print(multipliers)
# Visualizeplt.figure(figsize=(10, 6))budget_by_period.plot(kind='bar')plt.title('Budget Allocation: Black Friday Window')plt.xlabel('Week')plt.ylabel('Total Budget')plt.show()Questions to ask:
- Is budget ramping up in weeks leading to Black Friday?
- Where is the peak allocation - before or during the event?
- How do seasonal multipliers change across the window?
Case Study: 26-Week Black Friday Planning
Section titled “Case Study: 26-Week Black Friday Planning”Scenario: Retail brand planning from mid-October through Q1
# Capture full Black Friday season + post-holiday periodresults = ammm.optimize_marketing_budget( model=driver.model, data=driver.processed_data, config=driver.config, results_dir='results', n_time_periods=26, # Oct through April multiperiod_mode=True, use_seasonality=True, frequency='W', start_date='2025-10-17' # Mid-October)Expected seasonal pattern:
- Weeks 0-5 (Oct-Nov): Baseline/ramping (multipliers ~0.95-1.05)
- Weeks 6-7 (Black Friday/Cyber Monday): Peak event (multipliers >1.10)
- Weeks 8-10 (Dec - Christmas): Sustained high (multipliers 1.05-1.15)
- Weeks 11-12 (Post-Christmas): Drop-off (multipliers ~0.90-0.95)
- Weeks 13-26 (Jan-Apr): Q1 baseline (multipliers 0.85-1.00)
Optimization will:
- Front-load budget to Q4 (higher seasonal multipliers)
- Concentrate spend 2-3 weeks before Black Friday (based on learned carryover)
- Reduce budget post-holidays (lower multipliers)
Channel-Specific Holiday Strategies
Section titled “Channel-Specific Holiday Strategies”Different channels may have different optimal timing:
Search (Google, Bing):
- Peak effectiveness: During event (high-intent queries spike)
- Strategy: Moderate buildup, heavy during Black Friday
Social (Facebook, TikTok, Instagram):
- Peak effectiveness: 2-3 weeks before (awareness and consideration)
- Strategy: Heavy buildup, maintain during event
TV/Display:
- Peak effectiveness: 3-4 weeks before (brand awareness, long lead)
- Strategy: Very heavy buildup, reduce during event
Your MMM learns this automatically from historical data and will allocate accordingly.
Extending Beyond Black Friday
Section titled “Extending Beyond Black Friday”The same principles apply to other retail events:
Prime Day (Amazon):
- Shorter customer journey (impulse/deal-driven)
- Suggest: 60% buildup (1-2 weeks), 40% during event
Christmas:
- Very long planning window (starts October)
- Suggest: Steady buildup from Oct, peak mid-December
Back to School:
- Medium planning window (3-4 weeks)
- Suggest: 70% buildup, 30% during peak weeks
Let your MMM’s optimization discover the optimal timing for each event based on your specific customer behaviour.
Further Reading
Section titled “Further Reading”Peer-Reviewed Academic Research:
- Naik, Mantrala & Sawyer (1998) - “Planning Media Schedules in the Presence of Dynamic Advertising Quality”, Marketing Science
- Sethuraman, Tellis & Briesch (2011) - “How Well Does Advertising Work?”, Journal of Marketing Research
- Tellis & Weiss (1995) - “Does TV Advertising Really Affect Sales?”, Journal of Marketing
- Bruce, Foutz & Kolsarici (2012) - “Dynamic Effectiveness of Advertising and Word of Mouth in Sequential Distribution of Short Lifecycle Products”, Journal of Marketing Research
Industry Research (Non-Peer-Reviewed):
- Google/Ipsos (2019) - “Black Friday Shopping Behaviour” - Consumer research study
- Google Retail Insights - Annual Black Friday shopper behaviour studies
- Facebook Holiday Marketing Guide - Customer journey research
- Kantar - Seasonal advertising effectiveness studies
Key Takeaway: Multi-period optimization with seasonality lets your MMM empirically determine optimal timing based on YOUR data, rather than relying solely on industry averages.
Troubleshooting
Section titled “Troubleshooting”Issue: “No Prophet model found”
Section titled “Issue: “No Prophet model found””Cause: Your MMM model doesn’t have Prophet seasonality fitted.
Solution: Ensure Prophet is configured in your model config:
baseline_model: 'prophet' # Use Prophet for baseline/trendWorkaround: Run without seasonality:
use_seasonality=FalseIssue: Optimization fails with “Infeasible constraints”
Section titled “Issue: Optimization fails with “Infeasible constraints””Cause: Per-period budget limits are too restrictive given total budget.
Example:
- Total budget: £500K
- Per-period limits: (£200K, £300K) per week
- 4 weeks: Minimum needed = 4 × £200K = £800K (exceeds £500K!)
Solution: Adjust constraints:
period_budget_limits=(100_000, 200_000) # More flexibleOr increase total budget.
Issue: Results show same budget for all periods
Section titled “Issue: Results show same budget for all periods”Cause: Seasonality not enabled or no seasonal variation in data.
Solutions:
- Enable seasonality:
use_seasonality=True - Check if Prophet has seasonal components (see “Checking Seasonal Patterns” above)
- If data truly has no seasonality, this is expected behaviour
Issue: Budget allocation seems unrealistic
Section titled “Issue: Budget allocation seems unrealistic”Cause: Budget bounds may be too wide or too narrow.
Solution: Adjust bounds:
from src.driver.opt import optimize_multiperiod_budget
results = optimize_multiperiod_budget( ..., lower_bound_pct=0.10, # Channels can go 10% below historical mean upper_bound_pct=0.30 # Channels can go 30% above historical mean)Issue: Slow optimization (>5 minutes)
Section titled “Issue: Slow optimization (>5 minutes)”Cause: Large number of periods × channels creates many variables.
Example: 52 weeks × 20 channels = 1,040 optimization variables
Solutions:
- Reduce planning horizon:
n_periods=13instead of 52 - Use faster frequency: monthly instead of weekly
- Simplify constraints (remove per-period limits)
Issue: Optimization fails with “Positive directional derivative” or “failed to converge”
Section titled “Issue: Optimization fails with “Positive directional derivative” or “failed to converge””Error message:
RuntimeError: Multi-period budget optimization failed to converge.Solver message: Positive directional derivative for linesearchRoot cause: Over-constrained optimization problem.
What this means:
The combination of features you’ve enabled creates too many competing constraints for the solver to satisfy simultaneously. The optimizer is “trapped in a corner” where it cannot improve the objective without violating at least one constraint.
Common scenarios:
Scenario 1: All Features Enabled (Most Common)
Section titled “Scenario 1: All Features Enabled (Most Common)”# This often fails for 8+ weeks:python runme.py --multiperiod --multiperiod-weeks 8 \ --ramp-abs 15000 --ramp-pct 0.20Why:
- Absolute ramps: ~45 upper/lower bounds
- Percentage ramps: ~45 more upper/lower bounds
- Channel bounds: ~30 min/max constraints
- Total: many constraints relative to variables
If you enable adstock-aware optimisation via the low-level API, it adds additional state dynamics and can make the problem significantly harder to solve.
Scenario 2: Tight Ramp Constraints
Section titled “Scenario 2: Tight Ramp Constraints”# Very restrictive ramp limits:python runme.py --multiperiod --ramp-abs 5000 --ramp-pct 0.10Why:
- 10% ramp on a £100K/week channel = ±£10K
- If optimal solution needs £20K jump, ramp prevents it
- Solver cannot find feasible path
Scenario 3: Conflicting Constraints
Section titled “Scenario 3: Conflicting Constraints”# Absolute ramp too loose, percentage too tight:--ramp-abs 50000 # ±£50K allowed--ramp-pct 0.05 # ±5% allowedFor a £30K/week channel:
- Absolute allows: ±£50K (very loose)
- Percentage allows: ±£1.5K (very tight)
- Percentage always wins → very restrictive
Solutions (ranked by effectiveness):
Solution 1: Use Single Ramp Type ✅ RECOMMENDED
Section titled “Solution 1: Use Single Ramp Type ✅ RECOMMENDED”Choose either absolute OR percentage, not both:
# Option A: Absolute onlypython runme.py --multiperiod --multiperiod-weeks 4 --ramp-abs 20000
# Option B: Percentage onlypython runme.py --multiperiod --multiperiod-weeks 4 --ramp-pct 0.25Impact: Removes ~45 constraints, typically resolves convergence issues.
Solution 2: Avoid adstock-aware optimisation (advanced)
Section titled “Solution 2: Avoid adstock-aware optimisation (advanced)”If you are using the low-level, adstock-aware multi-period optimiser, disable it and retry.
In runme.py multi-period mode, adstock-aware optimisation is currently not enabled, so this typically only
applies to advanced usage.
Solution 3: Reduce Planning Horizon
Section titled “Solution 3: Reduce Planning Horizon”# 4 weeks instead of 8:python runme.py --multiperiod --multiperiod-weeks 4Impact: Fewer variables (periods × channels) and fewer inter-period constraints.
Trade-off: Shorter planning window may not capture full seasonal patterns.
Solution 4: Relax Constraints
Section titled “Solution 4: Relax Constraints”# Widen ramp limits:python runme.py --multiperiod --ramp-abs 25000 --ramp-pct 0.35Or modify code (src/driver/opt.py line ~607):
# Default is ±50%lower_bound_pct: float = 0.50, # Allow 50% below meanupper_bound_pct: float = 0.50, # Allow 50% above meanImpact: Gives solver more “wiggle room” to find feasible solutions.
Solution 5: Simplify Problem Structure
Section titled “Solution 5: Simplify Problem Structure”Remove advanced features temporarily to isolate the issue:
# Minimal config (seasonality only):python runme.py --multiperiod --multiperiod-weeks 4
# Add a single ramp constraint:python runme.py --multiperiod --multiperiod-weeks 4 --ramp-abs 20000
# Add a second ramp type (use with care):python runme.py --multiperiod --multiperiod-weeks 4 --ramp-abs 20000 --ramp-pct 0.25Test convergence at each step. Identifies which feature combination causes failure.
Understanding Constraint Ratios
Section titled “Understanding Constraint Ratios”The optimization problem has:
- Variables: Budget allocations (n_periods × n_channels)
- Constraints: Rules the solution must satisfy
Constraint-to-variable ratio:
- < 1.0: Under-constrained (many feasible solutions) ✅ Easy
- 1.0-1.5: Well-constrained (goldilocks zone) ✅ Good
- 1.5-2.5: Moderately over-constrained ⚠ Challenging
- > 2.5: Heavily over-constrained ❌ Often fails
Example calculation:
15 channels × 4 periods = 60 variables
Constraints:
- Budget equality: 1
- Channel bounds: 2 × 15 = 30
- Absolute ramps: 2 × 3 × 15 = 90
- Percentage ramps: 2 × 3 × 15 = 90
- Total: 211 constraints
Ratio: 211 / 60 = 3.5× → Will likely fail
(Advanced) If using adstock-aware state dynamics, this adds additional coupling between periods and can make the problem harder to solve.
Fix: Remove both ramp types → 76 constraints → 1.3× ratio → Should converge
Best Practices for Constraint Design
Section titled “Best Practices for Constraint Design”-
Start simple, add incrementally
Terminal window # Week 1: Test basicpython runme.py --multiperiod --multiperiod-weeks 4# Week 2: Add a single ramp constraintpython runme.py --multiperiod --multiperiod-weeks 4 --ramp-abs 20000# Week 3: Add a second ramp type (optional)python runme.py --multiperiod --multiperiod-weeks 4 --ramp-abs 20000 --ramp-pct 0.25 -
Choose features for your use case
- Accuracy priority: Seasonality with relaxed constraints (no ramps)
- Operational realism: Single ramp type + seasonality
- Maximum control (advanced): Both ramps + per-period budget limits (requires direct Python API)
-
Monitor solver output
[!] CONSTRAINT COMPLEXITY ANALYSISVariables: 120 (8 periods × 15 channels)Constraints: 256Ratio: 2.1× (>1.5 = challenging, >2.5 = very difficult)⚠ WARNING: Problem is moderately constrainedConvergence may be slow or failIf you see ratio >2.0, expect convergence issues.
-
Test before production
- Run with
--multiperiod-weeks 4first - Verify convergence with small problem
- Scale up gradually
- Run with
When All Else Fails
Section titled “When All Else Fails”If you’ve tried all solutions and still cannot converge:
-
Disable multi-period mode entirely
Terminal window # Fall back to single-period optimizationpython runme.py # No --multiperiod flag -
Use scenario planning instead
Terminal window # Test multiple budget levels without optimizationpython runme.py --scenarios "-10,-5,0,5,10" -
Manual budget allocation
- Export
response_curve_fit_combined.csv - Manually allocate budget using response curves
- Not optimal but guaranteed feasible
- Export
-
Report to developers
- Open GitHub issue with your configuration
- Include error message and
--multiperiodflags used - Developers may improve solver for your scenario
Advanced Topics
Section titled “Advanced Topics”Channel-Specific Budget Bounds
Section titled “Channel-Specific Budget Bounds”Currently all channels use the same bound percentages (±20%). To customize per-channel:
# Directly call core function with custom boundsfrom src.core.opt import optimize_multiperiod_budget_distribution
budget_ranges = { 'google': (5000, 25000), # Google: £5K-£25K per period 'facebook': (10000, 50000), # Facebook: £10K-£50K per period 'tv': (50000, 200000) # TV: £50K-£200K per period}
optimized_budget = optimize_multiperiod_budget_distribution( method='sigmoid', total_budget=12_500_000, budget_ranges=budget_ranges, parameters=sigmoid_params, channels=list(budget_ranges.keys()), n_periods=13, seasonal_effects=seasonal_effects)Equal Budget Per Period
Section titled “Equal Budget Per Period”To force equal budget across all periods:
period_budget = total_budget / n_periodsperiod_budget_limits=(period_budget, period_budget) # Force exact amountWeekly vs Monthly Planning
Section titled “Weekly vs Monthly Planning”Weekly Planning (more granular):
- Pros: Accounts for within-month variation, precise allocation
- Cons: More variables (slower), requires weekly data
Monthly Planning (aggregated):
- Pros: Faster optimization, simpler to execute
- Cons: Misses intra-month patterns, less flexible
Best Practices
Section titled “Best Practices”- Start with fewer periods - Test with 4-8 periods before running full year
- Validate seasonality - Check Prophet components make sense for your business
- Compare with/without seasonality - See impact of seasonal adjustments
- Monitor constraint feasibility - Ensure constraints don’t over-restrict optimization
- Cross-validate results - Compare multi-period results with historical performance
Integration with Main Pipeline
Section titled “Integration with Main Pipeline”Multi-period optimization is integrated into runme.py and can be enabled via CLI:
# Enable multi-period mode (13 weeks, with seasonality)python runme.py --multiperiod
# Or modify runme.py directly for custom settingsSee runme.py for full integration example.
Examples in Code
Section titled “Examples in Code”See test_multiperiod.py for detailed examples testing:
- Basic multi-period optimization
- With seasonality adjustments
- With per-period budget constraints
- Validation of results
Run tests:
python test_multiperiod.pyFurther Reading
Section titled “Further Reading”- Optimisation Guide - Single-period budget optimization
- Prophet Documentation - Seasonality configuration
- Configuration Guide - Setting up your model config
Support
Section titled “Support”For issues or questions:
- Check the Troubleshooting section above
- Review GitHub Issues
- Create new issue with reproducible example