Decision rules

All decision rules take the same input — a dict of {choice: {scenario: payoff}} — and return a solver with a .solve() method. They differ only in the attitude towards uncertainty they encode. Payoffs can be integers, or floats.

outcomes = {
    "stocks": {"recession": -20, "stagnation":  5, "growth": 30},
    "bonds":  {"recession":   5, "stagnation":  5, "growth":  7},
    "cash":   {"recession":   2, "stagnation":  2, "growth":  2},
}

Maximin

Which choice has the best worst case?

Pessimistic. Assumes the worst scenario will occur and picks the choice that hurts least. Useful when downside protection matters more than upside.

from formative.game import maximin

print(maximin(outcomes).solve())
MaximinResult(
  stocks  worst case: -20
  bonds   worst case: +5  ← chosen
  cash    worst case: +2
)

Bonds win: their worst case (recession, +5) is better than stocks (−20) or cash (+2).

class formative.game.Maximin(outcomes)

Maximin decision rule.

Parameters:

outcomes (dict of dict) – Mapping of {choice: {scenario: payoff}}.

Examples

>>> result = maximin({
...     "stocks": {"recession": -20, "stagnation":  5, "growth": 30},
...     "bonds":  {"recession":   5, "stagnation":  5, "growth":  7},
...     "cash":   {"recession":   2, "stagnation":  2, "growth":  2},
... }).solve()
>>> result.choice
'bonds'
solve()

Apply the maximin rule and return the safest choice.

Returns:

MaximinResult

class formative.game.MaximinResult(choice, guaranteed, worst_cases)

Result of applying the maximin decision rule.

choice

The choice that maximises the worst-case payoff.

Type:

str

guaranteed

Worst-case payoff of the chosen action.

Type:

float

worst_cases

Worst-case payoff for every choice: {choice: float}.

Type:

dict

Parameters:
  • choice (str)

  • guaranteed (float)

  • worst_cases (dict)

Maximax

Which choice has the best best case?

Optimistic. Assumes the best scenario will occur and picks accordingly. Appropriate when you can absorb losses and want to maximise upside.

from formative.game import maximax

print(maximax(outcomes).solve())
MaximaxResult(
  stocks  best case: +30  ← chosen
  bonds   best case: +7
  cash    best case: +2
)

Stocks win: their best case (growth, +30) is highest, even though they have the worst downside.

class formative.game.Maximax(outcomes)

Maximax decision rule.

Parameters:

outcomes (dict of dict) – Mapping of {choice: {scenario: payoff}}.

Examples

>>> result = maximax({
...     "stocks": {"recession": -20, "stagnation":  5, "growth": 30},
...     "bonds":  {"recession":   5, "stagnation":  5, "growth":  7},
...     "cash":   {"recession":   2, "stagnation":  2, "growth":  2},
... }).solve()
>>> result.choice
'stocks'
solve()

Apply the maximax rule and return the most optimistic choice.

Returns:

MaximaxResult

class formative.game.MaximaxResult(choice, best_case, best_cases)

Result of applying the maximax decision rule.

choice

The choice with the best best-case payoff.

Type:

str

best_case

The best possible payoff for the chosen choice.

Type:

float

best_cases

Best-case payoff for every choice: {choice: float}.

Type:

dict

Parameters:
  • choice (str)

  • best_case (float)

  • best_cases (dict)

Minimax Regret

Which choice do you regret the least in hindsight?

Regret in a given scenario is the gap between what you received and the best you could have received in that scenario. Minimax regret picks the choice where the worst-case regret is smallest — a middle ground between maximin and maximax.

First, find the best available payoff in each scenario: recession = 5 (bonds), stagnation = 5 (stocks or bonds), growth = 30 (stocks).

Then compute regret for every combination — how much you miss out on versus the best choice:

Regret table

recession

stagnation

growth

max regret

stocks

25

0

0

25

bonds

0

0

23

23 ← lowest

cash

3

3

28

28

from formative.game import minimax_regret

result = minimax_regret(outcomes).solve()
print(result)
MinimaxRegretResult(
  stocks  max regret: +25
  bonds   max regret: +23  ← chosen
  cash    max regret: +28
)

Stocks score zero regret in growth (they are the best there) but 25 in recession. Bonds score zero in recession and stagnation, but 23 in growth where stocks outperform them. Cash never comes close to the best option in any scenario, giving it the highest max regret of 28. Bonds minimise the worst-case regret.

class formative.game.MinimaxRegret(outcomes)

Minimax regret decision rule.

Parameters:

outcomes (dict of dict) – Mapping of {choice: {scenario: payoff}}.

Examples

>>> result = minimax_regret({
...     "stocks": {"recession": -20, "stagnation":  5, "growth": 30},
...     "bonds":  {"recession":   5, "stagnation":  5, "growth":  7},
...     "cash":   {"recession":   2, "stagnation":  2, "growth":  2},
... }).solve()
>>> result.choice
'bonds'
solve()

Apply the minimax regret rule.

Returns:

MinimaxRegretResult

class formative.game.MinimaxRegretResult(choice, max_regret, max_regrets, regret_table)

Result of applying the minimax regret decision rule.

Regret for a given choice and scenario is the difference between the best payoff available in that scenario and the payoff actually received. The minimax regret rule picks the choice whose worst-case regret is smallest.

choice

The choice that minimises the maximum regret.

Type:

str

max_regret

The maximum regret for the chosen choice.

Type:

float

max_regrets

Maximum regret for every choice: {choice: float}.

Type:

dict

regret_table

Full regret for every (choice, scenario) pair.

Type:

dict of dict

Parameters:
  • choice (str)

  • max_regret (float)

  • max_regrets (dict)

  • regret_table (dict)

Hurwicz Criterion

Which choice scores best on a blend of optimism and pessimism?

The Hurwicz criterion scores each choice as:

\[\text{score} = \alpha \cdot \text{best case} + (1 - \alpha) \cdot \text{worst case}\]

The parameter alpha controls how optimistic you are. At alpha=0 the rule is identical to maximin (pure pessimism); at alpha=1 it is identical to maximax (pure optimism). Values in between express a considered attitude towards risk.

With alpha=0.5 (equal weight to best and worst case):

  • stocks: 0.5 × 30 + 0.5 × (−20) = +5

  • bonds: 0.5 × 7 + 0.5 × 5 = +6 ← chosen

  • cash: 0.5 × 2 + 0.5 × 2 = +2

from formative.game import hurwicz

print(hurwicz(outcomes, alpha=0.5).solve())
HurwiczResult(alpha=0.5,
  stocks  score: +5
  bonds   score: +6  ← chosen
  cash    score: +2
)

At alpha=0.8 the optimistic weight dominates and stocks pull ahead:

print(hurwicz(outcomes, alpha=0.8).solve())
HurwiczResult(alpha=0.8,
  stocks  score: +20  ← chosen
  bonds   score: +6.6
  cash    score: +2
)
class formative.game.Hurwicz(outcomes, alpha)

Hurwicz decision rule.

Parameters:
  • outcomes (dict of dict) – Mapping of {choice: {scenario: payoff}}.

  • alpha (float) – Optimism coefficient in [0, 1]. At alpha=0 the rule reduces to maximin (pessimistic); at alpha=1 it reduces to maximax (optimistic).

Examples

>>> result = hurwicz({
...     "stocks": {"recession": -20, "stagnation":  5, "growth": 30},
...     "bonds":  {"recession":   5, "stagnation":  5, "growth":  7},
...     "cash":   {"recession":   2, "stagnation":  2, "growth":  2},
... }, alpha=0.5).solve()
>>> result.choice
'bonds'
solve()

Apply the Hurwicz rule and return the chosen action.

Returns:

HurwiczResult

class formative.game.HurwiczResult(choice, score, alpha, scores)

Result of applying the Hurwicz decision rule.

choice

The choice with the highest Hurwicz score.

Type:

str

score

Hurwicz score of the chosen choice.

Type:

float

alpha

The optimism coefficient used (0 = pure maximin, 1 = pure maximax).

Type:

float

scores

Hurwicz score for every choice: {choice: float}.

Type:

dict

Parameters:
  • choice (str)

  • score (float)

  • alpha (float)

  • scores (dict)

Laplace Criterion

Which choice has the best average payoff?

With no information about which scenario is more likely, treat them all as equally probable (Laplace’s principle of indifference) and pick the choice with the highest average payoff.

  • stocks: (−20 + 5 + 30) ÷ 3 = +5.0

  • bonds: (5 + 5 + 7) ÷ 3 = +5.67 ← chosen

  • cash: (2 + 2 + 2) ÷ 3 = +2.0

from formative.game import laplace

print(laplace(outcomes).solve())
LaplaceResult(
  stocks  average: +5
  bonds   average: +5.667  ← chosen
  cash    average: +2
)
class formative.game.Laplace(outcomes)

Laplace decision rule.

Parameters:

outcomes (dict of dict) – Mapping of {choice: {scenario: payoff}}.

Examples

>>> result = laplace({
...     "stocks": {"recession": -20, "stagnation":  5, "growth": 30},
...     "bonds":  {"recession":   5, "stagnation":  5, "growth":  7},
...     "cash":   {"recession":   2, "stagnation":  2, "growth":  2},
... }).solve()
>>> result.choice
'stocks'
solve()

Apply the Laplace rule and return the choice with the highest average payoff.

Returns:

LaplaceResult

class formative.game.LaplaceResult(choice, average, averages)

Result of applying the Laplace decision rule.

choice

The choice with the highest average payoff.

Type:

str

average

Average payoff of the chosen choice.

Type:

float

averages

Average payoff for every choice: {choice: float}.

Type:

dict

Parameters:
  • choice (str)

  • average (float)

  • averages (dict)

Expected Value

Which choice has the highest expected payoff given your probability beliefs?

When you have beliefs about how likely each scenario is, use those directly rather than assuming equal probability. The expected payoff of a choice is the probability-weighted sum of its payoffs across all scenarios.

With probabilities recession=0.2, stagnation=0.5, growth=0.3:

  • stocks: 0.2 × (−20) + 0.5 × 5 + 0.3 × 30 = +7.5 ← chosen

  • bonds: 0.2 × 5 + 0.5 × 5 + 0.3 × 7 = +5.6

  • cash: 0.2 × 2 + 0.5 × 2 + 0.3 × 2 = +2.0

from formative.game import expected_value

print(expected_value(outcomes, probabilities={
    "recession": 0.2,
    "stagnation": 0.5,
    "growth": 0.3,
}).solve())
ExpectedValueResult(
  stocks  E[payoff]: +7.5  ← chosen
  bonds   E[payoff]: +5.6
  cash    E[payoff]: +2
)

Unlike the Laplace criterion, which treats all scenarios as equally likely, expected value lets you express a considered view. If you are uncertain about your probabilities, compare the result against Laplace or Hurwicz to see how sensitive the choice is to that assumption.

class formative.game.ExpectedValue(outcomes, probabilities)

Expected value decision rule with explicit scenario probabilities.

Parameters:
  • outcomes (dict of dict) – Mapping of {choice: {scenario: payoff}}.

  • probabilities (dict) – Mapping of {scenario: probability}. Must cover every scenario present in outcomes and sum to 1.0 (within 1e-6 tolerance).

Examples

>>> result = expected_value({
...     "stocks": {"recession": -20, "stagnation":  5, "growth": 30},
...     "bonds":  {"recession":   5, "stagnation":  5, "growth":  7},
...     "cash":   {"recession":   2, "stagnation":  2, "growth":  2},
... }, probabilities={"recession": 0.2, "stagnation": 0.5, "growth": 0.3}).solve()
>>> result.choice
'stocks'
solve()

Apply the expected value rule and return the choice with the highest expected payoff.

Returns:

ExpectedValueResult

class formative.game.ExpectedValueResult(choice, expected, expected_values, probabilities)

Result of applying the expected value decision rule.

choice

The choice with the highest expected payoff.

Type:

str

expected

Expected payoff of the chosen choice.

Type:

float

expected_values

Expected payoff for every choice: {choice: float}.

Type:

dict

probabilities

Scenario probabilities used: {scenario: float}.

Type:

dict

Parameters:
  • choice (str)

  • expected (float)

  • expected_values (dict)

  • probabilities (dict)