added intermediate printing
This commit is contained in:
parent
87b6529671
commit
2db8ff63c7
3 changed files with 185 additions and 12 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
.serena
|
||||||
|
__pycache__
|
||||||
|
output.txt
|
||||||
174
main.py
174
main.py
|
|
@ -11,7 +11,7 @@ LP/MIP solver. Read the comments at:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from ortools.sat.python import cp_model
|
from ortools.sat.python import cp_model
|
||||||
|
import printer
|
||||||
|
|
||||||
# ======================================================================
|
# ======================================================================
|
||||||
# PARAMETERS -- edit these
|
# PARAMETERS -- edit these
|
||||||
|
|
@ -24,10 +24,10 @@ INITIAL = (3, 3, 3, 3)
|
||||||
# at the START of that step. Types: 'H' Hub, 'F' Foundry, 'M' Metropolis, 'N' Monument.
|
# at the START of that step. Types: 'H' Hub, 'F' Foundry, 'M' Metropolis, 'N' Monument.
|
||||||
# Total cities across all steps must be <= 7. Arriving cities act that same step.
|
# Total cities across all steps must be <= 7. Arriving cities act that same step.
|
||||||
ARRIVALS = {
|
ARRIVALS = {
|
||||||
1: ["H", "F", "H", "N", "F"],
|
1: ["H", "F", "H"],
|
||||||
2: ["M"],
|
2: ["F"],
|
||||||
3: [],
|
3: [],
|
||||||
4: [],
|
4: ["N"],
|
||||||
5: [],
|
5: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,9 +81,12 @@ def solve(
|
||||||
|
|
||||||
# action variables, t in 1..NUM_STEPS
|
# action variables, t in 1..NUM_STEPS
|
||||||
col, ua, ub, ud = {}, {}, {}, {} # collect / upgrade a,b,d
|
col, ua, ub, ud = {}, {}, {}, {} # collect / upgrade a,b,d
|
||||||
|
ow = {} # overwork (global limit: 1 per step)
|
||||||
rH, rF, rM = {}, {}, {} # renovate -> Hub/Foundry/Metro
|
rH, rF, rM = {}, {}, {} # renovate -> Hub/Foundry/Metro
|
||||||
cvE, cvB, cvS = {}, {}, {} # foundry: which vat collected
|
cvE, cvB, cvS = {}, {}, {} # foundry: which vat collected (normal collect)
|
||||||
|
owcvE, owcvB, owcvS = {}, {}, {} # foundry: which vat collected (overwork)
|
||||||
mE, mB, mS, mC = {}, {}, {}, {} # metropolis: resources picked (+1 each)
|
mE, mB, mS, mC = {}, {}, {}, {} # metropolis: resources picked (+1 each)
|
||||||
|
owmE, owmB, owmS, owmC = {}, {}, {}, {} # metropolis: resources picked (overwork)
|
||||||
|
|
||||||
for i in range(N):
|
for i in range(N):
|
||||||
a_step, a_type = cities[i]
|
a_step, a_type = cities[i]
|
||||||
|
|
@ -110,16 +113,24 @@ def solve(
|
||||||
ua[i, t] = m.NewBoolVar(f"ua_{i}_{t}")
|
ua[i, t] = m.NewBoolVar(f"ua_{i}_{t}")
|
||||||
ub[i, t] = m.NewBoolVar(f"ub_{i}_{t}")
|
ub[i, t] = m.NewBoolVar(f"ub_{i}_{t}")
|
||||||
ud[i, t] = m.NewBoolVar(f"ud_{i}_{t}")
|
ud[i, t] = m.NewBoolVar(f"ud_{i}_{t}")
|
||||||
|
ow[i, t] = m.NewBoolVar(f"ow_{i}_{t}")
|
||||||
rH[i, t] = m.NewBoolVar(f"rH_{i}_{t}")
|
rH[i, t] = m.NewBoolVar(f"rH_{i}_{t}")
|
||||||
rF[i, t] = m.NewBoolVar(f"rF_{i}_{t}")
|
rF[i, t] = m.NewBoolVar(f"rF_{i}_{t}")
|
||||||
rM[i, t] = m.NewBoolVar(f"rM_{i}_{t}")
|
rM[i, t] = m.NewBoolVar(f"rM_{i}_{t}")
|
||||||
cvE[i, t] = m.NewBoolVar(f"cvE_{i}_{t}")
|
cvE[i, t] = m.NewBoolVar(f"cvE_{i}_{t}")
|
||||||
cvB[i, t] = m.NewBoolVar(f"cvB_{i}_{t}")
|
cvB[i, t] = m.NewBoolVar(f"cvB_{i}_{t}")
|
||||||
cvS[i, t] = m.NewBoolVar(f"cvS_{i}_{t}")
|
cvS[i, t] = m.NewBoolVar(f"cvS_{i}_{t}")
|
||||||
|
owcvE[i, t] = m.NewBoolVar(f"owcvE_{i}_{t}")
|
||||||
|
owcvB[i, t] = m.NewBoolVar(f"owcvB_{i}_{t}")
|
||||||
|
owcvS[i, t] = m.NewBoolVar(f"owcvS_{i}_{t}")
|
||||||
mE[i, t] = m.NewIntVar(0, 3, f"mE_{i}_{t}")
|
mE[i, t] = m.NewIntVar(0, 3, f"mE_{i}_{t}")
|
||||||
mB[i, t] = m.NewIntVar(0, 3, f"mB_{i}_{t}")
|
mB[i, t] = m.NewIntVar(0, 3, f"mB_{i}_{t}")
|
||||||
mS[i, t] = m.NewIntVar(0, 3, f"mS_{i}_{t}")
|
mS[i, t] = m.NewIntVar(0, 3, f"mS_{i}_{t}")
|
||||||
mC[i, t] = m.NewIntVar(0, 3, f"mC_{i}_{t}")
|
mC[i, t] = m.NewIntVar(0, 3, f"mC_{i}_{t}")
|
||||||
|
owmE[i, t] = m.NewIntVar(0, 6, f"owmE_{i}_{t}")
|
||||||
|
owmB[i, t] = m.NewIntVar(0, 6, f"owmB_{i}_{t}")
|
||||||
|
owmS[i, t] = m.NewIntVar(0, 6, f"owmS_{i}_{t}")
|
||||||
|
owmC[i, t] = m.NewIntVar(0, 6, f"owmC_{i}_{t}")
|
||||||
|
|
||||||
# ---- per-step gain/cost accumulators (linear expressions) ---------
|
# ---- per-step gain/cost accumulators (linear expressions) ---------
|
||||||
gain_E = {t: [] for t in range(1, NUM_STEPS + 1)}
|
gain_E = {t: [] for t in range(1, NUM_STEPS + 1)}
|
||||||
|
|
@ -150,7 +161,30 @@ def solve(
|
||||||
for v in (isH, isF, isM, isMon, hasA, hasB, hasD, vE, vB, vS):
|
for v in (isH, isF, isM, isMon, hasA, hasB, hasD, vE, vB, vS):
|
||||||
m.Add(v[i, t] == 0)
|
m.Add(v[i, t] == 0)
|
||||||
if t <= NUM_STEPS:
|
if t <= NUM_STEPS:
|
||||||
for v in (col, ua, ub, ud, rH, rF, rM, cvE, cvB, cvS, mE, mB, mS, mC):
|
for v in (
|
||||||
|
col,
|
||||||
|
ua,
|
||||||
|
ub,
|
||||||
|
ud,
|
||||||
|
ow,
|
||||||
|
rH,
|
||||||
|
rF,
|
||||||
|
rM,
|
||||||
|
cvE,
|
||||||
|
cvB,
|
||||||
|
cvS,
|
||||||
|
owcvE,
|
||||||
|
owcvB,
|
||||||
|
owcvS,
|
||||||
|
mE,
|
||||||
|
mB,
|
||||||
|
mS,
|
||||||
|
mC,
|
||||||
|
owmE,
|
||||||
|
owmB,
|
||||||
|
owmS,
|
||||||
|
owmC,
|
||||||
|
):
|
||||||
m.Add(v[i, t] == 0)
|
m.Add(v[i, t] == 0)
|
||||||
|
|
||||||
# action + transition logic for active steps
|
# action + transition logic for active steps
|
||||||
|
|
@ -167,6 +201,7 @@ def solve(
|
||||||
+ ua[i, t]
|
+ ua[i, t]
|
||||||
+ ub[i, t]
|
+ ub[i, t]
|
||||||
+ ud[i, t]
|
+ ud[i, t]
|
||||||
|
+ ow[i, t]
|
||||||
+ rH[i, t]
|
+ rH[i, t]
|
||||||
+ rF[i, t]
|
+ rF[i, t]
|
||||||
+ rM[i, t]
|
+ rM[i, t]
|
||||||
|
|
@ -192,6 +227,12 @@ def solve(
|
||||||
m.Add(ua[i, t] == 0).OnlyEnforceIf(isMon[i, t])
|
m.Add(ua[i, t] == 0).OnlyEnforceIf(isMon[i, t])
|
||||||
m.Add(ub[i, t] == 0).OnlyEnforceIf(isMon[i, t])
|
m.Add(ub[i, t] == 0).OnlyEnforceIf(isMon[i, t])
|
||||||
m.Add(ud[i, t] == 0).OnlyEnforceIf(isMon[i, t])
|
m.Add(ud[i, t] == 0).OnlyEnforceIf(isMon[i, t])
|
||||||
|
m.Add(ow[i, t] == 0).OnlyEnforceIf(isMon[i, t])
|
||||||
|
|
||||||
|
# overwork cooldown: city that overworked last step can't collect or overwork
|
||||||
|
if t > a_step:
|
||||||
|
m.Add(col[i, t] == 0).OnlyEnforceIf(ow[i, t - 1])
|
||||||
|
m.Add(ow[i, t] == 0).OnlyEnforceIf(ow[i, t - 1])
|
||||||
|
|
||||||
# ---- type transition t -> t+1 ----
|
# ---- type transition t -> t+1 ----
|
||||||
m.Add(isH[i, t + 1] == isH[i, t]).OnlyEnforceIf(no_ren)
|
m.Add(isH[i, t + 1] == isH[i, t]).OnlyEnforceIf(no_ren)
|
||||||
|
|
@ -272,6 +313,61 @@ def solve(
|
||||||
gain_B[t].append(AND(cvB[i, t], hasB[i, t]))
|
gain_B[t].append(AND(cvB[i, t], hasB[i, t]))
|
||||||
gain_S[t].append(AND(cvS[i, t], hasB[i, t]))
|
gain_S[t].append(AND(cvS[i, t], hasB[i, t]))
|
||||||
|
|
||||||
|
# ---- overwork sub-choices (double-collect; no Capital cost) ----
|
||||||
|
fow = AND(isF[i, t], ow[i, t])
|
||||||
|
mow = AND(isM[i, t], ow[i, t])
|
||||||
|
how = AND(isH[i, t], ow[i, t])
|
||||||
|
|
||||||
|
# foundry overwork: exactly one vat chosen iff foundry overworks
|
||||||
|
m.Add(owcvE[i, t] + owcvB[i, t] + owcvS[i, t] == fow)
|
||||||
|
|
||||||
|
# metropolis overwork: pick 2*(2 + hasB) resources; else nothing
|
||||||
|
ow_npick = m.NewIntVar(0, 6, "")
|
||||||
|
m.Add(ow_npick == 2 * (2 + hasB[i, t])).OnlyEnforceIf(mow)
|
||||||
|
m.Add(ow_npick == 0).OnlyEnforceIf(mow.Not())
|
||||||
|
m.Add(owmE[i, t] + owmB[i, t] + owmS[i, t] + owmC[i, t] == ow_npick)
|
||||||
|
|
||||||
|
# ================= OVERWORK GAINS (2x normal collect) =================
|
||||||
|
# Hub overwork: 2*(2 + hasB) Capital
|
||||||
|
hub_ow_gain = m.NewIntVar(0, 6, "")
|
||||||
|
m.Add(hub_ow_gain == 2 * (2 + hasB[i, t])).OnlyEnforceIf(how)
|
||||||
|
m.Add(hub_ow_gain == 0).OnlyEnforceIf(how.Not())
|
||||||
|
gain_C[t].append(hub_ow_gain)
|
||||||
|
|
||||||
|
# Metropolis overwork: +1 per pick (picks are already doubled via ow_npick)
|
||||||
|
gain_E[t].append(owmE[i, t])
|
||||||
|
gain_B[t].append(owmB[i, t])
|
||||||
|
gain_S[t].append(owmS[i, t])
|
||||||
|
gain_C[t].append(owmC[i, t])
|
||||||
|
|
||||||
|
# Foundry overwork: 2 * chosen vat's value
|
||||||
|
owgEf = m.NewIntVar(0, 2 * max_vat, "")
|
||||||
|
owgBf = m.NewIntVar(0, 2 * max_vat, "")
|
||||||
|
owgSf = m.NewIntVar(0, 2 * max_vat, "")
|
||||||
|
_owE = m.NewIntVar(0, max_vat, "")
|
||||||
|
_owB = m.NewIntVar(0, max_vat, "")
|
||||||
|
_owS = m.NewIntVar(0, max_vat, "")
|
||||||
|
m.AddMultiplicationEquality(_owE, [owcvE[i, t], vE[i, t]])
|
||||||
|
m.AddMultiplicationEquality(_owB, [owcvB[i, t], vB[i, t]])
|
||||||
|
m.AddMultiplicationEquality(_owS, [owcvS[i, t], vS[i, t]])
|
||||||
|
m.Add(owgEf == 2 * _owE)
|
||||||
|
m.Add(owgBf == 2 * _owB)
|
||||||
|
m.Add(owgSf == 2 * _owS)
|
||||||
|
gain_E[t].append(owgEf)
|
||||||
|
gain_B[t].append(owgBf)
|
||||||
|
gain_S[t].append(owgSf)
|
||||||
|
|
||||||
|
# Collect Bonus (b) for foundry overwork: 2 * (+1) = +2 of that resource
|
||||||
|
ow_be = AND(owcvE[i, t], hasB[i, t])
|
||||||
|
ow_bb = AND(owcvB[i, t], hasB[i, t])
|
||||||
|
ow_bs = AND(owcvS[i, t], hasB[i, t])
|
||||||
|
gain_E[t].append(ow_be)
|
||||||
|
gain_E[t].append(ow_be) # added twice == *2
|
||||||
|
gain_B[t].append(ow_bb)
|
||||||
|
gain_B[t].append(ow_bb)
|
||||||
|
gain_S[t].append(ow_bs)
|
||||||
|
gain_S[t].append(ow_bs)
|
||||||
|
|
||||||
# ---- vat update producing vat[i, t+1] ----
|
# ---- vat update producing vat[i, t+1] ----
|
||||||
# increment added to the two non-collected vats (1, or 2 with upgrade d)
|
# increment added to the two non-collected vats (1, or 2 with upgrade d)
|
||||||
inc = m.NewIntVar(1, 2, "")
|
inc = m.NewIntVar(1, 2, "")
|
||||||
|
|
@ -293,11 +389,24 @@ def solve(
|
||||||
m.Add(vSn == 0).OnlyEnforceIf(cvS[i, t])
|
m.Add(vSn == 0).OnlyEnforceIf(cvS[i, t])
|
||||||
m.Add(vEn == vE[i, t] + inc).OnlyEnforceIf(cvS[i, t])
|
m.Add(vEn == vE[i, t] + inc).OnlyEnforceIf(cvS[i, t])
|
||||||
m.Add(vBn == vB[i, t] + inc).OnlyEnforceIf(cvS[i, t])
|
m.Add(vBn == vB[i, t] + inc).OnlyEnforceIf(cvS[i, t])
|
||||||
# foundry but not collecting (upgrade / renovate-away): vats unchanged
|
# overwork vat transitions: same reset/increment as normal collect
|
||||||
f_noncollect = AND(isF[i, t], col[i, t].Not())
|
m.Add(vEn == 0).OnlyEnforceIf(owcvE[i, t])
|
||||||
m.Add(vEn == vE[i, t]).OnlyEnforceIf(f_noncollect)
|
m.Add(vBn == vB[i, t] + inc).OnlyEnforceIf(owcvE[i, t])
|
||||||
m.Add(vBn == vB[i, t]).OnlyEnforceIf(f_noncollect)
|
m.Add(vSn == vS[i, t] + inc).OnlyEnforceIf(owcvE[i, t])
|
||||||
m.Add(vSn == vS[i, t]).OnlyEnforceIf(f_noncollect)
|
m.Add(vBn == 0).OnlyEnforceIf(owcvB[i, t])
|
||||||
|
m.Add(vEn == vE[i, t] + inc).OnlyEnforceIf(owcvB[i, t])
|
||||||
|
m.Add(vSn == vS[i, t] + inc).OnlyEnforceIf(owcvB[i, t])
|
||||||
|
m.Add(vSn == 0).OnlyEnforceIf(owcvS[i, t])
|
||||||
|
m.Add(vEn == vE[i, t] + inc).OnlyEnforceIf(owcvS[i, t])
|
||||||
|
m.Add(vBn == vB[i, t] + inc).OnlyEnforceIf(owcvS[i, t])
|
||||||
|
|
||||||
|
# foundry but neither collecting nor overworking: vats unchanged
|
||||||
|
# f_noncollect_noow = isF AND NOT fcol AND NOT fow = isF - fcol - fow
|
||||||
|
f_noncollect_noow = m.NewBoolVar("")
|
||||||
|
m.Add(f_noncollect_noow == isF[i, t] - fcol - fow)
|
||||||
|
m.Add(vEn == vE[i, t]).OnlyEnforceIf(f_noncollect_noow)
|
||||||
|
m.Add(vBn == vB[i, t]).OnlyEnforceIf(f_noncollect_noow)
|
||||||
|
m.Add(vSn == vS[i, t]).OnlyEnforceIf(f_noncollect_noow)
|
||||||
|
|
||||||
# assign vat[i, t+1]:
|
# assign vat[i, t+1]:
|
||||||
# renovate-to-foundry -> reset to 1
|
# renovate-to-foundry -> reset to 1
|
||||||
|
|
@ -319,6 +428,10 @@ def solve(
|
||||||
m.Add(vB[i, t + 1] == 0).OnlyEnforceIf(not_F_next)
|
m.Add(vB[i, t + 1] == 0).OnlyEnforceIf(not_F_next)
|
||||||
m.Add(vS[i, t + 1] == 0).OnlyEnforceIf(not_F_next)
|
m.Add(vS[i, t + 1] == 0).OnlyEnforceIf(not_F_next)
|
||||||
|
|
||||||
|
# ---- global overwork limit: at most 1 city overworks per step ----
|
||||||
|
for t in range(1, NUM_STEPS + 1):
|
||||||
|
m.Add(sum(ow[i, t] for i in range(N)) <= 1)
|
||||||
|
|
||||||
# ---- resource pool recursion --------------------------------------
|
# ---- resource pool recursion --------------------------------------
|
||||||
E = {1: m.NewIntVar(initial[0], initial[0], "E1")}
|
E = {1: m.NewIntVar(initial[0], initial[0], "E1")}
|
||||||
B = {1: m.NewIntVar(initial[1], initial[1], "B1")}
|
B = {1: m.NewIntVar(initial[1], initial[1], "B1")}
|
||||||
|
|
@ -382,7 +495,12 @@ def solve(
|
||||||
solver = cp_model.CpSolver()
|
solver = cp_model.CpSolver()
|
||||||
solver.parameters.max_time_in_seconds = time_limit
|
solver.parameters.max_time_in_seconds = time_limit
|
||||||
solver.parameters.num_search_workers = num_workers
|
solver.parameters.num_search_workers = num_workers
|
||||||
status = solver.Solve(m)
|
status = solver.Solve(
|
||||||
|
m,
|
||||||
|
printer.IntermediateSolutionPrinter(
|
||||||
|
{"electrum": finalE, "brass": finalB, "steel": finalS}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
if verbose:
|
if verbose:
|
||||||
print(f"(resource ceilings used: E<={capE} B<={capB} S<={capS})")
|
print(f"(resource ceilings used: E<={capE} B<={capB} S<={capS})")
|
||||||
|
|
@ -399,16 +517,24 @@ def solve(
|
||||||
ua,
|
ua,
|
||||||
ub,
|
ub,
|
||||||
ud,
|
ud,
|
||||||
|
ow,
|
||||||
rH,
|
rH,
|
||||||
rF,
|
rF,
|
||||||
rM,
|
rM,
|
||||||
cvE,
|
cvE,
|
||||||
cvB,
|
cvB,
|
||||||
cvS,
|
cvS,
|
||||||
|
owcvE,
|
||||||
|
owcvB,
|
||||||
|
owcvS,
|
||||||
mE,
|
mE,
|
||||||
mB,
|
mB,
|
||||||
mS,
|
mS,
|
||||||
mC,
|
mC,
|
||||||
|
owmE,
|
||||||
|
owmB,
|
||||||
|
owmS,
|
||||||
|
owmC,
|
||||||
hasA,
|
hasA,
|
||||||
hasB,
|
hasB,
|
||||||
hasD,
|
hasD,
|
||||||
|
|
@ -439,16 +565,24 @@ def _report(
|
||||||
ua,
|
ua,
|
||||||
ub,
|
ub,
|
||||||
ud,
|
ud,
|
||||||
|
ow,
|
||||||
rH,
|
rH,
|
||||||
rF,
|
rF,
|
||||||
rM,
|
rM,
|
||||||
cvE,
|
cvE,
|
||||||
cvB,
|
cvB,
|
||||||
cvS,
|
cvS,
|
||||||
|
owcvE,
|
||||||
|
owcvB,
|
||||||
|
owcvS,
|
||||||
mE,
|
mE,
|
||||||
mB,
|
mB,
|
||||||
mS,
|
mS,
|
||||||
mC,
|
mC,
|
||||||
|
owmE,
|
||||||
|
owmB,
|
||||||
|
owmS,
|
||||||
|
owmC,
|
||||||
hasA,
|
hasA,
|
||||||
hasB,
|
hasB,
|
||||||
hasD,
|
hasD,
|
||||||
|
|
@ -497,6 +631,22 @@ def _report(
|
||||||
picks += [nm] * solver.Value(var[i, t])
|
picks += [nm] * solver.Value(var[i, t])
|
||||||
return "Collect {" + ",".join(picks) + "}"
|
return "Collect {" + ",".join(picks) + "}"
|
||||||
return "Collect (+Capital)"
|
return "Collect (+Capital)"
|
||||||
|
if solver.Value(ow[i, t]):
|
||||||
|
if typ_name[i, t] == "Foundry":
|
||||||
|
v = (
|
||||||
|
"E"
|
||||||
|
if solver.Value(owcvE[i, t])
|
||||||
|
else "B"
|
||||||
|
if solver.Value(owcvB[i, t])
|
||||||
|
else "S"
|
||||||
|
)
|
||||||
|
return f"Overwork vat {v} (2x)"
|
||||||
|
if typ_name[i, t] == "Metro":
|
||||||
|
picks = []
|
||||||
|
for nm, var in (("E", owmE), ("B", owmB), ("S", owmS), ("C", owmC)):
|
||||||
|
picks += [nm] * solver.Value(var[i, t])
|
||||||
|
return "Overwork {" + ",".join(picks) + "} (2x)"
|
||||||
|
return "Overwork (+Capital 2x)"
|
||||||
if solver.Value(ua[i, t]):
|
if solver.Value(ua[i, t]):
|
||||||
return "Upgrade a (cost-reduction)"
|
return "Upgrade a (cost-reduction)"
|
||||||
if solver.Value(ub[i, t]):
|
if solver.Value(ub[i, t]):
|
||||||
|
|
|
||||||
20
printer.py
Normal file
20
printer.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
from ortools.sat.python import cp_model
|
||||||
|
|
||||||
|
|
||||||
|
class IntermediateSolutionPrinter(cp_model.CpSolverSolutionCallback):
|
||||||
|
"""Callback that prints intermediate solutions."""
|
||||||
|
|
||||||
|
def __init__(self, variables):
|
||||||
|
cp_model.CpSolverSolutionCallback.__init__(self)
|
||||||
|
self._variables = variables
|
||||||
|
self._solution_count = 0
|
||||||
|
|
||||||
|
def on_solution_callback(self):
|
||||||
|
"""Called each time an improving solution is found."""
|
||||||
|
print("\n--- Solution ---")
|
||||||
|
for name, var in self._variables.items():
|
||||||
|
print(f"{name} = {self.Value(var)}")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def solution_count(self):
|
||||||
|
return self._solution_count
|
||||||
Loading…
Reference in a new issue