Variables#
DAE Variables, or variables for short, are unknowns to be solved using numerical or analytical methods. A variable stores values, equation values, and addresses in the DAE array.
Background#
Variables are both v-providers and e-providers (see Atomic Types).
Each variable has member attributes v and e for variable values and equation
values, respectively. The initial value of v is set by the initialization routine,
and the initial value of e is set to zero.
During power flow calculation or time domain simulation:
vis not directly modifiable by models but rather updated after solving non-linear equationseis updated by the models and summed up before solving equations
Each variable also stores addresses in its member attribute a. The addresses are
0-based indices into the numerical DAE array (f or g, based on variable type).
Equations associated with state variables take the form:
where \(\mathbf{x}\) are the differential variables, \(\mathbf{y}\) are the algebraic variables, \(\mathbf{M}\) is the mass matrix, and \(\mathbf{f}\) is the right-hand side of differential equations.
Equations associated with algebraic variables take the form:
where \(\mathbf{g}\) is the equation right-hand side.
Variable Types#
|
Base variable class. |
|
Differential variable class, an alias of the BaseVar. |
|
Algebraic variable class, an alias of |
|
Algebraic variable that links to an external model. |
|
External state variable type. |
|
External algebraic variable type. |
|
Alias state variable. |
|
Alias algebraic variable. |
|
A variable computed by explicit assignment rather than solved in the DAE system. |
State#
State variables are described by differential equations and can only change
continuously. Use State for dynamics that evolve over time.
from andes.core.var import State
class Generator(Model):
def __init__(self):
# Rotor angle
self.delta = State(
v_str='delta0', # Initial value
e_str='omega - 1', # d(delta)/dt = omega - 1
info='Rotor angle',
tex_name=r'\delta'
)
# Rotor speed
self.omega = State(
v_str='1.0',
e_str='(Tm - Te - D*(omega-1)) / M',
info='Rotor speed',
tex_name=r'\omega'
)
- class andes.core.var.State(name: str | None = None, tex_name: str | None = None, info: str | None = None, unit: str | None = None, v_str: str | float | None = None, v_iter: str | None = None, e_str: str | None = None, discrete: Discrete | None = None, t_const: BaseParam | DummyValue | BaseService | None = None, check_init: bool | None = True, v_setter: bool | None = False, e_setter: bool | None = False, addressable: bool | None = True, export: bool | None = True, diag_eps: float | None = 0.0, deps: List | None = None)[source]
Differential variable class, an alias of the BaseVar.
- Parameters:
- t_constBaseParam, DummyValue
Left-hand time constant for the differential equation. They will be collected to array
dae.Tf. Time constants will not be used when evaluating the right-hand side specified ine_strbut will be applied to the left-hand side.- check_initbool
True to check if the equation right-hand-side is zero initially. Disabling the checking can be used for integrators when the initial input may not be zero.
- Attributes:
- e_codestr
Equation code string, equals string literal
f- v_codestr
Variable code string, equals string literal
x
Examples
To implement the swing equation
\[M \dot {\omega} = \tau_m - \tau_e - D(\omega - 1)\]Do the following in the
__init__()of a model class:self.omega = State(e_str = 'tm - te - D * (omega - 1)', t_const = self.M, ... )
Note that
self.M, the inertia parameter is given throught_constand is not part ofe_str.
Algeb#
Algebraic variables satisfy instantaneous constraints and can be discontinuous.
Use Algeb for power balance equations, output calculations, and constraints.
from andes.core.var import Algeb
class Generator(Model):
def __init__(self):
# Electrical power output
self.Pe = Algeb(
v_str='p0',
e_str='vd*Id + vq*Iq - Pe',
info='Electrical power',
tex_name='P_e'
)
- class andes.core.var.Algeb(name: str | None = None, tex_name: str | None = None, info: str | None = None, unit: str | None = None, v_str: str | float | None = None, v_iter: str | None = None, e_str: str | None = None, discrete: Discrete | None = None, v_setter: bool | None = False, e_setter: bool | None = False, v_str_add: bool | None = False, addressable: bool | None = True, export: bool | None = True, diag_eps: float | None = 0.0, deps: List | None = None, is_output: bool | None = False)[source]
Algebraic variable class, an alias of
andes.core.var.BaseVar.Note that residual equations corresponding to algebraic variables are given in an implicit form.
- Attributes:
- e_codestr
Equation code string, equals string literal
g- v_codestr
Variable code string, equals string literal
y
Examples
When an algebraic variable
yand the equationy = x + zshall be defined, usee_str = 'x + z - y'
because it expresses the equation
x + z - y = 0. It is a common mistake to usee_str = 'x + z', which will result in a singular Jacobian matrix becaused(x + z) / d(y)is zero.
External Variables#
Some models have "public" variables that can be accessed by other models. For
example, a Bus defines v for voltage magnitude. Each device attached to a
particular bus needs to access the value and impose reactive power injection.
External variables link with existing variables from another model or group using
ExtAlgeb or ExtState.
ExtAlgeb#
from andes.core.var import ExtAlgeb
class PQ(Model):
def __init__(self):
# Access bus voltage
self.v = ExtAlgeb(
src='v', # Source variable name
model='Bus', # Source model
indexer=self.bus, # IdxParam for lookup
e_str='-q', # Inject reactive power
)
- class andes.core.var.ExtAlgeb(model: str, src: str, indexer: List | ndarray | BaseParam | BaseService | None = None, allow_none: bool | None = False, name: str | None = None, tex_name: str | None = None, ename: str | None = None, tex_ename: str | None = None, info: str | None = None, unit: str | None = None, v_str: str | float | None = None, v_iter: str | None = None, e_str: str | None = None, v_setter: bool | None = False, e_setter: bool | None = False, addressable: bool | None = True, export: bool | None = True, diag_eps: float | None = 0.0, is_input: bool | None = False)[source]
External algebraic variable type.
ExtState#
from andes.core.var import ExtState
class Exciter(Model):
def __init__(self):
# Access generator field voltage
self.vf = ExtState(
src='vf',
model='GENROU',
indexer=self.syn,
e_str='Efd - vf0',
)
- class andes.core.var.ExtState(model: str, src: str, indexer: List | ndarray | BaseParam | BaseService | None = None, allow_none: bool | None = False, name: str | None = None, tex_name: str | None = None, ename: str | None = None, tex_ename: str | None = None, info: str | None = None, unit: str | None = None, v_str: str | float | None = None, v_iter: str | None = None, e_str: str | None = None, v_setter: bool | None = False, e_setter: bool | None = False, addressable: bool | None = True, export: bool | None = True, diag_eps: float | None = 0.0, is_input: bool | None = False)[source]
External state variable type.
Warning
ExtStateis not allowed to sett_const, as it may conflict with the sourceStatevariable.Only in rare cases should one set
e_strforExtState. Thet_constof the source State variable is used.
Value and Equation Strings#
The most important feature of the symbolic framework is allowing equations to be defined using strings. There are three types of strings for a variable:
v_str: Explicit Initialization#
Equation string for explicit initialization in the form of v = v_str(x, y).
The expression evaluates directly into the initial value.
self.omega = State(v_str='1.0') # Start at 1.0 pu
self.Pe = Algeb(v_str='p0') # Start at initial power
v_iter: Implicit Initialization#
Equation string for implicit initialization in the form of v_iter(x, y) = 0.
All v_iter equations are solved numerically using the Newton-Krylov iterative method.
self.Efd = Algeb(v_iter='Vf - Efd') # Solve: Vf - Efd = 0
e_str: Equation Definition#
Equation string for the differential or algebraic equation:
For
State: right-hand side of \(\dot{x} = f(x, y)\)For
Algeb: residual that should equal zero, \(g(x, y) = 0\)
# Differential: d(omega)/dt = (Tm - Te) / M
self.omega = State(e_str='(Tm - Te) / M')
# Algebraic: 0 = P_gen - P_load
self.P = Algeb(e_str='Pgen - Pload')
Device Status: u vs ue#
Every model has a u parameter (own online status, from input data) and a ue
service (effective status, accounting for parent devices). Use ue in
e_str to ensure equations are zeroed out when the device or any of its
ancestors (bus, generator, exciter, etc.) is offline:
# Correct: equation respects parent status
self.omega = State(e_str='ue * (Tm - Te - D*(omega-1)) / M')
# Wrong: ignores bus/parent offline status
self.omega = State(e_str='u * (Tm - Te - D*(omega-1)) / M')
ue is computed as the product of the device's own u and the effective
status of all parents declared with status_parent=True (see
Parameters). For example, a governor's ue is
u * generator.ue * bus.ue.
Use u in v_str for initialization, since v_str is evaluated before
status propagation runs.
Flags for Value Overwriting#
Variables have special flags for handling value initialization and equation values. This is only relevant for public or external variables.
v_setter Flag#
The v_setter flag indicates whether a particular variable instance sets the initial
value. If v_setter=False (default), variable values of the same address are added.
If v_setter=True, the variable will set the values in the DAE array to its value,
overwriting any previous values.
Only one variable at the same address is allowed to have v_setter=True.
e_setter Flag#
The e_setter flag indicates whether the equation associated with a variable sets
the equation value rather than adding to it.
A v_setter Example#
A Bus is allowed to default the initial voltage magnitude to 1 and the voltage phase angle to 0. If a PV device is connected to a Bus device, the PV should override the voltage initial value with its voltage set point.
In Bus.__init__():
self.v = Algeb(v_str='1')
In PV.__init__():
self.v0 = Param()
self.bus = IdxParam(model='Bus')
self.v = ExtAlgeb(src='v',
model='Bus',
indexer=self.bus,
v_str='v0',
v_setter=True)
An ExtAlgeb is defined to access Bus.v using indexer self.bus. The v_str
sets the initial value to v0. During the variable initialization phase for PV,
PV.v.v is set to v0.
During value collection into DAE.y by the System class, PV.v, as a final
v_setter, will overwrite the voltage magnitude for Bus devices with the indices
provided in PV.bus.
Observable#
Observable variables are computed by explicit assignment rather than solved in the
DAE system. At code generation time, the expression is substituted into all
referencing equations so that Jacobians are computed correctly. Post-solve, values
are evaluated and stored in dae.b for recording.
Use Observable instead of Algeb when a variable is a pure explicit function
of other variables and does not need to participate in the Newton-Raphson solve.
This reduces the DAE system size without sacrificing Jacobian accuracy.
from andes.core.observable import Observable
class MyModel(Model):
def __init__(self):
self.vd = Observable(
e_str='v * cos(delta - a)',
info='d-axis voltage',
tex_name='V_d',
)
Note
Observable variables cannot be referenced via ExtAlgeb. If another model needs
to link to the variable externally, keep it as Algeb.
- class andes.core.observable.Observable(e_str: str | None = None, name: str | None = None, tex_name: str | None = None, info: str | None = None, unit: str | None = None, discrete=None)[source]
A variable computed by explicit assignment rather than solved in the DAE system.
At code generation time, the Observable's expression is substituted into all referencing equations so that Jacobians are correctly computed via direct differentiation. Post-solve, values are evaluated and stored in
dae.bfor recording.- Parameters:
- e_strstr, optional
Assignment expression string. Unlike
Algebwheree_strmeans residual (0 = e_str), for Observable it means direct assignment (b = e_str).
- Attributes:
- e_codeNone
Observable has no equation residual.
- v_codestr
Variable code string, equals string literal
b.
Examples
To define an observable
vdcomputed from bus voltage and angle:self.vd = Observable(e_str='v * cos(delta - a)', info='d-axis voltage', tex_name='V_d')
The symbol
vdcan then be used in other equations. At code generation time,vdwill be replaced byv * cos(delta - a)in those equations.- reset()[source]
Reset the internal numpy arrays and flags.
- set_address(addr: ndarray, contiguous=False)[source]
Set the address of this Observable in
dae.b.Overrides BaseVar to ensure
e_inplaceis always False (no equation array for Observable).
- set_arrays(dae, inplace=True, alloc=True)[source]
Set the value array for this Observable.
Only sets
self.v(intodae.b). No equation array is allocated since Observable has no residual.
Alias Variables#
Create references to existing variables for convenience:
from andes.core.var import AliasState, AliasAlgeb
# Reference generator speed
self.omega = AliasState(self.GENROU.omega)
- class andes.core.var.AliasState(var, **kwargs)[source]
Alias state variable.
Refer to the docs of
AliasAlgeb.
- class andes.core.var.AliasAlgeb(var, **kwargs)[source]
Alias algebraic variable. Essentially
ExtAlgebthat links to a a model's own variable.AliasAlgebis useful when the final output of a model is from a block, but the model must provide the final output in a pre-defined name. UsingAliasAlgeb, A model can avoid adding an additional variable with a dummy equations.Like
ExtVar, labels ofAliasAlgebwill not be saved in the final output. When plotting from file, one need to look up the original variable name.
BaseVar#
The abstract base class defining the variable interface.
- class andes.core.var.BaseVar(name: str | None = None, tex_name: str | None = None, info: str | None = None, unit: str | None = None, v_str: str | float | None = None, v_iter: str | None = None, e_str: str | None = None, discrete: Discrete | None = None, v_setter: bool | None = False, e_setter: bool | None = False, v_str_add: bool | None = False, addressable: bool | None = True, export: bool | None = True, diag_eps: float | None = 0.0, deps: List | None = None, is_output: bool | None = False)[source]
Base variable class.
Derived classes State and Algeb should be used to build model variables.
- Parameters:
- infostr, optional
Descriptive information
- unitstr, optional
Unit
- tex_namestr
LaTeX-formatted variable symbol. If is None, the value of name will be used.
- discreteDiscrete
Discrete component on which this variable depends. ANDES will call check_var() of the discrete component before initializing this variable.
- namestr, optional
Variable name. One should typically assigning the name directly because it will be automatically assigned by the model. The value of
namewill be the symbol name to be used in expressions.
- Attributes:
- aarray-like
variable address
- varray-like
local-storage of the variable value
- earray-like
local-storage of the corresponding equation value
- e_strstr
the string/symbolic representation of the equation
- v_strstr
explicit initialization equation
- v_str_addbool
True if the value of v_str will be added to the variable. Useful when other models access this variable and set part of the initial value
- v_iterstr
implicit iterative equation in the form of 0 = v_iter
- reset()[source]
Reset the internal numpy arrays and flags.
- set_address(addr: ndarray, contiguous=False)[source]
Set the address of internal variables.
- Parameters:
- addrnp.ndarray
The assigned address for this variable
- contiguousbool, optional
If the addresses are contiguous
- set_arrays(dae, inplace=True, alloc=True)[source]
Set the equation and values arrays.
- Parameters:
- daeDAE
Reference to System.dae
Common Patterns#
Power Balance#
# Algebraic equation: 0 = P_gen - P_load - P_line
self.P = Algeb(e_str='Pgen - Pload - Pline')
Swing Equation#
self.delta = State(e_str='omega - 1')
self.omega = State(e_str='(Tm - Te - D*(omega-1)) / M')
External Power Injection#
# Inject power to bus (negative = generation/injection)
self.a = ExtAlgeb(src='a', model='Bus', indexer=self.bus,
e_str='-p')
self.v = ExtAlgeb(src='v', model='Bus', indexer=self.bus,
e_str='-q')
See Also#
Atomic Types - v-provider and e-provider concepts
DAE Formulation - DAE mathematical background
Parameters - Parameter types
Services - Intermediate calculations