Source code for oemof.solph.flows.experimental._electrical_line

# -*- coding: utf-8 -*-

"""
In-development electrical line components.

SPDX-FileCopyrightText: Uwe Krien <krien@uni-bremen.de>
SPDX-FileCopyrightText: Simon Hilpert
SPDX-FileCopyrightText: Cord Kaldemeyer
SPDX-FileCopyrightText: Patrik Schönfeldt
SPDX-FileCopyrightText: Johannes Röder
SPDX-FileCopyrightText: jakob-wo
SPDX-FileCopyrightText: gplssm
SPDX-FileCopyrightText: jnnr
SPDX-FileCopyrightText: Johannes Kochems

SPDX-License-Identifier: MIT

"""

import logging

from pyomo.core.base.block import ScalarBlock
from pyomo.environ import BuildAction
from pyomo.environ import Constraint
from pyomo.environ import Set
from pyomo.environ import Var

from oemof.solph._plumbing import sequence as solph_sequence
from oemof.solph.buses.experimental._electrical_bus import ElectricalBus
from oemof.solph.flows._flow import Flow


[docs]class ElectricalLine(Flow): r"""An ElectricalLine to be used in linear optimal power flow calculations. based on angle formulation. Check out the Notes below before using this component! Parameters ---------- reactance : float or array of floats Reactance of the line to be modelled Note: This component is experimental. Use it with care. Notes ----- * To use this object the connected buses need to be of the type :py:class:`~oemof.solph.experimental.ElectricalBus`. * It does not work together with flows that have set the attr.`nonconvex`, i.e. unit commitment constraints are not possible * Input and output of this component are set equal, therefore just use either only the input or the output to parameterize. * Default attribute `min` of in/outflows is overwritten by -1 if not set differently by the user The following sets, variables, constraints and objective parts are created * :py:class:`~oemof.solph.experimental.electrical_line.ElectricalLineBlock` """ # noqa: E501 def __init__(self, **kwargs): super().__init__( nominal_value=kwargs.get("nominal_value"), variable_costs=kwargs.get("variable_costs", 0), min=kwargs.get("min"), max=kwargs.get("max"), fix=kwargs.get("fix"), positive_gradient_limit=kwargs.get("positive_gradient_limit"), negative_gradient_limit=kwargs.get("negative_gradient_limit"), full_load_time_max=kwargs.get("full_load_time_max"), full_load_time_min=kwargs.get("full_load_time_min"), integer=kwargs.get("integer", False), bidirectional=kwargs.get("bidirectiona", False), investment=kwargs.get("investment"), nonconvex=kwargs.get("nonconvex"), custom_attributes=kwargs.get("costom_attributes"), ) self.reactance = solph_sequence(kwargs.get("reactance", 0.00001)) self.input = kwargs.get("input") self.output = kwargs.get("output") # set input / output flow values to -1 by default if not set by user if self.nonconvex is not None: raise ValueError( ( "Attribute `nonconvex` must be None for " + "component `ElectricalLine` from {} to {}!" ).format(self.input, self.output) ) if self.min is None: self.min = -1 # to be used in grouping for all bidi flows self.bidirectional = True
[docs] def constraint_group(self): return ElectricalLineBlock
[docs]class ElectricalLineBlock(ScalarBlock): r"""Block for the linear relation of nodes with type class:`.ElectricalLine` Note: This component is experimental. Use it with care. **The following constraints are created:** Linear relation :attr:`om.ElectricalLine.electrical_flow[n,t]` .. math:: flow(n, o, p, t) = 1 / reactance(n, t) \cdot voltage\_angle(i(n), t) - voltage\_angle(o(n), t), \\ \forall p, t \in \textrm{TIMEINDEX}, \\ \forall n \in \textrm{ELECTRICAL\_LINES}. TODO: Add equate constraint of flows **The following variable are created:** TODO: Add voltage angle variable TODO: Add fix slack bus voltage angle to zero constraint / bound TODO: Add tests """ CONSTRAINT_GROUP = True def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def _create(self, group=None): """Creates the linear constraint for the class:`ElectricalLine` block. Parameters ---------- group : list List of oemof.solph.ElectricalLine (eline) objects for which the linear relation of inputs and outputs is created e.g. group = [eline1, eline2, ...]. The components inside the list need to hold a attribute `reactance` of type Sequence containing the reactance of the line. """ if group is None: return None m = self.parent_block() # create voltage angle variables self.ELECTRICAL_BUSES = Set( initialize=[n for n in m.es.nodes if isinstance(n, ElectricalBus)] ) def _voltage_angle_bounds(block, b, t): return b.v_min, b.v_max self.voltage_angle = Var( self.ELECTRICAL_BUSES, m.TIMESTEPS, bounds=_voltage_angle_bounds ) if True not in [b.slack for b in self.ELECTRICAL_BUSES]: # TODO: Make this robust to select the same slack bus for # the same problems bus = [b for b in self.ELECTRICAL_BUSES][0] logging.info( "No slack bus set,setting bus {0} as slack bus".format( bus.label ) ) bus.slack = True def _voltage_angle_relation(block): for p, t in m.TIMEINDEX: for n in group: if n.input.slack is True: self.voltage_angle[n.output, t].value = 0 self.voltage_angle[n.output, t].fix() try: lhs = m.flow[n.input, n.output, p, t] rhs = ( 1 / n.reactance[t] * ( self.voltage_angle[n.input, t] - self.voltage_angle[n.output, t] ) ) except ValueError: raise ValueError( "Error in constraint creation", "of node {}".format(n.label), ) block.electrical_flow.add((n, p, t), (lhs == rhs)) self.electrical_flow = Constraint(group, m.TIMEINDEX, noruleinit=True) self.electrical_flow_build = BuildAction(rule=_voltage_angle_relation)