-
Notifications
You must be signed in to change notification settings - Fork 235
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Price taker model for DISPATCHES, Rehashed #1358
base: main
Are you sure you want to change the base?
Changes from 137 commits
a3d688c
67ce6a2
e716c36
e3788fb
8ef54be
f154700
422d0ba
2f36bbb
0aa1034
9042e5c
5549028
2fde47f
d0a6c13
e5f6c07
56d9369
9b5edd4
948f40c
30fed63
fc43a3f
4170501
1d87aea
4710a10
be68154
66db0d8
b39f4d9
799ed0b
7625d07
0c2c461
16710a6
4518240
35ddead
fd15af9
b84ea43
15ea641
53e427e
14ee544
4e10801
d68d8fd
6cd966e
f7e3245
b223cf6
56f8099
48d4d98
fde3f31
5bd0d5b
0df8b45
fd595b4
a54c3ac
70cbaba
85b7bd0
9851b88
6ede14e
d31b272
769ba7b
0bba4d2
b06d467
0cf82bf
6af8c9e
1fc5f53
e991392
f2725cc
522023b
22dbe12
6dbb183
7a86d03
7a24fba
b1629eb
d193e65
fb543c7
460af99
c90bbe1
643707f
8aa02c6
323e4a9
04d07ae
4ae5779
893743d
82f8bdc
ebdc1b2
aad41fb
e2dac86
5ad727e
2ae4635
8762f7b
7110dea
8550569
69c3c9b
177618c
ce4b0fb
343d191
cec302e
652d1cc
d8ade5d
cd3f3d3
ce6b3db
f4cfbdb
cbd7aaa
f10ea59
46a3331
7bdd94d
1c85981
6956d73
8b4b479
646b88b
a1f1eb2
8101448
d8de0dd
fc7369e
75a56f4
df687af
47b66c5
8ac5987
d10b9a7
1e7842a
8b1393a
6481545
b9f3ef4
bd2db6f
9cf4a3a
112761f
fb0a05b
5d960a4
d7a0002
727be78
aebe5b8
411bb60
bd3244c
b8149b5
043ac34
2cd22e4
e9ff144
422ec6e
042a1d9
0fd8edb
892e555
a2c97c4
20db58f
a4620de
a1ae6b7
bef17fb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
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. | ||
|
||
.. |pricetaker| image:: images/pricetaker.png | ||
:width: 1200 | ||
:alt: Alternative text | ||
:align: middle | ||
|
||
|pricetaker| | ||
|
||
The following equations represent the multi-period price taker model, where :math:`d` are design decisions, | ||
:math:`u` are operating decisions, :math:`δ` are power decisions, :math:`s` are scenarios (timeseries/representative days), | ||
:math:`w` is weight/frequency, :math:`R` is revenue, :math:`π` is price data, | ||
:math:`C` is capital and operating costs, :math:`g` is the process model, and :math:`h` is the temporal constraint. | ||
|
||
.. math:: | ||
|
||
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})] | ||
|
||
.. math:: | ||
|
||
g(d,u_{s,t},δ_{s,t}) = 0; ∀_{s} ∈ S, t ∈ T | ||
|
||
.. math:: | ||
|
||
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. | ||
|
||
.. |hybrid_energy_system| image:: images/hybrid_energy_system.png | ||
:width: 1200 | ||
:alt: Alternative text | ||
:align: middle | ||
|
||
|hybrid_energy_system| | ||
|
||
.. module:: idaes.apps.grid_integration.multiperiod.price_taker_model | ||
|
||
.. autoclass:: PriceTakerModel | ||
:members: |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
Multi-Period Modeling | ||
===================== | ||
|
||
Multi-period modeling can be used to simplify dynamic optimization problems | ||
by linking steady-state models over a time horizon with rate-limiting constraints. | ||
Therefore, sets of constraints at one temporal index will affect decisions taken | ||
at a different moment in time. These interactions can be used to model the relationship | ||
between the energy systems and wholesale electricity markets. | ||
|
||
.. toctree:: | ||
:maxdepth: 2 | ||
|
||
Price_Taker |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,215 @@ | ||
################################################################################# | ||
# The Institute for the Design of Advanced Energy Systems Integrated Platform | ||
# Framework (IDAES IP) was produced under the DOE Institute for the | ||
# Design of Advanced Energy Systems (IDAES). | ||
# | ||
# Copyright (c) 2018-2023 by the software owners: The Regents of the | ||
# University of California, through Lawrence Berkeley National Laboratory, | ||
# National Technology & Engineering Solutions of Sandia, LLC, Carnegie Mellon | ||
# University, West Virginia University Research Corporation, et al. | ||
# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md | ||
# for full copyright and license information. | ||
################################################################################# | ||
|
||
import logging | ||
from pyomo.environ import Binary, Param, Var | ||
from pyomo.common.config import Bool, ConfigDict, ConfigValue | ||
from idaes.core.base.process_base import declare_process_block_class | ||
from idaes.core.base.process_base import ProcessBlockData | ||
|
||
_logger = logging.getLogger(__name__) | ||
|
||
|
||
# pylint: disable = attribute-defined-outside-init, too-many-ancestors | ||
# pylint: disable = invalid-name, logging-fstring-interpolation | ||
@declare_process_block_class("DesignModel") | ||
class DesignModelData(ProcessBlockData): | ||
""" | ||
Builds the 'design model' for a unit/process. | ||
|
||
Args: | ||
model_func: Function that builds the design model | ||
model_args: Dictionary containing the arguments needed for model_func | ||
|
||
The class defines `install_unit` binary variable that takes the value 1 | ||
if the unit is built/installed, and 0 otherwise. | ||
|
||
Function model_func must declare all the necessary design variables, | ||
relations among design variables, capital cost correlations, and fixed O&M | ||
cost correlations. The function must also define attributes `capex` for | ||
capital cost, and `fom` for fixed O&M cost. If not defined, these attributes | ||
will be set to zero. | ||
|
||
Example Usage: | ||
|
||
.. code-block:: python | ||
|
||
def my_design_model(m, p_min, p_max, cost): | ||
m.power = Var() | ||
m.min_capacity = Constraint( | ||
expr=p_min * m.install_unit <= m.power | ||
) | ||
m.max_capacity = Constraint( | ||
expr=m.power <= p_max * m.install_unit | ||
) | ||
|
||
# capex and fom must either be a constant, or Var, or Expression | ||
m.capex = Expression(expr=cost["capex"] * m.power) | ||
m.fom = Expression(expr=cost["fom"] * m.power) | ||
|
||
m = ConcreteModel() | ||
m.unit_1 = DesignModel( | ||
model_func=my_design_model, | ||
model_args={ | ||
"p_min": 150, "p_max": 600, "cost": {"capex": 10, "fom": 1}, | ||
}, | ||
) | ||
Comment on lines
+28
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @andrewlee94 - documentation for design model |
||
""" | ||
|
||
CONFIG = ConfigDict() | ||
CONFIG.declare( | ||
"model_func", | ||
ConfigValue( | ||
doc="Function that builds the design model", | ||
), | ||
) | ||
CONFIG.declare( | ||
"model_args", | ||
ConfigValue( | ||
default={}, | ||
doc="Dictionary containing arguments needed for model_func", | ||
), | ||
) | ||
|
||
def build(self): | ||
super().build() | ||
|
||
self.install_unit = Var( | ||
within=Binary, | ||
doc="Binary: 1, if the unit is installed, 0 otherwise", | ||
) | ||
|
||
if self.config.model_func is None: | ||
# Function that builds the design model is not specified | ||
return | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the expected behaviour/usage in this case? I note that if this occurs then none of the following code will run, and I do not see any documentation or messages about what this would mean for the user. I think a logger message is required here at the least. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that we should add a logger message here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've addressed this already, but feel free to comment on the warning itself |
||
|
||
# Call the function that builds the design model | ||
self.config.model_func(self, **self.config.model_args) | ||
adam-a-a marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
# Check if capital and fixed O&M costs are defined | ||
if not hasattr(self, "capex"): | ||
_logger.warning( | ||
f"'capex' attribute is not set for the design model " | ||
f"{self.name}. Setting the capital cost of the unit to zero." | ||
) | ||
self.capex = 0 | ||
|
||
if not hasattr(self, "fom"): | ||
_logger.warning( | ||
f"'fom' attribute is not set for the design model " | ||
f"{self.name}. Setting the fixed O&M cost of the unit to zero." | ||
) | ||
self.fom = 0 | ||
|
||
|
||
@declare_process_block_class("OperationModel") | ||
class OperationModelData(ProcessBlockData): | ||
""" | ||
Builds the 'operation model' for a unit/process. | ||
|
||
Args: | ||
model_func: Function that builds the operation model | ||
model_args: Dictionary containing the arguments needed for model_func | ||
|
||
The class defines `op_mode`, `startup`, and `shutdown` binary variables | ||
to track the operation, startup, and shutdown of the unit/process. | ||
|
||
Function model_func must declare all the necessary operation variables, | ||
relations among operation variables, and variable O&M cost correlations. | ||
|
||
Example Usage: | ||
|
||
.. code-block:: python | ||
|
||
def my_operation_model(m, design_blk): | ||
m.power = Var() | ||
m.fuel_flow = Var() | ||
... | ||
|
||
m = ConcreteModel() | ||
m.unit_1 = DesignModel( | ||
model_func=my_design_model, | ||
model_args={ | ||
"p_min": 150, "p_max": 600, "cost": {"capex": 10, "fom": 1}, | ||
}, | ||
) | ||
m.op_unit_1 = OperationModel( | ||
model_func=my_operation_model, model_args={"design_blk": m.unit_1}, | ||
) | ||
""" | ||
Comment on lines
+124
to
+155
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @andrewlee94 - documentation for operation model |
||
|
||
CONFIG = ConfigDict() | ||
CONFIG.declare( | ||
"model_func", | ||
ConfigValue( | ||
doc="Function that builds the design model", | ||
), | ||
) | ||
CONFIG.declare( | ||
"model_args", | ||
ConfigValue( | ||
default={}, | ||
doc="Dictionary containing arguments needed for model_func", | ||
), | ||
) | ||
CONFIG.declare( | ||
"declare_op_vars", | ||
ConfigValue( | ||
default=True, | ||
domain=Bool, | ||
doc="Should op_mode, startup, shutdown vars be defined?", | ||
), | ||
) | ||
CONFIG.declare( | ||
"declare_lmp_param", | ||
ConfigValue( | ||
default=True, | ||
domain=Bool, | ||
doc="Should LMP data automatically be appended to the model?", | ||
), | ||
) | ||
|
||
# noinspection PyAttributeOutsideInit | ||
def build(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similarly, the usage of this class will need to be clearly documented, especially what There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @andrewlee94 I was just thinking about this, and on the one hand, using derived classes would be more in line with how the rest of IDAES does things. On the other hand, using this In any case, I think that any refactor of this formulation should be part of a subsequent PR. I think it is more important for this overall capability to get merged in first, since there has already been published work on this Pricetaker approach (for one). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This approach is backwards. If we know a refactor is coming, it's better to do it before the PR is merged. Otherwise we need to deprecate the old class, leave it in a few cycles, and create a similarly-named new class in order to maintain backwards compatibility. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In fact, we don't know a refactor is coming and it is something that is really up for discussion at this point--which is why I said "if any refactor." This PriceTaker class has been delayed for quite some time now, and I don't think we should wait until the discussion settles. |
||
super().build() | ||
|
||
# Build the model | ||
if self.config.declare_op_vars: | ||
self.op_mode = Var( | ||
within=Binary, | ||
doc="Binary: 1 if the unit is operating, 0 otherwise", | ||
) | ||
|
||
self.startup = Var( | ||
within=Binary, | ||
doc="Binary: 1 if the startup is initiated, 0 otherwise", | ||
) | ||
|
||
self.shutdown = Var( | ||
within=Binary, | ||
doc="Binary: 1 if the shutdown is initiated, 0 otherwise", | ||
) | ||
|
||
if self.config.declare_lmp_param: | ||
self.LMP = Param( | ||
initialize=1, | ||
mutable=True, | ||
doc="Time-varying locational marginal prices (LMPs) [in $/MWh]", | ||
) | ||
|
||
if self.config.model_func is None: | ||
# Function that builds the operation model is not specified | ||
return | ||
MarcusHolly marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
# Call the function that builds the operation model | ||
self.config.model_func(self, **self.config.model_args) | ||
radhakrishnatg marked this conversation as resolved.
Show resolved
Hide resolved
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are these necessary? It is generally bad practice to do blanket disabling of pylint warnings, and by doing this I cannot see what was the cause of the warning to suggest how to fix it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @andrewlee94. My suggestion: