Skip to content

Results: end of horizon storage valuation #323

@jkiviluo

Description

@jkiviluo

Context. Nodes with storage_solve_horizon_method = use_reference_price
get a credit in the LP objective at the last timestep of the last period
(flextool.mod ~line 2390-2394):

− Σ_{n, last t, last d}
    p_storage_state_reference_price[n, d]
    × v_state[n, d, t] × p_entity_unitsize[n]
    × p_rp_cost_weight[d, t] × p_inflation_factor_operations_yearly[d]
    / complete_period_share_of_year[d] × pdt_branch_weight[d, t]

This term is not aggregated by process_outputs/calc_costs.py — so
costs_discounted_d_p / costs_discounted_p_ understate the solver's
objective by this credit whenever use_reference_price is active.

Why it's been left out. End-of-horizon storage valuation has several
valid interpretations depending on what the analyst wants to express:

  1. Reference price × end state — the method currently in the mod.
    Attractive because it's a marginal value the user can set. But the
    resulting dollar figure is sensitive to the reference price choice
    and is often treated as "shadow accounting" rather than a real cost.
  2. Difference between initial and final state, priced at a
    representative price
    — e.g. average commodity price over the
    horizon, or the marginal storage use-cost at the last timestep.
  3. Opportunity cost under a rolling assumption — project forward
    using the average trajectory and credit the expected value of having
    stored energy at horizon boundary.
  4. Book value — zero if the horizon is long enough that the credit
    is immaterial.

Because the choice is model-scoped (different users want different
behavior) and because it's usually computed after the optimization
rather than baked into cost reports, FlexTool currently omits the term
from costs_discounted_* entirely. Users needing it today can compute
it in a post-processing notebook using the solver objective plus the
reference-price term separately.

What would be required to add it.

  1. Mod: emit p_storage_state_reference_price[n, d] and the set of
    nodes with use_reference_price method to CSV (not currently
    written). Also emit set_period_last and set_period__time_last
    if not already.
  2. Python: new helper in calc_costs.py / out_costs.py that computes
    the credit per the formula above, adds it to costOper_and_penalty_d
    (or a new dedicated bucket).
  3. Tests: fixture with non-trivial storage + use_reference_price;
    hand-derived expected credit; assert solver ↔ parquet match.
  4. Docs: briefly note where the term now shows up in summary_solve.csv
    and the parquet categories.

Existing xfail marker:
tests/test_cost_aggregation_semantics.py::test_storage_state_reference_price_credit
documents the gap. Ref: flextool/flextool.mod:2390-2394.

Decision pending on whether FlexTool should implement method 1
(currently in the mod objective) or leave end-of-horizon valuation as
post-analysis only.

Status (2026-04-21): The documentation side has been addressed —
docs/how_to.md ("How to add a storage unit") and docs/reference.md
(node storage_solve_horizon_method section) now state explicitly
that the use_reference_price credit appears in the solver objective
but not in the reported cost totals (commit 720212d). The
implementation decision (methods 1–4 above) is still pending.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions