Compare commits
No commits in common. "d18e533559a58655d2e8672e8f4c95c1f8ad9809" and "b8b21bad6bd08927576c3397db3b75e8fef822b2" have entirely different histories.
d18e533559
...
b8b21bad6b
3 changed files with 5 additions and 110 deletions
|
|
@ -1,105 +0,0 @@
|
||||||
# Configurable Maximize Criteria for `solve()`
|
|
||||||
|
|
||||||
**Date:** 2026-06-11
|
|
||||||
**Status:** Approved
|
|
||||||
|
|
||||||
## Problem
|
|
||||||
|
|
||||||
The objective in `solve.py` is hardcoded at the bottom of `solve()` (the
|
|
||||||
"OBJECTIVE IS SET HERE" block): it maximizes the monomial
|
|
||||||
`finalE² · finalB¹ · finalS²` via hand-chained `AddMultiplicationEquality`
|
|
||||||
calls, with intermediate bounds derived from the Phase-1 per-resource
|
|
||||||
ceilings. Changing the criteria means editing solver internals. The actual
|
|
||||||
usage pattern (per git history) is varying the per-resource powers, so the
|
|
||||||
criteria should be a parameter of `solve()`.
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
Two new keyword-only arguments on `solve()`, with module-level defaults in
|
|
||||||
the PARAMETERS section (matching the existing `INITIAL` / `FIXED_CHOICES`
|
|
||||||
pattern):
|
|
||||||
|
|
||||||
```python
|
|
||||||
OBJECTIVE_FACTORS = {"E": 2, "B": 1, "S": 2} # missing keys = 0 (excluded)
|
|
||||||
OBJECTIVE_MODE = "product" # "product" or "sum"
|
|
||||||
|
|
||||||
def solve(*, ..., objective_factors=None, objective_mode=None):
|
|
||||||
```
|
|
||||||
|
|
||||||
- `None` for either argument falls back to the module-level default, the
|
|
||||||
same convention `resource_constraints` already uses.
|
|
||||||
- Defaults reproduce the current hardcoded objective (`E²·B·S²`) exactly.
|
|
||||||
- Valid factor keys: `"E"`, `"B"`, `"S"`, `"C"` (Capital becomes
|
|
||||||
targetable). Values are integers.
|
|
||||||
|
|
||||||
### Validation (fail fast with `ValueError`)
|
|
||||||
|
|
||||||
- Unknown key in `objective_factors`.
|
|
||||||
- All factors zero (or empty dict) — degenerate objective.
|
|
||||||
- `objective_mode` not in `{"product", "sum"}`.
|
|
||||||
- Non-integer factor values.
|
|
||||||
- Negative factor values **in product mode only**: a negative exponent
|
|
||||||
means division, which integer CP-SAT cannot express. Sum mode accepts
|
|
||||||
negative weights — CP-SAT handles negative coefficients in linear
|
|
||||||
objectives natively.
|
|
||||||
|
|
||||||
## Semantics
|
|
||||||
|
|
||||||
The factor means **exponent** in product mode and **weight** in sum mode.
|
|
||||||
|
|
||||||
- **`product`**: maximize `Πₖ finalₖ^factorₖ`. Built generically: expand the
|
|
||||||
factors to a flat list of final-resource vars (e.g.
|
|
||||||
`[finalE, finalE, finalB, finalS, finalS]`), then fold pairwise with
|
|
||||||
`AddMultiplicationEquality`, carrying a running upper bound multiplied
|
|
||||||
from the Phase-1 caps. This replaces the hand-chained `prodEE`/`prodSS`/
|
|
||||||
`prodBB`/`prodEB`/`obj` block and the `Eprod`/`Bprod`/`Sprod` bound
|
|
||||||
helpers.
|
|
||||||
- **`sum`**: maximize `Σₖ factorₖ · finalₖ` — a single `m.Maximize(...)` on
|
|
||||||
a linear expression; no auxiliary variables. May be negative when
|
|
||||||
negative weights are used.
|
|
||||||
|
|
||||||
A factor of 0 drops the resource from the objective (exponent 0 → factor
|
|
||||||
of 1 in product mode), matching current behavior where Capital simply
|
|
||||||
isn't in the objective. It does not force the resource to zero.
|
|
||||||
|
|
||||||
## Phase-1 ceilings
|
|
||||||
|
|
||||||
Ceilings (`_ceiling`) are computed only for resources with a nonzero
|
|
||||||
factor. Rationale:
|
|
||||||
|
|
||||||
- Each ceiling solve costs up to 20 s; skipping unused resources is a real
|
|
||||||
win.
|
|
||||||
- `C` gets a ceiling only when it appears in a product objective, where the
|
|
||||||
bound is required for the intermediate product variables.
|
|
||||||
- Sum mode does not need caps for bounds, but the redundant
|
|
||||||
`final ≤ cap` constraints are still added for computed ceilings since
|
|
||||||
they prune the search.
|
|
||||||
|
|
||||||
## Reporting
|
|
||||||
|
|
||||||
`_report` receives the resolved objective spec (factors + mode) and prints
|
|
||||||
the expression that was actually maximized along with its value, replacing
|
|
||||||
the hardcoded `product(scaled) = E·B·S` line. Examples:
|
|
||||||
|
|
||||||
- product mode: `objective E^2*B*S^2 = <value>` with the value descaled by
|
|
||||||
`10^(sum of exponents)`.
|
|
||||||
- sum mode: `objective 2E + B - S = <value>` descaled by 10.
|
|
||||||
|
|
||||||
`finalC` (`C[NUM_STEPS + 1]`) participates in the printout when `"C"` has a
|
|
||||||
nonzero factor; `_report` already receives the `C` pool dict. The
|
|
||||||
per-resource FINAL line and the intermediate solution printer (E/B/S)
|
|
||||||
remain unchanged.
|
|
||||||
|
|
||||||
## Unchanged
|
|
||||||
|
|
||||||
- The intermediate solution printer still tracks E/B/S.
|
|
||||||
- `web_solve.py` keeps working unmodified — both new arguments are
|
|
||||||
optional with behavior-preserving defaults.
|
|
||||||
|
|
||||||
## Bounds note
|
|
||||||
|
|
||||||
With `MAX_RES = 2000`, the default product bound is
|
|
||||||
`2000⁵ = 3.2 × 10¹⁶`, comfortably inside CP-SAT's int64 objective range.
|
|
||||||
Extreme factor values could overflow; the fold computes the running bound
|
|
||||||
explicitly, so an overflow would surface as a CP-SAT model error rather
|
|
||||||
than silent wraparound. No additional guard is included (YAGNI).
|
|
||||||
5
solve.py
5
solve.py
|
|
@ -137,6 +137,7 @@ AGENT_AVAILABILITY = {
|
||||||
"fence": [],
|
"fence": [],
|
||||||
"foreman": [],
|
"foreman": [],
|
||||||
"industrialist": [],
|
"industrialist": [],
|
||||||
|
"economist": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
# ======================================================================
|
# ======================================================================
|
||||||
|
|
@ -1566,9 +1567,9 @@ def solve(
|
||||||
return v * v
|
return v * v
|
||||||
|
|
||||||
prodEE = m.NewIntVar(0, Eprod(capE), "prodEE")
|
prodEE = m.NewIntVar(0, Eprod(capE), "prodEE")
|
||||||
m.AddMultiplicationEquality(prodEE, [finalE, finalE])
|
m.AddMultiplicationEquality(prodEE, [finalE])
|
||||||
prodSS = m.NewIntVar(0, Sprod(capS), "prodSS")
|
prodSS = m.NewIntVar(0, Sprod(capS), "prodSS")
|
||||||
m.AddMultiplicationEquality(prodSS, [finalS, finalS])
|
m.AddMultiplicationEquality(prodSS, [finalS])
|
||||||
prodBB = m.NewIntVar(0, Bprod(capB), "prodBB")
|
prodBB = m.NewIntVar(0, Bprod(capB), "prodBB")
|
||||||
m.AddMultiplicationEquality(prodBB, [finalB])
|
m.AddMultiplicationEquality(prodBB, [finalB])
|
||||||
prodEB = m.NewIntVar(0, Eprod(capE) * Bprod(capB), "prodEB")
|
prodEB = m.NewIntVar(0, Eprod(capE) * Bprod(capB), "prodEB")
|
||||||
|
|
|
||||||
|
|
@ -159,9 +159,8 @@ def solve_handler():
|
||||||
max_res=solve.MAX_RES,
|
max_res=solve.MAX_RES,
|
||||||
max_vat=solve.MAX_VAT,
|
max_vat=solve.MAX_VAT,
|
||||||
# min to avoid bricking stuff
|
# min to avoid bricking stuff
|
||||||
# time_limit=min(time_limit, 60.0),
|
time_limit=min(time_limit, 60.0),
|
||||||
time_limit=time_limit,
|
num_workers=1,
|
||||||
num_workers=8,
|
|
||||||
verbose=verbose,
|
verbose=verbose,
|
||||||
fixed_choices=fixed_choices,
|
fixed_choices=fixed_choices,
|
||||||
resource_constraints=resource_constraints,
|
resource_constraints=resource_constraints,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue