From bb21eccca83ee32b11e99665b456ec1b578d5c9f Mon Sep 17 00:00:00 2001 From: Jeremy Saklad Date: Thu, 26 May 2022 08:47:52 -0600 Subject: [PATCH] feat: Add Segmented Ribcage to appendages Segmented Ribcages can be used to add more limb slots, but carry a variety of unusual requirements. In addition to a scaling cost in Nevercold Brass, they require an unfilled tail slot when applied. It may be prudent to clean up the partial sum formula using helper methods in the future, as it currently requires a considerable number of intermediate terms. itertools.repeat is now being used to perform exponentiation, rather than repeating terms directly. --- bonemarketsolver/data/appendages.py | 11 +++ bonemarketsolver/solve.py | 144 +++++++++++++++++++++++++++- 2 files changed, 152 insertions(+), 3 deletions(-) diff --git a/bonemarketsolver/data/appendages.py b/bonemarketsolver/data/appendages.py index 53c896c..9d9313e 100644 --- a/bonemarketsolver/data/appendages.py +++ b/bonemarketsolver/data/appendages.py @@ -19,6 +19,17 @@ class Appendage(Enum): amalgamy = 2 ) + # Cost from this scales with segments and is partially implemented separately + # Requires Torso Style 110 + # Requires initial tail slot + SEGMENTED_RIBCAGE = Action( + "Extend the tail end with another Segmented Ribcage", + cost = Cost.ACTION.value + Cost.SEGMENTED_RIBCAGE.value, + value = 250, + limbs_needed = 2, + segments = 1 + ) + ALBATROSS_WING = Action( "Put an Albatross Wing on your (Skeleton Type)", cost = Cost.ACTION.value + Cost.ALBATROSS_WING.value, diff --git a/bonemarketsolver/solve.py b/bonemarketsolver/solve.py index c27e203..514f93e 100644 --- a/bonemarketsolver/solve.py +++ b/bonemarketsolver/solve.py @@ -4,6 +4,7 @@ __all__ = ['Adjustment', 'Appendage', 'Buyer', 'Declaration', 'DiplomatFascinati __author__ = "Jeremy Saklad" from functools import partialmethod +from itertools import chain, repeat from os import cpu_count from ortools.sat.python import cp_model @@ -328,10 +329,139 @@ def Solve(shadowy_level, bone_market_fluctuations = None, zoological_mania = Non del add_joints, add_joints_amber_cost_multiple - cost = model.NewIntVar('cost', lb = 0, ub = maximum_cost) - model.Add(cost == cp_model.LinearExpr.WeightedSum(actions.values(), [int(action.value.cost) for action in actions.keys()]) + add_joints_amber_cost + sale_cost) + # Calculate cost of adding segments. + # This is a partial sum formula. + add_segments_brass_cost = model.NewIntVar('add segments brass cost', lb = 0) - del sale_cost, add_joints_amber_cost + add_segments = actions[Appendage.SEGMENTED_RIBCAGE] + + # Additional segments may be added once the torso and skulls are chosen, so the sum of their properties are the starting point. + base_segments = model.NewIntVar('base segments', lb = 0) + model.Add(base_segments == cp_model.LinearExpr.WeightedSum([value for (key, value) in actions.items() if isinstance(key, (Torso, Skull))], [action.value.segments for action in chain(Torso, Skull)])) + + first_term, *_ = model.NewIntermediateIntVar( + partialmethod(BoneMarketModel.AddMultiplicationEquality, + variables=( + 25, + *repeat(add_segments, 4), + ) + ), + 'add segments brass cost multiple first term' + ) + + second_term, *_ = model.NewIntermediateIntVar( + partialmethod(BoneMarketModel.AddMultiplicationEquality, + variables=( + 100, + *repeat(add_segments, 3), + base_segments, + ) + ), + 'add segments brass cost multiple second term' + ) + + third_term, *_ = model.NewIntermediateIntVar( + partialmethod(BoneMarketModel.AddMultiplicationEquality, + variables=( + 50, + *repeat(add_segments, 3), + ) + ), + 'add segments brass cost multiple third term' + ) + + fourth_term, *_ = model.NewIntermediateIntVar( + partialmethod(BoneMarketModel.AddMultiplicationEquality, + variables=( + 150, + *repeat(add_segments, 2), + *repeat(base_segments, 2), + ) + ), + 'add segments brass cost multiple fourth term' + ) + + fifth_term, *_ = model.NewIntermediateIntVar( + partialmethod(BoneMarketModel.AddMultiplicationEquality, + variables=( + 150, + *repeat(add_segments, 2), + base_segments, + ) + ), + 'add segments brass cost multiple fifth term' + ) + + sixth_term, *_ = model.NewIntermediateIntVar( + partialmethod(BoneMarketModel.AddMultiplicationEquality, + variables=( + 25, + *repeat(add_segments, 2), + ) + ), + 'add segments brass cost multiple sixth term' + ) + + seventh_term, *_ = model.NewIntermediateIntVar( + partialmethod(BoneMarketModel.AddMultiplicationEquality, + variables=( + 100, + add_segments, + *repeat(base_segments, 3), + ) + ), + 'add segments brass cost multiple seventh term' + ) + + eighth_term, *_ = model.NewIntermediateIntVar( + partialmethod(BoneMarketModel.AddMultiplicationEquality, + variables=( + 150, + add_segments, + *repeat(base_segments, 2), + ) + ), + 'add segments brass cost multiple eighth term' + ) + + ninth_term, *_ = model.NewIntermediateIntVar( + partialmethod(BoneMarketModel.AddMultiplicationEquality, + variables=( + 50, + add_segments, + base_segments, + ) + ), + 'add segments brass cost multiple ninth term' + ) + + add_segments_brass_cost_multiple, *_ = model.NewIntermediateIntVar( + partialmethod(BoneMarketModel.AddDivisionEquality, + num=first_term + second_term + third_term + fourth_term + fifth_term + sixth_term + seventh_term + eighth_term + ninth_term, + denom=2 + ), + 'add segments brass cost multiple' + ) + + del first_term, second_term, third_term, fourth_term, fifth_term, sixth_term, seventh_term, eighth_term, ninth_term + + add_segments_brass_cost, *_ = model.NewIntermediateIntVar( + partialmethod(BoneMarketModel.AddMultiplicationEquality, + variables=( + add_segments_brass_cost_multiple, + Cost.NEVERCOLD_BRASS.value, + ) + ), + 'add segments brass cost' + ) + + del add_segments, base_segments, add_segments_brass_cost_multiple + + + cost = model.NewIntVar('cost', lb = 0, ub = maximum_cost) + model.Add(cost == cp_model.LinearExpr.WeightedSum(actions.values(), [int(action.value.cost) for action in actions.keys()]) + add_joints_amber_cost + add_segments_brass_cost + sale_cost) + + del sale_cost, add_joints_amber_cost, add_segments_brass_cost # Type of skeleton @@ -451,6 +581,14 @@ def Solve(shadowy_level, bone_market_fluctuations = None, zoological_mania = Non .OnlyEnforceIf(model.BoolExpression(actions[Skull.SEGMENTED_RIBCAGE] > 0)) + # Appendage requirements + + model.AddIf(model.BoolExpression(actions[Appendage.SEGMENTED_RIBCAGE] > 0), + torso_style == 110, + cp_model.LinearExpr.WeightedSum([value for (key, value) in actions.items() if isinstance(key, (Torso, Skull))], [action.value.tails_needed for action in chain(Torso, Skull)]) >= 1, + ) + + # Declaration requirements model.AddIf(actions[Declaration.HUMANOID],