Skip to content

Instantly share code, notes, and snippets.

@Mijago
Last active February 6, 2022 01:39
Show Gist options
  • Save Mijago/724b1a7a03be5cf752d82d8562ff390d to your computer and use it in GitHub Desktop.
Save Mijago/724b1a7a03be5cf752d82d8562ff390d to your computer and use it in GitHub Desktop.
Test the Possibility of certain Armor Stat Distribuion with Linear Programming
import numpy as np
import pulp as pl
# %%% CONFIG
MINIMUM_TIERS = 28
MAXIMUM_WASTE = 15
AVAILABLE_MODS = 5
AVAILABLE_BONUS = [
20, # PF
20, # stasis
10, # stasis
0,
10, # stasis
20 + 10 # RL # stasis
]
# Four entries. Use them to test if you can use an item for a specific distribution
ITEMS = [
#[2,7,24,2,10, 21], # helmet
#[2,19,12,2,10,21], # legz
#[2,24,7,2,26,6], # chest
#[2,15,14,],
# [2,6,25, 6,20,6], # coda chest
#[2,20,11,6,6,20] , # boots ascendan
#[2,13,16,2,14,16], #some helmet
# [6,27,2,2,26,2], # stronghold
#[2,6,25,6,20,6],
[9, 16, 10, 11, 17, 2],
None, # [2,2,30, 2,2,30],
None,
None,
None
]
# The stats you want
#DESIRED_STATS = [0, 100, 0, 0, 100, 100]
AVAILABLE_BONUS = [0, 0, 0, 0, 0, 0]
DESIRED_STATS = [0,90,100,100,0,0]
#AVAILABLE_MODS = 0
MINIMUM_TIERS = 31
# %%% SOLVER
solver = pl.CPLEX_CMD()
model = pl.LpProblem("Stats", pl.LpMaximize)
possibleBonusStats = [
[0,0,0], [0,0,2],[0,1,1], [0,1,2], [0,2,0], [0,2,1], [1,0,1], [1,1,0], [1,1,1], [1,2,0], [2,0,0], [2,0,1], [2,1,0]
]
plugs = np.array([[1, 1, 10], [1, 1, 11], [1, 1, 12], [1, 1, 13], [1, 1, 14], [1, 1, 15],
[1, 5, 5], [1, 5, 6], [1, 5, 7], [1, 5, 8], [1, 5, 9], [1, 5, 10],
[1, 5, 11], [1, 6, 5], [1, 6, 6], [1, 6, 7], [1, 6, 8], [1, 6, 9],
[1, 7, 5], [1, 7, 6], [1, 7, 7], [1, 7, 8], [1, 8, 5], [1, 8, 6],
[1, 8, 7], [1, 9, 5], [1, 9, 6], [1, 10, 1], [1, 10, 5], [1, 11, 1],
[1, 11, 5], [1, 12, 1], [1, 13, 1], [1, 14, 1], [1, 15, 1], [5, 1, 5],
[5, 1, 6], [5, 1, 7], [5, 1, 8], [5, 1, 9], [5, 1, 10], [5, 1, 11],
[5, 5, 1], [5, 5, 5], [5, 6, 1], [5, 7, 1], [5, 8, 1], [5, 9, 1],
[5, 10, 1], [5, 11, 1], [6, 1, 5], [6, 1, 6], [6, 1, 7], [6, 1, 8],
[6, 1, 9], [6, 5, 1], [6, 6, 1], [6, 7, 1], [6, 8, 1], [6, 9, 1],
[7, 1, 5], [7, 1, 6], [7, 1, 7], [7, 1, 8], [7, 5, 1], [7, 6, 1],
[7, 7, 1], [7, 8, 1], [8, 1, 5], [8, 1, 6], [8, 1, 7], [8, 5, 1],
[8, 6, 1], [8, 7, 1], [9, 1, 5], [9, 1, 6], [9, 5, 1], [9, 6, 1],
[10, 1, 1], [10, 1, 5], [10, 5, 1], [11, 1, 1], [11, 1, 5], [11, 5, 1],
[12, 1, 1], [13, 1, 1], [14, 1, 1], [15, 1, 1]])
v_total_stats = pl.LpVariable.dicts('stat', range(0, 6), lowBound=0, upBound=0, cat='Integer')
for n in range(0, 6):
v_total_stats[n] += 10 # Masterworks :)
for n in range(0, 6):
v_total_stats[n] += AVAILABLE_BONUS[n] # add bonus
#for n in range(0, 6): model += v_total_stats[n] >= 2 # minimum of 2
NUM_ITEMS = 4
items = dict()
for item in range(0, NUM_ITEMS):
if ITEMS[item] is not None:
for n in range(0, 6):
v_total_stats[n] += ITEMS[item][n]
continue
v_item_stats = pl.LpVariable.dicts('item_%d_stat' % item, range(0, 6), lowBound=0, upBound=0, cat='Integer')
v_item_plugs_g1 = pl.LpVariable.dicts('item_%d_plug1' % item, range(0, len(plugs)), lowBound=0, upBound=2, cat='Integer')
v_item_plugs_g2 = pl.LpVariable.dicts('item_%d_plug2' % item, range(0, len(plugs)), lowBound=0, upBound=2, cat='Integer')
model += pl.lpSum(v_item_plugs_g1) == 2
model += pl.lpSum(v_item_plugs_g2) == 2
for n in range(0, 3):
for k, v in enumerate(plugs):
v_item_stats[n] += v_item_plugs_g1[k] * plugs[k][n]
v_item_stats[3 + n] += v_item_plugs_g2[k] * plugs[k][n]
v_total_stats[n] += v_item_stats[n]
v_total_stats[3 + n] += v_item_stats[3 + n]
items[item] = v_item_stats
# MODS
v_mods = pl.LpVariable.dicts('mod', range(0, 6), cat='Integer', lowBound=0, upBound=5)
model += pl.lpSum(v_mods) <= AVAILABLE_MODS
for mod in v_mods:
v_total_stats[mod] += 10 * v_mods[mod]
# Bonus of an exotic
v_exotic_boost = pl.LpVariable.dicts('exotic_boost', range(0, len(possibleBonusStats)), lowBound=0, upBound=1, cat='Integer')
model += pl.lpSum(v_exotic_boost) <= 1
for boost in v_exotic_boost:
entry = possibleBonusStats[boost]
for r in [0,1,2]:
if entry[r] > 0:
v_total_stats[r] += entry[r] * v_exotic_boost[boost]
# desired stats
for stat in v_total_stats:
model += v_total_stats[stat] >= DESIRED_STATS[stat]
model += v_total_stats[stat] <= 109
v_stat_sum = pl.lpSum(v_total_stats)
###### Variables to calculate waste
tiers = pl.LpVariable.dicts('tier', range(0, 6), lowBound=0, cat='Integer')
for stat in range(0, 6):
model += tiers[stat] >= v_total_stats[stat] / 10 - 0.5
model += tiers[stat] <= v_total_stats[stat] / 10
v_tier_sum = pl.lpSum(tiers)
model += v_tier_sum >= 0
model += v_tier_sum >= MINIMUM_TIERS
waste = pl.LpVariable('waste')
model += (
waste ==
pl.lpSum([pl.lpSum(v_total_stats[stat] - tiers[stat] * 10) for stat in range(0, 6)])
# + pl.lpSum([pl.lpSum(v_over_100[stat] * 10) for stat in range(0, 6)])
# pl.lpSum([v_over_100[stat] * (v_total_stats[stat] - 100) for stat in range(0, 6)])
)
model += waste <= MAXIMUM_WASTE
model += (
v_stat_sum
# - modcost
-5* waste
)
#model += v_tier_sum;
result = model.solve()
print("~~~ Input ~~~")
print("Desired stats:", DESIRED_STATS)
print("Available bonus:", AVAILABLE_BONUS)
print("Existing items", ITEMS, "('None' means it must be calculated)")
print("Available Mods", AVAILABLE_MODS)
print()
print("~~~ OUTPUT ~~~")
print("stat\tvalue\tmods")
for stat in range(0, 6):
print(stat, "\t", pl.value(v_total_stats[stat]), "\t", pl.value(v_mods[stat]))
print("Using these (masterworked) items:")
for i in items:
print([pl.value(items[i][m]) for m in items[i]])
print("Wasted points:", pl.value(waste))
print("Total Stat Points:", pl.value(v_stat_sum))
print("Total Tiers:", pl.value(pl.lpSum(tiers)))
print("Exotic Bonus Stats:", [possibleBonusStats[f] for f in range(0, len(possibleBonusStats)) if pl.value(v_exotic_boost[f]) == 1])
import numpy as np
import pulp as pl
solver = pl.CPLEX_CMD()
availableBonus = np.array([
20, # PF
20, # stasis
10, # stasis
0,
10, # stasis
20 +10 # RL # stasis
])
availableMods = 5
#x/7/8/x/8/6
desiredStats= np.array([0,100,100,100,0,100])
#availableBonus = np.array([0,20,10,0,10,10])
existing_items = np.array([
None,
None,
None,
None
])
model = pl.LpProblem("Stats", pl.LpMinimize)
names = ["mob", "res", "rec", "dis", "int", "str", ]
var = [pl.LpVariable('%s%d'%(names[n%6], n//6), cat='Integer', lowBound=2, upBound=30) for n in range(0,4*6)]
mod = [pl.LpVariable('mod_%s%d'%(names[n%6], n//6),cat='Integer', lowBound=0, upBound=5) for n in range(0,6)]
i1,i2,i3,i4 = 0,6,12,18
for n in var:
model += var != 3
model += var != 4
model += var != 5
for item in [i1,i2,i3,i4]:
model += var[item + 0] + var[item + 1] + var[item + 2] <= 34
model += var[item + 3] + var[item + 4] + var[item + 5] <= 34
model += var[item + 0] + var[item + 1] + var[item + 2] >= 22
model += var[item + 3] + var[item + 4] + var[item + 5] >= 22
#for n in range(0,6):
#model += var[item + n] >= 2
#model += var[item + n] <= 30
model += mod[0]+mod[1]+mod[2]+mod[3]+mod[4]+mod[5] <= availableMods
for n in range(0,6):
#model += mod[n] >=0
#model += mod[n] <=5
model += var[i1 + n] + var[i2 + n]+ var[i3+ n] + var[i4 + n] + 10*mod[n] >= max(0,(desiredStats-availableBonus - 10)[n])
model += var[i1 + n] + var[i2 + n]+ var[i3+ n] + var[i4 + n] + 10*mod[n] + availableBonus[n] + 10 <= 109
# add existing items
for k, x in enumerate(existing_items):
if x is None: continue
for n in range(0,6):
model += var[6*k + n] == x[n]
# Goal: minimize the total stats
model += pl.lpSum(var) + 10 * (mod[0]+mod[1]+mod[2]+mod[3]+mod[4]+mod[5]) - 20* (mod[0]+mod[1]+mod[2]+mod[3]+mod[4]+mod[5])
tiers = [pl.LpVariable('tier_%s%d'%(names[n%6], n//6),cat='Integer') for n in range(0,6)]
for tier in range(0,6):
model += tiers[tier] >= pl.lpSum([var[x + tier] for x in [i1,i2,i3,i4]])/10 -0.5
model += tiers[tier] <= pl.lpSum([var[x + tier] for x in [i1,i2,i3,i4]])/10
# optimize by waste; i just introduce it as a variable for readability
waste = pl.LpVariable('waste')
model += waste == pl.lpSum([
(pl.lpSum([var[x + tier] for x in [i1,i2,i3,i4]]) - tiers[tier]*10 ) for tier in range(0, 6)
])
model += waste
model += -pl.lpSum(var)
result = model.solve()
stats = np.array([pl.value(x) for x in var]).reshape(4,6)
mods = np.array([pl.value(x) for x in mod])
tiers = np.array([pl.value(x) for x in tiers]) + 1 + availableBonus/10 + mods
statstotal = stats.sum(axis=0) + 10 + availableBonus + 10 * mods
print()
print()
print("desired stats: ", desiredStats)
print("available bonus stats:",availableBonus)
print("existing items:",existing_items)
print()
print("stats per item:\n",stats)
print("mods:", mods)
print()
print("stats", statstotal)
print("tiers by stats:", tiers)
print("waste:",pl.value(waste))
print()
#print(model)
import numpy as np
import pulp as pl
# %%% CONFIG
MAXIMUM_WASTE = 100 # don't be too restrictive when you use a broad search. Slow!
AVAILABLE_MODS = 5
AVAILABLE_BONUS = [
20, # PF
20, # stasis
10, # stasis
0,
10, # stasis
20 + 10 # RL # stasis
]
# Four entries. Use them to test if you can use an item for a specific distribution
ITEMS = [
None, # [2,2,30, 2,2,30],
None,
None,
None
]
# The stats you want
DESIRED_STATS = [0, 100, 0, 0, 0, 100]
# DESIRED_STATS = [0, 0, 0, 0, 0, 0]
# %%% SOLVER
solver = pl.CPLEX_CMD()
model = pl.LpProblem("Stats", pl.LpMaximize)
plugs = np.array([
[1, 5, 11], [8, 5, 1], [8, 5, 1], [5, 5, 1], [9, 1, 6], [11, 1, 1], [5, 6, 1], [9, 6, 1], [5, 5, 5], [6, 1, 9], [5, 1, 11], [1, 8, 5],
[7, 8, 1], [6, 8, 1], [8, 1, 6], [5, 1, 7], [7, 1, 6], [1, 10, 1], [7, 1, 6], [1, 11, 1], [1, 8, 7], [11, 1, 5], [7, 5, 1], [8, 7, 1],
[1, 6, 9], [1, 12, 1], [1, 1, 13], [14, 1, 1], [1, 1, 11], [8, 6, 1], [7, 8, 1], [5, 5, 1], [1, 5, 9], [1, 15, 1], [5, 1, 8], [1, 6, 6],
[7, 1, 7], [1, 9, 6], [7, 1, 5], [9, 6, 1], [1, 6, 9], [10, 1, 1], [5, 1, 7], [5, 8, 1], [1, 6, 7], [9, 5, 1], [1, 7, 5], [7, 7, 1],
[1, 5, 6], [5, 9, 1], [6, 1, 9], [6, 1, 6], [6, 6, 1], [1, 5, 5], [7, 1, 8], [5, 7, 1], [5, 1, 5], [8, 1, 7], [1, 7, 8], [11, 5, 1],
[13, 1, 1], [6, 1, 8], [1, 14, 1], [11, 1, 1], [6, 1, 7], [1, 7, 8], [1, 11, 5], [15, 1, 1], [5, 1, 10], [5, 5, 5], [6, 7, 1], [7, 6, 1],
[1, 6, 6], [13, 1, 1], [1, 5, 10], [6, 1, 7], [8, 7, 1], [5, 8, 1], [5, 1, 8], [5, 1, 6], [1, 8, 5], [7, 5, 1], [1, 1, 10], [7, 1, 5],
[7, 6, 1], [6, 9, 1], [1, 10, 5], [6, 5, 1], [9, 1, 6], [6, 7, 1], [1, 6, 7], [12, 1, 1], [1, 1, 11], [1, 15, 1], [7, 1, 8], [1, 1, 12],
[1, 6, 5], [1, 9, 5], [8, 1, 5], [1, 13, 1], [1, 12, 1], [1, 5, 5], [6, 6, 1], [1, 11, 1], [1, 7, 5], [5, 11, 1], [1, 13, 1], [5, 7, 1],
[1, 1, 14], [1, 5, 7], [1, 1, 13], [1, 5, 7], [1, 7, 7], [15, 1, 1], [1, 5, 8], [1, 1, 15], [6, 9, 1], [10, 1, 5], [9, 1, 5], [6, 1, 5],
[5, 1, 9], [6, 1, 6], [8, 1, 5], [1, 9, 6], [1, 7, 6], [1, 5, 8], [1, 7, 6], [10, 5, 1], [1, 1, 15], [1, 6, 8], [5, 10, 1], [1, 1, 12],
[1, 8, 6], [8, 1, 7], [12, 1, 1], [1, 8, 7], [5, 1, 5]
])
v_total_stats = pl.LpVariable.dicts('stat', range(0, 6), lowBound=0, upBound=0, cat='Integer')
for n in range(0, 6):
v_total_stats[n] += 10 # Masterworks :)
for n in range(0, 6):
v_total_stats[n] += AVAILABLE_BONUS[n] # add bonus
NUM_ITEMS = 4
items = dict()
for item in range(0, NUM_ITEMS):
if ITEMS[item] is not None:
for n in range(0, 6):
v_total_stats[n] += ITEMS[item][n]
continue
v_item_stats = pl.LpVariable.dicts('item_%d_stat' % item, range(0, 6), lowBound=0, upBound=0, cat='Integer')
v_item_plugs_g1 = pl.LpVariable.dicts('item_%d_plug1' % item, range(0, len(plugs)), lowBound=0, upBound=2, cat='Integer')
v_item_plugs_g2 = pl.LpVariable.dicts('item_%d_plug2' % item, range(0, len(plugs)), lowBound=0, upBound=2, cat='Integer')
model += pl.lpSum(v_item_plugs_g1) == 2
model += pl.lpSum(v_item_plugs_g2) == 2
for n in range(0, 3):
for k, v in enumerate(plugs):
v_item_stats[n] += v_item_plugs_g1[k] * plugs[k][n]
v_item_stats[3 + n] += v_item_plugs_g2[k] * plugs[k][n]
v_total_stats[n] += v_item_stats[n]
v_total_stats[3 + n] += v_item_stats[3 + n]
items[item] = v_item_stats
# MODS
v_mods = pl.LpVariable.dicts('mod', range(0, 6), cat='Integer', lowBound=0, upBound=5)
model += pl.lpSum(v_mods) <= 5
for mod in v_mods:
v_total_stats[mod] += 10 * v_mods[mod]
# desired stats
for stat in v_total_stats:
model += v_total_stats[stat] >= DESIRED_STATS[stat]
model += v_total_stats[stat] <= 109
v_stat_sum = pl.lpSum(v_total_stats)
###### Variables to calculate waste
tiers = pl.LpVariable.dicts('tier', range(0, 6), lowBound=0, cat='Integer')
for stat in range(0, 6):
model += tiers[stat] >= v_total_stats[stat] / 10 - 0.5
model += tiers[stat] <= v_total_stats[stat] / 10
waste = pl.LpVariable('waste')
model += (
waste ==
pl.lpSum([pl.lpSum(v_total_stats[stat] - tiers[stat] * 10) for stat in range(0, 6)])
# + pl.lpSum([pl.lpSum(v_over_100[stat] * 10) for stat in range(0, 6)])
# pl.lpSum([v_over_100[stat] * (v_total_stats[stat] - 100) for stat in range(0, 6)])
)
model += waste <= MAXIMUM_WASTE
model += (
v_stat_sum
# - modcost
- waste
)
result = model.solve()
print("~~~ Input ~~~")
print("Desired stats:", DESIRED_STATS)
print("Available bonus:", AVAILABLE_BONUS)
print("Existing items", ITEMS, "('None' means it must be calculated)")
print("Available Mods", AVAILABLE_MODS)
print()
print("~~~ OUTPUT ~~~")
print("stat\tvalue\tmods")
for stat in range(0, 6):
print(stat, "\t", pl.value(v_total_stats[stat]), "\t", pl.value(v_mods[stat]))
print("Using these (masterworked) items:")
for i in items:
print([pl.value(items[i][m]) for m in items[i]])
print("Wasted points:", pl.value(waste))
@Mijago
Copy link
Author

Mijago commented Jan 24, 2022

Note that both files do the same.

  • use armorStatSolver.old.py for simple checking like [0,10,0,0,10,0]
  • use armorStatSolver.plugs.py is way slower for these checks, but more accurate for things like [0,10,10,0,10,0]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment