AnayShukla commited on
Commit
21b054e
·
1 Parent(s): 1f058dc

solver updates

Browse files
Files changed (2) hide show
  1. solver.py +62 -5
  2. solver_engine.py +30 -0
solver.py CHANGED
@@ -15,6 +15,7 @@ def run_milp_model(data: dict):
15
  current_squad = data["current_squad"]
16
  start_itb = data["itb"]
17
  start_ft = data["ft"]
 
18
 
19
  settings = data.get("settings", {})
20
 
@@ -141,6 +142,48 @@ def run_milp_model(data: dict):
141
  prev_w = gws[i - 1]
142
  gw_ft_gain[w] = gw_ft_value[w] - gw_ft_value[prev_w]
143
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  # --- OBJECTIVE FUNCTION (Decayed to match original) ---
145
  obj_parts = []
146
  for i, w in enumerate(gws):
@@ -173,6 +216,9 @@ def run_milp_model(data: dict):
173
  for p in players:
174
  obj_parts.append(-transfer_in[p][w] * ft_use_penalty * decay_factor)
175
 
 
 
 
176
  prob += pulp.lpSum(obj_parts), "Total_EV_Objective"
177
 
178
  # --- ADVANCED HORIZON-LEVEL CONSTRAINTS ---
@@ -419,7 +465,13 @@ def run_milp_model(data: dict):
419
 
420
  # THE FIX: Boot up the HiGHS engine instead of CBC
421
  try:
422
- solver = pulp.getSolver("HiGHS", msg=False, timeLimit=time_limit_sec)
 
 
 
 
 
 
423
  except pulp.PulpSolverError:
424
  print("HiGHS not found! Falling back to CBC...")
425
  solver = pulp.PULP_CBC_CMD(msg=False, timeLimit=time_limit_sec)
@@ -530,14 +582,19 @@ def run_milp_model(data: dict):
530
  if chip == "bb":
531
  gw_pure_ev += sum(raw_ev_matrix[p].get(w, 0) for p in gw_bench_sorted)
532
  else:
533
- # THE FIX 2: Perfectly mimic React's horizonEV fractional bench math
534
- of_bench_ws = [0.17, 0.05, 0.02]
535
  of_idx = 0
536
  for p in gw_bench_sorted:
537
  if positions.get(p, "M") == "G":
538
- gw_pure_ev += raw_ev_matrix[p].get(w, 0) * 0.04
 
539
  else:
540
- bw = of_bench_ws[of_idx] if of_idx < len(of_bench_ws) else 0.02
 
 
 
 
 
541
  gw_pure_ev += raw_ev_matrix[p].get(w, 0) * bw
542
  of_idx += 1
543
 
 
15
  current_squad = data["current_squad"]
16
  start_itb = data["itb"]
17
  start_ft = data["ft"]
18
+ gw_opp_teams = data.get("gw_opp_teams", {})
19
 
20
  settings = data.get("settings", {})
21
 
 
142
  prev_w = gws[i - 1]
143
  gw_ft_gain[w] = gw_ft_value[w] - gw_ft_value[prev_w]
144
 
145
+ # --- CROSS-PLAY PENALTY LOGIC ---
146
+ cp_penalty_expr = {w: 0 for w in gws}
147
+
148
+ if settings.get("no_opposing_play") and gw_opp_teams:
149
+ opp_group = settings.get("opposing_play_group", "all")
150
+ pen_val = float(settings.get("opposing_play_penalty", 0.5))
151
+
152
+ opp_pos = [
153
+ ("G", "F"),
154
+ ("G", "M"),
155
+ ("D", "F"),
156
+ ("D", "M"),
157
+ ("F", "G"),
158
+ ("M", "G"),
159
+ ("F", "D"),
160
+ ("M", "D"),
161
+ ]
162
+
163
+ cp_list = []
164
+ for w in gws:
165
+ for p1 in players:
166
+ for p2 in players:
167
+ # Look up the pre-calculated dictionary!
168
+ if p1 != p2 and (teams[p1], teams[p2]) in gw_opp_teams.get(w, []):
169
+ if (
170
+ opp_group == "all"
171
+ or (positions[p1], positions[p2]) in opp_pos
172
+ ):
173
+ cp_list.append((p1, p2, w))
174
+
175
+ if cp_list:
176
+ cp_vars = pulp.LpVariable.dicts("cp", cp_list, cat="Binary")
177
+ for p1, p2, w in cp_list:
178
+ prob += lineup[p1][w] + lineup[p2][w] <= 1 + cp_vars[(p1, p2, w)]
179
+ prob += cp_vars[(p1, p2, w)] <= lineup[p1][w]
180
+ prob += cp_vars[(p1, p2, w)] <= lineup[p2][w]
181
+
182
+ for w in gws:
183
+ cp_penalty_expr[w] = pen_val * pulp.lpSum(
184
+ cp_vars[(p1, p2, gw)] for (p1, p2, gw) in cp_list if gw == w
185
+ )
186
+
187
  # --- OBJECTIVE FUNCTION (Decayed to match original) ---
188
  obj_parts = []
189
  for i, w in enumerate(gws):
 
216
  for p in players:
217
  obj_parts.append(-transfer_in[p][w] * ft_use_penalty * decay_factor)
218
 
219
+ if cp_penalty_expr[w] != 0:
220
+ obj_parts.append(-cp_penalty_expr[w] * decay_factor)
221
+
222
  prob += pulp.lpSum(obj_parts), "Total_EV_Objective"
223
 
224
  # --- ADVANCED HORIZON-LEVEL CONSTRAINTS ---
 
465
 
466
  # THE FIX: Boot up the HiGHS engine instead of CBC
467
  try:
468
+ gap = float(settings.get("gap", 0.005))
469
+ solver = pulp.getSolver(
470
+ "HiGHS",
471
+ msg=False,
472
+ timeLimit=time_limit_sec,
473
+ options=["presolve=on", f"mip_rel_gap={gap}"],
474
+ )
475
  except pulp.PulpSolverError:
476
  print("HiGHS not found! Falling back to CBC...")
477
  solver = pulp.PULP_CBC_CMD(msg=False, timeLimit=time_limit_sec)
 
582
  if chip == "bb":
583
  gw_pure_ev += sum(raw_ev_matrix[p].get(w, 0) for p in gw_bench_sorted)
584
  else:
585
+ # THE FIX 2: Stop hardcoding! Use the dynamic weights extracted from React at the top of the file!
 
586
  of_idx = 0
587
  for p in gw_bench_sorted:
588
  if positions.get(p, "M") == "G":
589
+ # Uses the gk_bench_w parsed at line 46
590
+ gw_pure_ev += raw_ev_matrix[p].get(w, 0) * gk_bench_w
591
  else:
592
+ # Uses the of_bench_ws array parsed at line 47
593
+ bw = (
594
+ of_bench_ws[of_idx]
595
+ if of_idx < len(of_bench_ws)
596
+ else avg_of_bench_w
597
+ )
598
  gw_pure_ev += raw_ev_matrix[p].get(w, 0) * bw
599
  of_idx += 1
600
 
solver_engine.py CHANGED
@@ -1,5 +1,8 @@
 
1
  from typing import Any
2
 
 
 
3
 
4
  def _norm_id_list(raw) -> list[int]:
5
  if not raw:
@@ -180,6 +183,32 @@ def prep_solver_data(payload_data: dict):
180
  ev_matrix[pid][gw] = float(raw_ev) * decay_factor
181
  raw_ev_matrix[pid][gw] = float(raw_ev)
182
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  return {
184
  "players": list(buy_prices.keys()),
185
  "gws": horizon_gws,
@@ -192,4 +221,5 @@ def prep_solver_data(payload_data: dict):
192
  "itb": float(payload_data["in_the_bank"]),
193
  "ft": int(payload_data["free_transfers"]),
194
  "settings": settings,
 
195
  }
 
1
+ import requests
2
  from typing import Any
3
 
4
+ _FIXTURE_CACHE = []
5
+
6
 
7
  def _norm_id_list(raw) -> list[int]:
8
  if not raw:
 
183
  ev_matrix[pid][gw] = float(raw_ev) * decay_factor
184
  raw_ev_matrix[pid][gw] = float(raw_ev)
185
 
186
+ # --- CROSS-PLAY FIXTURE PREP ---
187
+ global _FIXTURE_CACHE
188
+ gw_opp_teams = {w: [] for w in horizon_gws}
189
+
190
+ if settings.get("no_opposing_play"):
191
+ if not _FIXTURE_CACHE:
192
+ try:
193
+ # Same fast cache logic as solver_the_real_one
194
+ _FIXTURE_CACHE = requests.get(
195
+ "https://fantasy.premierleague.com/api/fixtures/"
196
+ ).json()
197
+ except: # noqa: E722
198
+ pass
199
+
200
+ # Map the opponent matchups for the solver
201
+ team_mapping = {v: k for k, v in teams.items()} # Reverse lookup
202
+ for f in _FIXTURE_CACHE:
203
+ w = f.get("event")
204
+ if w in horizon_gws:
205
+ home_team = team_mapping.get(f.get("team_h"))
206
+ away_team = team_mapping.get(f.get("team_a"))
207
+ if home_team and away_team:
208
+ gw_opp_teams[w].extend(
209
+ [(home_team, away_team), (away_team, home_team)]
210
+ )
211
+
212
  return {
213
  "players": list(buy_prices.keys()),
214
  "gws": horizon_gws,
 
221
  "itb": float(payload_data["in_the_bank"]),
222
  "ft": int(payload_data["free_transfers"]),
223
  "settings": settings,
224
+ "gw_opp_teams": gw_opp_teams,
225
  }