Bone-Market-Solver/bonemarketsolver/solve.py
Jeremy Saklad 17add382ec
Replace BoneMarketModel.NewIntermediateBoolVar
The replacement method, BoolExpression, accepts a
BoundedLinearExpression directly, and uses memoization to reuse Boolean
variables. This technique is unsuitable for other methods, as
constraints need to be applied separately, but in this case it is fine.

The new method is much easier to understand, and far easier to read at
call sites.

Variable names are generated from the expression, rather than being
explicitly specified.
2021-10-15 15:04:37 -05:00

1432 lines
78 KiB
Python

"""Use constraint programming to devise the optimal skeleton at the Bone Market in Fallen London."""
__all__ = ['Adjustment', 'Appendage', 'Buyer', 'Declaration', 'DiplomatFascination', 'Embellishment', 'Fluctuation', 'OccasionalBuyer', 'Skull', 'Solve', 'Torso']
__author__ = "Jeremy Saklad"
from os import cpu_count
from ortools.sat.python import cp_model
from .data.adjustments import Adjustment
from .data.appendages import Appendage
from .data.buyers import Buyer
from .data.costs import Cost
from .data.declarations import Declaration
from .data.diplomat_fascinations import DiplomatFascination
from .data.embellishments import Embellishment
from .data.fluctuations import Fluctuation
from .data.occasional_buyers import OccasionalBuyer
from .data.skulls import Skull
from .data.torsos import Torso
from .objects.bone_market_model import BoneMarketModel
# This multiplier is applied to the profit margin to avoid losing precision due to rounding.
PROFIT_MARGIN_MULTIPLIER = 10000000
# This is the highest number of attribute to calculate fractional exponents for.
MAXIMUM_ATTRIBUTE = 100
# This is a constant used to calculate difficulty checks. You almost certainly do not need to change this.
DIFFICULTY_SCALER = 0.6
def Solve(shadowy_level, bone_market_fluctuations = None, zoological_mania = None, occasional_buyer = None, diplomat_fascination = None, desired_buyers = [], maximum_cost = cp_model.INT32_MAX, maximum_exhaustion = cp_model.INT32_MAX, time_limit = float('inf'), workers = cpu_count(), blacklist = [], stdscr = None):
model = BoneMarketModel()
actions = {}
# Torso
for torso in Torso:
actions[torso] = model.NewBoolVar(torso.value.name)
# Skull
for skull in Skull:
actions[skull] = model.NewIntVar(skull.value.name, lb = 0)
# Appendage
for appendage in Appendage:
if appendage == Appendage.SKIP_TAILS:
actions[appendage] = model.NewBoolVar(appendage.value.name)
else:
actions[appendage] = model.NewIntVar(appendage.value.name, lb = 0)
# Avoid adding joints at first
model.AddHint(actions[Appendage.ADD_JOINTS], 0)
# Adjustment
for adjustment in Adjustment:
actions[adjustment] = model.NewIntVar(adjustment.value.name, lb = 0)
# Declaration
for declaration in Declaration:
actions[declaration] = model.NewBoolVar(declaration.value.name)
# Try non-Chimera declarations first
model.AddHint(actions[Declaration.CHIMERA], 0)
# Embellishment
for embellishment in Embellishment:
actions[embellishment] = model.NewIntVar(embellishment.value.name, lb = 0)
# Buyer
for buyer in Buyer:
actions[buyer] = model.NewBoolVar(buyer.value.name)
# Mark unavailable buyers
model.AddAssumptions([
actions[buyer].Not()
for unavailable_buyer in OccasionalBuyer if unavailable_buyer != occasional_buyer
for buyer in unavailable_buyer.value if buyer not in desired_buyers
])
model.AddAssumptions([
actions[outmoded_fascination.value].Not()
for outmoded_fascination in DiplomatFascination if outmoded_fascination != diplomat_fascination and outmoded_fascination.value not in desired_buyers
])
# Restrict to desired buyers
if desired_buyers:
model.Add(cp_model.LinearExpr.Sum([actions[desired_buyer] for desired_buyer in desired_buyers]) == 1)
# Blacklist
model.Add(cp_model.LinearExpr.Sum([actions[forbidden] for forbidden in blacklist]) == 0)
# One torso
model.Add(cp_model.LinearExpr.Sum([value for (key, value) in actions.items() if isinstance(key, Torso)]) == 1)
# One declaration
model.Add(cp_model.LinearExpr.Sum([value for (key, value) in actions.items() if isinstance(key, Declaration)]) == 1)
# One buyer
model.Add(cp_model.LinearExpr.Sum([value for (key, value) in actions.items() if isinstance(key, Buyer)]) == 1)
# Value calculation
value = model.NewIntVar('value', lb = 0)
base_value = model.NewIntVar('base value', lb = 0)
model.Add(base_value == cp_model.LinearExpr.ScalProd(actions.values(), [action.value.value for action in actions.keys()]))
# Calculate value from Vake skulls
# This is a partial sum formula.
vake_skull_value = model.NewIntVar('vake skull value')
vake_skulls = actions[Skull.VAKE_SKULL]
vake_skulls_squared = model.NewIntVar('vake skulls squared', lb = 0)
model.AddMultiplicationEquality(vake_skulls_squared, (vake_skulls, vake_skulls))
model.Add(vake_skull_value == -250 * vake_skulls_squared + 6750 * vake_skulls)
del vake_skulls, vake_skulls_squared
model.Add(value == base_value + vake_skull_value)
del base_value, vake_skull_value
# Zoological Mania
zoological_mania_bonus = model.NewIntVar('zoological mania bonus', lb = 0)
if zoological_mania:
multiplier = 15 if zoological_mania in [Declaration.FISH, Declaration.INSECT, Declaration.SPIDER] else 10
potential_zoological_mania_bonus = model.NewIntVar('potential zoological mania bonus', lb = 0)
multiplied_value = model.NewIntVar('multiplied value', lb = 0)
model.Add(multiplied_value == multiplier*value)
model.AddDivisionEquality(potential_zoological_mania_bonus, multiplied_value, 100)
model.Add(zoological_mania_bonus == potential_zoological_mania_bonus).OnlyEnforceIf(actions[zoological_mania])
model.Add(zoological_mania_bonus == 0).OnlyEnforceIf(actions[zoological_mania].Not())
del multiplier, potential_zoological_mania_bonus, multiplied_value
else:
model.Add(zoological_mania_bonus == 0)
# Torso Style calculation
torso_style = model.NewIntVarFromDomain(cp_model.Domain.FromValues([torso.value.torso_style for torso in Torso]), 'torso style')
for torso, torso_variable in {key: value for (key, value) in actions.items() if isinstance(key, Torso)}.items():
model.Add(torso_style == torso.value.torso_style).OnlyEnforceIf(torso_variable)
# Skulls calculation
skulls = model.NewIntVar('skulls', lb = 0)
model.Add(skulls == cp_model.LinearExpr.ScalProd(actions.values(), [action.value.skulls for action in actions.keys()]))
# Arms calculation
arms = model.NewIntVar('arms', lb = 0)
model.Add(arms == cp_model.LinearExpr.ScalProd(actions.values(), [action.value.arms for action in actions.keys()]))
# Legs calculation
legs = model.NewIntVar('legs', lb = 0)
model.Add(legs == cp_model.LinearExpr.ScalProd(actions.values(), [action.value.legs for action in actions.keys()]))
# Tails calculation
tails = model.NewIntVar('tails', lb = 0)
model.Add(tails == cp_model.LinearExpr.ScalProd(actions.values(), [action.value.tails for action in actions.keys()]))
# Wings calculation
wings = model.NewIntVar('wings', lb = 0)
model.Add(wings == cp_model.LinearExpr.ScalProd(actions.values(), [action.value.wings for action in actions.keys()]))
# Fins calculation
fins = model.NewIntVar('fins', lb = 0)
model.Add(fins == cp_model.LinearExpr.ScalProd(actions.values(), [action.value.fins for action in actions.keys()]))
# Tentacles calculation
tentacles = model.NewIntVar('tentacles', lb = 0)
model.Add(tentacles == cp_model.LinearExpr.ScalProd(actions.values(), [action.value.tentacles for action in actions.keys()]))
# Amalgamy calculation
amalgamy = model.NewIntVar('amalgamy')
model.Add(amalgamy == cp_model.LinearExpr.ScalProd(actions.values(), [action.value.amalgamy for action in actions.keys()]))
# Antiquity calculation
antiquity = model.NewIntVar('antiquity')
model.Add(antiquity == cp_model.LinearExpr.ScalProd(actions.values(), [action.value.antiquity for action in actions.keys()]))
# Menace calculation
menace = model.NewIntVar('menace')
constant_base_menace = model.NewIntVar('constant base menace')
model.Add(constant_base_menace == cp_model.LinearExpr.ScalProd(actions.values(), [action.value.menace for action in actions.keys()]))
# Calculate menace from Vake skulls
vake_skull_bonus_menace = model.NewIntVarFromDomain(cp_model.Domain.FromValues([0, 2, 3]), 'vake skull bonus menace')
vake_skulls_times_two = model.NewIntVar('vake skulls times two', lb = 0)
model.AddMultiplicationEquality(vake_skulls_times_two, (2, actions[Skull.VAKE_SKULL]))
model.AddMinEquality(vake_skull_bonus_menace, [vake_skulls_times_two, 3])
del vake_skulls_times_two
model.Add(menace == constant_base_menace + vake_skull_bonus_menace)
del constant_base_menace, vake_skull_bonus_menace
# Implausibility calculation
implausibility = model.NewIntVar('implausibility')
constant_base_implausibility = model.NewIntVar('implausibility')
model.Add(constant_base_implausibility == cp_model.LinearExpr.ScalProd(actions.values(), [action.value.implausibility for action in actions.keys()]))
# Calculate implausibility from Vake skulls
# This is a partial sum formula.
vake_skull_implausibility = model.NewIntVar('vake skull implausibility', lb = 0)
vake_skull_implausibility_numerator = model.NewIntVar('vake skull implausibility numerator', lb = 0)
vake_skulls = actions[Skull.VAKE_SKULL]
vake_skull_implausibility_numerator_second_term = model.NewIntVar('vake skull implausibility numerator second term', lb = 0)
model.AddMultiplicationEquality(vake_skull_implausibility_numerator_second_term, (vake_skulls, vake_skulls))
vake_skull_implausibility_numerator_third_term = model.NewIntVar('vake skull implausibility numerator third term', lb = 0, ub = 1)
model.AddModuloEquality(vake_skull_implausibility_numerator_third_term, vake_skulls, 2)
model.Add(vake_skull_implausibility_numerator == -2 * vake_skulls + vake_skull_implausibility_numerator_second_term + vake_skull_implausibility_numerator_third_term)
del vake_skulls, vake_skull_implausibility_numerator_second_term, vake_skull_implausibility_numerator_third_term
model.AddDivisionEquality(vake_skull_implausibility, vake_skull_implausibility_numerator, 4)
del vake_skull_implausibility_numerator
model.Add(implausibility == constant_base_implausibility + vake_skull_implausibility)
del constant_base_implausibility, vake_skull_implausibility
# Counter-church calculation
# Calculate amount of Counter-church from Holy Relics of the Thigh of Saint Fiacre
holy_relic = actions[Appendage.FIACRE_THIGH]
torso_style_divided_by_ten = model.NewIntVar('torso style divided by ten', lb = 0)
model.AddDivisionEquality(torso_style_divided_by_ten, torso_style, 10)
holy_relic_counter_church = model.NewIntVar('holy relic counter-church', lb = 0)
model.AddMultiplicationEquality(holy_relic_counter_church, (holy_relic, torso_style_divided_by_ten))
counter_church = model.NewIntVar('counter-church')
model.Add(counter_church == cp_model.LinearExpr.ScalProd(actions.values(), [action.value.counter_church for action in actions.keys()]) + holy_relic_counter_church)
del holy_relic, torso_style_divided_by_ten, holy_relic_counter_church
# Exhaustion calculation
exhaustion = model.NewIntVar('exhaustion', lb = 0, ub = maximum_exhaustion)
# Exhaustion added by certain buyers
added_exhaustion = model.NewIntVar('added exhaustion', lb = 0, ub = maximum_exhaustion)
model.Add(exhaustion == cp_model.LinearExpr.ScalProd(actions.values(), [action.value.exhaustion for action in actions.keys()]) + added_exhaustion)
# Profit intermediate variables
primary_revenue = model.NewIntVar('primary revenue', lb = 0)
secondary_revenue = model.NewIntVar('secondary revenue', lb = 0)
total_revenue = model.NewIntVar('total revenue', lb = 0)
model.Add(total_revenue == cp_model.LinearExpr.Sum([primary_revenue, secondary_revenue]))
# Cost
# Calculate value of actions needed to sell the skeleton.
difficulty_level = model.NewIntVar('difficulty level')
non_zero_difficulty_level = model.NewIntVar('non-zero difficulty level', lb = 1)
model.AddMaxEquality(non_zero_difficulty_level, [difficulty_level, 1])
sale_actions_times_action_value = model.NewIntVar('sale actions times action value', lb = 0)
model.AddDivisionEquality(sale_actions_times_action_value, model.NewConstant(round(DIFFICULTY_SCALER*shadowy_level*Cost.ACTION.value)), non_zero_difficulty_level)
abstract_sale_cost = model.NewIntVar('abstract sale cost', lb = 0)
model.AddDivisionEquality(abstract_sale_cost, Cost.ACTION.value**2, sale_actions_times_action_value)
sale_cost = model.NewIntVar('sale cost', lb = 0)
model.AddMaxEquality(sale_cost, [abstract_sale_cost, Cost.ACTION.value])
del non_zero_difficulty_level, sale_actions_times_action_value, abstract_sale_cost
# Calculate cost of adding joints
# This is a partial sum formula.
add_joints_amber_cost = model.NewIntVar('add joints amber cost', lb = 0)
add_joints = actions[Appendage.ADD_JOINTS]
base_joints = model.NewIntVar('base joints', lb = 0)
model.Add(base_joints == cp_model.LinearExpr.ScalProd([value for (key, value) in actions.items() if isinstance(key, Torso)], [torso.value.limbs_needed + torso.value.arms + torso.value.legs + torso.value.wings + torso.value.fins + torso.value.tentacles for torso in Torso]))
add_joints_amber_cost_multiple = model.NewIntVar('add joints amber cost multiple', lb = 0)
add_joints_amber_cost_multiple_first_term = model.NewIntVar('add joints amber cost multiple first term', lb = 0)
model.AddMultiplicationEquality(add_joints_amber_cost_multiple_first_term, (25, base_joints, base_joints, add_joints))
add_joints_amber_cost_multiple_second_term = model.NewIntVar('add joints amber cost multiple second term', lb = 0)
model.AddMultiplicationEquality(add_joints_amber_cost_multiple_second_term, (100, base_joints, add_joints, add_joints))
add_joints_amber_cost_multiple_third_term = model.NewIntVar('add joints amber cost multiple third term', lb = 0)
model.AddMultiplicationEquality(add_joints_amber_cost_multiple_third_term, (100, base_joints, add_joints))
add_joints_amber_cost_multiple_fourth_term = model.NewIntVar('add joints amber cost multiple fourth term', lb = 0)
add_joints_amber_cost_multiple_fourth_term_numerator = model.NewIntVar('add joints amber cost multiple fourth term numerator', lb = 0)
add_joints_amber_cost_multiple_fourth_term_numerator_first_term = model.NewIntVar('add joints amber cost multiple fourth term numerator first term', lb = 0)
model.AddMultiplicationEquality(add_joints_amber_cost_multiple_fourth_term_numerator_first_term, (400, add_joints, add_joints, add_joints))
model.Add(add_joints_amber_cost_multiple_fourth_term_numerator == add_joints_amber_cost_multiple_fourth_term_numerator_first_term + 200*add_joints)
model.AddDivisionEquality(add_joints_amber_cost_multiple_fourth_term, add_joints_amber_cost_multiple_fourth_term_numerator, 3)
del add_joints_amber_cost_multiple_fourth_term_numerator, add_joints_amber_cost_multiple_fourth_term_numerator_first_term
add_joints_amber_cost_multiple_fifth_term = model.NewIntVar('add joints amber cost multiple fifth term', lb = 0)
model.AddMultiplicationEquality(add_joints_amber_cost_multiple_fifth_term, (200, add_joints, add_joints))
model.Add(add_joints_amber_cost_multiple == add_joints_amber_cost_multiple_first_term + add_joints_amber_cost_multiple_second_term - add_joints_amber_cost_multiple_third_term + add_joints_amber_cost_multiple_fourth_term - add_joints_amber_cost_multiple_fifth_term)
del add_joints_amber_cost_multiple_first_term, add_joints_amber_cost_multiple_second_term, add_joints_amber_cost_multiple_third_term, add_joints_amber_cost_multiple_fourth_term, add_joints_amber_cost_multiple_fifth_term
model.AddMultiplicationEquality(add_joints_amber_cost, (add_joints_amber_cost_multiple, Cost.WARM_AMBER.value))
del add_joints, add_joints_amber_cost_multiple
cost = model.NewIntVar('cost', lb = 0, ub = maximum_cost)
model.Add(cost == cp_model.LinearExpr.ScalProd(actions.values(), [int(action.value.cost) for action in actions.keys()]) + add_joints_amber_cost + sale_cost)
del sale_cost, add_joints_amber_cost
# Type of skeleton
skeleton_in_progress = model.NewIntVar('skeleton in progress', lb = 0)
# Chimera
model.Add(skeleton_in_progress == 100) \
.OnlyEnforceIf(actions[Declaration.CHIMERA])
# Humanoid
model.Add(skeleton_in_progress == 110) \
.OnlyEnforceIf(actions[Declaration.HUMANOID]) \
.OnlyEnforceIf(model.BoolExpression(antiquity <= 0))
# Ancient Humanoid (UNCERTAIN)
model.Add(skeleton_in_progress == 111) \
.OnlyEnforceIf(actions[Declaration.HUMANOID]) \
.OnlyEnforceIf(model.BoolExpression(cp_model.BoundedLinearExpression(antiquity, (1, 5))))
# Neanderthal
model.Add(skeleton_in_progress == 112) \
.OnlyEnforceIf(actions[Declaration.HUMANOID]) \
.OnlyEnforceIf(model.BoolExpression(antiquity >= 6))
# Ape (UNCERTAIN)
model.Add(skeleton_in_progress == 120) \
.OnlyEnforceIf(actions[Declaration.APE]) \
.OnlyEnforceIf(model.BoolExpression(antiquity <= 1))
# Primordial Ape (UNCERTAIN)
model.Add(skeleton_in_progress == 121) \
.OnlyEnforceIf(actions[Declaration.APE]) \
.OnlyEnforceIf(model.BoolExpression(antiquity >= 2))
# Monkey
model.Add(skeleton_in_progress == 125) \
.OnlyEnforceIf(actions[Declaration.MONKEY]) \
.OnlyEnforceIf(model.BoolExpression(antiquity <= 0))
# Catarrhine Monkey (UNCERTAIN)
model.Add(skeleton_in_progress == 126) \
.OnlyEnforceIf(actions[Declaration.MONKEY]) \
.OnlyEnforceIf(model.BoolExpression(cp_model.BoundedLinearExpression(antiquity, (1, 8))))
# Catarrhine Monkey
model.Add(skeleton_in_progress == 128) \
.OnlyEnforceIf(actions[Declaration.MONKEY]) \
.OnlyEnforceIf(model.BoolExpression(antiquity >= 9))
# Crocodile
model.Add(skeleton_in_progress == 160) \
.OnlyEnforceIf(actions[Declaration.REPTILE]) \
.OnlyEnforceIf(model.BoolExpression(antiquity <= 1))
# Dinosaur
model.Add(skeleton_in_progress == 161) \
.OnlyEnforceIf(actions[Declaration.REPTILE]) \
.OnlyEnforceIf(model.BoolExpression(cp_model.BoundedLinearExpression(antiquity, (2, 4))))
# Mesosaur (UNCERTAIN)
model.Add(skeleton_in_progress == 162) \
.OnlyEnforceIf(actions[Declaration.REPTILE]) \
.OnlyEnforceIf(model.BoolExpression(antiquity >= 5))
# Toad
model.Add(skeleton_in_progress == 170) \
.OnlyEnforceIf(actions[Declaration.AMPHIBIAN]) \
.OnlyEnforceIf(model.BoolExpression(antiquity <= 1))
# Primordial Amphibian
model.Add(skeleton_in_progress == 171) \
.OnlyEnforceIf(actions[Declaration.AMPHIBIAN]) \
.OnlyEnforceIf(model.BoolExpression(cp_model.BoundedLinearExpression(antiquity, (2, 4))))
# Temnospondyl
model.Add(skeleton_in_progress == 172) \
.OnlyEnforceIf(actions[Declaration.AMPHIBIAN]) \
.OnlyEnforceIf(model.BoolExpression(antiquity >= 5))
# Owl
model.Add(skeleton_in_progress == 180) \
.OnlyEnforceIf(actions[Declaration.BIRD]) \
.OnlyEnforceIf(model.BoolExpression(antiquity <= 1))
# Archaeopteryx
model.Add(skeleton_in_progress == 181) \
.OnlyEnforceIf(actions[Declaration.BIRD]) \
.OnlyEnforceIf(model.BoolExpression(cp_model.BoundedLinearExpression(antiquity, (2, 4))))
# Ornithomimosaur (UNCERTAIN)
model.Add(skeleton_in_progress == 182) \
.OnlyEnforceIf(actions[Declaration.BIRD]) \
.OnlyEnforceIf(model.BoolExpression(antiquity >= 5))
# Lamprey
model.Add(skeleton_in_progress == 190) \
.OnlyEnforceIf(actions[Declaration.FISH]) \
.OnlyEnforceIf(model.BoolExpression(antiquity <= 0))
# Coelacanth (UNCERTAIN)
model.Add(skeleton_in_progress == 191) \
.OnlyEnforceIf(actions[Declaration.FISH]) \
.OnlyEnforceIf(model.BoolExpression(antiquity >= 1))
# Spider (UNCERTAIN)
model.Add(skeleton_in_progress == 200) \
.OnlyEnforceIf(actions[Declaration.SPIDER]) \
.OnlyEnforceIf(model.BoolExpression(antiquity <= 1))
# Primordial Orb-Weaver (UNCERTAIN)
model.Add(skeleton_in_progress == 201) \
.OnlyEnforceIf(actions[Declaration.SPIDER]) \
.OnlyEnforceIf(model.BoolExpression(cp_model.BoundedLinearExpression(antiquity, (2, 7))))
# Trigonotarbid
model.Add(skeleton_in_progress == 203) \
.OnlyEnforceIf(actions[Declaration.SPIDER]) \
.OnlyEnforceIf(model.BoolExpression(antiquity >= 8))
# Beetle (UNCERTAIN)
model.Add(skeleton_in_progress == 210) \
.OnlyEnforceIf(actions[Declaration.INSECT]) \
.OnlyEnforceIf(model.BoolExpression(antiquity <= 1))
# Primordial Beetle (UNCERTAIN)
model.Add(skeleton_in_progress == 211) \
.OnlyEnforceIf(actions[Declaration.INSECT]) \
.OnlyEnforceIf(model.BoolExpression(cp_model.BoundedLinearExpression(antiquity, (2, 6))))
# Rhyniognatha
model.Add(skeleton_in_progress == 212) \
.OnlyEnforceIf(actions[Declaration.INSECT]) \
.OnlyEnforceIf(model.BoolExpression(antiquity >= 7))
# Curator
model.Add(skeleton_in_progress == 300) \
.OnlyEnforceIf(actions[Declaration.CURATOR])
# Humanoid requirements
model.Add(skulls == 1).OnlyEnforceIf(actions[Declaration.HUMANOID])
model.Add(legs == 2).OnlyEnforceIf(actions[Declaration.HUMANOID])
model.Add(arms == 2).OnlyEnforceIf(actions[Declaration.HUMANOID])
model.AddLinearExpressionInDomain(torso_style, cp_model.Domain.FromFlatIntervals([10, 20])).OnlyEnforceIf(actions[Declaration.HUMANOID])
for prohibited_quality in [tails, fins, wings]:
model.Add(prohibited_quality == 0).OnlyEnforceIf(actions[Declaration.HUMANOID])
# Ape requirements
model.Add(skulls == 1).OnlyEnforceIf(actions[Declaration.APE])
model.Add(arms == 4).OnlyEnforceIf(actions[Declaration.APE])
model.AddLinearExpressionInDomain(torso_style, cp_model.Domain.FromFlatIntervals([10, 20])).OnlyEnforceIf(actions[Declaration.APE])
for prohibited_quality in [legs, tails, fins, wings]:
model.Add(prohibited_quality == 0).OnlyEnforceIf(actions[Declaration.APE])
# Monkey requirements
model.Add(skulls == 1).OnlyEnforceIf(actions[Declaration.MONKEY])
model.Add(arms == 4).OnlyEnforceIf(actions[Declaration.MONKEY])
model.Add(tails == 1).OnlyEnforceIf(actions[Declaration.MONKEY])
model.AddLinearExpressionInDomain(torso_style, cp_model.Domain.FromFlatIntervals([10, 20])).OnlyEnforceIf(actions[Declaration.MONKEY])
for prohibited_quality in [legs, fins, wings]:
model.Add(prohibited_quality == 0).OnlyEnforceIf(actions[Declaration.MONKEY])
# Bird requirements
model.Add(legs == 2).OnlyEnforceIf(actions[Declaration.BIRD])
model.Add(wings == 2).OnlyEnforceIf(actions[Declaration.BIRD])
model.Add(torso_style >= 20).OnlyEnforceIf(actions[Declaration.BIRD])
for prohibited_quality in [arms, fins]:
model.Add(prohibited_quality == 0).OnlyEnforceIf(actions[Declaration.BIRD])
model.Add(tails < 2).OnlyEnforceIf(actions[Declaration.BIRD])
# Curator requirements
model.Add(skulls == 1).OnlyEnforceIf(actions[Declaration.CURATOR])
model.Add(arms == 2).OnlyEnforceIf(actions[Declaration.CURATOR])
model.Add(legs == 2).OnlyEnforceIf(actions[Declaration.CURATOR])
model.Add(wings == 2).OnlyEnforceIf(actions[Declaration.CURATOR])
for prohibited_quality in [fins, tails]:
model.Add(prohibited_quality == 0).OnlyEnforceIf(actions[Declaration.CURATOR])
# Reptile requirements
model.Add(torso_style >= 20).OnlyEnforceIf(actions[Declaration.REPTILE])
model.Add(tails == 1).OnlyEnforceIf(actions[Declaration.REPTILE])
model.Add(skulls == 1).OnlyEnforceIf(actions[Declaration.REPTILE])
for prohibited_quality in [fins, wings, arms]:
model.Add(prohibited_quality == 0).OnlyEnforceIf(actions[Declaration.REPTILE])
model.Add(legs < 5).OnlyEnforceIf(actions[Declaration.REPTILE])
# Amphibian requirements
model.Add(torso_style >= 20).OnlyEnforceIf(actions[Declaration.AMPHIBIAN])
model.Add(legs == 4).OnlyEnforceIf(actions[Declaration.AMPHIBIAN])
model.Add(skulls == 1).OnlyEnforceIf(actions[Declaration.AMPHIBIAN])
for prohibited_quality in [tails, fins, wings, arms]:
model.Add(prohibited_quality == 0).OnlyEnforceIf(actions[Declaration.AMPHIBIAN])
# Fish requirements
model.Add(skulls == 1).OnlyEnforceIf(actions[Declaration.FISH])
model.Add(fins >= 2).OnlyEnforceIf(actions[Declaration.FISH])
model.Add(tails <= 1).OnlyEnforceIf(actions[Declaration.FISH])
model.Add(torso_style >= 20).OnlyEnforceIf(actions[Declaration.FISH])
for prohibited_quality in [arms, legs, wings]:
model.Add(prohibited_quality == 0).OnlyEnforceIf(actions[Declaration.FISH])
# Insect requirements
model.Add(skulls == 1).OnlyEnforceIf(actions[Declaration.INSECT])
model.Add(legs == 6).OnlyEnforceIf(actions[Declaration.INSECT])
model.Add(torso_style >= 20).OnlyEnforceIf(actions[Declaration.INSECT])
for prohibited_quality in [arms, fins, tails]:
model.Add(prohibited_quality == 0).OnlyEnforceIf(actions[Declaration.INSECT])
model.Add(wings < 5).OnlyEnforceIf(actions[Declaration.INSECT])
# Spider requirements
model.Add(legs == 8).OnlyEnforceIf(actions[Declaration.SPIDER])
model.Add(tails <= 1).OnlyEnforceIf(actions[Declaration.SPIDER])
model.Add(torso_style >= 20).OnlyEnforceIf(actions[Declaration.SPIDER])
for prohibited_quality in [skulls, arms, wings, fins]:
model.Add(prohibited_quality == 0).OnlyEnforceIf(actions[Declaration.SPIDER])
# Skeleton must have no unfilled skulls
model.Add(cp_model.LinearExpr.ScalProd(actions.values(), [action.value.skulls_needed for action in actions.keys()]) == 0)
# Skeleton must have no unfilled limbs
model.Add(cp_model.LinearExpr.ScalProd(actions.values(), [action.value.limbs_needed for action in actions.keys()]) == 0)
# Skeleton must have no unfilled tails, unless they were skipped
model.Add(cp_model.LinearExpr.ScalProd(actions.values(), [action.value.tails_needed for action in actions.keys()]) == 0).OnlyEnforceIf(actions[Appendage.SKIP_TAILS].Not())
model.Add(cp_model.LinearExpr.ScalProd(actions.values(), [action.value.tails_needed for action in actions.keys()]) > 0).OnlyEnforceIf(actions[Appendage.SKIP_TAILS])
# A Palaeontologist with Hoarding Propensities
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.A_PALAEONTOLOGIST_WITH_HOARDING_PROPENSITIES])
model.Add(primary_revenue == value + zoological_mania_bonus + 5).OnlyEnforceIf(actions[Buyer.A_PALAEONTOLOGIST_WITH_HOARDING_PROPENSITIES])
model.Add(secondary_revenue == 500).OnlyEnforceIf(actions[Buyer.A_PALAEONTOLOGIST_WITH_HOARDING_PROPENSITIES])
model.Add(difficulty_level == 40*implausibility).OnlyEnforceIf(actions[Buyer.A_PALAEONTOLOGIST_WITH_HOARDING_PROPENSITIES])
model.Add(added_exhaustion == 0).OnlyEnforceIf(actions[Buyer.A_PALAEONTOLOGIST_WITH_HOARDING_PROPENSITIES])
# A Naive Collector
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.A_NAIVE_COLLECTOR])
total_value = model.NewIntVar(f'{Buyer.A_NAIVE_COLLECTOR.name}: total value', lb = 0)
model.Add(total_value == value + zoological_mania_bonus)
total_value_remainder = model.NewIntVar(f'{Buyer.A_NAIVE_COLLECTOR.name}: total value remainder', lb = 0, ub = 249)
model.AddModuloEquality(total_value_remainder, total_value, 250)
model.Add(primary_revenue == total_value - total_value_remainder).OnlyEnforceIf(actions[Buyer.A_NAIVE_COLLECTOR])
model.Add(secondary_revenue == 0).OnlyEnforceIf(actions[Buyer.A_NAIVE_COLLECTOR])
model.Add(difficulty_level == 25*implausibility).OnlyEnforceIf(actions[Buyer.A_NAIVE_COLLECTOR])
model.Add(added_exhaustion == 0).OnlyEnforceIf(actions[Buyer.A_NAIVE_COLLECTOR])
del total_value, total_value_remainder
# A Familiar Bohemian Sculptress
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.A_FAMILIAR_BOHEMIAN_SCULPTRESS])
model.Add(antiquity <= 0).OnlyEnforceIf(actions[Buyer.A_FAMILIAR_BOHEMIAN_SCULPTRESS])
total_value = model.NewIntVar(f'{Buyer.A_FAMILIAR_BOHEMIAN_SCULPTRESS.name}: total value', lb = 0)
model.Add(total_value == value + zoological_mania_bonus)
total_value_remainder = model.NewIntVar(f'{Buyer.A_FAMILIAR_BOHEMIAN_SCULPTRESS.name}: total value remainder', lb = 0, ub = 249)
model.AddModuloEquality(total_value_remainder, total_value, 250)
model.Add(primary_revenue == total_value - total_value_remainder + 1000).OnlyEnforceIf(actions[Buyer.A_FAMILIAR_BOHEMIAN_SCULPTRESS])
model.Add(secondary_revenue == 250*counter_church).OnlyEnforceIf(actions[Buyer.A_FAMILIAR_BOHEMIAN_SCULPTRESS])
model.Add(difficulty_level == 50*implausibility).OnlyEnforceIf(actions[Buyer.A_FAMILIAR_BOHEMIAN_SCULPTRESS])
model.Add(added_exhaustion == 0).OnlyEnforceIf(actions[Buyer.A_FAMILIAR_BOHEMIAN_SCULPTRESS])
del total_value, total_value_remainder
# A Pedagogically Inclined Grandmother
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.A_PEDAGOGICALLY_INCLINED_GRANDMOTHER])
model.Add(menace <= 0).OnlyEnforceIf(actions[Buyer.A_PEDAGOGICALLY_INCLINED_GRANDMOTHER])
total_value = model.NewIntVar(f'{Buyer.A_PEDAGOGICALLY_INCLINED_GRANDMOTHER.name}: total value', lb = 0)
model.Add(total_value == value + zoological_mania_bonus)
total_value_remainder = model.NewIntVar(f'{Buyer.A_PEDAGOGICALLY_INCLINED_GRANDMOTHER.name}: total value remainder', lb = 0, ub = 49)
model.AddModuloEquality(total_value_remainder, total_value, 50)
model.Add(primary_revenue == total_value - total_value_remainder + 1000).OnlyEnforceIf(actions[Buyer.A_PEDAGOGICALLY_INCLINED_GRANDMOTHER])
model.Add(secondary_revenue == 0).OnlyEnforceIf(actions[Buyer.A_PEDAGOGICALLY_INCLINED_GRANDMOTHER])
model.Add(difficulty_level == 50*implausibility).OnlyEnforceIf(actions[Buyer.A_PEDAGOGICALLY_INCLINED_GRANDMOTHER])
model.Add(added_exhaustion == 0).OnlyEnforceIf(actions[Buyer.A_PEDAGOGICALLY_INCLINED_GRANDMOTHER])
del total_value, total_value_remainder
# A Theologian of the Old School
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.A_THEOLOGIAN_OF_THE_OLD_SCHOOL])
model.Add(amalgamy <= 0).OnlyEnforceIf(actions[Buyer.A_THEOLOGIAN_OF_THE_OLD_SCHOOL])
total_value = model.NewIntVar(f'{Buyer.A_THEOLOGIAN_OF_THE_OLD_SCHOOL.name}: total value', lb = 0)
model.Add(total_value == value + zoological_mania_bonus)
total_value_remainder = model.NewIntVar(f'{Buyer.A_THEOLOGIAN_OF_THE_OLD_SCHOOL.name}: total value remainder', lb = 0, ub = 249)
model.AddModuloEquality(total_value_remainder, total_value, 250)
model.Add(primary_revenue == total_value - total_value_remainder + 1000).OnlyEnforceIf(actions[Buyer.A_THEOLOGIAN_OF_THE_OLD_SCHOOL])
model.Add(secondary_revenue == 0).OnlyEnforceIf(actions[Buyer.A_THEOLOGIAN_OF_THE_OLD_SCHOOL])
model.Add(difficulty_level == 50*implausibility).OnlyEnforceIf(actions[Buyer.A_THEOLOGIAN_OF_THE_OLD_SCHOOL])
model.Add(added_exhaustion == 0).OnlyEnforceIf(actions[Buyer.A_THEOLOGIAN_OF_THE_OLD_SCHOOL])
del total_value, total_value_remainder
# An Enthusiast of the Ancient World
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.AN_ENTHUSIAST_OF_THE_ANCIENT_WORLD])
model.Add(antiquity > 0).OnlyEnforceIf(actions[Buyer.AN_ENTHUSIAST_OF_THE_ANCIENT_WORLD])
total_value = model.NewIntVar(f'{Buyer.A_PEDAGOGICALLY_INCLINED_GRANDMOTHER.name}: total value', lb = 0)
model.Add(total_value == value + zoological_mania_bonus)
total_value_remainder = model.NewIntVar(f'{Buyer.AN_ENTHUSIAST_OF_THE_ANCIENT_WORLD.name}: total value remainder', lb = 0, ub = 49)
model.AddModuloEquality(total_value_remainder, total_value, 50)
model.Add(primary_revenue == total_value - total_value_remainder).OnlyEnforceIf(actions[Buyer.AN_ENTHUSIAST_OF_THE_ANCIENT_WORLD])
model.Add(secondary_revenue == 250*antiquity + (250 if bone_market_fluctuations == Fluctuation.ANTIQUITY else 0)).OnlyEnforceIf(actions[Buyer.AN_ENTHUSIAST_OF_THE_ANCIENT_WORLD])
model.Add(difficulty_level == 45*implausibility).OnlyEnforceIf(actions[Buyer.AN_ENTHUSIAST_OF_THE_ANCIENT_WORLD])
model.Add(added_exhaustion == 0).OnlyEnforceIf(actions[Buyer.AN_ENTHUSIAST_OF_THE_ANCIENT_WORLD])
del total_value, total_value_remainder
# Mrs Plenty
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.MRS_PLENTY])
model.Add(menace > 0).OnlyEnforceIf(actions[Buyer.MRS_PLENTY])
total_value = model.NewIntVar(f'{Buyer.MRS_PLENTY.name}: total value', lb = 0)
model.Add(total_value == value + zoological_mania_bonus)
total_value_remainder = model.NewIntVar(f'{Buyer.MRS_PLENTY.name}: total value remainder', lb = 0, ub = 49)
model.AddModuloEquality(total_value_remainder, total_value, 50)
model.Add(primary_revenue == total_value - total_value_remainder).OnlyEnforceIf(actions[Buyer.MRS_PLENTY])
model.Add(secondary_revenue == 250*menace).OnlyEnforceIf(actions[Buyer.MRS_PLENTY])
model.Add(difficulty_level == 45*implausibility).OnlyEnforceIf(actions[Buyer.MRS_PLENTY])
model.Add(added_exhaustion == 0).OnlyEnforceIf(actions[Buyer.MRS_PLENTY])
del total_value, total_value_remainder
# A Tentacled Servant
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.A_TENTACLED_SERVANT])
model.Add(amalgamy > 0).OnlyEnforceIf(actions[Buyer.A_TENTACLED_SERVANT])
total_value = model.NewIntVar(f'{Buyer.A_TENTACLED_SERVANT.name}: total value', lb = 0)
model.Add(total_value == value + zoological_mania_bonus)
total_value_remainder = model.NewIntVar(f'{Buyer.A_TENTACLED_SERVANT.name}: total value remainder', lb = 0, ub = 49)
model.AddModuloEquality(total_value_remainder, total_value, 50)
model.Add(primary_revenue == total_value - total_value_remainder + 250).OnlyEnforceIf(actions[Buyer.A_TENTACLED_SERVANT])
model.Add(secondary_revenue == 250*amalgamy + (250 if bone_market_fluctuations == Fluctuation.AMALGAMY else 0)).OnlyEnforceIf(actions[Buyer.A_TENTACLED_SERVANT])
model.Add(difficulty_level == 45*implausibility).OnlyEnforceIf(actions[Buyer.A_TENTACLED_SERVANT])
model.Add(added_exhaustion == 0).OnlyEnforceIf(actions[Buyer.A_TENTACLED_SERVANT])
del total_value, total_value_remainder
# An Investment-Minded Ambassador
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.AN_INVESTMENT_MINDED_AMBASSADOR])
model.Add(antiquity > 0).OnlyEnforceIf(actions[Buyer.AN_INVESTMENT_MINDED_AMBASSADOR])
antiquity_squared_times_four_fifths = model.NewIntVar(f'{Buyer.AN_INVESTMENT_MINDED_AMBASSADOR.name}: antiquity squared times four-fifths', lb = 0)
antiquity_squared_times_four = model.NewIntVar(f'{Buyer.AN_INVESTMENT_MINDED_AMBASSADOR.name}: antiquity squared times four', lb = 0)
model.AddMultiplicationEquality(antiquity_squared_times_four, (antiquity, antiquity, 4))
model.AddDivisionEquality(antiquity_squared_times_four_fifths, antiquity_squared_times_four, 5)
del antiquity_squared_times_four
tailfeathers = model.NewIntVar(f'{Buyer.AN_INVESTMENT_MINDED_AMBASSADOR.name}: tailfeathers')
if bone_market_fluctuations == Fluctuation.ANTIQUITY:
boosted_antiquity = model.NewIntVar(f'{Buyer.AN_INVESTMENT_MINDED_AMBASSADOR.name}: boosted antiquity', lb = 0)
model.AddApproximateExponentiationEquality(boosted_antiquity, antiquity, 2.1, MAXIMUM_ATTRIBUTE)
boosted_antiquity_times_four = model.NewIntVar(f'{Buyer.AN_INVESTMENT_MINDED_AMBASSADOR.name}: boosted antiquity times four', lb = 0)
model.AddMultiplicationEquality(boosted_antiquity_times_four, (boosted_antiquity, 4))
boosted_antiquity_times_four_fifths = model.NewIntVar(f'{Buyer.AN_INVESTMENT_MINDED_AMBASSADOR.name}: boosted antiquity times four-fifths', lb = 0)
model.AddDivisionEquality(boosted_antiquity_times_four_fifths, boosted_antiquity_times_four, 5)
model.Add(tailfeathers == boosted_antiquity_times_four_fifths).OnlyEnforceIf(actions[Buyer.AN_INVESTMENT_MINDED_AMBASSADOR])
del boosted_antiquity, boosted_antiquity_times_four, boosted_antiquity_times_four_fifths
else:
model.Add(tailfeathers == antiquity_squared_times_four_fifths).OnlyEnforceIf(actions[Buyer.AN_INVESTMENT_MINDED_AMBASSADOR])
total_value = model.NewIntVar(f'{Buyer.AN_INVESTMENT_MINDED_AMBASSADOR.name}: total value', lb = 0)
model.Add(total_value == value + zoological_mania_bonus)
total_value_remainder = model.NewIntVar(f'{Buyer.AN_INVESTMENT_MINDED_AMBASSADOR.name}: total value remainder', lb = 0, ub = 49)
model.AddModuloEquality(total_value_remainder, total_value, 50)
extra_value = model.BoolExpression(total_value_remainder >= 0)
model.Add(primary_revenue == total_value + 50*extra_value + 250).OnlyEnforceIf(actions[Buyer.AN_INVESTMENT_MINDED_AMBASSADOR])
model.Add(secondary_revenue == 250*tailfeathers).OnlyEnforceIf(actions[Buyer.AN_INVESTMENT_MINDED_AMBASSADOR])
model.Add(difficulty_level == 75*implausibility).OnlyEnforceIf(actions[Buyer.AN_INVESTMENT_MINDED_AMBASSADOR])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(f'{Buyer.AN_INVESTMENT_MINDED_AMBASSADOR.name}: derived exhaustion', lb = 0)
model.AddDivisionEquality(derived_exhaustion, antiquity_squared_times_four_fifths, 20)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.AN_INVESTMENT_MINDED_AMBASSADOR])
del antiquity_squared_times_four_fifths, tailfeathers, total_value, total_value_remainder, extra_value, derived_exhaustion
# A Teller of Terrors
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.A_TELLER_OF_TERRORS])
model.Add(menace > 0).OnlyEnforceIf(actions[Buyer.A_TELLER_OF_TERRORS])
menace_squared = model.NewIntVar(f'{Buyer.A_TELLER_OF_TERRORS.name}: menace squared', lb = 0)
model.AddMultiplicationEquality(menace_squared, (menace, menace))
feathers = model.NewIntVar(f'{Buyer.A_TELLER_OF_TERRORS.name}: feathers')
if bone_market_fluctuations == Fluctuation.MENACE:
boosted_menace = model.NewIntVar(f'{Buyer.A_TELLER_OF_TERRORS.name}: boosted menace')
model.AddApproximateExponentiationEquality(boosted_menace, menace, 2.1, MAXIMUM_ATTRIBUTE)
model.Add(feathers == 4*boosted_menace).OnlyEnforceIf(actions[Buyer.A_TELLER_OF_TERRORS])
del boosted_menace
else:
model.Add(feathers == 4*menace_squared).OnlyEnforceIf(actions[Buyer.A_TELLER_OF_TERRORS])
total_value = model.NewIntVar(f'{Buyer.A_TELLER_OF_TERRORS.name}: total value', lb = 0)
model.Add(total_value == value + zoological_mania_bonus)
total_value_remainder = model.NewIntVar(f'{Buyer.A_TELLER_OF_TERRORS.name}: total value remainder', lb = 0, ub = 9)
model.AddModuloEquality(total_value_remainder, total_value, 10)
model.Add(primary_revenue == total_value - total_value_remainder + 50).OnlyEnforceIf(actions[Buyer.A_TELLER_OF_TERRORS])
model.Add(secondary_revenue == 50*feathers).OnlyEnforceIf(actions[Buyer.A_TELLER_OF_TERRORS])
model.Add(difficulty_level == 75*implausibility).OnlyEnforceIf(actions[Buyer.A_TELLER_OF_TERRORS])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(f'{Buyer.A_TELLER_OF_TERRORS.name}: derived exhaustion', lb = 0)
model.AddDivisionEquality(derived_exhaustion, menace_squared, 25)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.A_TELLER_OF_TERRORS])
del menace_squared, feathers, total_value, total_value_remainder, derived_exhaustion
# A Tentacled Entrepreneur
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.A_TENTACLED_ENTREPRENEUR])
model.Add(amalgamy > 0).OnlyEnforceIf(actions[Buyer.A_TENTACLED_ENTREPRENEUR])
amalgamy_squared = model.NewIntVar(f'{Buyer.A_TENTACLED_ENTREPRENEUR.name}: amalgamy squared', lb = 0)
model.AddMultiplicationEquality(amalgamy_squared, (amalgamy, amalgamy))
final_breaths = model.NewIntVar(f'{Buyer.A_TENTACLED_ENTREPRENEUR.name}: final breaths')
if bone_market_fluctuations == Fluctuation.AMALGAMY:
boosted_amalgamy = model.NewIntVar(f'{Buyer.A_TENTACLED_ENTREPRENEUR.name}: boosted amalgamy')
model.AddApproximateExponentiationEquality(boosted_amalgamy, amalgamy, 2.1, MAXIMUM_ATTRIBUTE)
model.Add(final_breaths == 4*boosted_amalgamy).OnlyEnforceIf(actions[Buyer.A_TENTACLED_ENTREPRENEUR])
del boosted_amalgamy
else:
model.Add(final_breaths == 4*amalgamy_squared).OnlyEnforceIf(actions[Buyer.A_TENTACLED_ENTREPRENEUR])
total_value = model.NewIntVar(f'{Buyer.A_TENTACLED_ENTREPRENEUR.name}: total value', lb = 0)
model.Add(total_value == value + zoological_mania_bonus)
total_value_remainder = model.NewIntVar(f'{Buyer.A_TENTACLED_ENTREPRENEUR.name}: total value remainder', lb = 0, ub = 49)
model.AddModuloEquality(total_value_remainder, total_value, 50)
model.Add(primary_revenue == total_value - total_value_remainder + 250).OnlyEnforceIf(actions[Buyer.A_TENTACLED_ENTREPRENEUR])
model.Add(secondary_revenue == 50*final_breaths).OnlyEnforceIf(actions[Buyer.A_TENTACLED_ENTREPRENEUR])
model.Add(difficulty_level == 75*implausibility).OnlyEnforceIf(actions[Buyer.A_TENTACLED_ENTREPRENEUR])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(f'{Buyer.A_TENTACLED_ENTREPRENEUR.name}: derived exhaustion', lb = 0)
model.AddDivisionEquality(derived_exhaustion, amalgamy_squared, 25)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.A_TENTACLED_ENTREPRENEUR])
del amalgamy_squared, final_breaths, total_value, total_value_remainder, derived_exhaustion
# An Author of Gothic Tales
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.AN_AUTHOR_OF_GOTHIC_TALES])
model.Add(antiquity > 0).OnlyEnforceIf(actions[Buyer.AN_AUTHOR_OF_GOTHIC_TALES])
model.Add(menace > 0).OnlyEnforceIf(actions[Buyer.AN_AUTHOR_OF_GOTHIC_TALES])
antiquity_times_menace = model.NewIntVar(f'{Buyer.AN_AUTHOR_OF_GOTHIC_TALES.name}: antiquity times menace')
model.AddMultiplicationEquality(antiquity_times_menace, (antiquity, menace))
antiquity_fluctuation_bonus = model.NewIntVar(f'{Buyer.AN_AUTHOR_OF_GOTHIC_TALES.name}: antiquity fluctuation bonus')
model.AddDivisionEquality(antiquity_fluctuation_bonus, antiquity, 2)
menace_fluctuation_bonus = model.NewIntVar(f'{Buyer.AN_AUTHOR_OF_GOTHIC_TALES.name}: menace fluctuation bonus')
model.AddDivisionEquality(menace_fluctuation_bonus, menace, 2)
total_value = model.NewIntVar(f'{Buyer.AN_AUTHOR_OF_GOTHIC_TALES.name}: total value', lb = 0)
model.Add(total_value == value + zoological_mania_bonus)
total_value_remainder = model.NewIntVar(f'{Buyer.AN_AUTHOR_OF_GOTHIC_TALES.name}: total value remainder', lb = 0, ub = 49)
model.AddModuloEquality(total_value_remainder, total_value, 50)
model.Add(primary_revenue == total_value - total_value_remainder + 250).OnlyEnforceIf(actions[Buyer.AN_AUTHOR_OF_GOTHIC_TALES])
model.Add(secondary_revenue == 250*antiquity_times_menace + 250*(antiquity_fluctuation_bonus if bone_market_fluctuations == Fluctuation.ANTIQUITY else menace_fluctuation_bonus if bone_market_fluctuations == Fluctuation.MENACE else 0)).OnlyEnforceIf(actions[Buyer.AN_AUTHOR_OF_GOTHIC_TALES])
model.Add(difficulty_level == 75*implausibility).OnlyEnforceIf(actions[Buyer.AN_AUTHOR_OF_GOTHIC_TALES])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(f'{Buyer.AN_AUTHOR_OF_GOTHIC_TALES.name}: derived exhaustion')
model.AddDivisionEquality(derived_exhaustion, antiquity_times_menace, 20)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.AN_AUTHOR_OF_GOTHIC_TALES])
del antiquity_times_menace, antiquity_fluctuation_bonus, menace_fluctuation_bonus, total_value, total_value_remainder, derived_exhaustion
# A Zailor with Particular Interests
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.A_ZAILOR_WITH_PARTICULAR_INTERESTS])
model.Add(antiquity > 0).OnlyEnforceIf(actions[Buyer.A_ZAILOR_WITH_PARTICULAR_INTERESTS])
model.Add(amalgamy > 0).OnlyEnforceIf(actions[Buyer.A_ZAILOR_WITH_PARTICULAR_INTERESTS])
amalgamy_times_antiquity = model.NewIntVar(f'{Buyer.A_ZAILOR_WITH_PARTICULAR_INTERESTS.name}: amalgamy times antiquity')
model.AddMultiplicationEquality(amalgamy_times_antiquity, (amalgamy, antiquity))
amalgamy_fluctuation_bonus = model.NewIntVar(f'{Buyer.A_ZAILOR_WITH_PARTICULAR_INTERESTS.name}: amalgamy fluctuation bonus')
model.AddDivisionEquality(amalgamy_fluctuation_bonus, amalgamy, 2)
antiquity_fluctuation_bonus = model.NewIntVar(f'{Buyer.A_ZAILOR_WITH_PARTICULAR_INTERESTS.name}: antiquity fluctuation bonus')
model.AddDivisionEquality(antiquity_fluctuation_bonus, antiquity, 2)
total_value = model.NewIntVar(f'{Buyer.A_ZAILOR_WITH_PARTICULAR_INTERESTS.name}: total value', lb = 0)
model.Add(total_value == value + zoological_mania_bonus)
total_value_remainder = model.NewIntVar(f'{Buyer.A_ZAILOR_WITH_PARTICULAR_INTERESTS.name}: total value remainder', lb = 0, ub = 9)
model.AddModuloEquality(total_value_remainder, total_value, 10)
model.Add(primary_revenue == total_value - total_value_remainder + 250).OnlyEnforceIf(actions[Buyer.A_ZAILOR_WITH_PARTICULAR_INTERESTS])
model.Add(secondary_revenue == 250*amalgamy_times_antiquity + 250*(amalgamy_fluctuation_bonus if bone_market_fluctuations == Fluctuation.AMALGAMY else antiquity_fluctuation_bonus if bone_market_fluctuations == Fluctuation.ANTIQUITY else 0)).OnlyEnforceIf(actions[Buyer.A_ZAILOR_WITH_PARTICULAR_INTERESTS])
model.Add(difficulty_level == 75*implausibility).OnlyEnforceIf(actions[Buyer.A_ZAILOR_WITH_PARTICULAR_INTERESTS])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(f'{Buyer.A_ZAILOR_WITH_PARTICULAR_INTERESTS.name}: derived exhaustion')
model.AddDivisionEquality(derived_exhaustion, amalgamy_times_antiquity, 20)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.A_ZAILOR_WITH_PARTICULAR_INTERESTS])
del amalgamy_times_antiquity, amalgamy_fluctuation_bonus, antiquity_fluctuation_bonus, total_value, total_value_remainder, derived_exhaustion
# A Rubbery Collector
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.A_RUBBERY_COLLECTOR])
model.Add(amalgamy > 0).OnlyEnforceIf(actions[Buyer.A_RUBBERY_COLLECTOR])
model.Add(menace > 0).OnlyEnforceIf(actions[Buyer.A_RUBBERY_COLLECTOR])
amalgamy_times_menace = model.NewIntVar(f'{Buyer.A_RUBBERY_COLLECTOR.name}: amalgamy times menace')
model.AddMultiplicationEquality(amalgamy_times_menace, (amalgamy, menace))
amalgamy_fluctuation_bonus = model.NewIntVar(f'{Buyer.A_RUBBERY_COLLECTOR.name}: amalgamy fluctuation bonus')
model.AddDivisionEquality(amalgamy_fluctuation_bonus, amalgamy, 2)
menace_fluctuation_bonus = model.NewIntVar(f'{Buyer.A_RUBBERY_COLLECTOR.name}: menace fluctuation bonus')
model.AddDivisionEquality(menace_fluctuation_bonus, menace, 2)
total_value = model.NewIntVar(f'{Buyer.A_RUBBERY_COLLECTOR.name}: total value', lb = 0)
model.Add(total_value == value + zoological_mania_bonus)
total_value_remainder = model.NewIntVar(f'{Buyer.A_RUBBERY_COLLECTOR.name}: total value remainder', lb = 0, ub = 49)
model.AddModuloEquality(total_value_remainder, total_value, 50)
model.Add(primary_revenue == total_value - total_value_remainder + 250).OnlyEnforceIf(actions[Buyer.A_RUBBERY_COLLECTOR])
model.Add(secondary_revenue == 250*amalgamy_times_menace + 250*(amalgamy_fluctuation_bonus if bone_market_fluctuations == Fluctuation.AMALGAMY else menace_fluctuation_bonus if bone_market_fluctuations == Fluctuation.MENACE else 0)).OnlyEnforceIf(actions[Buyer.A_RUBBERY_COLLECTOR])
model.Add(difficulty_level == 75*implausibility).OnlyEnforceIf(actions[Buyer.A_RUBBERY_COLLECTOR])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(f'{Buyer.A_RUBBERY_COLLECTOR.name}: derived exhaustion')
model.AddDivisionEquality(derived_exhaustion, amalgamy_times_menace, 20)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.A_RUBBERY_COLLECTOR])
del amalgamy_times_menace, amalgamy_fluctuation_bonus, menace_fluctuation_bonus, total_value, total_value_remainder, derived_exhaustion
# A Constable
model.AddLinearExpressionInDomain(skeleton_in_progress, cp_model.Domain.FromFlatIntervals([110, 119])).OnlyEnforceIf(actions[Buyer.A_CONSTABLE])
value_remainder = model.NewIntVar(f'{Buyer.A_CONSTABLE.name}: value remainder', lb = 0, ub = 49)
model.AddModuloEquality(value_remainder, value, 50)
model.Add(primary_revenue == value - value_remainder + 1000).OnlyEnforceIf(actions[Buyer.A_CONSTABLE])
model.Add(secondary_revenue == 0).OnlyEnforceIf(actions[Buyer.A_CONSTABLE])
model.Add(difficulty_level == 50*implausibility).OnlyEnforceIf(actions[Buyer.A_CONSTABLE])
model.Add(added_exhaustion == 0).OnlyEnforceIf(actions[Buyer.A_CONSTABLE])
del value_remainder
# An Enthusiast in Skulls
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.AN_ENTHUSIAST_IN_SKULLS])
model.Add(skulls >= 2).OnlyEnforceIf(actions[Buyer.AN_ENTHUSIAST_IN_SKULLS])
extra_skulls = model.NewIntVar(f'{Buyer.AN_ENTHUSIAST_IN_SKULLS.name}: extra skulls', lb = 0)
model.Add(extra_skulls == skulls - 1).OnlyEnforceIf(actions[Buyer.AN_ENTHUSIAST_IN_SKULLS])
vital_intelligence = model.NewIntVar(f'{Buyer.AN_ENTHUSIAST_IN_SKULLS.name}: vital intelligence')
model.AddApproximateExponentiationEquality(vital_intelligence, extra_skulls, 1.8, MAXIMUM_ATTRIBUTE)
model.Add(primary_revenue == value + zoological_mania_bonus).OnlyEnforceIf(actions[Buyer.AN_ENTHUSIAST_IN_SKULLS])
model.Add(secondary_revenue == 1250*vital_intelligence).OnlyEnforceIf(actions[Buyer.AN_ENTHUSIAST_IN_SKULLS])
model.Add(difficulty_level == 60*implausibility).OnlyEnforceIf(actions[Buyer.AN_ENTHUSIAST_IN_SKULLS])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(f'{Buyer.AN_ENTHUSIAST_IN_SKULLS.name}: derived exhaustion', lb = 0)
model.AddDivisionEquality(derived_exhaustion, vital_intelligence, 4)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.AN_ENTHUSIAST_IN_SKULLS])
del extra_skulls, vital_intelligence, derived_exhaustion
# A Dreary Midnighter
model.AddLinearExpressionInDomain(skeleton_in_progress, cp_model.Domain.FromFlatIntervals([110, 299])).OnlyEnforceIf(actions[Buyer.A_DREARY_MIDNIGHTER])
model.Add(amalgamy <= 0).OnlyEnforceIf(actions[Buyer.A_DREARY_MIDNIGHTER])
model.Add(counter_church <= 0).OnlyEnforceIf(actions[Buyer.A_DREARY_MIDNIGHTER])
total_value = model.NewIntVar(f'{Buyer.A_DREARY_MIDNIGHTER.name}: total value', lb = 0)
model.Add(total_value == value + zoological_mania_bonus)
total_value_remainder = model.NewIntVar(f'{Buyer.A_DREARY_MIDNIGHTER.name}: total value remainder', lb = 0, ub = 2)
model.AddModuloEquality(total_value_remainder, total_value, 3)
model.Add(primary_revenue == total_value - total_value_remainder + 300).OnlyEnforceIf(actions[Buyer.A_DREARY_MIDNIGHTER])
model.Add(secondary_revenue == 250).OnlyEnforceIf(actions[Buyer.A_DREARY_MIDNIGHTER])
model.Add(difficulty_level == 100*implausibility).OnlyEnforceIf(actions[Buyer.A_DREARY_MIDNIGHTER])
model.Add(added_exhaustion == 0).OnlyEnforceIf(actions[Buyer.A_DREARY_MIDNIGHTER])
del total_value, total_value_remainder
# A Colourful Phantasist - Bazaarine
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_BAZAARINE])
model.Add(implausibility >= 2).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_BAZAARINE])
model.Add(amalgamy >= 4).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_BAZAARINE])
amalgamy_times_implausibility = model.NewIntVar(f'{Buyer.A_COLOURFUL_PHANTASIST_BAZAARINE.name}: amalgamy times implausibility')
model.AddMultiplicationEquality(amalgamy_times_implausibility, (amalgamy, implausibility))
bazaarine_poetry = model.NewIntVar(f'{Buyer.A_COLOURFUL_PHANTASIST_BAZAARINE.name}: bazaarine poetry')
model.Add(bazaarine_poetry == amalgamy_times_implausibility + 1)
total_value = model.NewIntVar(f'{Buyer.A_COLOURFUL_PHANTASIST_BAZAARINE.name}: total value', lb = 0)
model.Add(total_value == value + zoological_mania_bonus)
total_value_remainder = model.NewIntVar(f'{Buyer.A_COLOURFUL_PHANTASIST_BAZAARINE.name}: total value remainder', lb = 0, ub = 49)
model.AddModuloEquality(total_value_remainder, total_value, 50)
model.Add(primary_revenue == total_value - total_value_remainder + 100).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_BAZAARINE])
model.Add(secondary_revenue == 250*bazaarine_poetry).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_BAZAARINE])
model.Add(difficulty_level == 0).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_BAZAARINE])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(f'{Buyer.A_COLOURFUL_PHANTASIST_BAZAARINE.name}: derived exhaustion')
model.AddDivisionEquality(derived_exhaustion, bazaarine_poetry, 20)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_BAZAARINE])
del amalgamy_times_implausibility, bazaarine_poetry, total_value, total_value_remainder, derived_exhaustion
# A Colourful Phantasist - Nocturnal
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_NOCTURNAL])
model.Add(implausibility >= 2).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_NOCTURNAL])
model.Add(menace >= 4).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_NOCTURNAL])
menace_times_implausibility = model.NewIntVar(f'{Buyer.A_COLOURFUL_PHANTASIST_NOCTURNAL.name}: menace times implausibility')
model.AddMultiplicationEquality(menace_times_implausibility, (menace, implausibility))
stygian_ivory = model.NewIntVar(f'{Buyer.A_COLOURFUL_PHANTASIST_NOCTURNAL.name}: stygian ivory')
model.Add(stygian_ivory == menace_times_implausibility + 1)
total_value = model.NewIntVar(f'{Buyer.A_COLOURFUL_PHANTASIST_NOCTURNAL.name}: total value', lb = 0)
model.Add(total_value == value + zoological_mania_bonus)
total_value_remainder = model.NewIntVar(f'{Buyer.A_COLOURFUL_PHANTASIST_NOCTURNAL.name}: total value remainder', lb = 0, ub = 49)
model.AddModuloEquality(total_value_remainder, total_value, 50)
model.Add(primary_revenue == total_value - total_value_remainder + 100).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_NOCTURNAL])
model.Add(secondary_revenue == 250*stygian_ivory).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_NOCTURNAL])
model.Add(difficulty_level == 0).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_NOCTURNAL])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(f'{Buyer.A_COLOURFUL_PHANTASIST_NOCTURNAL.name}: derived exhaustion')
model.AddDivisionEquality(derived_exhaustion, stygian_ivory, 20)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_NOCTURNAL])
del menace_times_implausibility, stygian_ivory, total_value, total_value_remainder, derived_exhaustion
# A Colourful Phantasist - Celestial
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_CELESTIAL])
model.Add(implausibility >= 2).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_CELESTIAL])
model.Add(antiquity >= 4).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_CELESTIAL])
antiquity_times_implausibility = model.NewIntVar(f'{Buyer.A_COLOURFUL_PHANTASIST_CELESTIAL.name}: antiquity times implausibility')
model.AddMultiplicationEquality(antiquity_times_implausibility, (antiquity, implausibility))
knob_of_scintillack = model.NewIntVar(f'{Buyer.A_COLOURFUL_PHANTASIST_CELESTIAL.name}: knob of scintillack')
model.Add(knob_of_scintillack == antiquity_times_implausibility + 1)
total_value = model.NewIntVar(f'{Buyer.A_COLOURFUL_PHANTASIST_CELESTIAL.name}: total value', lb = 0)
model.Add(total_value == value + zoological_mania_bonus)
total_value_remainder = model.NewIntVar(f'{Buyer.A_COLOURFUL_PHANTASIST_CELESTIAL.name}: total value remainder', lb = 0, ub = 49)
model.AddModuloEquality(total_value_remainder, total_value, 50)
model.Add(primary_revenue == total_value - total_value_remainder + 100).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_CELESTIAL])
model.Add(secondary_revenue == 250*knob_of_scintillack).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_CELESTIAL])
model.Add(difficulty_level == 0).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_CELESTIAL])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(f'{Buyer.A_COLOURFUL_PHANTASIST_CELESTIAL.name}: derived exhaustion')
model.AddDivisionEquality(derived_exhaustion, knob_of_scintillack, 20)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.A_COLOURFUL_PHANTASIST_CELESTIAL])
del antiquity_times_implausibility, knob_of_scintillack, total_value, total_value_remainder, derived_exhaustion
# An Ingenuous Malacologist
model.Add(tentacles >= 4).OnlyEnforceIf(actions[Buyer.AN_INGENUOUS_MALACOLOGIST])
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.AN_INGENUOUS_MALACOLOGIST])
exponentiated_tentacles = model.NewIntVar(f'{Buyer.AN_INGENUOUS_MALACOLOGIST.name}: exponentiated tentacles', lb = 0)
model.AddApproximateExponentiationEquality(exponentiated_tentacles, tentacles, 2.2, MAXIMUM_ATTRIBUTE)
collated_research = model.NewIntVar(f'{Buyer.AN_INGENUOUS_MALACOLOGIST.name}: collated research')
model.AddDivisionEquality(collated_research, exponentiated_tentacles, 5)
value_remainder = model.NewIntVar(f'{Buyer.AN_INGENUOUS_MALACOLOGIST.name}: value remainder', lb = 0, ub = 249)
model.AddModuloEquality(value_remainder, value, 250)
model.Add(primary_revenue == value - value_remainder + 250).OnlyEnforceIf(actions[Buyer.AN_INGENUOUS_MALACOLOGIST])
model.Add(secondary_revenue == 250*collated_research).OnlyEnforceIf(actions[Buyer.AN_INGENUOUS_MALACOLOGIST])
model.Add(difficulty_level == 60*implausibility).OnlyEnforceIf(actions[Buyer.AN_INGENUOUS_MALACOLOGIST])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(f'{Buyer.AN_INGENUOUS_MALACOLOGIST.name}: derived exhaustion')
model.AddDivisionEquality(derived_exhaustion, exponentiated_tentacles, 100)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.AN_INGENUOUS_MALACOLOGIST])
del exponentiated_tentacles, collated_research, value_remainder, derived_exhaustion
# An Enterprising Boot Salesman
model.Add(menace <= 0).OnlyEnforceIf(actions[Buyer.AN_ENTERPRISING_BOOT_SALESMAN])
model.Add(amalgamy <= 0).OnlyEnforceIf(actions[Buyer.AN_ENTERPRISING_BOOT_SALESMAN])
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.AN_ENTERPRISING_BOOT_SALESMAN])
model.Add(legs >= 4).OnlyEnforceIf(actions[Buyer.AN_ENTERPRISING_BOOT_SALESMAN])
diamonds = model.NewIntVar(f'{Buyer.AN_ENTERPRISING_BOOT_SALESMAN.name}: diamonds', lb = 0)
model.AddApproximateExponentiationEquality(diamonds, legs, 2.2, MAXIMUM_ATTRIBUTE)
total_value = model.NewIntVar(f'{Buyer.AN_ENTERPRISING_BOOT_SALESMAN.name}: total value', lb = 0)
model.Add(total_value == value + zoological_mania_bonus)
total_value_remainder = model.NewIntVar(f'{Buyer.AN_ENTERPRISING_BOOT_SALESMAN.name}: total value remainder', lb = 0, ub = 49)
model.AddModuloEquality(total_value_remainder, total_value, 50)
model.Add(primary_revenue == total_value - total_value_remainder).OnlyEnforceIf(actions[Buyer.AN_ENTERPRISING_BOOT_SALESMAN])
model.Add(secondary_revenue == 50*diamonds).OnlyEnforceIf(actions[Buyer.AN_ENTERPRISING_BOOT_SALESMAN])
model.Add(difficulty_level == 0).OnlyEnforceIf(actions[Buyer.AN_ENTERPRISING_BOOT_SALESMAN])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(f'{Buyer.AN_ENTERPRISING_BOOT_SALESMAN.name}: derived exhaustion')
model.AddDivisionEquality(derived_exhaustion, diamonds, 100)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.AN_ENTERPRISING_BOOT_SALESMAN])
del diamonds, total_value, total_value_remainder, derived_exhaustion
# The Dumbwaiter of Balmoral
model.AddLinearExpressionInDomain(skeleton_in_progress, cp_model.Domain.FromFlatIntervals([180, 189])).OnlyEnforceIf(actions[Buyer.THE_DUMBWAITER_OF_BALMORAL])
model.Add(value >= 250).OnlyEnforceIf(actions[Buyer.THE_DUMBWAITER_OF_BALMORAL])
value_remainder = model.NewIntVar(f'{Buyer.THE_DUMBWAITER_OF_BALMORAL.name}: value remainder', lb = 0, ub = 249)
model.AddModuloEquality(value_remainder, value, 250)
model.Add(primary_revenue == value - value_remainder).OnlyEnforceIf(actions[Buyer.THE_DUMBWAITER_OF_BALMORAL])
model.Add(secondary_revenue == 0).OnlyEnforceIf(actions[Buyer.THE_DUMBWAITER_OF_BALMORAL])
model.Add(difficulty_level == 200).OnlyEnforceIf(actions[Buyer.THE_DUMBWAITER_OF_BALMORAL])
model.Add(added_exhaustion == 0).OnlyEnforceIf(actions[Buyer.THE_DUMBWAITER_OF_BALMORAL])
del value_remainder
# The Carpenter's Granddaughter
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.THE_CARPENTERS_GRANDDAUGHTER])
model.Add(value + zoological_mania_bonus >= 30000).OnlyEnforceIf(actions[Buyer.THE_CARPENTERS_GRANDDAUGHTER])
model.Add(primary_revenue == 31250).OnlyEnforceIf(actions[Buyer.THE_CARPENTERS_GRANDDAUGHTER])
model.Add(secondary_revenue == 0).OnlyEnforceIf(actions[Buyer.THE_CARPENTERS_GRANDDAUGHTER])
model.Add(difficulty_level == 100*implausibility).OnlyEnforceIf(actions[Buyer.THE_CARPENTERS_GRANDDAUGHTER])
model.Add(added_exhaustion == 0).OnlyEnforceIf(actions[Buyer.THE_CARPENTERS_GRANDDAUGHTER])
# The Trifling Diplomat - Amalgamy
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_AMALGAMY])
model.Add(amalgamy >= 5).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_AMALGAMY])
amalgamy_squared = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_AMALGAMY.name}: amalgamy squared', lb = 0)
model.AddMultiplicationEquality(amalgamy_squared, (amalgamy, amalgamy))
value_remainder = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_AMALGAMY.name}: value remainder', lb = 0, ub = 49)
model.AddModuloEquality(value_remainder, value, 50)
model.Add(primary_revenue == value - value_remainder + 50).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_AMALGAMY])
model.Add(secondary_revenue == 50*amalgamy_squared).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_AMALGAMY])
model.Add(difficulty_level == 0).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_AMALGAMY])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_AMALGAMY.name}: derived exhaustion')
model.AddDivisionEquality(derived_exhaustion, amalgamy_squared, 100)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_AMALGAMY])
del amalgamy_squared, value_remainder, derived_exhaustion
# The Trifling Diplomat - Antiquity
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_ANTIQUITY])
model.Add(antiquity >= 5).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_ANTIQUITY])
antiquity_squared = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_ANTIQUITY.name}: antiquity squared', lb = 0)
model.AddMultiplicationEquality(antiquity_squared, (antiquity, antiquity))
value_remainder = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_ANTIQUITY.name}: value remainder', lb = 0, ub = 49)
model.AddModuloEquality(value_remainder, value, 50)
model.Add(primary_revenue == value - value_remainder + 50).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_ANTIQUITY])
model.Add(secondary_revenue == 50*antiquity_squared).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_ANTIQUITY])
model.Add(difficulty_level == 0).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_ANTIQUITY])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_ANTIQUITY.name}: derived exhaustion')
model.AddDivisionEquality(derived_exhaustion, antiquity_squared, 100)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_ANTIQUITY])
del antiquity_squared, value_remainder, derived_exhaustion
# The Trifling Diplomat - Bird
model.AddLinearExpressionInDomain(skeleton_in_progress, cp_model.Domain.FromFlatIntervals([180, 189])).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_BIRD])
non_negative_amalgamy = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_BIRD.name}: non-negative amalgamy')
model.AddMaxEquality(non_negative_amalgamy, [amalgamy, 0])
non_negative_menace = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_BIRD.name}: non-negative menace')
model.AddMaxEquality(non_negative_menace, [menace, 0])
non_negative_antiquity = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_BIRD.name}: non-negative antiquity')
model.AddMaxEquality(non_negative_antiquity, [antiquity, 0])
compromising_documents = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_BIRD.name}: compromising documents', lb = 0)
model.AddMultiplicationEquality(compromising_documents, (non_negative_amalgamy, non_negative_menace, non_negative_antiquity))
value_remainder = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_BIRD.name}: value remainder', lb = 0, ub = 49)
model.AddModuloEquality(value_remainder, value, 50)
model.Add(primary_revenue == value - value_remainder + 50).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_BIRD])
model.Add(secondary_revenue == 50*compromising_documents).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_BIRD])
model.Add(difficulty_level == 0).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_BIRD])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_BIRD.name}: derived exhaustion')
model.AddDivisionEquality(derived_exhaustion, compromising_documents, 100)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_BIRD])
del non_negative_amalgamy, non_negative_menace, non_negative_antiquity, compromising_documents, value_remainder, derived_exhaustion
# The Trifling Diplomat - Fish
model.AddLinearExpressionInDomain(skeleton_in_progress, cp_model.Domain.FromFlatIntervals([190, 199])).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_FISH])
non_negative_amalgamy = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_FISH.name}: non-negative amalgamy')
model.AddMaxEquality(non_negative_amalgamy, [amalgamy, 0])
non_negative_menace = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_FISH.name}: non-negative menace')
model.AddMaxEquality(non_negative_menace, [menace, 0])
non_negative_antiquity = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_FISH.name}: non-negative antiquity')
model.AddMaxEquality(non_negative_antiquity, [antiquity, 0])
compromising_documents = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_FISH.name}: compromising documents', lb = 0)
model.AddMultiplicationEquality(compromising_documents, (non_negative_amalgamy, non_negative_menace, non_negative_antiquity))
value_remainder = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_FISH.name}: value remainder', lb = 0, ub = 49)
model.AddModuloEquality(value_remainder, value, 50)
model.Add(primary_revenue == value - value_remainder + 50).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_FISH])
model.Add(secondary_revenue == 50*compromising_documents).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_FISH])
model.Add(difficulty_level == 0).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_FISH])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_FISH.name}: derived exhaustion')
model.AddDivisionEquality(derived_exhaustion, compromising_documents, 100)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_FISH])
del non_negative_amalgamy, non_negative_menace, non_negative_antiquity, compromising_documents, value_remainder, derived_exhaustion
# The Trifling Diplomat - Insect
model.AddLinearExpressionInDomain(skeleton_in_progress, cp_model.Domain.FromFlatIntervals([210, 219])).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_INSECT])
non_negative_amalgamy = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_INSECT.name}: non-negative amalgamy')
model.AddMaxEquality(non_negative_amalgamy, [amalgamy, 0])
non_negative_menace = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_INSECT.name}: non-negative menace')
model.AddMaxEquality(non_negative_menace, [menace, 0])
non_negative_antiquity = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_INSECT.name}: non-negative antiquity')
model.AddMaxEquality(non_negative_antiquity, [antiquity, 0])
compromising_documents = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_INSECT.name}: compromising documents', lb = 0)
model.AddMultiplicationEquality(compromising_documents, (non_negative_amalgamy, non_negative_menace, non_negative_antiquity))
value_remainder = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_INSECT.name}: value remainder', lb = 0, ub = 49)
model.AddModuloEquality(value_remainder, value, 50)
model.Add(primary_revenue == value - value_remainder + 50).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_INSECT])
model.Add(secondary_revenue == 50*compromising_documents).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_INSECT])
model.Add(difficulty_level == 0).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_INSECT])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_INSECT.name}: derived exhaustion')
model.AddDivisionEquality(derived_exhaustion, compromising_documents, 100)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_INSECT])
del non_negative_amalgamy, non_negative_menace, non_negative_antiquity, compromising_documents, value_remainder, derived_exhaustion
# The Trifling Diplomat - Reptile
model.AddLinearExpressionInDomain(skeleton_in_progress, cp_model.Domain.FromFlatIntervals([160, 169])).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_REPTILE])
non_negative_amalgamy = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_REPTILE.name}: non-negative amalgamy')
model.AddMaxEquality(non_negative_amalgamy, [amalgamy, 0])
non_negative_menace = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_REPTILE.name}: non-negative menace')
model.AddMaxEquality(non_negative_menace, [menace, 0])
non_negative_antiquity = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_REPTILE.name}: non-negative antiquity')
model.AddMaxEquality(non_negative_antiquity, [antiquity, 0])
compromising_documents = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_REPTILE.name}: compromising documents', lb = 0)
model.AddMultiplicationEquality(compromising_documents, (non_negative_amalgamy, non_negative_menace, non_negative_antiquity))
value_remainder = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_REPTILE.name}: value remainder', lb = 0, ub = 49)
model.AddModuloEquality(value_remainder, value, 50)
model.Add(primary_revenue == value - value_remainder + 50).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_REPTILE])
model.Add(secondary_revenue == 50*compromising_documents).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_REPTILE])
model.Add(difficulty_level == 0).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_REPTILE])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_REPTILE.name}: derived exhaustion')
model.AddDivisionEquality(derived_exhaustion, compromising_documents, 100)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_REPTILE])
del non_negative_amalgamy, non_negative_menace, non_negative_antiquity, compromising_documents, value_remainder, derived_exhaustion
# The Trifling Diplomat - Skulls
model.Add(skeleton_in_progress >= 100).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_SKULLS])
model.Add(skulls >= 5).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_SKULLS])
non_negative_amalgamy = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_SKULLS.name}: non-negative amalgamy')
model.AddMaxEquality(non_negative_amalgamy, [amalgamy, 0])
non_negative_menace = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_SKULLS.name}: non-negative menace')
model.AddMaxEquality(non_negative_menace, [menace, 0])
non_negative_antiquity = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_SKULLS.name}: non-negative antiquity')
model.AddMaxEquality(non_negative_antiquity, [antiquity, 0])
compromising_documents = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_SKULLS.name}: compromising documents', lb = 0)
model.AddMultiplicationEquality(compromising_documents, (non_negative_amalgamy, non_negative_menace, non_negative_antiquity))
value_remainder = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_SKULLS.name}: value remainder', lb = 0, ub = 49)
model.AddModuloEquality(value_remainder, value, 50)
model.Add(primary_revenue == value - value_remainder + 50).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_SKULLS])
model.Add(secondary_revenue == 50*compromising_documents).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_SKULLS])
model.Add(difficulty_level == 0).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_SKULLS])
# The indirection is necessary for applying an enforcement literal
derived_exhaustion = model.NewIntVar(f'{Buyer.THE_TRIFLING_DIPLOMAT_SKULLS.name}: derived exhaustion')
model.AddDivisionEquality(derived_exhaustion, compromising_documents, 100)
model.Add(added_exhaustion == derived_exhaustion).OnlyEnforceIf(actions[Buyer.THE_TRIFLING_DIPLOMAT_SKULLS])
del non_negative_amalgamy, non_negative_menace, non_negative_antiquity, compromising_documents, value_remainder, derived_exhaustion
# Maximize profit margin
net_profit = model.NewIntVar('net profit')
model.Add(net_profit == total_revenue - cost)
# This is necessary to preserve some degree of precision after dividing
multiplied_net_profit = model.NewIntVar('multiplied net profit', lb = cp_model.INT32_MIN*PROFIT_MARGIN_MULTIPLIER, ub = cp_model.INT32_MAX*PROFIT_MARGIN_MULTIPLIER)
model.AddMultiplicationEquality(multiplied_net_profit, (net_profit, PROFIT_MARGIN_MULTIPLIER))
absolute_multiplied_net_profit = model.NewIntVar('absolute multiplied net profit', lb = 0, ub = cp_model.INT32_MAX*PROFIT_MARGIN_MULTIPLIER)
model.AddAbsEquality(absolute_multiplied_net_profit, multiplied_net_profit)
absolute_profit_margin = model.NewIntVar('absolute profit margin', lb = cp_model.INT32_MIN*PROFIT_MARGIN_MULTIPLIER, ub = cp_model.INT32_MAX*PROFIT_MARGIN_MULTIPLIER)
model.AddDivisionEquality(absolute_profit_margin, absolute_multiplied_net_profit, total_revenue)
profit_margin = model.NewIntVar('profit margin', lb = cp_model.INT32_MIN*PROFIT_MARGIN_MULTIPLIER, ub = cp_model.INT32_MAX*PROFIT_MARGIN_MULTIPLIER)
positive_net_profit = model.BoolExpression(net_profit >= 0)
model.Add(profit_margin == absolute_profit_margin).OnlyEnforceIf(positive_net_profit)
model.Add(profit_margin == absolute_profit_margin*-1).OnlyEnforceIf(positive_net_profit.Not())
del multiplied_net_profit, absolute_multiplied_net_profit, absolute_profit_margin, positive_net_profit
model.Maximize(profit_margin)
class SkeletonPrinter(cp_model.CpSolverSolutionCallback):
"""A class that prints the steps that comprise a skeleton as well as relevant attributes."""
__slots__ = 'this', '__solution_count'
def __init__(self):
cp_model.CpSolverSolutionCallback.__init__(self)
self.__solution_count = 0
def PrintableSolution(self, solver = None):
"""Print the latest solution of a provided solver."""
output = ""
# Allows use as a callback
if solver is None:
solver = self
for action in actions.keys():
for _ in range(int(solver.Value(actions[action]))):
output += str(action) + "\n"
output += f"""
Profit: £{solver.Value(net_profit)/100:,.2f}
Profit Margin: {solver.Value(profit_margin)/PROFIT_MARGIN_MULTIPLIER:+,.2%}
Total Revenue: £{solver.Value(total_revenue)/100:,.2f}
Primary Revenue: £{solver.Value(primary_revenue)/100:,.2f}
Secondary Revenue: £{solver.Value(secondary_revenue)/100:,.2f}
Cost: £{solver.Value(cost)/100:,.2f}
Value: £{solver.Value(value)/100:,.2f}
Amalgamy: {solver.Value(amalgamy):n}
Antiquity: {solver.Value(antiquity):n}
Menace: {solver.Value(menace):n}
Counter-Church: {solver.Value(counter_church):n}
Implausibility: {solver.Value(implausibility):n}
Exhaustion: {solver.Value(exhaustion):n}"""
return output
def OnSolutionCallback(self):
self.__solution_count += 1
# Prints current solution to window
stdscr.clear()
stdscr.addstr(self.PrintableSolution())
stdscr.addstr(stdscr.getmaxyx()[0] - 1, 0, f"Skeleton #{self.__solution_count:n}")
stdscr.refresh()
def SolutionCount(self):
return self.__solution_count
printer = SkeletonPrinter()
solver = cp_model.CpSolver()
solver.parameters.num_search_workers = workers
solver.parameters.max_time_in_seconds = time_limit
# There's no window in verbose mode
if stdscr is None:
solver.parameters.log_search_progress = True
solver.Solve(model)
else:
solver.SolveWithSolutionCallback(model, printer)
status = solver.StatusName()
if status == 'INFEASIBLE':
raise RuntimeError("There is no satisfactory skeleton.")
elif status == 'FEASIBLE':
print("WARNING: skeleton may be suboptimal.")
elif status != 'OPTIMAL':
raise RuntimeError(f"Unknown status returned: {status}.")
return printer.PrintableSolution(solver)