fix cyklovani
This commit is contained in:
@@ -42,8 +42,9 @@ NT/VT podle hodin Europe/Prague (--buy-nt-kwh, VT = NT + --buy-vt-surcharge-kwh)
|
||||
nebo od raw OTE spotu: --buy-spot-add-fixed-kwh / --buy-spot-asym-pct; u všech režimů
|
||||
lze přičíst --buy-distribution-kwh a --buy-other-fees-kwh a výslednou cenu násobit
|
||||
--buy-vat-multiplier. Model explicitně zakazuje současný import+export a současné
|
||||
nabíjení+vybíjení v jednom slotu. Mikroinvertory / GEN nejsou; zelený bonus není
|
||||
v účelové funkci. Výsledek = screening, ne nabídka.
|
||||
nabíjení+vybíjení v jednom slotu. Dlouhé běhy MILP lze řídit přes
|
||||
--solver-time-limit-sec a průběžný tisk přes --progress-every-days. Mikroinvertory /
|
||||
GEN nejsou; zelený bonus není v účelové funkci. Výsledek = screening, ne nabídka.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -55,6 +56,7 @@ import sys
|
||||
from dataclasses import dataclass
|
||||
from datetime import date, datetime, timedelta
|
||||
from pathlib import Path
|
||||
from time import perf_counter
|
||||
from typing import Iterable, Sequence, Mapping
|
||||
|
||||
try:
|
||||
@@ -386,6 +388,7 @@ def solve_one_day(
|
||||
p_batt_w: float,
|
||||
site: SiteLimits,
|
||||
soc_start_wh: float,
|
||||
solver_time_limit_sec: float,
|
||||
) -> tuple[float, float, float, float]:
|
||||
"""
|
||||
Vrátí (cash_kc, soc_end_wh, curtailed_wh, discharged_wh_sum).
|
||||
@@ -434,7 +437,10 @@ def solve_one_day(
|
||||
|
||||
prob += pulp.lpSum(obj)
|
||||
|
||||
solver = pulp.PULP_CBC_CMD(msg=False, timeLimit=60)
|
||||
solver_kwargs: dict[str, object] = {"msg": False}
|
||||
if solver_time_limit_sec > 0:
|
||||
solver_kwargs["timeLimit"] = solver_time_limit_sec
|
||||
solver = pulp.PULP_CBC_CMD(**solver_kwargs)
|
||||
prob.solve(solver)
|
||||
if prob.status != pulp.LpStatusOptimal:
|
||||
raise RuntimeError(f"LP status {pulp.LpStatus[prob.status]}")
|
||||
@@ -447,7 +453,7 @@ def solve_one_day(
|
||||
|
||||
|
||||
def simulate_year(
|
||||
days: Iterable[date],
|
||||
days: Sequence[date],
|
||||
px_day: dict[date, list[float]],
|
||||
usable_kwh: float,
|
||||
site: SiteLimits,
|
||||
@@ -459,6 +465,8 @@ def simulate_year(
|
||||
load_kw: float,
|
||||
shape: Sequence[float],
|
||||
monthly_ed_kwh: Mapping[int, float] | None,
|
||||
solver_time_limit_sec: float,
|
||||
progress_every_days: int,
|
||||
) -> dict[str, float]:
|
||||
e_wh = usable_kwh * 1000.0
|
||||
p_batt = batt_power_cap_w(usable_kwh, site)
|
||||
@@ -467,10 +475,30 @@ def simulate_year(
|
||||
curt_total = 0.0
|
||||
dis_total = 0.0
|
||||
soc_state = 0.5 * (site.soc_min_frac + site.soc_max_frac) * e_wh
|
||||
run_days = [d for d in days if d in px_day]
|
||||
total_days = len(run_days)
|
||||
started = perf_counter()
|
||||
n_days = 0
|
||||
for d in days:
|
||||
if d not in px_day:
|
||||
continue
|
||||
if progress_every_days > 0:
|
||||
limit_msg = (
|
||||
f"{solver_time_limit_sec:g} s/den"
|
||||
if solver_time_limit_sec > 0
|
||||
else "bez limitu / den"
|
||||
)
|
||||
print(
|
||||
f"[{usable_kwh:.1f} kWh] start: {total_days} dnů, CBC limit {limit_msg}",
|
||||
flush=True,
|
||||
)
|
||||
for idx, d in enumerate(run_days, start=1):
|
||||
if progress_every_days > 0 and (
|
||||
idx == 1 or idx % progress_every_days == 0 or idx == total_days
|
||||
):
|
||||
elapsed_sec = perf_counter() - started
|
||||
print(
|
||||
f"[{usable_kwh:.1f} kWh] den {idx}/{total_days}: {d.isoformat()} "
|
||||
f"(elapsed {elapsed_sec:.1f} s)",
|
||||
flush=True,
|
||||
)
|
||||
raw = px_day[d]
|
||||
p_sell = [effective_sell_kc_kwh(x, sell_margin_fixed, sell_margin_pct) for x in raw]
|
||||
p_buy = build_buy_prices_96(raw, buy_cfg)
|
||||
@@ -479,12 +507,25 @@ def simulate_year(
|
||||
else:
|
||||
pv_wh = daily_pv_wh(d, summer_kwh, winter_kwh, shape)
|
||||
cash, soc_state, curt, dis = solve_one_day(
|
||||
pv_wh, load_wh, p_sell, p_buy, e_wh, p_batt, site, soc_state
|
||||
pv_wh,
|
||||
load_wh,
|
||||
p_sell,
|
||||
p_buy,
|
||||
e_wh,
|
||||
p_batt,
|
||||
site,
|
||||
soc_state,
|
||||
solver_time_limit_sec,
|
||||
)
|
||||
cash_total += cash
|
||||
curt_total += curt
|
||||
dis_total += dis
|
||||
n_days += 1
|
||||
if progress_every_days > 0:
|
||||
print(
|
||||
f"[{usable_kwh:.1f} kWh] hotovo za {perf_counter() - started:.1f} s",
|
||||
flush=True,
|
||||
)
|
||||
feq = (dis_total / e_wh / n_days) if n_days and e_wh > 0 else 0.0
|
||||
return {
|
||||
"cash_kc": cash_total,
|
||||
@@ -585,6 +626,18 @@ def main() -> None:
|
||||
ap.add_argument("--max-import-w", type=float, default=17_000.0)
|
||||
ap.add_argument("--inv-batt-max-w", type=float, default=12_000.0)
|
||||
ap.add_argument("--c-rate", type=float, default=0.5)
|
||||
ap.add_argument(
|
||||
"--solver-time-limit-sec",
|
||||
type=float,
|
||||
default=60.0,
|
||||
help="CBC time limit na jeden den; 0 = bez limitu",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--progress-every-days",
|
||||
type=int,
|
||||
default=1,
|
||||
help="Po kolika dnech vytisknout průběh; 0 = tichý režim",
|
||||
)
|
||||
ap.add_argument("--capex-per-kwh", type=float, default=0.0, help="CAPEX za 1 kWh rozšíření; vypíše jednoduchou návratnost vs. nejmenší baterie")
|
||||
args = ap.parse_args()
|
||||
|
||||
@@ -599,6 +652,10 @@ def main() -> None:
|
||||
ap.error("Zvol jen jeden režim základu nákupu: flat, NT/VT, --buy-spot-add-fixed-kwh nebo --buy-spot-asym-pct")
|
||||
if args.buy_vat_multiplier <= 0:
|
||||
ap.error("--buy-vat-multiplier musí být > 0")
|
||||
if args.solver_time_limit_sec < 0:
|
||||
ap.error("--solver-time-limit-sec musí být >= 0")
|
||||
if args.progress_every_days < 0:
|
||||
ap.error("--progress-every-days musí být >= 0")
|
||||
for hour_arg, hour_value in (("nt-from-hour", args.nt_from_hour), ("nt-to-hour", args.nt_to_hour)):
|
||||
if not (0 <= hour_value <= 23):
|
||||
ap.error(f"--{hour_arg} musí být v rozsahu 0..23")
|
||||
@@ -683,6 +740,8 @@ def main() -> None:
|
||||
args.load_kw,
|
||||
shape,
|
||||
monthly_ed,
|
||||
args.solver_time_limit_sec,
|
||||
args.progress_every_days,
|
||||
)
|
||||
results.append((kwh, r))
|
||||
|
||||
@@ -725,6 +784,12 @@ def main() -> None:
|
||||
)
|
||||
print(f" Load (konstanta) {args.load_kw} kW")
|
||||
print(f" Limity: export {args.max_export_w} W, import {args.max_import_w} W, P_batt = min({args.c_rate}*E_kWh, {args.inv_batt_max_w} W)")
|
||||
limit_msg = (
|
||||
f"{args.solver_time_limit_sec:g} s/den"
|
||||
if args.solver_time_limit_sec > 0
|
||||
else "bez limitu / den"
|
||||
)
|
||||
print(f" Solver: CBC, limit {limit_msg}, progress every {args.progress_every_days} dnů")
|
||||
print()
|
||||
|
||||
print(f"{'kWh':>8} {'P_batt_kW':>10} {'cash_kc/rok':>14} {'Δ vs min':>12} {'curt_MWh/y':>12} {'Feq/den':>8}")
|
||||
|
||||
Reference in New Issue
Block a user