Skip to content

Commit

Permalink
Merge pull request #93 from cuihantao/develop
Browse files Browse the repository at this point in the history
Prepare for v1.2.2 release.
  • Loading branch information
cuihantao committed Nov 1, 2020
2 parents 5d0885a + 194bdac commit 518ab41
Show file tree
Hide file tree
Showing 33 changed files with 890 additions and 412 deletions.
2 changes: 1 addition & 1 deletion andes/cases/GBnetwork/README.md
@@ -1,4 +1,4 @@
GBnetwork power flow case downloaded from
https://www.maths.ed.ac.uk/optenergy/NetworkData/fullGB/
<https://www.maths.ed.ac.uk/optenergy/NetworkData/fullGB/>

Dynamic data is randomly generated and does not represent the actual system.
2 changes: 1 addition & 1 deletion andes/cases/ieee14/README.md
Expand Up @@ -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 <https://drive.google.com/drive/folders/0B7uS9L2Woq_7YzYzcGhXT2VQYXc>

Dynamic data is created by H. Cui for ANDES.

Expand Down
Binary file added andes/cases/ieee14/ieee14_pvd1.xlsx
Binary file not shown.
14 changes: 14 additions & 0 deletions andes/cases/ieee14/pert.py
@@ -0,0 +1,14 @@
"""
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
Binary file added andes/cases/kundur/kundur_vsc.xlsx
Binary file not shown.
2 changes: 1 addition & 1 deletion 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: <https://github.com/ALSETLab/Nordic44-Nordpool/tree/master/nordic44/models>

Most dynamic models in this system have not been supported.
Binary file added andes/cases/wecc/wecc_full.xlsx
Binary file not shown.
Binary file added andes/cases/wecc/wecc_gencls.xlsx
Binary file not shown.
64 changes: 63 additions & 1 deletion andes/core/block.py
Expand Up @@ -1467,14 +1467,16 @@ class GainLimiter(Block):
│ │ _____/
└─────┘ lower
TODO: Add an extra gain block "R" for y.
Parameters
----------
u : str, BaseVar
Input variable, or an equation string for constructing an anonymous variable
"""

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)
Expand Down Expand Up @@ -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`.
Expand Down
8 changes: 6 additions & 2 deletions andes/core/discrete.py
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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])
Expand Down
36 changes: 23 additions & 13 deletions andes/core/model.py
Expand Up @@ -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
Expand Down Expand Up @@ -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)`.
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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):
"""
Expand Down Expand Up @@ -1621,22 +1625,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:
"""
Expand Down Expand Up @@ -1673,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()
Expand Down Expand Up @@ -1798,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)
Expand Down
11 changes: 11 additions & 0 deletions andes/core/service.py
Expand Up @@ -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.")
Expand Down Expand Up @@ -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)
Expand All @@ -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:
Expand Down
20 changes: 10 additions & 10 deletions andes/io/__init__.py
Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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.')
Expand Down
2 changes: 1 addition & 1 deletion andes/io/psse.py
Expand Up @@ -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:
Expand Down
7 changes: 4 additions & 3 deletions andes/models/__init__.py
@@ -1,4 +1,4 @@
from collections import OrderedDict # NOQA
from collections import OrderedDict


# Notes:
Expand All @@ -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']),
Expand All @@ -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']),
])

0 comments on commit 518ab41

Please sign in to comment.