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
.
|
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
andLVGate
.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 intonp.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
, wheretstep
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 likeDelay
.
- 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