Skip to content

chc.allocation

Stability tier: experimental.

A media budget spread over channels: one spend a period for each channel, within its box, for the most return, carryover counted in and out.

allocate plans periods periods of spend on chc.response.Channel\ s. Each channel runs at one rate a period, between its lower and upper rates, and the rates spend the budget. A plan's worth is what its spend returns on the channels: the adstock the history leaves runs into the plan's periods, and the plan's spend runs on for each kernel's length after them with nothing spent, so a channel whose carryover is long is not undervalued for the periods the plan ends before its return does.

Exact where every curve is concave. A channel's worth is its coefficient times the curve summed over periods whose adstock is affine in the rate, so it is concave in the rate when the curve is concave and the coefficient not negative, and the channels add. At a price mu per currency unit each channel then runs where the slope of its worth meets periods * mu, or at an end of its box, and mu is where the rates spend the budget. The price moves every rate the same way, so a bisection on it is exact; where a channel is linear its rate jumps at one price, and the plan spends the last of the budget on the rates either side of it, both of which are best there.

An S-shaped curve is planned by branch and bound. Where a curve starts convex the worth is not concave, and the bisection's plan on each curve's concave envelope (chc.response.Envelope) returns less on the curves than the best: two Hill curves of slope 3 and scales 1 and 1.01 at a budget of 1.6 are planned 1.27/0.33 for 0.705 there, where 1.6 on the first returns 512/637, 0.804. So the rates are searched in boxes (Udell and Boyd 2016). A box of a channel's rate is an interval of each period's adstock, and the curve's envelope over that interval is the chord from its floor to where it touches the curve, or to its cap where that comes first, and the curve beyond: concave in the rate, above the worth, and equal to it at both ends. The bisection on those envelopes bounds every plan in the box, and its plan, read on the curves, is a plan. The box of the largest bound is cut first, on the channel whose envelope stands furthest above its curve at the plan, at its rate, where both halves' envelopes meet the curve. Allocation.bound is the largest bound left when every bound is within the search's tolerance of the best plan's worth, or once the next cut would plan past max_boxes boxes, 500 by default; on concave curves it is the worth. The tolerance is the caller's: the larger of atol and rtol of the first box's bound, a share 1e-9 by default, but never below 64 epsilons of the dtype the curves are read in, of that bound, where the gap is rounding that no cut closes. Allocation.stopped says which, Allocation.limit what stopped the search, and Allocation.boxes how many boxes were planned; a search the cap stops logs a warning (chc_event="allocation_cap") with its gap. Rates that jump at one price, on chords of one slope, are filled one at a time, so at most one channel is left inside its chord, where the curve is below the envelope. With kernels of length one, floors at zero and caps past the tangencies, that bounds the gap of the first box before any is cut: the largest periods * coefficient * nonconvexity (chc.response.Saturation.nonconvexity), Shapley and Folkman's lemma for one constraint (Aubin and Ekeland 1976; Udell and Boyd 2016). It is all but reached: four equal Hill curves of slope 3 at three scales leave a gap of 0.1544 against 0.1547.

The price is the budget's shadow price on the planned channels: the return one more currency unit of budget buys, spread the way the plan spends it. It is where the channels running inside their boxes meet; a channel at its cap returns more a unit and one at its floor less. On S-shaped curves it is read on the curves at the plan, in the caller's box: the slope of the channel below its cap where one more unit returns the most, the same in whichever box the search found the plan.

A goal in place of a budget. budget_for finds the budget that meets a goal, and plans it: the least budget that gains a return (ReturnTarget), the budget at which one more unit returns a given amount (MarginalReturnTarget), or the most budget whose plan returns a given amount a unit on average, a target return on ad spend (ReturnOnSpendTarget). A plan's gain is what its spend adds, worth - idle, where idle is what the channels return with nothing spent in the plan: the history's carryover alone. As the price falls every rate rises, so the plans for every budget lie on one path, and each goal is a point on it. On S-shaped curves each plan is allocate's search at its budget; those plans lie on no one path, and the budget is found over them.

A split for several readings of the channels. Tests that never bent a curve fit several families alike, and the families part where the plan goes. minimax_allocate takes each reading of every channel and chooses the split whose worst regret over them is least: the regret under a reading is its best return at the budget over the split's, both on its curves, the best its allocate plan's. A reading's regret is convex in the split where its curves are concave, so the worst is too, and cutting planes (Kelley 1960) close on it from below while the splits they propose close on it from above; the two ends are the certificate. The worst regret is the least of the readings' gains over their bests, negated, so it is the worst share below at one reading, each gain read against the reading's best, and on S-shaped curves it is searched in boxes as that share is.

A split that gains in the worst share of a posterior's draws. A fit's draws are readings too, many and alike, and over them the worst case is one draw. cvar_allocate takes the readings and a reference split, the plan in place, and chooses the split whose mean gain over the reference in the worst level share of the readings is the most: the gain's conditional value at risk (Rockafellar and Uryasev 2000). Every gain is read on the curves, and the reference gains nothing under any reading, so the split never does worse in that share than keeping it; at level = 1 it is the split for the mean return. The share's mean gain is concave in the split where the curves are, and the same cutting planes close on it from above. On S-shaped curves the rates are searched in boxes, as allocate searches them: in each box the planes are the tangents of each curve's envelope over the box, which stand above the curve, so they bound the share's mean gain in the box from above, and each split they propose, read on the curves, is a candidate, the reference among them. Every reading's curves of a channel are read and bounded together, by one compiled program for each structure of curve among them.

What a wrong channel costs the plan. A plan made on channels whose parameters are off by d loses d' W d / 2 of the worth the true channels' plan returns, to second order: decision_weight is that W, the decision weight of an experiment's value of information (chc.experiment) with this allocation as the decision. The rates inside their boxes meet at one price, so an error moves them along the budget, each by its slope's response to the error, less the share of the whole that keeps the budget spent; W weighs those moves by each worth's curvature. An experiment that leaves the parameters with covariance S leaves an expected regret of tr(W S) / 2 (AllocationWeight.expected_regret).

Geos and channels together. allocate_geos plans one budget over a grid of cells, every geo's channels, each cell a channel of its own with its curve, carryover and history. Besides each cell's box, Totals bound what each geo spends over the plan, and what each channel spends across the geos. At the best plan a cell inside its box returns, a currency unit, the budget's price plus its geo's and its channel's: a total's price is what one more currency unit of room in it returns, positive where it binds at its most, negative at its least, nothing where it does not bind. A plan made in two steps, the budget split over the geos first and each geo's share over its channels after, is one plan of the grid, so it never returns more. The best plan is found by cutting planes on the cells' worths (Kelley 1960) under a linear program, whose value bounds every plan from above while the plans it proposes approach from below; where every cell inside its box is strictly concave, Newton's method on the binding totals' prices then makes the plan exact. The program's duals name the totals that bind only to within its gap, so a total a hair from binding may be named wrongly: one whose price comes out on the wrong side is released and one the plan breaks is bound, until the plan meets the conditions that make it the best. On S-shaped curves the rates are searched in boxes, as allocate searches them, the totals rows of each box's program: the planes on each cell's envelope over the box bound every plan in it, and their plan, read on the curves, is a plan. budget_for_geos finds the budget that meets a goal over the grid: on concave curves the best plan's gain is concave in the budget and its slope is the budget's price, so Newton's method on the budget meets a return target or a return on spend in a few plans, and a marginal target is one plan, the budget left free and each unit of it charged the target's return.

A channel bought nationally. A channel bought once for every geo, as national media allotted by population is, has one spend a period, of which each geo's cell spends a fixed share (National). Both geo planners then choose that national spend, within its own box, in place of its cells' rates. Its worth is its cells' summed, each reading its share, so it is concave where their curves are; it counts in the budget and in its channel's total whole, and in each geo's total by the geo's share. Inside its box it returns, a currency unit, the budget's price plus its channel's and each geo's weighted by the geo's share. On S-shaped cells its worth over a box of the national spend is bounded by its cells' envelopes over the box, summed: concave, above the worth and equal to it at both ends of the box, so the box search cuts it as it cuts a cell.

HONEST SCOPE:

  • The channels are read as given: a fitted channel's error goes straight into the plan. Where the channels come from an experiment (chc.lift.fit_lift), a plan that runs a channel past the adstock the tests covered reads the family's shape, not the experiment.
  • One rate a period for each channel, not a schedule: with a response that does not change with the period, only the history's carryover at the start and the tail at the end make periods differ.
  • The tail is counted with nothing spent after the plan. Spend that continues after it would take the tail further up the curve, so a long kernel's tail is valued at its most.
  • chc.response.Ricker is not monotone, and a negative coefficient turns a concave curve convex; the bisection proves nothing for either, and both are refused.
  • On an S-shaped curve the best plan's gain rises with the budget but need not be concave in it, so a goal is met over searched plans by Brent's method on the budget: a return target where the gain crosses it, the least budget to the search's tolerance, and a target return on ad spend where the gain less the target a unit falls through nothing past its peak, not proved the most.
  • On S-shaped curves every plan and split here is the best only to the search's tolerance, a share 1e-9 of its scale by default and never finer than 64 epsilons of the dtype, or as near as max_boxes boxes come, 500 by default; a goal's, at each budget it tries. Sums of S-shaped curves under a budget are NP-hard to plan (Udell and Boyd 2016), so nothing short of that cap bounds the count of boxes, and many channels near their thresholds at once can reach it; the gap is then reported as it stands. Each box of allocate's costs one bisection of the price, as a plan on concave curves does; each of cvar_allocate's and minimax_allocate's up to 30 rounds of planes, every one a read of each reading on its envelopes and its curves and a linear program over the planes kept, one row a reading for every split tried; each of allocate_geos' as many rounds of planes as close it, up to 500, every one a linear program over the planes on its cells and the totals. The bound on the first box's gap holds only as stated above: a longer kernel runs each period at its own adstock, and a floor or a cap inside a chord holds its channel there, each with an excess of its own.
  • A split for several readings is robust to the readings it is given and to no other: it hedges between the families the tests could not tell apart, not against one none of them is.
  • A split for the worst share weighs every reading alike, as draws of a posterior are, and its share is of the readings, a probability only as far as they are a posterior's draws. Where they disagree on which way the budget should move, no move may gain on average in the worst share, and the split is then the reference.
  • The decision weight is local: second order in the error, at the plan on the channels as given, and a channel held at an end of its box carries no weight. One that would leave its end under a small error costs more than W says, as chc.experiment's pinned levers do. It is read on concave curves only.
  • chc.mmm.prescribe plans a budget over a continuous plant whose adstock is a state; this plans discrete channels, which that plant does not describe.
  • Over geos and channels, a plan with a cell inside its box on a straight stretch of its curve (a linear curve, or an envelope's chord in a box of a search) is the cutting planes', within the search's tolerance of the bound or as near as 500 rounds come; its rates are as near the best as that gap allows, not exact to rounding, and its prices are the last program's duals, which certify the bound but need not be the best plan's where the program is degenerate and several prices nearly certify it. A searched plan is priced on the curves at the plan instead, by the prices that miss the conditions for the best plan least, the totals' misses first: its rates meet their prices only to the search's tolerance. In a search Newton's method on the prices stops at its first step that does not shrink the totals' miss: a chord holds its cell at an end of it at every price but its slope, and at that price the rate jumps across it, which a line search would circle. Where every geo's spend and every channel's are fixed, the split of the price between the geos' totals and the channels' is the one the solver reaches, or on S-shaped curves the one least in the sum of the prices' sizes; the sum each cell meets is the same however it is split. In single precision, JAX's default, Newton's method does not close and the cutting planes' plan comes back: its worth and bound are good to single precision, its rates only as near as the gap allows.
  • A goal over geos and channels on an S-shaped curve is met where the searched plans' gain crosses it, not proved the least or the most budget: under totals the best plan's gain need not rise with the budget.
  • A national channel's shares are the caller's and held fixed: the plan chooses how much to buy, not where it lands. Its bound over a box of its spend, the sum of its cells' envelopes, can stand above the sum's own envelope, so on S-shaped cells it may take more boxes than one cell's.

SearchStatus = Literal['closed', 'cap'] module-attribute

Why a plan's search for the best stopped: closed, its bound within the search's tolerance of its worth; cap, with the gap open at a limit (SearchLimit).

SearchLimit = Literal['max_boxes', 'rounds', 'unsolved'] module-attribute

What stopped a search with its gap open: max_boxes, the caller's cap, where the next box's split would plan past it; rounds, the 500 rounds of a box's cutting planes where no cut of the box is left to close it: the one box of concave curves of cvar_allocate, minimax_allocate and allocate_geos, or a box of allocate_geos' whose plan holds every cell at an end of its interval; unsolved, a linear program HiGHS left unsolved there, at its iteration limit or with its optimality conditions unmet, which ends that box's planes.

Allocation dataclass

A plan's spend a period for each channel, what it returns, and what the budget is worth.

Attributes:

Name Type Description
spend ndarray

(channels,) spend a period, in the budget's currency.

worth float

the channels' return on the plan over its periods and each kernel's length after.

bound float

no plan in the box at the budget returns more on the channels; bound - worth bounds the plan's shortfall from the best, and is 0 when every curve is concave. On S-shaped curves it is the largest bound allocate's search left.

price float

the return on one more currency unit of budget: on concave curves the budget's shadow price, on S-shaped curves read on the curves at the plan, where the unit goes to the channel below its cap that returns the most a unit.

budget float

what the plan spends over its periods.

idle float

what the channels return over the same periods with nothing spent in the plan, the history's carryover alone.

stopped SearchStatus

whether bound - worth closed, or the search stopped at its cap (SearchStatus).

boxes int

how many boxes of the rates were planned: 1 for a plan on concave curves.

gap float

bound - worth.

relative_gap float

gap as a share of the first box's bound, the scale rtol is a share of, or of 1 where that bound is 0.

tolerance float

the gap the search closes at: the larger of atol and rtol of the first box's bound, but not below 64 epsilons of the dtype the curves are read in, of that bound.

floored bool

whether that floor, the dtype's rounding, raised the tolerance above the caller's.

limit SearchLimit | None

what stopped a search with its gap open (SearchLimit); None where the gap closed.

compile_seconds float

the seconds JAX spent compiling during the call: tracing, lowering and the backend's compile, none where every program was compiled before.

search_seconds float

the rest of the call's seconds. Both are recorded, not promised.

gain property

What the plan's spend adds to the channels' return: worth - idle.

ReturnTarget dataclass

The least budget whose plan gains amount, in the channels' units.

MarginalReturnTarget dataclass

The budget at which one more currency unit returns per_unit: below it a unit returns more, past it less. On revenue, per_unit = 1 is where spend stops paying for itself.

ReturnOnSpendTarget dataclass

The most budget whose plan gains per_unit for each currency unit it spends: a target return on ad spend, read on the gain.

MinimaxAllocation dataclass

A split chosen for the least worst regret over several readings of the channels.

Attributes:

Name Type Description
spend ndarray

(channels,) spend a period, in the budget's currency.

best ndarray

(readings,) each reading's best return at the budget on its curves: the worth of allocate's plan for it, exact on concave curves and the best to that search's tolerance on S-shaped ones.

regret ndarray

(readings,) each reading's best return over the split's, both on its curves, were it true.

worst float

the largest regret, the split's.

bound float

no split in the box at the budget has a worst regret below this, so worst - bound bounds how far the split is from the least worst regret. On S-shaped curves it is the least bound the search left.

stopped SearchStatus

whether worst - bound closed to the search's tolerance, or the search stopped at its cap (SearchStatus).

boxes int

how many boxes of the rates were searched: 1 where every curve is concave.

gap float

worst - bound.

relative_gap float

gap as a share of the largest of best, the scale rtol is a share of, or of 1 where every best return is 0.

tolerance float

the gap the search closes at: the larger of atol and rtol of the largest best return, but not below 64 epsilons of the dtype the curves are read in, of that return.

floored bool

whether that floor, the dtype's rounding, raised the tolerance above the caller's.

limit SearchLimit | None

what stopped the search with its gap open (SearchLimit); None where the gap closed.

unsolved tuple[int, ...]

the iterations of each linear program HiGHS left unsolved, in the order they were solved; each left its box the bound of the programs before it.

compile_seconds float

the seconds JAX spent compiling during the call, each reading's plan included: tracing, lowering and the backend's compile, none where every program was compiled before.

search_seconds float

the rest of the call's seconds. Both are recorded, not promised.

CvarAllocation dataclass

A split chosen for the most mean gain over a reference in the worst share of the readings.

Attributes:

Name Type Description
spend ndarray

(channels,) spend a period, in the budget's currency.

gain ndarray

(readings,) each reading's return on the split less its return on the reference, both on its curves.

cvar float

the mean of the worst level share of gain, the split's; never below 0, which is the reference's own.

bound float

no split in the box at the budget has a cvar above this, so bound - cvar bounds how far the split is from the best. On S-shaped curves it is the largest bound the search left.

stopped SearchStatus

whether bound - cvar closed to the search's tolerance, or the search stopped at its cap (SearchStatus).

boxes int

how many boxes of the rates were searched: 1 where every curve is concave.

gap float

bound - cvar.

relative_gap float

gap as a share of the reference's largest return, the scale rtol is a share of, or of 1 where every return on the reference is 0.

tolerance float

the gap the search closes at: the larger of atol and rtol of the reference's largest return, but not below 64 epsilons of the dtype the curves are read in, of that return.

floored bool

whether that floor, the dtype's rounding, raised the tolerance above the caller's.

limit SearchLimit | None

what stopped the search with its gap open (SearchLimit); None where the gap closed.

unsolved tuple[int, ...]

the iterations of each linear program HiGHS left unsolved, in the order they were solved; each left its box the bound of the programs before it.

readings int

how many readings the split was chosen over.

compile_seconds float

the seconds JAX spent compiling during the call: tracing, lowering and the backend's compile, none where every program was compiled before.

search_seconds float

the rest of the call's seconds. Both are recorded, not promised.

AllocationWeight dataclass

How much a plan loses when the channels it is made on are wrong. Experimental.

Attributes:

Name Type Description
parameters tuple[str, ...]

(p,) each parameter's place, the channel's position and the parameter's name in it, "1.curve.scale".

matrix ndarray

(p, p) the regret's Hessian W in the parameters: a plan made on parameters off by d returns d' W d / 2 less than the plan on the channels as given, to second order.

allocation Allocation

the plan on the channels as given.

pinned tuple[int, ...]

the channels held at an end of their box, whose parameters carry no weight.

expected_regret(covariance)

tr(W S) / 2 at covariance = S: what a plan made on estimates with that covariance loses on average, to second order.

Raises:

Type Description
ValueError

when covariance is not (p, p).

Totals dataclass

Bounds on what each of several groups of cells spends over the plan, every period together: least[k] <= periods * (group k's rates summed) <= most[k].

allocate_geos takes one for the geos, a geo's channels a group, and one for the channels, a channel across the geos a group. A fixed spend is least == most; most may be infinite, and a least of zero binds nothing a box does not.

Raises:

Type Description
ValueError

on bounds that are not one least and one most a group, with 0 <= least <= most and least finite.

National dataclass

A channel bought once for every geo: one spend a period, of which each geo's cell spends its share, as national media allotted by population is.

allocate_geos and budget_for_geos take one for each national channel, keyed by the channel's column in the grid. The plan chooses the national spend a period, between lower and upper; geo g's cell of the channel spends shares[g] of it and reads that on its own curve, carryover and history. The cells have no box of their own.

Attributes:

Name Type Description
shares ndarray

(geos,) each geo's share of the national spend, held as given.

lower, upper

the least and the most national spend a period.

Raises:

Type Description
ValueError

shares that are not one a geo, each positive and finite, summing to 1 within as many epsilons as there are shares: rounding, as shares a population divides into leave; bounds that are not finite with 0 <= lower <= upper.

GeoAllocation dataclass

A plan's spend a period for each geo's channels, what it returns, and what each constraint is worth.

Attributes:

Name Type Description
spend ndarray

(geos, channels) spend a period, in the budget's currency. A national channel's column holds each geo's share of its national spend, which the column sums to, to rounding.

worth float

the cells' return on the plan over its periods and each kernel's length after.

bound float

no plan meeting the constraints and spending as much returns more on the cells; bound - worth bounds the plan's shortfall from the best, and is 0 when every curve is concave and the plan is exact. On S-shaped curves it is the largest bound allocate_geos' search left.

price float

the return on one more currency unit of budget; 0 where every geo's total is fixed, or every channel's, which fixes the budget and carries its price. On S-shaped curves it and the totals' prices are read on the curves at the plan: the prices that meet the conditions for the best plan there, or miss them least, the budget's nearest nothing and then the totals' least; one geo with no totals is priced as allocate prices its channels.

geo_prices ndarray

(geos,) the return on one more currency unit of room in each geo's total: positive where the total binds at its most, negative at its least, 0 where it does not bind or no geo totals were given.

channel_prices ndarray

(channels,) the same for each channel's total across the geos, a national channel's its national spend over the plan.

budget float

what the plan spends over its periods.

idle float

what the cells return over the same periods with nothing spent in the plan, the history's carryover alone.

stopped SearchStatus

whether bound - worth closed to the search's tolerance, or the search stopped at a limit (SearchStatus).

boxes int

how many boxes of the rates were planned: 1 where every curve is concave.

gap float

bound - worth.

relative_gap float

gap as a share of the first box's bound, the scale rtol is a share of, or of 1 where that bound is 0.

tolerance float

the gap the search closes at: the larger of atol and rtol of the first box's bound, but not below 64 epsilons of the dtype the curves are read in, of that bound.

floored bool

whether that floor, the dtype's rounding, raised the tolerance above the caller's.

limit SearchLimit | None

what stopped the search with its gap open (SearchLimit); None where the gap closed.

compile_seconds float

the seconds JAX spent compiling during the call: tracing, lowering and the backend's compile, none where every program was compiled before.

search_seconds float

the rest of the call's seconds. Both are recorded, not promised.

gain property

What the plan's spend adds to the cells' return: worth - idle.

allocate(channels, budget, periods, *, lower, upper, history=None, rtol=1e-09, atol=0.0, max_boxes=500)

Spend budget over periods at one rate a period for each channel, for the most return.

Exact where every curve is concave; on S-shaped curves the best to the tolerance rtol and atol set, by branch and bound (see the module).

Parameters:

Name Type Description Default
channels Sequence[Channel]

the channels, each read as given.

required
budget float

what the plan spends over its periods, every channel together.

required
periods int

how many periods the plan runs.

required
lower, upper

(channels,) each channel's least and most spend a period. There is no default: a box is where the channels were seen, and a plan outside it reads the curves where nothing measured them.

required
history ArrayLike | None

(T, channels) spend in the periods before the plan, whose adstock runs into it; nothing spent before the plan when omitted.

None
rtol float

the share of the first box's bound the search closes the gap to.

1e-09
atol float

the gap the search closes to, in the channels' units. The search stops at the larger of the two, but not below 64 epsilons of the dtype the curves are read in, of the first box's bound: there the gap is rounding (Allocation.floored).

0.0
max_boxes int

the most boxes the search plans. A box is cut only where its halves fit within the count.

500

Raises:

Type Description
TypeError

a channel is not a chc.response.Channel.

ValueError

a curve the bisection cannot certify on (see the module's scope), a negative coefficient, a box or history of the wrong shape, a box with lower > upper or a negative end, a budget the box cannot spend over the periods, an rtol or atol that is negative or not finite, or a max_boxes that is not a whole number of at least 1.

budget_for(channels, goal, periods, *, lower, upper, history=None, rtol=1e-09, atol=0.0, max_boxes=500)

The budget that meets goal, and the plan allocate makes with it.

A plan's gain is Allocation.gain, what its spend adds to the channels' return over its periods and each kernel's length after. Where every curve is concave, as the budget grows every rate rises along one path of plans, and each goal is a point on it:

  • ReturnTarget: the least budget whose plan gains amount. The gain rises along the path, so the budget is exact.
  • MarginalReturnTarget: the rates at which each channel's slope meets per_unit, or the end of its box it presses on. Where a channel's return is linear with that slope, its least rate.
  • ReturnOnSpendTarget: the most budget whose gain is per_unit times the budget or more. The gain less per_unit a unit is concave in the budget and peaks at the marginal target's budget, so past that budget it falls, and the most budget is where it falls through nothing. The average can rise before the peak, where a floor holds spend on a channel that returns little a unit, so it can meet the target at a smaller budget too; that one is not returned.

Where a curve is S-shaped the plans are allocate's search at each budget, and no one path holds them; the best plan's gain still rises with the budget, but it need not be concave in it. A return target is met by Brent's method on the budget, between the floors and the caps, where the searched plan's gain crosses it: the least budget, since that gain rises. A marginal target is the budget of the best plan with the budget free and each currency unit charged per_unit, searched in boxes as allocate searches a budget: no budget gains more less its charge, so below it a unit returns more on average and past it less. A return on spend is met where the searched gain less per_unit a unit falls through nothing past that peak, by Brent's method; refused where the peak falls short, but past the peak the gain need not fall all the way, and the budget is not proved the most.

Parameters:

Name Type Description Default
channels, periods, lower, upper, history

as allocate takes them.

required
goal Goal

what the budget must meet.

required
rtol, atol, max_boxes

each plan's search's, as allocate takes them.

required

Raises:

Type Description
TypeError

goal is none of the three, or a channel is not a chc.response.Channel.

ValueError

what allocate refuses of the channels, the box and the search's settings; a goal whose value is not finite; a gain beyond what the box returns at its caps; a return on spend that no budget in the box reaches, which the error says by how much it falls short at the peak.

minimax_allocate(readings, budget, periods, *, lower, upper, history=None, rtol=1e-09, atol=0.0, max_boxes=500)

Spend budget for the least worst regret over readings of the channels.

Each reading is a whole set of channels, one a column, as allocate takes them: the same channels read another way, by another curve family, say. The regret of a split under a reading is the reading's best return at the budget over the split's, both on its curves, the best being the return of allocate's plan for the reading. The split returned has the least worst regret, to the tolerance rtol and atol set, or as near as 500 rounds of cutting planes come on concave curves and max_boxes boxes of the rates past them; MinimaxAllocation.stopped says which, and MinimaxAllocation.limit what stopped it. A program HiGHS leaves unsolved, one it stalls on stopped by an iteration limit, leaves its box the bound of the programs before it.

The worst regret is the least of the readings' gains over their bests, negated: the worst share cvar_allocate maximises, at one reading of them, each gain read against the reading's best in place of a reference's return. It is searched as cvar_allocate searches that share, from every reading's own plan. A reading's regret is convex in the split where its curves are concave, so the worst is too: the cutting planes are every reading's tangents at every split tried, the linear program over them bounds the worst regret from below, and each split it proposes is tried next. Where a curve is S-shaped the rates are searched in boxes, each box's planes cut on each curve's envelope over the box, which stands above the curve, so they bound every split in the box. MinimaxAllocation.bound is read from HiGHS's duals on each program as written and rounded down, not HiGHS's objective, which can stand above the program's least; worst is the best split's, so the gap between them is what the split may still be from the least worst regret. A reading's own plan returns its best, so no worst regret is below nothing where each best is exact; on S-shaped curves, below the least of the readings' own searches' gaps, negated. A reading's own search that stops at its cap logs a warning as allocate does.

Parameters:

Name Type Description Default
readings Sequence[Sequence[Channel]]

the readings, each with one channel a column; at least one.

required
budget, periods, lower, upper, history

as allocate takes them.

required
rtol float

the share of the largest best return the search closes the gap to, and each reading's own plan's search the share of its first box's bound.

1e-09
atol float

the gap the search closes to, in the channels' units, and each reading's own plan's search. The search stops at the larger of the two, but not below 64 epsilons of the dtype the curves are read in, of the largest best return: there the gap is rounding (MinimaxAllocation.floored).

0.0
max_boxes int

the most boxes the search plans, and each reading's own plan's search. A box is cut only where its halves fit within the count.

500

Raises:

Type Description
(TypeError, ValueError)

what allocate refuses of any reading, the box, the history, the budget or the search's settings; no readings; readings of different numbers of channels.

cvar_allocate(readings, budget, periods, *, level, against, lower, upper, history=None, rtol=1e-09, atol=0.0, max_boxes=500)

Spend budget for the most mean gain over against in the worst level share of readings of the channels: the gain's conditional value at risk.

Each reading is a whole set of channels, one a column, as minimax_allocate takes them: a posterior's draws, say, weighed alike. A split's gain under a reading is its return less the reference's, both on the reading's curves, and the split returned has the most mean gain over the worst level share of the readings, to the tolerance rtol and atol set, or as near as 500 rounds of cutting planes come on concave curves and max_boxes boxes of the rates past them; CvarAllocation.stopped says which, and CvarAllocation.limit what stopped it. A program HiGHS leaves unsolved, one it stalls on stopped by an iteration limit, leaves its box the bound of the programs before it. The reference spends the budget in the box and gains nothing under any reading, so the split returned never does worse in that share than keeping it. At level = 1 the split has the most mean gain, the posterior's expected return; as the level falls it moves only as far as the worst readings agree it gains.

The mean of the worst share is max_eta eta - E[(eta - gain)_+] / level (Rockafellar and Uryasev 2000), concave in the split where every curve is concave. The cutting planes (Kelley 1960) are every reading's tangent at every split tried, starting from the reference, and the linear program over them bounds the most from above; its solution, moved onto the budget, is the next split tried.

Where a curve starts convex the rates are searched in boxes, as allocate searches them (Udell and Boyd 2016). In a box the planes are cut on each curve's envelope over the box, which stands above the curve and is concave in the rate, so the program bounds every split in the box, and each split it proposes, read on the curves, is a candidate. The box of the largest bound is cut first, on the channel whose envelope stands furthest above its curve at the box's best split on the envelopes, each reading weighed as the worst share weighs it there, since the share's mean gain on the envelopes exceeds the curves' by no more than that weighed excess (the risk envelope's dual). A box's planes stop after 30 rounds, or once their bound is within half the box's gap to the best split of its best on the envelopes, and the box is cut instead; a box's planes bound its halves too, so they carry over, those the last program left slack dropped. Two Hill curves of slope 3 at scales 1 and 1.01, a budget of 1.6 and the reference all on the first: on the envelopes the planes propose [1.27, 0.33], which returns 0.705 against the reference's 0.804, and the search closes on the reference, its cvar 0.

Parameters:

Name Type Description Default
readings Sequence[Sequence[Channel]]

the readings, each with one channel a column; at least one.

required
budget, periods, lower, upper, history

as allocate takes them.

required
level float

the share of the readings, the worst, whose mean gain the split maximises, in (0, 1]. There is no default: it is how much of the readings the plan may not lose on.

required
against ArrayLike

(channels,) the reference's spend a period, in the box and spending the budget: the plan the split must beat, the current one at this budget, say.

required
rtol float

the share of the reference's largest return the search closes the gap to.

1e-09
atol float

the gap the search closes to, in the channels' units. The search stops at the larger of the two, but not below 64 epsilons of the dtype the curves are read in, of the reference's largest return: there the gap is rounding (CvarAllocation.floored).

0.0
max_boxes int

the most boxes the search plans. A box is cut only where its halves fit within the count.

500

Raises:

Type Description
(TypeError, ValueError)

what allocate refuses of any reading, the box, the history or the search's settings; no readings; readings of different numbers of channels; a level outside (0, 1]; a reference of the wrong shape, outside the box or not spending the budget.

decision_weight(channels, budget, periods, *, lower, upper, history=None)

How much allocate's plan loses when the channels it is made on are wrong. Experimental.

The arguments are allocate's, and so is the plan. Each channel's parameters are its inexact leaves, the kernel's, the curve's and the coefficient. Inside their boxes the rates meet at the budget's price, V_c'(u_c) = periods * price; an error d_c in channel c moves its rate by g_c d_c / h_c, g_c the slope's gradient in its parameters and h_c the worth's curvature -V_c'', and the price moves every rate back by the share (1/h_c) / sum(1/h) of the total, so the budget stays spent. The first-order loss is nil, since the slopes are equal and the moves sum to nothing, and the second is du' diag(h) du / 2, which is d' W d / 2. validation/allocation_decision_weight.mac holds it to the Hessian of the realised loss for two exponential and two Michaelis-Menten channels.

Raises:

Type Description
(TypeError, ValueError)

as allocate; and ValueError on an S-shaped curve, where a small error can move the plan's spend from one channel to another at once, which no second-order weight reads, or a channel inside its box whose worth is not strictly concave at its rate.

allocate_geos(cells, budget, periods, *, lower, upper, geo_totals=None, channel_totals=None, national=None, history=None, rtol=1e-09, atol=0.0, max_boxes=500)

Spend budget over periods on every geo's channels at once, for the most return.

Each cell, one geo's channel, runs at one rate a period as allocate's channels do, its worth counted the same way, carryover in from its history and out after the plan. The budget is spent exactly; each geo's spend over the plan, its channels together, stays within geo_totals, and each channel's, its geos together, within channel_totals.

A channel bought once for every geo (National) is one rate, its national spend a period, within its own box: each geo's cell spends its share of it. The plan chooses that rate as it chooses a cell's. Its worth is its cells' summed, each reading its share, and it counts in the budget and its channel's total whole and in each geo's total by the geo's share. Inside its box it returns, a currency unit, the budget's price plus its channel's and each geo's weighted by the geo's share.

The plan is found by cutting planes on the cells' worths, each bounded above by its tangents under a linear program, until the program's bound is within the tolerance rtol and atol set of the best plan it has proposed, or after 500 rounds, or at a program HiGHS leaves unsolved, one it stalls on stopped by an iteration limit; a first program left so is refused. Where every cell inside its box is then strictly concave, Newton's method on the prices of the totals that bind closes the budget and those totals with each cell's rate exact at its prices; a step that does not bring them closer is replaced by the least of the dual, convex in the prices, along it. A total whose price comes out on the wrong side is released and one the plan breaks is bound, until each binding total's price has its side's sign and the others hold: the conditions for the best plan on concave worths. Otherwise the cutting planes' best plan is returned, with the least bound their programs gave. Each program's bound is read from HiGHS's duals and rounded up, not HiGHS's objective, which can fall below the program's most.

Where a curve is S-shaped the rates are searched in boxes, as allocate searches them (Udell and Boyd 2016). In a box each cell's worth is bounded by its envelope over the box, concave in the rate, above the curve and equal to it at both ends, and the planes on those envelopes bound every plan in the box, under the same totals; their plan, read on the curves, is a plan. The box of the largest bound is cut first, on the cell inside its interval whose envelope stands furthest above its curve at the box's plan, at its rate there, so that both halves hold that plan. GeoAllocation.bound is the largest bound left when every bound is within the tolerance of the best plan's worth, or once the next cut would plan past max_boxes boxes; GeoAllocation.stopped says which, and a search the cap stops logs a warning (chc_event="allocation_cap") with its gap. A national channel's worth over a box of its spend is bounded by its cells' envelopes over the box, summed, and it is cut as a cell is.

Parameters:

Name Type Description Default
cells Sequence[Sequence[Channel]]

one row a geo, each with the same channels in the same order.

required
budget, periods

as allocate takes them.

required
lower, upper

(geos, channels) each cell's least and most spend a period; nan in a national channel's column, whose cells have no box of their own.

required
geo_totals Totals | None

what each geo spends over the plan, its channels together; no bound when omitted.

None
channel_totals Totals | None

what each channel spends over the plan, its geos together; no bound when omitted.

None
national Mapping[int, National] | None

the channels bought once for every geo, each by its column in the grid; none when omitted.

None
history ArrayLike | None

(T, geos, channels) spend in the periods before the plan, whose adstock runs into it; nothing spent before the plan when omitted. A national channel's cells keep their own: each geo's share of the past need not be its share of the plan.

None
rtol, atol, max_boxes

as allocate takes them; the scale rtol is a share of is the first box's bound.

required

Raises:

Type Description
TypeError

a cell is not a chc.response.Channel, a total is not a Totals, national is not a mapping, or one of its keys not a column or its values not a National.

ValueError

what allocate refuses of a cell, its box, its history or the search's settings; rows of different lengths; totals of the wrong length, or one outside what its cells' boxes spend; a national channel the grid has no column for, or with shares not one a geo; a channel both national and given a box of its own in lower or upper; and a budget, boxes and totals no plan meets together.

budget_for_geos(cells, goal, periods, *, lower, upper, geo_totals=None, channel_totals=None, national=None, history=None, rtol=1e-09, atol=0.0, max_boxes=500)

The budget that meets goal over every geo's channels, and the plan allocate_geos makes with it.

The budgets run from the least to the most a plan within the boxes and the totals spends. On concave curves the best plan's gain is concave in the budget, and its slope there is the budget's price; each step below is one plan. Under totals the gain can fall past some budget: a cap on a geo and one on a channel can leave the last of the budget only to cells that return less than the spend they take from a cell in both.

  • ReturnTarget: the least budget whose plan gains amount, by Newton's method on the budget from the least, whose steps a concave gain keeps short of the target. Refused above the most any plan gains, the plan with nothing charged for its spend.
  • MarginalReturnTarget: one plan, its budget left free and each currency unit it spends charged per_unit of return: every cell inside its box then returns per_unit a unit plus its totals' prices, a national channel per_unit plus its channel's and each geo's weighted by its share, and the budget is what the plan spends, an end of the budgets where the price stays on one side of per_unit across them.
  • ReturnOnSpendTarget: the most budget whose gain is per_unit times the budget or more. The gain less per_unit a unit is concave in the budget and peaks where the price is per_unit, so past that budget it falls, and the most budget is where it falls through nothing: Newton's method from the most budget.

On an S-shaped curve each plan is allocate_geos' search at its budget, and the best plan's gain need not be concave in the budget: a return target and a return on spend are met where that gain crosses them, by Brent's method between budgets either side, and neither is proved the least or the most. The marginal target's plan is searched with its budget free.

Parameters:

Name Type Description Default
cells, periods, lower, upper, geo_totals, channel_totals, national, history

as allocate_geos takes them.

required
goal Goal

what the budget must meet.

required
rtol, atol, max_boxes

each plan's search's, as allocate_geos takes them.

required

Raises:

Type Description
TypeError

goal is none of the three, or what allocate_geos refuses as a type.

ValueError

what allocate_geos refuses of the cells, the boxes, the totals and the search's settings; a goal whose value is not finite; a gain beyond the most any plan returns; a return on spend no budget reaches, which the error says by how much it falls short at the peak.