Blocks#

The block library contains commonly used blocks such as transfer functions and nonlinear functions. Variables and equations are pre-defined for blocks to be used as "lego pieces" for scripting DAE models.

Background#

The base class for blocks is andes.core.block.Block.

All variables in a block must be defined as attributes in the constructor, just like variable definition in models. The difference is that the variables are "exported" from a block to the capturing model. All exported variables need to be placed in a dictionary, self.vars at the end of the block constructor.

Blocks can be nested as advanced usage. See the API documentation below for more details.

class andes.core.block.Block(name: str | None = None, tex_name: str | None = None, info: str | None = None, namespace: str = 'local')[source]

Base class for control blocks.

Blocks are meant to be instantiated as Model attributes to provide pre-defined equation sets. Subclasses must overload the __init__ method to take custom inputs. Subclasses of Block must overload the define method to provide initialization and equation strings. Exported variables, services and blocks must be constructed into a dictionary self.vars at the end of the constructor.

Blocks can be nested. A block can have blocks but itself as attributes and therefore reuse equations. When a block has sub-blocks, the outer block must be constructed with a``name``.

Nested block works in the following way: the parent block modifies the sub-block's name attribute by prepending the parent block's name at the construction phase. The parent block then exports the sub-block as a whole. When the parent Model class picks up the block, it will recursively import the variables in the block and the sub-blocks correctly. See the example section for details.

Parameters:
namestr, optional

Block name

tex_namestr, optional

Block LaTeX name

infostr, optional

Block description.

namespacestr, local or parent

Namespace of the exported elements. If 'local', the block name will be prepended by the parent. If 'parent', the original element name will be used when exporting.

Warning

It is a good practice to avoid more than one level of nesting, to avoid multi-underscore variable names.

Examples

Example for two-level nested blocks. Suppose we have the following hierarchy

SomeModel instance M contains an instance of LeadLag block named A, which contains a Lag instance named B. Both A and B exports two variables x and y.

In the code for SomeModel, the following code is used to instantiate LeadLag

class SomeModel:
    def __init__(...)
        ... self.A = LeadLag(name='A',
                         u=self.foo1, T1=self.foo2, T2=self.foo3)

To use Lag in the LeadLag code, the following lines are found in the constructor of LeadLag

class LeadLag:
    def __init__(name, ...)
        ... self.B = Lag(u=self.y, K=self.K, T=self.T)

        # register `self.B` with the name `A` self.vars = {..., 'B':
        self.B}

When instantiating any block instance, its __setattr__ function assigns names to exported variables and blocks. For the LeadLag instance with the name A, its member attribute B is assigned the name A_B by convention. That is, A_B will be set to B.name.

When A is picked up by SomeModel.__setattr__, B is captured from A's exports with the name A_B. Recursively, B's variables are exported, Recall that B.name is now A_B, following the naming rule (parent block's name + variable name), B's internal variables become A_B_x and A_B_y.

Again, the LeadLag instance name (A.name in this example) must be given when instantiating in SomeModel's constructor to ensure correct name propagation. If there is more than one level of nesting, other than the terminal-level block, all names of the parent blocks must be provided at instantiation.

In such a way, B's define() needs no modification since the naming rule is the same. For example, B's internal y is always {self.name}_y, although the nested B has gotten a new name A_B.

define()[source]

Function for setting the initialization and equation strings for internal variables. This method must be implemented by subclasses.

The equations should be written with the "final" variable names. Let's say the block instance is named blk (kept at self.name of the block), and an internal variable v is defined. The internal variable will be captured as blk_v by the parent model. Therefore, all equations should use {self.name}_v to represent variable v, where {self.name} is the name of the block at run time.

On the other hand, the names of externally provided parameters or variables are obtained by directly accessing the name attribute. For example, if self.T is a parameter provided through the block constructor, {self.T.name} should be used in the equation.

See also

PIController.define

Equations for the PI Controller block

Examples

An internal variable v has a trivial equation T = v, where T is a parameter provided to the block constructor.

In the model, one has

class SomeModel():
    def __init__(...)
        self.input = Algeb()
        self.T = Param()

        self.blk = ExampleBlock(u=self.input, T=self.T)

In the ExampleBlock function, the internal variable is defined in the constructor as

class ExampleBlock():
    def __init__(...):
        self.v = Algeb()
        self.vars = {'v', self.v}

In the define, the equation is provided as

def define(self):
    self.v.v_str = '{self.T.name}'
    self.v.e_str = '{self.T.name} - {self.name}_v'

In the parent model, v from the block will be captured as blk_v, and the equation will evaluate into

self.blk_v.v_str = 'T'
self.blk_v.e_str = 'T - blk_v'

Block Types#

Block([name, tex_name, info, namespace])

Base class for control blocks.

Gain(u, K[, name, tex_name, info])

Gain block.

GainLimiter(u, K, R, lower, upper[, ...])

Gain followed by a limiter and another gain.

Piecewise(u, points, funs[, name, tex_name, ...])

Piecewise block.

HVGate(u1, u2[, name, tex_name, info])

High Value Gate.

LVGate(u1, u2[, name, tex_name, info])

Low Value Gate.

DeadBand1(u, center, lower, upper[, gain, ...])

Deadband type 1 (linear, non-step).

Integrator(u, T, K, y0[, check_init, name, ...])

Integrator block.

IntegratorAntiWindup(u, T, K, y0, lower, upper)

Integrator block with anti-windup limiter.

Lag(u, T, K[, D, name, tex_name, info])

Lag (low pass filter) transfer function.

LagAntiWindup(u, T, K, lower, upper[, D, ...])

Lag (low pass filter) transfer function block with an anti-windup limiter.

LagFreeze(u, T, K, freeze[, D, name, ...])

Lag with an input to freeze the state.

LagAWFreeze(u, T, K, lower, upper, freeze[, ...])

Lag with anti-windup limiter and state freeze.

LagRate(u, T, K, rate_lower, rate_upper[, ...])

Lag (low pass filter) transfer function block with a rate limiter.

LagAntiWindupRate(u, T, K, lower, upper, ...)

Lag (low pass filter) transfer function block with a rate limiter and an anti-windup limiter.

Washout(u, T, K[, name, tex_name, info])

Washout filter (high pass) block.

WashoutOrLag(u, T, K[, name, zero_out, ...])

Washout with the capability to convert to Lag when K = 0.

LeadLag(u, T1, T2[, K, zero_out, name, ...])

Lead-Lag transfer function block in series implementation.

LeadLagLimit(u, T1, T2, lower, upper[, ...])

Lead-Lag transfer function block with hard limiter (series implementation).

Lag2ndOrd(u, K, T1, T2[, name, tex_name, info])

Second order lag transfer function (low-pass filter).

LeadLag2ndOrd(u, T1, T2, T3, T4[, zero_out, ...])

Second-order lead-lag transfer function block.

PIController(u, kp, ki[, ref, x0, name, ...])

Proportional Integral Controller.

PIAWHardLimit(u, kp, ki, aw_lower, aw_upper, ...)

PI controller with anti-windup limiter on the integrator and hard limit on the output.

PITrackAW(u, kp, ki, ks, lower, upper[, ...])

PI with tracking anti-windup limiter

PIFreeze(u, kp, ki, freeze[, ref, x0, name, ...])

PI controller with state freeze.

PITrackAWFreeze(u, kp, ki, ks, lower, upper, ...)

PI controller with tracking anti-windup limiter and state freeze.

PIDController(u, kp, ki, kd, Td, name[, ...])

Proportional Integral Derivative Controller.

PIDAWHardLimit(u, kp, ki, kd, Td, aw_lower, ...)

PID controller with anti-windup limiter on the integrator and hard limit on the output.

PIDTrackAW(u, kp, ki, kd, Td, ks, lower, upper)

PID with tracking anti-windup limiter

Linear Blocks#

class andes.core.block.Gain(u, K, name=None, tex_name=None, info=None)[source]

Gain block.

     ┌───┐
u -> │ K │ -> y
     └───┘

Exports an algebraic output y.

define()[source]

Implemented equation and the initial condition are

\[\begin{split}y = K u \\ y^{(0)} = K u^{(0)}\end{split}\]
class andes.core.block.GainLimiter(u, K, R, lower, upper, no_lower=False, no_upper=False, sign_lower=1, sign_upper=1, allow_adjust=True, name=None, tex_name=None, info=None)[source]

Gain followed by a limiter and another gain.

Exports the limited output y, unlimited output x, and HardLimiter lim.

     ┌─────┐         upper  ┌─────┐
     │     │        /¯¯¯¯¯  │     │
u -> │  K  │ -> x  /   ->   │  R  │ -> y
     │     │ _____/         │     │
     └─────┘ lower          └─────┘
Parameters:
ustr, BaseVar

Input variable, or an equation string for constructing an anonymous variable

Kstr, BaseParam, BaseService

Initial gain for u before limiter

Rstr, BaseParam, BaseService

Post limiter gain

define()[source]

TODO: write docstring

class andes.core.block.Piecewise(u, points: List | Tuple, funs: List | Tuple, name=None, tex_name=None, info=None)[source]

Piecewise block. Outputs an algebraic variable y.

This block takes a list of N points, [x0, x1, ...x_{n-1}] to define N+1 ranges, namely (-inf, x0), (x0, x1), ..., (x_{n-1}, +inf). and a list of N+1 function strings [fun0, ..., fun_n].

Inputs that fall within each range applies the corresponding function. The first range (-inf, x0) applies fun_0, and the last range (x_{n-1}, +inf) applies the last function fun_n.

The function returns zero if no condition is met.

Note

Piecewise.y must remain Algeb (not Observable) because: (1) model authors may set v_iter on the output for coupled iterative initialization (e.g., EXAC1), and (2) zero-valued branches cause ComplexInfinity when the expression is substituted into denominators during code generation.

Parameters:
pointslist, tuple

A list of piecewise points. Need to be provided in the constructor function.

funslist, tuple

A list of strings for the piecewise functions. Need to be provided in the overloaded define function.

define()[source]

Build the equation string for the piecewise equations.

self.funs needs to be provided with the function strings corresponding to each range.

class andes.core.block.HVGate(u1, u2, name=None, tex_name=None, info=None)[source]

High Value Gate. Outputs the maximum of two inputs.

      ┌─────────┐
u1 -> │ HV Gate │
      │         │ ->  y
u2 -> │  (MAX)  │
      └─────────┘
class andes.core.block.LVGate(u1, u2, name=None, tex_name=None, info=None)[source]

Low Value Gate. Outputs the minimum of the two inputs.

      ┌─────────┐
u1 -> │ LV Gate |
      │         | ->  y
u2 -> │  (MIN)  |
      └─────────┘
class andes.core.block.DeadBand1(u, center, lower, upper, gain=1.0, enable=True, name=None, tex_name=None, info=None, namespace='local')[source]

Deadband type 1 (linear, non-step).

Note

DeadBand1.y must remain Algeb (not Observable) because other models (e.g., DGPRCT1) reference it via ExtAlgeb.

Parameters:
center

Default value when within the deadband. If the input is an error signal, center should be set to zero.

gain

Gain multiplied to DeadBand discrete block's output.

Notes

Block diagram

      |   /
______|__/___   -> Gain -> DeadBand1_y
   /  |
  /   |

First Order Blocks#

class andes.core.block.Integrator(u, T, K, y0, check_init=True, name=None, tex_name=None, info=None)[source]

Integrator block.

     ┌──────┐
u -> │ K/sT │ -> y
     └──────┘

Exports a differential variable y.

The initial output needs to be specified through y0.

define()[source]

Implemented equation and the initial condition are

\[\begin{split}\dot{y} = K u \\ y^{(0)} = 0\end{split}\]
class andes.core.block.IntegratorAntiWindup(u, T, K, y0, lower, upper, name=None, tex_name=None, info=None, no_warn=False)[source]

Integrator block with anti-windup limiter.

           upper
          /¯¯¯¯¯
     ┌──────┐
u -> │ K/sT │ -> y
     └──────┘
   _____/
   lower

Exports a differential variable y and an AntiWindup lim. The initial output must be specified through y0.

define()[source]

Implemented equation and the initial condition are

\[\begin{split}\dot{y} = K u \\ y^{(0)} = 0\end{split}\]
class andes.core.block.Lag(u, T, K, D=1, name=None, tex_name=None, info=None)[source]

Lag (low pass filter) transfer function.

     ┌────────┐
     │    K   │
u -> │ ────── │ -> y
     │ D + sT │
     └────────┘

Exports one state variable y as the output.

Parameters:
K

Gain

T

Time constant

D

Constant

u

Input variable

define()[source]

Notes

Equations and initial values are

\[\begin{split}T \dot{y} &= (Ku - Dy) \\ y^{(0)} &= Ku / D\end{split}\]
class andes.core.block.LagAntiWindup(u, T, K, lower, upper, D=1, name=None, tex_name=None, info=None)[source]

Lag (low pass filter) transfer function block with an anti-windup limiter.

             upper
           /¯¯¯¯¯¯
     ┌────────┐
     │    K   │
u -> │ ────── │ -> y
     │ D + sT │
     └────────┘
   ______/
   lower

Exports one state variable y as the output and one AntiWindup instance lim.

Parameters:
K

Gain

T

Time constant

D

Constant

u

Input variable

define()[source]

Notes

Equations and initial values are

\[\begin{split}T \dot{y} &= (Ku - Dy) \\ y^{(0)} &= K u / D\end{split}\]
class andes.core.block.LagFreeze(u, T, K, freeze, D=1, name=None, tex_name=None, info=None)[source]

Lag with an input to freeze the state.

During the period when the freeze signal is 1, the LagFreeze output will be frozen.

define()[source]

Notes

Equations and initial values are

\[\begin{split}T \dot{y} &= (1 - freeze) * (Ku - y) \\ y^{(0)} &= K u\end{split}\]
class andes.core.block.LagAWFreeze(u, T, K, lower, upper, freeze, D=1, name=None, tex_name=None, info=None)[source]

Lag with anti-windup limiter and state freeze.

Note that the output y is a state variable.

define()[source]

Notes

Equations and initial values are

\[\begin{split}T \dot{y} &= (1 - freeze) (Ku - y) \\ y^{(0)} &= K u\end{split}\]

y undergoes an anti-windup limiter.

class andes.core.block.LagRate(u, T, K, rate_lower, rate_upper, D=1, rate_no_lower=False, rate_no_upper=False, rate_lower_cond=None, rate_upper_cond=None, name=None, tex_name=None, info=None)[source]

Lag (low pass filter) transfer function block with a rate limiter.

             / rate_upper
       ┌────────┐
       │    K   │
  u -> │ ────── │ -> y
       │ D + sT │
       └────────┘
rate_lower /

Exports one state variable y as the output and one AntiWindupRate instance lim.

Parameters:
K

Gain

T

Time constant

D

Constant

u

Input variable

define()[source]

Notes

Equations and initial values are

\[\begin{split}T \dot{y} &= (Ku - y) \\ y^{(0)} &= K u\end{split}\]
class andes.core.block.LagAntiWindupRate(u, T, K, lower, upper, rate_lower, rate_upper, D=1, no_lower=False, no_upper=False, rate_no_lower=False, rate_no_upper=False, rate_lower_cond=None, rate_upper_cond=None, name=None, tex_name=None, info=None)[source]

Lag (low pass filter) transfer function block with a rate limiter and an anti-windup limiter.

             upper
rate_upper /¯¯¯¯¯¯
     ┌────────┐
     │    K   │
u -> │ ────── │ -> y
     │ D + sT │
     └────────┘
   ______/ rate_lower
   lower

Exports one state variable y as the output and one AntiWindupRate instance lim.

Parameters:
K

Gain

T

Time constant

D

Constant

u

Input variable

define()[source]

Notes

Equations and initial values are

\[\begin{split}T \dot{y} &= (Ku - Dy) \\ y^{(0)} &= K u / D\end{split}\]
class andes.core.block.Washout(u, T, K, name=None, tex_name=None, info=None)[source]

Washout filter (high pass) block.

     ┌────────┐
     │   sK   │
u -> │ ────── │ -> y
     │ 1 + sT │
     └────────┘

Exports state x (symbol x') and output algebraic variable y.

define()[source]

Notes

Equations and initial values:

\[\begin{split}T \dot{x'} &= (u - x') \\ T y &= K (u - x') \\ x'^{(0)} &= u \\ y^{(0)} &= 0\end{split}\]
class andes.core.block.WashoutOrLag(u, T, K, name=None, zero_out=True, tex_name=None, info=None)[source]

Washout with the capability to convert to Lag when K = 0.

Can be enabled with zero_out. Need to provide name to construct.

Exports state x (symbol x'), output algebraic variable y, and a LessThan block LT.

Parameters:
zero_outbool, optional

If True, sT will become 1, and the washout will become a low-pass filter. If False, functions as a regular Washout.

define()[source]

Notes

Equations and initial values:

\[\begin{split}T \dot{x'} &= (u - x') \\ T y = z_0 K (u - x') + z_1 T x \\ x'^{(0)} &= u \\ y^{(0)} &= 0\end{split}\]

where z_0 is a flag array for the greater-than-zero elements, and z_1 is that for the less-than or equal-to zero elements.

class andes.core.block.LeadLag(u, T1, T2, K=1, zero_out=True, name=None, tex_name=None, info=None)[source]

Lead-Lag transfer function block in series implementation.

     ┌───────────┐
     │   1 + sT1 │
u -> │ K ─────── │ -> y
     │   1 + sT2 │
     └───────────┘

Exports two variables: internal state x and output algebraic variable y.

Parameters:
uBaseVar or BaseParam

Input signal.

T1BaseParam

Numerator (lead) time constant.

T2BaseParam

Denominator (lag) time constant. Also used as the t_const of the internal state x.

KBaseParam or numeric, optional

Static gain (default 1).

zero_outbool, optional

If True (default), bypass the block as y = K * u when T2 <= 0. Set to False only if T2 > 0 is guaranteed by the data.

define()[source]

Notes

Implemented equations and initial values

\[\begin{split}T_2 \dot{x'} &= (u - x') \\ T_2 y &= K T_1 (u - x') + K T_2 x' + E_2 \, , \text{where} \\ E_2 = & \left\{\begin{matrix} (y - K u) &\text{ if } T_2 \leq 0 \& zero\_out=True \\ 0& \text{ otherwise } \end{matrix}\right. \\ x'^{(0)} & = u\\ y^{(0)} & = Ku\\\end{split}\]
class andes.core.block.LeadLagLimit(u, T1, T2, lower, upper, name=None, tex_name=None, info=None)[source]

Lead-Lag transfer function block with hard limiter (series implementation).

     ┌─────────┐          upper
     │ 1 + sT1 │         /¯¯¯¯¯
u -> │ ─────── │ -> ynl / -> y
     │ 1 + sT2 │  _____/
     └─────────┘  lower

Exports four variables: state x, output before hard limiter ynl, output y, and AntiWindup lim.

define()[source]

Notes

Implemented control block equations (without limiter) and initial values

\[\begin{split}T_2 \dot{x'} &= (u - x') \\ T_2 y &= T_1 (u - x') + T_2 x' \\ x'^{(0)} &= y^{(0)} = u\end{split}\]

Second Order Blocks#

class andes.core.block.Lag2ndOrd(u, K, T1, T2, name=None, tex_name=None, info=None)[source]

Second order lag transfer function (low-pass filter).

     ┌──────────────────┐
     │         K        │
u -> │ ──────────────── │ -> y
     │ 1 + sT1 + s^2 T2 │
     └──────────────────┘

Exports one two state variables (x, y), where y is the output.

Parameters:
u

Input

K

Gain

T1

First order time constant

T2

Second order time constant

define()[source]

Notes

Implemented equations and initial values are

\[\begin{split}T_2 \dot{x} &= Ku - y - T_1 x \\ \dot{y} &= x \\ x^{(0)} &= 0 \\ y^{(0)} &= K u\end{split}\]
class andes.core.block.LeadLag2ndOrd(u, T1, T2, T3, T4, zero_out=False, name=None, tex_name=None, info=None)[source]

Second-order lead-lag transfer function block.

     ┌──────────────────┐
     │ 1 + sT3 + s^2 T4 │
u -> │ ──────────────── │ -> y
     │ 1 + sT1 + s^2 T2 │
     └──────────────────┘

Exports two internal states (x1 and x2) and output algebraic variable y.

Parameters:
uBaseVar or BaseParam

Input signal.

T1BaseParam

Denominator first-order time constant.

T2BaseParam

Denominator second-order time constant. Also used as the t_const of the internal state x1.

T3BaseParam

Numerator first-order time constant.

T4BaseParam

Numerator second-order time constant.

zero_outbool, optional

If True, bypass the block as y = u when T2 <= 0. Default is False.

define()[source]

Notes

Implemented equations and initial values are

\[\begin{split}T_2 \dot{x}_1 &= u - x_2 - T_1 x_1 \\ \dot{x}_2 &= x_1 \\ T_2 y &= T_2 x_2 + T_2 T_3 x_1 + T_4 (u - x_2 - T_1 x_1) + E_2 \, , \text{ where} \\ E_2 = & \left\{\begin{matrix} (y - u) &\text{ if } T_2 \leq 0 \& zero\_out=True \\ 0& \text{ otherwise } \end{matrix}\right. \\ x_1^{(0)} &= 0 \\ x_2^{(0)} &= y^{(0)} = u\end{split}\]

PI Controllers#

class andes.core.block.PIController(u, kp, ki, ref=0.0, x0=0.0, name=None, tex_name=None, info=None)[source]

Proportional Integral Controller.

The controller takes an error signal as the input. It takes an optional ref signal, which will be subtracted from the input.

Parameters:
uBaseVar

The input variable instance

kpBaseParam

The proportional gain parameter instance

ki[type]

The integral gain parameter instance

define()[source]

Define equations for the PI Controller.

Notes

One state variable xi and one algebraic variable y are added.

Equations implemented are

\[\begin{split}\dot{x_i} &= k_i * (u - ref) \\ y &= x_i + k_p * (u - ref)\end{split}\]
class andes.core.block.PIAWHardLimit(u, kp, ki, aw_lower, aw_upper, lower, upper, no_lower=False, no_upper=False, ref=0.0, x0=0.0, name=None, tex_name=None, info=None)[source]

PI controller with anti-windup limiter on the integrator and hard limit on the output.

Limits lower and upper are on the final output, and aw_lower aw_upper are on the integrator.

define()[source]

Define equations for the PI Controller.

Notes

One state variable xi and one algebraic variable y are added.

Equations implemented are

\[\begin{split}\dot{x_i} &= k_i * (u - ref) \\ y &= x_i + k_p * (u - ref)\end{split}\]
class andes.core.block.PITrackAW(u, kp, ki, ks, lower, upper, no_lower=False, no_upper=False, ref=0.0, x0=0.0, name=None, tex_name=None, info=None)[source]

PI with tracking anti-windup limiter

define()[source]

Function for setting the initialization and equation strings for internal variables. This method must be implemented by subclasses.

The equations should be written with the "final" variable names. Let's say the block instance is named blk (kept at self.name of the block), and an internal variable v is defined. The internal variable will be captured as blk_v by the parent model. Therefore, all equations should use {self.name}_v to represent variable v, where {self.name} is the name of the block at run time.

On the other hand, the names of externally provided parameters or variables are obtained by directly accessing the name attribute. For example, if self.T is a parameter provided through the block constructor, {self.T.name} should be used in the equation.

See also

PIController.define

Equations for the PI Controller block

Examples

An internal variable v has a trivial equation T = v, where T is a parameter provided to the block constructor.

In the model, one has

class SomeModel():
    def __init__(...)
        self.input = Algeb()
        self.T = Param()

        self.blk = ExampleBlock(u=self.input, T=self.T)

In the ExampleBlock function, the internal variable is defined in the constructor as

class ExampleBlock():
    def __init__(...):
        self.v = Algeb()
        self.vars = {'v', self.v}

In the define, the equation is provided as

def define(self):
    self.v.v_str = '{self.T.name}'
    self.v.e_str = '{self.T.name} - {self.name}_v'

In the parent model, v from the block will be captured as blk_v, and the equation will evaluate into

self.blk_v.v_str = 'T'
self.blk_v.e_str = 'T - blk_v'
class andes.core.block.PIFreeze(u, kp, ki, freeze, ref=0.0, x0=0.0, name=None, tex_name=None, info=None)[source]

PI controller with state freeze.

Freezes state when the corresponding freeze == 1.

Notes

Tested in experimental.TestPITrackAW.PIFreeze.

define()[source]

Notes

One state variable xi and one algebraic variable y are added.

Equations implemented are

\[\begin{split}\dot{x_i} &= k_i * (u - ref) \\ y &= (1-freeze) * (x_i + k_p * (u - ref)) + freeze * y\end{split}\]
class andes.core.block.PITrackAWFreeze(u, kp, ki, ks, lower, upper, freeze, no_lower=False, no_upper=False, ref=0.0, x0=0.0, name=None, tex_name=None, info=None)[source]

PI controller with tracking anti-windup limiter and state freeze.

define()[source]

Function for setting the initialization and equation strings for internal variables. This method must be implemented by subclasses.

The equations should be written with the "final" variable names. Let's say the block instance is named blk (kept at self.name of the block), and an internal variable v is defined. The internal variable will be captured as blk_v by the parent model. Therefore, all equations should use {self.name}_v to represent variable v, where {self.name} is the name of the block at run time.

On the other hand, the names of externally provided parameters or variables are obtained by directly accessing the name attribute. For example, if self.T is a parameter provided through the block constructor, {self.T.name} should be used in the equation.

See also

PIController.define

Equations for the PI Controller block

Examples

An internal variable v has a trivial equation T = v, where T is a parameter provided to the block constructor.

In the model, one has

class SomeModel():
    def __init__(...)
        self.input = Algeb()
        self.T = Param()

        self.blk = ExampleBlock(u=self.input, T=self.T)

In the ExampleBlock function, the internal variable is defined in the constructor as

class ExampleBlock():
    def __init__(...):
        self.v = Algeb()
        self.vars = {'v', self.v}

In the define, the equation is provided as

def define(self):
    self.v.v_str = '{self.T.name}'
    self.v.e_str = '{self.T.name} - {self.name}_v'

In the parent model, v from the block will be captured as blk_v, and the equation will evaluate into

self.blk_v.v_str = 'T'
self.blk_v.e_str = 'T - blk_v'
class andes.core.block.PIDController(u, kp, ki, kd, Td, name, ref=0.0, x0=0.0, tex_name=None, info=None)[source]

Proportional Integral Derivative Controller.

     ┌────────────────────┐
     │      ki     skd    │
u -> │kp + ─── + ───────  │ -> y
     │      s    1 + sTd  │
     └────────────────────┘

The controller takes an error signal as the input. It takes an optional ref signal, which will be subtracted from the input.

The name is suggessted to be specified the same as the instance name.

This block assembles a PIController and a Washout.

Parameters:
uBaseVar

The input variable instance

kpBaseParam

The proportional gain parameter instance

kiBaseParam

The integral gain parameter instance

kdBaseParam

The derivative gain parameter instance

TdBaseParam

The derivative time constant parameter instance

x0BaseParam

The initial value of the integrator

define()[source]

Define equations for the PID Controller.

Notes

One PIController PIC, one Washout xd, and one algebraic variable y are added.

Equations implemented are

\[\begin{split}\dot{x_i} &= k_i * (u - ref) \\ xd &= Washout(u - ref) \\ y &= x_i + k_p * (u - ref) + xd\end{split}\]
class andes.core.block.PIDAWHardLimit(u, kp, ki, kd, Td, aw_lower, aw_upper, lower, upper, name, no_lower=False, no_upper=False, ref=0.0, x0=0.0, tex_name=None, info=None)[source]

PID controller with anti-windup limiter on the integrator and hard limit on the output.

                  upper
                /¯¯¯¯¯¯
     ┌────────────────────┐
     │      ki     skd    │
u -> │kp + ─── + ───────  │ -> y
     │      s    1 + sTd  │
     └────────────────────┘
         ______/
         lower

The controller takes an error signal as the input.

Limits lower and upper are on the final output, and aw_lower aw_upper are on the integrator.

The name is suggessted to be specified the same as the instance name.

Parameters:
uBaseVar

The input variable instance

kpBaseParam

The proportional gain parameter instance

kiBaseParam

The integral gain parameter instance

kdBaseParam

The derivative gain parameter instance

TdBaseParam

The derivative time constant parameter instance

define()[source]

Define equations for the PI Controller.

Notes

One state variable xi and one algebraic variable y are added.

Equations implemented are

\[\begin{split}\dot{x_i} &= k_i * (u - ref) \\ y &= x_i + k_p * (u - ref)\end{split}\]
class andes.core.block.PIDTrackAW(u, kp, ki, kd, Td, ks, lower, upper, no_lower=False, no_upper=False, ref=0.0, x0=0.0, name=None, tex_name=None, info=None)[source]

PID with tracking anti-windup limiter

define()[source]

Function for setting the initialization and equation strings for internal variables. This method must be implemented by subclasses.

The equations should be written with the "final" variable names. Let's say the block instance is named blk (kept at self.name of the block), and an internal variable v is defined. The internal variable will be captured as blk_v by the parent model. Therefore, all equations should use {self.name}_v to represent variable v, where {self.name} is the name of the block at run time.

On the other hand, the names of externally provided parameters or variables are obtained by directly accessing the name attribute. For example, if self.T is a parameter provided through the block constructor, {self.T.name} should be used in the equation.

See also

PIController.define

Equations for the PI Controller block

Examples

An internal variable v has a trivial equation T = v, where T is a parameter provided to the block constructor.

In the model, one has

class SomeModel():
    def __init__(...)
        self.input = Algeb()
        self.T = Param()

        self.blk = ExampleBlock(u=self.input, T=self.T)

In the ExampleBlock function, the internal variable is defined in the constructor as

class ExampleBlock():
    def __init__(...):
        self.v = Algeb()
        self.vars = {'v', self.v}

In the define, the equation is provided as

def define(self):
    self.v.v_str = '{self.T.name}'
    self.v.e_str = '{self.T.name} - {self.name}_v'

In the parent model, v from the block will be captured as blk_v, and the equation will evaluate into

self.blk_v.v_str = 'T'
self.blk_v.e_str = 'T - blk_v'

Saturation#

class andes.models.exciter.ExcExpSat(E1, SE1, E2, SE2, name=None, tex_name=None, info=None)[source]

Exponential exciter saturation block to calculate A and B from E1, SE1, E2 and SE2. Input parameters will be corrected and the user will be warned. To disable saturation, set either E1 or E2 to 0.

Parameters:
E1BaseParam

First point of excitation field voltage

SE1: BaseParam

Coefficient corresponding to E1

E2BaseParam

Second point of excitation field voltage

SE2: BaseParam

Coefficient corresponding to E2

define()[source]

Notes

The implementation solves for coefficients A and B which satisfy

\[E_1 S_{E1} = A e^{E1\times B} E_2 S_{E2} = A e^{E2\times B}\]

The solutions are given by

\[E_{1} S_{E1} e^{ \frac{E_1 \log{ \left( \frac{E_2 S_{E2}} {E_1 S_{E1}} \right)} } {E_1 - E_2}} - \frac{\log{\left(\frac{E_2 S_{E2}}{E_1 S_{E1}} \right)}}{E_1 - E_2}\]

Usage Examples#

First-Order Lag#

from andes.core.block import Lag

self.LG = Lag(
    u=self.x,   # Input
    T=self.T1,  # Time constant
    K=1         # Gain (default=1)
)
# Output: LG_y

Washout Filter#

from andes.core.block import Washout

self.WO = Washout(
    u=self.x,
    T=self.Tw,
    K=self.Kw
)
# Output: WO_y

Lead-Lag Compensator#

from andes.core.block import LeadLag

self.LL = LeadLag(
    u=self.x,
    T1=self.T1,  # Lead time constant
    T2=self.T2   # Lag time constant
)
# Output: LL_y

PI Controller with Anti-Windup#

from andes.core.block import PIAWHardLimit

self.PIAW = PIAWHardLimit(
    u=self.error,
    kp=self.Kp,
    ki=self.Ki,
    lower=0,
    upper=1
)
# Output: PIAW_y

Chaining Blocks#

Blocks can be chained by using one block's output as another's input:

# Input → Lag → LeadLag → Output
self.LG = Lag(u=self.input, T=self.T1, K=1)
self.LL = LeadLag(u=self.LG_y, T1=self.T2, T2=self.T3)
self.output = Algeb(e_str='LL_y - output')

Naming Convention#

We loosely follow a naming convention when using modeling blocks. An instance of a modeling block is named with a two-letter acronym, followed by a number or a meaningful but short variable name. The acronym and the name are spelled in one word without underscore, as the output of the block already contains _y.

For example, two washout filters can be named WO1 and WO2. In another case, a first-order lag function for voltage sensing can be called LGv, or even LG if there is only one Lag instance in the model.

Block

Acronym

Example

Lag

LG

LG, LGv, LG1

Washout

WO

WO, WO1, WO2

LeadLag

LL

LL, LL1, LLv

Integrator

IN

IN, INT

PIController

PI

PI, PIv

Naming conventions are not strictly enforced. Expressiveness and concision are encouraged.

See Also#