ltbams 1.0.7__py3-none-any.whl → 1.0.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ams/io/matpower.py CHANGED
@@ -2,11 +2,16 @@
2
2
  MATPOWER parser.
3
3
  """
4
4
  import logging
5
+ import re
5
6
  import numpy as np
6
7
 
7
- from andes.io.matpower import m2mpc
8
+ from andes.io import read_file_like
9
+ from andes.io.xlsx import confirm_overwrite
8
10
  from andes.shared import deg2rad, rad2deg
9
11
 
12
+ from ams import __version__ as version
13
+ from ams.shared import copyright_msg, nowarranty_msg, report_time
14
+
10
15
  logger = logging.getLogger(__name__)
11
16
 
12
17
 
@@ -29,6 +34,151 @@ def read(system, file):
29
34
  return mpc2system(mpc, system)
30
35
 
31
36
 
37
+ def m2mpc(infile: str) -> dict:
38
+ """
39
+ Parse a MATPOWER file and return a dictionary containing the parsed data.
40
+
41
+ This function processes MATPOWER case files and extracts relevant fields
42
+ into a structured dictionary. It is revised from ``andes.io.matpower.m2mpc``.
43
+
44
+ Supported fields include:
45
+ - `baseMVA`: The system base power in MVA.
46
+ - `bus`: Bus data, including voltage, load, and generation information.
47
+ - `bus_name`: Names of the buses (if available).
48
+ - `gen`: Generator data, including power limits and voltage setpoints.
49
+ - `branch`: Branch data, including line impedances and ratings.
50
+ - `gencost`: Generator cost data (parsed but not used in this implementation).
51
+ - `areas`: Area data (parsed but not used in this implementation).
52
+ - `gentype`: Generator type information (if available).
53
+ - `genfuel`: Generator fuel type information (if available).
54
+
55
+ Parameters
56
+ ----------
57
+ infile : str
58
+ Path to the MATPOWER file to be parsed.
59
+
60
+ Returns
61
+ -------
62
+ dict
63
+ A dictionary containing the parsed MATPOWER data, where keys correspond
64
+ to MATPOWER struct names and values are numpy arrays or lists.
65
+ """
66
+
67
+ func = re.compile(r'function\s')
68
+ mva = re.compile(r'\s*mpc.baseMVA\s*=\s*')
69
+ bus = re.compile(r'\s*mpc.bus\s*=\s*\[?')
70
+ gen = re.compile(r'\s*mpc.gen\s*=\s*\[')
71
+ branch = re.compile(r'\s*mpc.branch\s*=\s*\[')
72
+ area = re.compile(r'\s*mpc.areas\s*=\s*\[')
73
+ gencost = re.compile(r'\s*mpc.gencost\s*=\s*\[')
74
+ bus_name = re.compile(r'\s*mpc.bus_name\s*=\s*{')
75
+ gentype = re.compile(r'\s*mpc.gentype\s*=\s*{')
76
+ genfuel = re.compile(r'\s*mpc.genfuel\s*=\s*{')
77
+ end = re.compile(r'\s*[\];}]')
78
+ has_digit = re.compile(r'.*\d+\s*]?;?')
79
+
80
+ field = None
81
+ info = True
82
+
83
+ mpc = {
84
+ 'version': 2, # not in use
85
+ 'baseMVA': 100,
86
+ 'bus': [],
87
+ 'gen': [],
88
+ 'branch': [],
89
+ 'area': [],
90
+ 'gencost': [],
91
+ 'bus_name': [],
92
+ 'gentype': [],
93
+ 'genfuel': [],
94
+ }
95
+
96
+ input_list = read_file_like(infile)
97
+
98
+ for line in input_list:
99
+ line = line.strip().rstrip(';')
100
+ if not line:
101
+ continue
102
+ elif func.search(line): # skip function declaration
103
+ continue
104
+ elif len(line.split('%')[0]) == 0:
105
+ if info is True:
106
+ logger.info(line[1:])
107
+ info = False
108
+ else:
109
+ continue
110
+ elif mva.search(line):
111
+ mpc["baseMVA"] = float(line.split('=')[1])
112
+
113
+ if not field:
114
+ if bus.search(line):
115
+ field = 'bus'
116
+ elif gen.search(line):
117
+ field = 'gen'
118
+ elif branch.search(line):
119
+ field = 'branch'
120
+ elif area.search(line):
121
+ field = 'area'
122
+ elif gencost.search(line):
123
+ field = 'gencost'
124
+ elif bus_name.search(line):
125
+ field = 'bus_name'
126
+ elif gentype.search(line):
127
+ field = 'gentype'
128
+ elif genfuel.search(line):
129
+ field = 'genfuel'
130
+ else:
131
+ continue
132
+ elif end.search(line):
133
+ field = None
134
+ continue
135
+
136
+ # parse mpc sections
137
+ if field:
138
+ if line.find('=') >= 0:
139
+ line = line.split('=')[1]
140
+ if line.find('[') >= 0:
141
+ line = re.sub(r'\[', '', line)
142
+ elif line.find('{') >= 0:
143
+ line = re.sub(r'{', '', line)
144
+
145
+ if field in ['bus_name', 'gentype', 'genfuel']:
146
+ # Handle string-based fields
147
+ line = line.split(';')
148
+ data = [i.strip('\'').strip() for i in line if i.strip()]
149
+ mpc[field].extend(data)
150
+ else:
151
+ if not has_digit.search(line):
152
+ continue
153
+ line = line.split('%')[0].strip()
154
+ line = line.split(';')
155
+ for item in line:
156
+ if not has_digit.search(item):
157
+ continue
158
+ try:
159
+ data = np.array([float(val) for val in item.split()])
160
+ except Exception as e:
161
+ logger.error('Error parsing "%s"', infile)
162
+ raise e
163
+ mpc[field].append(data)
164
+
165
+ # convert mpc to np array
166
+ mpc_array = dict()
167
+ for key, val in mpc.items():
168
+ if isinstance(val, (float, int)):
169
+ mpc_array[key] = val
170
+ elif isinstance(val, list):
171
+ if len(val) == 0:
172
+ continue
173
+ if key in ['bus_name', 'gentype', 'genfuel']:
174
+ mpc_array[key] = np.array(val, dtype=object)
175
+ else:
176
+ mpc_array[key] = np.array(val)
177
+ else:
178
+ raise NotImplementedError("Unknown type for mpc, ", type(val))
179
+ return mpc_array
180
+
181
+
32
182
  def mpc2system(mpc: dict, system) -> bool:
33
183
  """
34
184
  Load an mpc dict into an empty AMS system.
@@ -97,7 +247,20 @@ def mpc2system(mpc: dict, system) -> bool:
97
247
  mpc_gen[:, 19] = system.PV.Rq.default * base_mva / 60
98
248
  else:
99
249
  mpc_gen = mpc['gen']
100
- for data in mpc_gen:
250
+
251
+ # Ensure 'gentype' and 'genfuel' keys exist in mpc, with default values if missing
252
+ gentype = mpc.get('gentype', [''] * mpc_gen.shape[0])
253
+ genfuel = mpc.get('genfuel', [''] * mpc_gen.shape[0])
254
+
255
+ # Validate lengths of 'gentype' and 'genfuel' against the number of generators
256
+ if len(gentype) != mpc_gen.shape[0]:
257
+ raise ValueError(
258
+ f"'gentype' length ({len(gentype)}) does not match the number of generators ({mpc_gen.shape[0]})")
259
+ if len(genfuel) != mpc_gen.shape[0]:
260
+ raise ValueError(
261
+ f"'genfuel' length ({len(genfuel)}) does not match the number of generators ({mpc_gen.shape[0]})")
262
+
263
+ for data, gt, gf in zip(mpc_gen, gentype, genfuel):
101
264
  # bus pg qg qmax qmin vg mbase status pmax pmin
102
265
  # 0 1 2 3 4 5 6 7 8 9
103
266
  # pc1 pc2 qc1min qc1max qc2min qc2max ramp_agc ramp_10
@@ -142,7 +305,7 @@ def mpc2system(mpc: dict, system) -> bool:
142
305
  Qc2min=qc2min, Qc2max=qc2max,
143
306
  Ragc=ramp_agc, R10=ramp_10,
144
307
  R30=ramp_30, Rq=ramp_q,
145
- apf=apf)
308
+ apf=apf, gentype=gt, genfuel=gf)
146
309
  else:
147
310
  system.add('PV', idx=gen_idx, bus=bus_idx, busr=bus_idx,
148
311
  name=None,
@@ -155,7 +318,7 @@ def mpc2system(mpc: dict, system) -> bool:
155
318
  Qc2min=qc2min, Qc2max=qc2max,
156
319
  Ragc=ramp_agc, R10=ramp_10,
157
320
  R30=ramp_30, Rq=ramp_q,
158
- apf=apf)
321
+ apf=apf, gentype=gt, genfuel=gf)
159
322
 
160
323
  for data in mpc['branch']:
161
324
  # fbus tbus r x b rateA rateB rateC ratio angle
@@ -204,9 +367,8 @@ def mpc2system(mpc: dict, system) -> bool:
204
367
  gen_idx = np.arange(mpc['gen'].shape[0]) + 1
205
368
  mpc_cost = mpc['gencost']
206
369
  if mpc_cost[0, 0] == 1:
207
- logger.warning("Type 1 gencost detected. "
208
- "This is not supported in AMS. "
209
- "Default type 2 cost parameters will be used as a fallback."
370
+ logger.warning("Type 1 gencost detected, which is not supported in AMS.\n"
371
+ "Default type 2 cost parameters will be used as a fallback.\n"
210
372
  "It is recommended to manually convert the gencost data to type 2.")
211
373
  mpc_cost = np.repeat(np.array([[2, 0, 0, 3, 0, 0, 0]]),
212
374
  mpc_cost.shape[0], axis=0)
@@ -238,14 +400,24 @@ def mpc2system(mpc: dict, system) -> bool:
238
400
  c2=c2, c1=c1, c0=c0
239
401
  )
240
402
 
403
+ # --- Area ---
404
+ area_id = np.unique(system.Bus.area.v).astype(int)
405
+ for area in area_id:
406
+ area_idx = f'AREA_{area}'
407
+ system.add('Area', idx=area_idx, name=area)
408
+ bus_area = system.Bus.area.v
409
+ bus_area = [f'AREA_{int(area)}' for area in bus_area]
410
+ system.Bus.area.v = bus_area
411
+
241
412
  # --- Zone ---
242
413
  zone_id = np.unique(system.Bus.zone.v).astype(int)
243
414
  for zone in zone_id:
244
415
  zone_idx = f'ZONE_{zone}'
245
- system.add('Zone', idx=zone_idx, name=None)
416
+ system.add('Zone', idx=zone_idx, name=zone)
246
417
  bus_zone = system.Bus.zone.v
247
418
  bus_zone = [f'ZONE_{int(zone)}' for zone in bus_zone]
248
419
  system.Bus.zone.v = bus_zone
420
+
249
421
  return True
250
422
 
251
423
 
@@ -271,25 +443,29 @@ def _get_bus_id_caller(bus):
271
443
 
272
444
  def system2mpc(system) -> dict:
273
445
  """
274
- Convert data from an AMS system to an mpc dict.
446
+ Convert a **setup** AMS system to a MATPOWER mpc dictionary.
447
+
448
+ This function is revised from ``andes.io.matpower.system2mpc``.
275
449
 
276
- In the ``gen`` section, slack generators preceeds PV generators.
450
+ In the ``gen`` section, slack generators are listed before PV generators.
277
451
 
278
- Compared to the ``andes.io.matpower.system2mpc``,
279
- this function includes the generator cost data in the ``gencost``
280
- section.
281
- Additionally, ``c2`` and ``c1`` are scaled by ``base_mva`` to match
282
- MATPOWER unit ``MW``.
452
+ In the converted MPC, the indices of area (bus[:, 6]) and zone (bus[:, 10])
453
+ may differ from the original MPC. However, the mapping relationship is preserved.
454
+ For example, if the original MPC numbers areas starting from 1, the converted
455
+ MPC may number them starting from 0.
456
+
457
+ The coefficients ``c2`` and ``c1`` in the generator cost data are scaled by
458
+ ``base_mva`` to match MATPOWER's unit convention (MW).
283
459
 
284
460
  Parameters
285
461
  ----------
286
462
  system : ams.core.system.System
287
- AMS system
463
+ The AMS system to be converted.
288
464
 
289
465
  Returns
290
466
  -------
291
- mpc: dict
292
- MATPOWER mpc dict
467
+ mpc : dict
468
+ A dictionary in MATPOWER format representing the converted AMS system.
293
469
  """
294
470
 
295
471
  mpc = dict(version='2',
@@ -300,12 +476,16 @@ def system2mpc(system) -> dict:
300
476
  gencost=np.zeros((system.GCost.n, 7), dtype=np.float64),
301
477
  )
302
478
 
479
+ if not system.is_setup:
480
+ logger.warning("System is not setup and will be setup now.")
481
+ system.setup()
482
+
303
483
  if system.Bus.name.v is not None:
304
484
  mpc['bus_name'] = system.Bus.name.v
305
485
 
306
486
  base_mva = system.config.mva
307
487
 
308
- # --- bus ---
488
+ # --- Bus ---
309
489
  bus = mpc['bus']
310
490
  gen = mpc['gen']
311
491
 
@@ -313,18 +493,16 @@ def system2mpc(system) -> dict:
313
493
 
314
494
  bus[:, 0] = to_busid(system.Bus.idx.v)
315
495
  bus[:, 1] = 1
496
+ if system.Area.n > 0:
497
+ bus[:, 6] = system.Area.idx2uid(system.Bus.area.v)
316
498
  bus[:, 7] = system.Bus.v0.v
317
499
  bus[:, 8] = system.Bus.a0.v * rad2deg
318
500
  bus[:, 9] = system.Bus.Vn.v
501
+ if system.Zone.n > 0:
502
+ bus[:, 10] = system.Zone.idx2uid(system.Bus.zone.v)
319
503
  bus[:, 11] = system.Bus.vmax.v
320
504
  bus[:, 12] = system.Bus.vmin.v
321
505
 
322
- # --- zone ---
323
- ZONE_I = system.Zone.idx.v
324
- if len(ZONE_I) > 0:
325
- mapping = {busi0: i for i, busi0 in enumerate(ZONE_I)}
326
- bus[:, 10] = np.array([mapping[busi0] for busi0 in system.Bus.zone.v])
327
-
328
506
  # --- PQ ---
329
507
  if system.PQ.n > 0:
330
508
  pq_pos = system.Bus.idx2uid(system.PQ.bus.v)
@@ -410,4 +588,136 @@ def system2mpc(system) -> dict:
410
588
  else:
411
589
  mpc.pop('gencost')
412
590
 
591
+ # --- gentype ---
592
+ stg = system.StaticGen.get_all_idxes()
593
+ gentype = system.StaticGen.get(src='gentype', attr='v', idx=stg)
594
+ if any(gentype):
595
+ mpc['gentype'] = np.array(gentype)
596
+
597
+ # --- genfuel ---
598
+ genfuel = system.StaticGen.get(src='genfuel', attr='v', idx=stg)
599
+ if any(genfuel):
600
+ mpc['genfuel'] = np.array(genfuel)
601
+
602
+ # --- Bus Name ---
603
+ if any(system.Bus.name.v):
604
+ mpc['bus_name'] = np.array(system.Bus.name.v)
605
+
413
606
  return mpc
607
+
608
+
609
+ def mpc2m(mpc: dict, outfile: str) -> str:
610
+ """
611
+ Write a MATPOWER mpc dict to a M-file.
612
+
613
+ Parameters
614
+ ----------
615
+ mpc : dict
616
+ MATPOWER mpc dictionary.
617
+ outfile : str
618
+ Path to the output M-file.
619
+ """
620
+ with open(outfile, 'w') as f:
621
+ # Add version info
622
+ f.write(f"%% Converted by AMS {version}\n")
623
+ f.write(f"%% {copyright_msg}\n\n")
624
+ f.write(f"%% {nowarranty_msg}\n")
625
+ f.write(f"%% Convert time: {report_time}\n\n")
626
+
627
+ f.write("function mpc = mpc_case\n")
628
+ f.write("mpc.version = '2';\n\n")
629
+
630
+ # Write baseMVA
631
+ f.write(f"%% system MVA base\nmpc.baseMVA = {mpc['baseMVA']};\n\n")
632
+
633
+ # Write bus data
634
+ f.write("%% bus data\n")
635
+ f.write("%% bus_i type Pd Qd Gs Bs area Vm Va baseKV zone Vmax Vmin\n")
636
+ f.write("mpc.bus = [\n")
637
+ for row in mpc['bus']:
638
+ f.write(" " + "\t".join(f"{val:.6g}" for val in row) + ";\n")
639
+ f.write("];\n\n")
640
+
641
+ # Write generator data
642
+ f.write("%% generator data\n")
643
+ f.write("%% bus Pg Qg Qmax Qmin Vg mBase status Pmax Pmin\n")
644
+ f.write("%% Pc1 Pc2 Qc1min Qc1max Qc2min Qc2max ramp_agc ramp_10 ramp_30 ramp_q apf\n")
645
+ f.write("mpc.gen = [\n")
646
+ for row in mpc['gen']:
647
+ f.write(" " + "\t".join(f"{val:.6g}" for val in row) + ";\n")
648
+ f.write("];\n\n")
649
+
650
+ # Write branch data
651
+ f.write("%% branch data\n")
652
+ f.write("%% fbus tbus r x b rateA rateB rateC ratio angle status angmin angmax PF QF PT QT\n")
653
+ f.write("mpc.branch = [\n")
654
+ for row in mpc['branch']:
655
+ f.write(" " + "\t".join(f"{val:.6g}" for val in row) + ";\n")
656
+ f.write("];\n\n")
657
+
658
+ # Write generator cost data if available
659
+ if 'gencost' in mpc:
660
+ f.write("%% generator cost data\n")
661
+ f.write("%% 1 startup shutdown n x1 y1 ... xn yn\n")
662
+ f.write("%% 2 startup shutdown n c(n-1) ... c0\n")
663
+ f.write("mpc.gencost = [\n")
664
+ for row in mpc['gencost']:
665
+ f.write(" " + "\t".join(f"{val:.6g}" for val in row) + ";\n")
666
+ f.write("];\n\n")
667
+
668
+ # Write bus names if available and not all None
669
+ if 'bus_name' in mpc and any(mpc['bus_name']):
670
+ f.write("%% bus names\n")
671
+ f.write("mpc.bus_name = {\n")
672
+ for name in mpc['bus_name']:
673
+ f.write(f" '{name}';\n")
674
+ f.write("};\n\n")
675
+
676
+ # Write generator types if available and not all None
677
+ if 'gentype' in mpc and any(mpc['gentype']):
678
+ f.write("%% generator types\n")
679
+ f.write("mpc.gentype = {\n")
680
+ for gentype in mpc['gentype']:
681
+ f.write(f" '{gentype}';\n")
682
+ f.write("};\n\n")
683
+
684
+ # Write generator fuels if available and not all None
685
+ if 'genfuel' in mpc and any(mpc['genfuel']):
686
+ f.write("%% generator fuels\n")
687
+ f.write("mpc.genfuel = {\n")
688
+ for genfuel in mpc['genfuel']:
689
+ f.write(f" '{genfuel}';\n")
690
+ f.write("};\n\n")
691
+
692
+ logger.info(f"Finished writing MATPOWER case to {outfile}")
693
+ return outfile
694
+
695
+
696
+ def write(system, outfile: str, overwrite: bool = None) -> bool:
697
+ """
698
+ Export an AMS system to a MATPOWER mpc file.
699
+
700
+ This function converts an AMS system object into a MATPOWER-compatible
701
+ mpc dictionary and writes it to a specified output file in MATPOWER format.
702
+
703
+ Parameters
704
+ ----------
705
+ system : ams.system.System
706
+ A loaded system.
707
+ outfile : str
708
+ Path to the output file.
709
+ overwrite : bool, optional
710
+ None to prompt for overwrite selection; True to overwrite; False to not overwrite.
711
+
712
+ Returns
713
+ -------
714
+ bool
715
+ True if the file was successfully written, False otherwise.
716
+ """
717
+ if not confirm_overwrite(outfile, overwrite=overwrite):
718
+ return False
719
+
720
+ mpc = system2mpc(system)
721
+ mpc2m(mpc, outfile)
722
+ logger.info('MATPOWER m case file written to "%s"', outfile)
723
+ return True
ams/io/psse.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """
2
- Excel reader and writer for AMS.
2
+ PSS/E .raw reader for AMS.
3
3
  This module is the existing module in ``andes.io.psse``.
4
4
  """
5
5
 
ams/io/pypower.py CHANGED
@@ -101,3 +101,17 @@ def system2ppc(system) -> dict:
101
101
  mpc['branch'][:, 0] = np.array([bus_map[busi0] for busi0 in mpc['branch'][:, 0].astype(int)])
102
102
  mpc['branch'][:, 1] = np.array([bus_map[busi0] for busi0 in mpc['branch'][:, 1].astype(int)])
103
103
  return mpc
104
+
105
+
106
+ def ppc2py(ppc: dict, outfile: str) -> str:
107
+ """
108
+ Placeholder.
109
+ """
110
+ return NotImplemented
111
+
112
+
113
+ def write(system, outfile: str) -> bool:
114
+ """
115
+ Placeholder.
116
+ """
117
+ return NotImplemented
ams/models/group.py CHANGED
@@ -175,20 +175,22 @@ class Reserve(GroupBase):
175
175
 
176
176
  class StaticGen(GroupBase):
177
177
  """
178
- Generator group.
178
+ Static Generator Group.
179
+
180
+ The generator types and fuel types are referenced from MATPOWER.
179
181
 
180
182
  Notes
181
183
  -----
182
- For co-simulation with ANDES, check
183
- `ANDES StaticGen <https://docs.andes.app/en/latest/groupdoc/StaticGen.html#staticgen>`_
184
- for replacing static generators with dynamic generators.
184
+ For co-simulation with ANDES, refer to the `ANDES StaticGen Documentation
185
+ <https://docs.andes.app/en/latest/groupdoc/StaticGen.html#staticgen>`_ for
186
+ replacing static generators with dynamic generators.
185
187
  """
186
188
 
187
189
  def __init__(self):
188
190
  super().__init__()
189
191
  self.common_params.extend(('bus', 'Sn', 'Vn', 'p0', 'q0', 'ra', 'xs', 'subidx',
190
192
  'pmax', 'pmin', 'pg0', 'ctrl', 'R10', 'td1', 'td2',
191
- 'area', 'zone'))
193
+ 'area', 'zone', 'gentype', 'genfuel'))
192
194
  self.common_vars.extend(('p', 'q'))
193
195
 
194
196
 
ams/models/line.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from andes.models.line.line import LineData
2
2
  from andes.models.line.jumper import JumperData
3
3
  from andes.core.param import NumParam
4
- from andes.shared import deg2rad, np, spmatrix
4
+ from andes.shared import deg2rad
5
5
 
6
6
  from ams.core.model import Model
7
7
 
@@ -49,167 +49,6 @@ class Line(LineData, Model):
49
49
  self.a1a = None
50
50
  self.a2a = None
51
51
 
52
- # NOTE: following code are minly copied from `andes.models.line.Line`
53
- # and they are not fully verified
54
- # potential issues:
55
- # `build_Bp` contains 'fdxb', which is not included in the input parameters,
56
- # and the results are the negative of `Bbus` from `makeBdc` in PYPOWER
57
- # `build_Bpp` ignores the line resistance for all three methods
58
- # `build_Bdc` results are the negative of `Bbus` from `makeBdc` in PYPOWER
59
- # `build_y` results have inignorable differences at diagonal elements with `makeYbus` in PYPOWER
60
-
61
- def build_y(self):
62
- """
63
- Build bus admittance matrix. Copied from ``andes.models.line.line.Line``.
64
-
65
- Returns
66
- -------
67
- Y : spmatrix
68
- Bus admittance matrix.
69
- """
70
-
71
- nb = self.system.Bus.n
72
-
73
- y1 = self.u.v * (self.g1.v + self.b1.v * 1j)
74
- y2 = self.u.v * (self.g2.v + self.b2.v * 1j)
75
- y12 = self.u.v / (self.r.v + self.x.v * 1j)
76
- m = self.tap.v * np.exp(1j * self.phi.v)
77
- m2 = self.tap.v**2
78
- mconj = np.conj(m)
79
-
80
- # build self and mutual admittances into Y
81
- Y = spmatrix((y12 + y1 / m2), self.a1a, self.a1a, (nb, nb), 'z')
82
- Y -= spmatrix(y12 / mconj, self.a1a, self.a2a, (nb, nb), 'z')
83
- Y -= spmatrix(y12 / m, self.a2a, self.a1a, (nb, nb), 'z')
84
- Y += spmatrix(y12 + y2, self.a2a, self.a2a, (nb, nb), 'z')
85
-
86
- return Y
87
-
88
- def build_Bp(self, method='fdpf'):
89
- """
90
- Function for building B' matrix.
91
-
92
- Parameters
93
- ----------
94
- method : str
95
- Method for building B' matrix. Choose from 'fdpf', 'fdbx', 'dcpf'.
96
-
97
- Returns
98
- -------
99
- Bp : spmatrix
100
- B' matrix.
101
- """
102
- nb = self.system.Bus.n
103
-
104
- if method not in ("fdpf", "fdbx", "dcpf"):
105
- raise ValueError(f"Invalid method {method}; choose from 'fdpf', 'fdbx', 'dcpf'")
106
-
107
- # Build B prime matrix -- FDPF
108
- # `y1`` neglects line charging shunt, and g1 is usually 0 in HV lines
109
- # `y2`` neglects line charging shunt, and g2 is usually 0 in HV lines
110
- y1 = self.u.v * self.g1.v
111
- y2 = self.u.v * self.g2.v
112
-
113
- # `m` neglected tap ratio
114
- m = np.exp(self.phi.v * 1j)
115
- mconj = np.conj(m)
116
- m2 = np.ones(self.n)
117
-
118
- if method in ('fdxb', 'dcpf'):
119
- # neglect line resistance in Bp in XB method
120
- y12 = self.u.v / (self.x.v * 1j)
121
- else:
122
- y12 = self.u.v / (self.r.v + self.x.v * 1j)
123
-
124
- Bdc = spmatrix((y12 + y1) / m2, self.a1a, self.a1a, (nb, nb), 'z')
125
- Bdc -= spmatrix(y12 / mconj, self.a1a, self.a2a, (nb, nb), 'z')
126
- Bdc -= spmatrix(y12 / m, self.a2a, self.a1a, (nb, nb), 'z')
127
- Bdc += spmatrix(y12 + y2, self.a2a, self.a2a, (nb, nb), 'z')
128
- Bdc = Bdc.imag()
129
-
130
- for item in range(nb):
131
- if abs(Bdc[item, item]) == 0:
132
- Bdc[item, item] = 1e-6 + 0j
133
-
134
- return Bdc
135
-
136
- def build_Bpp(self, method='fdpf'):
137
- """
138
- Function for building B'' matrix.
139
-
140
- Parameters
141
- ----------
142
- method : str
143
- Method for building B'' matrix. Choose from 'fdpf', 'fdbx', 'dcpf'.
144
-
145
- Returns
146
- -------
147
- Bpp : spmatrix
148
- B'' matrix.
149
- """
150
-
151
- nb = self.system.Bus.n
152
-
153
- if method not in ("fdpf", "fdbx", "dcpf"):
154
- raise ValueError(f"Invalid method {method}; choose from 'fdpf', 'fdbx', 'dcpf'")
155
-
156
- # Build B double prime matrix
157
- # y1 neglected line charging shunt, and g1 is usually 0 in HV lines
158
- # y2 neglected line charging shunt, and g2 is usually 0 in HV lines
159
- # m neglected phase shifter
160
- y1 = self.u.v * (self.g1.v + self.b1.v * 1j)
161
- y2 = self.u.v * (self.g2.v + self.b2.v * 1j)
162
-
163
- m = self.tap.v
164
- m2 = abs(m)**2
165
-
166
- if method in ('fdbx', 'fdpf', 'dcpf'):
167
- # neglect line resistance in Bpp in BX method
168
- y12 = self.u.v / (self.x.v * 1j)
169
- else:
170
- y12 = self.u.v / (self.r.v + self.x.v * 1j)
171
-
172
- Bpp = spmatrix((y12 + y1) / m2, self.a1a, self.a1a, (nb, nb), 'z')
173
- Bpp -= spmatrix(y12 / np.conj(m), self.a1a, self.a2a, (nb, nb), 'z')
174
- Bpp -= spmatrix(y12 / m, self.a2a, self.a1a, (nb, nb), 'z')
175
- Bpp += spmatrix(y12 + y2, self.a2a, self.a2a, (nb, nb), 'z')
176
- Bpp = Bpp.imag()
177
-
178
- for item in range(nb):
179
- if abs(Bpp[item, item]) == 0:
180
- Bpp[item, item] = 1e-6 + 0j
181
-
182
- return Bpp
183
-
184
- def build_Bdc(self):
185
- """
186
- The MATPOWER-flavor Bdc matrix for DC power flow.
187
-
188
- The method neglects line charging and line resistance. It retains tap ratio.
189
-
190
- Returns
191
- -------
192
- Bdc : spmatrix
193
- Bdc matrix.
194
- """
195
-
196
- nb = self.system.Bus.n
197
-
198
- y12 = self.u.v / (self.x.v * 1j)
199
- y12 = y12 / self.tap.v
200
-
201
- Bdc = spmatrix(y12, self.a1a, self.a1a, (nb, nb), 'z')
202
- Bdc -= spmatrix(y12, self.a1a, self.a2a, (nb, nb), 'z')
203
- Bdc -= spmatrix(y12, self.a2a, self.a1a, (nb, nb), 'z')
204
- Bdc += spmatrix(y12, self.a2a, self.a2a, (nb, nb), 'z')
205
- Bdc = Bdc.imag()
206
-
207
- for item in range(nb):
208
- if abs(Bdc[item, item]) == 0:
209
- Bdc[item, item] = 1e-6
210
-
211
- return Bdc
212
-
213
52
 
214
53
  class Jumper(JumperData, Model):
215
54
  """