ltbams 1.0.9__py3-none-any.whl → 1.0.10__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.
Files changed (79) hide show
  1. ams/__init__.py +0 -1
  2. ams/_version.py +3 -3
  3. ams/cases/5bus/pjm5bus_demo.json +1324 -0
  4. ams/core/__init__.py +1 -0
  5. ams/core/common.py +30 -0
  6. ams/core/model.py +1 -1
  7. ams/core/symprocessor.py +1 -1
  8. ams/extension/eva.py +1 -1
  9. ams/interface.py +40 -24
  10. ams/io/matpower.py +31 -17
  11. ams/io/psse.py +278 -1
  12. ams/main.py +2 -2
  13. ams/models/group.py +2 -1
  14. ams/models/static/pq.py +7 -3
  15. ams/opt/param.py +1 -2
  16. ams/routines/__init__.py +2 -3
  17. ams/routines/acopf.py +5 -108
  18. ams/routines/dcopf.py +8 -0
  19. ams/routines/dcpf.py +1 -1
  20. ams/routines/ed.py +4 -2
  21. ams/routines/grbopt.py +150 -0
  22. ams/routines/pflow.py +2 -2
  23. ams/routines/pypower.py +631 -0
  24. ams/routines/routine.py +4 -10
  25. ams/routines/uc.py +2 -2
  26. ams/shared.py +26 -43
  27. ams/system.py +118 -2
  28. docs/source/api.rst +2 -0
  29. docs/source/getting_started/install.rst +9 -6
  30. docs/source/images/dcopf_time.png +0 -0
  31. docs/source/images/educ_pie.png +0 -0
  32. docs/source/release-notes.rst +21 -47
  33. {ltbams-1.0.9.dist-info → ltbams-1.0.10.dist-info}/METADATA +87 -47
  34. {ltbams-1.0.9.dist-info → ltbams-1.0.10.dist-info}/RECORD +54 -71
  35. {ltbams-1.0.9.dist-info → ltbams-1.0.10.dist-info}/WHEEL +1 -1
  36. tests/test_1st_system.py +1 -1
  37. tests/test_case.py +14 -14
  38. tests/test_export_csv.py +1 -1
  39. tests/test_interface.py +24 -2
  40. tests/test_io.py +50 -0
  41. tests/test_omodel.py +1 -1
  42. tests/test_report.py +6 -6
  43. tests/test_routine.py +2 -2
  44. tests/test_rtn_acopf.py +75 -0
  45. tests/test_rtn_dcopf.py +1 -1
  46. tests/test_rtn_dcopf2.py +1 -1
  47. tests/test_rtn_ed.py +9 -9
  48. tests/test_rtn_opf.py +142 -0
  49. tests/test_rtn_pflow.py +0 -72
  50. tests/test_rtn_pypower.py +315 -0
  51. tests/test_rtn_rted.py +8 -8
  52. tests/test_rtn_uc.py +18 -18
  53. ams/pypower/__init__.py +0 -8
  54. ams/pypower/_compat.py +0 -9
  55. ams/pypower/core/__init__.py +0 -8
  56. ams/pypower/core/pips.py +0 -894
  57. ams/pypower/core/ppoption.py +0 -244
  58. ams/pypower/core/ppver.py +0 -18
  59. ams/pypower/core/solver.py +0 -2451
  60. ams/pypower/eps.py +0 -6
  61. ams/pypower/idx.py +0 -174
  62. ams/pypower/io.py +0 -604
  63. ams/pypower/make/__init__.py +0 -11
  64. ams/pypower/make/matrices.py +0 -665
  65. ams/pypower/make/pdv.py +0 -506
  66. ams/pypower/routines/__init__.py +0 -7
  67. ams/pypower/routines/cpf.py +0 -513
  68. ams/pypower/routines/cpf_callbacks.py +0 -114
  69. ams/pypower/routines/opf.py +0 -1803
  70. ams/pypower/routines/opffcns.py +0 -1946
  71. ams/pypower/routines/pflow.py +0 -852
  72. ams/pypower/toggle.py +0 -1098
  73. ams/pypower/utils.py +0 -293
  74. ams/routines/cpf.py +0 -65
  75. ams/routines/dcpf0.py +0 -196
  76. ams/routines/pflow0.py +0 -113
  77. tests/test_rtn_dcpf.py +0 -77
  78. {ltbams-1.0.9.dist-info → ltbams-1.0.10.dist-info}/entry_points.txt +0 -0
  79. {ltbams-1.0.9.dist-info → ltbams-1.0.10.dist-info}/top_level.txt +0 -0
ams/routines/acopf.py CHANGED
@@ -1,117 +1,14 @@
1
1
  """
2
- ACOPF routines using PYPOWER.
2
+ ACOPF routines.
3
3
  """
4
- import logging
5
- from collections import OrderedDict
6
4
 
7
- from ams.pypower import runopf
8
- from ams.pypower.core import ppoption
5
+ from ams.routines.pypower import ACOPF1
9
6
 
10
- from ams.io.pypower import system2ppc
11
- from ams.core.param import RParam
12
7
 
13
- from ams.routines.dcpf0 import DCPF0
14
- from ams.opt import Var, Constraint, Objective
15
-
16
- logger = logging.getLogger(__name__)
17
-
18
-
19
- class ACOPF(DCPF0):
8
+ class ACOPF(ACOPF1):
20
9
  """
21
- Standard AC optimal power flow.
22
-
23
- Notes
24
- -----
25
- 1. ACOPF is solved with PYPOWER ``runopf`` function.
26
- 2. ACOPF formulation in AMS style is NOT DONE YET,
27
- but this does not affect the results
28
- because the data are passed to PYPOWER for solving.
10
+ Alias for ACOPF1.
29
11
  """
30
12
 
31
13
  def __init__(self, system, config):
32
- DCPF0.__init__(self, system, config)
33
- self.info = 'AC Optimal Power Flow'
34
- self.type = 'ACED'
35
-
36
- self.map1 = OrderedDict() # ACOPF does not receive
37
- self.map2.update({
38
- 'vBus': ('Bus', 'v0'),
39
- 'ug': ('StaticGen', 'u'),
40
- 'pg': ('StaticGen', 'p0'),
41
- })
42
-
43
- # --- params ---
44
- self.c2 = RParam(info='Gen cost coefficient 2',
45
- name='c2', tex_name=r'c_{2}',
46
- unit=r'$/(p.u.^2)', model='GCost',
47
- indexer='gen', imodel='StaticGen',
48
- nonneg=True)
49
- self.c1 = RParam(info='Gen cost coefficient 1',
50
- name='c1', tex_name=r'c_{1}',
51
- unit=r'$/(p.u.)', model='GCost',
52
- indexer='gen', imodel='StaticGen',)
53
- self.c0 = RParam(info='Gen cost coefficient 0',
54
- name='c0', tex_name=r'c_{0}',
55
- unit=r'$', model='GCost',
56
- indexer='gen', imodel='StaticGen',
57
- no_parse=True)
58
- self.qd = RParam(info='reactive demand',
59
- name='qd', tex_name=r'q_{d}',
60
- model='StaticLoad', src='q0',
61
- unit='p.u.',)
62
- # --- bus ---
63
- self.vBus = Var(info='Bus voltage magnitude',
64
- unit='p.u.',
65
- name='vBus', tex_name=r'v_{Bus}',
66
- src='v', model='Bus',)
67
- # --- gen ---
68
- self.qg = Var(info='Gen reactive power',
69
- unit='p.u.',
70
- name='qg', tex_name=r'q_{g}',
71
- model='StaticGen', src='q',)
72
- # --- constraints ---
73
- self.pb = Constraint(name='pb',
74
- info='power balance',
75
- e_str='sum(pd) - sum(pg)',
76
- is_eq=True,)
77
- # TODO: ACOPF formulation
78
- # --- objective ---
79
- self.obj = Objective(name='obj',
80
- info='total cost',
81
- e_str='sum(c2 * pg**2 + c1 * pg + c0)',
82
- sense='min',)
83
-
84
- def solve(self, method=None, **kwargs):
85
- """
86
- Solve ACOPF using PYPOWER with PIPS.
87
- """
88
- ppc = system2ppc(self.system)
89
- ppopt = ppoption()
90
- res, sstats = runopf(casedata=ppc, ppopt=ppopt, **kwargs)
91
- return res, sstats
92
-
93
- def run(self, **kwargs):
94
- """
95
- Run ACOPF using PYPOWER with PIPS.
96
- *args and **kwargs go to `self.solve()`, which are not used yet.
97
-
98
- Examples
99
- --------
100
- >>> ss = ams.load(ams.get_case('matpower/case14.m'))
101
- >>> ss.ACOPF.run()
102
-
103
- Parameters
104
- ----------
105
- force_init : bool
106
- Force initialization.
107
- no_code : bool
108
- Disable showing code.
109
- method : str
110
- Placeholder for future use.
111
-
112
- Returns
113
- -------
114
- exit_code : int
115
- Exit code of the routine.
116
- """
117
- super().run(**kwargs)
14
+ super().__init__(system, config)
ams/routines/dcopf.py CHANGED
@@ -141,6 +141,14 @@ class DCOPF(DCPFBase):
141
141
  name='pi', unit='$/p.u.',
142
142
  model='Bus', src=None,
143
143
  e_str='pb.dual_variables[0]')
144
+ self.mu1 = ExpressionCalc(info='Lagrange multipliers, dual of <plflb>',
145
+ name='mu1', unit='$/p.u.',
146
+ model='Line', src=None,
147
+ e_str='plflb.dual_variables[0]')
148
+ self.mu2 = ExpressionCalc(info='Lagrange multipliers, dual of <plfub>',
149
+ name='mu2', unit='$/p.u.',
150
+ model='Line', src=None,
151
+ e_str='plfub.dual_variables[0]')
144
152
 
145
153
  # --- objective ---
146
154
  obj = 'sum(mul(c2, pg**2))'
ams/routines/dcpf.py CHANGED
@@ -125,7 +125,7 @@ class DCPFBase(RoutineBase):
125
125
  """
126
126
  return self.om.prob.solve(**kwargs)
127
127
 
128
- def unpack(self, **kwargs):
128
+ def unpack(self, res, **kwargs):
129
129
  """
130
130
  Unpack the results from CVXPY model.
131
131
  """
ams/routines/ed.py CHANGED
@@ -104,6 +104,8 @@ class MPBase:
104
104
  self.vBus.horizon = self.timeslot
105
105
  self.vBus.info = '2D Bus voltage'
106
106
  self.pi.horizon = self.timeslot
107
+ self.mu1.horizon = self.timeslot
108
+ self.mu2.horizon = self.timeslot
107
109
 
108
110
 
109
111
  class ED(RTED, MPBase, SRBase):
@@ -214,14 +216,14 @@ class ED(RTED, MPBase, SRBase):
214
216
  cost += '+ sum(mul(ugt, mul(c0, tlv)))'
215
217
  self.obj.e_str = cost
216
218
 
217
- def dc2ac(self, **kwargs):
219
+ def dc2ac(self, kloss=1.0, **kwargs):
218
220
  """
219
221
  AC conversion ``dc2ac`` is not implemented yet for
220
222
  multi-period scheduling.
221
223
  """
222
224
  return NotImplementedError
223
225
 
224
- def unpack(self, **kwargs):
226
+ def unpack(self, res, **kwargs):
225
227
  """
226
228
  Multi-period scheduling will not unpack results from
227
229
  solver into devices.
ams/routines/grbopt.py ADDED
@@ -0,0 +1,150 @@
1
+ """
2
+ Routines using gurobi-optimods.
3
+ """
4
+ import logging
5
+
6
+ import io
7
+
8
+ import scipy.io
9
+
10
+ from andes.shared import np, pd
11
+ from andes.utils.misc import elapsed
12
+
13
+ from ams.io.pypower import system2ppc
14
+
15
+ from ams.opt import Var, Objective
16
+
17
+ from ams.routines.pypower import DCPF1
18
+ from ams.shared import opf
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class OPF(DCPF1):
24
+ """
25
+ Optimal Power Flow (OPF) routine using gurobi-optimods.
26
+
27
+ This class provides an interface for performing optimal power flow analysis
28
+ with gurobi-optimods, supporting both AC and DC OPF formulations.
29
+
30
+ In addition to optimizing generator dispatch, this routine can also optimize
31
+ transmission line statuses (branch switching), enabling topology optimization.
32
+ Refer to the gurobi-optimods documentation for further details:
33
+
34
+ https://gurobi-optimods.readthedocs.io/en/stable/mods/opf/opf.html
35
+ """
36
+
37
+ def __init__(self, system, config):
38
+ DCPF1.__init__(self, system, config)
39
+ self.info = 'Optimal Power Flow'
40
+ self.type = 'ACED'
41
+
42
+ self.obj = Objective(name='obj',
43
+ info='total cost, placeholder',
44
+ e_str='sum(c2 * pg**2 + c1 * pg + c0)',
45
+ sense='min',)
46
+
47
+ self.pi = Var(info='Lagrange multiplier on real power mismatch',
48
+ name='pi', unit='$/p.u.',
49
+ model='Bus', src=None,)
50
+
51
+ self.uld = Var(info='Line commitment decision',
52
+ name='uld', tex_name=r'u_{l,d}',
53
+ model='Line', src='u',)
54
+
55
+ def solve(self, **kwargs):
56
+ ppc = system2ppc(self.system)
57
+ mat = io.BytesIO()
58
+ scipy.io.savemat(mat, {'mpc': ppc})
59
+ mat.seek(0)
60
+ res = opf.solve_opf(opf.read_case_matpower(mat), **kwargs)
61
+ return res
62
+
63
+ def unpack(self, res, **kwargs):
64
+ """
65
+ Unpack the results from the gurobi-optimods.
66
+ """
67
+ # NOTE: Map gurobi-optimods results to PPC-compatible format.
68
+ # Only relevant columns are populated, as required by `DCOPF.unpack()`.
69
+ # If future versions of gurobi-optimods provide additional outputs,
70
+ # this mapping may need to be updated to extract and assign new fields.
71
+
72
+ res_new = dict()
73
+ res_new['success'] = res['success']
74
+ res_new['et'] = res['et']
75
+ res_new['f'] = res['f']
76
+ res_new['baseMVA'] = res['baseMVA']
77
+
78
+ bus = pd.DataFrame(res['bus'])
79
+ res_new['bus'] = np.zeros((self.system.Bus.n, 17))
80
+ res_new['bus'][:, 7] = bus['Vm'].values
81
+ res_new['bus'][:, 8] = bus['Va'].values
82
+ # NOTE: As of v2.3.2, gurobi-optimods does not return LMP
83
+
84
+ gen = pd.DataFrame(res['gen'])
85
+ res_new['gen'] = np.zeros((self.system.StaticGen.n, 14))
86
+ res_new['gen'][:, 1] = gen['Pg'].values
87
+ res_new['gen'][:, 2] = gen['Qg'].values
88
+
89
+ branch = pd.DataFrame(res['branch'])
90
+ res_new['branch'] = np.zeros((self.system.Line.n, 14))
91
+ res_new['branch'][:, 13] = branch['Pf'].values
92
+ # NOTE: unpack branch_switching decision
93
+ res_new['branch'][:, 10] = branch['switching'].values
94
+ return super().unpack(res_new)
95
+
96
+ def run(self, **kwargs):
97
+ """
98
+ Run the OPF routine using gurobi-optimods.
99
+
100
+ This method invokes `self.solve(**kwargs)`, which internally utilizes
101
+ `gurobi-optimods` to solve the OPF problem.
102
+
103
+ Keyword arguments
104
+ -------------------
105
+ - ``opftype`` : str
106
+ Type of OPF to solve (default: 'AC').
107
+ - ``branch_switching`` : bool
108
+ Enable branch switching (default: False).
109
+ - ``min_active_branches`` : float
110
+ Defines the minimum number of branches that must be turned on when
111
+ branch switching is active, i.e. the minimum number of turned on
112
+ branches is equal to ``numbranches * min_active_branches``. Has no
113
+ effect if ``branch_switching`` is set to False.
114
+ - ``use_mip_start`` : bool
115
+ Use MIP start (default: False).
116
+ - ``time_limit`` : float
117
+ Time limit for the solver (default: 0.0, no limit).
118
+ """
119
+ if not self.initialized:
120
+ self.init()
121
+ t0, _ = elapsed()
122
+
123
+ # --- solve optimization ---
124
+ t0, _ = elapsed()
125
+ res = self.solve(**kwargs)
126
+ self.converged = res['success']
127
+ self.exit_code = 0 if res['success'] else 1
128
+ _, s = elapsed(t0)
129
+ self.exec_time = float(s.split(" ")[0])
130
+ try:
131
+ n_iter = res['raw']['output']['iterations']
132
+ except Exception:
133
+ n_iter = -1
134
+ n_iter_str = f"{n_iter} iterations " if n_iter > 1 else f"{n_iter} iteration "
135
+ if self.exit_code == 0:
136
+ msg = f"<{self.class_name}> converged in {s}, "
137
+ msg += n_iter_str + "with gurobi-optimods."
138
+ logger.warning(msg)
139
+ try:
140
+ self.unpack(res)
141
+ except Exception as e:
142
+ logger.error(f"Failed to unpack results from {self.class_name}.\n{e}")
143
+ return False
144
+ self.system.report()
145
+ return True
146
+ else:
147
+ msg = f"{self.class_name} failed to converge in {s}, "
148
+ msg += n_iter_str + "with gurobi-optimods."
149
+ logger.warning(msg)
150
+ return False
ams/routines/pflow.py CHANGED
@@ -130,7 +130,7 @@ class PFlow(RoutineBase):
130
130
  _, s = elapsed(t0)
131
131
  self.exec_time = float(s.split(" ")[0])
132
132
 
133
- self.unpack()
133
+ self.unpack(res=None)
134
134
  return self.converged
135
135
 
136
136
  def _post_solve(self):
@@ -139,7 +139,7 @@ class PFlow(RoutineBase):
139
139
  """
140
140
  return True
141
141
 
142
- def unpack(self, **kwargs):
142
+ def unpack(self, res, **kwargs):
143
143
  """
144
144
  Unpack the results from ANDES PFlow routine.
145
145
  """