From 17add382ec5bc4cc023a2e650b9f371019ef7860 Mon Sep 17 00:00:00 2001 From: Jeremy Saklad Date: Fri, 24 Sep 2021 22:04:54 -0500 Subject: [PATCH] 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. --- bonemarketsolver/objects/bone_market_model.py | 9 ++-- bonemarketsolver/solve.py | 54 +++++++++---------- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/bonemarketsolver/objects/bone_market_model.py b/bonemarketsolver/objects/bone_market_model.py index 9a76161..30a576a 100644 --- a/bonemarketsolver/objects/bone_market_model.py +++ b/bonemarketsolver/objects/bone_market_model.py @@ -1,6 +1,6 @@ __author__ = "Jeremy Saklad" -from functools import reduce +from functools import cache, reduce from ortools.sat.python import cp_model @@ -44,10 +44,13 @@ Each parameter is interpreted as a BoundedLinearExpression, and a layer of indir # Avoid mutating parameter directly return Multiply(target, variables.copy() if isinstance(variables, list) else list(variables)) - def NewIntermediateBoolVar(self, name, linear_exp, domain): + @cache + def BoolExpression(self, bounded_linear_exp): """Add a fully-reified implication using an intermediate Boolean variable.""" - intermediate = self.NewBoolVar(name) + intermediate = self.NewBoolVar(str(bounded_linear_exp)) + linear_exp = bounded_linear_exp.Expression() + domain = cp_model.Domain(*bounded_linear_exp.Bounds()) self.AddLinearExpressionInDomain(linear_exp, domain).OnlyEnforceIf(intermediate) self.AddLinearExpressionInDomain(linear_exp, domain.Complement()).OnlyEnforceIf(intermediate.Not()) return intermediate diff --git a/bonemarketsolver/solve.py b/bonemarketsolver/solve.py index c603298..48accd9 100644 --- a/bonemarketsolver/solve.py +++ b/bonemarketsolver/solve.py @@ -334,103 +334,103 @@ def Solve(shadowy_level, bone_market_fluctuations = None, zoological_mania = Non # Humanoid model.Add(skeleton_in_progress == 110) \ .OnlyEnforceIf(actions[Declaration.HUMANOID]) \ - .OnlyEnforceIf(model.NewIntermediateBoolVar('humanoid antiquity', antiquity, cp_model.Domain.FromFlatIntervals([cp_model.INT_MIN, 0]))) + .OnlyEnforceIf(model.BoolExpression(antiquity <= 0)) # Ancient Humanoid (UNCERTAIN) model.Add(skeleton_in_progress == 111) \ .OnlyEnforceIf(actions[Declaration.HUMANOID]) \ - .OnlyEnforceIf(model.NewIntermediateBoolVar('ancient humanoid antiquity', antiquity, cp_model.Domain.FromFlatIntervals([1, 5]))) + .OnlyEnforceIf(model.BoolExpression(cp_model.BoundedLinearExpression(antiquity, (1, 5)))) # Neanderthal model.Add(skeleton_in_progress == 112) \ .OnlyEnforceIf(actions[Declaration.HUMANOID]) \ - .OnlyEnforceIf(model.NewIntermediateBoolVar('neanderthal antiquity', antiquity, cp_model.Domain.FromFlatIntervals([6, cp_model.INT_MAX]))) + .OnlyEnforceIf(model.BoolExpression(antiquity >= 6)) # Ape (UNCERTAIN) model.Add(skeleton_in_progress == 120) \ .OnlyEnforceIf(actions[Declaration.APE]) \ - .OnlyEnforceIf(model.NewIntermediateBoolVar('ape antiquity', antiquity, cp_model.Domain.FromFlatIntervals([cp_model.INT_MIN, 1]))) + .OnlyEnforceIf(model.BoolExpression(antiquity <= 1)) # Primordial Ape (UNCERTAIN) model.Add(skeleton_in_progress == 121) \ .OnlyEnforceIf(actions[Declaration.APE]) \ - .OnlyEnforceIf(model.NewIntermediateBoolVar('primordial ape antiquity', antiquity, cp_model.Domain.FromFlatIntervals([2, cp_model.INT_MAX]))) + .OnlyEnforceIf(model.BoolExpression(antiquity >= 2)) # Monkey model.Add(skeleton_in_progress == 125) \ .OnlyEnforceIf(actions[Declaration.MONKEY]) \ - .OnlyEnforceIf(model.NewIntermediateBoolVar('monkey antiquity', antiquity, cp_model.Domain.FromFlatIntervals([cp_model.INT_MIN, 0]))) + .OnlyEnforceIf(model.BoolExpression(antiquity <= 0)) # Catarrhine Monkey (UNCERTAIN) model.Add(skeleton_in_progress == 126) \ .OnlyEnforceIf(actions[Declaration.MONKEY]) \ - .OnlyEnforceIf(model.NewIntermediateBoolVar('catarrhine monkey 126 antiquity', antiquity, cp_model.Domain.FromFlatIntervals([1, 8]))) + .OnlyEnforceIf(model.BoolExpression(cp_model.BoundedLinearExpression(antiquity, (1, 8)))) # Catarrhine Monkey model.Add(skeleton_in_progress == 128) \ .OnlyEnforceIf(actions[Declaration.MONKEY]) \ - .OnlyEnforceIf(model.NewIntermediateBoolVar('catarrhine monkey 128 antiquity', antiquity, cp_model.Domain.FromFlatIntervals([9, cp_model.INT_MAX]))) + .OnlyEnforceIf(model.BoolExpression(antiquity >= 9)) # Crocodile model.Add(skeleton_in_progress == 160) \ .OnlyEnforceIf(actions[Declaration.REPTILE]) \ - .OnlyEnforceIf(model.NewIntermediateBoolVar('crocodile antiquity', antiquity, cp_model.Domain.FromFlatIntervals([cp_model.INT_MIN, 1]))) + .OnlyEnforceIf(model.BoolExpression(antiquity <= 1)) # Dinosaur model.Add(skeleton_in_progress == 161) \ .OnlyEnforceIf(actions[Declaration.REPTILE]) \ - .OnlyEnforceIf(model.NewIntermediateBoolVar('dinosaur antiquity', antiquity, cp_model.Domain.FromFlatIntervals([2, 4]))) + .OnlyEnforceIf(model.BoolExpression(cp_model.BoundedLinearExpression(antiquity, (2, 4)))) # Mesosaur (UNCERTAIN) model.Add(skeleton_in_progress == 162) \ .OnlyEnforceIf(actions[Declaration.REPTILE]) \ - .OnlyEnforceIf(model.NewIntermediateBoolVar('mesosaur antiquity', antiquity, cp_model.Domain.FromFlatIntervals([5, cp_model.INT_MAX]))) + .OnlyEnforceIf(model.BoolExpression(antiquity >= 5)) # Toad model.Add(skeleton_in_progress == 170) \ .OnlyEnforceIf(actions[Declaration.AMPHIBIAN]) \ - .OnlyEnforceIf(model.NewIntermediateBoolVar('toad antiquity', antiquity, cp_model.Domain.FromFlatIntervals([cp_model.INT_MIN, 1]))) + .OnlyEnforceIf(model.BoolExpression(antiquity <= 1)) # Primordial Amphibian model.Add(skeleton_in_progress == 171) \ .OnlyEnforceIf(actions[Declaration.AMPHIBIAN]) \ - .OnlyEnforceIf(model.NewIntermediateBoolVar('primordial amphibian antiquity', antiquity, cp_model.Domain.FromFlatIntervals([2, 4]))) + .OnlyEnforceIf(model.BoolExpression(cp_model.BoundedLinearExpression(antiquity, (2, 4)))) # Temnospondyl model.Add(skeleton_in_progress == 172) \ .OnlyEnforceIf(actions[Declaration.AMPHIBIAN]) \ - .OnlyEnforceIf(model.NewIntermediateBoolVar('temnospondyl antiquity', antiquity, cp_model.Domain.FromFlatIntervals([5, cp_model.INT_MAX]))) + .OnlyEnforceIf(model.BoolExpression(antiquity >= 5)) # Owl model.Add(skeleton_in_progress == 180) \ .OnlyEnforceIf(actions[Declaration.BIRD]) \ - .OnlyEnforceIf(model.NewIntermediateBoolVar('owl antiquity', antiquity, cp_model.Domain.FromFlatIntervals([cp_model.INT_MIN, 1]))) + .OnlyEnforceIf(model.BoolExpression(antiquity <= 1)) # Archaeopteryx model.Add(skeleton_in_progress == 181) \ .OnlyEnforceIf(actions[Declaration.BIRD]) \ - .OnlyEnforceIf(model.NewIntermediateBoolVar('archaeopteryx antiquity', antiquity, cp_model.Domain.FromFlatIntervals([2, 4]))) + .OnlyEnforceIf(model.BoolExpression(cp_model.BoundedLinearExpression(antiquity, (2, 4)))) # Ornithomimosaur (UNCERTAIN) model.Add(skeleton_in_progress == 182) \ .OnlyEnforceIf(actions[Declaration.BIRD]) \ - .OnlyEnforceIf(model.NewIntermediateBoolVar('ornithomimosaur antiquity', antiquity, cp_model.Domain.FromFlatIntervals([5, cp_model.INT_MAX]))) + .OnlyEnforceIf(model.BoolExpression(antiquity >= 5)) # Lamprey model.Add(skeleton_in_progress == 190) \ .OnlyEnforceIf(actions[Declaration.FISH]) \ - .OnlyEnforceIf(model.NewIntermediateBoolVar('lamprey antiquity', antiquity, cp_model.Domain.FromFlatIntervals([cp_model.INT_MIN, 0]))) + .OnlyEnforceIf(model.BoolExpression(antiquity <= 0)) # Coelacanth (UNCERTAIN) model.Add(skeleton_in_progress == 191) \ .OnlyEnforceIf(actions[Declaration.FISH]) \ - .OnlyEnforceIf(model.NewIntermediateBoolVar('coelacanth antiquity', antiquity, cp_model.Domain.FromFlatIntervals([1, cp_model.INT_MAX]))) + .OnlyEnforceIf(model.BoolExpression(antiquity >= 1)) # Spider (UNCERTAIN) model.Add(skeleton_in_progress == 200) \ .OnlyEnforceIf(actions[Declaration.SPIDER]) \ - .OnlyEnforceIf(model.NewIntermediateBoolVar('spider antiquity', antiquity, cp_model.Domain.FromFlatIntervals([cp_model.INT_MIN, 1]))) + .OnlyEnforceIf(model.BoolExpression(antiquity <= 1)) # Primordial Orb-Weaver (UNCERTAIN) model.Add(skeleton_in_progress == 201) \ .OnlyEnforceIf(actions[Declaration.SPIDER]) \ - .OnlyEnforceIf(model.NewIntermediateBoolVar('primordial orb-weaver antiquity', antiquity, cp_model.Domain.FromFlatIntervals([2, 7]))) + .OnlyEnforceIf(model.BoolExpression(cp_model.BoundedLinearExpression(antiquity, (2, 7)))) # Trigonotarbid model.Add(skeleton_in_progress == 203) \ .OnlyEnforceIf(actions[Declaration.SPIDER]) \ - .OnlyEnforceIf(model.NewIntermediateBoolVar('trigonotarbid antiquity', antiquity, cp_model.Domain.FromFlatIntervals([8, cp_model.INT_MAX]))) + .OnlyEnforceIf(model.BoolExpression(antiquity >= 8)) # Beetle (UNCERTAIN) model.Add(skeleton_in_progress == 210) \ .OnlyEnforceIf(actions[Declaration.INSECT]) \ - .OnlyEnforceIf(model.NewIntermediateBoolVar('beetle antiquity', antiquity, cp_model.Domain.FromFlatIntervals([cp_model.INT_MIN, 1]))) + .OnlyEnforceIf(model.BoolExpression(antiquity <= 1)) # Primordial Beetle (UNCERTAIN) model.Add(skeleton_in_progress == 211) \ .OnlyEnforceIf(actions[Declaration.INSECT]) \ - .OnlyEnforceIf(model.NewIntermediateBoolVar('primordial beetle antiquity', antiquity, cp_model.Domain.FromFlatIntervals([2, 6]))) + .OnlyEnforceIf(model.BoolExpression(cp_model.BoundedLinearExpression(antiquity, (2, 6)))) # Rhyniognatha model.Add(skeleton_in_progress == 212) \ .OnlyEnforceIf(actions[Declaration.INSECT]) \ - .OnlyEnforceIf(model.NewIntermediateBoolVar('rhyniognatha antiquity', antiquity, cp_model.Domain.FromFlatIntervals([7, cp_model.INT_MAX]))) + .OnlyEnforceIf(model.BoolExpression(antiquity >= 7)) # Curator model.Add(skeleton_in_progress == 300) \ .OnlyEnforceIf(actions[Declaration.CURATOR]) @@ -702,7 +702,7 @@ def Solve(shadowy_level, bone_market_fluctuations = None, zoological_mania = Non 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.NewIntermediateBoolVar(f'{Buyer.AN_INVESTMENT_MINDED_AMBASSADOR.name}: extra value', total_value_remainder, cp_model.Domain.FromFlatIntervals([0, cp_model.INT32_MAX])) + 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]) @@ -1336,7 +1336,7 @@ def Solve(shadowy_level, bone_market_fluctuations = None, zoological_mania = Non 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.NewIntermediateBoolVar('positive net profit', net_profit, cp_model.Domain.FromFlatIntervals([0, cp_model.INT32_MAX])) + 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())