Source code for prepshot._model.cost

#!/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

[docs]class AddCostObjective: """Objective function class to determine the total cost of the model. """
[docs] 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()
[docs] 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)
[docs] 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
[docs] 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)))
[docs] 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)))
[docs] 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)))
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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)
[docs] 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
[docs] 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)
[docs] 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)
[docs] 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
[docs] 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
[docs] 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)
[docs] 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)
[docs] 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)