This document describes fixed and flexible budget scenarios and gives example
code to help you understand how to customize your budget optimization.

## Fixed budget: Maximize ROI or incremental KPI

In this scenario for a given budget, you can find the optimal allocation across
channels and tactics to maximize the return on investment (ROI). If `kpi_type`
is set to non-revenue and `revenue_per_kpi` is unknown, the fixed budget
scenario finds the optimal allocation to maximize the incremental KPI.

This scenario represents the default settings and runs if you call the
`optimize()` method from the `BudgetOptimizer` class without any additional
arguments. There is also the option to modify the fixed budget scenario by
altering the optimization time window, inputting distinct budget and
pre-optimized media spend allocations, and indicating the lower and upper limits
for media expenditures.

### Example code

The following code example shows a customized fixed budget scenario:

    from meridian.analysis import optimizer
    from meridian.model import model

    mmm = model.Meridian(...)
    mmm.sample_posterior(...)
    budget_optimizer = optimizer.BudgetOptimizer(mmm)

    pct_of_spend = {
        'Channel0': 0.2,
        'Channel1': 0.2,
        'Channel2': 0.2,
        'Channel3': 0.1,
        'Channel4': 0.2,
        'Channel5': 0.1,
    }
    spend_constraint = {
        'Channel0': 0.3,
        'Channel1': 0.2,
        'Channel2': 0.3,
        'Channel3': 0.3,
        'Channel4': 0.3,
        'Channel5': 0.2,
    }

    build_channel_args = mmm.input_data.get_paid_channels_argument_builder()

    optimization_results = budget_optimizer.optimize(
          start_date='2023-01-16',
          end_date='2024-01-15',
          budget=70_000_000,
          pct_of_spend=build_channel_args(**pct_of_spend),
          spend_constraint_lower=build_channel_args(**spend_constraint),
          spend_constraint_upper=build_channel_args(**spend_constraint),
    )

### Key parameters

- `start_date` and `end_date`: Strings indicating the start and end dates for
  the duration to run the optimization on. Time dimensions should align with
  the Meridian time dimensions. By default, all time periods are used.

  **Example**

  To modify the date range:

      budget_optimizer = optimizer.BudgetOptimizer(mmm)
      # date range from week 100 to 200
      opt = budget_optimizer.optimize(
          start_date='2023-01-16', end_date='2024-01-15'
      )

- `budget`: Number indicating the total budget for the fixed budget scenario.
  Defaults to the historical budget.

  **Example**

  To change the budget to `70_000_000`:

      budget_optimizer = optimizer.BudgetOptimizer(mmm)
      opt = budget_optimizer.optimize(budget=70_000_000)

- `pct_of_spend`: Numeric list of size `n_paid_channels` containing the
  percentage allocations for channel spend. The order must match
  `(InputData.media + InputData.reach)` with values between 0-1, summing to
  `1`. By default, the historical allocation is used. Budget and allocation
  are used in conjunction to determine the non-optimized media-level spend,
  which is used to calculate the non-optimized performance metrics (for
  example, ROI) and construct the feasible range of media-level spend with the
  spend constraints.

  **Example**

  To specify the allocation:

      budget_optimizer = optimizer.BudgetOptimizer(mmm)
      opt = budget_optimizer.optimize(
          pct_of_spend=build_channel_args(
              Channel0=0.2,
              Channel1=0.2,
              Channel2=0.2,
              Channel4=0.1,
              Channel5=0.2,
              Channel6=0.1,
          ), # non-optimized allocation is 20% to Channel0, etc
      )

- `spend_constraint_lower`: Numeric list of length `n_paid_channels` to use
  different spend constraints for different channels, or a single `float`
  value as the same spend constraint for all media channels. If given as a
  channel-indexed array, the order must match
  `(InputData.media + InputData.reach)`. Indicates the lower bound of
  media-level spend. The lower bound of media-level spend is `((1 -
  spend_constraint_lower) * budget * allocation)`. The value entered must be
  between 0-1. The default value is 0.3 (meaning 70% of media spend) for a
  fixed budget and 1 (meaning 0% of media spend) for a flexible budget.

  **Example**

  To change the lower spend constraint:

      budget_optimizer = optimizer.BudgetOptimizer(mmm)
      build_channel_args = build_channel_args.with_default_value(0.3)
      opt = budget_optimizer.optimize(
          spend_constraint_lower=build_channel_args(
              Channel1=0.2,
              Channel5=0.2,
          ), # change the lower bound to 70%, 80%, etc of media spends
      )

- `spend_constraint_upper`: Numeric list of length `n_paid_channels` to use
  different spend constraints for different channels, or a single `float`
  value as the same spend constraint for all media channels. If given as a
  channel-indexed array, the order must match
  `(InputData.media + InputData.reach)`. Indicates the upper bound of
  media-level spend. The upper bound of media-level spend is `((1 +
  spend_constraint_upper) * budget * allocation)`. The default value is 0.3
  (meaning 130% of media spend) for a fixed budget and 1 (meaning 200% of
  media spend) for a flexible budget.

  **Example**

  To change the upper spend constraint:

      budget_optimizer = optimizer.BudgetOptimizer(mmm)
      build_channel_args = build_channel_args.with_default_value(0.3)
      opt = budget_optimizer.optimize(
          spend_constraint_upper=build_channel_args(
              Channel1=0.2,
              Channel5=0.2,
          ), # change the upper bound to 130%, 120%, etc of media spends
      )

## Flexible budget: Target minimum ROI

In this scenario for a flexible budget, you input a specific minimum ROI target.
This option lets you find out what is the maximum budget you can spend without
any budget limit until the overall ROI hits X at the total marketing level.
Meridian provides a recommended maximum budget, as well as an optimal
allocation of the budget across media channels, to achieve that ROI target.
Notably, the target ROI constraint is applied at the total marketing ROI level
and not at channel level.

### Example code

The following code example shows a customized flexible budget scenario with a
target minimum ROI:

    from meridian.analysis import optimizer
    from meridian.model import model

    mmm = model.Meridian(...)
    mmm.sample_posterior(...)
    budget_optimizer = optimizer.BudgetOptimizer(mmm)
    optimization_results = budget_optimizer.optimize(
          start_date='2023-01-16',
          end_date='2024-01-15',
          fixed_budget=False,
          spend_constraint_lower=0.5,
          spend_constraint_upper=0.5,
          target_roi=1,
    )

### Key parameters

- `fixed_budget`: Boolean indicating whether it is a fixed budget optimization
  or flexible budget optimization. Defaults to `True`, indicating a fixed
  budget. If `False`, you must also specify either `target_roi` or
  `target_mroi`.

- `target_roi`: Float indicating the target ROI constraint. Only used for
  flexible budget scenarios. The budget is constrained to when the ROI of the
  total media spend hits `target_roi`.

  **Example**

  To switch to flexible budget scenario and set the target ROI to `1`:

      budget_optimizer = optimizer.BudgetOptimizer(mmm)
      opt = budget_optimizer.optimize(fixed_budget=False, target_roi=1)

- `start_date` and `end_date`: Strings indicating the start and end dates for
  the duration to run the optimization on. Time dimensions should align with
  the Meridian time dimensions. By default, all time periods are used.

- `pct_of_spend`: Numeric list of size `n_paid_channels` containing the
  percentage allocation for the media spend. The order must match
  `(InputData.media + InputData.reach)` with values between 0-1, summing to
  `1`. By default, the historical allocation is used. Budget and allocation
  are used in conjunction to determine the non-optimized media-level spend,
  which is used to calculate the non-optimized performance metrics (for
  example, ROI) and construct the feasible range of media-level spend with the
  spend constraints.

- `spend_constraint_lower`: Numeric list of length `n_paid_channels` to use
  different spend constraints for different channels, or a single `float`
  value as the same spend constraint for all media channels. If given as a
  channel-indexed array, the order must match
  `(InputData.media + InputData.reach)`. Indicates the lower bound of
  media-level spend. The lower bound of media-level spend is `((1 -
  spend_constraint_lower) * budget * allocation)`. The value entered must be
  between 0-1. The default value is 0.3 (meaning 70% of media spend) for a
  fixed budget and 1 (meaning 0% of media spend) for a flexible budget.

- `spend_constraint_upper`: Numeric list of length `n_paid_channels` to use
  different spend constraints for different channels, or a single `float`
  value as the same spend constraint for all media channels. If given as a
  channel-indexed array, the order must match
  `(InputData.media + InputData.reach)`. Indicates the upper bound of
  media-level spend. The upper bound of media-level spend is `((1 +
  spend_constraint_upper) * budget * allocation)`. The default value is 0.3
  (meaning 130% of media spend) for a fixed budget and 1 (meaning 200% of
  media spend) for a flexible budget.

## Flexible budget: Target marginal ROI

In this scenario for a flexible budget, you input a marginal ROI target for
incremental spend. This option tells what is the maximum budget you can spend
without any budget limit until the marginal ROI of each media channel hits the
target marginal ROI. It's still possible that the marginal ROI of some channels
won't be equal to the target marginal ROI due to the feasible range of media
spend. However, the marginal ROI will effectively shrink toward the target
marginal ROI. Notably, the target marginal ROI is applied at channel level and
not at the total marketing level.

### Example code

The following code example shows a customized flexible budget scenario with a
target marginal ROI:

    from meridian.analysis import optimizer
    from meridian.model import model

    mmm = model.Meridian(...)
    mmm.sample_posterior(...)
    budget_optimizer = optimizer.BudgetOptimizer(mmm)
    optimization_results = budget_optimizer.optimize(
          start_date='2023-01-16',
          end_date='2024-01-15',
          fixed_budget=False,
          spend_constraint_lower=0.5,
          spend_constraint_upper=0.5,
          target_mroi=1,
    )

### Key parameters

- `fixed_budget`: Boolean indicating whether it is a fixed budget optimization
  or flexible budget optimization. Defaults to `True`, indicating a fixed
  budget. If `False`, you must specify either `target_roi` or `target_mroi`.

- `target_mroi`: Float indicating the target marginal ROI constraint. Only
  used for flexible budget scenarios. The budget is constrained to when the
  marginal ROI of the total media spend hits `target_mroi`.

  **Example**

  To switch to flexible budget scenario and set the target marginal ROI to
  `1`:

      budget_optimizer = optimizer.BudgetOptimizer(mmm)
      opt = budget_optimizer.optimize(fixed_budget=False, target_mroi=1)

- `start_date` and `end_date`: Strings indicating the start and end dates for
  the duration to run the optimization on. Time dimensions should align with
  the Meridian time dimensions. By default, all time periods are used.

- `pct_of_spend`: Numeric list of size `n_paid_channels` containing the
  percentage allocation for the media spend. The order must match
  `(InputData.media + InputData.reach)` with values between 0-1, summing to
  `1`. By default, the historical allocation is used. Budget and allocation
  are used in conjunction to determine the non-optimized media-level spend,
  which is used to calculate the non-optimized performance metrics (for
  example, ROI) and construct the feasible range of media-level spend with the
  spend constraints.

- `spend_constraint_lower`: Numeric list of length `n_paid_channels` to use
  different spend constraints for different channels, or a single `float`
  value as the same spend constraint for all media channels. If given as a
  channel-indexed array, the order must match
  `(InputData.media + InputData.reach)`. Indicates the lower bound of
  media-level spend. The lower bound of media-level spend is `((1 -
  spend_constraint_lower) * budget * allocation)`. The value entered must be
  between 0-1. The default value is 0.3 (meaning 70% of media spend) for a
  fixed budget and 1 (meaning 0% of media spend) for a flexible budget.

- `spend_constraint_upper`: Numeric list of length `n_paid_channels` to use
  different spend constraints for different channels, or a single `float`
  value as the same spend constraint for all media channels. If given as a
  channel-indexed array, the order must match
  `(InputData.media + InputData.reach)`. Indicates the upper bound of
  media-level spend. The upper bound of media-level spend is `((1 +
  spend_constraint_upper) * budget * allocation)`. The default value is 0.3
  (meaning 130% of media spend) for a fixed budget and 1 (meaning 200% of
  media spend) for a flexible budget.