Barycentric method Loss Calculation from delunary tringles
AnsweredTriangular File Explanation:
The triangular file consists of multiple triangles generated through Delaunay triangulation, partitioning the state-of-charge (SOC) and power space into smaller segments. Each triangle is defined by three vertices, each containing:
- State-of-charge (SOC, in %)
- Power (in kW)
- Corresponding losses (in kW)
These triangles allow efficient and accurate piecewise-linear interpolation for battery losses, essential for optimization accuracy.
Barycentric Coordinates Function Explanation:
Barycentric coordinates (a
, b
, c
) are used to interpolate within each triangle. They represent weights for each vertex, calculated as follows:
def barycentric_coordinates(self, x1, y1, x2, y2, x3, y3, x, y):
v0x, v0y = x2 - x1, y2 - y1
v1x, v1y = x3 - x1, y3 - y1
denom = v0x * v1y - v1x * v0y
inv_denom = 1.0 / denom
a = (v1y * (x - x1) + v0x * (y - y1)) * inv_denom
b = (v0y * (x - x1) + v1x * (y - y1)) * inv_denom
c = 1.0 - a - b
return a, b, c
Model Implementation for Charging and Discharging:
The implementation ensures accurate loss calculations using barycentric coordinates within selected triangles.
M = 100 # Big-M method constant for constraint relaxation
for period in range(self.SimPeriods):
soc_rfb = (e_rfb[period] / self.RFBCapacity) * 100 # SOC percentage
# === Charging Mode ===
charging_loss_expr = gp.LinExpr()
# Iterate through charging triangles to calculate losses
for i, tri in enumerate(self.charge_triangles):
v1, v2, v3 = tri["vertices"]
x1, y1, z1 = v1
x2, y2, z2 = v2
x3, y3, z3 = v3
# Calculate barycentric coordinates
a, b, c = self.barycentric_coordinates(x1, y1, x2, y2, x3, y3, soc_rfb, p_to_rfb[period])
# Binary variables for checking validity of coordinates
valid_a_ch = self.Problem.addVar(vtype=gp.GRB.BINARY, name=f"valid_a_ch_{period}_{i}")
valid_b_ch = self.Problem.addVar(vtype=gp.GRB.BINARY, name=f"valid_b_ch_{period}_{i}")
valid_c_ch = self.Problem.addVar(vtype=gp.GRB.BINARY, name=f"valid_c_ch_{period}_{i}")
# Constraints to ensure coordinates are within valid range [0,1]
self.Problem.addConstr(a >= 0 - M * (1 - valid_a_ch))
self.Problem.addConstr(a <= 1 + M * valid_a_ch)
self.Problem.addConstr(b >= 0 - M * (1 - valid_b_ch))
self.Problem.addConstr(b <= 1 + M * valid_b_ch)
self.Problem.addConstr(c >= 0 - M * (1 - valid_c_ch))
self.Problem.addConstr(c <= 1 + M * valid_c_ch)
# Activate delta_ch if triangle conditions are valid
self.Problem.addConstr(delta_ch[period, i] <= valid_a_ch)
self.Problem.addConstr(delta_ch[period, i] <= valid_b_ch)
self.Problem.addConstr(delta_ch[period, i] <= valid_c_ch)
self.Problem.addConstr(delta_ch[period, i] >= valid_a_ch + valid_b_ch + valid_c_ch - 2)
# Compute loss expression if triangle is selected
charging_loss_expr = delta_ch[period, i] * (a * z1 + b * z2 + c * z3)
# Constraint ensuring exactly one triangle is selected per period for charging
self.Problem.addConstr(p_aux_losscharging_rfb_ch[period] == charging_loss_expr)
# === Discharging Mode ===
discharging_loss_expr = gp.LinExpr()
for i, tri in enumerate(self.discharge_triangles):
v1, v2, v3 = tri["vertices"]
x1, y1, z1 = v1
x2, y2, z2 = v2
x3, y3, z3 = v3
# Calculate barycentric coordinates
a, b, c = self.barycentric_coordinates(x1, y1, x2, y2, x3, y3, soc_rfb, p_from_rfb[period])
valid_a_dch = self.Problem.addVar(vtype=gp.GRB.BINARY, name=f"valid_a_dch_{period}_{i}")
valid_b_dch = self.Problem.addVar(vtype=gp.GRB.BINARY, name=f"valid_b_dch_{period}_{i}")
valid_c_dch = self.Problem.addVar(vtype=gp.GRB.BINARY, name=f"valid_c_dch_{period}_{i}")
self.Problem.addConstr(a >= 0 - M * (1 - valid_a_dch))
self.Problem.addConstr(a <= 1 + M * valid_a_dch)
self.Problem.addConstr(b >= 0 - M * (1 - valid_b_dch))
self.Problem.addConstr(b <= 1 + M * valid_b_dch)
self.Problem.addConstr(c >= 0 - M * (1 - valid_c_dch))
self.Problem.addConstr(c <= 1 + M * valid_c_dch)
self.Problem.addConstr(delta_dch[period, i] <= valid_a_dch)
self.Problem.addConstr(delta_dch[period, i] <= valid_b_dch)
self.Problem.addConstr(delta_dch[period, i] <= valid_c_dch)
self.Problem.addConstr(delta_dch[period, i] >= valid_a_dch + valid_b_dch + valid_c_dch - 2)
discharging_loss_expr = delta_dch[period, i] * (a * z1 + b * z2 + c * z3)
self.Problem.addConstr(p_aux_lossdischarging_rfb_dch[period] == discharging_loss_expr)
Ensuring Single Triangle Selection:
To guarantee the selection of exactly one triangle, add:
self.Problem.addConstr(gp.quicksum(delta_ch[period, i] for i in range(num_triangles)) == 1)
self.Problem.addConstr(gp.quicksum(delta_dch[period, i] for i in range(num_triangles)) == 1)
This ensures the solver selects only the optimal triangle, effectively ignoring others.
-
Hi Parshwa,
It seems you forgot to add your question.
- Riley
0 -
Triangular File Explanation:
The triangular file consists of multiple triangles generated through Delaunay triangulation, partitioning the state-of-charge (SOC) and power space into smaller segments. Each triangle is defined by three vertices, each containing:
- State-of-charge (SOC, in %)
- Power (in kW)
- Corresponding losses (in kW)
These triangles allow efficient and accurate piecewise-linear interpolation for battery losses, essential for optimization accuracy.
Barycentric Coordinates Function Explanation:
Barycentric coordinates (
a
,b
,c
) are used to interpolate within each triangle. They represent weights for each vertex, calculated as follows:def barycentric_coordinates(self, x1, y1, x2, y2, x3, y3, x, y): v0x, v0y = x2 - x1, y2 - y1 v1x, v1y = x3 - x1, y3 - y1 denom = v0x * v1y - v1x * v0y inv_denom = 1.0 / denom a = (v1y * (x - x1) + v0x * (y - y1)) * inv_denom b = (v0y * (x - x1) + v1x * (y - y1)) * inv_denom c = 1.0 - a - b return a, b, c
Model Implementation for Charging and Discharging:
The implementation ensures accurate loss calculations using barycentric coordinates within selected triangles.
M = 100 # Big-M method constant for constraint relaxation for period in range(self.SimPeriods): soc_rfb = (e_rfb[period] / self.RFBCapacity) * 100 # SOC percentage # === Charging Mode === charging_loss_expr = gp.LinExpr() # Iterate through charging triangles to calculate losses for i, tri in enumerate(self.charge_triangles): v1, v2, v3 = tri["vertices"] x1, y1, z1 = v1 x2, y2, z2 = v2 x3, y3, z3 = v3 # Calculate barycentric coordinates a, b, c = self.barycentric_coordinates(x1, y1, x2, y2, x3, y3, soc_rfb, p_to_rfb[period]) # Binary variables for checking validity of coordinates valid_a_ch = self.Problem.addVar(vtype=gp.GRB.BINARY, name=f"valid_a_ch_{period}_{i}") valid_b_ch = self.Problem.addVar(vtype=gp.GRB.BINARY, name=f"valid_b_ch_{period}_{i}") valid_c_ch = self.Problem.addVar(vtype=gp.GRB.BINARY, name=f"valid_c_ch_{period}_{i}") # Constraints to ensure coordinates are within valid range [0,1] self.Problem.addConstr(a >= 0 - M * (1 - valid_a_ch)) self.Problem.addConstr(a <= 1 + M * valid_a_ch) self.Problem.addConstr(b >= 0 - M * (1 - valid_b_ch)) self.Problem.addConstr(b <= 1 + M * valid_b_ch) self.Problem.addConstr(c >= 0 - M * (1 - valid_c_ch)) self.Problem.addConstr(c <= 1 + M * valid_c_ch) # Activate delta_ch if triangle conditions are valid self.Problem.addConstr(delta_ch[period, i] <= valid_a_ch) self.Problem.addConstr(delta_ch[period, i] <= valid_b_ch) self.Problem.addConstr(delta_ch[period, i] <= valid_c_ch) self.Problem.addConstr(delta_ch[period, i] >= valid_a_ch + valid_b_ch + valid_c_ch - 2) # Compute loss expression if triangle is selected charging_loss_expr = delta_ch[period, i] * (a * z1 + b * z2 + c * z3) # Constraint ensuring exactly one triangle is selected per period for charging self.Problem.addConstr(p_aux_losscharging_rfb_ch[period] == charging_loss_expr) # === Discharging Mode === discharging_loss_expr = gp.LinExpr() for i, tri in enumerate(self.discharge_triangles): v1, v2, v3 = tri["vertices"] x1, y1, z1 = v1 x2, y2, z2 = v2 x3, y3, z3 = v3 # Calculate barycentric coordinates a, b, c = self.barycentric_coordinates(x1, y1, x2, y2, x3, y3, soc_rfb, p_from_rfb[period]) valid_a_dch = self.Problem.addVar(vtype=gp.GRB.BINARY, name=f"valid_a_dch_{period}_{i}") valid_b_dch = self.Problem.addVar(vtype=gp.GRB.BINARY, name=f"valid_b_dch_{period}_{i}") valid_c_dch = self.Problem.addVar(vtype=gp.GRB.BINARY, name=f"valid_c_dch_{period}_{i}") self.Problem.addConstr(a >= 0 - M * (1 - valid_a_dch)) self.Problem.addConstr(a <= 1 + M * valid_a_dch) self.Problem.addConstr(b >= 0 - M * (1 - valid_b_dch)) self.Problem.addConstr(b <= 1 + M * valid_b_dch) self.Problem.addConstr(c >= 0 - M * (1 - valid_c_dch)) self.Problem.addConstr(c <= 1 + M * valid_c_dch) self.Problem.addConstr(delta_dch[period, i] <= valid_a_dch) self.Problem.addConstr(delta_dch[period, i] <= valid_b_dch) self.Problem.addConstr(delta_dch[period, i] <= valid_c_dch) self.Problem.addConstr(delta_dch[period, i] >= valid_a_dch + valid_b_dch + valid_c_dch - 2) discharging_loss_expr = delta_dch[period, i] * (a * z1 + b * z2 + c * z3) self.Problem.addConstr(p_aux_lossdischarging_rfb_dch[period] == discharging_loss_expr)
Ensuring Single Triangle Selection:
To guarantee the selection of exactly one triangle, add:
self.Problem.addConstr(gp.quicksum(delta_ch[period, i] for i in range(num_triangles)) == 1) self.Problem.addConstr(gp.quicksum(delta_dch[period, i] for i in range(num_triangles)) == 1)
This ensures the solver selects only the optimal triangle, effectively ignoring others.
"I'm solving an optimization problem to estimate auxiliary losses in a Redox Flow Battery (RFB) using measured SOC-Power data. The losses depend on SOC and Power, so I applied Delaunay triangulation to divide the SOC-Power space into triangles, where each vertex has a known loss value. Using barycentric interpolation, I estimate losses for a given SOC and Power and integrate this into a Mixed Integer Linear Programming (MILP) model.
The key steps in my approach:
Binary variables select the triangle containing the (SOC, Power) point. Barycentric weights (a, b, c) are computed. The loss is interpolated as: Loss = a * Z1 + b * Z2 + c * Z3 where Z1, Z2, Z3 are the loss values at triangle vertices. The MILP model minimizes total cost, including these losses. Issue:
The optimizer sometimes fails to correctly assign (SOC, Power) to the right triangle, leading to incorrect loss calculations. The barycentric weight constraints (a, b, c) might not be enforced properly in MILP. Big-M constraints could be causing numerical instability. I need guidance on how to properly integrate Delaunay triangulation into MILP, ensuring correct triangle selection and loss calculation. i want to know that the way i have implented constrin for barycentric that is right or not as i am getting error and zero losses ?
0
Please sign in to leave a comment.
Comments
2 comments