# Copyright 2016-2023 Blue Marble Analytics LLC.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
The **gridpath.project.operations.cap_factor_limits** module is a project-level
module that adds to the formulation components that limit the minimum and maximum
cap factor of a project over a horizon.
"""
import csv
import os.path
from pyomo.environ import Set, Param, Constraint, Expression, Reals, value
from gridpath.auxiliary.db_interface import import_csv
Infinity = float("inf")
Negative_Infinity = float("-inf")
[docs]
def add_model_components(
m,
d,
scenario_directory,
weather_iteration,
hydro_iteration,
availability_iteration,
subproblem,
stage,
):
"""
The tables below list the Pyomo model components defined in the
'gen_commit_bin' module followed below by the respective components
defined in the 'gen_commit_lin" module.
+-------------------------------------------------------------------------+
| Sets |
+=========================================================================+
| | :code:`CAP_FACTOR_LIMIT_PRJ_BT_HRZ` |
| | *Within*: :code:`PROJECTS*BLN_TYPE_HRZS` |
| |
| Three-dimensional set with the project, horizon balancing type, and |
| horizon over which cap factor limits should be enforced. |
+-------------------------------------------------------------------------+
+-------------------------------------------------------------------------+
| Input Params |
+=========================================================================+
| | :code:`min_cap_factor` |
| | *Defined over*: :code:`CAP_FACTOR_LIMIT_PRJ_BT_HRZ` |
| | *Within*: :code:`Reals` |
| | *Default*: :code:`Negative_Infinity` |
| |
| The project's minimum cap factor for this horizon balancing type and |
| horizon. It can be negative to allow us to limit storage (which is a |
| net load over the course of the horizon due to losses. |
+-------------------------------------------------------------------------+
| | :code:`max_cap_factor` |
| | *Defined over*: :code:`CAP_FACTOR_LIMIT_PRJ_BT_HRZ` |
| | *Within*: :code:`Reals` |
| | *Default*: :code:`Infinity` |
| |
| The project's maximum cap factor for this horizon balancing type and |
| horizon. It can be negative to allow us to limit storage (which is a |
| net load over the course of the horizon due to losses. |
+-------------------------------------------------------------------------+
+-------------------------------------------------------------------------+
| Constraints |
+-------------------------------------------------------------------------+
| | :code:`Min_Cap_Factor_Constraint` |
| | *Defined over*: :code:`CAP_FACTOR_LIMIT_PRJ_BT_HRZ` |
| |
| Energy output from this project over this balancing type and horizon |
| must equal or exceed the minimum capacity factor multiplied by the |
| maximum possible output over this balancing type and horizon. |
+-------------------------------------------------------------------------+
| | :code:`Max_Cap_Factor_Constraint` |
| | *Defined over*: :code:`CAP_FACTOR_LIMIT_PRJ_BT_HRZ` |
| |
| Energy output from this project over this balancing type and horizon |
| must be less than or equal to the maximum capacity factor multiplied |
| by the maximum possible output over this balancing type and horizon. |
+-------------------------------------------------------------------------+
"""
m.CAP_FACTOR_LIMIT_PRJ_BT_HRZ = Set(dimen=3, within=m.PROJECTS * m.BLN_TYPE_HRZS)
m.min_cap_factor = Param(
m.CAP_FACTOR_LIMIT_PRJ_BT_HRZ, within=Reals, default=Negative_Infinity
) # allow negative values, e.g. for storage (net power is <0 due to losses)
m.max_cap_factor = Param(
m.CAP_FACTOR_LIMIT_PRJ_BT_HRZ, within=Reals, default=Infinity
) # allow negative values, e.g. for storage (net power is <0 due to losses)
def actual_power_provision_in_horizon_rule(mod, prj, bt, h):
""" """
return sum(
mod.Power_Provision_MW[prj, tmp] * mod.hrs_in_tmp[tmp] * mod.tmp_weight[tmp]
for tmp in mod.TMPS_BY_BLN_TYPE_HRZ[bt, h]
)
m.Actual_Power_Provision_in_Horizon_Expression = Expression(
m.CAP_FACTOR_LIMIT_PRJ_BT_HRZ, rule=actual_power_provision_in_horizon_rule
)
def possible_power_provision_in_horizon_rule(mod, prj, bt, h):
return sum(
mod.Capacity_MW[prj, mod.period[tmp]]
* mod.hrs_in_tmp[tmp]
* mod.tmp_weight[tmp]
for tmp in mod.TMPS_BY_BLN_TYPE_HRZ[bt, h]
)
m.Possible_Power_Provision_in_Horizon_Expression = Expression(
m.CAP_FACTOR_LIMIT_PRJ_BT_HRZ, rule=possible_power_provision_in_horizon_rule
)
def min_cap_factor_constraint_rule(mod, prj, bt, h):
if mod.min_cap_factor[prj, bt, h] == Negative_Infinity:
return Constraint.Skip
else:
return (
mod.Actual_Power_Provision_in_Horizon_Expression[prj, bt, h]
>= mod.min_cap_factor[prj, bt, h]
* mod.Possible_Power_Provision_in_Horizon_Expression[prj, bt, h]
)
m.Min_Cap_Factor_Constraint = Constraint(
m.CAP_FACTOR_LIMIT_PRJ_BT_HRZ,
rule=min_cap_factor_constraint_rule,
)
def max_cap_factor_constraint_rule(mod, prj, bt, h):
if mod.max_cap_factor[prj, bt, h] == Infinity:
return Constraint.Skip
else:
return (
mod.Actual_Power_Provision_in_Horizon_Expression[prj, bt, h]
<= mod.max_cap_factor[prj, bt, h]
* mod.Possible_Power_Provision_in_Horizon_Expression[prj, bt, h]
)
m.Max_Cap_Factor_Constraint = Constraint(
m.CAP_FACTOR_LIMIT_PRJ_BT_HRZ,
rule=max_cap_factor_constraint_rule,
)
def load_model_data(
m,
d,
data_portal,
scenario_directory,
weather_iteration,
hydro_iteration,
availability_iteration,
subproblem,
stage,
):
"""
:param mod:
:param data_portal:
:param scenario_directory:
:param subproblem:
:param stage:
:return:
"""
input_file = os.path.join(
scenario_directory,
weather_iteration,
hydro_iteration,
availability_iteration,
subproblem,
stage,
"inputs",
"cap_factor_limits.tab",
)
if os.path.exists(input_file):
data_portal.load(
filename=input_file,
index=m.CAP_FACTOR_LIMIT_PRJ_BT_HRZ,
param=(
m.min_cap_factor,
m.max_cap_factor,
),
)
def export_results(
scenario_directory,
weather_iteration,
hydro_iteration,
availability_iteration,
subproblem,
stage,
m,
d,
):
""" """
input_file = os.path.join(
scenario_directory,
weather_iteration,
hydro_iteration,
availability_iteration,
subproblem,
stage,
"inputs",
"cap_factor_limits.tab",
)
if os.path.exists(input_file):
with open(
os.path.join(
scenario_directory,
weather_iteration,
hydro_iteration,
availability_iteration,
subproblem,
stage,
"results",
"project_cap_factor_limits.csv",
),
"w",
newline="",
) as results_f:
writer = csv.writer(results_f)
writer.writerow(
[
"project",
"balancing_type_horizon",
"horizon",
"min_cap_factor",
"max_cap_factor",
"actual_power_provision_mwh",
"possible_power_provision_mwh",
]
)
for prj, bt, h in sorted(m.CAP_FACTOR_LIMIT_PRJ_BT_HRZ):
writer.writerow(
[
prj,
bt,
h,
(
None
if m.min_cap_factor[prj, bt, h] == Negative_Infinity
else m.min_cap_factor[prj, bt, h]
),
(
None
if m.max_cap_factor[prj, bt, h] == Infinity
else m.max_cap_factor[prj, bt, h]
),
value(
m.Actual_Power_Provision_in_Horizon_Expression[prj, bt, h]
),
value(
m.Possible_Power_Provision_in_Horizon_Expression[prj, bt, h]
),
]
)
# Database
###############################################################################
def import_results_into_database(
scenario_id,
weather_iteration,
hydro_iteration,
availability_iteration,
subproblem,
stage,
c,
db,
results_directory,
quiet,
):
"""
:param scenario_id:
:param c:
:param db:
:param results_directory:
:param quiet:
:return:
"""
which_results = "project_cap_factor_limits"
if os.path.exists(
os.path.join(
results_directory,
weather_iteration,
hydro_iteration,
availability_iteration,
subproblem,
stage,
"results",
f"{which_results}.csv",
)
):
import_csv(
conn=db,
cursor=c,
scenario_id=scenario_id,
hydro_iteration=hydro_iteration,
availability_iteration=availability_iteration,
subproblem=subproblem,
stage=stage,
quiet=quiet,
results_directory=results_directory,
which_results=which_results,
)