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 got calcualtion of loses as zero and whetaher my logic is right or not and the constrin i have put using bycentirc method Continue reading...