This Jupyter notebook can be used to maximize alchemy profitability using the ingredients you have on hand in The Elder Scrolls V: Skyrim.
It uses integer linear programming from scipy.optimize.milp to determine which potions to make, and in what quantities, to maximize total value. It needs a csv file of the ingredients you have with their quantities, and a csv file of all possible potions you could make with your ingredients. I used this helpful spreadsheet to create my csvs, which are available here as examples.
Python Setup
import numpy as np
import pandas as pd
from scipy.optimize import milp, Bounds, LinearConstraint
Read in Ingredients
ingredients = pd.read_csv('ingredients_have.csv')
Ingredient | Quantity |
---|---|
Blisterwort | 4 |
Blue Butterfly Wing | 4 |
Blue Dartwing | 1 |
Blue Mountain Flower | 24 |
Bone Meal | 5 |
… more ingredients
Read in Recipes
recipes = pd.read_csv('recipes_can_make.csv')
recipes = recipes[recipes['Magnitude'] > 0]
Magnitude | Ingredient 1 | Ingredient 2 | Ingredient 3 | MyPotionID |
---|---|---|---|---|
159 | Blue Dartwing | Blue Mountain Flower | Glow Dust | 3028 |
156 | Blue Dartwing | Blue Mountain Flower | Nightshade | 3037 |
156 | Blue Dartwing | Blue Mountain Flower | Spider Egg | 3045 |
156 | Blue Dartwing | Blue Mountain Flower | Spriggan Sap | 3046 |
113 | Blisterwort | Blue Mountain Flower | Blue Butterfly Wing | 2130 |
… more recipes
Create recipe matrix A in Ax <= b
One row for each ingredient, one column for each potion. “1” indicates the ingredient is used in the potion.
A = pd.DataFrame(0, index=range(len(ingredients)),columns=range(len(recipes)))
for i in range(len(recipes)):
if ingredients.iloc[ingredients["Ingredient"].str.find(recipes.loc[i, "Ingredient 1"]).idxmax()]["Quantity"] > 0:
A.iloc[ingredients["Ingredient"].str.find(recipes.loc[i, "Ingredient 1"]).idxmax(), i] = 1
if ingredients.iloc[ingredients["Ingredient"].str.find(recipes.loc[i, "Ingredient 2"]).idxmax()]["Quantity"] > 0:
A.iloc[ingredients["Ingredient"].str.find(recipes.loc[i, "Ingredient 2"]).idxmax(), i] = 1
if not pd.isnull(recipes.loc[i, "Ingredient 3"]):
if ingredients.iloc[ingredients["Ingredient"].str.find(recipes.loc[i, "Ingredient 3"]).idxmax()]["Quantity"] > 0:
A.iloc[ingredients["Ingredient"].str.find(recipes.loc[i, "Ingredient 3"]).idxmax(), i] = 1
A
0 | 1 | 2 | … | 2382 | 2383 | 2384 | |
---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | … | 0 | 0 | 0 |
1 | 0 | 0 | 0 | … | 0 | 0 | 0 |
2 | 1 | 1 | 1 | … | 0 | 0 | 0 |
3 | 1 | 1 | 1 | … | 0 | 0 | 0 |
4 | 0 | 0 | 0 | … | 0 | 0 | 0 |
5 | 0 | 0 | 0 | … | 0 | 0 | 0 |
6 | 0 | 0 | 0 | … | 0 | 0 | 0 |
7 | 0 | 0 | 0 | … | 0 | 0 | 0 |
8 | 0 | 0 | 0 | … | 0 | 0 | 0 |
9 | 0 | 0 | 0 | … | 0 | 0 | 0 |
10 | 0 | 0 | 0 | … | 0 | 0 | 0 |
… more rows
Optimization Setup
f = np.array(-1 * recipes['Magnitude'], dtype=int)
b_max = np.array(ingredients['Quantity'], dtype=int)
x_lb = np.zeros(shape=len(recipes))
bounds = Bounds(lb=x_lb)
constraint = LinearConstraint(A, ub=b_max)
integrality = np.ones(shape=len(recipes), dtype=int)
Perform Optimization
res = milp(c=f, integrality=integrality, bounds=bounds, constraints=constraint)
Display Recommended Potions
total_magnitude = int(-res.fun)
num_potions = int(sum(res.x))
indices_to_make = np.nonzero(res.x > 0)
to_make_df = recipes.iloc[indices_to_make].copy()
to_make_df['QtyToMake'] = res.x[indices_to_make].astype(int)
to_make_df[['Magnitude','Type','Ingredient 1','Ingredient 2','Ingredient 3','MyPotionID','QtyToMake']].head(50)
Magnitude | Type | Ingredient 1 | Ingredient 2 | Ingredient 3 | MyPotionID | QtyToMake | |
---|---|---|---|---|---|---|---|
6 | 112 | Mixed | Frost Mirriam | Histcarp | Purple Mountain Flower | 10371 | 2 |
7 | 110 | Mixed | Blue Butterfly Wing | Blue Mountain Flower | Butterfly Wing | 2666 | 4 |
11 | 109 | Mixed | Blisterwort | Blue Mountain Flower | Spriggan Sap | 2210 | 3 |
19 | 108 | Mixed | Blisterwort | Blue Mountain Flower | Spider Egg | 2209 | 1 |
31 | 108 | Mixed | Blue Mountain Flower | Bone Meal | Spider Egg | 3416 | 4 |
33 | 108 | Mixed | Blue Mountain Flower | Glow Dust | Hagraven Feathers | 3628 | 1 |
34 | 108 | Mixed | Blue Mountain Flower | Glow Dust | Swamp Fungal Pod | 3643 | 1 |
35 | 108 | Mixed | Blue Mountain Flower | Rock Warbler Egg | Spider Egg | 3780 | 1 |
45 | 107 | Mixed | Creep Cluster | Ectoplasm | Skeever Tail | 6318 | 1 |
57 | 107 | Mixed | Frost Mirriam | Purple Mountain Flower | Skeever Tail | 10519 | 1 |
… more recommendations
print(f"To maximize magnitude and therefore value, create {num_potions} potions of the {len(to_make_df)} unique types listed above for a total magnitude of {total_magnitude}.")
To maximize magnitude and therefore value, create 76 potions of the 42 unique types listed above for a total magnitude of 3905.