1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 """Scales fixed and/or dispatchable loads.
18 """
19
20 from sys import stderr
21
22 from numpy import array, zeros, arange, in1d, ix_
23 from numpy import flatnonzero as find
24
25 from scipy.sparse import csr_matrix as sparse
26
27 from isload import isload
28
29 from idx_bus import PD, QD, BUS_AREA, BUS_I
30 from idx_gen import PG, QG, QMAX, QMIN, GEN_BUS, GEN_STATUS, PMIN
31
32
33 -def scale_load(load, bus, gen=None, load_zone=None, opt=None):
34 """Scales fixed and/or dispatchable loads.
35
36 Assumes consecutive bus numbering when dealing with dispatchable loads.
37
38 @param load: Each element specifies the amount of scaling for the
39 corresponding load zone, either as a direct scale factor
40 or as a target quantity. If there are C{nz} load zones this
41 vector has C{nz} elements.
42 @param bus: Standard C{bus} matrix with C{nb} rows, where the fixed active
43 and reactive loads available for scaling are specified in
44 columns C{PD} and C{QD}
45 @param gen: (optional) standard C{gen} matrix with C{ng} rows, where the
46 dispatchable loads available for scaling are specified by
47 columns C{PG}, C{QG}, C{PMIN}, C{QMIN} and C{QMAX} (in rows for which
48 C{isload(gen)} returns C{true}). If C{gen} is empty, it assumes
49 there are no dispatchable loads.
50 @param load_zone: (optional) C{nb} element vector where the value of
51 each element is either zero or the index of the load zone
52 to which the corresponding bus belongs. If C{load_zone[b] = k}
53 then the loads at bus C{b} will be scaled according to the
54 value of C{load[k]}. If C{load_zone[b] = 0}, the loads at bus C{b}
55 will not be modified. If C{load_zone} is empty, the default is
56 determined by the dimensions of the C{load} vector. If C{load} is
57 a scalar, a single system-wide zone including all buses is
58 used, i.e. C{load_zone = ones(nb)}. If C{load} is a vector, the
59 default C{load_zone} is defined as the areas specified in the
60 C{bus} matrix, i.e. C{load_zone = bus[:, BUS_AREA]}, and C{load}
61 should have dimension C{= max(bus[:, BUS_AREA])}.
62 @param opt: (optional) dict with three possible fields, 'scale',
63 'pq' and 'which' that determine the behavior as follows:
64 - C{scale} (default is 'FACTOR')
65 - 'FACTOR' : C{load} consists of direct scale factors, where
66 C{load[k] =} scale factor C{R[k]} for zone C{k}
67 - 'QUANTITY' : C{load} consists of target quantities, where
68 C{load[k] =} desired total active load in MW for
69 zone C{k} after scaling by an appropriate C{R(k)}
70 - C{pq} (default is 'PQ')
71 - 'PQ' : scale both active and reactive loads
72 - 'P' : scale only active loads
73 - C{which} (default is 'BOTH' if GEN is provided, else 'FIXED')
74 - 'FIXED' : scale only fixed loads
75 - 'DISPATCHABLE' : scale only dispatchable loads
76 - 'BOTH' : scale both fixed and dispatchable loads
77
78 @see: L{total_load}
79
80 @author: Ray Zimmerman (PSERC Cornell)
81 @author: Richard Lincoln
82 """
83 nb = bus.shape[0]
84
85
86 bus = bus.copy()
87 if gen is None:
88 gen = array([])
89 else:
90 gen = gen.copy()
91 if load_zone is None:
92 load_zone = array([], int)
93 if opt is None:
94 opt = {}
95
96
97 if len(gen) == 0:
98 opt["which"] = 'FIXED'
99 if 'pq' not in opt:
100 opt["pq"] = 'PQ'
101 if 'which' not in opt:
102 opt["which"] = 'BOTH'
103 if 'scale' not in opt:
104 opt["scale"] = 'FACTOR'
105 if (opt["pq"] != 'P') and (opt["pq"] != 'PQ'):
106 stderr.write("scale_load: opt['pq'] must equal 'PQ' or 'P'\n")
107 if (opt["which"][0] != 'F') and (opt["which"][0] != 'D') and (opt["which"][0] != 'B'):
108 stderr.write("scale_load: opt.which should be 'FIXED, 'DISPATCHABLE or 'BOTH'\n")
109 if (opt["scale"][0] != 'F') and (opt["scale"][0] != 'Q'):
110 stderr.write("scale_load: opt.scale should be 'FACTOR or 'QUANTITY'\n")
111 if (len(gen) == 0) and (opt["which"][0] != 'F'):
112 stderr.write('scale_load: need gen matrix to scale dispatchable loads\n')
113
114
115 if len(gen) > 0:
116 ng = gen.shape[0]
117 is_ld = isload(gen) & (gen[:, GEN_STATUS] > 0)
118 ld = find(is_ld)
119
120
121 i2e = bus[:, BUS_I].astype(int)
122 e2i = zeros(max(i2e) + 1, int)
123 e2i[i2e] = arange(nb)
124
125 gbus = gen[:, GEN_BUS].astype(int)
126 Cld = sparse((is_ld, (e2i[gbus], arange(ng))), (nb, ng))
127 else:
128 ng = 0
129 ld = array([], int)
130
131 if len(load_zone) == 0:
132 if len(load) == 1:
133 load_zone = zeros(nb, int)
134 load_zone[bus[:, PD] != 0] = 1
135 if len(gen) > 0:
136 gbus = gen[ld, GEN_BUS].astype(int)
137 load_zone[e2i[gbus]] = 1
138 else:
139 load_zone = bus[:, BUS_AREA]
140
141
142 if max(load_zone) > len(load):
143 stderr.write('scale_load: load vector must have a value for each load zone specified\n')
144
145
146 scale = load.copy()
147 Pdd = zeros(nb)
148 if opt["scale"][0] == 'Q':
149
150 if len(gen) > 0:
151 Pdd = -Cld * gen[:, PMIN]
152
153
154 for k in range(len(load)):
155 idx = find(load_zone == k + 1)
156 fixed = sum(bus[idx, PD])
157 dispatchable = sum(Pdd[idx])
158 total = fixed + dispatchable
159 if opt["which"][0] == 'B':
160 if total != 0:
161 scale[k] = load[k] / total
162 elif load[k] == total:
163 scale[k] = 1
164 else:
165 raise ScalingError('scale_load: impossible to make zone %d load equal %g by scaling non-existent loads\n' % (k, load[k]))
166 elif opt["which"][0] == 'F':
167 if fixed != 0:
168 scale[k] = (load[k] - dispatchable) / fixed
169 elif load[k] == dispatchable:
170 scale[k] = 1
171 else:
172 raise ScalingError('scale_load: impossible to make zone %d load equal %g by scaling non-existent fixed load\n' % (k, load[k]))
173 elif opt["which"][0] == 'D':
174 if dispatchable != 0:
175 scale[k] = (load[k] - fixed) / dispatchable
176 elif load[k] == fixed:
177 scale[k] = 1
178 else:
179 raise ScalingError('scale_load: impossible to make zone %d load equal %g by scaling non-existent dispatchable load\n' % (k, load[k]))
180
181
182
183 if opt["which"][0] != 'D':
184 for k in range(len(scale)):
185 idx = find(load_zone == k + 1)
186 bus[idx, PD] = bus[idx, PD] * scale[k]
187 if opt["pq"] == 'PQ':
188 bus[idx, QD] = bus[idx, QD] * scale[k]
189
190
191 if opt["which"][0] != 'F':
192 for k in range(len(scale)):
193 idx = find(load_zone == k + 1)
194 gbus = gen[ld, GEN_BUS].astype(int)
195 i = find( in1d(e2i[gbus], idx) )
196 ig = ld[i]
197
198 gen[ix_(ig, [PG, PMIN])] = gen[ix_(ig, [PG, PMIN])] * scale[k]
199 if opt["pq"] == 'PQ':
200 gen[ix_(ig, [QG, QMIN, QMAX])] = gen[ix_(ig, [QG, QMIN, QMAX])] * scale[k]
201
202 return bus, gen
203
204
207