# 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.capacity.capacity** module is a project-level
module that adds to the formulation components that describe the amount of
power that a project is providing in each study timepoint.
"""
import os.path
import pandas as pd
from pyomo.environ import Expression, value, Constraint
from db.common_functions import spin_on_database_lock
from gridpath.auxiliary.auxiliary import get_required_subtype_modules
from gridpath.common_functions import create_results_df
from gridpath.project.operations.common_functions import load_operational_type_modules
import gridpath.project.operations.operational_types as op_type_init
from gridpath.project import PROJECT_TIMEPOINT_DF
[docs]
def add_model_components(
m,
d,
scenario_directory,
weather_iteration,
hydro_iteration,
availability_iteration,
subproblem,
stage,
):
"""
The following Pyomo model components are defined in this module:
+-------------------------------------------------------------------------+
| Expressions |
+=========================================================================+
| | :code:`Project_Power_Provision_MW` |
| | *Defined over*: :code:`PRJ_OPR_TMPS` |
| |
| Defines the power a project is producing in each of its operational |
| timepoints. The exact formulation of the expression depends |
| on the project's *operational_type*. For each project, we call its |
| *capacity_type* module's *power_provision_rule* method in order to |
| formulate the expression. E.g. a project of the *gen_must_run* |
| operational_type will be producing power equal to its capacity while a |
| dispatchable project will have a variable in its power provision |
| expression. This expression will then be used by other modules. |
+-------------------------------------------------------------------------+
| | :code:`Bulk_Power_Provision_MW` |
| | *Defined over*: :code:`PRJ_OPR_TMPS` |
| |
| Defines the power a project is producing in each of its operational |
| timepoints from the bulk system perspective (adjusted for distribution |
| losses. |
+-------------------------------------------------------------------------+
"""
# Dynamic Inputs
###########################################################################
required_operational_modules = get_required_subtype_modules(
scenario_directory=scenario_directory,
weather_iteration=weather_iteration,
hydro_iteration=hydro_iteration,
availability_iteration=availability_iteration,
subproblem=subproblem,
stage=stage,
which_type="operational_type",
)
imported_operational_modules = load_operational_type_modules(
required_operational_modules
)
# Expressions
###########################################################################
def project_power_provision_rule(mod, prj, tmp):
"""
**Expression Name**: Project_Power_Provision_MW
**Defined Over**: PRJ_OPR_TMPS
Power provision is a variable for some generators, but not others; get
the appropriate expression for each generator based on its operational
type. This is a project-level variable that is not yet adjusted for
distribution system losses to get bulk system equivalent power.
"""
gen_op_type = mod.operational_type[prj]
if hasattr(imported_operational_modules[gen_op_type], "power_provision_rule"):
return imported_operational_modules[gen_op_type].power_provision_rule(
mod, prj, tmp
)
else:
return op_type_init.power_provision_rule(mod, prj, tmp)
m.Project_Power_Provision_MW = Expression(
m.PRJ_OPR_TMPS, rule=project_power_provision_rule
)
def bulk_power_provision_rule(mod, prj, tmp):
"""
This is power production from the perspective of the bulk system (
project power production is multiplied by (1 +
distribution_loss_adjustment_factor)). If a resource is producing power on the
distribution side (and hence does not incur distribution losses),
we'll adjust the power production from the bulk system perspective
based on the distribution_loss_adjustment_factor. Similarly, if the resource is
increasing demand on the distribution side (negative power production),
a higher total demand will be seen on the bulk system side.
"""
return mod.Project_Power_Provision_MW[prj, tmp] * (
1 + mod.distribution_loss_adjustment_factor[prj]
)
m.Bulk_Power_Provision_MW = Expression(
m.PRJ_OPR_TMPS, rule=bulk_power_provision_rule
)
# Input-Output
###############################################################################
def export_results(
scenario_directory,
weather_iteration,
hydro_iteration,
availability_iteration,
subproblem,
stage,
m,
d,
):
"""
Export operations results.
:param scenario_directory:
:param subproblem:
:param stage:
:param m:
The Pyomo abstract model
:param d:
Dynamic components
:return:
Nothing
"""
results_columns = ["project_power_mw", "power_mw"]
data = [
[
prj,
tmp,
value(m.Project_Power_Provision_MW[prj, tmp]),
value(m.Bulk_Power_Provision_MW[prj, tmp]),
]
for (prj, tmp) in m.PRJ_OPR_TMPS
]
results_df = create_results_df(
index_columns=["project", "timepoint"],
results_columns=results_columns,
data=data,
)
for c in results_columns:
getattr(d, PROJECT_TIMEPOINT_DF)[c] = None
getattr(d, PROJECT_TIMEPOINT_DF).update(results_df)
required_operational_modules = get_required_subtype_modules(
scenario_directory=scenario_directory,
weather_iteration=weather_iteration,
hydro_iteration=hydro_iteration,
availability_iteration=availability_iteration,
subproblem=subproblem,
stage=stage,
which_type="operational_type",
)
imported_operational_modules = load_operational_type_modules(
required_operational_modules
)
for optype_module in imported_operational_modules:
if hasattr(
imported_operational_modules[optype_module], "add_to_prj_tmp_results"
):
results_columns, optype_df = imported_operational_modules[
optype_module
].add_to_prj_tmp_results(mod=m)
for column in results_columns:
if column not in getattr(d, PROJECT_TIMEPOINT_DF):
getattr(d, PROJECT_TIMEPOINT_DF)[column] = None
getattr(d, PROJECT_TIMEPOINT_DF).update(optype_df)
# Database
###############################################################################
def process_results(db, c, scenario_id, subscenarios, quiet):
"""
Aggregate dispatch by technology
Aggregate dispatch by technology and period
:param db:
:param c:
:param subscenarios:
:param quiet:
:return:
"""
if not quiet:
print("aggregate dispatch by technology")
# Delete old dispatch by technology
del_sql = """
DELETE FROM results_project_dispatch_by_technology
WHERE scenario_id = ?
"""
spin_on_database_lock(
conn=db, cursor=c, sql=del_sql, data=(scenario_id,), many=False
)
# Aggregate dispatch by technology
agg_sql = """
INSERT INTO results_project_dispatch_by_technology (
scenario_id,
weather_iteration,
hydro_iteration,
availability_iteration,
subproblem_id,
stage_id,
period,
timepoint,
timepoint_weight,
number_of_hours_in_timepoint,
spinup_or_lookahead,
load_zone,
technology,
power_mw
)
SELECT
scenario_id,
weather_iteration,
hydro_iteration,
availability_iteration,
subproblem_id,
stage_id,
period,
timepoint,
timepoint_weight,
number_of_hours_in_timepoint,
spinup_or_lookahead,
load_zone,
technology,
sum(power_mw) AS power_mw
FROM results_project_timepoint
WHERE scenario_id = ?
GROUP BY
weather_iteration,
hydro_iteration,
availability_iteration,
subproblem_id,
stage_id,
timepoint,
load_zone,
technology
ORDER BY
weather_iteration,
hydro_iteration,
availability_iteration,
subproblem_id,
stage_id,
timepoint,
load_zone,
technology;"""
spin_on_database_lock(
conn=db, cursor=c, sql=agg_sql, data=(scenario_id,), many=False
)
if not quiet:
print("aggregate dispatch by technology-period")
# Delete old dispatch by technology
del_sql = """
DELETE FROM results_project_dispatch_by_technology_period
WHERE scenario_id = ?
"""
spin_on_database_lock(
conn=db, cursor=c, sql=del_sql, data=(scenario_id,), many=False
)
# Aggregate dispatch by technology, period, and spinup_or_lookahead
agg_sql = """
INSERT INTO results_project_dispatch_by_technology_period (
scenario_id,
weather_iteration,
hydro_iteration,
availability_iteration,
subproblem_id,
stage_id,
period,
load_zone,
technology, spinup_or_lookahead, energy_mwh)
SELECT
scenario_id,
weather_iteration,
hydro_iteration,
availability_iteration,
subproblem_id,
stage_id,
period,
load_zone,
technology,
spinup_or_lookahead,
SUM(power_mw * timepoint_weight * number_of_hours_in_timepoint ) AS energy_mwh
FROM results_project_dispatch_by_technology
WHERE scenario_id = ?
GROUP BY
weather_iteration,
hydro_iteration,
availability_iteration,
subproblem_id,
stage_id,
period,
load_zone,
technology,
spinup_or_lookahead
ORDER BY
weather_iteration,
hydro_iteration,
availability_iteration,
subproblem_id,
stage_id,
period,
load_zone,
technology,
spinup_or_lookahead
;
"""
spin_on_database_lock(
conn=db, cursor=c, sql=agg_sql, data=(scenario_id,), many=False
)