An open API service indexing awesome lists of open source software.

https://github.com/brayvid/skyrim-alchemy-optimizer

Make the most of your ingredients in Skyrim with linear programming.
https://github.com/brayvid/skyrim-alchemy-optimizer

elder-scrolls-v integer-linear-programming optimization recreational-mathematics scipy skyrim

Last synced: 8 months ago
JSON representation

Make the most of your ingredients in Skyrim with linear programming.

Awesome Lists containing this project

README

          

# Skyrim Alchemy Optimizer

Blake Rayvid - https://github.com/brayvid

Make the most of the ingredients you have. Maximize total magnitude (essentially in-game value) with integer linear programming in scipy.

```python
import numpy as np
import pandas as pd
from scipy.optimize import milp, Bounds, LinearConstraint
```

## Read in ingredients and recipes
Uses local files "ingredients_have.csv" and "recipes_can_make.csv"

I made my CSVs using this helpful spreadsheet: https://docs.google.com/spreadsheets/d/1010C6ltqv7apuBoNYuFIFSBZER4YI03Y54kIsoKs5RI/edit?usp=sharing

```python
# Ingredients we have with quantity on hand
ingredients = pd.read_csv('ingredients_have.csv');ingredients
```





Ingredient
Quantity




0
Blisterwort
4


1
Blue Butterfly Wing
4


2
Blue Dartwing
1


3
Blue Mountain Flower
24


4
Bone Meal
5


5
Butterfly Wing
6


6
Canis Root
2


7
Creep Cluster
1


8
Deathbell
6


9
Dragons Tongue
5


10
Ectoplasm
5


11
Elves Ear
10


12
Fire Salts
1


13
Fly Amanita
1


14
Frost Mirriam
3


15
Garlic
7


16
Giant Lichen
2


17
Glow Dust
2


18
Hagraven Feathers
2


19
Histcarp
2


20
Honeycomb
4


21
Ice Wraith Teeth
2


22
Imp Stool
2


23
Lavender
17


24
Luna Moth Wing
4


25
Mora Tapinella
2


26
Mudcrab Chitin
2


27
Nightshade
7


28
Nirnroot
3


29
Nordic Barnacle
2


30
Orange Dartwing
2


31
Purple Mountain Flower
15


32
Red Mountain Flower
1


33
River Betty
2


34
Rock Warbler Egg
3


35
Salt Pile
14


36
Scaly Pholiota
1


37
Skeever Tail
2


38
Slaughterfish Scales
3


39
Snowberries
6


40
Spider Egg
8


41
Spriggan Sap
3


42
Swamp Fungal Pod
2


43
Taproot
2


44
Thistle Branch
2


45
Torchbug Thorax
3


46
Troll Fat
3


47
Tundra Cotton
7


48
Vampire Dust
1


49
Void Salts
1


50
White Cap
6


```python
# Potions list with magnitude and ingredient names (1,2 + optional 3rd)
recipes = pd.read_csv('recipes_can_make.csv')
recipes = recipes[recipes['Magnitude'] > 0]
recipes[['Magnitude','Ingredient 1','Ingredient 2','Ingredient 3','MyPotionID']].head(50)
```




Magnitude
Ingredient 1
Ingredient 2
Ingredient 3
MyPotionID




0
159
Blue Dartwing
Blue Mountain Flower
Glow Dust
3028


1
156
Blue Dartwing
Blue Mountain Flower
Nightshade
3037


2
156
Blue Dartwing
Blue Mountain Flower
Spider Egg
3045


3
156
Blue Dartwing
Blue Mountain Flower
Spriggan Sap
3046


4
113
Blisterwort
Blue Butterfly Wing
Blue Mountain Flower
2130


5
113
Blue Butterfly Wing
Blue Mountain Flower
Rock Warbler Egg
2680


6
112
Frost Mirriam
Histcarp
Purple Mountain Flower
10371


7
110
Blue Butterfly Wing
Blue Mountain Flower
Butterfly Wing
2666


8
110
Blue Butterfly Wing
Blue Mountain Flower
Imp Stool
2677


9
110
Blue Butterfly Wing
Blue Mountain Flower
Swamp Fungal Pod
2684


10
110
Glow Dust
Nightshade
River Betty
11628


11
109
Blisterwort
Blue Mountain Flower
Spriggan Sap
2210


12
109
Blue Butterfly Wing
Bone Meal
Spriggan Sap
2703


13
109
Butterfly Wing
Glow Dust
Nightshade
4738


14
109
Creep Cluster
Ectoplasm
Histcarp
6302


15
109
Creep Cluster
Histcarp
Red Mountain Flower
6550


16
109
Creep Cluster
River Betty
Skeever Tail
6725


17
109
Nightshade
River Betty
Spriggan Sap
14461


18
108
Blisterwort
Blue Butterfly Wing
Spriggan Sap
2154


19
108
Blisterwort
Blue Mountain Flower
Spider Egg
2209


20
108
Blue Butterfly Wing
Blue Mountain Flower
Bone Meal
2665


21
108
Blue Butterfly Wing
Blue Mountain Flower
Canis Root
2667


22
108
Blue Butterfly Wing
Blue Mountain Flower
Nirnroot
2679


23
108
Blue Butterfly Wing
Blue Mountain Flower
Spider Egg
2682


24
108
Blue Butterfly Wing
Bone Meal
Glow Dust
2693


25
108
Blue Butterfly Wing
Bone Meal
Nightshade
2700


26
108
Blue Butterfly Wing
Bone Meal
Spider Egg
2702


27
108
Blue Butterfly Wing
Glow Dust
Hagraven Feathers
2860


28
108
Blue Butterfly Wing
Hagraven Feathers
Spider Egg
2908


29
108
Blue Butterfly Wing
Lavender
Spider Egg
2969


30
108
Blue Dartwing
Glow Dust
Nightshade
3203


31
108
Blue Mountain Flower
Bone Meal
Spider Egg
3416


32
108
Blue Mountain Flower
Butterfly Wing
Glow Dust
3429


33
108
Blue Mountain Flower
Glow Dust
Hagraven Feathers
3628


34
108
Blue Mountain Flower
Glow Dust
Swamp Fungal Pod
3643


35
108
Blue Mountain Flower
Rock Warbler Egg
Spider Egg
3780


36
108
Glow Dust
Hagraven Feathers
Nightshade
11513


37
108
Glow Dust
Luna Moth Wing
Nightshade
11598


38
108
Glow Dust
Nightshade
Nordic Barnacle
11624


39
108
Glow Dust
Nightshade
Snowberries
11631


40
108
Glow Dust
Nightshade
Swamp Fungal Pod
11632


41
107
Blisterwort
Spider Egg
Spriggan Sap
2648


42
107
Blue Butterfly Wing
Canis Root
Spider Egg
2724


43
107
Blue Mountain Flower
Imp Stool
Nightshade
3716


44
107
Canis Root
Spider Egg
Spriggan Sap
5163


45
107
Creep Cluster
Ectoplasm
Skeever Tail
6318


46
107
Creep Cluster
Frost Mirriam
Purple Mountain Flower
6392


47
107
Creep Cluster
Frost Mirriam
Red Mountain Flower
6393


48
107
Creep Cluster
Frost Mirriam
Taproot
6402


49
107
Creep Cluster
Frost Mirriam
White Cap
6406


## 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.

```python
# Boolean matrix A says what ingredients are in what recipes
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
3
4
5
6
7
8
9
...
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384




0
0
0
0
0
1
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


1
0
0
0
0
1
1
0
1
1
1
...
0
0
0
0
0
0
0
0
0
0


2
1
1
1
1
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


3
1
1
1
1
1
1
0
1
1
1
...
0
0
0
0
0
0
0
0
0
0


4
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


5
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


6
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


7
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


8
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


9
0
0
0
0
0
0
0
0
0
0
...
1
0
0
0
0
0
0
0
0
0


10
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


11
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


12
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


13
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


14
0
0
0
0
0
0
1
0
0
0
...
0
0
0
0
0
0
0
0
0
0


15
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


16
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


17
1
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


18
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


19
0
0
0
0
0
0
1
0
0
0
...
0
0
0
0
0
0
0
0
0
0


20
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


21
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
1
0
0
0


22
0
0
0
0
0
0
0
0
1
0
...
0
0
0
0
0
0
0
0
0
0


23
0
0
0
0
0
0
0
0
0
0
...
0
1
1
1
0
0
0
1
0
0


24
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
1
0


25
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


26
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


27
0
1
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


28
0
0
0
0
0
0
0
0
0
0
...
0
1
0
0
1
0
1
1
1
1


29
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


30
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


31
0
0
0
0
0
0
1
0
0
0
...
0
0
0
0
0
0
0
0
0
0


32
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


33
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


34
0
0
0
0
0
1
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


35
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


36
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


37
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


38
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


39
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
1
0
0
0
0


40
0
0
1
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


41
0
0
0
1
0
0
0
0
0
0
...
0
0
0
0
0
1
0
0
0
0


42
0
0
0
0
0
0
0
0
0
1
...
0
0
0
0
0
0
0
0
0
0


43
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


44
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


45
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


46
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0


47
0
0
0
0
0
0
0
0
0
0
...
1
0
1
0
1
0
1
0
1
1


48
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
1
0
1


49
0
0
0
0
0
0
0
0
0
0
...
0
0
0
1
0
0
0
0
0
0


50
0
0
0
0
0
0
0
0
0
0
...
0
0
0
0
0
0
0
0
0
0

51 rows × 2385 columns



## Set up optimization variables
find x to minimize f.x with Ax <= b, x >= lb

f = -1 * magnitude, b = qty of each ingredient on hand

```python
# Objective function f.x to minimize
f = np.array(-1 * recipes['Magnitude'],dtype=int); # f = -1*value so that minimizing f.x maximizes total value
```

```python
# Bounds
b_max = np.array(ingredients['Quantity'],dtype=int) # Cannot use more than we have on hand
x_lb = np.zeros(shape=len(recipes)) # Cannot use less than 0
```

```python
# milp parameters
bounds = Bounds(lb=x_lb)
constraint = LinearConstraint(A, ub=b_max)
integrality = np.ones(shape=len(recipes),dtype=int) # All x should be integers
```

# Perform optimization with scipy.optimize.milp

```python
# Perform optimization
res = milp(c=f, integrality=integrality, bounds=bounds, constraints=constraint)
```

## Display recommended potions to make

```python
# Display the potions we should make to maximize magnitude where the last column is quantity to make
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.loc[:,'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


103
105
Mixed
Blue Mountain Flower
Lavender
Nightshade
3749
7


104
105
Mixed
Blue Mountain Flower
Lavender
Spider Egg
3755
2


303
59
Potion
Blue Dartwing
Swamp Fungal Pod
NaN
3388
1


334
57
Mixed
Deathbell
Salt Pile
Taproot
8001
1


361
55
Poison
River Betty
Salt Pile
Troll Fat
15006
2


367
53
Poison
Deathbell
Nirnroot
Salt Pile
7937
2


370
53
Poison
Deathbell
Salt Pile
Troll Fat
8004
1


379
50
Poison
Deathbell
Salt Pile
NaN
8006
2


384
17
Mixed
Elves Ear
Fire Salts
Salt Pile
8930
1


391
16
Potion
Dragons Tongue
Fly Amanita
Scaly Pholiota
8094
1


397
15
Potion
Garlic
Taproot
Vampire Dust
11060
1


400
14
Mixed
Ectoplasm
Giant Lichen
Void Salts
8576
1


443
12
Mixed
Canis Root
Imp Stool
Rock Warbler Egg
5088
2


461
12
Mixed
Luna Moth Wing
Nordic Barnacle
Orange Dartwing
14094
1


465
12
Potion
Dragons Tongue
Elves Ear
Mora Tapinella
8058
2


468
12
Potion
Honeycomb
Purple Mountain Flower
Slaughterfish Scales
12905
1


469
12
Potion
Mudcrab Chitin
Purple Mountain Flower
Thistle Branch
14343
2


470
11
Mixed
Ectoplasm
Red Mountain Flower
NaN
8873
1


493
11
Mixed
Dragons Tongue
Elves Ear
White Cap
8067
2


516
11
Mixed
Elves Ear
Snowberries
White Cap
9130
2


582
10
Mixed
Elves Ear
Ice Wraith Teeth
White Cap
9037
2


658
9
Mixed
Bone Meal
Lavender
Nirnroot
4095
1


704
9
Mixed
Purple Mountain Flower
Snowberries
Torchbug Thorax
14933
3


760
9
Potion
Ectoplasm
Elves Ear
Tundra Cotton
8459
1


765
9
Potion
Ectoplasm
Giant Lichen
Tundra Cotton
8575
1


804
9
Potion
Garlic
Lavender
Luna Moth Wing
10953
1


806
9
Potion
Garlic
Lavender
Salt Pile
10961
5


855
9
Potion
Honeycomb
Purple Mountain Flower
Tundra Cotton
12911
3


879
8
Mixed
Luna Moth Wing
Nordic Barnacle
NaN
14098
1


1017
8
Mixed
Hagraven Feathers
Lavender
Luna Moth Wing
12101
1


1144
8
Potion
Orange Dartwing
Purple Mountain Flower
Snowberries
14612
1


1443
7
Potion
Purple Mountain Flower
Slaughterfish Scales
Tundra Cotton
14922
2


```python
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.