Skip to content
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

WIP - Support storage in RTS-GMLC input #184

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions doc/OnlineDocs/source/_static/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,13 @@
white-space: normal;
word-break: break-word;
}

.rst-content span.collabel {
white-space: nowrap;
}

/* Put space between paragraphs in tables by adding a top margin to all but the first paragraph. */
/* This preserves zero margin at the top and bottom of each cell. */
.rst-content .wy-table-responsive table.docutils p ~ p {
margin-top: 0.8rem;
}
2 changes: 1 addition & 1 deletion doc/OnlineDocs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@

class ColumnRole(SphinxRole):
def run(self):
node = nodes.inline(rawtext=self.rawtext, classes=['guilabel'])
node = nodes.inline(rawtext=self.rawtext, classes=['guilabel', 'collabel'])
node += nodes.Text(self.text)
return [node], []

Expand Down
4 changes: 2 additions & 2 deletions doc/OnlineDocs/source/reference/file_formats/rts-gmlc/gen.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ generator in the model, including both thermal and renewable generators.
- **Description**
- **Egret**
* - :col:`GEN UID`
- A unique string identifier for the generator.
- Used as the branch name in Egret. Data for this branch is stored in a
- A unique string identifier for the generator
- Used as the generator name in Egret. Data for this generator is stored in a
generator dictionary stored at :samp:`['elements']['generator'][{<GEN UID>}]`.
* - :col:`Bus ID`
- :col:`Bus ID` of connecting bus
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ Optional Files
:maxdepth: 1

dc_branch
storage
initial_status
185 changes: 185 additions & 0 deletions doc/OnlineDocs/source/reference/file_formats/rts-gmlc/storage.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
storage.csv
===========

This file is where storage elements are defined. Add one row for each
storage component in the model.

Note that Prescient supports more storage properties than what is
supported by the standard RTS-GMLC input format. Prescient extends
the standard RTS-GMLC file format by supporting a set of additional
columns that are not part of the RTS-GMLC standard. Some of these
non-standard columns are required by Prescient; Prescient is unable
to use standard RTS-GMLC storage.csv files without a few extra
columns.

Optional properties can be left out of the CSV file, either by leaving
the entire column out of the file, or by leaving values blank. Any
missing values will use default values.

.. note::
Be aware that some default values may prevent a storage element from
being utilized as intended. For example, the default `End State of
Charge` defaults to the initial state of charge. If the storage
element starts fully charged, the default `End State of Charge` is set
to 1.0, requiring the storage element to be fully charged at the end
of each RUC and SCED.

.. note::
Note for reviewers: As implemented, default values are handled by Egret. If a value is omitted from
the CSV file, it is also omitted from the Egret model. Egret then handles the
missing value using its own default value logic. The defaults listed in the table
below were taken from the Egret source code.


.. list-table:: storage.csv Columns
:header-rows: 1
:widths: auto
:class: table-top-align-cells

* - **Column Name**
- **Description**
- **Egret**
- **Default Value**
* - :col:`GEN UID`
- The name of a generator on the same bus as this storage element
- Used to identify the bus this storage element is attached to.
The storage will be placed on the same bus as the named generator.
No relationship between the generator and the storage is reflected
in the Egret model, other than being placed on the same bus.
- *Required*
* - :col:`Storage`
- A unique name for the storage element
- Used as the storage element name in Egret. Data for this storage component is placed in a
dictionary located at :samp:`['elements']['generator'][{<Storage>}]`.
- *Required*
* - :col:`Max Volume GWh`
- The maximum storage capacity, in GWh
- Converted to MWh, then placed in the storage dictionary as ``energy_capacity``
- *Required*
* - :col:`Initial Volume GWh`
- The quantity of energy stored in the storage element at the beginning
of the simulation, in GWh.
- Converted to a fraction of *Max Volume GWh*, then placed in the storage dictionary as
``initial_state_of_charge``
- 0.0
* - :col:`Start Energy`
- The rate at which energy is being drawn from the storage element (if positive),
or the rate at which energy is being injected into the storage element (if negative),
at the start of the simulation. Units are GW.
- First converted to MW.

If positive, placed in the storage dictionary as ``initial_discharge_rate``.

If negative, placed in the storage dictionary as ``initial_charge_rate``.

This column serves the same purpose as either the :col:`Initial Charge Rate MW`
or the :col:`Initial Discharge Rate MW` column, depending on sign. If a row
has a value in the :col:`Start Energy` column and in the corresponding initial
charge/discharge column, the initial charge/discharge column takes precedence and
the value in :col:`Start Energy` is ignored.
- 0.0
* - :col:`Initial Charge Rate MW`
- The rate at which energy is being injected into the storage element
at the start of the simulation. Units are MW.
- Placed in the storage dictionary as ``initial_charge_rate``.

Takes precedence over negative values in the :col:`Start Energy` column.
- 0.0
* - :col:`Initial Discharge Rate MW`
- The rate at which energy is being drawn from the storage element
at the start of the simulation. Units are MW.
- Placed in the storage dictionary as ``initial_discharge_rate``.

Takes precedence over positive values in the :col:`Start Energy` column.
- 0.0
* - :col:`Inflow Limit GWh`
- The maximum rate at which energy can be injected into the storage element, in GW

.. note::
(The column name says GWh. Should this really be GW instead???). Also, the
Egret default is 0.0. Is that a good default? I guess it means "not rechargeable"?
- Converted to MW, then placed in the storage dictionary as ``max_charge_rate``
- 0.0
* - :col:`Rating MVA`
- The maximum rate at which energy can be drawn from the storage element
- Placed in the storage dictionary as ``max_discharge_rate``
- 0.0
* - :col:`Min Discharge Rate MW`
- The minimum rate at which energy can be drawn from the storage element
- Placed in the storage dictionary as ``min_discharge_rate``
- 0.0
* - :col:`Min Charge Rate MW`
- The minimum rate at which energy can be injected into the storage element
- Placed in the storage dictionary as ``min_charge_rate``
- 0.0
* - :col:`Max Hourly Discharge Ramp Up MW`
- The maximum increase in the discharge rate within a 60 minute period
- Placed in the storage dictionary as ``ramp_up_output_60min``
- *Required*

.. note::
Required by Egret, has no default. Consider updating Egret to make this
optional, with some sort of "Unlimited" default.
* - :col:`Max Hourly Discharge Ramp Down MW`
- The maximum decrease in the discharge rate within a 60 minute period
- Placed in the storage dictionary as ``ramp_down_output_60min``
- *Required*

.. note::
Required by Egret, has no default. Consider updating Egret to make this
optional, with some sort of "Unlimited" default.
* - :col:`Max Hourly Charge Ramp Up MW`
- The maximum increase in the charging rate within a 60 minute period
- Placed in the storage dictionary as ``ramp_up_input_60min``
- *Required*

.. note::
Required by Egret, has no default. Consider updating Egret to make this
optional, with some sort of "Unlimited" default.
* - :col:`Max Hourly Charge Ramp Down MW`
- The maximum decrease in the charging rate within a 60 minute period
- Placed in the storage dictionary as ``ramp_down_input_60min``
- *Required*

.. note::
Required by Egret, has no default. Consider updating Egret to make this
optional, with some sort of "Unlimited" default.
* - :col:`Min SoC`
- The minimum state of charge the storage element is allowed to be drawn down to.
Below this point, the system is not allowed to draw additional energy from
the storage element. Expressed as a number between 0 and 1 that indicates the
fraction of the maximum storage capacity below which additional energy
may not be drawn. A value of 0 means all energy is allowed to be drawn from the
storage element; a value of 0.5 means the system must stop drawing energy
from the storage element once its stored energy drops below half of its capacity.
- Placed in the storage dictionary as ``minimum_state_of_charge``
- 0.0
* - :col:`Charge Efficiency`
- The fraction of injected energy that is stored in the storage
element. Between 0 and 1.
- Placed in the storage dictionary as ``charge_efficiency``
- 1.0
* - :col:`Discharge Efficiency`
- The fraction of drawn energy that is injected onto the bus.
Between 0 and 1.
- Placed in the storage dictionary as ``discharge_efficiency``
- 1.0
* - :col:`Hourly Retention Rate`
- The fraction of stored energy that is still stored in the storage
element after 60 minutes of idle time. Between 0 and 1.
- Placed in the storage dictionary as ``retention_rate_60min``
- 1.0
* - :col:`Charge Cost`
- The cost per MW of inflow, before losses due to charge efficiency.
- Placed in the storage dictionary as ``charge_cost``
- 0.0
* - :col:`Discharge Cost`
- The cost per MW of outflow, before losses due to discharge efficiency.
- Placed in the storage dictionary as ``discharge_cost``
- 0.0
* - :col:`End State of Charge`
- The minimum state of charge at the end of each RUC and SCED.
Between 0 and 1.
- Placed in the storage dictionary as ``end_state_of_charge``.
- Defaults to the initial state of charge fraction implied by :col:`Initial Volume GWh`,
if specified. If that value is also omitted, defaults to 0.5.
8 changes: 3 additions & 5 deletions doc/OnlineDocs/source/tasks/configure.rst
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,9 @@ The table below describes all available configuration options.
-
-
-
* - \-\-data-path

or

\-\-data-directory
* - | \-\-data-path
| or
| \-\-data-directory
- data_path
- Path. Default=input_data.
- Path to a file or folder where input data is located. Whether it
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ def apply_ruc(self, options, ruc:RucModel) -> None:
max_ruc_length = ruc_delay + options.ruc_horizon
self._commits[g] = deque(maxlen=max_ruc_length)
for s,s_dict in ruc.elements('storage'):
self._init_state_of_charge[s] = s_dict['initial_state_of_charge']
self._init_soc[s] = s_dict['initial_state_of_charge']

# If this is first RUC, also save data to indicate when to pop RUC-related state
self._minutes_per_forecast_step = ruc.data['system']['time_period_length_minutes']
Expand Down
2 changes: 1 addition & 1 deletion prescient/engine/data_extractors.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ def get_all_storage_soc_dispatch_levels(self, sced: OperationsModel) -> Dict[S,
for s in self.get_all_storage(sced)}

def get_all_storage_types(self, sced: OperationsModel) -> Dict[S, float]:
return {s: self.get_storage_soc_dispatch_level(sced, s)
return {s: self.get_storage_type(sced, s)
for s in self.get_all_storage(sced)}


Expand Down
10 changes: 5 additions & 5 deletions prescient/engine/egret/data_extractors.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,20 +340,20 @@ def get_bus_mismatch(sced: OperationsModel, bus: B) -> float:

@staticmethod
def get_storage_input_dispatch_level(sced: OperationsModel, storage: S) -> float:
return sced.data['elements']['storage'][s]['p_charge']['values'][0]
return sced.data['elements']['storage'][storage]['p_charge']['values'][0]

@staticmethod
def get_storage_output_dispatch_level(sced: OperationsModel, storage: S) -> float:
return sced.data['elements']['storage'][s]['p_discharge']['values'][0]
return sced.data['elements']['storage'][storage]['p_discharge']['values'][0]

@staticmethod
def get_storage_soc_dispatch_level(sced: OperationsModel, storage: S) -> float:
return sced.data['elements']['storage'][s]['state_of_charge']['values'][0]
return sced.data['elements']['storage'][storage]['state_of_charge']['values'][0]

@staticmethod
def get_storage_type(sced: OperationsModel, storage: S) -> str:
if 'fuel' in sced.data['elements']['storage'][s]:
return sced.data['elements']['storage'][s]['fuel']
if 'fuel' in sced.data['elements']['storage'][storage]:
return sced.data['elements']['storage'][storage]['fuel']
return 'Other'

@staticmethod
Expand Down