From fd6fbb3ce09c46abb2d4f4304c7c17bcd7675c0f Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Sun, 11 Oct 2020 21:40:39 -0400 Subject: [PATCH 01/57] Text update and docstring fix. --- andes/system.py | 9 ++++++--- docs/source/modeling.rst | 2 +- docs/source/tutorial.rst | 10 +++++----- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/andes/system.py b/andes/system.py index d0f84766d..b00a3171e 100644 --- a/andes/system.py +++ b/andes/system.py @@ -989,9 +989,9 @@ def find_models(self, flag: Optional[Union[str, Tuple]], skip_zero: bool = True) def dill(self): """ - Serialize generated numerical functions in `System.calls` with package `dill`. + Serialize generated numerical functions in ``System.calls`` with package ``dill``. - The serialized file will be stored to ``~/andes/calls.pkl``, where `~` is the home directory path. + The serialized file will be stored to ``~/.andes/calls.pkl``, where `~` is the home directory path. Notes ----- @@ -1008,6 +1008,9 @@ def dill(self): @staticmethod def _load_pkl(): + """ + Helper function to open and load dill-pickled functions. + """ import dill dill.settings['recurse'] = True pkl_path = get_pkl_path() @@ -1027,7 +1030,7 @@ def _load_pkl(): def undill(self): """ - Deserialize the function calls from ``~/andes.calls.pkl`` with dill. + Deserialize the function calls from ``~/.andes/calls.pkl`` with ``dill``. If no change is made to models, future calls to ``prepare()`` can be replaced with ``undill()`` for acceleration. diff --git a/docs/source/modeling.rst b/docs/source/modeling.rst index 6a058bef5..3ac7ad67b 100644 --- a/docs/source/modeling.rst +++ b/docs/source/modeling.rst @@ -48,7 +48,7 @@ It takes several seconds up to a minute to finish the generation. .. warning:: When models are modified (such as adding new models or changing equation strings), code generation needs to be executed again for consistency. It can be more conveniently triggered from command line with - ``andes prepare -qi``. + ``andes prepare -i``. .. autofunction:: andes.system.System.prepare :noindex: diff --git a/docs/source/tutorial.rst b/docs/source/tutorial.rst index 85a658fd3..b195711b2 100644 --- a/docs/source/tutorial.rst +++ b/docs/source/tutorial.rst @@ -94,14 +94,14 @@ Generated code are stored in the folder ``.andes/calls.pkl`` in your home direct In addition, ``andes selftest`` implicitly calls the code generation. If you are using ANDES as a package in the user mode, you won't need to call it again. -For developers, ``andes prepare`` needs to be called immediately following any model equation -modification. Otherwise, simulation results will not reflect the new equations and will likely lead to an error. - Option ``-q`` or ``--quick`` (enabled by default) can be used to speed up the code generation. It skips the generation of :math:`\LaTeX`-formatted equations, which are only used in documentation and the interactive mode. -Option ``-i`` or ``--incremental`` can be used to further speed up the code generation during model development. +For developers, ``andes prepare`` needs to be called immediately following any model equation +modification. Otherwise, simulation results will not reflect the new equations and will likely lead to an error. +Option ``-i`` or ``--incremental``, instead of ``-q``, can be used to further speed up the code generation +during model development. ``andes prepare -i`` only generates code for models with modified equations. andes run @@ -247,7 +247,7 @@ For example, to convert ``case5.m`` into the ``xlsx`` format, run andes run case5.m --convert xlsx -The output messages will look like +The output messages will look like :: Parsing input file CASE5 Power flow data for modified 5 bus, 5 gen case based on PJM 5-bus system From fcf8db8d2e4de955ffac7121cb5c86af712e9466 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Sun, 11 Oct 2020 21:42:03 -0400 Subject: [PATCH 02/57] Moved dill import to top level. --- andes/system.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/andes/system.py b/andes/system.py index b00a3171e..8fc653993 100644 --- a/andes/system.py +++ b/andes/system.py @@ -18,6 +18,7 @@ import os import sys import inspect +import dill from collections import OrderedDict from typing import List, Dict, Tuple, Union, Optional @@ -308,7 +309,6 @@ def _prepare_mp(self, quick=False): Function is not working. Serialization failed for `conj`. """ from andes.shared import Pool - import dill dill.settings['recurse'] = True # consistency check for group parameters and variables @@ -999,7 +999,6 @@ def dill(self): """ logger.debug("Dumping calls to calls.pkl with dill") - import dill dill.settings['recurse'] = True pkl_path = get_pkl_path() @@ -1011,7 +1010,6 @@ def _load_pkl(): """ Helper function to open and load dill-pickled functions. """ - import dill dill.settings['recurse'] = True pkl_path = get_pkl_path() From c6660be6934612654c3628746cf52ad137cb2439 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Sun, 11 Oct 2020 21:42:58 -0400 Subject: [PATCH 03/57] Removed unneeded model jit code. --- andes/system.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/andes/system.py b/andes/system.py index 8fc653993..2aeec09e9 100644 --- a/andes/system.py +++ b/andes/system.py @@ -1223,7 +1223,6 @@ def import_models(self): ``system.models['Bus']`` points the same instance as ``system.Bus``. """ - # non-JIT models for file, cls_list in file_classes.items(): for model_name in cls_list: the_module = importlib.import_module('andes.models.' + file) @@ -1236,13 +1235,6 @@ def import_models(self): group_name = self.__dict__[model_name].group self.__dict__[group_name].add_model(model_name, self.__dict__[model_name]) - # *** JIT import code *** - # import JIT models - # for file, pair in jits.items(): - # for cls, name in pair.items(): - # self.__dict__[name] = JIT(self, file, cls, name) - # *********************** - def import_routines(self): """ Import routines as defined in ``routines/__init__.py``. From cfcb09ec62a2af233e60baa64da06ded18513fbd Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Sun, 11 Oct 2020 21:44:31 -0400 Subject: [PATCH 04/57] Added docs. --- andes/system.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/andes/system.py b/andes/system.py index 2aeec09e9..80ca6fa11 100644 --- a/andes/system.py +++ b/andes/system.py @@ -1358,6 +1358,10 @@ def _store_calls(self): self.calls[name] = mdl.calls def _list2array(self): + """ + Helper function to call models' ``list2array`` method, which + usually performs memory preallocation. + """ self.call_models('list2array', self.models) def set_config(self, config=None): From 1d76564c8198c8e241c82108c4ca7a620da9e98e Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Mon, 12 Oct 2020 11:19:25 -0400 Subject: [PATCH 05/57] Patch numba jit issue on generated pycode. --- andes/core/model.py | 23 ++++++++++++++--------- andes/routines/pflow.py | 2 +- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/andes/core/model.py b/andes/core/model.py index 147ebd6d7..154171081 100644 --- a/andes/core/model.py +++ b/andes/core/model.py @@ -14,7 +14,7 @@ import os import logging from collections import OrderedDict, defaultdict -from typing import Iterable, Sized +from typing import Iterable, Sized, Callable, Union from andes.core.common import ModelFlags, JacTriplet, Config from andes.core.discrete import Discrete @@ -1621,22 +1621,27 @@ def numba_jitify(self, parallel=False, cache=False): if self.system.config.numba != 1: return - try: - import numba - except ImportError: - return - if self.flags.jited is True: return - self.calls.f = numba.jit(self.calls.f, parallel=parallel, cache=cache) - self.calls.g = numba.jit(self.calls.g, parallel=parallel, cache=cache) + self.calls.f = self._jitify_func_only(self.calls.f, parallel=parallel, cache=cache) + self.calls.g = self._jitify_func_only(self.calls.g, parallel=parallel, cache=cache) for jname in self.calls.j: - self.calls.j[jname] = numba.jit(self.calls.j[jname], parallel=parallel, cache=cache) + self.calls.j[jname] = self._jitify_func_only(self.calls.j[jname], + parallel=parallel, cache=cache) self.flags.jited = True + def _jitify_func_only(self, func: Union[Callable, None], parallel=False, cache=False): + try: + import numba + except ImportError: + return + + if func is not None: + return numba.jit(func, parallel=parallel, cache=cache) + class SymProcessor: """ diff --git a/andes/routines/pflow.py b/andes/routines/pflow.py index 064256ce8..6b02cc8b5 100644 --- a/andes/routines/pflow.py +++ b/andes/routines/pflow.py @@ -64,6 +64,7 @@ def init(self): self.y_sol = None self.system.init(self.models, routine='pflow') + logger.info('Power flow initialized.') # force precompile if numba is on - improves timing accuracy if self.system.config.numba: @@ -71,7 +72,6 @@ def init(self): self.system.g_update(self.models) self.system.j_update(models=self.models) - logger.info('Power flow initialized.') return self.system.dae.xy def nr_step(self): From 46edc1e80c61593381d06e5e7a25f54840cf51d7 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Mon, 12 Oct 2020 11:31:52 -0400 Subject: [PATCH 06/57] Style fixes following pylint. --- andes/routines/pflow.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/andes/routines/pflow.py b/andes/routines/pflow.py index 6b02cc8b5..0cc523522 100644 --- a/andes/routines/pflow.py +++ b/andes/routines/pflow.py @@ -1,3 +1,8 @@ +""" +Module for power flow calculation. +""" + +import logging from collections import OrderedDict from andes.utils.misc import elapsed @@ -5,7 +10,6 @@ from andes.variables.report import Report from andes.shared import np, matrix, sparse, newton_krylov, IP_ADD -import logging logger = logging.getLogger(__name__) @@ -162,18 +166,18 @@ def run(self, **kwargs): self.niter = 0 while True: mis = self.nr_step() - logger.info(f'{self.niter}: |F(x)| = {mis:<10g}') + logger.info('%d: |F(x)| = %.10g', self.niter, mis) if mis < self.config.tol: self.converged = True break - elif self.niter > self.config.max_iter: + if self.niter > self.config.max_iter: break - elif np.isnan(mis).any(): + if np.isnan(mis).any(): logger.error('NaN found in solution. Convergence not likely') self.niter = self.config.max_iter + 1 break - elif mis > 1e4 * self.mis[0]: + if mis > 1e4 * self.mis[0]: logger.error('Mismatch increased too fast. Convergence not likely.') break self.niter += 1 @@ -185,12 +189,12 @@ def run(self, **kwargs): max_idx = np.argmax(np.abs(system.dae.xy)) name = system.dae.xy_name[max_idx] logger.error('Mismatch is not correctable possibly due to large load-generation imbalance.') - logger.error(f'Largest mismatch on equation associated with <{name}>') + logger.error('Largest mismatch on equation associated with <%s>', name) else: - logger.error(f'Power flow failed after {self.niter + 1} iterations for {system.files.case}.') + logger.error('Power flow failed after %d iterations for "%s".', self.niter + 1, system.files.case) else: - logger.info(f'Converged in {self.niter+1} iterations in {s1}.') + logger.info('Converged in %d iterations in %s.', self.niter + 1, s1) # make a copy of power flow solutions self.x_sol = system.dae.x.copy() From b0b83114d61b62d6ba41fa1ee4a84c2c716ff37c Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Mon, 12 Oct 2020 11:47:21 -0400 Subject: [PATCH 07/57] Style fix. --- andes/routines/base.py | 4 +-- andes/routines/eig.py | 59 +++++++++++++++++++++--------------------- 2 files changed, 31 insertions(+), 32 deletions(-) diff --git a/andes/routines/base.py b/andes/routines/base.py index 15a682aa6..935087edb 100644 --- a/andes/routines/base.py +++ b/andes/routines/base.py @@ -43,8 +43,8 @@ def init(self): def run(self, **kwargs): raise NotImplementedError - def summary(self): + def summary(self, **kwargs): raise NotImplementedError - def report(self): + def report(self, **kwargs): raise NotImplementedError diff --git a/andes/routines/eig.py b/andes/routines/eig.py index bb78384d1..87c327c35 100644 --- a/andes/routines/eig.py +++ b/andes/routines/eig.py @@ -1,13 +1,18 @@ -import logging -import scipy.io +""" +Module for eigenvalue analysis. +""" +import logging from math import ceil, pi + +import scipy.io from cvxopt import mul, div, spdiag from cvxopt.lapack import gesv from andes.io.txt import dump_data from andes.utils.misc import elapsed from andes.routines.base import BaseRoutine +from andes.variables.report import report_info from andes.shared import np, matrix, spmatrix, plt, mpl from andes.plot import set_latex @@ -142,10 +147,10 @@ def run(self, **kwargs): if system.PFlow.converged is False: logger.warning('Power flow not solved. Eig analysis will not continue.') return succeed - else: - if system.TDS.initialized is False: - system.TDS.init() - system.TDS._itm_step() + + if system.TDS.initialized is False: + system.TDS.init() + system.TDS._itm_step() if system.dae.n == 0: logger.error('No dynamic model. Eig analysis will not continue.') @@ -153,7 +158,7 @@ def run(self, **kwargs): else: if sum(system.dae.Tf != 0) != len(system.dae.Tf): self.singular_idx = np.argwhere(np.equal(system.dae.Tf, 0.0)).ravel().astype(int) - logger.info(f"System contains {len(self.singular_idx)} zero time constants. ") + logger.info("System contains %d zero time constants. ", len(self.singular_idx)) logger.debug([system.dae.x_name[i] for i in self.singular_idx]) self.x_name = np.array(system.dae.x_name) @@ -183,6 +188,9 @@ def run(self, **kwargs): def plot(self, mu=None, fig=None, ax=None, left=-6, right=0.5, ymin=-8, ymax=8, damping=0.05, line_width=0.5, dpi=150, show=True, latex=True): + """ + Plot utility for eigenvalues in the S domain. + """ mpl.rc('font', family='Times New Roman', size=12) if mu is None: @@ -206,11 +214,10 @@ def plot(self, mu=None, fig=None, ax=None, left=-6, right=0.5, ymin=-8, ymax=8, if len(p_mu_real) > 0: logger.warning( - 'System is not stable due to {} positive eigenvalues.'.format( - len(p_mu_real))) + 'System is not stable due to %d positive eigenvalues.', len(p_mu_real)) else: logger.info( - 'System is small-signal stable in the initial neighbourhood.') + 'System is small-signal stable in the initial neighborhood.') set_latex(latex) @@ -257,33 +264,25 @@ def export_state_matrix(self): 'x_tex_name': np.array(system.dae.x_tex_name, dtype=np.object), } scipy.io.savemat(system.files.mat, mdict=out) - logger.info(f'State matrix saved to "{system.files.mat}".') + logger.info('State matrix saved to "%s"', system.files.mat) return True - def report(self, x_name=None): + def report(self, x_name=None, **kwargs): """ - Save eigenvalue analysis reports + Save eigenvalue analysis reports. Returns ------- None """ - from andes.variables.report import report_info - - system = self.system - mu = self.mu - part_fact = self.part_fact if x_name is None: x_name = self.x_name - text = [] - header = [] - rowname = [] - data = [] + text, header, rowname, data = list(), list(), list(), list() - neig = len(mu) - mu_real = mu.real() - mu_imag = mu.imag() + neig = len(self.mu) + mu_real = self.mu.real() + mu_imag = self.mu.imag() n_positive = sum(1 for x in mu_real if x > 0) n_zeros = sum(1 for x in mu_real if x == 0) n_negative = sum(1 for x in mu_real if x < 0) @@ -302,7 +301,7 @@ def report(self, x_name=None): freq = [0] * neig ufreq = [0] * neig damping = [0] * neig - for idx, item in enumerate(mu): + for idx, item in enumerate(self.mu): if item.imag == 0: freq[idx] = 0 ufreq[idx] = 0 @@ -315,7 +314,7 @@ def report(self, x_name=None): # obtain most associated variables var_assoc = [] for prow in range(neig): - temp_row = part_fact[prow, :] + temp_row = self.part_fact[prow, :] name_idx = list(temp_row).index(max(temp_row)) var_assoc.append(x_name[name_idx]) @@ -323,7 +322,7 @@ def report(self, x_name=None): for prow in range(neig): temp_row = [] for pcol in range(neig): - temp_row.append(round(part_fact[prow, pcol], 5)) + temp_row.append(round(self.part_fact[prow, pcol], 5)) pf.append(temp_row) # opening info section @@ -372,5 +371,5 @@ def report(self, x_name=None): rowname.append(x_name) data.append(pf[start:end]) - dump_data(text, header, rowname, data, system.files.eig) - logger.info(f'Report saved to "{system.files.eig}".') + dump_data(text, header, rowname, data, self.system.files.eig) + logger.info('Report saved to "%s".', self.system.files.eig) From c6abed77f34633c82abfb398b4664aa581bf56c7 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Mon, 12 Oct 2020 12:07:58 -0400 Subject: [PATCH 08/57] Some style fixes. --- andes/routines/tds.py | 114 +++++++++++++++++++++++------------------- 1 file changed, 63 insertions(+), 51 deletions(-) diff --git a/andes/routines/tds.py b/andes/routines/tds.py index cbb2f2f63..4ce403c55 100644 --- a/andes/routines/tds.py +++ b/andes/routines/tds.py @@ -1,7 +1,12 @@ +""" +ANDES module for time-domain simulation. +""" + import sys import os import time import importlib +import logging from collections import OrderedDict from andes.routines.base import BaseRoutine @@ -10,7 +15,6 @@ from andes.shared import tqdm, np, pd from andes.shared import matrix, sparse, spdiag -import logging logger = logging.getLogger(__name__) @@ -102,6 +106,13 @@ def __init__(self, system=None, config=None): self.qrt_start = None self.headroom = 0.0 + # internal storage for iterations + self.x0 = None + self.y0 = None + self.f0 = None + self.Ac = None + self.inc = None + def init(self): """ Initialize the status, storage and values for TDS. @@ -170,9 +181,9 @@ def init(self): _, s1 = elapsed(t0) if self.initialized is True: - logger.info(f"Initialization for dynamics was successful in {s1}.") + logger.info("Initialization for dynamics was successful in %s.", s1) else: - logger.error(f"Initialization for dynamics failed in {s1}.") + logger.error("Initialization for dynamics failed in %s.", s1) if system.dae.n == 0: tqdm.write('No dynamic component loaded.') @@ -300,7 +311,7 @@ def run(self, no_pbar=False, no_summary=False, **kwargs): # if the ending time has passed if time.time() - rt_end > 0: - logger.debug(f'Simulation over-run at t={dae.t:4.4g} s.') + logger.debug('Simulation over-run at t=%4.4g s.', dae.t) else: self.headroom += (rt_end - time.time()) @@ -320,7 +331,7 @@ def run(self, no_pbar=False, no_summary=False, **kwargs): if self.busted: logger.error(self.err_msg) - logger.error(f"Simulation terminated at t={system.dae.t:.4f}.") + logger.error("Simulation terminated at t=%.4f s.", system.dae.t) system.exit_code += 1 elif system.dae.t == self.config.tf: succeed = True # success flag @@ -329,10 +340,10 @@ def run(self, no_pbar=False, no_summary=False, **kwargs): system.exit_code += 1 _, s1 = elapsed(t0) - logger.info(f'Simulation completed in {s1}.') + logger.info('Simulation completed in %s.', s1) if config.qrt: - logger.debug(f'QRT headroom time: {self.headroom} s.') + logger.debug('QRT headroom time: %.4g s.', self.headroom) system.TDS.save_output() @@ -648,26 +659,27 @@ def test_init(self): if np.max(np.abs(system.dae.fg)) < self.config.tol: logger.debug('Initialization tests passed.') return True - else: - fail_idx = np.where(abs(system.dae.fg) >= self.config.tol) - fail_names = [system.dae.xy_name[int(i)] for i in np.ravel(fail_idx)] + # otherwise, show suspect initialization error + fail_idx = np.where(abs(system.dae.fg) >= self.config.tol) + fail_names = [system.dae.xy_name[int(i)] for i in np.ravel(fail_idx)] - title = 'Suspect initialization issue! Simulation may crash!' - err_data = {'Name': fail_names, - 'Var. Value': system.dae.xy[fail_idx], - 'Eqn. Mismatch': system.dae.fg[fail_idx], - } - tab = Tab(title=title, - header=err_data.keys(), - data=list(map(list, zip(*err_data.values())))) + title = 'Suspect initialization issue! Simulation may crash!' + err_data = {'Name': fail_names, + 'Var. Value': system.dae.xy[fail_idx], + 'Eqn. Mismatch': system.dae.fg[fail_idx], + } + tab = Tab(title=title, + header=err_data.keys(), + data=list(map(list, zip(*err_data.values()))), + ) - logger.error(tab.draw()) + logger.error(tab.draw()) - if system.options.get('verbose') == 1: - breakpoint() - system.exit_code += 1 - return False + if system.options.get('verbose') == 1: + breakpoint() + system.exit_code += 1 + return False def save_output(self, npz=True): """ @@ -685,19 +697,18 @@ def save_output(self, npz=True): """ if self.system.files.no_output: return False - else: - t0, _ = elapsed() - self.system.dae.write_lst(self.system.files.lst) + t0, _ = elapsed() - if npz is True: - self.system.dae.write_npz(self.system.files.npz) - else: - self.system.dae.write_npy(self.system.files.npy) + self.system.dae.write_lst(self.system.files.lst) + if npz is True: + self.system.dae.write_npz(self.system.files.npz) + else: + self.system.dae.write_npy(self.system.files.npy) - _, s1 = elapsed(t0) - logger.info(f'TDS outputs saved in {s1}.') - return True + _, s1 = elapsed(t0) + logger.info('TDS outputs saved in %s.', s1) + return True def do_switch(self): """ @@ -782,19 +793,21 @@ def _load_pert(self): Load perturbation files to ``self.callpert``. """ system = self.system - if system.files.pert: - if not os.path.isfile(system.files.pert): - logger.warning(f'Pert file not found at "{system.files.pert}".') - return False - - sys.path.append(system.files.case_path) - _, full_name = os.path.split(system.files.pert) - name, ext = os.path.splitext(full_name) - - module = importlib.import_module(name) - self.callpert = getattr(module, 'pert') - logger.info(f'Perturbation file "{system.files.pert}" loaded.') - return True + if not system.files.pert: + return False + + if not os.path.isfile(system.files.pert): + logger.warning('Pert file not found at "%s".', system.files.pert) + return False + + sys.path.append(system.files.case_path) + _, full_name = os.path.split(system.files.pert) + name, _ = os.path.splitext(full_name) + + module = importlib.import_module(name) + self.callpert = getattr(module, 'pert') + logger.info('Perturbation file "%s" loaded.', system.files.pert) + return True def _load_csv(self, csv_file): """ @@ -811,7 +824,7 @@ def _load_csv(self, csv_file): data = df.to_numpy() if data.ndim != 2: - raise ValueError("Data from CSV is not 2-dimentional (time versus variable)") + raise ValueError("Data from CSV is not 2-dimensional (time versus variable)") if data.shape[0] < 2: logger.warning("CSV data does not contain more than one time step.") @@ -831,7 +844,8 @@ def _debug_g(self, y_idx): Index of the equation into the `g` array. Diff. eqns. are not counted in. """ y_idx = y_idx.tolist() - logger.debug(f'Max. algebraic mismatch associated with {self.system.dae.y_name[y_idx]} [y_idx={y_idx}]') + logger.debug('Max. algebraic mismatch associated with <%s> [y_idx=%d]', + self.system.dae.y_name[y_idx], y_idx) assoc_vars = self.system.dae.gy[y_idx, :] vars_idx = np.where(np.ravel(matrix(assoc_vars)))[0] @@ -839,9 +853,7 @@ def _debug_g(self, y_idx): logger.debug(f'{"y_index":<10} {"Variable":<20} {"Derivative":<20}') for v in vars_idx: v = v.tolist() - logger.debug(f'{v:<10} {self.system.dae.y_name[v]:<20} {assoc_vars[v]:<20g}') - - pass + logger.debug('%10d %20s %20g', v, self.system.dae.y_name[v], assoc_vars[v]) def _debug_ac(self, xy_idx): """ From 41813765c8f223fe46428b3e2d9c24edfbd4fbe4 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Mon, 12 Oct 2020 12:49:17 -0400 Subject: [PATCH 09/57] Style fixes for links. --- andes/cases/GBnetwork/README.md | 2 +- andes/cases/ieee14/README.md | 2 +- andes/cases/nordic44/README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/andes/cases/GBnetwork/README.md b/andes/cases/GBnetwork/README.md index ac10babbd..c24e2574f 100644 --- a/andes/cases/GBnetwork/README.md +++ b/andes/cases/GBnetwork/README.md @@ -1,4 +1,4 @@ GBnetwork power flow case downloaded from -https://www.maths.ed.ac.uk/optenergy/NetworkData/fullGB/ + Dynamic data is randomly generated and does not represent the actual system. diff --git a/andes/cases/ieee14/README.md b/andes/cases/ieee14/README.md index ff1fba78b..859425af2 100644 --- a/andes/cases/ieee14/README.md +++ b/andes/cases/ieee14/README.md @@ -2,7 +2,7 @@ IEEE 14-Bus System Power flow data is created by J. Conto. -Data is available at https://drive.google.com/drive/folders/0B7uS9L2Woq_7YzYzcGhXT2VQYXc +Data is available at Dynamic data is created by H. Cui for ANDES. diff --git a/andes/cases/nordic44/README.md b/andes/cases/nordic44/README.md index 97b1b1f2b..68d43081c 100644 --- a/andes/cases/nordic44/README.md +++ b/andes/cases/nordic44/README.md @@ -1,5 +1,5 @@ Nordic 44-Bus System -Source: https://github.com/ALSETLab/Nordic44-Nordpool/tree/master/nordic44/models +Source: Most dynamic models in this system have not been supported. From 6eecf2504a3771988357bda843b85ad3bcf68dc8 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Mon, 12 Oct 2020 12:57:54 -0400 Subject: [PATCH 10/57] Style fixes. --- andes/core/service.py | 11 +++++++++++ andes/io/psse.py | 2 +- andes/plot.py | 2 +- andes/utils/func.py | 4 ++-- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/andes/core/service.py b/andes/core/service.py index 254011601..026426cda 100644 --- a/andes/core/service.py +++ b/andes/core/service.py @@ -173,6 +173,9 @@ def __init__(self, self.u = dummify(u) def check(self, **kwargs): + """ + Check status and set event flags. + """ if not np.all(self.v == self.u.v): self.owner.system.TDS.custom_event = True logger.debug(f"Event flag set at t={self.owner.system.dae.t:.6f} sec.") @@ -255,6 +258,9 @@ def __init__(self, self.n_ext = 0 # number of extended events def assign_memory(self, n): + """ + Assign memory for internal data. + """ VarService.assign_memory(self, n) self.t_final = np.zeros_like(self.v) self.v_event = np.zeros_like(self.v) @@ -265,6 +271,11 @@ def assign_memory(self, n): self.t_ext.v = np.ones_like(self.u.v) * self.t_ext.v def check(self, **kwargs): + """ + Check if an extended event is in place. + + Supplied as a ``v_numeric`` to ``VarService``. + """ dae_t = self.owner.system.dae.t if dae_t == 0.0: diff --git a/andes/io/psse.py b/andes/io/psse.py index 6181e6628..d32abb215 100644 --- a/andes/io/psse.py +++ b/andes/io/psse.py @@ -153,7 +153,7 @@ def _read_dyr_dict(file): # concatenate multi-line device data input_concat_dict = defaultdict(list) multi_line = list() - for i, line in enumerate(input_list): + for line in input_list: if line == '': continue if '/' not in line: diff --git a/andes/plot.py b/andes/plot.py index dc0e16a12..33f143890 100644 --- a/andes/plot.py +++ b/andes/plot.py @@ -416,7 +416,7 @@ def guess_event_time(self): def bqplot_data(self, xdata, ydata, *, xheader=None, yheader=None, xlabel=None, ylabel=None, left=None, right=None, ymin=None, ymax=None, legend=True, grid=False, fig=None, latex=True, dpi=150, line_width=1.0, greyscale=False, savefig=None, save_format=None, - show=True, title=None, + title=None, **kwargs): """ Plot with ``bqplot``. Experimental and incomplete. diff --git a/andes/utils/func.py b/andes/utils/func.py index 173d39dde..f2f36a94b 100644 --- a/andes/utils/func.py +++ b/andes/utils/func.py @@ -10,8 +10,8 @@ def list_flatten(input_list): """ if len(input_list) > 0 and isinstance(input_list[0], (list, np.ndarray)): return functools.reduce(operator.iconcat, input_list, []) - else: - return input_list + + return input_list def interp_n2(t, x, y): From 0638961e1206f7373a518687aefb45dd19e06fcc Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Thu, 15 Oct 2020 11:50:34 -0400 Subject: [PATCH 11/57] Minor debug msg edit. --- andes/routines/tds.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/andes/routines/tds.py b/andes/routines/tds.py index 4ce403c55..ca7b16c90 100644 --- a/andes/routines/tds.py +++ b/andes/routines/tds.py @@ -872,8 +872,8 @@ def _debug_ac(self, xy_idx): eqns_idx = np.where(np.ravel(matrix(assoc_eqns)))[0] vars_idx = np.where(np.ravel(matrix(assoc_vars)))[0] - logger.debug(f'Max. correction is for variable {self.system.dae.xy_name[xy_idx]} [{xy_idx}]') - logger.debug(f'Associated equation value is {self.system.dae.fg[xy_idx]:<20g}') + logger.debug('Max. correction is for variable %s [%d]', self.system.dae.xy_name[xy_idx], xy_idx) + logger.debug('Associated equation value is %20g', self.system.dae.fg[xy_idx]) logger.debug('') logger.debug(f'{"xy_index":<10} {"Equation":<20} {"Derivative":<20} {"Eq. Mismatch":<20}') From 0162269277a1c29e6c5deedaac47da58daddcedb Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Fri, 16 Oct 2020 23:28:34 -0400 Subject: [PATCH 12/57] Changes `_itm_step` to `itm_step`. --- andes/routines/eig.py | 2 +- andes/routines/tds.py | 23 ++++++++++++++--------- examples/5. profiling.ipynb | 4 ++-- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/andes/routines/eig.py b/andes/routines/eig.py index 87c327c35..ec3d5eba9 100644 --- a/andes/routines/eig.py +++ b/andes/routines/eig.py @@ -150,7 +150,7 @@ def run(self, **kwargs): if system.TDS.initialized is False: system.TDS.init() - system.TDS._itm_step() + system.TDS.itm_step() if system.dae.n == 0: logger.error('No dynamic model. Eig analysis will not continue.') diff --git a/andes/routines/tds.py b/andes/routines/tds.py index ca7b16c90..9250ef02d 100644 --- a/andes/routines/tds.py +++ b/andes/routines/tds.py @@ -178,6 +178,11 @@ def init(self): # if `dae.n == 1`, `calc_h_first` depends on new `dae.gy` self.calc_h() + # allocate for internal variables + self.x0 = np.zeros_like(system.dae.x) + self.y0 = np.zeros_like(system.dae.y) + self.f0 = np.zeros_like(system.dae.f) + _, s1 = elapsed(t0) if self.initialized is True: @@ -281,7 +286,7 @@ def run(self, no_pbar=False, no_summary=False, **kwargs): step_status = False # call the stepping method of the integration method (or data replay) if self.data_csv is None: - step_status = self._itm_step() # compute for the current step + step_status = self.itm_step() # compute for the current step else: step_status = self._csv_step() @@ -357,7 +362,7 @@ def run(self, no_pbar=False, no_summary=False, **kwargs): return succeed - def _itm_step(self): + def itm_step(self): """ Integrate with Implicit Trapezoidal Method (ITM) to the current time. @@ -377,12 +382,12 @@ def _itm_step(self): self.niter = 0 self.converged = False - self.x0 = np.array(dae.x) - self.y0 = np.array(dae.y) - self.f0 = np.array(dae.f) + self.x0[:] = dae.x + self.y0[:] = dae.y + self.f0[:] = dae.f while True: - self._fg_update(models=system.exist.pflow_tds) + self.fg_update(models=system.exist.pflow_tds) # lazy Jacobian update @@ -647,7 +652,7 @@ def test_init(self): Update f and g to see if initialization is successful. """ system = self.system - self._fg_update(system.exist.pflow_tds) + self.fg_update(system.exist.pflow_tds) system.j_update(models=system.exist.pflow_tds) # warn if variables are initialized at limits @@ -751,7 +756,7 @@ def do_switch(self): return ret - def _fg_update(self, models): + def fg_update(self, models): """ Update `f` and `g` equations. """ @@ -784,7 +789,7 @@ def _fg_wrapper(self, xy): system.dae.y[:] = xy[system.dae.n:] system.vars_to_models() - self._fg_update(system.exist.pflow_tds) + self.fg_update(system.exist.pflow_tds) return system.dae.fg diff --git a/examples/5. profiling.ipynb b/examples/5. profiling.ipynb index eec9ec03c..0322067b5 100644 --- a/examples/5. profiling.ipynb +++ b/examples/5. profiling.ipynb @@ -101,7 +101,7 @@ "\n", " ncalls tottime percall cumtime percall filename:lineno(function)\n", " 1 0.007 0.007 1.597 1.597 /Users/hcui7/repos/andes/andes/routines/tds.py:160(run)\n", - " 602 0.104 0.000 1.533 0.003 /Users/hcui7/repos/andes/andes/routines/tds.py:251(_itm_step)\n", + " 602 0.104 0.000 1.533 0.003 /Users/hcui7/repos/andes/andes/routines/tds.py:251(itm_step)\n", " 2318 0.010 0.000 1.141 0.000 /Users/hcui7/repos/andes/andes/routines/tds.py:590(_fg_update)\n", " 11649 0.092 0.000 1.085 0.000 /Users/hcui7/repos/andes/andes/system.py:1014(call_models)\n", " 2323 0.002 0.000 0.596 0.000 /Users/hcui7/repos/andes/andes/system.py:700(g_update)\n", @@ -375,7 +375,7 @@ } ], "source": [ - "%lprun -f ss.TDS._itm_step ss.TDS.run()" + "%lprun -f ss.TDS.itm_step ss.TDS.run()" ] }, { From 0c3045e3fa2baa4b43120af23686bfd93a6cc90d Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Fri, 16 Oct 2020 23:29:25 -0400 Subject: [PATCH 13/57] Added notes. --- docs/source/release-notes.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/source/release-notes.rst b/docs/source/release-notes.rst index 463ef25ff..9866607bc 100644 --- a/docs/source/release-notes.rst +++ b/docs/source/release-notes.rst @@ -8,6 +8,9 @@ The APIs before v3.0.0 are in beta and may change without prior notice. v1.2 Notes ---------- +v1.2.2 +``````````````````` +- Renamed `TDS._itm_step` to `TDS.itm_step` as a public API. v1.2.1 (2020-10-11) ``````````````````` From 07e43cdaa11c14af647231f100df1b3191b46913 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Sun, 18 Oct 2020 16:41:07 -0400 Subject: [PATCH 14/57] Patch an issue for pert path. --- andes/routines/tds.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/andes/routines/tds.py b/andes/routines/tds.py index 9250ef02d..59635bcdf 100644 --- a/andes/routines/tds.py +++ b/andes/routines/tds.py @@ -805,8 +805,10 @@ def _load_pert(self): logger.warning('Pert file not found at "%s".', system.files.pert) return False - sys.path.append(system.files.case_path) - _, full_name = os.path.split(system.files.pert) + pert_path, full_name = os.path.split(system.files.pert) + logger.debug('Pert file "%s" located at path %s', full_name, pert_path) + + sys.path.append(pert_path) name, _ = os.path.splitext(full_name) module = importlib.import_module(name) From 4ba92b2c5aa2ed7a6b94ac2372505a21017e3117 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Sun, 18 Oct 2020 17:57:31 -0400 Subject: [PATCH 15/57] Added tests for pert path. --- andes/cases/ieee14/pert.py | 7 +++++++ tests/test_paths.py | 28 ++++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 andes/cases/ieee14/pert.py diff --git a/andes/cases/ieee14/pert.py b/andes/cases/ieee14/pert.py new file mode 100644 index 000000000..ae10577b5 --- /dev/null +++ b/andes/cases/ieee14/pert.py @@ -0,0 +1,7 @@ +""" +Empty pert file +""" + + +def pert(t, system): + pass diff --git a/tests/test_paths.py b/tests/test_paths.py index ee0b67a8c..8761529c6 100644 --- a/tests/test_paths.py +++ b/tests/test_paths.py @@ -8,16 +8,36 @@ class TestPaths(unittest.TestCase): def setUp(self) -> None: self.kundur = 'kundur/' self.matpower = 'matpower/' + self.ieee14 = andes.get_case("ieee14/ieee14.raw") def test_tree(self): list_cases(self.kundur, no_print=True) list_cases(self.matpower, no_print=True) def test_addfile_path(self): - ieee14 = andes.get_case("ieee14/ieee14.raw") - path, case = os.path.split(ieee14) - andes.load('ieee14.raw', addfile='ieee14.dyr', input_path=path, default_config=True) + path, case = os.path.split(self.ieee14) + andes.load('ieee14.raw', addfile='ieee14.dyr', + input_path=path, default_config=True, + ) - andes.run('ieee14.raw', addfile='ieee14.dyr', input_path=path, + andes.run('ieee14.raw', addfile='ieee14.dyr', + input_path=path, no_output=True, default_config=True, ) + + def test_pert_file(self): + path, case = os.path.split(self.ieee14) + + # --- with pert file --- + ss = andes.run('ieee14.raw', pert='pert.py', + input_path=path, no_output=True, default_config=True, + ) + ss.TDS.init() + self.assertIsNotNone(ss.TDS.callpert) + + # --- without pert file --- + ss = andes.run('ieee14.raw', + input_path=path, no_output=True, default_config=True, + ) + ss.TDS.init() + self.assertIsNone(ss.TDS.callpert) From 67d033f2500959b0ac90726e6bdbb5d9eafae47c Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Mon, 19 Oct 2020 15:48:17 -0400 Subject: [PATCH 16/57] Moved PVD1 to distributed.py --- andes/models/__init__.py | 5 +- andes/models/distributed.py | 221 ++++++++++++++++++++++++++++++++++++ andes/models/renewable.py | 170 --------------------------- 3 files changed, 224 insertions(+), 172 deletions(-) create mode 100644 andes/models/distributed.py diff --git a/andes/models/__init__.py b/andes/models/__init__.py index 6becf2429..b2cb57a40 100644 --- a/andes/models/__init__.py +++ b/andes/models/__init__.py @@ -1,4 +1,4 @@ -from collections import OrderedDict # NOQA +from collections import OrderedDict # Notes: @@ -22,6 +22,7 @@ ('coi', ['COI', ]), ('dcbase', ['Node', 'Ground', 'R', 'L', 'C', 'RCp', 'RCs', 'RLs', 'RLCs', 'RLCp']), ('vsc', ['VSCShunt']), - ('renewable', ['REGCA1', 'REECA1', 'REPCA1', 'WTDTA1', 'WTDS', 'WTARA1', 'WTPTA1', 'WTTQA1', 'PVD1']), + ('renewable', ['REGCA1', 'REECA1', 'REPCA1', 'WTDTA1', 'WTDS', 'WTARA1', 'WTPTA1', 'WTTQA1']), + ('distributed', ['PVD1']), ('experimental', ['PI2', 'TestDB1', 'TestPI', 'TestLagAWFreeze']), ]) diff --git a/andes/models/distributed.py b/andes/models/distributed.py new file mode 100644 index 000000000..55267be05 --- /dev/null +++ b/andes/models/distributed.py @@ -0,0 +1,221 @@ +""" +Distributed energy resource models. +""" + +from andes.core.model import Model, ModelData +from andes.core.param import NumParam, IdxParam +from andes.core.block import Lag, GainLimiter, DeadBand1 # NOQA +from andes.core.var import ExtAlgeb, ExtState, Algeb, State # NOQA + +from andes.core.service import ConstService, ExtService, FlagValue, DataSelect, DeviceFinder # NOQA +from andes.core.service import VarService, Replace # NOQA +from andes.core.service import NumSelect # NOQA +from andes.core.discrete import Switcher, Limiter # NOQA + + +class PVD1Data(ModelData): + """ + Data for distributed PV. + """ + def __init__(self): + ModelData.__init__(self) + + self.bus = IdxParam(model='Bus', + info="interface bus id", + mandatory=True, + ) + + self.gen = IdxParam(info="static generator index", + mandatory=True, + ) + + self.Sn = NumParam(default=100.0, tex_name='S_n', + info='Model MVA base', + unit='MVA', + ) + + self.busf = IdxParam(info='Optional BusFreq idx', + model='BusFreq', + default=None, + ) + + self.xc = NumParam(default=0.0, tex_name='x_c', + info='coupling reactance', + unit='p.u.', + z=True, + ) + + # --- parameters found from ESIG.energy --- + self.igreg = IdxParam(model='Bus', + info='Remote bus idx for droop response, None for local', + ) + + self.qmx = NumParam(default=0.33, tex_name='q_{mx}', + info='Max. reactive power command', + power=True, + ) + + self.qmn = NumParam(default=-0.33, tex_name='q_{mn}', + info='Min. reactive power command', + power=True, + ) + + self.v0 = NumParam(default=0.0, tex_name='v_0', + info='Lower limit of deadband for Vdroop response', + unit='pu', + ) + self.v1 = NumParam(default=0.0, tex_name='v_1', + info='Upper limit of deadband for Vdroop response', + unit='pu', + ) + + self.dqdv = NumParam(default=0.0, tex_name='dq/dv', + info='Q-V droop characteristics', + power=True, + ) + + self.fdbd = NumParam(default=-0.01, tex_name='f_{dbd}', + info='frequency deviation deadband', + ) + + self.ddn = NumParam(default=0.0, tex_name='D_{dn}', + info='Gain after f deadband', + ) + + self.ialim = NumParam(default=1.3, tex_name='I_{alim}', + info='Apparent power limit', + current=True, + ) + + self.vt0 = NumParam(default=0.88, tex_name='V_{t0}', + info='Voltage tripping response curve point 0', + ) + + self.vt1 = NumParam(default=0.90, tex_name='V_{t1}', + info='Voltage tripping response curve point 1', + ) + + self.vt2 = NumParam(default=1.1, tex_name='V_{t2}', + info='Voltage tripping response curve point 2', + ) + + self.vt3 = NumParam(default=1.2, tex_name='V_{t3}', + info='Voltage tripping response curve point 3', + ) + + self.vrflag = NumParam(default=0.0, tex_name='z_{VR}', + info='Voltage tripping is latching (0) or partially self-resetting (0-1)', + ) + + self.ft0 = NumParam(default=59.5, tex_name='f_{t0}', + info='Frequency tripping response curve point 0', + ) + + self.ft1 = NumParam(default=59.7, tex_name='f_{t1}', + info='Frequency tripping response curve point 1', + ) + + self.ft2 = NumParam(default=60.3, tex_name='f_{t2}', + info='Frequency tripping response curve point 2', + ) + + self.ft3 = NumParam(default=60.5, tex_name='f_{t3}', + info='Frequency tripping response curve point 3', + ) + + self.frflag = NumParam(default=0.0, tex_name='z_{FR}', + info='Frequency tripping is latching (0) or partially self-resetting (0-1)', + ) + + self.tip = NumParam(default=0.02, tex_name='T_{ip}', + info='Inverter active current lag time constant', + unit='s', + ) + + self.tiq = NumParam(default=0.02, tex_name='T_{iq}', + info='Inverter reactive current lag time constant', + unit='s', + ) + + +class PVD1Model(Model): + """ + Model implementation of PVD1. + """ + def __init__(self, system, config): + Model.__init__(self, system, config) + + self.flags.tds = True + self.group = 'DG' + + self.buss = DataSelect(self.igreg, self.bus, + info='selected bus (bus or igreg)', + ) + + self.busfreq = DeviceFinder(self.busf, link=self.buss, idx_name='bus') + + # initial voltages from selected bus + self.v = ExtAlgeb(model='Bus', src='v', indexer=self.buss, tex_name='V', + info='bus (or igreg) terminal voltage', + unit='p.u.', + ) + + self.a = ExtAlgeb(model='Bus', src='a', indexer=self.buss, tex_name=r'\theta', + info='bus (or igreg) phase angle', + unit='rad.', + ) + + self.p0 = ExtService(model='StaticGen', + src='p', + indexer=self.gen, + tex_name='P_0', + ) + self.q0 = ExtService(model='StaticGen', + src='q', + indexer=self.gen, + tex_name='Q_0', + ) + # frequency measurement variable `f` + self.f = ExtAlgeb(model='FreqMeasurement', src='f', indexer=self.busfreq, export=False, + info='Bus frequency', unit='p.u.', + ) + + # --- frequency branch --- + self.FL1 = Limiter(u=self.f, lower=self.ft0, upper=self.ft1, + info='Under frequency comparer', + ) + self.FL2 = Limiter(u=self.f, lower=self.ft2, upper=self.ft3, + info='Over frequency comparer', + ) + + self.Kft01 = ConstService(v_str='1/(ft1 - ft0)', tex_name='K_{ft01}') + + self.Ffl = Algeb(info='Coeff. for under frequency', + v_str='FL1_zi * Kft01 * (f - ft0) + FL1_zu', + e_str='FL1_zi * Kft01 * (f - ft0) + FL1_zu - Ffl', + tex_name='F_{fl}', + discrete=self.FL1, + ) + + self.Kft23 = ConstService(v_str='-1/(ft2 - ft3)', tex_name='K_{ft23}') + + self.Ffh = Algeb(info='Coeff. for over frequency', + v_str='FL2_zl + FL2_zi * (1 + Kft23 * (ft2 - f))', + e_str='FL2_zl + FL2_zi * (1 + Kft23 * (ft2 - f)) - Ffh', + tex_name='F_{fh}', + discrete=self.FL2, + ) + + +class PVD1(PVD1Data, PVD1Model): + """ + Distributed PV model. (TODO: work in progress) + + Power rating specified in `Sn`. + + Reference: ESIG, WECC Distributed and Small PV Plants Generic Model (PVD1), [Online], + Available: https://www.esig.energy/wiki-main-page/wecc-distributed-and-small-pv-plants-generic-model-pvd1/ + """ + def __init__(self, system, config): + PVD1Data.__init__(self) + PVD1Model.__init__(self, system, config) diff --git a/andes/models/renewable.py b/andes/models/renewable.py index d37a796a5..b073f74fc 100644 --- a/andes/models/renewable.py +++ b/andes/models/renewable.py @@ -2102,173 +2102,3 @@ class WTTQA1(WTTQA1Data, WTTQA1Model): def __init__(self, config, system): WTTQA1Data.__init__(self) WTTQA1Model.__init__(self, system, config) - - -class PVD1Data(ModelData): - """ - Data for distributed PV. - """ - def __init__(self): - ModelData.__init__(self) - - self.bus = IdxParam(model='Bus', - info="interface bus id", - mandatory=True, - ) - - self.gen = IdxParam(info="static generator index", - mandatory=True, - ) - - self.Sn = NumParam(default=100.0, tex_name='S_n', - info='Model MVA base', - unit='MVA', - ) - - self.xc = NumParam(default=0.0, tex_name='x_c', - info='coupling reactance', - unit='p.u.', - z=True, - ) - - self.igreg = IdxParam(model='Bus', - info='Remote bus idx for droop response, None for local', - ) - - self.qmx = NumParam(default=0.33, tex_name='q_{mx}', - info='Max. reactive power command', - power=True, - ) - - self.qmn = NumParam(default=-0.33, tex_name='q_{mn}', - info='Min. reactive power command', - power=True, - ) - - self.v0 = NumParam(default=0.0, tex_name='v_0', - info='Lower limit of deadband for Vdroop response', - unit='pu', - ) - self.v1 = NumParam(default=0.0, tex_name='v_1', - info='Upper limit of deadband for Vdroop response', - unit='pu', - ) - - self.dqdv = NumParam(default=0.0, tex_name='dq/dv', - info='Q-V droop characteristics', - power=True, - ) - - self.fdbd = NumParam(default=-0.01, tex_name='f_{dbd}', - info='frequency deviation deadband', - ) - - self.ddn = NumParam(default=0.0, tex_name='D_{dn}', - info='Gain after f deadband', - ) - - self.ialim = NumParam(default=1.3, tex_name='I_{alim}', - info='Apparent power limit', - current=True, - ) - - self.vt0 = NumParam(default=0.88, tex_name='V_{t0}', - info='Voltage tripping response curve point 0', - ) - - self.vt1 = NumParam(default=0.90, tex_name='V_{t1}', - info='Voltage tripping response curve point 1', - ) - - self.vt2 = NumParam(default=1.1, tex_name='V_{t2}', - info='Voltage tripping response curve point 2', - ) - - self.vt3 = NumParam(default=1.2, tex_name='V_{t3}', - info='Voltage tripping response curve point 3', - ) - - self.vrflag = NumParam(default=0.0, tex_name='z_{VR}', - info='Voltage tripping is latching (0) or partially self-resetting (0-1)', - ) - - self.ft0 = NumParam(default=59.5, tex_name='f_{t0}', - info='Frequency tripping response curve point 0', - ) - - self.ft1 = NumParam(default=59.7, tex_name='f_{t1}', - info='Frequency tripping response curve point 1', - ) - - self.ft2 = NumParam(default=60.3, tex_name='f_{t2}', - info='Frequency tripping response curve point 2', - ) - - self.ft3 = NumParam(default=60.5, tex_name='f_{t3}', - info='Frequency tripping response curve point 3', - ) - - self.frflag = NumParam(default=0.0, tex_name='z_{FR}', - info='Frequency tripping is latching (0) or partially self-resetting (0-1)', - ) - - self.tip = NumParam(default=0.02, tex_name='T_{ip}', - info='Inverter active current lag time constant', - unit='s', - ) - - self.tiq = NumParam(default=0.02, tex_name='T_{iq}', - info='Inverter reactive current lag time constant', - unit='s', - ) - - -class PVD1Model(Model): - """ - Model implementation of PVD1. - """ - def __init__(self, system, config): - Model.__init__(self, system, config) - - self.flags.tds = True - self.group = 'DG' - - self.buss = DataSelect(self.igreg, self.bus, - info='selected bus (bus or igreg)', - ) - - # initial voltages from selected bus - self.v = ExtAlgeb(model='Bus', src='v', indexer=self.buss, tex_name='V', - info='bus (or igreg) terminal voltage', - unit='p.u.', - ) - - self.a = ExtAlgeb(model='Bus', src='a', indexer=self.buss, tex_name=r'\theta', - info='bus (or igreg) phase angle', - unit='rad.', - ) - - self.p0 = ExtService(model='StaticGen', - src='p', - indexer=self.gen, - tex_name='P_0', - ) - self.q0 = ExtService(model='StaticGen', - src='q', - indexer=self.gen, - tex_name='Q_0', - ) - - -class PVD1(PVD1Data, PVD1Model): - """ - Distributed PV model. (TODO: work in progress) - - Power rating specified in `Sn`. - - Reference: ESIG, WECC Distributed and Small PV Plants Generic Model (PVD1), [Online], - Available: https://www.esig.energy/wiki-main-page/wecc-distributed-and-small-pv-plants-generic-model-pvd1/ - """ - def __init__(self, system, config): - PVD1Data.__init__(self) - PVD1Model.__init__(self, system, config) From 369dd7ea87789379c6c0eadf0626c82a494307f5 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Mon, 19 Oct 2020 16:33:05 -0400 Subject: [PATCH 17/57] PVD1 implements frequency droop. --- andes/models/distributed.py | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/andes/models/distributed.py b/andes/models/distributed.py index 55267be05..ca1f3f11e 100644 --- a/andes/models/distributed.py +++ b/andes/models/distributed.py @@ -34,6 +34,11 @@ def __init__(self): unit='MVA', ) + self.fn = NumParam(default=60.0, tex_name='f_n', + info='nominal frequency', + unit='Hz', + ) + self.busf = IdxParam(info='Optional BusFreq idx', model='BusFreq', default=None, @@ -180,19 +185,26 @@ def __init__(self, system, config): info='Bus frequency', unit='p.u.', ) + self.fHz = Algeb(info='frequency in Hz', + v_str='fn * f', e_str='fn * f - fHz', + unit='Hz', + ) + # --- frequency branch --- - self.FL1 = Limiter(u=self.f, lower=self.ft0, upper=self.ft1, + self.FL1 = Limiter(u=self.fHz, lower=self.ft0, upper=self.ft1, info='Under frequency comparer', ) - self.FL2 = Limiter(u=self.f, lower=self.ft2, upper=self.ft3, + self.FL2 = Limiter(u=self.fHz, lower=self.ft2, upper=self.ft3, info='Over frequency comparer', ) + self.FL1.no_warn = True + self.FL2.no_warn = True self.Kft01 = ConstService(v_str='1/(ft1 - ft0)', tex_name='K_{ft01}') self.Ffl = Algeb(info='Coeff. for under frequency', - v_str='FL1_zi * Kft01 * (f - ft0) + FL1_zu', - e_str='FL1_zi * Kft01 * (f - ft0) + FL1_zu - Ffl', + v_str='FL1_zi * Kft01 * (fHz - ft0) + FL1_zu', + e_str='FL1_zi * Kft01 * (fHz - ft0) + FL1_zu - Ffl', tex_name='F_{fl}', discrete=self.FL1, ) @@ -200,11 +212,23 @@ def __init__(self, system, config): self.Kft23 = ConstService(v_str='-1/(ft2 - ft3)', tex_name='K_{ft23}') self.Ffh = Algeb(info='Coeff. for over frequency', - v_str='FL2_zl + FL2_zi * (1 + Kft23 * (ft2 - f))', - e_str='FL2_zl + FL2_zi * (1 + Kft23 * (ft2 - f)) - Ffh', + v_str='FL2_zl + FL2_zi * (1 + Kft23 * (ft2 - fHz))', + e_str='FL2_zl + FL2_zi * (1 + Kft23 * (ft2 - fHz)) - Ffh', tex_name='F_{fh}', discrete=self.FL2, ) + self.Fdev = Algeb(info='Frequency deviation', + v_str='fn - fHz', e_str='fn - fHz - Fdev', + unit='Hz', tex_name='f_{dev}', + ) + + self.Dfdbd = ConstService(v_str='fdbd * ddn', info='Deadband lower limit after gain', + tex_name='D_{fdbd}', + ) + self.DB = DeadBand1(u=self.Fdev, center=0.0, lower=self.Dfdbd, upper=0.0, + info='frequency deviation deadband with gain', + ) # outputs `Pdrp` + self.DB.db.no_warn = True class PVD1(PVD1Data, PVD1Model): From 94a27971ad4c2140da3de94dba8b94133585fdb1 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Mon, 19 Oct 2020 17:39:45 -0400 Subject: [PATCH 18/57] Added `no_warn` for Limiter. --- andes/core/discrete.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/andes/core/discrete.py b/andes/core/discrete.py index 86d8cf592..33c0dbca6 100644 --- a/andes/core/discrete.py +++ b/andes/core/discrete.py @@ -203,8 +203,10 @@ class Limiter(Discrete): True to only use the upper limit no_upper : bool True to only use the lower limit - equal: bool + equal : bool True to include equal signs in comparison (>= or <=). + no_warn : bool + Disable initial limit warnings zu : 0 or 1 Default value for `zu` if not enabled zl : 0 or 1 @@ -224,7 +226,8 @@ class Limiter(Discrete): """ def __init__(self, u, lower, upper, enable=True, name=None, tex_name=None, info=None, - no_upper=False, no_lower=False, equal=True, zu=0.0, zl=0.0, zi=1.0): + no_upper=False, no_lower=False, equal=True, no_warn=False, + zu=0.0, zl=0.0, zi=1.0): Discrete.__init__(self, name=name, tex_name=tex_name, info=info) self.u = u self.lower = dummify(lower) @@ -233,6 +236,7 @@ def __init__(self, u, lower, upper, enable=True, name=None, tex_name=None, info= self.no_upper = no_upper self.no_lower = no_lower self.equal = equal + self.no_warn = no_warn self.zu = np.array([zu]) self.zl = np.array([zl]) From 75d7e694d6784b35f30039c4164ea7dbb93eea2f Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Mon, 19 Oct 2020 17:52:33 -0400 Subject: [PATCH 19/57] Added voltage flags for PVD1 --- andes/models/distributed.py | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/andes/models/distributed.py b/andes/models/distributed.py index ca1f3f11e..1ca7d1148 100644 --- a/andes/models/distributed.py +++ b/andes/models/distributed.py @@ -192,13 +192,11 @@ def __init__(self, system, config): # --- frequency branch --- self.FL1 = Limiter(u=self.fHz, lower=self.ft0, upper=self.ft1, - info='Under frequency comparer', + info='Under frequency comparer', no_warn=True, ) self.FL2 = Limiter(u=self.fHz, lower=self.ft2, upper=self.ft3, - info='Over frequency comparer', + info='Over frequency comparer', no_warn=True, ) - self.FL1.no_warn = True - self.FL2.no_warn = True self.Kft01 = ConstService(v_str='1/(ft1 - ft0)', tex_name='K_{ft01}') @@ -209,7 +207,7 @@ def __init__(self, system, config): discrete=self.FL1, ) - self.Kft23 = ConstService(v_str='-1/(ft2 - ft3)', tex_name='K_{ft23}') + self.Kft23 = ConstService(v_str='1/(ft3 - ft2)', tex_name='K_{ft23}') self.Ffh = Algeb(info='Coeff. for over frequency', v_str='FL2_zl + FL2_zi * (1 + Kft23 * (ft2 - fHz))', @@ -230,6 +228,31 @@ def __init__(self, system, config): ) # outputs `Pdrp` self.DB.db.no_warn = True + # --- Voltage flags --- + self.VL1 = Limiter(u=self.v, lower=self.vt0, upper=self.vt1, + info='Under voltage comparer', no_warn=True, + ) + self.VL2 = Limiter(u=self.v, lower=self.vt2, upper=self.vt3, + info='Over voltage comparer', no_warn=True, + ) + + self.Kvt01 = ConstService(v_str='1/(vt1 - vt0)', tex_name='K_{vt01}') + + self.Fvl = Algeb(info='Coeff. for under voltage', + v_str='VL1_zi * Kvt01 * (v - vt0) + VL1_zu', + e_str='VL1_zi * Kvt01 * (v - vt0) + VL1_zu - Fvl', + tex_name='F_{vl}', + discrete=self.VL1, + ) + + self.Kvt23 = ConstService(v_str='1/(vt3 - vt2)', tex_name='K_{vt23}') + + self.Fvh = Algeb(info='Coeff. for over voltage', + v_str='VL2_zl + VL2_zi * (1 + Kvt23 * (vt2 - v))', + e_str='VL2_zl + VL2_zi * (1 + Kvt23 * (vt2 - v)) - Fvh', + tex_name='F_{vh}', + discrete=self.VL2, + ) class PVD1(PVD1Data, PVD1Model): """ From de3ff1347f8951a58877d95f9bbd99299bc86e4c Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Mon, 19 Oct 2020 18:22:57 -0400 Subject: [PATCH 20/57] Added variables for power ref. --- andes/models/distributed.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/andes/models/distributed.py b/andes/models/distributed.py index 1ca7d1148..3f85133f6 100644 --- a/andes/models/distributed.py +++ b/andes/models/distributed.py @@ -253,6 +253,32 @@ def __init__(self, system, config): tex_name='F_{vh}', discrete=self.VL2, ) + # --- sensed voltage with lower limit of 0.01 --- + + self.VLo = Limiter(u=self.v, lower=0.01, upper=999, no_upper=True, + info='Voltage lower limit (0.01) flag', + ) + self.vp = Algeb(tex_name='V_p', + info='Sensed positive voltage', + v_str='v * VLo_zi + 0.01 * VLo_zl', + e_str='v * VLo_zi + 0.01 * VLo_zl - vp', + ) + + self.Pext = Algeb(tex_name='P_{ext}', + info='External power signal', + v_str='0', + e_str='0 - Pext' + ) + + self.Psum = Algeb(tex_name='P_{tot}', + info='Sum of P signals', + v_str='Pext + p0 + DB_y', + e_str='Pext + p0 + DB_y - Psum', + ) # `p0` is the initial `Pref`, and `DB_y` is `Pdrp` (f droop) + + # TODO: retrieve line and calculate current It + # TODO: implement the voltage droop on reactive power + class PVD1(PVD1Data, PVD1Model): """ From 7fd7441ec4be2079556aabf2241b1685a705cefe Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Tue, 27 Oct 2020 14:09:01 -0400 Subject: [PATCH 21/57] Use mass-matrix formulation for dc components. --- andes/models/dcbase.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/andes/models/dcbase.py b/andes/models/dcbase.py index 6e2402df6..c432daf28 100644 --- a/andes/models/dcbase.py +++ b/andes/models/dcbase.py @@ -272,7 +272,8 @@ def __init__(self, system, config): info='Inductance current', unit='p.u.', v_str='0', - e_str='-u * (v1 - v2) / L', + e_str='-u * (v1 - v2)', + t_const=self.L, ) self.v1.e_str = '-IL' self.v2.e_str = '+IL' @@ -297,7 +298,8 @@ def __init__(self, system, config): info='Capacitor current', unit='p.u.', v_str='0', - e_str='-u * Idc / C', + e_str='-u * Idc', + t_const=self.C ) self.Idc = Algeb(tex_name='I_{dc}', info='Current from node 2 to 1', @@ -334,8 +336,9 @@ def __init__(self, system, config): self.IL = State(tex_name='I_L', info='Inductance current', unit='p.u.', - e_str='u * (v1 - v2 - R * IL) / L', + e_str='u * (v1 - v2 - R * IL)', v_str='(v1 - v2) / R', + t_const=self.L, ) self.Idc = Algeb(tex_name='I_{dc}', info='Current from node 2 to 1', @@ -370,8 +373,9 @@ def __init__(self, system, config): self.vC = State(tex_name='v_C', info='Capacitor current', unit='p.u.', - e_str='-u * (Idc - vC/R) / C', + e_str='-u * (Idc - vC/R)', v_str='v1 - v2', + t_const=self.C, ) self.Idc = Algeb(tex_name='I_{dc}', info='Current from node 2 to 1', @@ -416,13 +420,15 @@ def __init__(self, system, config): info='Inductance current', unit='p.u.', v_str='0', - e_str='u * vC / L', + e_str='u * vC', + t_const=self.L, ) self.vC = State(tex_name='v_C', info='Capacitor current', unit='p.u.', - e_str='-u * (Idc - vC/R - IL) / C', + e_str='-u * (Idc - vC/R - IL)', v_str='v1 - v2', + t_const=self.C, ) self.Idc = Algeb(tex_name='I_{dc}', info='Current from node 2 to 1', @@ -459,8 +465,9 @@ def __init__(self, system, config): self.vC = State(tex_name='v_C', info='Capacitor current', unit='p.u.', - e_str='-u * Idc / C', + e_str='-u * Idc', v_str='v1 - v2', + t_const=self.C, ) self.Idc = Algeb(tex_name='I_{dc}', info='Current from node 2 to 1', @@ -504,14 +511,16 @@ def __init__(self, system, config): self.IL = State(tex_name='I_L', info='Inductance current', unit='p.u.', - e_str='u * (v1 - v2 - R * IL - vC) / L', + e_str='u * (v1 - v2 - R * IL - vC)', v_str='0', + t_const=self.L, ) self.vC = State(tex_name='v_C', info='Capacitor current', unit='p.u.', - e_str='u * IL / C', + e_str='u * IL', v_str='v1 - v2', + t_const=self.C, ) self.Idc = Algeb(tex_name='I_{dc}', info='Current from node 2 to 1', From 7141d48c01d5991ea736cfeb2a3f376c5588ceef Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Wed, 28 Oct 2020 14:10:24 -0400 Subject: [PATCH 22/57] Refactored logger info with lazy eval. --- andes/io/__init__.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/andes/io/__init__.py b/andes/io/__init__.py index 683c00b71..1a8c86cda 100644 --- a/andes/io/__init__.py +++ b/andes/io/__init__.py @@ -73,7 +73,7 @@ def guess(system): if testlines(fid): true_format = item files.input_format = true_format - logger.debug(f'Input format guessed as {true_format}.') + logger.debug('Input format guessed as %s.', true_format) break if not true_format: @@ -85,7 +85,7 @@ def guess(system): for key, val in input_formats.items(): if add_ext[1:] in val: files.add_format = key - logger.debug(f'Addfile format guessed as {key}.') + logger.debug('Addfile format guessed as %s.', key) break return true_format @@ -106,32 +106,32 @@ def parse(system): # exit when no input format is given if not system.files.input_format: if not guess(system): - logger.error('Input format is not specified and cannot be inferred.') + logger.error('Input format unknown for file "%s".', system.files.case) return False # try parsing the base case file - logger.info(f'Parsing input file "{system.files.case}"') + logger.info('Parsing input file "%s"...', system.files.case) input_format = system.files.input_format parser = importlib.import_module('.' + input_format, __name__) if not parser.read(system, system.files.case): - logger.error(f'Error parsing case file {system.files.fullname} with {input_format} format parser.') + logger.error('Error parsing file "%s" with <%s> parser.', system.files.fullname, input_format) return False _, s = elapsed(t) - logger.info(f'Input file parsed in {s}.') + logger.info('Input file parsed in %s.', s) # Try parsing the addfile t, _ = elapsed() if system.files.addfile: - logger.info(f'Parsing additional file "{system.files.addfile}"') + logger.info('Parsing additional file "%s"...', system.files.addfile) add_format = system.files.add_format add_parser = importlib.import_module('.' + add_format, __name__) if not add_parser.read_add(system, system.files.addfile): - logger.error(f'Error parsing addfile {system.files.addfile} with {input_format} parser.') + logger.error('Error parsing addfile "%s" with %s parser.', system.files.addfile, input_format) return False _, s = elapsed(t) - logger.info(f'Addfile parsed in {s}.') + logger.info('Addfile parsed in %s.', s) return True @@ -175,7 +175,7 @@ def dump(system, output_format, full_path=None, overwrite=False, **kwargs): ret = writer.write(system, system.files.dump, overwrite=overwrite, **kwargs) _, s = elapsed(t) if ret: - logger.info(f'Format conversion completed in {s}.') + logger.info('Format conversion completed in %s.', s) return True else: logger.error('Format conversion failed.') From 03979a6de6d7fdf3a5c0fac2b896e1279918b531 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Wed, 28 Oct 2020 14:48:03 -0400 Subject: [PATCH 23/57] Patch pycode import for Jacobians. --- andes/system.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/andes/system.py b/andes/system.py index 80ca6fa11..5ed458880 100644 --- a/andes/system.py +++ b/andes/system.py @@ -813,11 +813,10 @@ def j_update(self, models: OrderedDict, info=None): f'j_size={j_size}') raise e - msg = f"Jacobian updated at t={self.dae.t}" if info: - msg += f' due to {info}' - - logger.debug(msg) + logger.debug("Jacobian updated at t=%.6f due to %s.", self.dae.t, info) + else: + logger.debug("Jacobian updated at t=%.6f.", self.dae.t) def store_sparse_pattern(self, models: OrderedDict): """ @@ -1056,11 +1055,13 @@ def undill(self): # try to replace equations and jacobian calls with saved code if pycode is not None and self.config.use_pycode: for model in self.models.values(): - model.calls.f = pycode.__dict__[model.class_name].__dict__.get("f_update") - model.calls.g = pycode.__dict__[model.class_name].__dict__.get("g_update") + pycode_model = pycode.__dict__[model.class_name] + + model.calls.f = pycode_model.__dict__.get("f_update") + model.calls.g = pycode_model.__dict__.get("g_update") for jname in model.calls.j: - model.calls.j[jname] = pycode.__dict__[model.class_name].__dict__[f'{jname}_update'] + model.calls.j[jname] = pycode_model.__dict__.get(f'{jname}_update') logger.info("Using generated Python code for equations and Jacobians.") else: logger.debug("Using undilled lambda functions.") From b92e1c1b0504367e6e953bca25534b8a2b29ef25 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Thu, 29 Oct 2020 15:49:10 -0400 Subject: [PATCH 24/57] Support kvxopt library (mostly for Windows). --- andes/shared.py | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/andes/shared.py b/andes/shared.py index 22d392874..f0e5176a8 100644 --- a/andes/shared.py +++ b/andes/shared.py @@ -12,28 +12,41 @@ from andes.utils.lazyimport import LazyImport import math -import coloredlogs # NOQA -import numpy as np # NOQA -from tqdm import tqdm # NOQA - -import cvxopt # NOQA -from cvxopt import umfpack # NOQA -from cvxopt import spmatrix, matrix, sparse, spdiag # NOQA -from numpy import ndarray # NOQA - -from andes.utils.texttable import Texttable # NOQA -from andes.utils.paths import get_dot_andes_path # NOQA +import coloredlogs # NOQA +import numpy as np # NOQA +from numpy import ndarray # NOQA +from tqdm import tqdm # NOQA try: - from cvxoptklu import klu + import kvxopt # NOQA except ImportError: - klu = None + kvxopt = None +from cvxopt import spmatrix if hasattr(spmatrix, 'ipadd'): IP_ADD = True else: IP_ADD = False +if IP_ADD or (kvxopt is None): + from cvxopt import umfpack # NOQA + from cvxopt import spmatrix, matrix, sparse, spdiag # NOQA + from cvxopt import mul, div # NOQA + from cvxopt.lapack import gesv # NOQA + try: + from cvxoptklu import klu # NOQA + except ImportError: + klu = None +else: + from kvxopt import umfpack, klu # NOQA + from kvxopt import spmatrix, matrix, sparse, spdiag # NOQA + from kvxopt import mul, div # NOQA + from kvxopt.lapack import gesv # NOQA + + +from andes.utils.texttable import Texttable # NOQA +from andes.utils.paths import get_dot_andes_path # NOQA + # --- constants --- deg2rad = math.pi/180 From 5df8cafec8c0cdd42a02fdb102912c790ffde1ba Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Thu, 29 Oct 2020 15:52:07 -0400 Subject: [PATCH 25/57] Format change. --- andes/shared.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/andes/shared.py b/andes/shared.py index f0e5176a8..e0350d1e6 100644 --- a/andes/shared.py +++ b/andes/shared.py @@ -62,12 +62,14 @@ pd = LazyImport('import pandas') cupy = LazyImport('import cupy') -plt = LazyImport('from matplotlib import pyplot') mpl = LazyImport('import matplotlib') -Pool = LazyImport('from pathos.multiprocessing import Pool') -Process = LazyImport('from multiprocess import Process') unittest = LazyImport('import unittest') yaml = LazyImport('import yaml') + +plt = LazyImport('from matplotlib import pyplot') +Pool = LazyImport('from pathos.multiprocessing import Pool') +Process = LazyImport('from multiprocess import Process') + newton_krylov = LazyImport('from scipy.optimize import newton_krylov') fsolve = LazyImport('from scipy.optimize import fsolve') solve_ivp = LazyImport('from scipy.integrate import solve_ivp') From 91b808009617481b312f7b17d1cf686a56224899 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Thu, 29 Oct 2020 16:21:20 -0400 Subject: [PATCH 26/57] Import from andes share. --- andes/routines/eig.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/andes/routines/eig.py b/andes/routines/eig.py index ec3d5eba9..558b79a15 100644 --- a/andes/routines/eig.py +++ b/andes/routines/eig.py @@ -6,8 +6,7 @@ from math import ceil, pi import scipy.io -from cvxopt import mul, div, spdiag -from cvxopt.lapack import gesv +from andes.shared import mul, div, spdiag, gesv from andes.io.txt import dump_data from andes.utils.misc import elapsed From 3c27f76d909f0c6e159de0004c5ae41066bc3605 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Thu, 29 Oct 2020 16:22:16 -0400 Subject: [PATCH 27/57] Added reactive power droop arm [wip] --- andes/models/distributed.py | 75 +++++++++++++++++++++++++++++++++---- 1 file changed, 67 insertions(+), 8 deletions(-) diff --git a/andes/models/distributed.py b/andes/models/distributed.py index 3f85133f6..8e2790b5b 100644 --- a/andes/models/distributed.py +++ b/andes/models/distributed.py @@ -65,18 +65,18 @@ def __init__(self): power=True, ) - self.v0 = NumParam(default=0.0, tex_name='v_0', + self.v0 = NumParam(default=0.8, tex_name='v_0', info='Lower limit of deadband for Vdroop response', - unit='pu', + unit='pu', non_zero=True, ) - self.v1 = NumParam(default=0.0, tex_name='v_1', + self.v1 = NumParam(default=1.1, tex_name='v_1', info='Upper limit of deadband for Vdroop response', - unit='pu', + unit='pu', non_zero=True, ) - self.dqdv = NumParam(default=0.0, tex_name='dq/dv', - info='Q-V droop characteristics', - power=True, + self.dqdv = NumParam(default=-1.0, tex_name='dq/dv', + info='Q-V droop characteristics (negative)', + power=True, non_zero=True ) self.fdbd = NumParam(default=-0.01, tex_name='f_{dbd}', @@ -258,6 +258,7 @@ def __init__(self, system, config): self.VLo = Limiter(u=self.v, lower=0.01, upper=999, no_upper=True, info='Voltage lower limit (0.01) flag', ) + self.vp = Algeb(tex_name='V_p', info='Sensed positive voltage', v_str='v * VLo_zi + 0.01 * VLo_zl', @@ -276,13 +277,71 @@ def __init__(self, system, config): e_str='Pext + p0 + DB_y - Psum', ) # `p0` is the initial `Pref`, and `DB_y` is `Pdrp` (f droop) + # self.Vcomp = VarService(v_str='abs(v*exp(1j*a) + (1j * Xc) * (Id + 1j * Iq))', + # info='Voltage before Xc compensation', + # tex_name='V_{comp}' + # ) + + self.Vcomp = VarService(v_str='abs(v*exp(1j*a) + (1j * xc) * 1)', + info='Voltage before Xc compensation', + tex_name='V_{comp}' + ) + + self.Vqu = ConstService(v_str='v1 - (q0 - qmn) / dqdv', + info='Upper voltage bound => qmx', + tex_name='V_{qu}', + ) + + self.Vql = ConstService(v_str='v0 + (qmx - q0) / dqdv', + info='Lower voltage bound => qmn', + tex_name='V_{ql}', + ) + + self.VQ1 = Limiter(u=self.Vcomp, lower=self.Vql, upper=self.v0, + info='Under voltage comparer for Q droop', + no_warn=True, + ) + + self.VQ2 = Limiter(u=self.Vcomp, lower=self.v1, upper=self.Vqu, + info='Over voltage comparer for Q droop', + no_warn=True, + ) + + Qsum = 'VQ1_zl * qmx + VQ2_zu * qmn + ' \ + 'VQ1_zi * (qmx + dqdv *(Vqu - Vcomp)) + ' \ + 'VQ2_zi * (q0 + dqdv * (v1 - Vcomp)) + ' \ + 'VQ1_zu * VQ2_zl * p0 + p0' + + self.Qsum = Algeb(info='Total Q (droop + initial)', + v_str=Qsum, + e_str=f'{Qsum} - Qsum', + tex_name='Q_{sum}', + discrete=(self.VQ1, self.VQ2), + ) + + self.Ipul = Algeb(info='Ipcmd before hard limit', + v_str='Psum / vp', + e_str='Psum / vp - Ipul', + tex_name='I_{p,ul}', + ) + + self.Iqul = Algeb(info='Iqcmd before hard limit', + v_str='Qsum / vp', + e_str='Qsum / vp - Iqul', + tex_name='I_{q,ul}', + ) + + self.Fcoef = VarService(v_str='Ffh * Ffh * Fvl * Fvh', + tex_name='F_{coef}', + ) + # TODO: retrieve line and calculate current It # TODO: implement the voltage droop on reactive power class PVD1(PVD1Data, PVD1Model): """ - Distributed PV model. (TODO: work in progress) + WECC Distributed PV model. (TODO: work in progress) Power rating specified in `Sn`. From b3ac38f54a245c695379ea572bde37acb01d67ed Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Thu, 29 Oct 2020 17:54:12 -0400 Subject: [PATCH 28/57] Added LimiterGain block. --- andes/core/block.py | 64 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/andes/core/block.py b/andes/core/block.py index b2b5cf101..1fe436029 100644 --- a/andes/core/block.py +++ b/andes/core/block.py @@ -1467,6 +1467,8 @@ class GainLimiter(Block): │ │ _____/ └─────┘ lower + TODO: Add an extra gain block "R" for y. + Parameters ---------- u : str, BaseVar @@ -1474,7 +1476,7 @@ class GainLimiter(Block): """ - def __init__(self, u, K, upper, lower, no_upper=False, no_lower=False, + def __init__(self, u, K, lower, upper, no_lower=False, no_upper=False, name=None, tex_name=None, info=None): Block.__init__(self, name=name, tex_name=tex_name, info=info) self.u = dummify(u) @@ -1518,6 +1520,66 @@ def define(self): self.y.e_str += f' - {self.name}_y' +class LimiterGain(Block): + """ + Limiter followed by a gain. + + Exports the limited output `y`, unlimited output `x`, and HardLimiter `lim`. :: + + upper ┌─────┐ + /¯¯¯¯¯ │ │ + u -> / -> │ K │ -> y + _____/ │ │ + lower └─────┘ + + The intermediate variable before the gain is not saved. + + Parameters + ---------- + u : str, BaseVar + Input variable, or an equation string for constructing an anonymous variable + + """ + + def __init__(self, u, K, lower, upper, no_lower=False, no_upper=False, + name=None, tex_name=None, info=None): + Block.__init__(self, name=name, tex_name=tex_name, info=info) + self.u = u + self.K = dummify(K) + self.upper = dummify(upper) + self.lower = dummify(lower) + + if (no_upper and no_lower) is True: + raise ValueError("no_upper or no_lower cannot both be True") + + self.no_lower = no_lower + self.no_upper = no_upper + + self.lim = HardLimiter(u=self.u, lower=self.lower, upper=self.upper, + no_upper=no_upper, no_lower=no_lower, + tex_name='lim') + + self.y = Algeb(info='Gain output after limiter', tex_name='y', discrete=self.lim) + + self.vars = {'lim': self.lim, 'y': self.y} + + def define(self): + """ + TODO: write docstring + """ + self.y.e_str = f'{self.K.name} * {self.u.name} * {self.name}_lim_zi' + self.y.v_str = f'{self.K.name} * {self.u.name} * {self.name}_lim_zi' + + if not self.no_upper: + self.y.e_str += f' + {self.K.name} * {self.name}_lim_zu*{self.upper.name}' + self.y.v_str += f' + {self.K.name} * {self.name}_lim_zu*{self.upper.name}' + if not self.no_lower: + self.y.e_str += f' + {self.K.name} * {self.name}_lim_zl*{self.lower.name}' + self.y.v_str += f' + {self.K.name} * {self.name}_lim_zl*{self.lower.name}' + + self.y.e_str += f' - {self.name}_y' + + class Piecewise(Block): """ Piecewise block. Outputs an algebraic variable `y`. From c0450f0f4a2ce7119d8f44e34f16fb2a28b7994c Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Thu, 29 Oct 2020 17:54:20 -0400 Subject: [PATCH 29/57] Added current limits. --- andes/models/distributed.py | 54 ++++++++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/andes/models/distributed.py b/andes/models/distributed.py index 8e2790b5b..48957ff93 100644 --- a/andes/models/distributed.py +++ b/andes/models/distributed.py @@ -4,7 +4,7 @@ from andes.core.model import Model, ModelData from andes.core.param import NumParam, IdxParam -from andes.core.block import Lag, GainLimiter, DeadBand1 # NOQA +from andes.core.block import Lag, DeadBand1, LimiterGain # NOQA from andes.core.var import ExtAlgeb, ExtState, Algeb, State # NOQA from andes.core.service import ConstService, ExtService, FlagValue, DataSelect, DeviceFinder # NOQA @@ -50,6 +50,11 @@ def __init__(self): z=True, ) + self.pqflag = NumParam(info='P/Q priority for I limit; 0-Q priority, 1-P priority', + mandatory=True, + unit='bool', + ) + # --- parameters found from ESIG.energy --- self.igreg = IdxParam(model='Bus', info='Remote bus idx for droop response, None for local', @@ -215,6 +220,7 @@ def __init__(self, system, config): tex_name='F_{fh}', discrete=self.FL2, ) + self.Fdev = Algeb(info='Frequency deviation', v_str='fn - fHz', e_str='fn - fHz - Fdev', unit='Hz', tex_name='f_{dev}', @@ -310,7 +316,7 @@ def __init__(self, system, config): Qsum = 'VQ1_zl * qmx + VQ2_zu * qmn + ' \ 'VQ1_zi * (qmx + dqdv *(Vqu - Vcomp)) + ' \ 'VQ2_zi * (q0 + dqdv * (v1 - Vcomp)) + ' \ - 'VQ1_zu * VQ2_zl * p0 + p0' + 'q0' self.Qsum = Algeb(info='Total Q (droop + initial)', v_str=Qsum, @@ -331,12 +337,46 @@ def __init__(self, system, config): tex_name='I_{q,ul}', ) - self.Fcoef = VarService(v_str='Ffh * Ffh * Fvl * Fvh', - tex_name='F_{coef}', - ) + # --- Ipmax, Iqmax and Iqmin --- + Ipmaxsq = "(Piecewise((0, Le(ialim**2 - Iqcmd_y**2, 0)), ((ialim**2 - Iqcmd_y ** 2), True)))" + Ipmaxsq0 = "(Piecewise((0, Le(ialim**2 - (q0 / v)**2, 0)), ((ialim**2 - (q0 / v) ** 2), True)))" + self.Ipmaxsq = VarService(v_str=Ipmaxsq) + self.Ipmaxsq0 = ConstService(v_str=Ipmaxsq0) + + self.Ipmax = Algeb(v_str='SWPQ_s1 * ialim + SWPQ_s0 * sqrt(Ipmaxsq0)', + e_str='SWPQ_s1 * ialim + SWPQ_s0 * sqrt(Ipmaxsq) - Ipmax', + tex_name='I_{pmax}', + ) + + Iqmaxsq = "(Piecewise((0, Le(ialim**2 - Ipcmd_y**2, 0)), ((ialim**2 - Ipcmd_y ** 2), True)))" + Iqmaxsq0 = "(Piecewise((0, Le(ialim**2 - (p0 / v)**2, 0)), ((ialim**2 - (p0 / v) ** 2), True)))" + self.Iqmaxsq = VarService(v_str=Iqmaxsq) + self.Iqmaxsq0 = ConstService(v_str=Iqmaxsq0) + + self.Iqmax = Algeb(v_str='SWPQ_s0 * ialim + SWPQ_s1 * sqrt(Iqmaxsq0)', + e_str='SWPQ_s0 * ialim + SWPQ_s1 * sqrt(Iqmaxsq) - Iqmax', + tex_name='I_{qmax}', + ) + + self.Iqmin = VarService(v_str='-Iqmax') + + # --- Ipcmd, Iqcmd --- + + self.Ipcmd = LimiterGain(u=self.Ipul, K='Fvl * Fvh * Ffl * Ffh', + lower=0.0, upper=self.Ipmax, + info='Ip with limiter and coeff.', + ) + + self.Iqcmd = LimiterGain(u=self.Iqul, K='Fvl * Fvh * Ffl * Ffh', + lower=self.Iqmin, upper=self.Iqmax, + info='Iq with limiter and coeff.', + ) + + self.Ipout = Lag(u=self.Ipcmd_y, T=self.tip, K=1.0) + + self.Iqout = Lag(u=self.Iqcmd_y, T=self.tiq, K=1.0) - # TODO: retrieve line and calculate current It - # TODO: implement the voltage droop on reactive power + self.SWPQ = Switcher(u=self.pqflag, options=(0, 1), tex_name='SW_{PQ}', cache=True) class PVD1(PVD1Data, PVD1Model): From 4747d22cd46e4a8d2593c0892ee1d4f33d9ceb20 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Thu, 29 Oct 2020 20:00:56 -0400 Subject: [PATCH 30/57] Prototype of PVD1 model. --- andes/models/distributed.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/andes/models/distributed.py b/andes/models/distributed.py index 48957ff93..48e808fb7 100644 --- a/andes/models/distributed.py +++ b/andes/models/distributed.py @@ -154,10 +154,11 @@ class PVD1Model(Model): """ def __init__(self, system, config): Model.__init__(self, system, config) - self.flags.tds = True self.group = 'DG' + self.SWPQ = Switcher(u=self.pqflag, options=(0, 1), tex_name='SW_{PQ}', cache=True) + self.buss = DataSelect(self.igreg, self.bus, info='selected bus (bus or igreg)', ) @@ -168,11 +169,13 @@ def __init__(self, system, config): self.v = ExtAlgeb(model='Bus', src='v', indexer=self.buss, tex_name='V', info='bus (or igreg) terminal voltage', unit='p.u.', + e_str='-Iqout_y * v', ) self.a = ExtAlgeb(model='Bus', src='a', indexer=self.buss, tex_name=r'\theta', info='bus (or igreg) phase angle', unit='rad.', + e_str='-Ipout_y * v', ) self.p0 = ExtService(model='StaticGen', @@ -283,12 +286,7 @@ def __init__(self, system, config): e_str='Pext + p0 + DB_y - Psum', ) # `p0` is the initial `Pref`, and `DB_y` is `Pdrp` (f droop) - # self.Vcomp = VarService(v_str='abs(v*exp(1j*a) + (1j * Xc) * (Id + 1j * Iq))', - # info='Voltage before Xc compensation', - # tex_name='V_{comp}' - # ) - - self.Vcomp = VarService(v_str='abs(v*exp(1j*a) + (1j * xc) * 1)', + self.Vcomp = VarService(v_str='abs(v*exp(1j*a) + (1j * xc) * (Ipout_y + 1j * Iqout_y))', info='Voltage before Xc compensation', tex_name='V_{comp}' ) @@ -376,7 +374,11 @@ def __init__(self, system, config): self.Iqout = Lag(u=self.Iqcmd_y, T=self.tiq, K=1.0) - self.SWPQ = Switcher(u=self.pqflag, options=(0, 1), tex_name='SW_{PQ}', cache=True) + def v_numeric(self, **kwargs): + """ + Disable the corresponding `StaticGen`s. + """ + self.system.groups['StaticGen'].set(src='u', idx=self.gen.v, attr='v', value=0) class PVD1(PVD1Data, PVD1Model): From a1e7c76116a73abf13ca27e7101cb791470bdb73 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Thu, 29 Oct 2020 20:05:46 -0400 Subject: [PATCH 31/57] Updated PVD1 test case. --- andes/cases/ieee14/ieee14_pvd1.xlsx | Bin 0 -> 25947 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 andes/cases/ieee14/ieee14_pvd1.xlsx diff --git a/andes/cases/ieee14/ieee14_pvd1.xlsx b/andes/cases/ieee14/ieee14_pvd1.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..688242ef18a4f4a80adeb020f009b06b7d0982b0 GIT binary patch literal 25947 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 Date: Thu, 29 Oct 2020 20:57:37 -0400 Subject: [PATCH 32/57] Update docs. --- andes/models/distributed.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/andes/models/distributed.py b/andes/models/distributed.py index 48e808fb7..39eab989b 100644 --- a/andes/models/distributed.py +++ b/andes/models/distributed.py @@ -370,9 +370,13 @@ def __init__(self, system, config): info='Iq with limiter and coeff.', ) - self.Ipout = Lag(u=self.Ipcmd_y, T=self.tip, K=1.0) + self.Ipout = Lag(u=self.Ipcmd_y, T=self.tip, K=1.0, + info='Output Ip filter', + ) - self.Iqout = Lag(u=self.Iqcmd_y, T=self.tiq, K=1.0) + self.Iqout = Lag(u=self.Iqcmd_y, T=self.tiq, K=1.0, + info='Output Iq filter', + ) def v_numeric(self, **kwargs): """ @@ -383,11 +387,12 @@ def v_numeric(self, **kwargs): class PVD1(PVD1Data, PVD1Model): """ - WECC Distributed PV model. (TODO: work in progress) + WECC Distributed PV model. Power rating specified in `Sn`. - Reference: ESIG, WECC Distributed and Small PV Plants Generic Model (PVD1), [Online], + Reference: + [1] ESIG, WECC Distributed and Small PV Plants Generic Model (PVD1), [Online], Available: https://www.esig.energy/wiki-main-page/wecc-distributed-and-small-pv-plants-generic-model-pvd1/ """ def __init__(self, system, config): From dbaa0e43e6b1087ebea5a57f94dde7f1f0e2a6e9 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Thu, 29 Oct 2020 21:30:58 -0400 Subject: [PATCH 33/57] Updated tex names. --- andes/models/distributed.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/andes/models/distributed.py b/andes/models/distributed.py index 39eab989b..31f021a46 100644 --- a/andes/models/distributed.py +++ b/andes/models/distributed.py @@ -157,7 +157,7 @@ def __init__(self, system, config): self.flags.tds = True self.group = 'DG' - self.SWPQ = Switcher(u=self.pqflag, options=(0, 1), tex_name='SW_{PQ}', cache=True) + self.SWPQ = Switcher(u=self.pqflag, options=(0, 1), tex_name='SW^{PQ}', cache=True) self.buss = DataSelect(self.igreg, self.bus, info='selected bus (bus or igreg)', @@ -196,6 +196,7 @@ def __init__(self, system, config): self.fHz = Algeb(info='frequency in Hz', v_str='fn * f', e_str='fn * f - fHz', unit='Hz', + tex_name='f_{Hz}', ) # --- frequency branch --- @@ -338,8 +339,8 @@ def __init__(self, system, config): # --- Ipmax, Iqmax and Iqmin --- Ipmaxsq = "(Piecewise((0, Le(ialim**2 - Iqcmd_y**2, 0)), ((ialim**2 - Iqcmd_y ** 2), True)))" Ipmaxsq0 = "(Piecewise((0, Le(ialim**2 - (q0 / v)**2, 0)), ((ialim**2 - (q0 / v) ** 2), True)))" - self.Ipmaxsq = VarService(v_str=Ipmaxsq) - self.Ipmaxsq0 = ConstService(v_str=Ipmaxsq0) + self.Ipmaxsq = VarService(v_str=Ipmaxsq, tex_name='I_{pmax}^2') + self.Ipmaxsq0 = ConstService(v_str=Ipmaxsq0, tex_name='I_{pmax0}^2') self.Ipmax = Algeb(v_str='SWPQ_s1 * ialim + SWPQ_s0 * sqrt(Ipmaxsq0)', e_str='SWPQ_s1 * ialim + SWPQ_s0 * sqrt(Ipmaxsq) - Ipmax', @@ -348,26 +349,28 @@ def __init__(self, system, config): Iqmaxsq = "(Piecewise((0, Le(ialim**2 - Ipcmd_y**2, 0)), ((ialim**2 - Ipcmd_y ** 2), True)))" Iqmaxsq0 = "(Piecewise((0, Le(ialim**2 - (p0 / v)**2, 0)), ((ialim**2 - (p0 / v) ** 2), True)))" - self.Iqmaxsq = VarService(v_str=Iqmaxsq) - self.Iqmaxsq0 = ConstService(v_str=Iqmaxsq0) + self.Iqmaxsq = VarService(v_str=Iqmaxsq, tex_name='I_{qmax}^2') + self.Iqmaxsq0 = ConstService(v_str=Iqmaxsq0, tex_name='I_{qmax0}^2') self.Iqmax = Algeb(v_str='SWPQ_s0 * ialim + SWPQ_s1 * sqrt(Iqmaxsq0)', e_str='SWPQ_s0 * ialim + SWPQ_s1 * sqrt(Iqmaxsq) - Iqmax', tex_name='I_{qmax}', ) - self.Iqmin = VarService(v_str='-Iqmax') + self.Iqmin = VarService(v_str='-Iqmax', tex_name='I_{qmin}') # --- Ipcmd, Iqcmd --- self.Ipcmd = LimiterGain(u=self.Ipul, K='Fvl * Fvh * Ffl * Ffh', lower=0.0, upper=self.Ipmax, info='Ip with limiter and coeff.', + tex_name='I^{pcmd}', ) self.Iqcmd = LimiterGain(u=self.Iqul, K='Fvl * Fvh * Ffl * Ffh', lower=self.Iqmin, upper=self.Iqmax, info='Iq with limiter and coeff.', + tex_name='I^{qcmd}', ) self.Ipout = Lag(u=self.Ipcmd_y, T=self.tip, K=1.0, From 26c913df1f1c2375b06b876e0499992277d1de58 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Thu, 29 Oct 2020 21:37:15 -0400 Subject: [PATCH 34/57] Patch for a codegen error --- andes/models/distributed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/andes/models/distributed.py b/andes/models/distributed.py index 31f021a46..9e6a8228e 100644 --- a/andes/models/distributed.py +++ b/andes/models/distributed.py @@ -157,7 +157,7 @@ def __init__(self, system, config): self.flags.tds = True self.group = 'DG' - self.SWPQ = Switcher(u=self.pqflag, options=(0, 1), tex_name='SW^{PQ}', cache=True) + self.SWPQ = Switcher(u=self.pqflag, options=(0, 1), tex_name='SW_{PQ}', cache=True) self.buss = DataSelect(self.igreg, self.bus, info='selected bus (bus or igreg)', From 27fa9635821d3284761024e660f4776610395123 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Fri, 30 Oct 2020 12:07:52 -0400 Subject: [PATCH 35/57] Added `gammap` and `gammaq` power ratios. --- andes/models/distributed.py | 47 ++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/andes/models/distributed.py b/andes/models/distributed.py index 9e6a8228e..9632b80fa 100644 --- a/andes/models/distributed.py +++ b/andes/models/distributed.py @@ -147,6 +147,16 @@ def __init__(self): unit='s', ) + self.gammap = NumParam(default=1.0, tex_name=r'\gamma_p', + info='Ratio of P from PVD1 w.r.t to that from PV generator', + unit='(0, 1]', + ) + + self.gammaq = NumParam(default=1.0, tex_name=r'\gamma_q', + info='Ratio of Q from PVD1 w.r.t to that from PV generator', + unit='(0, 1]', + ) + class PVD1Model(Model): """ @@ -165,7 +175,12 @@ def __init__(self, system, config): self.busfreq = DeviceFinder(self.busf, link=self.buss, idx_name='bus') - # initial voltages from selected bus + # --- initial values from power flow --- + # v : bus voltage magnitude + # a : bus voltage angle + # p0s : active power from connected static PV generator + # q0s : reactive power from connected static PV generator + self.v = ExtAlgeb(model='Bus', src='v', indexer=self.buss, tex_name='V', info='bus (or igreg) terminal voltage', unit='p.u.', @@ -178,16 +193,26 @@ def __init__(self, system, config): e_str='-Ipout_y * v', ) - self.p0 = ExtService(model='StaticGen', - src='p', - indexer=self.gen, - tex_name='P_0', - ) - self.q0 = ExtService(model='StaticGen', - src='q', - indexer=self.gen, - tex_name='Q_0', - ) + self.p0s = ExtService(model='StaticGen', + src='p', + indexer=self.gen, + tex_name='P_{0s}', + info='Initial P from static gen', + ) + self.q0s = ExtService(model='StaticGen', + src='q', + indexer=self.gen, + tex_name='Q_{0s}', + info='Initial Q from static gen', + ) + # --- calculate the initial P and Q for this distributed device --- + self.p0 = ConstService(v_str='gammap * p0s', tex_name='P_0', + info='Initial P for the PVD1 device', + ) + self.q0 = ConstService(v_str='gammaq * q0s', tex_name='Q_0', + info='Initial Q for the PVD1 device', + ) + # frequency measurement variable `f` self.f = ExtAlgeb(model='FreqMeasurement', src='f', indexer=self.busfreq, export=False, info='Bus frequency', unit='p.u.', From 19e79a1cd47220c742af01bcaa8af87c926a4ad8 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Fri, 30 Oct 2020 12:08:14 -0400 Subject: [PATCH 36/57] Added multiple PVD1s on the same bus. --- andes/cases/ieee14/ieee14_pvd1.xlsx | Bin 25947 -> 27291 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/andes/cases/ieee14/ieee14_pvd1.xlsx b/andes/cases/ieee14/ieee14_pvd1.xlsx index 688242ef18a4f4a80adeb020f009b06b7d0982b0..93e834b5edfbcd16e57277149c1a101f8971695e 100644 GIT binary patch delta 6342 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 JbN&1De*mpIB(?wm delta 4954 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 r#`Mt;PoeC Date: Fri, 30 Oct 2020 12:34:42 -0400 Subject: [PATCH 37/57] Minor edits --- andes/models/distributed.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/andes/models/distributed.py b/andes/models/distributed.py index 9632b80fa..2fcf930c5 100644 --- a/andes/models/distributed.py +++ b/andes/models/distributed.py @@ -149,12 +149,12 @@ def __init__(self): self.gammap = NumParam(default=1.0, tex_name=r'\gamma_p', info='Ratio of P from PVD1 w.r.t to that from PV generator', - unit='(0, 1]', + vrange='(0, 1]', ) self.gammaq = NumParam(default=1.0, tex_name=r'\gamma_q', info='Ratio of Q from PVD1 w.r.t to that from PV generator', - unit='(0, 1]', + vrange='(0, 1]', ) From d02c07c34584c3ea9fa92a67544a7406fc9c51f5 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Fri, 30 Oct 2020 12:39:17 -0400 Subject: [PATCH 38/57] Added a note. --- andes/models/distributed.py | 1 + 1 file changed, 1 insertion(+) diff --git a/andes/models/distributed.py b/andes/models/distributed.py index 2fcf930c5..9c58e61f7 100644 --- a/andes/models/distributed.py +++ b/andes/models/distributed.py @@ -418,6 +418,7 @@ class PVD1(PVD1Data, PVD1Model): WECC Distributed PV model. Power rating specified in `Sn`. + Frequency and voltage recovery latching has not been implemented. Reference: [1] ESIG, WECC Distributed and Small PV Plants Generic Model (PVD1), [Online], From cf320d5b820b5b28e755a83e0d415b432ca52cd3 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Fri, 30 Oct 2020 12:46:30 -0400 Subject: [PATCH 39/57] Add connection status to interface equations. --- andes/models/distributed.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/andes/models/distributed.py b/andes/models/distributed.py index 9c58e61f7..fbff22799 100644 --- a/andes/models/distributed.py +++ b/andes/models/distributed.py @@ -184,13 +184,13 @@ def __init__(self, system, config): self.v = ExtAlgeb(model='Bus', src='v', indexer=self.buss, tex_name='V', info='bus (or igreg) terminal voltage', unit='p.u.', - e_str='-Iqout_y * v', + e_str='-Iqout_y * v * u', ) self.a = ExtAlgeb(model='Bus', src='a', indexer=self.buss, tex_name=r'\theta', info='bus (or igreg) phase angle', unit='rad.', - e_str='-Ipout_y * v', + e_str='-Ipout_y * v * u', ) self.p0s = ExtService(model='StaticGen', From 3fe5ee6f60b2d32bca8dbc2b12853185736f178d Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Sun, 1 Nov 2020 16:09:49 -0500 Subject: [PATCH 40/57] Added continuous ACE model `ACEc`. --- andes/models/__init__.py | 2 +- andes/models/area.py | 52 ++++++++++++++++++++++++++++------------ 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/andes/models/__init__.py b/andes/models/__init__.py index b2cb57a40..d5b86546e 100644 --- a/andes/models/__init__.py +++ b/andes/models/__init__.py @@ -12,7 +12,7 @@ ('pv', ['PV', 'Slack']), ('shunt', ['Shunt']), ('line', ['Line']), - ('area', ['Area', 'ACE']), + ('area', ['Area', 'ACE', 'ACEc']), ('synchronous', ['GENCLS', 'GENROU']), ('governor', ['TG2', 'TGOV1', 'TGOV1DB', 'IEEEG1']), ('exciter', ['EXDC2', 'IEEEX1', 'ESDC2A', 'EXST1', 'ESST3A', 'SEXS']), diff --git a/andes/models/area.py b/andes/models/area.py index 524c9f9ec..4fa8414c7 100644 --- a/andes/models/area.py +++ b/andes/models/area.py @@ -65,13 +65,13 @@ def __init__(self): self.bias = NumParam(default=1.0, info='bias parameter', tex_name=r'\beta', unit='MW/0.1Hz', power=True) - self.busf = IdxParam(info='Optional BusFreq idx', model='BusFreq', + self.busf = IdxParam(info='Optional BusFreq device idx', model='BusFreq', default=None) -class ACE(ACEData, Model): +class ACEc(ACEData, Model): """ - Area Control Error model. + Area Control Error model. Continuous frequency sampling. Note: area idx is automatically retrieved from `bus`. """ @@ -84,13 +84,10 @@ def __init__(self, system, config): self.group = 'Calculation' self.config.add(OrderedDict([('freq_model', 'BusFreq'), - ('interval', 4.0), - ('offset', 0.0), ])) - self.config.add_extra('_help', {'freq_model': 'default freq. measurement model', - 'interval': 'sampling time interval', - 'offset': 'sampling time offset'}) - + self.config.add_extra('_help', + {'freq_model': 'default freq. measurement model', + }) self.config.add_extra('_alt', {'freq_model': ('BusFreq',)}) self.area = ExtParam(model='Bus', src='area', indexer=self.bus, export=False) @@ -98,9 +95,37 @@ def __init__(self, system, config): self.busf.model = self.config.freq_model self.busfreq = DeviceFinder(self.busf, link=self.bus, idx_name='bus') - self.f = ExtAlgeb(model='FreqMeasurement', src='f', indexer=self.busfreq, - export=False, info='Bus frequency', + self.f = ExtAlgeb(model='FreqMeasurement', + src='f', + indexer=self.busfreq, + export=False, + info='Bus frequency', ) + self.ace = Algeb(info='area control error', unit='MW (p.u.)', + tex_name='ace', + e_str='10 * bias * (f - 1) - ace', + ) + + +class ACE(ACEc): + """ + Area Control Error model. Discrete frequency sampling. + + Frequency sampling period (in seconds) can be specified in + ``ACE.config.interval``. The sampling start time (in seconds) + can be specified in ``ACE.config.offset``. + + Note: area idx is automatically retrieved from `bus`. + """ + + def __init__(self, system, config): + ACEc.__init__(self, system, config) + + self.config.add(OrderedDict([('interval', 4.0), + ('offset', 0.0), + ])) + self.config.add_extra('_help', {'interval': 'sampling time interval', + 'offset': 'sampling time offset'}) self.fs = Sampling(self.f, interval=self.config.interval, @@ -109,7 +134,4 @@ def __init__(self, system, config): info='Sampled freq.', ) - self.ace = Algeb(info='area control error', unit='MW (p.u.)', - tex_name='ace', - e_str='10 * bias * (fs_v - 1) - ace', - ) + self.ace.e_str = '10 * bias * (fs_v - 1) - ace' From 01f409041de99c4de8a87253439e39380bf309a7 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Sun, 1 Nov 2020 16:16:26 -0500 Subject: [PATCH 41/57] Allow `sys_f` for system frequency in equations. --- andes/core/model.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/andes/core/model.py b/andes/core/model.py index 154171081..f07de476f 100644 --- a/andes/core/model.py +++ b/andes/core/model.py @@ -504,7 +504,8 @@ def __init__(self, system, config): where the `e_str` attribute is the equation string attribute. `u` is the connectivity status. Any parameter, config, service or variables can be used in equation strings. - An addition variable `dae_t` for the current simulation time can be used if the model has flag `tds`. + The addition variable `dae_t` for the current simulation time can be used if the model has flag `tds`. + The additional variable `sys_f` is for system frequency (from ``system.config.freq``). The above example is overly simplified. Our `PQ` model wants a feature to switch itself to a constant impedance if the voltage is out of the range `(vmin, vmax)`. @@ -572,7 +573,9 @@ def __init__(self, system=None, config=None): self.services_ext = OrderedDict() # external services (to be retrieved) self.services_ops = OrderedDict() # operational services (for special usages) - self.tex_names = OrderedDict((('dae_t', 't_{dae}'),)) + self.tex_names = OrderedDict((('dae_t', 't_{dae}'), + ('sys_f', 'f_{sys}'), + )) # Model behavior flags self.flags = ModelFlags() @@ -877,8 +880,9 @@ def refresh_inputs(self): for key, val in self.config.as_dict(refresh=True).items(): self._input[key] = np.array(val) - # update`dae_t` + # update`dae_t` and `sys_f` self._input['dae_t'] = self.system.dae.t + self._input['sys_f'] = self.system.config.freq def refresh_inputs_arg(self): """ @@ -1678,7 +1682,7 @@ def __init__(self, parent): self.parent = parent # symbols that are input to lambda functions - # including parameters, variables, services, configs and "dae_t" + # including parameters, variables, services, configs, and scalars (dae_t, sys_f) self.inputs_dict = OrderedDict() self.vars_dict = OrderedDict() self.iters_dict = OrderedDict() @@ -1803,6 +1807,7 @@ def generate_symbols(self): self.tex_names[Symbol(var)] = Symbol(self.parent.__dict__[var].tex_name) self.inputs_dict['dae_t'] = Symbol('dae_t') + self.inputs_dict['sys_f'] = Symbol('sys_f') # build ``non_vars_dict`` by removing ``vars_dict`` keys from a copy of ``inputs`` self.non_vars_dict = OrderedDict(self.inputs_dict) From b3d00dda973e6855b077ab18f8077459396c8ee7 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Sun, 1 Nov 2020 16:24:16 -0500 Subject: [PATCH 42/57] Fix ACE/ACEc model equation (sys_f) --- andes/models/area.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/andes/models/area.py b/andes/models/area.py index 4fa8414c7..bd6d05d6c 100644 --- a/andes/models/area.py +++ b/andes/models/area.py @@ -71,7 +71,10 @@ def __init__(self): class ACEc(ACEData, Model): """ - Area Control Error model. Continuous frequency sampling. + Area Control Error model. + + Continuous frequency sampling. + System base frequency from ``system.config.freq`` is used. Note: area idx is automatically retrieved from `bus`. """ @@ -100,16 +103,21 @@ def __init__(self, system, config): indexer=self.busfreq, export=False, info='Bus frequency', + unit='p.u. (Hz)' ) - self.ace = Algeb(info='area control error', unit='MW (p.u.)', + self.ace = Algeb(info='area control error', + unit='p.u. (MW)', tex_name='ace', - e_str='10 * bias * (f - 1) - ace', + e_str='10 * bias * sys_f * (f - 1) - ace', ) class ACE(ACEc): """ - Area Control Error model. Discrete frequency sampling. + Area Control Error model. + + Discrete frequency sampling. + System base frequency from ``system.config.freq`` is used. Frequency sampling period (in seconds) can be specified in ``ACE.config.interval``. The sampling start time (in seconds) @@ -134,4 +142,4 @@ def __init__(self, system, config): info='Sampled freq.', ) - self.ace.e_str = '10 * bias * (fs_v - 1) - ace' + self.ace.e_str = '10 * bias * sys_f * (fs_v - 1) - ace' From 7c8519e67e75802aa626c9226de82ed9cff59a34 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Sun, 1 Nov 2020 16:25:38 -0500 Subject: [PATCH 43/57] Use KVXOPT.spmatrix inplace functions if available --- andes/shared.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/andes/shared.py b/andes/shared.py index e0350d1e6..09542845e 100644 --- a/andes/shared.py +++ b/andes/shared.py @@ -19,18 +19,19 @@ try: import kvxopt # NOQA + from kvxopt import spmatrix except ImportError: kvxopt = None + from cvxopt import spmatrix -from cvxopt import spmatrix if hasattr(spmatrix, 'ipadd'): IP_ADD = True else: IP_ADD = False -if IP_ADD or (kvxopt is None): +if kvxopt is None: from cvxopt import umfpack # NOQA - from cvxopt import spmatrix, matrix, sparse, spdiag # NOQA + from cvxopt import matrix, sparse, spdiag # NOQA from cvxopt import mul, div # NOQA from cvxopt.lapack import gesv # NOQA try: @@ -39,7 +40,7 @@ klu = None else: from kvxopt import umfpack, klu # NOQA - from kvxopt import spmatrix, matrix, sparse, spdiag # NOQA + from kvxopt import matrix, sparse, spdiag # NOQA from kvxopt import mul, div # NOQA from kvxopt.lapack import gesv # NOQA From d993eff80165dcef867eea47e73c2cb01ecced11 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Sun, 1 Nov 2020 16:38:59 -0500 Subject: [PATCH 44/57] Test case for VSCShunt in power flow. --- andes/cases/kundur/kundur_vsc.xlsx | Bin 0 -> 17537 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 andes/cases/kundur/kundur_vsc.xlsx diff --git a/andes/cases/kundur/kundur_vsc.xlsx b/andes/cases/kundur/kundur_vsc.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..bd4a351a9df386a7551ed53b97469ab9e8fd6863 GIT binary patch literal 17537 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 B5aR#< literal 0 HcmV?d00001 From 52522383088da2d4164635329d51acdc64d3e3a1 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Sun, 1 Nov 2020 16:40:39 -0500 Subject: [PATCH 45/57] WECC test cases in xlsx format. --- andes/cases/wecc/wecc_full.xlsx | Bin 0 -> 70809 bytes andes/cases/wecc/wecc_gencls.xlsx | Bin 0 -> 68485 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 andes/cases/wecc/wecc_full.xlsx create mode 100644 andes/cases/wecc/wecc_gencls.xlsx diff --git a/andes/cases/wecc/wecc_full.xlsx b/andes/cases/wecc/wecc_full.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..afeade47033ce3e3fcf589664db8a0771d30d262 GIT binary patch literal 70809 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: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 literal 0 HcmV?d00001 diff --git a/andes/cases/wecc/wecc_gencls.xlsx b/andes/cases/wecc/wecc_gencls.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..4a09240b786c0cb339e6177c67d27a7a4908aa5c GIT binary patch literal 68485 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 literal 0 HcmV?d00001 From 3d2589854912fa32da473c6a0793b215cccdd30b Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Sun, 1 Nov 2020 16:54:42 -0500 Subject: [PATCH 46/57] Fix logic for kvxopt. --- andes/shared.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/andes/shared.py b/andes/shared.py index 09542845e..5a709e075 100644 --- a/andes/shared.py +++ b/andes/shared.py @@ -1,13 +1,10 @@ """ Shared constants and delayed imports. """ - -# # Known issues of LazyImport :: # # 1) High overhead when called hundreds of thousands times. # For example, NumPy must not be imported with LazyImport. -# from andes.utils.lazyimport import LazyImport @@ -17,36 +14,40 @@ from numpy import ndarray # NOQA from tqdm import tqdm # NOQA +# import `kvxopt` and test for `ipadd` +# only use `kvxopt` when `ipadd` is available try: - import kvxopt # NOQA - from kvxopt import spmatrix + from kvxopt import spmatrix as kspmatrix + if not hasattr(kspmatrix, 'ipadd'): + kvxopt = None + IP_ADD = False + else: + IP_ADD = True except ImportError: kvxopt = None - from cvxopt import spmatrix - -if hasattr(spmatrix, 'ipadd'): - IP_ADD = True -else: - IP_ADD = False if kvxopt is None: from cvxopt import umfpack # NOQA - from cvxopt import matrix, sparse, spdiag # NOQA + from cvxopt import spmatrix, matrix, sparse, spdiag # NOQA from cvxopt import mul, div # NOQA from cvxopt.lapack import gesv # NOQA try: from cvxoptklu import klu # NOQA except ImportError: klu = None + if hasattr(spmatrix, 'ipadd'): + IP_ADD = True + else: + IP_ADD = False else: from kvxopt import umfpack, klu # NOQA - from kvxopt import matrix, sparse, spdiag # NOQA + from kvxopt import spmatrix, matrix, sparse, spdiag # NOQA from kvxopt import mul, div # NOQA from kvxopt.lapack import gesv # NOQA -from andes.utils.texttable import Texttable # NOQA -from andes.utils.paths import get_dot_andes_path # NOQA +from andes.utils.texttable import Texttable # NOQA +from andes.utils.paths import get_dot_andes_path # NOQA # --- constants --- From 3ee9e0b803debaa799bd56717e387541f4319adc Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Sun, 1 Nov 2020 16:57:57 -0500 Subject: [PATCH 47/57] Patch kvxopt logic again. --- andes/shared.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/andes/shared.py b/andes/shared.py index 5a709e075..5f8124128 100644 --- a/andes/shared.py +++ b/andes/shared.py @@ -14,19 +14,19 @@ from numpy import ndarray # NOQA from tqdm import tqdm # NOQA -# import `kvxopt` and test for `ipadd` -# only use `kvxopt` when `ipadd` is available try: + import kvxopt from kvxopt import spmatrix as kspmatrix - if not hasattr(kspmatrix, 'ipadd'): - kvxopt = None - IP_ADD = False - else: - IP_ADD = True except ImportError: kvxopt = None -if kvxopt is None: +# only use `kvxopt` when `ipadd` is available +if kvxopt and not hasattr(kspmatrix, 'ipadd'): + IP_ADD = False +else: + IP_ADD = True + +if IP_ADD is False: from cvxopt import umfpack # NOQA from cvxopt import spmatrix, matrix, sparse, spdiag # NOQA from cvxopt import mul, div # NOQA From fdb2c844e49dac5c776cd910364340d55cb3c906 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Sun, 1 Nov 2020 17:03:21 -0500 Subject: [PATCH 48/57] Show ipadd module name. --- andes/routines/pflow.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/andes/routines/pflow.py b/andes/routines/pflow.py index 0cc523522..d18ba2076 100644 --- a/andes/routines/pflow.py +++ b/andes/routines/pflow.py @@ -130,9 +130,15 @@ def summary(self): Output a summary for the PFlow routine. """ ipadd_status = 'CVXOPT normal (ipadd not available)' + + # extract package name, `cvxopt` or `kvxopt` + sp_module = sparse.__module__ + if '.' in sp_module: + sp_module = sp_module.split('.')[0] + if IP_ADD: if self.system.config.ipadd: - ipadd_status = 'Fast in-place' + ipadd_status = f'Fast in-place ({sp_module})' else: ipadd_status = 'CVXOPT normal (ipadd disabled in config)' From fc751a180f4b1c5e36742d16b05be7e224ca72c7 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Sun, 1 Nov 2020 17:17:40 -0500 Subject: [PATCH 49/57] Patch kvxopt import --- andes/shared.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/andes/shared.py b/andes/shared.py index 5f8124128..c7f80fec6 100644 --- a/andes/shared.py +++ b/andes/shared.py @@ -19,12 +19,13 @@ from kvxopt import spmatrix as kspmatrix except ImportError: kvxopt = None + kspmatrix = None # only use `kvxopt` when `ipadd` is available -if kvxopt and not hasattr(kspmatrix, 'ipadd'): - IP_ADD = False -else: - IP_ADD = True +IP_ADD = False +if kvxopt: + if hasattr(kspmatrix, 'ipadd'): + IP_ADD = True if IP_ADD is False: from cvxopt import umfpack # NOQA From 12f8de98b16612441742c9a95427b0450120b298 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Sun, 1 Nov 2020 17:30:08 -0500 Subject: [PATCH 50/57] Updated library import preference. --- andes/shared.py | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/andes/shared.py b/andes/shared.py index c7f80fec6..53fdddc7a 100644 --- a/andes/shared.py +++ b/andes/shared.py @@ -14,20 +14,35 @@ from numpy import ndarray # NOQA from tqdm import tqdm # NOQA +# Library preference: +# KVXOPT + ipadd > CVXOPT + ipadd > KXVOPT > CVXOPT (+ KLU or not) + try: import kvxopt from kvxopt import spmatrix as kspmatrix + KIP_ADD = False + if hasattr(kspmatrix, 'ipadd'): + KIP_ADD = True except ImportError: kvxopt = None kspmatrix = None + KIP_ADD = False + + +from cvxopt import spmatrix as cspmatrix +if hasattr(cspmatrix, 'ipadd'): + CIP_ADD = True +else: + CIP_ADD = False -# only use `kvxopt` when `ipadd` is available -IP_ADD = False -if kvxopt: - if hasattr(kspmatrix, 'ipadd'): - IP_ADD = True -if IP_ADD is False: +if KIP_ADD is True: + from kvxopt import umfpack, klu # NOQA + from kvxopt import spmatrix, matrix, sparse, spdiag # NOQA + from kvxopt import mul, div # NOQA + from kvxopt.lapack import gesv # NOQA + IP_ADD = KIP_ADD +elif CIP_ADD is True: from cvxopt import umfpack # NOQA from cvxopt import spmatrix, matrix, sparse, spdiag # NOQA from cvxopt import mul, div # NOQA @@ -36,16 +51,7 @@ from cvxoptklu import klu # NOQA except ImportError: klu = None - if hasattr(spmatrix, 'ipadd'): - IP_ADD = True - else: - IP_ADD = False -else: - from kvxopt import umfpack, klu # NOQA - from kvxopt import spmatrix, matrix, sparse, spdiag # NOQA - from kvxopt import mul, div # NOQA - from kvxopt.lapack import gesv # NOQA - + IP_ADD = CIP_ADD from andes.utils.texttable import Texttable # NOQA from andes.utils.paths import get_dot_andes_path # NOQA From 5f416bdc09efbe6a3f27053569c18118a5e9d2af Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Sun, 1 Nov 2020 17:33:57 -0500 Subject: [PATCH 51/57] import logic fix. --- andes/shared.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/andes/shared.py b/andes/shared.py index 53fdddc7a..4a66722b7 100644 --- a/andes/shared.py +++ b/andes/shared.py @@ -36,13 +36,7 @@ CIP_ADD = False -if KIP_ADD is True: - from kvxopt import umfpack, klu # NOQA - from kvxopt import spmatrix, matrix, sparse, spdiag # NOQA - from kvxopt import mul, div # NOQA - from kvxopt.lapack import gesv # NOQA - IP_ADD = KIP_ADD -elif CIP_ADD is True: +if kvxopt is None or (KIP_ADD is False and CIP_ADD is True): from cvxopt import umfpack # NOQA from cvxopt import spmatrix, matrix, sparse, spdiag # NOQA from cvxopt import mul, div # NOQA @@ -52,6 +46,12 @@ except ImportError: klu = None IP_ADD = CIP_ADD +else: + from kvxopt import umfpack, klu # NOQA + from kvxopt import spmatrix, matrix, sparse, spdiag # NOQA + from kvxopt import mul, div # NOQA + from kvxopt.lapack import gesv # NOQA + IP_ADD = KIP_ADD from andes.utils.texttable import Texttable # NOQA from andes.utils.paths import get_dot_andes_path # NOQA From 86bd84fe790e37269aa8253e80655b4e2fa48610 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Sun, 1 Nov 2020 17:39:41 -0500 Subject: [PATCH 52/57] Added kvxopt as a dependency. --- docs/source/install.rst | 37 +++++++++++++---------------------- docs/source/release-notes.rst | 16 ++++++++++++++- requirements.txt | 1 + 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/docs/source/install.rst b/docs/source/install.rst index d7686f5a6..a4e5cbb17 100644 --- a/docs/source/install.rst +++ b/docs/source/install.rst @@ -165,46 +165,37 @@ Change directory to the ANDES source code folder that contains ``setup.py`` and Performance Packages (Advanced) =============================== -The following two forks of ``cvxopt``, ``cvxoptklu``, ``cvxopt`` with ``spmatrix.ipadd`` +The following two forks of ``cvxopt``: ``kvxopt`` and ``cvxopt`` with ``spmatrix.ipadd`` are optional but can significantly boost the performance of ANDES. -**Installation requires a C compiler**, ``openblas`` and ``SuiteSparse`` libraries. .. note:: Performance packages can be safely skipped and will not affect the functionality of ANDES. -.. warning:: - - We have not tried to compile either package on Windows. - Refer to the CVXOPT installation instructions for Windows at - http://cvxopt.org/install/index.html#windows - -cxvoptklu ---------- -``cvxoptklu`` is a fork of the CVXOPT with KLU by Uriel Sandoval (@sanurielf). -In addition to UMFPACK, ``cvxoptklu`` interfaces ``cvxopt`` to KLU, which is +KVXOPT +------ +``KVXOPT`` is a fork of the CVXOPT with KLU by Uriel Sandoval (@sanurielf). +In addition to UMFPACK, ``KVXOPT`` interfaces ``cvxopt`` to KLU, which is roughly 20% faster than UMFPACK for circuit simulation based on our testing. -To install ``cvxoptklu``, on Debian GNU/Linux, one can do +To install ``KVXOPT`` run .. code:: bash - sudo apt install libopenblas-dev libsuitesparse-dev - pip install cvxoptklu + python -m pip install kvxopt -On macOS, one can install with homebrew using +CVXOPT with ipadd +----------------- -.. code:: bash +**Installation requires a C compiler**, ``openblas`` and ``SuiteSparse`` libraries. - brew install openblas suitesparse - pip install cvxoptklu +.. warning:: -To install from source code, use the repository at -https://github.com/cuihantao/cvxoptklu. + We have not tried to compile CVXOPT on Windows. + Refer to the CVXOPT installation instructions for Windows at + http://cvxopt.org/install/index.html#windows -CVXOPT with ipadd ------------------ To install our fork of ``cvxopt`` with ``spmatrix.ipadd``, one need to clone the repository and compile from source. diff --git a/docs/source/release-notes.rst b/docs/source/release-notes.rst index 9866607bc..6037b5354 100644 --- a/docs/source/release-notes.rst +++ b/docs/source/release-notes.rst @@ -8,9 +8,23 @@ The APIs before v3.0.0 are in beta and may change without prior notice. v1.2 Notes ---------- -v1.2.2 +v1.2.2 (2020-11-01) ``````````````````` +New Models: + +- ``PVD1`` model, WECC distributed PV model. + Supports multiple PVD1 devices on the same bus. +- Added ``ACEc`` model, ACE calculation with continuous freq. + +Changes and fixes: + - Renamed `TDS._itm_step` to `TDS.itm_step` as a public API. +- Allow variable `sys_f` (system frequency) in equation strings. +- Fixed ACE equation. + measurement. +- Support ``kvxopt`` as a drop-in replacement for ``cvxopt`` + to bring KLU to Windows (and other platforms). +- Added ``kvxopt`` as a dependency for PyPI installation. v1.2.1 (2020-10-11) ``````````````````` diff --git a/requirements.txt b/requirements.txt index f42b79606..67815b9ca 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ cvxopt>=1.2.3 +kvxopt numpy scipy sympy==1.5.1 From 9211d194929ee6b207bfed65433228f6b6072fe8 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Sun, 1 Nov 2020 17:50:28 -0500 Subject: [PATCH 53/57] Skip kvxopt if shared libs are not available. --- andes/shared.py | 1 + 1 file changed, 1 insertion(+) diff --git a/andes/shared.py b/andes/shared.py index 4a66722b7..45548500e 100644 --- a/andes/shared.py +++ b/andes/shared.py @@ -19,6 +19,7 @@ try: import kvxopt + from kvxopt import umfpack # test if shared libs can be found from kvxopt import spmatrix as kspmatrix KIP_ADD = False if hasattr(kspmatrix, 'ipadd'): From 747fe28337149f24be21a542fe776bf24dac1a04 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Sun, 1 Nov 2020 17:54:11 -0500 Subject: [PATCH 54/57] Reverted kvxopt dependency. --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 67815b9ca..f42b79606 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ cvxopt>=1.2.3 -kvxopt numpy scipy sympy==1.5.1 From 9750d557c4ec5d0f272efc55f4c58ebaaaec2a64 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Sun, 1 Nov 2020 17:55:37 -0500 Subject: [PATCH 55/57] Updated docs. --- andes/cases/ieee14/pert.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/andes/cases/ieee14/pert.py b/andes/cases/ieee14/pert.py index ae10577b5..151dd266c 100644 --- a/andes/cases/ieee14/pert.py +++ b/andes/cases/ieee14/pert.py @@ -1,7 +1,14 @@ """ -Empty pert file +An empty pert file. """ def pert(t, system): + """ + Perturbation function called at each step. + + The function is named "pert" and takes two arguments: + ``t`` for simulation time, and ``system`` for the system object. + + """ pass From b8c07af6b17995610baac57e3d1a2969a4ac1f61 Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Sun, 1 Nov 2020 17:57:51 -0500 Subject: [PATCH 56/57] Removed unused imports. --- andes/models/distributed.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/andes/models/distributed.py b/andes/models/distributed.py index fbff22799..b7e38d672 100644 --- a/andes/models/distributed.py +++ b/andes/models/distributed.py @@ -4,13 +4,12 @@ from andes.core.model import Model, ModelData from andes.core.param import NumParam, IdxParam -from andes.core.block import Lag, DeadBand1, LimiterGain # NOQA -from andes.core.var import ExtAlgeb, ExtState, Algeb, State # NOQA +from andes.core.block import Lag, DeadBand1, LimiterGain +from andes.core.var import ExtAlgeb, Algeb -from andes.core.service import ConstService, ExtService, FlagValue, DataSelect, DeviceFinder # NOQA -from andes.core.service import VarService, Replace # NOQA -from andes.core.service import NumSelect # NOQA -from andes.core.discrete import Switcher, Limiter # NOQA +from andes.core.service import ConstService, ExtService, VarService +from andes.core.service import DataSelect, DeviceFinder +from andes.core.discrete import Switcher, Limiter class PVD1Data(ModelData): From 194bdac840c0b917de974321f3ebb3b1f2b01dcd Mon Sep 17 00:00:00 2001 From: Hantao Cui Date: Sun, 1 Nov 2020 17:58:04 -0500 Subject: [PATCH 57/57] Added docs. --- tests/test_paths.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_paths.py b/tests/test_paths.py index 8761529c6..a4d64659d 100644 --- a/tests/test_paths.py +++ b/tests/test_paths.py @@ -26,6 +26,7 @@ def test_addfile_path(self): ) def test_pert_file(self): + """Test path of pert file""" path, case = os.path.split(self.ieee14) # --- with pert file ---