Source code for monee.model.multi

from .branch import HeatExchanger
from .child import PowerGenerator, PowerLoad, Sink
from .core import (
    Intermediate,
    MultiGridBranchModel,
    MultiGridCompoundModel,
    MultiGridNodeModel,
    Node,
    Var,
    model,
)
from .grid import GasGrid, PowerGrid, WaterGrid
from .network import Network
from .node import Bus, Junction
from .phys.core.hydraulics import junction_mass_flow_balance
from .phys.nonlinear.ac import power_balance_equation


class MutableFloat(float):
    """
    No docstring provided.
    """

    def __init__(self, val):
        self._val = val

    def __int__(self):
        """
        No docstring provided.
        """
        return self._val

    def __index__(self):
        """
        No docstring provided.
        """
        return self._val

    def __str__(self):
        """
        No docstring provided.
        """
        return str(self._val)

    def __repr__(self):
        """
        No docstring provided.
        """
        return repr(self._val)

    def set(self, val):
        """
        No docstring provided.
        """
        self._val = val


[docs] @model class GenericTransferBranch(MultiGridBranchModel): """ No docstring provided. """ def __init__(self, loss=0, **kwargs) -> None: super().__init__(**kwargs) self._mass_flow_pos = Var(1, min=0, name="mass_flow_pos") self._mass_flow_neg = Var(1, min=0, name="mass_flow_neg") self.on_off = 1 self._p_mw = Var(1) self._q_mvar = Var(1) self._t_from_pu = Var(350) self._t_to_pu = Var(350) self._loss = loss
[docs] def loss_percent(self): """ No docstring provided. """ return self._loss
[docs] def is_cp(self): """ No docstring provided. """ return False
[docs] def init(self, grids): """ No docstring provided. """ if type(grids) is WaterGrid or (type(grids) is dict and WaterGrid in grids): self.mass_flow_pos = self._mass_flow_pos self.mass_flow_neg = self._mass_flow_neg self.t_from_pu = self._t_from_pu self.t_to_pu = self._t_to_pu if type(grids) is GasGrid or (type(grids) is dict and GasGrid in grids): self.mass_flow_pos = self._mass_flow_pos self.mass_flow_neg = self._mass_flow_neg self.gas_mass_flow = self.mass_flow_pos if type(grids) is PowerGrid or (type(grids) is dict and PowerGrid in grids): self.p_to_mw = self._p_mw self.p_from_mw = self._p_mw self.q_to_mvar = self._q_mvar self.q_from_mvar = self._q_mvar
[docs] def equations(self, grids, from_node_model, to_node_model, **kwargs): eqs = [] if type(grids) is WaterGrid or (type(grids) is dict and WaterGrid in grids): eqs += [self.t_from_pu == self.t_to_pu] eqs += [self.t_from_pu == from_node_model.t_pu] eqs += [to_node_model.t_pu == self.t_to_pu] eqs += [to_node_model.t_pu == from_node_model.t_pu] eqs += [from_node_model.pressure_pu == to_node_model.pressure_pu] if type(grids) is GasGrid or (type(grids) is dict and GasGrid in grids): pass return eqs
[docs] @model class GasToHeatControlNode(MultiGridNodeModel, Junction): """ No docstring provided. """ def __init__( self, heat_gen_w, efficiency_heat, hhv, regulation=1, **kwargs ) -> None: super().__init__(**kwargs) self.efficiency_heat = efficiency_heat self._hhv = hhv self.regulation = regulation self.gas_kgps = Var(1) self.heat_w = heat_gen_w self.t_k = Var(350) self.t_pu = Var(1) self.pressure_pa = Var(1000000) self.pressure_pu = Var(1)
[docs] def equations(self, grid, from_branch_models, to_branch_models, childs, **kwargs): """ No docstring provided. """ heat_to_branches = [ branch for branch in to_branch_models if "t_from_pu" in branch.vars or type(branch) is SubHE ] heat_from_branches = [ branch for branch in from_branch_models if "t_from_pu" in branch.vars or type(branch) is SubHE ] gas_to_branches = [ branch for branch in to_branch_models if "gas_mass_flow" in branch.vars ] gas_eqs = self.calc_signed_mass_flow([], gas_to_branches, [Sink(self.gas_kgps)]) heat_eqs = self.calc_signed_mass_flow(heat_from_branches, heat_to_branches, []) heat_energy_eqs = self.calc_signed_heat_flow( heat_from_branches, heat_to_branches, [], None ) return [ junction_mass_flow_balance(heat_eqs), junction_mass_flow_balance(heat_energy_eqs), junction_mass_flow_balance(gas_eqs), [branch for branch in heat_from_branches if type(branch) is SubHE][0].q_w / 1000000 == -self.efficiency_heat * self.gas_kgps * self.regulation * (3.6 * self._hhv), self.heat_w == [branch for branch in heat_from_branches if type(branch) is SubHE][ 0 ].q_w, self.t_pu == self.t_k / grid[0].t_ref, self.pressure_pu == self.pressure_pa / grid[0].pressure_ref, ]
[docs] @model class PowerToHeatControlNode(MultiGridNodeModel, Junction, Bus): """ No docstring provided. """ def __init__( self, load_p_mw, load_q_mvar, heat_energy_w, efficiency, **kwargs ) -> None: super().__init__(**kwargs) self.load_q_mvar = load_q_mvar self.efficiency = efficiency self.el_mw = load_p_mw self.heat_w = heat_energy_w self.t_k = Intermediate(1) self.t_pu = Var(1, min=0, max=2, name="t_pu") self.pressure_squared_pu = Var(1, min=0, max=3, name="p_squared_pu") self.pressure_pu = Var(1, min=0, max=3, name="p_pu")
[docs] def equations(self, grid, from_branch_models, to_branch_models, childs, **kwargs): """ No docstring provided. """ heat_to_branches = [ branch for branch in to_branch_models if "t_from_pu" in branch.vars or type(branch) is SubHE ] heat_from_branches = [ branch for branch in from_branch_models if "t_from_pu" in branch.vars or type(branch) is SubHE ] power_to_branches = [ branch for branch in to_branch_models if "p_from_mw" in branch.vars ] power_eqs = self.calc_signed_power_values( [], power_to_branches, [PowerLoad(self.el_mw, self.load_q_mvar)] ) heat_eqs = self.calc_signed_mass_flow(heat_from_branches, heat_to_branches, []) heat_energy_eqs = self.calc_signed_heat_flow( heat_from_branches, heat_to_branches, [], None ) return [ junction_mass_flow_balance(heat_eqs), junction_mass_flow_balance(heat_energy_eqs), [branch for branch in heat_to_branches if type(branch) is SubHE][0].q_w == -self.heat_w, sum(power_eqs[0]) == 0, sum(power_eqs[1]) == 0, self.heat_w == self.efficiency * self.el_mw * 1000000, ]
class SubHE(HeatExchanger): """ Represents a subordinate or auxiliary heat exchanger within a multi-energy network model. SubHE is a specialized subclass of HeatExchanger used to model secondary or supporting heat exchange processes, such as those found in combined heat and power (CHP), power-to-heat, or gas-to-heat conversion systems. This class is typically used as a building block in compound models where additional heat transfer elements are required to accurately represent energy flows and balances. Example: sub_he = SubHE(q_mw=-1000, diameter_m=0.3) # Integrate sub_he into a network branch or compound system Attributes: Inherits all attributes from HeatExchanger, such as heat transfer rate, diameter, and temperature settings. """
[docs] @model class CHPControlNode(MultiGridNodeModel, Junction, Bus): """ Represents a control node for combined heat and power (CHP) systems, managing energy and mass balances across power, heat, and gas domains. CHPControlNode extends MultiGridNodeModel, Junction, and Bus to provide a unified interface for modeling the operational logic and constraints of a CHP unit within a multi-energy network. It tracks key parameters such as fuel mass flow, electrical and thermal efficiencies, and regulation factors, and exposes variables for integration with network branches. Use this class when simulating or optimizing CHP systems that require explicit coupling between gas, heat, and electrical grids. Example:: chp_node = CHPControlNode( mass_flow_capacity=1.2, efficiency_power=0.35, efficiency_heat=0.5, hhv=42.5e6, q_mvar=0, regulation=1 ) # Integrate chp_node into a network and call chp_node.equations(...) during simulation Parameters: mass_flow_capacity: Maximum or setpoint mass flow of fuel (e.g., gas) supplied to the CHP unit. efficiency_power: Electrical efficiency (fraction, 0 < value ≤ 1). efficiency_heat: Thermal efficiency (fraction, 0 < value ≤ 1). hhv: Higher heating value of the fuel (J/kg). q_mvar (optional): Reactive power setpoint for the generator. Defaults to 0. regulation (optional): Regulation factor for operational flexibility. Defaults to 1. **kwargs: Additional keyword arguments for parent class initialization. Attributes: mass_flow_capacity: Fuel mass flow capacity or setpoint. efficiency_power: Electrical efficiency. efficiency_heat: Thermal efficiency. gen_q_mvar: Reactive power setpoint. _hhv: Higher heating value of the fuel. regulation: Regulation factor. _gen_p_mw: Electrical power generation variable. heat_gen_w: Thermal power generation variable. el_gen_mw: Electrical power generation variable (duplicate for unified interface). el_mw: Unified electrical power variable. gas_kgps: Unified gas mass flow variable. heat_w: Unified heat power variable. t_k: Node temperature (K). t_pu: Node temperature (per unit). pressure_pa: Node pressure (Pa). pressure_pu: Node pressure (per unit). Methods: equations(grid, from_branch_models, to_branch_models, childs, **kwargs): Defines the system of equations for the CHP node, including mass and energy balances, power and heat conversion, and normalization constraints. """ def __init__( self, mass_flow_capacity, efficiency_power, efficiency_heat, hhv, q_mvar=0, regulation=1, **kwargs, ) -> None: super().__init__(**kwargs) self.efficiency_heat = efficiency_heat self.efficiency_power = efficiency_power self.gen_q_mvar = q_mvar self._hhv = hhv self.regulation = regulation self.el_mw = Var(-1) self.gas_kgps = mass_flow_capacity self.heat_w = Var(-1000) self.t_k = Var(350) self.t_pu = Var(1) self.pressure_pa = Var(1000000) self.pressure_pu = Var(1)
[docs] def equations(self, grid, from_branch_models, to_branch_models, childs, **kwargs): """ Defines the system of equations for a combined heat and power (CHP) control node, capturing energy and mass balances across power, heat, and gas domains. This method assembles the physical and operational constraints for a CHP node, including mass flow balances for heat and gas, power balance equations, and thermodynamic relationships for energy conversion. It integrates the effects of efficiency, regulation, and fuel properties, and links the node's internal variables to the connected branches. Use this method during network simulation or optimization to ensure the CHP node's behavior is accurately represented within the multi-energy system. Args: grid: List or collection of grid objects, where grid[1] is expected to be the heat grid for reference values. from_branch_models (list): Branch models representing flows entering the node. to_branch_models (list): Branch models representing flows leaving the node. childs (list): Child component models attached to the node. **kwargs: Additional keyword arguments for solver options or equation customization. Returns: tuple: A tuple of equations representing: - Heat mass flow balance at the node. - Heat energy flow balance at the node. - Gas mass flow balance at the node. - Power balance equations (active and reactive). - Heat exchanger energy conversion constraint. - Electrical power generation constraint. - Consistency between internal heat and electrical variables and branch flows. - Temperature and pressure normalization constraints. Raises: IndexError: If no SubHE branch is found in the heat_from_branches list. KeyError: If expected variables are missing from branch models. Examples: # Called automatically during network simulation: eqs = chp_control_node.equations(grid, from_branches, to_branches, childs) """ heat_to_branches = [ branch for branch in to_branch_models if "t_from_pu" in branch.vars or type(branch) is SubHE ] heat_from_branches = [ branch for branch in from_branch_models if "t_from_pu" in branch.vars or type(branch) is SubHE ] gas_to_branches = [ branch for branch in to_branch_models if "gas_mass_flow" in branch.vars ] power_from_branches = [ branch for branch in from_branch_models if "p_to_mw" in branch.vars ] power_eqs = self.calc_signed_power_values( power_from_branches, [], [PowerGenerator(self.el_mw, self.gen_q_mvar)] ) gas_eqs = self.calc_signed_mass_flow( [], gas_to_branches, [Sink(self.gas_kgps * self.regulation)] ) heat_eqs = self.calc_signed_mass_flow(heat_from_branches, heat_to_branches, []) heat_energy_eqs = self.calc_signed_heat_flow( heat_from_branches, heat_to_branches, [], None ) return [ junction_mass_flow_balance(heat_eqs), junction_mass_flow_balance(heat_energy_eqs), junction_mass_flow_balance(gas_eqs), power_balance_equation(power_eqs[0]), power_balance_equation(power_eqs[1]), [branch for branch in heat_from_branches if type(branch) is SubHE][0].q_w == -self.efficiency_heat * self.gas_kgps * self.regulation * 1000000 * (3.6 * self._hhv), self.el_mw == -self.efficiency_power * self.gas_kgps * self.regulation * (3.6 * self._hhv), self.heat_w == [branch for branch in heat_from_branches if type(branch) is SubHE][ 0 ].q_w, self.t_k == self.t_pu * grid[1].t_ref, self.pressure_pu == self.pressure_pa / grid[1].pressure_ref, ]
[docs] @model class CHP(MultiGridCompoundModel): """ No docstring provided. """ def __init__( self, diameter_m: float, efficiency_power: float, efficiency_heat: float, mass_flow_setpoint: float, q_mvar_setpoint: float = 0, temperature_ext_k: float = 293, regulation=1, ) -> None: self.diameter_m = diameter_m self.temperature_ext_k = temperature_ext_k self.regulation = regulation self.efficiency_power = efficiency_power self.efficiency_heat = efficiency_heat self.mass_flow_setpoint = mass_flow_setpoint self.mass_flow = ( mass_flow_setpoint if type(mass_flow_setpoint) is Var else MutableFloat(mass_flow_setpoint) ) self.q_mvar = ( q_mvar_setpoint if type(q_mvar_setpoint) is Var else MutableFloat(q_mvar_setpoint) )
[docs] def create( self, network: Network, gas_node: Node, heat_node: Node, heat_return_node: Node, power_node: Node, ): """ No docstring provided. """ self._gas_grid = gas_node.grid self._control_node = CHPControlNode( self.mass_flow, self.efficiency_power, self.efficiency_heat, gas_node.grid.higher_heating_value, regulation=self.regulation, ) node_id_control = network.node( self._control_node, grid=[power_node.grid, heat_node.grid, gas_node.grid], position=power_node.position, ) network.branch(GenericTransferBranch(), gas_node.id, node_id_control) network.branch(GenericTransferBranch(), heat_node.id, node_id_control) network.branch( SubHE(Var(-1000), self.diameter_m), node_id_control, heat_return_node.id, grid=heat_return_node.grid, ) network.branch(GenericTransferBranch(), node_id_control, power_node.id)
[docs] @model class GasToHeat(MultiGridCompoundModel): """ No docstring provided. """ def __init__( self, heat_energy_w, diameter_m, temperature_ext_k, efficiency ) -> None: self.diameter_m = diameter_m self.temperature_ext_k = temperature_ext_k self.efficiency = efficiency self.heat_energy_w = MutableFloat(-heat_energy_w)
[docs] def create( self, network: Network, gas_node: Node, heat_node: Node, heat_return_node: Node ): """ No docstring provided. """ self._gas_grid = gas_node.grid node_id_control = network.node( GasToHeatControlNode( self.heat_energy_w, self.efficiency, gas_node.grid.higher_heating_value ), grid=[heat_node.grid, gas_node.grid], position=gas_node.position, ) network.branch(GenericTransferBranch(), gas_node.id, node_id_control) network.branch(GenericTransferBranch(), heat_node.id, node_id_control) network.branch( SubHE(Var(0.1), self.diameter_m), node_id_control, heat_return_node.id, grid=heat_return_node.grid, )
[docs] @model class PowerToHeat(MultiGridCompoundModel): """ No docstring provided. """ def __init__( self, heat_energy_w, diameter_m, temperature_ext_k, efficiency, q_mvar_setpoint=0, ) -> None: self.diameter_m = diameter_m self.temperature_ext_k = temperature_ext_k self.efficiency = efficiency self.heat_energy_w = ( heat_energy_w if type(heat_energy_w) is Var else MutableFloat(heat_energy_w) ) self.load_p_mw = Var(1) self.load_q_mvar = ( q_mvar_setpoint if type(q_mvar_setpoint) is Var else MutableFloat(q_mvar_setpoint) )
[docs] def set_active(self, activation_flag): """ No docstring provided. """ if activation_flag: self._control_node.heat_energy_w = self.heat_energy_w else: self._control_node.heat_energy_w = 0
[docs] def create( self, network: Network, power_node: Node, heat_node: Node, heat_return_node: Node, ): """ No docstring provided. """ self._control_node = PowerToHeatControlNode( self.load_p_mw, self.load_q_mvar, self.heat_energy_w, self.efficiency ) node_id_control = network.node( self._control_node, grid=[power_node.grid, heat_node.grid], position=power_node.position, ) network.branch(GenericTransferBranch(), power_node.id, node_id_control) network.branch(GenericTransferBranch(), node_id_control, heat_return_node.id) network.branch( SubHE(Var(0.1), self.diameter_m), heat_node.id, node_id_control, grid=heat_node.grid, )
[docs] @model class GasToPower(MultiGridBranchModel): """ No docstring provided. """ def __init__( self, efficiency, p_mw_setpoint, q_mvar_setpoint=0, regulation=1 ) -> None: super().__init__() self.efficiency = efficiency self.el_mw = -p_mw_setpoint self.gas_kgps = Var(1) self.on_off = 1 self.p_to_mw = Var(-p_mw_setpoint) self.q_to_mvar = -q_mvar_setpoint self.from_mass_flow = Var(1) self.regulation = regulation
[docs] def loss_percent(self): """ No docstring provided. """ return 1 - self.efficiency
[docs] def equations(self, grids, from_node_model, to_node_model, **kwargs): """ No docstring provided. """ return [ self.p_to_mw == self.regulation * self.el_mw, -self.p_to_mw == self.efficiency * self.from_mass_flow * (3.6 * grids[GasGrid].higher_heating_value), self.gas_kgps == self.from_mass_flow, ]
[docs] @model class PowerToGas(MultiGridBranchModel): """ No docstring provided. """ def __init__( self, efficiency, mass_flow_setpoint, consume_q_mvar_setpoint=0, regulation=1 ) -> None: super().__init__() self.efficiency = efficiency self.gas_kgps = -mass_flow_setpoint self.el_mw = Var(1.1) self.on_off = 1 self.p_from_mw = Var(1) self.q_from_mvar = consume_q_mvar_setpoint self.to_mass_flow = Var(self.gas_kgps) self.regulation = regulation
[docs] def loss_percent(self): """ No docstring provided. """ return 1 - self.efficiency
[docs] def equations(self, grids, from_node_model, to_node_model, **kwargs): """ No docstring provided. """ return [ self.to_mass_flow == -self.efficiency * self.p_from_mw * (1 / (grids[GasGrid].higher_heating_value * 3.6)), self.p_from_mw >= 0, self.p_from_mw == self.el_mw * self.regulation, self.gas_kgps == -self.efficiency * self.el_mw * (1 / (grids[GasGrid].higher_heating_value * 3.6)), ]