ltbams 1.0.9__py3-none-any.whl → 1.0.11__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/documenter.py +130 -62
  7. ams/core/model.py +1 -1
  8. ams/core/symprocessor.py +1 -1
  9. ams/extension/eva.py +1 -1
  10. ams/interface.py +42 -26
  11. ams/io/matpower.py +31 -17
  12. ams/io/psse.py +278 -1
  13. ams/main.py +2 -2
  14. ams/models/group.py +2 -1
  15. ams/models/static/pq.py +7 -3
  16. ams/opt/param.py +1 -2
  17. ams/routines/__init__.py +2 -3
  18. ams/routines/acopf.py +5 -108
  19. ams/routines/dcopf.py +8 -0
  20. ams/routines/dcpf.py +1 -1
  21. ams/routines/ed.py +4 -2
  22. ams/routines/grbopt.py +155 -0
  23. ams/routines/pflow.py +2 -2
  24. ams/routines/pypower.py +622 -0
  25. ams/routines/routine.py +4 -10
  26. ams/routines/uc.py +2 -2
  27. ams/shared.py +26 -43
  28. ams/system.py +120 -4
  29. ams/utils/paths.py +3 -3
  30. docs/source/api.rst +2 -0
  31. docs/source/examples/index.rst +2 -1
  32. docs/source/genroutineref.py +1 -1
  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 +37 -57
  37. {ltbams-1.0.9.dist-info → ltbams-1.0.11.dist-info}/METADATA +85 -48
  38. {ltbams-1.0.9.dist-info → ltbams-1.0.11.dist-info}/RECORD +58 -75
  39. {ltbams-1.0.9.dist-info → ltbams-1.0.11.dist-info}/WHEEL +1 -1
  40. tests/test_1st_system.py +2 -2
  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 +50 -0
  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.9.dist-info → ltbams-1.0.11.dist-info}/entry_points.txt +0 -0
  83. {ltbams-1.0.9.dist-info → ltbams-1.0.11.dist-info}/top_level.txt +0 -0
ams/routines/grbopt.py ADDED
@@ -0,0 +1,155 @@
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.core import Config
14
+
15
+ from ams.io.pypower import system2ppc
16
+
17
+ from ams.opt import Var, Objective
18
+
19
+ from ams.routines.pypower import DCPF1
20
+ from ams.shared import opf
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class OPF(DCPF1):
26
+ """
27
+ Optimal Power Flow (OPF) routine using gurobi-optimods.
28
+
29
+ This class provides an interface for performing optimal power flow analysis
30
+ with gurobi-optimods, supporting both AC and DC OPF formulations.
31
+
32
+ In addition to optimizing generator dispatch, this routine can also optimize
33
+ transmission line statuses (branch switching), enabling topology optimization.
34
+ Refer to the gurobi-optimods documentation for further details:
35
+
36
+ https://gurobi-optimods.readthedocs.io/en/stable/mods/opf/opf.html
37
+ """
38
+
39
+ def __init__(self, system, config):
40
+ DCPF1.__init__(self, system, config)
41
+ self.info = 'Optimal Power Flow'
42
+ self.type = 'ACED'
43
+
44
+ # Overwrite the config to be empty, as it is not used in this routine
45
+ self.config = Config(self.class_name)
46
+
47
+ self.obj = Objective(name='obj',
48
+ info='total cost, placeholder',
49
+ e_str='sum(c2 * pg**2 + c1 * pg + c0)',
50
+ unit='$',
51
+ sense='min',)
52
+
53
+ self.pi = Var(info='Lagrange multiplier on real power mismatch',
54
+ name='pi', unit='$/p.u.',
55
+ model='Bus', src=None,)
56
+
57
+ self.uld = Var(info='Line commitment decision',
58
+ name='uld', tex_name=r'u_{l,d}',
59
+ model='Line', src='u',)
60
+
61
+ def solve(self, **kwargs):
62
+ ppc = system2ppc(self.system)
63
+ mat = io.BytesIO()
64
+ scipy.io.savemat(mat, {'mpc': ppc})
65
+ mat.seek(0)
66
+ res = opf.solve_opf(opf.read_case_matpower(mat), **kwargs)
67
+ return res
68
+
69
+ def unpack(self, res, **kwargs):
70
+ """
71
+ Unpack the results from the gurobi-optimods.
72
+ """
73
+ # NOTE: Map gurobi-optimods results to PPC-compatible format.
74
+ # Only relevant columns are populated, as required by `DCOPF.unpack()`.
75
+ # If future versions of gurobi-optimods provide additional outputs,
76
+ # this mapping may need to be updated to extract and assign new fields.
77
+
78
+ res_new = dict()
79
+ res_new['success'] = res['success']
80
+ res_new['et'] = res['et']
81
+ res_new['f'] = res['f']
82
+ res_new['baseMVA'] = res['baseMVA']
83
+
84
+ bus = pd.DataFrame(res['bus'])
85
+ res_new['bus'] = np.zeros((self.system.Bus.n, 17))
86
+ res_new['bus'][:, 7] = bus['Vm'].values
87
+ res_new['bus'][:, 8] = bus['Va'].values
88
+ # NOTE: As of v2.3.2, gurobi-optimods does not return LMP
89
+
90
+ gen = pd.DataFrame(res['gen'])
91
+ res_new['gen'] = np.zeros((self.system.StaticGen.n, 14))
92
+ res_new['gen'][:, 1] = gen['Pg'].values
93
+ res_new['gen'][:, 2] = gen['Qg'].values
94
+
95
+ branch = pd.DataFrame(res['branch'])
96
+ res_new['branch'] = np.zeros((self.system.Line.n, 14))
97
+ res_new['branch'][:, 13] = branch['Pf'].values
98
+ # NOTE: unpack branch_switching decision
99
+ res_new['branch'][:, 10] = branch['switching'].values
100
+ return super().unpack(res_new)
101
+
102
+ def run(self, **kwargs):
103
+ """
104
+ Run the OPF routine using gurobi-optimods.
105
+
106
+ This method invokes `gurobi-optimods.opf.solve_opf` to solve the OPF problem.
107
+
108
+ Parameters
109
+ ----------
110
+ - opftype : str
111
+ Type of OPF to solve (default: 'AC').
112
+ - branch_switching : bool
113
+ Enable branch switching (default: False).
114
+ - min_active_branches : float
115
+ Defines the minimum number of branches that must be turned on when
116
+ branch switching is active, i.e. the minimum number of turned on
117
+ branches is equal to ``numbranches * min_active_branches``. Has no
118
+ effect if ``branch_switching`` is set to False.
119
+ - use_mip_start : bool
120
+ Use MIP start (default: False).
121
+ - time_limit : float
122
+ Time limit for the solver (default: 0.0, no limit).
123
+ """
124
+ if not self.initialized:
125
+ self.init()
126
+ t0, _ = elapsed()
127
+
128
+ # --- solve optimization ---
129
+ t0, _ = elapsed()
130
+ res = self.solve(**kwargs)
131
+ self.converged = res['success']
132
+ self.exit_code = 0 if res['success'] else 1
133
+ _, s = elapsed(t0)
134
+ self.exec_time = float(s.split(" ")[0])
135
+ try:
136
+ n_iter = res['raw']['output']['iterations']
137
+ except Exception:
138
+ n_iter = -1
139
+ n_iter_str = f"{n_iter} iterations " if n_iter > 1 else f"{n_iter} iteration "
140
+ if self.exit_code == 0:
141
+ msg = f"<{self.class_name}> converged in {s}, "
142
+ msg += n_iter_str + "with gurobi-optimods."
143
+ logger.warning(msg)
144
+ try:
145
+ self.unpack(res)
146
+ except Exception as e:
147
+ logger.error(f"Failed to unpack results from {self.class_name}.\n{e}")
148
+ return False
149
+ self.system.report()
150
+ return True
151
+ else:
152
+ msg = f"{self.class_name} failed to converge in {s}, "
153
+ msg += n_iter_str + "with gurobi-optimods."
154
+ logger.warning(msg)
155
+ 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
  """