diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 68877f5af..b8eb5c641 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -13,7 +13,7 @@ Types of Contributions Report Bugs ~~~~~~~~~~~ -Report bugs at https://github.com/cuihantao/andes/issues. +Report bugs at https://github.com/curent/andes/issues. If you are reporting a bug, please include: @@ -42,7 +42,7 @@ or even on the web in blog posts, articles, and such. Submit Feedback ~~~~~~~~~~~~~~~ -The best way to send feedback is to file an issue at https://github.com/cuihantao/andes/issues. +The best way to send feedback is to file an issue at https://github.com/curent/andes/issues. If you are proposing a feature: @@ -97,8 +97,8 @@ Before you submit a pull request, check that it meets these guidelines: 2. If the pull request adds functionality, the docs should be updated. Put your new functionality into a function with a docstring, and add the feature to the list in README.rst. -3. The pull request should work for Python 3.6 and up. Check - https://github.com/cuihantao/andes/actions +3. The pull request should work for Python 3.8 and up. Check + https://github.com/curent/andes/actions and make sure that the tests pass for all supported Python versions. ============ diff --git a/andes/io/matpower.py b/andes/io/matpower.py index 2a6d06234..ea9be607b 100644 --- a/andes/io/matpower.py +++ b/andes/io/matpower.py @@ -272,12 +272,12 @@ def mpc2system(mpc: dict, system) -> bool: if (data[8] == 0.0) or (data[8] == 1.0 and data[9] == 0.0): # not a transformer tf = False - ratio = 1 - angle = 0 + tap_raio = 1 + phase_shift = 0 else: tf = True - ratio = data[8] - angle = data[9] * deg2rad + tap_raio = data[8] + phase_shift = data[9] * deg2rad vf = system.Bus.Vn.v[system.Bus.idx2uid(fbus)] vt = system.Bus.Vn.v[system.Bus.idx2uid(tbus)] @@ -285,7 +285,7 @@ def mpc2system(mpc: dict, system) -> bool: Vn1=vf, Vn2=vt, bus1=fbus, bus2=tbus, r=r, x=x, b=b, - trans=tf, tap=ratio, phi=angle, + trans=tf, tap=tap_raio, phi=phase_shift, rate_a=rate_a, rate_b=rate_b, rate_c=rate_c) if ('bus_name' in mpc) and (len(mpc['bus_name']) == len(system.Bus.name.v)): diff --git a/andes/io/psse.py b/andes/io/psse.py index 176637e49..178638499 100644 --- a/andes/io/psse.py +++ b/andes/io/psse.py @@ -402,8 +402,16 @@ def _parse_fshunt_v33(raw, system): def _parse_gen_v33(raw, system, sw): - # 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11, 12, 13, 14, 15, 16,17,18,19 - # I,ID,PG,QG,QT,QB,VS,IREG,MBASE,ZR,ZX,RT,XT,GTAP,STAT,RMPCT,PT,PB,O1,F1 + """ + Helper function for parsing static generator section. + """ + + # 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + # I, ID, PG, QG, QT, QB, VS, IREG, MBASE, ZR, ZX, RT, XT, GTAP, STAT, + # 15, 16, 17, 18, 19, ..., 26, 27 + # RMPCT, PT, PB, O1, F1, ..., O4, F4, WMOD, WPF + # The columns above for v33 is different from the manual of v34.5, which includes two new columns: + # `NREG`` at 8 and `BSLOD` before `O1` mva = system.config.mva out = defaultdict(list) @@ -417,6 +425,7 @@ def _parse_gen_v33(raw, system, sw): gen_mva = data[8] gen_idx += 1 status = data[14] + wmod = data[26] if len(data) >= 26 else 0 param = {'Sn': gen_mva, 'Vn': vn, 'u': status, 'bus': bus, 'subidx': subidx, @@ -428,6 +437,7 @@ def _parse_gen_v33(raw, system, sw): 'v0': data[6], 'ra': data[9], # ra - armature resistance 'xs': data[10], # xs - synchronous reactance + 'wmod': wmod, # generator control mode } if data[0] in sw.keys(): @@ -443,12 +453,14 @@ def _parse_gen_v33(raw, system, sw): def _parse_line_v33(raw, system): # + # 0,1, 2,3,4,5, 6, 7, 8, 9,10,11,12,13, 14,15,16 # I,J,CKT,R,X,B,RATEA,RATEB,RATEC,GI,BI,GJ,BJ,ST,LEN,O1,F1,...,O4,F4 # out = defaultdict(list) for data in raw['branch']: param = { + 'u': data[13], 'bus1': data[0], 'bus2': data[1], 'r': data[3], 'x': data[4], 'b': data[5], 'rate_a': data[6], 'rate_b': data[7], 'rate_c': data[8], diff --git a/andes/models/line/line.py b/andes/models/line/line.py index bec300150..167b87cdc 100644 --- a/andes/models/line/line.py +++ b/andes/models/line/line.py @@ -3,8 +3,10 @@ """ import numpy as np + from andes.core import (ModelData, IdxParam, NumParam, DataParam, Model, ExtAlgeb, ConstService) +from andes.shared import spmatrix class LineData(ModelData): @@ -241,3 +243,168 @@ def get_tf_idx(self): """ return np.array(self.idx.v)[self.istf] + + def build_y(self): + """ + Build bus admittance matrix. Store the matrix in ``self.Y``. + + Returns + ------- + Y : spmatrix + Bus admittance matrix. + """ + + nb = self.system.Bus.n + + y1 = self.u.v * (self.g1.v + self.b1.v * 1j) + y2 = self.u.v * (self.g2.v + self.b2.v * 1j) + y12 = self.u.v / (self.r.v + self.x.v * 1j) + m = self.tap.v * np.exp(1j * self.phi.v) + m2 = self.tap.v**2 + mconj = np.conj(m) + + # build self and mutual admittances into Y + self.Y = spmatrix((y12 + y1 / m2), self.a1.a, self.a1.a, (nb, nb), 'z') + self.Y -= spmatrix(y12 / mconj, self.a1.a, self.a2.a, (nb, nb), 'z') + self.Y -= spmatrix(y12 / m, self.a2.a, self.a1.a, (nb, nb), 'z') + self.Y += spmatrix(y12 + y2, self.a2.a, self.a2.a, (nb, nb), 'z') + + return self.Y + + def build_b(self, method='fdpf'): + """ + build Bp and Bpp matrices for fast decoupled power flow and DC power flow. + + Results are saved to ``self.Bp`` and ``self.Bpp`` without return. + """ + self.build_Bp(method) + self.build_Bpp(method) + + def build_Bp(self, method='fdpf'): + """ + Function for building B' matrix. + + The result is saved to ``self.Bp`` and returned + + Parameters + ---------- + method : str + Method for building B' matrix. Choose from 'fdpf', 'fdbx', 'dcpf'. + Returns + ------- + Bp : spmatrix + B' matrix. + + """ + nb = self.system.Bus.n + + if method not in ("fdpf", "fdbx", "dcpf"): + raise ValueError(f"Invalid method {method}; choose from 'fdpf', 'fdbx', 'dcpf'") + + # Build B prime matrix -- FDPF + # `y1`` neglects line charging shunt, and g1 is usually 0 in HV lines + # `y2`` neglects line charging shunt, and g2 is usually 0 in HV lines + y1 = self.u.v * self.g1.v + y2 = self.u.v * self.g2.v + + # `m` neglected tap ratio + m = np.exp(self.phi.v * 1j) + mconj = np.conj(m) + m2 = np.ones(self.n) + + if method in ('fdxb', 'dcpf'): + # neglect line resistance in Bp in XB method + y12 = self.u.v / (self.x.v * 1j) + else: + y12 = self.u.v / (self.r.v + self.x.v * 1j) + + self.Bdc = spmatrix((y12 + y1) / m2, self.a1.a, self.a1.a, (nb, nb), 'z') + self.Bdc -= spmatrix(y12 / mconj, self.a1.a, self.a2.a, (nb, nb), 'z') + self.Bdc -= spmatrix(y12 / m, self.a2.a, self.a1.a, (nb, nb), 'z') + self.Bdc += spmatrix(y12 + y2, self.a2.a, self.a2.a, (nb, nb), 'z') + self.Bdc = self.Bdc.imag() + + for item in range(nb): + if abs(self.Bdc[item, item]) == 0: + self.Bdc[item, item] = 1e-6 + 0j + + return self.Bdc + + def build_Bpp(self, method='fdpf'): + """ + Function for building B'' matrix. + + The result is saved to ``self.Bpp`` and returned + + Parameters + ---------- + method : str + Method for building B'' matrix. Choose from 'fdpf', 'fdbx', 'dcpf'. + + Returns + ------- + Bpp : spmatrix + B'' matrix. + """ + + nb = self.system.Bus.n + + if method not in ("fdpf", "fdbx", "dcpf"): + raise ValueError(f"Invalid method {method}; choose from 'fdpf', 'fdbx', 'dcpf'") + + # Build B double prime matrix + # y1 neglected line charging shunt, and g1 is usually 0 in HV lines + # y2 neglected line charging shunt, and g2 is usually 0 in HV lines + # m neglected phase shifter + y1 = self.u.v * (self.g1.v + self.b1.v * 1j) + y2 = self.u.v * (self.g2.v + self.b2.v * 1j) + + m = self.tap.v + m2 = abs(m)**2 + + if method in ('fdbx', 'fdpf', 'dcpf'): + # neglect line resistance in Bpp in BX method + y12 = self.u.v / (self.x.v * 1j) + else: + y12 = self.u.v / (self.r.v + self.x.v * 1j) + + self.Bpp = spmatrix((y12 + y1) / m2, self.a1.a, self.a1.a, (nb, nb), 'z') + self.Bpp -= spmatrix(y12 / np.conj(m), self.a1.a, self.a2.a, (nb, nb), 'z') + self.Bpp -= spmatrix(y12 / m, self.a2.a, self.a1.a, (nb, nb), 'z') + self.Bpp += spmatrix(y12 + y2, self.a2.a, self.a2.a, (nb, nb), 'z') + self.Bpp = self.Bpp.imag() + + for item in range(nb): + if abs(self.Bpp[item, item]) == 0: + self.Bpp[item, item] = 1e-6 + 0j + + return self.Bpp + + def build_Bdc(self): + """ + The MATPOWER-flavor Bdc matrix for DC power flow. Saves results to `self.Bdc`. + + The method neglects line charging and line resistance. It retains tap ratio. + + Returns + ------- + Bdc : spmatrix + Bdc matrix. + """ + + nb = self.system.Bus.n + + y12 = self.u.v / (self.x.v * 1j) + y12 = y12 / self.tap.v + + self.Bdc = spmatrix(y12, self.a1.a, self.a1.a, (nb, nb), 'z') + self.Bdc -= spmatrix(y12, self.a1.a, self.a2.a, (nb, nb), 'z') + self.Bdc -= spmatrix(y12, self.a2.a, self.a1.a, (nb, nb), 'z') + self.Bdc += spmatrix(y12, self.a2.a, self.a2.a, (nb, nb), 'z') + self.Bdc = self.Bdc.imag() + + for item in range(nb): + if abs(self.Bdc[item, item]) == 0: + self.Bdc[item, item] = 1e-6 + + return self.Bdc diff --git a/andes/models/static/pv.py b/andes/models/static/pv.py index 9c052b449..f3280719e 100644 --- a/andes/models/static/pv.py +++ b/andes/models/static/pv.py @@ -36,6 +36,19 @@ def __init__(self): self.ra = NumParam(default=0.0, info='armature resistance', tex_name='r_a') self.xs = NumParam(default=0.3, info='armature reactance', tex_name='x_s') + # `wmod`: generator model included in PSS/E file; not yet used in ANDES + # 0: conventional + # 1: renewable standard QT/ QB limits + # 2: renewable +, - Qlimits based on WPF + # 3: renewable, fixed Qlimit based on WPF + # 4: Infeed machine + self.wmod = NumParam(default=0, + info='Machine ctrl. mode [PSS/E]', + tex_name=r'w_{mod}', + unit='int', + export=False, + ) + class PVModel(Model): """ diff --git a/andes/routines/eig.py b/andes/routines/eig.py index bdcd2cff1..94363688d 100644 --- a/andes/routines/eig.py +++ b/andes/routines/eig.py @@ -103,16 +103,18 @@ def _reduce(self, fx, fy, gx, gy, Tf, dense=True): spmatrix The reduced state matrix """ - gyx = matrix(gx) - self.solver.linsolve(gy, gyx) + self.gyx = matrix(gx) + self.solver.linsolve(gy, self.gyx) Tfnz = Tf + np.ones_like(Tf) * np.equal(Tf, 0.0) iTf = spdiag((1 / Tfnz).tolist()) + self.fxy = (fx - fy * self.gyx) + if dense: - return iTf * (fx - fy * gyx) + return iTf * self.fxy else: - return sparse(iTf * (fx - fy * gyx)) + return sparse(iTf * self.fxy) def _reorder(self): """ diff --git a/andes/system.py b/andes/system.py index 379d84b02..1536cd87a 100644 --- a/andes/system.py +++ b/andes/system.py @@ -1,8 +1,8 @@ """ -System class for power system data and methods +System class for power system data and methods. """ -# [ANDES] (C)2015-2022 Hantao Cui +# [ANDES] (C)2015-2024 Hantao Cui # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/andes/variables/dae.py b/andes/variables/dae.py index 87e842cc0..6d02ece62 100644 --- a/andes/variables/dae.py +++ b/andes/variables/dae.py @@ -282,7 +282,7 @@ class DAE: +===========+=============================================+ | m | The number of algebraic variables/equations | +-----------+---------------------------------------------+ - | n | The number of algebraic variables/equations | + | n | The number of state variables/equations | +-----------+---------------------------------------------+ | o | The number of limiter state flags | +-----------+---------------------------------------------+ diff --git a/docs/source/conf.py b/docs/source/conf.py index 0b42fffac..1090116c6 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -125,7 +125,7 @@ html_context = { "github_url": "https://github.com", - "github_user": "cuihantao", + "github_user": "curent", "github_repo": "andes", "github_version": "master", "doc_path": "docs/source", diff --git a/docs/source/examples/index.rst b/docs/source/examples/index.rst index dd725326b..c3acd93fb 100644 --- a/docs/source/examples/index.rst +++ b/docs/source/examples/index.rst @@ -6,9 +6,9 @@ Examples A collection of examples are presented to supplement the tutorial. The examples below are identical to the Jupyter Notebook in the ``examples`` folder of the repository -`here `__. You +`here `__. You can run the examples in a live Jupyter Notebook online using -`Binder `__. +`Binder `__. .. toctree:: :maxdepth: 2 diff --git a/docs/source/getting_started/install.rst b/docs/source/getting_started/install.rst index 005aeaefc..64cbcbb34 100644 --- a/docs/source/getting_started/install.rst +++ b/docs/source/getting_started/install.rst @@ -112,7 +112,7 @@ from either your fork or the original repository. Clone the repository with .. code:: bash - git clone https://github.com/cuihantao/andes + git clone https://github.com/curent/andes .. note:: @@ -120,7 +120,7 @@ from either your fork or the original repository. Clone the repository with update the source code and perform version control. Alternatively, you can download the ANDES source code from -https://github.com/cuihantao/andes and extract all files to the path of your +https://github.com/curent/andes and extract all files to the path of your choice. Although works, this method is discouraged because tracking changes and pushing back code edits will require significant manual efforts. diff --git a/docs/source/getting_started/overview.rst b/docs/source/getting_started/overview.rst index 434b1025d..069afbfe9 100644 --- a/docs/source/getting_started/overview.rst +++ b/docs/source/getting_started/overview.rst @@ -34,13 +34,13 @@ ANDES is currently under active development. To get involved, * Follow the tutorial at `https://andes.readthedocs.io `_ * Checkout the Notebook examples in the - `examples folder `_ + `examples folder `_ * Try ANDES in Jupyter Notebook - `with Binder `_ + `with Binder `_ * Download the PDF manual at `download `_ * Report issues in the - `GitHub issues page `_ + `GitHub issues page `_ * Learn version control with `the command-line git `_ or `GitHub Desktop `_ diff --git a/docs/source/getting_started/testcases/index.rst b/docs/source/getting_started/testcases/index.rst index b25ce7307..b81d569bd 100644 --- a/docs/source/getting_started/testcases/index.rst +++ b/docs/source/getting_started/testcases/index.rst @@ -8,7 +8,7 @@ Test Cases ANDES ships with with test cases in the ``andes/cases`` folder. The cases can be found in the `online repository`_. -.. _`online repository`: https://github.com/cuihantao/andes/tree/master/andes/cases +.. _`online repository`: https://github.com/curent/andes/tree/master/andes/cases Summary ======= diff --git a/docs/source/getting_started/tutorial/cheatsheet.rst b/docs/source/getting_started/tutorial/cheatsheet.rst index dc5cc36a7..3caaa1ac3 100644 --- a/docs/source/getting_started/tutorial/cheatsheet.rst +++ b/docs/source/getting_started/tutorial/cheatsheet.rst @@ -5,4 +5,4 @@ A cheatsheet is available for quick lookup of supported commands. View the PDF version at -https://www.cheatography.com//cuihantao/cheat-sheets/andes-for-power-system-simulation/pdf/ +https://www.cheatography.com/cuihantao/cheat-sheets/andes-for-power-system-simulation/pdf/ diff --git a/docs/source/getting_started/tutorial/cli.rst b/docs/source/getting_started/tutorial/cli.rst index 16d78fb46..21ea30e41 100644 --- a/docs/source/getting_started/tutorial/cli.rst +++ b/docs/source/getting_started/tutorial/cli.rst @@ -252,7 +252,7 @@ Power flow If you have cloned the ANDES repository, it can be found in ``andes/cases/kundur`` in the source code folder. You can also download it from - `here `_. + `here `_. To run power flow, change to the directory containing ``kundur_full.xlsx``, and execute the following in the command line: diff --git a/docs/source/index.rst b/docs/source/index.rst index 1c317eb13..fa174d959 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -21,7 +21,7 @@ ANDES documentation .. _`Report Issues`: https://github.com/CURENT/andes/issues .. _`Q&A`: https://github.com/CURENT/andes/discussions .. _`Binary Installer`: https://pypi.org/project/andes/ -.. _`Try in Jupyter Notebooks`: https://mybinder.org/v2/gh/cuihantao/andes/master +.. _`Try in Jupyter Notebooks`: https://mybinder.org/v2/gh/curent/andes/master .. _`LTB Repository`: https://github.com/CURENT/ .. image:: /images/sponsors/CURENT_Logo_NameOnTrans.png diff --git a/docs/source/release-notes.rst b/docs/source/release-notes.rst index 4aa228f9c..d766892bc 100644 --- a/docs/source/release-notes.rst +++ b/docs/source/release-notes.rst @@ -9,6 +9,15 @@ The APIs before v3.0.0 are in beta and may change without prior notice. v1.9 Notes ========== +v1.9.2 (2024-03-25) +------------------- +- Improve PSS/E parser for the `wmod` field in the static generator + section. +- Consider line status when parsing PSS/E file. +- Added functions in `Line` for building network admittance matrix, `Bdc` + matrix for DC power flow, and `Bp` and `Bpp` matrices for fast decoupled + power flow. See ``build_y``, ``build_b`` and ``build_Bdc``. + v1.9.1 (2024-02-04) ------------------- This is a hotfix to pass ``pip check`` for KVXOPT version. diff --git a/examples/ex2.ipynb b/examples/ex2.ipynb index 421e9b934..2c651f375 100644 --- a/examples/ex2.ipynb +++ b/examples/ex2.ipynb @@ -78,7 +78,7 @@ "The ANDES xlsx file is the best supported format. Other formats can be converted to the xlsx format.\n", "\n", "See the link below for more about format conversion.\n", - "https://github.com/cuihantao/andes/blob/master/README.md#format-converter" + "https://github.com/curent/andes/blob/master/README.md#format-converter" ] }, { @@ -181,8 +181,8 @@ } ], "source": [ - "ss = andes.load(get_case('kundur/kundur_full.xlsx'), \n", - " default_config=True, \n", + "ss = andes.load(get_case('kundur/kundur_full.xlsx'),\n", + " default_config=True,\n", " setup=False)" ] }, @@ -2783,4 +2783,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} \ No newline at end of file +} diff --git a/examples/ex5.ipynb b/examples/ex5.ipynb index c9f13bf74..ee5f93233 100644 --- a/examples/ex5.ipynb +++ b/examples/ex5.ipynb @@ -13,7 +13,7 @@ "source": [ "This example notebook is a supplement to the ANDES tutorial. Make sure you have read the tutorial on using the CLI first.\n", "\n", - "A brief version can be found at https://github.com/cuihantao/andes/blob/master/README.md#run-simulations" + "A brief version can be found at https://github.com/curent/andes/blob/master/README.md#run-simulations" ] }, { @@ -1096,4 +1096,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} \ No newline at end of file +} diff --git a/setup.py b/setup.py index c7d7cb0c1..938e88fc2 100644 --- a/setup.py +++ b/setup.py @@ -82,7 +82,7 @@ def get_extra_requires(filename, add_all=True): long_description_content_type='text/markdown', author="Hantao Cui", author_email='cuihantao@gmail.com', - url='https://github.com/cuihantao/andes', + url='https://github.com/curent/andes', packages=find_packages(exclude=[]), entry_points={ 'console_scripts': [ diff --git a/tests/test_pflow_matpower.py b/tests/test_pflow_matpower.py index b6b223f21..29e0e218d 100644 --- a/tests/test_pflow_matpower.py +++ b/tests/test_pflow_matpower.py @@ -3,8 +3,9 @@ import andes import numpy as np -from andes.utils.paths import get_case +from andes.linsolvers.scipy import spmatrix_to_csc from andes.shared import deg2rad +from andes.utils.paths import get_case andes.main.config_logger(30, file=True) @@ -33,6 +34,10 @@ def test_pflow_mpc_process(self): case_path = [get_case(os.path.join('matpower', item)) for item in self.cases] andes.run(case_path, no_output=True, ncpu=2, pool=False, verbose=40, default_config=True) +# Note: +# On Ubuntu, Octave needs to be installed with `apt`. +# The Octave installed with `snap` will run into I/O error. + @unittest.skipUnless(MATPOWER_WORKING, "MATPOWER not available") class TestMATPOWEROct2Py(unittest.TestCase): @@ -46,7 +51,8 @@ def test_pflow_against_matpower_extra_test(self): ss = andes.run(andes.get_case(os.path.join("matpower", name)), no_output=True, - default_config=True) + default_config=True, + ) m.eval('clear mpc') andes.interop.matpower.to_matpower(m, 'mpc', ss) @@ -61,3 +67,23 @@ def test_pflow_against_matpower_extra_test(self): np.testing.assert_almost_equal(v_mpc, v_andes, decimal=5) np.testing.assert_almost_equal(a_mpc, a_andes, decimal=5) + + def test_Bdc_against_matpower(self): + m = start_instance() + cases = ('case5.m', 'case14.m', 'case118.m') + + for name in cases: + ss = andes.load('/home/hacui/repos/matpower/data/case14.m', + no_output=True, + default_config=True, + ) + m.eval('clear mpc') + andes.interop.matpower.to_matpower(m, 'mpc', ss) + m.eval("mpopt = mpoption('verbose', 0, 'out.all', 0);") + m.eval('Bdc = makeBdc(mpc, mpopt);') + Bdc = m.pull('Bdc') + + ss.Line.build_Bdc() + Bp = spmatrix_to_csc(ss.Line.Bdc) + + np.testing.assert_array_almost_equal((Bp + Bdc).data, np.zeros_like((Bp + Bdc).data))