Source code for monee.model.formulation.miqcqp.convex.gas
r"""Epigraph-relaxed Weymouth gas formulation: a convex MIQCQP (MISOCP-shaped).
Weymouth is linear in pressure-squared space; the only quadratics are the
convex epigraph relaxations :math:`m \cdot m \le m_{sq}`, kept tight by a small :math:`\varepsilon` objective
term, plus the ``direction``/``on_off`` binaries.
"""
import monee.model.phys.core.hydraulics as hydraulicsmodel
import monee.model.phys.nonlinear.gf as ogfmodel
from monee.model.core import Const
from ...core import BranchFormulation
[docs]
class RelaxedWeymouthBranchFormulation(BranchFormulation):
EPIGRAPH_TIGHTENING_EPS = 1e-5
[docs]
def ensure_var(self, model, simulation=False, grid=None):
f_const = hydraulicsmodel.friction_at_high_re(
model.diameter_m, model.roughness_m
)
model.friction = Const(f_const)
model.reynolds_scaled = Const(0.0)
[docs]
def minimize(self, branch, grid, from_node_model, to_node_model, **kwargs):
return [
self.EPIGRAPH_TIGHTENING_EPS
* (branch.mass_flow_pos_kgs_squared + branch.mass_flow_neg_kgs_squared)
]
def _epigraph_eqs(self, branch):
r"""Convex epigraph relaxation :math:`m^2 \le m_{sq}`; the exact sibling
overrides this with the equality form."""
return [
branch.mass_flow_pos_kgs * branch.mass_flow_pos_kgs
<= branch.mass_flow_pos_kgs_squared,
branch.mass_flow_neg_kgs * branch.mass_flow_neg_kgs
<= branch.mass_flow_neg_kgs_squared,
]
[docs]
def equations(self, branch, grid, from_node_model, to_node_model, **kwargs):
branch._pipe_area = hydraulicsmodel.calc_pipe_area(branch.diameter_m)
# linearize sqrt(p) around nominal pressure
p0 = grid.nominal_pressure_pu
x0 = p0**2
p_from = p0 + (1 / (2 * p0)) * (
from_node_model.vars["pressure_squared_pu"] - x0
)
p_to = p0 + (1 / (2 * p0)) * (to_node_model.vars["pressure_squared_pu"] - x0)
p_avg = 0.5 * (p_from + p_to)
p_amb = getattr(grid, "pressure_ambient_pa", 0.0)
gas_density_kg_per_m3 = ogfmodel.reference_gas_density(grid)
f_max_local = min(
grid.max_mass_flow_kgs,
hydraulicsmodel.calc_max_mass_flow(
branch.diameter_m,
gas_density_kg_per_m3,
getattr(grid, "v_max_mps", 20.0),
),
)
return self._epigraph_eqs(branch) + [
branch.mass_flow_pos_kgs_squared <= f_max_local**2 * branch.direction,
branch.mass_flow_neg_kgs_squared <= f_max_local**2 * (1 - branch.direction),
branch.mass_flow_pos_kgs_squared <= f_max_local**2 * branch.on_off,
branch.mass_flow_neg_kgs_squared <= f_max_local**2 * branch.on_off,
ogfmodel.pipe_weymouth(
# ABSOLUTE squared pressure (Pa^2). In gauge mode the offset uses
# the LINEARIZED pressure p_from/p_to (affine in pressure_squared_pu,
# the same linearization used for the density above) so the
# constraint stays conic; a no-op when the grid's ambient is 0.
p_squared_i=ogfmodel.abs_psq_pu(
from_node_model.vars["pressure_squared_pu"],
p_from,
p_amb,
grid.pressure_ref_pa,
)
* grid.pressure_ref_pa**2,
p_squared_j=ogfmodel.abs_psq_pu(
to_node_model.vars["pressure_squared_pu"],
p_to,
p_amb,
grid.pressure_ref_pa,
)
* grid.pressure_ref_pa**2,
f_a_pos_sq=branch.mass_flow_pos_kgs_squared,
f_a_neg_sq=branch.mass_flow_neg_kgs_squared,
diameter_m=branch.diameter_m,
length_m=branch.length_m,
t_k=grid.t_k,
compressibility=grid.compressibility,
on_off=branch.on_off,
friction=branch.friction,
r_specific=grid.universal_gas_constant / grid.molar_mass,
**kwargs,
),
branch.gas_density_kg_per_m3
== (grid.pressure_ref_pa * p_avg + p_amb)
* grid.molar_mass
/ (grid.universal_gas_constant * grid.t_k),
]