ltbams 1.0.8__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 (83) 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 +192 -26
  11. ams/io/psse.py +278 -1
  12. ams/io/pypower.py +14 -0
  13. ams/main.py +2 -2
  14. ams/models/group.py +2 -70
  15. ams/models/static/pq.py +7 -3
  16. ams/opt/param.py +1 -2
  17. ams/report.py +3 -4
  18. ams/routines/__init__.py +2 -3
  19. ams/routines/acopf.py +5 -108
  20. ams/routines/dcopf.py +8 -0
  21. ams/routines/dcpf.py +1 -1
  22. ams/routines/ed.py +4 -2
  23. ams/routines/grbopt.py +150 -0
  24. ams/routines/pflow.py +2 -2
  25. ams/routines/pypower.py +631 -0
  26. ams/routines/routine.py +4 -10
  27. ams/routines/uc.py +2 -2
  28. ams/shared.py +30 -44
  29. ams/system.py +118 -2
  30. docs/source/api.rst +2 -0
  31. docs/source/getting_started/formats/matpower.rst +135 -0
  32. docs/source/getting_started/formats/pypower.rst +1 -2
  33. docs/source/getting_started/install.rst +9 -6
  34. docs/source/images/dcopf_time.png +0 -0
  35. docs/source/images/educ_pie.png +0 -0
  36. docs/source/release-notes.rst +29 -47
  37. {ltbams-1.0.8.dist-info → ltbams-1.0.10.dist-info}/METADATA +87 -47
  38. {ltbams-1.0.8.dist-info → ltbams-1.0.10.dist-info}/RECORD +58 -75
  39. {ltbams-1.0.8.dist-info → ltbams-1.0.10.dist-info}/WHEEL +1 -1
  40. tests/test_1st_system.py +1 -1
  41. tests/test_case.py +14 -14
  42. tests/test_export_csv.py +1 -1
  43. tests/test_interface.py +24 -2
  44. tests/test_io.py +125 -1
  45. tests/test_omodel.py +1 -1
  46. tests/test_report.py +6 -6
  47. tests/test_routine.py +2 -2
  48. tests/test_rtn_acopf.py +75 -0
  49. tests/test_rtn_dcopf.py +1 -1
  50. tests/test_rtn_dcopf2.py +1 -1
  51. tests/test_rtn_ed.py +9 -9
  52. tests/test_rtn_opf.py +142 -0
  53. tests/test_rtn_pflow.py +0 -72
  54. tests/test_rtn_pypower.py +315 -0
  55. tests/test_rtn_rted.py +8 -8
  56. tests/test_rtn_uc.py +18 -18
  57. ams/pypower/__init__.py +0 -8
  58. ams/pypower/_compat.py +0 -9
  59. ams/pypower/core/__init__.py +0 -8
  60. ams/pypower/core/pips.py +0 -894
  61. ams/pypower/core/ppoption.py +0 -244
  62. ams/pypower/core/ppver.py +0 -18
  63. ams/pypower/core/solver.py +0 -2451
  64. ams/pypower/eps.py +0 -6
  65. ams/pypower/idx.py +0 -174
  66. ams/pypower/io.py +0 -604
  67. ams/pypower/make/__init__.py +0 -11
  68. ams/pypower/make/matrices.py +0 -665
  69. ams/pypower/make/pdv.py +0 -506
  70. ams/pypower/routines/__init__.py +0 -7
  71. ams/pypower/routines/cpf.py +0 -513
  72. ams/pypower/routines/cpf_callbacks.py +0 -114
  73. ams/pypower/routines/opf.py +0 -1803
  74. ams/pypower/routines/opffcns.py +0 -1946
  75. ams/pypower/routines/pflow.py +0 -852
  76. ams/pypower/toggle.py +0 -1098
  77. ams/pypower/utils.py +0 -293
  78. ams/routines/cpf.py +0 -65
  79. ams/routines/dcpf0.py +0 -196
  80. ams/routines/pflow0.py +0 -113
  81. tests/test_rtn_dcpf.py +0 -77
  82. {ltbams-1.0.8.dist-info → ltbams-1.0.10.dist-info}/entry_points.txt +0 -0
  83. {ltbams-1.0.8.dist-info → ltbams-1.0.10.dist-info}/top_level.txt +0 -0
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
  """