#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""This module contains objective functions for the model. The objective
function of the model is to minimize the net present value of the system's
cost. This includes capital cost, fixed O&M cost, variable cost and fuel cost
by cost type, technology cost, transmission line cost by the source of cost,
and operation cost and planning cost by the source of cost.
The cost equations are defined as follows:
.. image:: ../_static/cost_eq.png
:width: 700
:align: center
:alt: Calculation of system-wide total cost
.. math::
\\rm{cost}_{\\rm{tech}}^{\\rm{var}} &= \\frac{\\sum_{h,m,y,z,\\rm{e}}
C_{y,z,\\rm{e}}^{\\rm{tech-var}}\\times \\rm{gen}_{h,m,y,z,\\rm{e}}}
{\omega} \\times \\rm{factor}_{y}^{\\rm{var}}
\\rm{cost}_{\\rm{line}}^{\\rm{var}} &= \\frac{\\sum_{h,m,y,z_s,z_o}
C_{y,z}^{\\rm{line-var}}\\times \\rm{export}_{h,m,y,z_s,z_o}}{\omega}
\\times \\rm{factor}_{y}^{\\rm{var}}
\\rm{cost}^{\\rm{fuel}} & = \\frac{\\sum_{h,m,y,z,\\rm{e}}
C_{y,z,\\rm{e}}^{\\rm{fuel}}\\times \\rm{gen}_{h,m,y,z,\\rm{e}}}{\omega}
\\times \\rm{factor}_{y}^{\\rm{var}}
\\rm{cost}_{\\rm{tech}}^{\\rm{fix}} &= \\sum_{y,z,\\rm{e}}
C_{y,z,\\rm{e}}^{\\rm{tech-fix}}\\times
\\rm{cap}_{y,z,\\rm{e}}^{\\rm{existing-tech}}\\times
\\rm{factor}_{y}^{\\rm{fix}}
\\rm{cost}_{\\rm{line}}^{\\rm{fix}} &= \\sum_{y,z_s,z_o}
C_{y,z_s,z_o}^{\\rm{line-fix}}\\times
\\rm{cap}_{y,z_s,z_o}^{\\rm{existing-line}}\\times
\\rm{factor}_{y}^{\\rm{fix}}
\\rm{cost}_{\\rm{tech}}^{\\rm{inv}} &= \\sum_{y,z,\\rm{e}}
C_{y,z,\\rm{e}}^{\\rm{tech-inv}}\\times
\\rm{cap}_{y,z,\\rm{e}}^{\\rm{tech-inv}}\\times
\\rm{factor}_{y}^{\\rm{inv}}
\\rm{cost}_{\\rm{line}}^{\\rm{inv}} &= \\sum_{y,z_s,z_o}
C_{y,z_s,z_o}^{\\rm{line-inv}}\\times
\\rm{cap}_{y,z_s,z_o}^{\\rm{line-inv}}\\times
\\rm{factor}_{y}^{\\rm{inv}} \\times 0.5
"""
from typing import Union
import pyoptinterface as poi
[文档]class AddCostObjective:
"""Objective function class to determine the total cost of the model.
"""
[文档] def __init__(self, model : object) -> None:
"""The constructor for objective functions class.
Parameters
----------
model : object
Model to be solved.
"""
self.model = model
self.define_objective()
[文档] def define_objective(self) -> None:
"""Objective function of the model, to minimize total cost.
"""
from prepshot._model.unit_commitment import add_uc_cost_terms
model = self.model
model.cost_var = self.var_cost_rule()
model.cost_newtech = self.newtech_cost_rule()
model.cost_fix = self.fix_cost_rule()
model.cost_newline = self.newline_cost_rule()
model.cost_carbon = self.carbon_cost_rule()
model.cost_carbon_offset = self.carbon_offset_cost_rule()
model.income = self.income_rule()
# UC contribution: startup cost + no-load cost on online units.
# Returns a zero ExprBuilder when UC is disabled, so adding it
# unconditionally is safe.
model.cost_uc = add_uc_cost_terms(model)
# Load-shedding penalty: VOLL ($/MWh) times slack volume,
# NPV-discounted by var_factor[y, z] and divided by weight to
# match the variable-cost convention. Zero when load
# shedding is disabled.
model.cost_lns = self.lns_cost_rule()
model.cost = model.cost_var + model.cost_newtech \
+ model.cost_fix + model.cost_newline \
+ model.cost_carbon + model.cost_carbon_offset \
+ model.cost_uc + model.cost_lns \
- model.income
model.set_objective(model.cost, sense=poi.ObjectiveSense.Minimize)
[文档] def lns_cost_rule(self) -> "poi.ExprBuilder":
"""VOLL penalty on the load-not-served slack, discounted to NPV.
Returns a zero ExprBuilder when ``allow_load_shedding`` is
false, so callers can add it unconditionally.
"""
model = self.model
if not hasattr(model, 'lns'):
return poi.ExprBuilder()
voll = float(model.params.get('voll', 10000.0))
vf = model.params['var_factor']
w = model.params['weight']
cost = poi.ExprBuilder()
for h in model.hour:
for m in model.month:
for y in model.year:
for z in model.zone:
cost += (
voll / w * vf[y, z]
* model.lns[h, m, y, z]
)
return cost
[文档] def fuel_cost_breakdown(
self, y : int, z : str, te : str
) -> poi.ExprBuilder:
"""Fuel cost breakdown of technologies.
Parameters
----------
y : int
Year.
z : str
Zone.
te : str
Technology.
Returns
-------
poi.ExprBuilder
Fuel cost at a given year, zone and technology.
"""
from prepshot._model.heat_rate import techs_with_heat_rate_curve
model = self.model
# Techs with a piecewise-linear heat-rate curve are priced
# through `add_heat_rate_fuel_cost` (per-segment) and must
# NOT be double-counted here.
if te in techs_with_heat_rate_curve(model):
return poi.ExprBuilder()
fp = model.params['fuel_price'][te, y]
vf = model.params['var_factor'][y, z]
w = model.params['weight']
return (1 / w * fp * vf
* poi.quicksum(model.gen.select('*', '*', y, z, te)))
[文档] def cost_var_line_breakdown(
self, y : int, z : str, z1 : str
) -> poi.ExprBuilder:
"""Variable operation and maintenance cost breakdown of transmission
lines.
Parameters
----------
y : int
Year.
z : str
Zone.
z1 : str
Zone.
Returns
-------
poi.ExprBuilder
Variable operation and maintenance cost of transmission lines at a
given year, source and destination zone.
"""
model = self.model
lvc = model.params['transmission_line_variable_OM_cost'][z, z1]
# Source-zone discount for inter-zone transmission costs.
vf = model.params['var_factor'][y, z]
w = model.params['weight']
return (0.5 / w * lvc * vf
* poi.quicksum(model.trans_export.select('*', '*', y, z, z1)))
[文档] def cost_var_tech_breakdown(
self, y : int, z : str, te : str
) -> poi.ExprBuilder:
"""Variable operation and maintenance cost breakdown.
Parameters
----------
y : int
Year.
z : str
Zone.
te : str
Technology.
Returns
-------
poi.ExprBuilder
Variable operation and maintenance cost of technologies at a given
year, zone and technology.
"""
model = self.model
tvc = model.params['technology_variable_OM_cost'][te, y]
vf = model.params['var_factor'][y, z]
w = model.params['weight']
return (1 / w * tvc * vf
* poi.quicksum(model.gen.select('*', '*', y, z, te)))
[文档] def cost_fix_line_breakdown(
self, y : int, z : str, z1 : str
) -> poi.ExprBuilder:
"""Fixed operation and maintenance cost breakdown of transmission
lines.
Parameters
----------
y : int
Year.
z : str
Zone.
z1 : str
Zone.
Returns
-------
poi.ExprBuilder
Fixed operation and maintenance cost of transmission lines at a given
year, source and destination zone.
"""
model = self.model
lfc = model.params['transmission_line_fixed_OM_cost']
ff = model.params['fix_factor']
return lfc[z, z1] * model.cap_lines_existing[y, z, z1] * ff[y, z] * 0.5
[文档] def cost_fix_tech_breakdown(
self, y : int, z : str, te : str
) -> poi.ExprBuilder:
"""Fixed operation and maintenance cost breakdown.
Parameters
----------
y : int
Year.
z : str
Zone.
te : str
Technology.
Returns
-------
poi.ExprBuilder
Fixed operation and maintenance cost of technologies at a given year,
zone and technology.
"""
model = self.model
tfc = model.params['technology_fixed_OM_cost'][te, y]
ff = model.params['fix_factor'][y, z]
return tfc * model.cap_existing[y, z, te] * ff
[文档] def cost_newtech_breakdown(
self, y : int, z : str, te : str
) -> poi.ExprBuilder:
"""New technology investment cost breakdown.
Parameters
----------
y : int
Year.
z : str
Zone.
te : str
Technology.
Returns
-------
poi.ExprBuilder
Investment cost of new technologies at a given year, zone and
technology.
"""
model = self.model
tic = model.params['technology_investment_cost'][te, y]
ivf = model.params['inv_factor'][te, y, z]
return tic * model.cap_newtech[y, z, te] * ivf
[文档] def cost_newline_breakdown(
self, y : int, z : str, z1 : str
) -> poi.ExprBuilder:
"""New transmission line investment cost breakdown.
Parameters
----------
y : int
Year.
z : str
Zone.
z1 : str
Zone.
Returns
-------
poi.ExprBuilder
Investment cost of new transmission lines at a given year, source and
destination zone.
"""
model = self.model
lic = model.params['transmission_line_investment_cost'][z, z1]
d = model.params['distance'][z, z1]
# Source-zone discount for inter-zone transmission investment.
ivf = model.params['trans_inv_factor'][y, z]
capacity_invested_line = model.cap_newline[y, z, z1]
return lic * capacity_invested_line * d * ivf * 0.5
[文档] def income_rule(self) -> poi.ExprBuilder:
"""Income from water withdrawal.
Reference: https://www.nature.com/articles/s44221-023-00126-0
Returns
-------
poi.ExprBuilder
Income from water withdrawal.
"""
model = self.model
if model.params['isinflow']:
coef = 3600 * model.params['dt'] * model.params['price']
vf = model.params['var_factor']
w = model.params['weight']
# Each hydro plant has a home zone -- use that zone's
# var_factor for discounting the plant's water-withdrawal
# income.
station_zone = model.params['reservoir_zone']
income = sum(
model.withdraw[s, h, m, y] * coef * vf[y, station_zone[s]]
for s in model.station
for h in model.hour
for m in model.month
for y in model.year
) / w
return income
return poi.ExprBuilder(0)
[文档] def var_cost_rule(self) -> poi.ExprBuilder:
"""Calculate total variable cost, which is sum of the fuel cost of
technologies and variable Operation and maintenance (O&M) cost of
technologies and transmission lines.
Returns
-------
poi.ExprBuilder
Total variable cost across all years, zones and technologies.
"""
model = self.model
from prepshot.utils import sparse_tupledict
# Iterate sparse: (year, z, te) only over real (z, te) pairs,
# (year, z, z1) only over real lines.
active_yzt = [(y, z, te) for y in model.year
for (z, te) in model.active_zt]
model.cost_var_tech_breakdown = sparse_tupledict(
active_yzt, self.cost_var_tech_breakdown
)
model.cost_fuel_breakdown = sparse_tupledict(
active_yzt, self.fuel_cost_breakdown
)
model.cost_var_line_breakdown = sparse_tupledict(
model.active_yz1z2, self.cost_var_line_breakdown
)
# Per-segment fuel cost for techs with a piecewise-linear heat
# rate curve; zero ExprBuilder when the module is disabled.
from prepshot._model.heat_rate import add_heat_rate_fuel_cost
model.cost_fuel_segment = add_heat_rate_fuel_cost(model)
cost_var = poi.ExprBuilder()
cost_var += poi.quicksum(model.cost_var_tech_breakdown)
cost_var += poi.quicksum(model.cost_fuel_breakdown)
cost_var += model.cost_fuel_segment
cost_var += poi.quicksum(model.cost_var_line_breakdown)
return cost_var
[文档] def newtech_cost_rule(self) -> poi.ExprBuilder:
"""Total investment cost of new technologies.
Returns
-------
poi.ExprBuilder
Total investment cost of new technologies over all years, zones and
technologies.
"""
from prepshot.utils import sparse_tupledict
model = self.model
active_yzt = [(y, z, te) for y in model.year
for (z, te) in model.active_zt]
model.cost_newtech_breakdown = sparse_tupledict(
active_yzt, self.cost_newtech_breakdown
)
return poi.quicksum(model.cost_newtech_breakdown)
[文档] def newline_cost_rule(self) -> poi.ExprBuilder:
"""Total investment cost of new transmission lines.
Returns
-------
poi.ExprBuilder
Total investment cost of new transmission lines over all years,
zones.
"""
from prepshot.utils import sparse_tupledict
model = self.model
model.cost_newline_breakdown = sparse_tupledict(
model.active_yz1z2, self.cost_newline_breakdown
)
return poi.quicksum(model.cost_newline_breakdown)
[文档] def carbon_cost_breakdown(
self, y : int, z : str
) -> poi.ExprBuilder:
"""Carbon-tax cost breakdown by year and zone.
Tax is applied to net zonal emissions (raw emissions minus purchased
offsets), in line with the carbon-market accounting convention.
Parameters
----------
y : int
Year.
z : str
Zone.
Returns
-------
poi.ExprBuilder
Carbon-tax cost at a given year and zone.
"""
model = self.model
ct = model.params['carbon_tax'][z, y]
vf = model.params['var_factor'][y, z]
return ct * model.carbon_capacity[y, z] * vf
[文档] def carbon_offset_cost_breakdown(
self, y : int, z : str
) -> poi.ExprBuilder:
"""Cost of purchased carbon offsets by year and zone.
Parameters
----------
y : int
Year.
z : str
Zone.
Returns
-------
poi.ExprBuilder
Carbon-offset cost at a given year and zone.
"""
model = self.model
cop = model.params['carbon_offset_price'][z, y]
vf = model.params['var_factor'][y, z]
return cop * model.carbon_offset[y, z] * vf
[文档] def carbon_cost_rule(self) -> poi.ExprBuilder:
"""Total carbon-tax cost over all years and zones.
Returns
-------
poi.ExprBuilder
Total carbon-tax cost.
"""
model = self.model
model.cost_carbon_breakdown = poi.make_tupledict(
model.year, model.zone,
rule=self.carbon_cost_breakdown
)
return poi.quicksum(model.cost_carbon_breakdown)
[文档] def carbon_offset_cost_rule(self) -> poi.ExprBuilder:
"""Total cost of purchased carbon offsets over all years and zones.
Returns
-------
poi.ExprBuilder
Total carbon-offset cost.
"""
model = self.model
model.cost_carbon_offset_breakdown = poi.make_tupledict(
model.year, model.zone,
rule=self.carbon_offset_cost_breakdown
)
return poi.quicksum(model.cost_carbon_offset_breakdown)
[文档] def fix_cost_rule(self) -> poi.ExprBuilder:
"""Fixed O&M cost of technologies and transmission lines.
Returns
-------
poi.ExprBuilder
Total fixed O&M cost of technologies and transmission lines over
all years, zones and technologies.
"""
from prepshot.utils import sparse_tupledict
model = self.model
active_yzt = [(y, z, te) for y in model.year
for (z, te) in model.active_zt]
model.cost_fix_tech_breakdown = sparse_tupledict(
active_yzt, self.cost_fix_tech_breakdown
)
model.cost_fix_line_breakdown = sparse_tupledict(
model.active_yz1z2, self.cost_fix_line_breakdown
)
return poi.quicksum(model.cost_fix_tech_breakdown) + \
poi.quicksum(model.cost_fix_line_breakdown)