oh-my-dear-ai commited on
Commit
f231efc
·
1 Parent(s): 275ca2d
Files changed (3) hide show
  1. app.py +130 -94
  2. plants.csv +7 -0
  3. requirements.txt +0 -0
app.py CHANGED
@@ -3,6 +3,7 @@ import pandas as pd
3
  import pulp
4
  import numpy as np
5
  from numpy.typing import NDArray
 
6
 
7
  # Define the quality tiers and names for the plants
8
  PLANTS_TIERS = {
@@ -105,70 +106,83 @@ def calculator(*args):
105
  the total gold earned, and the remaining inventory.
106
  Returns an error message if no solution is found.
107
  """
108
- budget: int = args[0] # 葭碧预算
109
- strategy: str = args[1] # 出售策略
110
- extra_rate: int = args[2] # 高价收购倍率
 
111
  stocks: NDArray[np.int_ | np.integer] = np.array(
112
- [x if x else 0 for x in args[3:]]
113
  ) # 植物库存
114
 
115
- # 植物名称列表
116
  plants_names = [
117
  f"{PLANTS_TIERS[row['tier']]} {PLANTS_LABLES[row['species']]}"
118
  for index, row in df.iterrows()
119
  ]
 
 
120
 
121
- gold = df["gold"] # 植物单价
122
- sold_prices = np.array(gold * (1 + extra_rate))
123
 
124
- if sum([sold_prices[i] * stocks[i] for i in range(len(stocks))]) < budget:
125
- return "No solution found.\nThe total value of the plants is lower than the budget."
126
-
127
- # 创建问题实例,根据出售策略设定优化目标
128
- if strategy == "MaximizeStock":
129
- prob = pulp.LpProblem("Maximize_Stock", pulp.LpMinimize)
130
- else:
131
- prob = pulp.LpProblem("Minimize_Stock", pulp.LpMaximize)
132
 
133
- # 决策变量,售出每种植物的件数,必须是整数
134
- x = pulp.LpVariable.dicts("x", range(len(stocks)), lowBound=0, cat="Integer")
135
 
136
- # 遍历,设置决策变量的上界为库存量
137
- for i in range(len(stocks)):
138
- x[i].upBound = stocks[i]
139
 
140
- # 目标函数:最大化 / 最小化 售出株数
141
- prob += pulp.lpSum([x[i] for i in range(len(stocks))])
142
 
143
- # 约束条件:每类产品售出数量乘以单价之和等于总价格
144
- prob += pulp.lpSum([sold_prices[i] * x[i] for i in range(len(stocks))]) == budget
 
145
 
146
- # 求解问题
147
- # CBC(Coin-or Branch and Cut)求解器使用分支定界算法来寻找整数规划问题的最优解
148
- solver = pulp.PULP_CBC_CMD(mip=True, msg=False)
149
- prob.solve(solver=solver)
150
 
151
- if pulp.LpStatus[prob.status] == "Optimal":
 
 
 
 
152
 
153
- sold = 0
154
  solution = []
155
- for i, v in x.items():
156
- if v.varValue:
157
- if v.varValue > 0:
 
 
 
158
  solution.append(
159
- f"{plants_names[i]}(${sold_prices[i]}): {int(v.varValue)}\n"
160
  )
161
- sold += int(v.varValue) * sold_prices[i]
162
-
163
- return f"Optimal solution found:\n\n{''.join(solution)}\nTotal Price: {sold}\nCount: {int(pulp.value(prob.objective))}"
164
-
 
 
 
 
 
165
  else:
166
- return f"Can't find a combination of plants such that the sum of their values equals ${budget}. \nTry again with different inputs."
167
 
168
 
169
  # 高亮每种植物的最高品质
170
  css = """
171
- .firstbox {background-color: #fafad2}
 
172
  """
173
 
174
  with gr.Blocks(css=css) as demo:
@@ -177,26 +191,27 @@ with gr.Blocks(css=css) as demo:
177
  <center><font size=8>HP-Magic-Awakened Herbologist Toolkit👾</font></center>
178
 
179
  This program is essentially a solver for a variant of the knapsack problem.
180
- Due to the limitations of the third-party library used, this program can only return solutions that exactly match the target amount.
181
- Another more versatile [application](https://huggingface.co/spaces/oh-my-dear-ai/easy-knapsack-problem), however, can return solutions that maximize value while being less than or equal to the target amount.
182
  """
183
  )
184
 
185
- # Language selection dropdown
186
- # language = gr.Radio(
187
- # choices=["cn", "en"],
188
- # value="cn",
189
- # label="Language",
190
- # info="Select the interface language:",
191
- # interactive=True,
192
- # )
193
 
194
  # Create a Gradio interface with a column layout
195
  with gr.Column():
 
 
 
 
 
 
 
 
 
196
  # Add a row for the budget input
197
  budget = gr.Number(
198
- label="Gold",
199
- info="Gabby's Gold Budget:", # "葭碧の金币预算:",
200
  value=0,
201
  minimum=0,
202
  maximum=20000,
@@ -205,9 +220,9 @@ with gr.Blocks(css=css) as demo:
205
  acquisition_rate = gr.Dropdown(
206
  choices=[
207
  "0(Gabby's Acquisition)",
208
- "+100%(Budding & Novice)",
209
- "+200%(Junior & Practiced)",
210
- "+300%(Natural & Master)",
211
  ],
212
  value="0(Gabby's Acquisition)",
213
  type="index",
@@ -215,51 +230,68 @@ with gr.Blocks(css=css) as demo:
215
  info="Select your high-value acquisition rate:",
216
  )
217
 
218
- # Add a radio selection for the strategy
219
- selected_strategy = gr.Radio(
220
- [
221
- (
222
- "Minimize the number of plants sold (prioritize high-priced plants)",
223
- "MaximizeStock",
224
- ),
225
- (
226
- "Maximize the number of plants sold (prioritize low-priced plants)",
227
- "MinimizeStock",
228
- ),
229
- ],
230
- value="MaximizeStock",
231
- label="Strategies",
232
- info="Select a strategy:",
233
- )
 
 
 
 
234
 
235
- # Add a row for the plant inventory inputs
236
- with gr.Row():
237
  inventory = {}
238
  species_set = set()
239
  species_count = 0
240
  for _, row in df.iterrows():
241
- if is_gold := row["gold"] != 0:
 
242
  species_set.add(row["species"])
243
- inventory[f"{row['species']}_{row['tier']}"] = gr.Number(
244
- # label=f"{PLANTS_I18N[row['species']]}_{PLANTS_TIERS[row['tier']]}",
245
- label=PLANTS_LABLES[row["species"]],
246
- info=PLANTS_TIERS[row["tier"]] + " $" + str(row["gold"]),
247
- value=0,
248
- precision=0,
249
- minimum=0,
250
- maximum=500,
251
- step=10,
252
- visible=is_gold,
253
- elem_classes=("firstbox" if len(species_set) > species_count else None),
254
- )
255
- species_count = len(species_set)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
256
 
257
  # Add a row for the Clear and Calculate buttons
258
  with gr.Row():
259
- clear_btn = gr.ClearButton(list(inventory.values()), size="sm", value="❌Clear")
260
 
261
- # Add a button to trigger the calculation
262
- submit_btn = gr.Button(value="🛠Calculate")
263
 
264
  # Add a row for the result textbox
265
  with gr.Row():
@@ -268,14 +300,18 @@ with gr.Blocks(css=css) as demo:
268
  # Set up the button click event to call the calculator function
269
  submit_btn.click(
270
  calculator,
271
- inputs=[budget]
272
- + [selected_strategy]
273
- + [acquisition_rate]
274
- + list(inventory.values()),
275
  outputs=[result],
276
  api_name=False,
277
  )
278
 
 
 
 
 
 
 
 
279
 
280
  # Launch the Gradio application
281
  demo.queue(api_open=False)
 
3
  import pulp
4
  import numpy as np
5
  from numpy.typing import NDArray
6
+ from pyscipopt import Model, quicksum
7
 
8
  # Define the quality tiers and names for the plants
9
  PLANTS_TIERS = {
 
106
  the total gold earned, and the remaining inventory.
107
  Returns an error message if no solution is found.
108
  """
109
+ currency, budget, strategy, extra_rate = args[0:4]
110
+ # budget: int = args[0] # 葭碧预算
111
+ # strategy: str = args[1] # 出售策略
112
+ # extra_rate: int = args[2] # 高价收购倍率
113
  stocks: NDArray[np.int_ | np.integer] = np.array(
114
+ [x if x else 0 for x in args[4:]]
115
  ) # 植物库存
116
 
117
+ # Plant names and prices
118
  plants_names = [
119
  f"{PLANTS_TIERS[row['tier']]} {PLANTS_LABLES[row['species']]}"
120
  for index, row in df.iterrows()
121
  ]
122
+ price = df[currency] # 植物单价
123
+ sold_prices = np.array(price * (1 + extra_rate))
124
 
125
+ # Initialize the master problem
126
+ model = Model("BewilderingBlossom")
127
 
128
+ # Decision variables in master problem
129
+ x = [
130
+ model.addVar(
131
+ vtype="I", name=f"x_{i}", lb=0, ub=int(stocks[i]) if stocks[i] else 0
132
+ )
133
+ for i in range(len(stocks))
134
+ ]
 
135
 
136
+ obj1 = quicksum(sold_prices[i] * x[i] for i in range(len(stocks)))
137
+ obj2 = quicksum(x[i] for i in range(len(stocks)))
138
 
139
+ # Objective: maximize total value of sold plants
140
+ model.setObjective(obj1, "maximize")
 
141
 
142
+ model.addCons(obj1 <= budget)
 
143
 
144
+ # first optimize
145
+ model.hideOutput()
146
+ model.optimize()
147
 
148
+ if model.getStatus() == "optimal":
149
+ optimal_total_value = model.getObjVal()
150
+ model.freeTransform()
 
151
 
152
+ model.setObjective(
153
+ obj2, "maximize" if strategy == "MinimizeStock" else "minimize"
154
+ )
155
+ model.addCons(obj1 == optimal_total_value)
156
+ model.optimize()
157
 
158
+ # Final solution processing
159
  solution = []
160
+ # total_price = 0
161
+ # total_count = 0
162
+
163
+ if model.getStatus() == "optimal":
164
+ for i, var in enumerate(x):
165
+ if (v := int(model.getVal(var))) > 0 and sold_prices[i] > 0:
166
  solution.append(
167
+ f"{plants_names[i]} ({sold_prices[i]} {currency}): {v}\n"
168
  )
169
+ # total_price += v * sold_prices[i]
170
+ # total_count += v
171
+
172
+ if optimal_total_value == budget:
173
+ return f"\nGreat! Found a combination of items with a total value equal to the budget ({budget} {currency}).😃\n\n{''.join(solution)}\nTotal Price: {int(optimal_total_value)} {currency}\n" # Count: {int(model.getObjVal())}
174
+ else:
175
+ return f"Oops! {int(budget - optimal_total_value)} {currency} short of the target value ({budget} {currency}).😂\n\n{''.join(solution)}\nTotal value: {int(optimal_total_value)} {currency}\n" # Count: {int(model.getObjVal())}
176
+ else:
177
+ return "No solution found for the second optimization!"
178
  else:
179
+ return "No solution found for the first optimization!"
180
 
181
 
182
  # 高亮每种植物的最高品质
183
  css = """
184
+ .first-gold-box {background-color: #fafad2}
185
+ .first-gems-box {background-color: #fed9b4}
186
  """
187
 
188
  with gr.Blocks(css=css) as demo:
 
191
  <center><font size=8>HP-Magic-Awakened Herbologist Toolkit👾</font></center>
192
 
193
  This program is essentially a solver for a variant of the knapsack problem.
194
+ Another more versatile [application](https://huggingface.co/spaces/oh-my-dear-ai/easy-knapsack-problem).
 
195
  """
196
  )
197
 
198
+ gold_or_gems = gr.State("gold")
 
 
 
 
 
 
 
199
 
200
  # Create a Gradio interface with a column layout
201
  with gr.Column():
202
+ # Add a row for the currency selection
203
+ currency_radio = gr.Radio(
204
+ choices=["gold", "gems"],
205
+ value="gold",
206
+ type="value",
207
+ label="Currency",
208
+ info="Select the currency:",
209
+ render=True,
210
+ )
211
  # Add a row for the budget input
212
  budget = gr.Number(
213
+ label="Target",
214
+ info="Gabby's Budget:", # "葭碧の金币预算:",
215
  value=0,
216
  minimum=0,
217
  maximum=20000,
 
220
  acquisition_rate = gr.Dropdown(
221
  choices=[
222
  "0(Gabby's Acquisition)",
223
+ "+100%(HVA for Budding & Novice)",
224
+ "+200%(HVA for Junior & Practiced)",
225
+ "+300%(HVA for Natural & Master)",
226
  ],
227
  value="0(Gabby's Acquisition)",
228
  type="index",
 
230
  info="Select your high-value acquisition rate:",
231
  )
232
 
233
+ # Add a radio selection for the strategy
234
+ selected_strategy = gr.Radio(
235
+ [
236
+ (
237
+ "Minimize the number of plants sold (prioritize high-priced plants)",
238
+ "MaximizeStock",
239
+ ),
240
+ (
241
+ "Maximize the number of plants sold (prioritize low-priced plants)",
242
+ "MinimizeStock",
243
+ ),
244
+ ],
245
+ value="MaximizeStock",
246
+ label="Strategies",
247
+ info="Select a strategy:",
248
+ )
249
+
250
+ def show_plant_boxes(currency):
251
+ # Update the state variable
252
+ gold_or_gems.value = currency
253
 
 
 
254
  inventory = {}
255
  species_set = set()
256
  species_count = 0
257
  for _, row in df.iterrows():
258
+ # Check if the plant should be shown based on the selected currency
259
+ if row[currency] != 0:
260
  species_set.add(row["species"])
261
+ # Create the Number component for the plant inventory
262
+ inventory[f"{row['species']}_{row['tier']}"] = gr.Number(
263
+ label=PLANTS_LABLES[row["species"]],
264
+ info=f"{PLANTS_TIERS[row['tier']]} ${row[currency]}",
265
+ value=0,
266
+ precision=0,
267
+ minimum=0,
268
+ maximum=500,
269
+ step=10,
270
+ visible=True,
271
+ elem_classes=(
272
+ f"first-{currency}-box"
273
+ if len(species_set) > species_count
274
+ else None
275
+ ),
276
+ )
277
+ species_count = len(species_set)
278
+ else:
279
+ # If not shown, create a dummy invisible component
280
+ inventory[f"{row['species']}_{row['tier']}"] = gr.Number(visible=False)
281
+
282
+ # Return the updated inventory components
283
+ return list(inventory.values())
284
+
285
+ # Create the dynamic plant inventory inputs
286
+ with gr.Row() as inventory_row:
287
+ inventory = show_plant_boxes(gold_or_gems.value)
288
 
289
  # Add a row for the Clear and Calculate buttons
290
  with gr.Row():
291
+ clear_btn = gr.ClearButton(inventory, size="sm", value="❌Clear")
292
 
293
+ # Add a button to trigger the calculation
294
+ submit_btn = gr.Button(value="🛠Calculate")
295
 
296
  # Add a row for the result textbox
297
  with gr.Row():
 
300
  # Set up the button click event to call the calculator function
301
  submit_btn.click(
302
  calculator,
303
+ inputs=[gold_or_gems, budget, selected_strategy, acquisition_rate] + inventory,
 
 
 
304
  outputs=[result],
305
  api_name=False,
306
  )
307
 
308
+ # Update the inventory when the currency changes
309
+ currency_radio.change(
310
+ fn=lambda selected_currency: show_plant_boxes(selected_currency)
311
+ + [selected_currency], # Adjusted function to return only the components
312
+ inputs=currency_radio,
313
+ outputs=inventory + [gold_or_gems], # Update each child in the inventory_row
314
+ )
315
 
316
  # Launch the Gradio application
317
  demo.queue(api_open=False)
plants.csv CHANGED
@@ -128,10 +128,17 @@ gold,gems,species,tier
128
  58,0,mosaic_flower,hardy_rarecolor
129
  52,0,mosaic_flower,hardy
130
  1,0,mosaic_flower,feeble
 
131
  0,4,sunflower,radiant
 
132
  96,0,sunflower,flourishing
 
133
  75,0,sunflower,hardy
134
  1,0,sunflower,feeble
 
 
 
 
135
  299,0,water_lily,radiant_rarecolor
136
  271,0,water_lily,radiant
137
  244,0,water_lily,flourishing_rarecolor
 
128
  58,0,mosaic_flower,hardy_rarecolor
129
  52,0,mosaic_flower,hardy
130
  1,0,mosaic_flower,feeble
131
+ 0,5,sunflower,radiant_rarecolor
132
  0,4,sunflower,radiant
133
+ 106,0,sunflower,flourishing_rarecolor
134
  96,0,sunflower,flourishing
135
+ 83,0,sunflower,hardy_rarecolor
136
  75,0,sunflower,hardy
137
  1,0,sunflower,feeble
138
+ 62,0,mimbulus_mimbletonia,radiant
139
+
140
+ 39,0,mimbulus_mimbletonia,hardy
141
+ 1,0,mimbulus_mimbletonia,feeble
142
  299,0,water_lily,radiant_rarecolor
143
  271,0,water_lily,radiant
144
  244,0,water_lily,flourishing_rarecolor
requirements.txt CHANGED
Binary files a/requirements.txt and b/requirements.txt differ