Coverage for src/distopf/matrix_models/lindist_loads.py: 94%
47 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-11-13 17:34 -0800
« prev ^ index » next coverage.py v7.10.6, created at 2025-11-13 17:34 -0800
1from typing import Optional
3import pandas as pd
4import distopf as opf
5from distopf.matrix_models.base import LinDistBase
8class LinDistModelL(LinDistBase):
9 """
10 LinDistFlow Model for linear power flow modeling which includes active and
11 reactive load powers as variables. This may be useful starting point if
12 custom load models need to be added. The disadvantage is that significantly
13 more variables are included which will increase computation time.
15 This class represents a linearized distribution model used for calculating
16 power flows, voltages, and other system properties in a distribution network
17 using the linearized branch-flow formulation from [1]. The model is composed of several power system components
18 such as buses, branches, generators, capacitors, and regulators.
20 Parameters
21 ----------
22 branch_data : pd.DataFrame
23 DataFrame containing branch data including resistance and reactance values and limits.
24 bus_data : pd.DataFrame
25 DataFrame containing bus data such as loads, voltages, and limits.
26 gen_data : pd.DataFrame
27 DataFrame containing generator data.
28 cap_data : pd.DataFrame
29 DataFrame containing capacitor data.
30 reg_data : pd.DataFrame
31 DataFrame containing regulator data.
33 References
34 ----------
35 [1] R. R. Jha, A. Dubey, C.-C. Liu, and K. P. Schneider,
36 “Bi-Level Volt-VAR Optimization to Coordinate Smart Inverters
37 With Voltage Control Devices,”
38 IEEE Trans. Power Syst., vol. 34, no. 3, pp. 1801–1813,
39 May 2019, doi: 10.1109/TPWRS.2018.2890613.
41 Examples
42 --------
43 This example demonstrates how to set up and solve a linear distribution flow model
44 using a provided case, and visualize the results.
46 >>> import distopf as opf
47 >>> # Prepare the case data
48 >>> case = opf.DistOPFCase(data_path="ieee123_30der")
49 >>> # Initialize the LinDistModel
50 >>> model = LinDistModelL(
51 ... branch_data=case.branch_data,
52 ... bus_data=case.bus_data,
53 ... gen_data=case.gen_data,
54 ... cap_data=case.cap_data,
55 ... reg_data=case.reg_data,
56 ... )
57 >>> # Solve the model using the specified objective function
58 >>> result = opf.lp_solve(model, opf.gradient_load_min(model))
59 >>> # Extract and plot results
60 >>> v = model.get_voltages(result.x)
61 >>> s = model.get_apparent_power_flows(result.x)
62 >>> p_gens = model.get_p_gens(result.x)
63 >>> q_gens = model.get_q_gens(result.x)
64 >>> # Visualize network and power flows
65 >>> opf.plot_network(model, v=v, s=s, p_gen=p_gens, q_gen=q_gens).show()
66 >>> opf.plot_voltages(v).show()
67 >>> opf.plot_power_flows(s).show()
68 >>> opf.plot_gens(p_gens, q_gens).show()
69 """
71 def __init__(
72 self,
73 branch_data: Optional[pd.DataFrame] = None,
74 bus_data: Optional[pd.DataFrame] = None,
75 gen_data: Optional[pd.DataFrame] = None,
76 cap_data: Optional[pd.DataFrame] = None,
77 reg_data: Optional[pd.DataFrame] = None,
78 ):
79 super().__init__(branch_data, bus_data, gen_data, cap_data, reg_data)
80 self.build()
82 def initialize_variable_index_pointers(self):
83 self.x_maps, self.n_x = self._variable_tables(self.branch)
84 self.v_map, self.n_x = self._add_device_variables(self.n_x, self.all_buses)
85 self.pg_map, self.n_x = self._add_device_variables(self.n_x, self.gen_buses)
86 self.qg_map, self.n_x = self._add_device_variables(self.n_x, self.gen_buses)
87 self.qc_map, self.n_x = self._add_device_variables(self.n_x, self.cap_buses)
88 self.vx_map, self.n_x = self._add_device_variables(self.n_x, self.reg_buses)
89 self.pl_map, self.n_x = self._add_device_variables(self.n_x, self.load_buses)
90 self.ql_map, self.n_x = self._add_device_variables(self.n_x, self.load_buses)
92 def additional_variable_idx(self, var, node_j, phase):
93 """
94 User added index function. Override this function to add custom variables. Return None if `var` is not found.
95 Parameters
96 ----------
97 var : name of variable
98 node_j : node index (0 based; bus.id - 1)
99 phase : "a", "b", or "c"
101 Returns
102 -------
103 ix : index or list of indices of variable within x-vector or None if `var` is not found.
104 """
105 if var in ["pl", "p_load"]: # reactive power load at node
106 return self.pl_map[phase].get(node_j, [])
107 if var in ["ql", "q_load"]: # reactive power injection by capacitor
108 return self.ql_map[phase].get(node_j, [])
109 return None
111 def add_load_model(self, a_eq, b_eq, j, phase):
112 pij = self.idx("pij", j, phase)
113 qij = self.idx("qij", j, phase)
114 pl = self.idx("pl", j, phase)
115 ql = self.idx("ql", j, phase)
116 vj = self.idx("v", j, phase)
117 p_load_nom, q_load_nom = 0, 0
118 a_eq[pij, pl] = -1 # add load variable to power flow equation
119 a_eq[qij, ql] = -1 # add load variable to power flow equation
120 if self.bus.bus_type[j] == opf.PQ_BUS:
121 p_load_nom = self.bus[f"pl_{phase}"][j]
122 q_load_nom = self.bus[f"ql_{phase}"][j]
123 if self.bus.bus_type[j] != opf.PQ_FREE:
124 # Set Load equation variable coefficients in a_eq
125 a_eq[pl, pl] = 1
126 a_eq[pl, vj] = -(self.bus.cvr_p[j] / 2) * p_load_nom
127 b_eq[pl] = (1 - (self.bus.cvr_p[j] / 2)) * p_load_nom
129 a_eq[ql, ql] = 1
130 a_eq[ql, vj] = -(self.bus.cvr_q[j] / 2) * q_load_nom
131 b_eq[ql] = (1 - (self.bus.cvr_q[j] / 2)) * q_load_nom
132 return a_eq, b_eq
134 def get_p_loads(self, x):
135 return self.get_device_variables(x, self.pl_map)
137 def get_q_loads(self, x):
138 return self.get_device_variables(x, self.ql_map)