Default export_rate to 0; add tie-breaker so battery fills before exporting
- DEFAULTS['export_rate'] = 0 (terugleveringskosten can be re-enabled per scenario via the Advanced field — keeping the headline default simple). - LP gets a microscopic per-hour penalty (1e-6 € · hour) on charging that prefers earlier hours, and on discharging that prefers later. Total effect on annual savings: < €0.001 (well below display rounding). But visually the battery now fills as soon as surplus arrives, instead of the LP arbitrarily mixing surplus hours with grid-arbitrage hours when both give the same daily total.
This commit is contained in:
parent
e39f78fcc5
commit
ab4f9712b5
3 changed files with 19 additions and 7 deletions
|
|
@ -238,9 +238,20 @@ def oracle_daily_schedule(df: pd.DataFrame, battery: Battery) -> np.ndarray:
|
||||||
# Cost = sum(import_price * gI - export_price * gE).
|
# Cost = sum(import_price * gI - export_price * gE).
|
||||||
# When import_price > export_price, the LP automatically picks
|
# When import_price > export_price, the LP automatically picks
|
||||||
# min(gI, gE) = 0 because keeping both > 0 costs (imp - exp) > 0.
|
# min(gI, gE) = 0 because keeping both > 0 costs (imp - exp) > 0.
|
||||||
|
#
|
||||||
|
# Tiny tie-breakers: when several surplus hours are equally good for
|
||||||
|
# charging (true whenever export_price is constant and we're capturing
|
||||||
|
# surplus), the simplex would otherwise pick an arbitrary subset.
|
||||||
|
# Adding a microscopic penalty proportional to hour-index makes early
|
||||||
|
# charging strictly cheaper than late charging by a fraction of a
|
||||||
|
# cent — so the battery visibly fills as soon as surplus appears.
|
||||||
|
# Symmetric for discharge: prefer late hours (when prices are
|
||||||
|
# typically highest anyway, but ties happen during evening plateaus).
|
||||||
|
EPS = 1e-6
|
||||||
|
hr = np.arange(m)
|
||||||
f = np.concatenate([
|
f = np.concatenate([
|
||||||
np.zeros(m), # c — already paid for via gI
|
EPS * hr, # c: prefer earlier charging (smaller h ⇒ less cost)
|
||||||
np.zeros(m), # d — earnings reach us via reduced gI / increased gE
|
EPS * (m - 1 - hr), # d: prefer later discharging
|
||||||
import_price, # pay for imports
|
import_price, # pay for imports
|
||||||
-export_price, # earn export credit
|
-export_price, # earn export credit
|
||||||
])
|
])
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Honest battery payback — vs thuisbatterijgids.nl</title>
|
<title>Honest battery payback — vs thuisbatterijgids.nl</title>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uplot@1.6.31/dist/uPlot.min.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uplot@1.6.31/dist/uPlot.min.css">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}?v=19">
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}?v=20">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|
@ -194,6 +194,6 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/uplot@1.6.31/dist/uPlot.iife.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/uplot@1.6.31/dist/uPlot.iife.min.js"></script>
|
||||||
<script src="{{ url_for('static', filename='app.js') }}?v=19"></script>
|
<script src="{{ url_for('static', filename='app.js') }}?v=20"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -326,11 +326,12 @@ DEFAULTS = dict(
|
||||||
# demand_kwh / ANNUAL_DEMAND_BASE; ANNUAL_DEMAND_BASE annualises the
|
# demand_kwh / ANNUAL_DEMAND_BASE; ANNUAL_DEMAND_BASE annualises the
|
||||||
# 8-month window's 1010 kWh → 1518 kWh-equivalent/yr, so set the
|
# 8-month window's 1010 kWh → 1518 kWh-equivalent/yr, so set the
|
||||||
# default to 1518 to keep the per-hour P1 values unscaled (scale = 1.0).
|
# default to 1518 to keep the per-hour P1 values unscaled (scale = 1.0).
|
||||||
# Dad's actual full-year net is 2351 kWh (from his complete CSV) — so
|
# export_rate = 0 by default to keep the model simple. Dad's actual
|
||||||
# using 1518 effectively represents 'an 8-month-extrapolated 2025 year'.
|
# situation has terugleveringskosten of −€0.106/kWh; set the field
|
||||||
|
# explicitly to that value when modelling his real bill.
|
||||||
demand_kwh=1518.0, retail=0.25, pv_kwp=0.0, pv_yield=875.0,
|
demand_kwh=1518.0, retail=0.25, pv_kwp=0.0, pv_yield=875.0,
|
||||||
fixed_rate=False, saldering=False, eta=0.88, inflation=0.03,
|
fixed_rate=False, saldering=False, eta=0.88, inflation=0.03,
|
||||||
export_rate=-0.106,
|
export_rate=0.0,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue