Discrete#

Background#

The discrete component library contains a special type of block for modeling the discontinuity in power system devices. Such continuities can be device-level physical constraints or algorithmic limits imposed on controllers.

The base class for discrete components is andes.core.discrete.Discrete.

Discrete([name, tex_name, info, no_warn, ...])

Base discrete class.

The uniqueness of discrete components is the way it works. Discrete components take inputs, criteria, and exports a set of flags with the component-defined meanings. These exported flags can be used in algebraic or differential equations to build piece-wise equations.

For example, Limiter takes a v-provider as input, two v-providers as the upper and the lower bound. It exports three flags: zi (within bound), zl (below lower bound), and zu (above upper bound). See the code example in models/pv.py for an example voltage-based PQ-to-Z conversion.

It is important to note when the flags are updated. Discrete subclasses can use three methods to check and update the value and equations. Among these methods, check_var is called before equation evaluation, but check_eq and set_eq are called after equation update. In the current implementation, check_var updates flags for variable-based discrete components (such as Limiter). check_eq updates flags for equation-involved discrete components (such as AntiWindup). set_var` is currently only used by AntiWindup to store the pegged states.

ANDES includes the following types of discrete components.

Limiters#

class andes.core.discrete.Limiter(u, lower, upper, enable=True, name: str = None, tex_name: str = None, info: str = None, min_iter: int = 2, err_tol: float = 0.01, allow_adjust: bool = True, no_lower=False, no_upper=False, sign_lower=1, sign_upper=1, equal=True, no_warn=False, zu=0.0, zl=0.0, zi=1.0)[source]

Base limiter class.

This class compares values and sets limit values. Exported flags are zi, zl and zu.

Parameters:
uBaseVar

Input Variable instance

lowerBaseParam

Parameter instance for the lower limit

upperBaseParam

Parameter instance for the upper limit

no_lowerbool

True to only use the upper limit

no_upperbool

True to only use the lower limit

sign_lower: 1 or -1

Sign to be multiplied to the lower limit

sign_upper: bool

Sign to be multiplied to the upper limit

equalbool

True to include equal signs in comparison (>= or <=).

no_warnbool

Disable initial limit warnings

zu0 or 1

Default value for zu if not enabled

zl0 or 1

Default value for zl if not enabled

zi0 or 1

Default value for zi if not enabled

Attributes:
zlarray-like

Flags of elements violating the lower limit; A array of zeros and/or ones.

ziarray-like

Flags for within the limits

zuarray-like

Flags for violating the upper limit

Notes

If not enabled, the default flags are zu = zl = 0, zi = 1.

class andes.core.discrete.SortedLimiter(u, lower, upper, n_select: int = 5, name=None, tex_name=None, enable=True, abs_violation=True, min_iter: int = 2, err_tol: float = 0.01, allow_adjust: bool = True, zu=0.0, zl=0.0, zi=1.0, ql=0.0, qu=0.0)[source]

A limiter that sorts inputs based on the absolute or relative amount of limit violations.

Parameters:
n_selectint

the number of violations to be flagged, for each of over-limit and under-limit cases. If n_select == 1, at most one over-limit and one under-limit inputs will be flagged. If n_select is zero, heuristics will be used.

abs_violationbool

True to use the absolute violation. False if the relative violation abs(violation/limit) is used for sorting. Since most variables are in per unit, absolute violation is recommended.

class andes.core.discrete.HardLimiter(u, lower, upper, enable=True, name: str = None, tex_name: str = None, info: str = None, min_iter: int = 2, err_tol: float = 0.01, allow_adjust: bool = True, no_lower=False, no_upper=False, sign_lower=1, sign_upper=1, equal=True, no_warn=False, zu=0.0, zl=0.0, zi=1.0)[source]

Hard limiter for algebraic or differential variable. This class is an alias of Limiter.

class andes.core.discrete.RateLimiter(u, lower, upper, enable=True, no_lower=False, no_upper=False, lower_cond=1, upper_cond=1, name=None, tex_name=None, info=None)[source]

Rate limiter for a differential variable.

RateLimiter does not export any variable. It directly modifies the differential equation value.

Warning

RateLimiter cannot be applied to a state variable that already undergoes an AntiWindup limiter. Use AntiWindupRate for a rate-limited anti-windup limiter.

Notes

RateLimiter inherits from Discrete to avoid internal naming conflicts with Limiter.

class andes.core.discrete.AntiWindup(u, lower, upper, enable=True, no_warn=False, no_lower=False, no_upper=False, sign_lower=1, sign_upper=1, name=None, tex_name=None, info=None, state=None, allow_adjust: bool = True)[source]

Anti-windup limiter.

Anti-windup limiter prevents the wind-up effect of a differential variable. The derivative of the differential variable is reset if it continues to increase in the same direction after exceeding the limits. During the derivative return, the limiter will be inactive

if x > xmax and x dot > 0: x = xmax and x dot = 0
if x < xmin and x dot < 0: x = xmin and x dot = 0

This class takes one more optional parameter for specifying the equation.

Parameters:
stateState, ExtState

A State (or ExtState) whose equation value will be checked and, when condition satisfies, will be reset by the anti-windup-limiter.

class andes.core.discrete.AntiWindupRate(u, lower, upper, rate_lower, rate_upper, no_lower=False, no_upper=False, rate_no_lower=False, rate_no_upper=False, rate_lower_cond=None, rate_upper_cond=None, enable=True, name=None, tex_name=None, info=None, allow_adjust: bool = True)[source]

Anti-windup limiter with rate limits

Comparers#

class andes.core.discrete.LessThan(u, bound, equal=False, enable=True, name=None, tex_name=None, info: str = None, cache: bool = False, z0=0, z1=1)[source]

Less than (<) comparison function that tests if u < bound.

Exports two flags: z1 and z0. For elements satisfying the less-than condition, the corresponding z1 = 1. z0 is the element-wise negation of z1.

Notes

The default z0 and z1, if not enabled, can be set through the constructor. By default, the model will not adjust the limit.

class andes.core.discrete.Selector(*args, fun, tex_name=None, info=None)[source]

Selection between two variables using the provided reduce function.

The reduce function should take the given number of arguments. An example function is np.maximum.reduce which can be used to select the maximum.

Names are in s0, s1.

Warning

A potential bug when more than two inputs are provided, and values in different inputs are equal. Only two inputs are allowed.

Deprecated since version 1.5.9: Use of this class for comparison-based output is discouraged. Instead, use LessThan and Limiter to construct piesewise equations.

See the new implementation of HVGate and LVGate.

See also

numpy.ufunc.reduce

NumPy reduce function

Notes

A common pitfall is the 0-based indexing in the Selector flags. Note that exported flags start from 0. Namely, s0 corresponds to the first variable provided for the Selector constructor.

Examples

Example 1: select the largest value between v0 and v1 and put it into vmax.

After the definitions of v0 and v1, define the algebraic variable vmax for the largest value, and a selector vs

self.vmax = Algeb(v_str='maximum(v0, v1)',
                  tex_name='v_{max}',
                  e_str='vs_s0 * v0 + vs_s1 * v1 - vmax')

self.vs = Selector(self.v0, self.v1, fun=np.maximum.reduce)

The initial value of vmax is calculated by maximum(v0, v1), which is the element-wise maximum in SymPy and will be generated into np.maximum(v0, v1). The equation of vmax is to select the values based on vs_s0 and vs_s1.

class andes.core.discrete.Switcher(u, options: list | Tuple, info: str = None, name: str = None, tex_name: str = None, cache=True)[source]

Switcher based on an input parameter.

The switch class takes one v-provider, compares the input with each value in the option list, and exports one flag array for each option. The flags are 0-indexed.

Exported flags are named with _s0, _s1, ..., with a total number of len(options). See the examples section.

Notes

Switches needs to be distinguished from Selector.

Switcher is for generating flags indicating option selection based on an input parameter. Selector is for generating flags at run time based on variable values and a selection function.

Examples

The IEEEST model takes an input for selecting the signal. Options are 1 through 6. One can construct

self.IC = NumParam(info='input code 1-6')  # input code
self.SW = Switcher(u=self.IC, options=[0, 1, 2, 3, 4, 5, 6])

If the IC values from the data file ends up being

self.IC.v = np.array([1, 2, 2, 4, 6])

Then, the exported flag arrays will be

{'IC_s0': np.array([0, 0, 0, 0, 0]),
 'IC_s1': np.array([1, 0, 0, 0, 0]),
 'IC_s2': np.array([0, 1, 1, 0, 0]),
 'IC_s3': np.array([0, 0, 0, 0, 0]),
 'IC_s4': np.array([0, 0, 0, 1, 0]),
 'IC_s5': np.array([0, 0, 0, 0, 0]),
 'IC_s6': np.array([0, 0, 0, 0, 1])
}

where IC_s0 is used for padding so that following flags align with the options.

Deadband#

class andes.core.discrete.DeadBand(u, center, lower, upper, enable=True, equal=False, zu=0.0, zl=0.0, zi=0.0, name=None, tex_name=None, info=None)[source]

The basic deadband type.

Parameters:
uNumParam

The pre-deadband input variable

centerNumParam

Neutral value of the output

lowerNumParam

Lower bound

upperNumParam

Upper bound

enablebool

Enabled if True; Disabled and works as a pass-through if False.

Notes

Input changes within a deadband will incur no output changes. This component computes and exports three flags.

Three flags computed from the current input:
  • zl: True if the input is below the lower threshold

  • zi: True if the input is within the deadband

  • zu: True if is above the lower threshold

Initial condition:

All three flags are initialized to zero. All flags are updated during check_var when enabled. If the deadband component is not enabled, all of them will remain zero.

Examples

Exported deadband flags need to be used in the algebraic equation corresponding to the post-deadband variable. Assume the pre-deadband input variable is var_in and the post-deadband variable is var_out. First, define a deadband instance db in the model using

self.db = DeadBand(u=self.var_in, center=self.dbc,
                   lower=self.dbl, upper=self.dbu)

To implement a no-memory deadband whose output returns to center when the input is within the band, the equation for var can be written as

var_out.e_str = 'var_in * (1 - db_zi) + \
                 (dbc * db_zi) - var_out'
class andes.core.discrete.DeadBandRT(u, center, lower, upper, enable=True)[source]

Deadband with flags for directions of return.

Parameters:
uNumParam

The pre-deadband input variable

centerNumParam

Neutral value of the output

lowerNumParam

Lower bound

upperNumParam

Upper bound

enablebool

Enabled if True; Disabled and works as a pass-through if False.

Notes

Input changes within a deadband will incur no output changes. This component computes and exports five flags. The additional two flags on top of DeadBand indicate the direction of return:

  • zur: True if the input is/has been within the deadband and was returned from the upper threshold

  • zlr: True if the input is/has been within the deadband and was returned from the lower threshold

Initial condition:

All five flags are initialized to zero. All flags are updated during check_var when enabled. If the deadband component is not enabled, all of them will remain zero.

Examples

To implement a deadband whose output is pegged at the nearest deadband bounds, the equation for var can be provided as

var_out.e_str = 'var_in * (1 - db_zi) + \
                 dbl * db_zlr + \
                 dbu * db_zur - var_out'

Others#

class andes.core.discrete.Average(u, mode='step', delay=0, name=None, tex_name=None, info=None)[source]

Compute the average value of a BaseVar over a period of time or a number of simulation steps.

Average is based on the memory implemented in the Delay class. The same modes as in Delay are supported.

The output of the Average class is named <INSTANCE_NAME>_v, where <INSTANCE_NAME> is the instance name of Average.

class andes.core.discrete.Delay(u, mode='step', delay=0, name=None, tex_name=None, info=None)[source]

The delay class.

Delay allows to impose a predefined and fixed "delay" (in either steps or seconds) for an input variable. The amount of delay must be a scalar and has to be given when instantiating the Delay class when defining the model.

Delay implements an internal memorize to store past variable values.

The default delay mode is step but can be set to time. In the time mode, the value at the current time - delay will be interpolated based on the two nearest times and values.

Delay can be applied to a state or an algebraic variable. The exported variable is named <INSTANCE_NAME>_v, where <INSTANCE_NAME> is the name of the Delay instance.

class andes.core.discrete.Derivative(u, name=None, tex_name=None, info=None)[source]

Compute the derivative of a variable using numerical differentiation.

Derivative is based on the storage implemented in the Delay class. The delay is set to 1 step so that the current and the previous step are used.

A simple first order derivative is computed using u(t) - u(t-1) / tstep, where tstep is the current step size.

Derivative is intended to be used for algebraic variables because of discontinuity. It can be applied to a state variable, but one should instead implement the right-hand side equation of the state variable in an algebraic equation to obtain the accurate derivative.

Alternatively, the washout filter (andes.core.block.Washout) can be used to implement a numerically stable derivative.

The output of the Derivative class is named <INSTANCE_NAME>_v just like Delay.

class andes.core.discrete.Sampling(u, interval=1.0, offset=0.0, name=None, tex_name=None, info=None)[source]

Sample and hold

Sample an input variable periodically at the given time interval and hold the value until the next sample time.

For example, this class can be used to implement a 4-second sampling of the AGC signal.

The output of Sampling is named <INSTANCE_NAME>_v, where <INSTANCE_NAME> is the Sampling instance name.

class andes.core.discrete.ShuntAdjust(*, v, lower, upper, bsw, gsw, dt, u, enable=True, min_iter=2, err_tol=0.01, name=None, tex_name=None, info=None, no_warn=False)[source]

Class for adjusting switchable shunts.

Parameters:
vBaseVar

Voltage measurement

lowerBaseParam

Lower voltage bound

upperBaseParam

Upper voltage bound

bswSwBlock

SwBlock instance for susceptance

gswSwBlock

SwBlock instance for conductance

dtNumParam

Delay time

uNumParam

Connection status

min_iterint

Minimum iteration number to enable shunt switching

err_tolfloat

Minimum iteration tolerance to enable switching