Price Taker#
Price takers are entities that must accept market prices since they lack the market share to directly influence the market price. Likewise, it is assumed that a price taker’s resource or energy system is small enough such that it does not significantly impact the market. When coupled with multi-period modeling, the price-taker model is able to synthesize grid-centric modeling with steady-state process-centric modeling, as depicted in figure below.
The following equations represent the multi-period price taker model, where \(d\) are design decisions, \(u\) are operating decisions, \(δ\) are power decisions, \(s\) are scenarios (timeseries/representative days), \(w\) is weight/frequency, \(R\) is revenue, \(π\) is price data, \(C\) is capital and operating costs, \(g\) is the process model, and \(h\) is the temporal constraint.
\[max_{d,u, δ} = \sum_{s ∈ S} \sum_{t ∈ T} w_{s}[R(d,u_{s,t},δ_{s,t},π_{s,t}) - C(d,u_{s,t},δ_{s,t})]\]\[g(d,u_{s,t},δ_{s,t}) = 0; ∀_{s} ∈ S, t ∈ T\]\[h(d,u_{s,t},δ_{s,t},u_{s,t+1},δ_{s,t+1}) = 0; ∀_{s} ∈ S, t ∈ T\]
The price taker multi-period modeling workflow involves the integration of multiple software platforms into the IDAES optimization model and can be broken down into two distinct functions, as shown in the figure below. In part 1, simulated or historical ISO (Independent System Operator) data is used to generate locational marginal price (LMP) signals, and production cost models (PCMs) are used to compute and optimize the time-varying dispatch schedules for each resource based on their respective bid curves. Advanced data analytics (RAVEN) reinterpret the LMP signals and PCM as stochastic realizations of the LMPs in the form of representative days (or simply the full-year price signals). In part 2, PRESCIENT uses a variety of input parameters (design capacity, minimum power output, ramp rate, minimum up/down time, marginal cost, no load cost, and startup profile) to generate data for the market surrogates. Meanwhile, IDAES uses the double loop simulation to integrate detailed process models (b, ii) into the daily (a, c) and hourly (i, iii) grid operations workflow.
- class idaes.apps.grid_integration.pricetaker.price_taker_model.PriceTakerModel(*args, **kwds)[source]#
Builds a price-taker model for a given system
- add_capacity_limits(op_block_name, commodity, capacity, op_range_lb)[source]#
Adds capacity limit constraints of the form: op_range_lb * capacity * op_mode(t) <= commodity(t) <= capacity * op_mode(t) ex: 0.3 * P_max * op_mode(t) <= P(t) <= P_max * op_mode(t), where P(t) is power at time t and op_mode(t) = 1 if the system is operating at time t; and op_mode(t) = 0, otherwise.
- Parameters:
op_block_name (str) – str, Name of the operation model block, ex: (“fs.ngcc”)
commodity (str) – str, Name of the commodity on the model the capacity constraints will be applied to, ex: (“total_power”)
capacity (float | Param | Var | Expression) – float, or Pyomo Var, Param, or Expression Maximum capacity on the commodity, ex: (650.0, or m.min_power_capacity)
op_range_lb (float) – float, Ratio of the capacity at minimum stable operation to the maximum capacity. Must be a number in the interval [0, 1]
- add_hourly_cashflows(revenue_streams=None, operational_costs=None)[source]#
Adds an expression for the net cash inflow for each flowsheet instance. Operational costs in each flowsheet instance may include ‘non_fuel_vom’ (non-fuel variable operating costs), ‘fuel_cost’ (cost of fuel), and ‘carbon_price’ (cost associated with producing carbon; i.e., a carbon tax). Revenue streams in each flowsheet instance may include the revenue from the wholesale electricity market, revenue from alternative products (e.g., hydrogen), etc.
The net cash inflow is calculated as:
Sum(revenue streams) - Sum(costs)
for each time period.
- Parameters:
revenue_streams (List | None) –
List of strings representing the names of the revenue streams, default: None
Example:
['elec_revenue', 'H2_revenue', ]
costs –
List of strings representing the names of the costs associated with operating at a time period. default: None Example:
['hourly_fixed_cost', 'electricity_cost',]
operational_costs (List | None)
- add_linking_constraints(previous_time_var, current_time_var)[source]#
Adds constraints to relate variables across two consecutive time periods. This method is usually needed if the system has storage. Using this method, the holdup at the end of the previous time period can be equated to the holdup at the beginning of the current time period.
- Parameters:
- current_time_varstr,
Name of the operational variable at the beginning of the current time step
- add_objective_function(objective_type='npv')[source]#
Appends the objective function to the model.
- Parameters:
objective_type – str, default=”npv”, Supported objective functions are annualized net present value (“npv”), net profit (“net_profit”), and lifetime net present value (“lifetime_npv”).
- add_overall_cashflows(lifetime=30, discount_rate=0.08, corporate_tax_rate=0.2, annualization_factor=None, cash_inflow_scale_factor=1.0)[source]#
Builds overall cashflow expressions.
- Parameters:
lifetime (int) – int, default=30, Lifetime of the unit/process [in years]
discount_rate (float) – float, default=0.08, Discount rate [fraction] for calculating the current value of the cashflow. It is also used to compute the annualization factor. Must be between 0 and 1.
corporate_tax_rate (float) – float, default=0.2, Fractional value of corporate tax used in NPV calculations. Must be between 0 and 1.
annualization_factor (float | None) – float, default=None, Annualization factor
cash_inflow_scale_factor (float | None) – float, default=1.0, Scaling factor for the sum of net hourly cashflows. If the specified price signal is for the full year, then the value of this argument must be 1.0. However, if the specified price signal is for a short period, say 1 month, then an appropriate scaling factor can be specified to compute the value for a year (12, for the case of 1 month’s price signal).
- add_ramping_limits(op_block_name, commodity, capacity, startup_rate, shutdown_rate, rampup_rate, rampdown_rate)[source]#
Adds ramping constraints of the form: ramping_var[t] - ramping_var[t-1] <= startup_rate * capacity * startup[t] + rampup_rate * capacity * op_mode[t-1]; ramping_var[t-1] - ramping_var[t] <= shutdown_rate * capacity * shutdown[t] + rampdown_rate * capacity * op_mode[t]
- Parameters:
op_block_name (str) – str, Name of the operation model block, e.g., (“fs.ngcc”)
commodity (str) – str, Name of the variable that the ramping constraints will be applied to, e.g., “power”
capacity (float | Param | Var | Expression) – float, or Pyomo Var, or Param, or Expression String of the name of the entity on the model the ramping constraints will be applied to, ex: (“total_power”)
startup_rate (float) – float, Fraction of the maximum capacity that variable ramping_var can increase during startup (between 0 and 1)
shutdown_rate (float) – float, Fraction of the maximum capacity that variable ramping_var can decrease during shutdown (between 0 and 1)
rampup_rate (float) – float, Fraction of the maximum capacity that variable ramping_var can increase during operation (between 0 and 1)
rampdown_rate (float) – float, Fraction of the maximum capacity that variable ramping_var can decrease during operation (between 0 and 1)
- add_startup_shutdown(op_block_name, des_block_name=None, up_time=1, down_time=1)[source]#
Adds minimum uptime/downtime constraints for a given unit/process
- Parameters:
op_block_name (str) – str, Name of the operation model block, e.g., “fs.ngcc”
des_block_name (str | None) – str, default=None, Name of the design model block for the operation block op_block_name. This argument is specified if the design is being optimized simultaneously, e.g., “ngcc_design”
up_time (int) – int, default=1, Total uptime (must be >= 1), e.g., 4 time periods Uptime must include the minimum uptime and the time required for shutdown.
down_time (int) – int, default=1, Total downtime (must be >= 1), e.g., 4 time periods Downtime must include the minimum downtime and the time required for startup
- append_lmp_data(lmp_data, num_representative_days=None, horizon_length=None, seed=42)[source]#
Appends the locational marginal price (LMP) data to the PriceTakerModel object. If desired, the method can cluster the data into representative periods before appending the data.
- Parameters:
lmp_data (list | DataFrame | Series) – Union[list, tuple, pd.DataFrame] List of locational marginal prices
num_representative_days (int | None) – Optional[int], default=None number of clusters or representative periods.
horizon_length (int | None) – Optional[int], default=None Length of each representative period
seed (int | None) – Optional[int], default=42 Seed value for k-means clustering technique
- build_multiperiod_model(flowsheet_func, flowsheet_options=None)[source]#
Builds the multiperiod model.
- get_optimal_representative_days(kmin=4, kmax=30, method='silhouette', generate_elbow_plot=True)[source]#
Returns the optimal number of representative days for the given price signal.
- property horizon_length#
Returns the length of each representative day
- property num_representative_days#
Returns the number of representative days