Services#
Services are helper variables outside the DAE variable list. They are most often used for storing intermediate constants but can be used for special operations to work around restrictions in the symbolic framework.
Background#
Services are v-providers, meaning each service has an attribute v for
storing service values. Unlike variables, services do not participate in the DAE
system directly but provide computed values that variables and equations can use.
The base class of services is andes.core.service.BaseService.
Service Types#
|
Base class for Service. |
|
Base class for a type of Service which performs specific operations. |
|
A type of Service that stays constant once initialized. |
|
Variable service that gets updated in each step/iteration before computing the residual equations. |
|
Service constants whose value is from an external model or group. |
|
Constant service that gets stored once after init. |
|
A helper Service type which reduces a linearly stored 2-D ExtParam into 1-D Service. |
|
A helper Service type which repeats a v-provider's value based on the shape from a BackRef |
|
Helper class to repeat IdxParam. |
|
A special type of reference collector. |
|
A service type for flattening |
|
Service to flag events when the input value changes. |
|
Service for holding the input when the hold signal is on. |
|
Service for indicating an event for an extended, predefined period of time following the event disappearance. |
|
Class for selecting values for optional DataParam or NumParam. |
|
Class for selecting values for optional NumParam. |
|
Service for finding |
|
Class for flagging values based on a condition function. |
|
Class for flagging values that equal to the given value. |
|
Service for flagging parameters > or >= the given value element-wise. |
|
Service for flagging parameters < or <= the given value element-wise. |
|
Class for checking init values against known typical values. |
|
Replace parameters with new values if the function returns True |
|
Class for applying a numerical function on a parameter.. |
Class |
Description |
|---|---|
|
Internal service for constant values |
|
Variable service updated at each iteration before equations |
|
External service for retrieving values from value providers |
|
Constant service evaluated after TDS initialization |
|
Reduce linear 2-D arrays into 1-D arrays |
|
Repeat a 1-D array to linear 2-D arrays |
|
Repeat a 1-D list to linear 2-D list |
|
Flag changes in inputs as an event |
|
Hold input value when a hold signal is active |
|
Extend an event signal for a given period of time |
|
Select optional str data if provided or use the fallback |
|
Select optional numerical data if provided |
|
Find or create devices linked to the given devices |
|
Collect idx-es for backward references |
|
Convert BackRef list of lists into a 1-D list |
|
Check initial values against typical values |
|
Flag values that equal the given value |
|
Replace values that return True for the given lambda func |
Internal Constants#
The most commonly used service is ConstService. It stores an array of constants
whose value is evaluated from a provided symbolic string. Constants are only
evaluated once in the model initialization phase, ahead of variable initialization.
ConstService is handy for calculating intermediate constants from parameters.
For example, a turbine governor has a NumParam R for the droop. ConstService
allows calculating the inverse of the droop (the gain) and using it in equations:
self.R = NumParam()
self.G = ConstService(v_str='u/R')
where u is the online status parameter. The model can then use G in subsequent
variable or equation strings.
- class andes.core.service.ConstService(v_str: str | None = None, v_numeric: Callable | None = None, vtype: type | None = None, name: str | None = None, tex_name: str | None = None, info: str | None = None, unit: str | None = None)[source]
A type of Service that stays constant once initialized.
ConstService are usually constants calculated from parameters. They are only evaluated once in the initialization phase before variables are initialized. Therefore, uninitialized variables must not be used in v_str`.
ConstService are evaluated in sequence after getting external variables and parameters and before initializing internal variables.
- Parameters:
- namestr
Name of the ConstService
- v_strstr
An equation string to calculate the variable value.
- v_numericCallable, optional
A callable which returns the value of the ConstService
- v_type: type, optional, default to float
Type of element in the value array in float or complex
- Attributes:
- varray-like or a scalar
ConstService value
- _v_t0np.ndarray or None
vat t=0, saved bysnapshot_init(). Used byrestore_init()to resetvto the post-init baseline.
VarService#
Updated at each iteration before equation evaluation. Use for intermediate values that depend on the current state of variables.
- class andes.core.service.VarService(v_str: str | None = None, v_numeric: Callable | None = None, vtype: type | None = None, name: str | None = None, tex_name: str | None = None, info: str | None = None, unit: str | None = None, sequential: bool | None = True)[source]
Variable service that gets updated in each step/iteration before computing the residual equations. As a results, variable values from the k-th step are used to compute a
VarServicethat will be used to compute the residual for the (k+1)-th step.This class is useful when one has non-differentiable algebraic equations, which make use of abs(), re and im. Instead of creating Algeb, one can put the equation in VarService, which will be updated before solving algebraic equations.
- Parameters:
- sequentialbool, optional, default to True
True if this VarService depends on previously defined VarService and should be evaluated in sequence. False if this VarService only uses known variables.
Warning
VarServiceis intended for non-differentiable expressions (e.g.,Abs(),re(),im()) that cannot appear in algebraic equations. If the expression is differentiable, anAlgebvariable should be used instead.During model initialization,
VarServiceis evaluated before any variablev_strassignment is computed. If the expression references algebraic or state variables, those variables will still hold their default (typically zero) values, resulting in an incorrect initialVarServicevalue. Variables whosev_strdepends on thisVarServicewill consequently be initialized from an erroneous starting point. At runtime, this effect is self-correcting becauseVarServiceis re-evaluated every iteration with updated variable values. However, the incorrect initial value can cause convergence difficulties or incorrect limiter flag settings during the first few iterations.VarServiceis not solved simultaneously with algebraic equations, meaning that a one-step delay exists between the algebraic variables and theVarServicevalue.Examples
In ESST3A model, the voltage and current sensors (vd + jvq), (Id + jIq) estimate the sensed VE using equation
\[VE = | K_{PC}*(v_d + 1j v_q) + 1j (K_I + K_{PC}*X_L)*(I_d + 1j I_q)|\]One can use VarService to implement this equation
self.VE = VarService( tex_name='V_E', info='VE', v_str='Abs(KPC*(vd + 1j*vq) + 1j*(KI + KPC*XL)*(Id + 1j*Iq))', )
PostInitService#
Evaluated after TDS initialization, when variable values have been determined.
- class andes.core.service.PostInitService(v_str: str | None = None, v_numeric: Callable | None = None, vtype: type | None = None, name: str | None = None, tex_name: str | None = None, info: str | None = None, unit: str | None = None)[source]
Constant service that gets stored once after init.
This service is useful when one need to store initialization values stored in variables.
Examples
In ESST3A model, the vf variable is initialized followed by other variables. One can store the initial vf into vf0 so that equation
vf - vf0 = 0will hold.self.vref0 = PostInitService(info='Initial reference voltage input', tex_name='V_{ref0}', v_str='vref', )
Since all ConstService are evaluated before equation evaluation, without using PostInitService, one will need to create lots of ConstService to store values in the initialization path towards vf0, in order to correctly initialize vf.
External Constants#
Service constants whose value is retrieved from an external model or group.
Using ExtService is similar to using external variables. The values of
ExtService will be retrieved once during the initialization phase before
ConstService evaluation.
For example, a synchronous generator needs to retrieve the p and q values
from static generators for initialization. In the __init__() of a synchronous
generator model, one can define:
self.p0 = ExtService(src='p',
model='StaticGen',
indexer=self.gen,
tex_name='P_0')
- class andes.core.service.ExtService(model: str, src: str, indexer: BaseParam | BaseService, attr: str = 'v', allow_none: bool = False, default=0, name: str = None, tex_name: str = None, vtype=None, info: str = None)[source]
Service constants whose value is from an external model or group.
- Parameters:
- srcstr
Variable or parameter name in the source model or group
- modelstr
A model name or a group name
- indexerIdxParam or BaseParam
An "Indexer" instance whose
vfield contains theidxof devices in the model or group.
Examples
A synchronous generator needs to retrieve the
pandqvalues from static generators for initialization.ExtServiceis used for this purpose.In a synchronous generator, one can define the following to retrieve
StaticGen.pasp0:class GENCLSModel(Model): def __init__(...): ... self.p0 = ExtService(src='p', model='StaticGen', indexer=self.gen, tex_name='P_0')
Shape Manipulators#
This section is for advanced model developers.
All generated equations operate on 1-dimensional arrays and can use algebraic
calculations only. In some cases, one model would use BackRef to retrieve
2-dimensional indices and use such indices to retrieve variable addresses.
The retrieved addresses usually have a different length than the referencing
model and cannot be used directly for calculation.
Shape manipulator services can be used in such cases:
NumReducereduces a linearly stored 2-D ExtParam into 1-D ServiceNumRepeatrepeats a 1-D value into linearly stored 2-D value based on the shape from aBackRef
- class andes.core.service.BackRef(**kwargs)[source]
A special type of reference collector.
BackRef is used for collecting device indices of other models referencing the parent model of the BackRef. The v``field will be a list of lists, each containing the `idx of other models referencing each device of the parent model.
BackRef can be passed as indexer for params and vars, or shape for NumReduce and NumRepeat. See examples for illustration.
See also
andes.core.service.NumReduceA more complete example using BackRef to build the COI model
Examples
A Bus device has an IdxParam of area, storing the idx of area to which the bus device belongs. In
Bus.__init__(), one hasself.area = IdxParam(model='Area')
Suppose Bus has the following data
idx
area
Vn
1
1
110
2
2
220
3
1
345
4
1
500
The Area model wants to collect the indices of Bus devices which points to the corresponding Area device. In
Area.__init__, one definesself.Bus = BackRef()
where the member attribute name Bus needs to match exactly model name that Area wants to collect idx for. Similarly, one can define
self.ACNode = BackRef()to collect devices in the ACNode group that references Area.The collection of idx happens in
andes.system.System._collect_ref_param(). It has to be noted that the specific Area entry must exist to collect model idx-dx referencing it. For example, if Area has the following dataidx 1
Then, only Bus 1, 3, and 4 will be collected into self.Bus.v, namely,
self.Bus.v == [ [1, 3, 4] ].If Area has data
idx 1 2
Then, self.Bus.v will end up with
[ [1, 3, 4], [2] ].
- class andes.core.service.NumReduce(u, ref: BackRef, fun: Callable, name=None, tex_name=None, info=None, cache=True)[source]
A helper Service type which reduces a linearly stored 2-D ExtParam into 1-D Service.
NumReduce works with ExtParam whose v field is a list of lists. A reduce function which takes an array-like and returns a scalar need to be supplied. NumReduce calls the reduce function on each of the lists and return all the scalars in an array.
- Parameters:
- uExtParam
Input ExtParam whose
vcontains linearly stored 2-dimensional values- refBackRef
The BackRef whose 2-dimensional shapes are used for indexing
- funCallable
The callable for converting a 1-D array-like to a scalar
Examples
Suppose one wants to calculate the mean value of the
Vnin one Area. In theAreaclass, one definesclass AreaModel(...): def __init__(...): ... # backward reference from `Bus` self.Bus = BackRef() # collect the Vn in an 1-D array self.Vn = ExtParam(model='Bus', src='Vn', indexer=self.Bus) self.Vn_mean = NumReduce(u=self.Vn, fun=np.mean, ref=self.Bus)
Suppose we define two areas, 1 and 2, the Bus data looks like
idx
area
Vn
1
1
110
2
2
220
3
1
345
4
1
500
Then, self.Bus.v is a list of two lists
[ [1, 3, 4], [2] ]. self.Vn.v will be retrieved and linearly stored as[110, 345, 500, 220]. Based on the shape from self.Bus,numpy.mean()will be called on[110, 345, 500]and[220]respectively. Thus, self.Vn_mean.v will become[318.33, 220].
- class andes.core.service.NumRepeat(u, ref, **kwargs)[source]
A helper Service type which repeats a v-provider's value based on the shape from a BackRef
Examples
NumRepeat was originally designed for computing the inertia-weighted average rotor speed (center of inertia speed). COI speed is computed with
\[\omega_{COI} = \frac{ \sum{M_i * \omega_i} } {\sum{M_i}}\]The numerator can be calculated with a mix of BackRef, ExtParam and ExtState. The denominator needs to be calculated with NumReduce and Service Repeat. That is, use NumReduce to calculate the sum, and use NumRepeat to repeat the summed value for each device.
In the COI class, one would have
class COIModel(...): def __init__(...): ... self.SynGen = BackRef() self.SynGenIdx = RefFlatten(ref=self.SynGen) self.M = ExtParam(model='SynGen', src='M', indexer=self.SynGenIdx) self.wgen = ExtState(model='SynGen', src='omega', indexer=self.SynGenIdx) self.Mt = NumReduce(u=self.M, fun=np.sum, ref=self.SynGen) self.Mtr = NumRepeat(u=self.Mt, ref=self.SynGen) self.pidx = IdxRepeat(u=self.idx,ref=self.SynGen)
Finally, one would define the center of inertia speed as
self.wcoi = Algeb(v_str='1', e_str='-wcoi') self.wcoi_sub = ExtAlgeb(model='COI', src='wcoi', e_str='M * wgen / Mtr', v_str='M / Mtr', indexer=self.pidx, )
It is very worth noting that the implementation uses a trick to separate the average weighted sum into n sub-equations, each calculating the \((M_i * \omega_i) / (\sum{M_i})\). Since all the variables are preserved in the sub-equation, the derivatives can be calculated correctly.
- class andes.core.service.IdxRepeat(u, ref, **kwargs)[source]
Helper class to repeat IdxParam.
This class has the same functionality as
andes.core.service.NumRepeatbut only operates on IdxParam, DataParam or NumParam.
- class andes.core.service.RefFlatten(ref, **kwargs)[source]
A service type for flattening
andes.core.service.BackRefinto a 1-D list.Examples
This class is used when one wants to pass BackRef values as indexer.
andes.models.coi.COIcollects referencingandes.models.group.SynGenwithself.SynGen = BackRef(info='SynGen idx lists', export=False)
After collecting BackRefs, self.SynGen.v will become a two-level list of indices, where the first level correspond to each COI and the second level correspond to generators of the COI.
Convert self.SynGen into 1-d as self.SynGenIdx, which can be passed as indexer for retrieving other parameters and variables
self.SynGenIdx = RefFlatten(ref=self.SynGen) self.M = ExtParam(model='SynGen', src='M', indexer=self.SynGenIdx, export=False, )
Value Manipulation#
- class andes.core.service.Replace(old_val, flt, new_val, name=None, tex_name=None, info=None, cache=True)[source]
Replace parameters with new values if the function returns True
- class andes.core.service.ParamCalc(param1, param2, func, name=None, tex_name=None, info=None, cache=True)[source]
Parameter calculation service.
Useful to create parameters calculated instantly from existing ones.
- class andes.core.service.ApplyFunc(u, func, name=None, tex_name=None, info=None, cache=True)[source]
Class for applying a numerical function on a parameter..
- Parameters:
- u
Input parameter
- func
A condition function that returns True or False.
Warning
This class is not ready.
Idx and References#
- class andes.core.service.DeviceFinder(u, link, idx_name: str, default_model: str, auto_find: bool | None = True, auto_add: bool | None = True, name: str | None = None, tex_name: str | None = None, info: str | None = None)[source]
Service for finding
idxof devices which are linked to the given devices.The
auto_findparameter controls if the device idx should be automatically looked up. Theauto_addparameter controls if the device will be automatically added. The two parameters are not exclusive. One can skip finding the device but automatically adding it.If
auto_findisTrueand theidxis None, DeviceFinder will look up for the device. If not found andauto_addisTrue, DevFinder will then automatically add the devices. Theidxof the devices that are found or added will be stored to the DeviceFinder instance, so that DeviceFinder can be used like any IdxParam.Adding new devices are called at the beginning of
andes.system.System.setup().Examples
The IEEEST stabilizer takes an optional parameter
busfof the type IdxParam for specifying the connected bus frequency measurement device, which is needed for mode 6. To avoid reimplementing BusFreq within IEEEST, one can doself.busfreq = DeviceFinder(self.busf, link=self.buss, idx_name='bus', default_model='BusFreq')
where
self.busfis for the optional parameter for theidxof bus frequency estimation devices (e.g., BusFreq),self.bussis for theidxof buses thatself.busfdevices should measure, andidx_nameis the name of the BusFreq parameter through which the indices of measured buses are given.For each
Noneor invalid values inself.busf, a BusFreq device will be created with itsbusset to the corresponding value inself.buss. That is,BusFreq.[idx_name].v = [link].At the end, the DeviceFinder instance will contain the list of
BusFreqthat are are connected to self.buss, respectively.In the case of any valid value in
self.busf, that is, the value is an existingBusFreqdevice, DeviceFinder will return it as is without checking if theBusFreqdevice actually measures the bus specified byself.buss. It allows to use the measurement at a different location, but the user have to perform the data consistency check.
Events#
- class andes.core.service.EventFlag(u, vtype: type | None = None, name: str | None = None, tex_name=None, info=None)[source]
Service to flag events when the input value changes. The typical input is a v-provider with binary values.
Implemented by providing self.check(**kwargs) as v_numeric. EventFlag.v stores the values of the input variable in the most recent iteration/step.
After the evaluation of self.check(), self.v will be updated.
- class andes.core.service.ExtendedEvent(u, t_ext: int | float | BaseParam | BaseService = 0.0, trig: str = 'rise', enable=True, v_disabled=0, extend_only=False, vtype: type | None = None, name: str | None = None, tex_name=None, info=None)[source]
Service for indicating an event for an extended, predefined period of time following the event disappearance.
The triggering of an event, whether the rise or fall edge, is specified through trig. For example, if trig = rise, the change of the input from 0 to 1 will be considered as an input, whereas the subsequent change back to 0 will be considered as the event end.
ExtendedEvent.v stores the flags whether the extended time has completed. Outputs will become 1 once the event starts and return to 0 when the extended time ends.
- Parameters:
- uv-provider
Triggering signal where the values are 0 or 1.
- trigstr in ("rise", "fall")
Triggering edge for the beginning of an event. rise by default.
- enablebool or v-provider
If disabled, the output will be v_disabled
- extend_onlybool
Only output during the extended period, not the event period.
Warning
The performance of this class needs to be optimized.
- class andes.core.service.VarHold(u, hold, vtype=None, name=None, tex_name=None, info=None)[source]
Service for holding the input when the hold signal is on.
- Parameters:
- holdv-provider, binary
Hold signal array with length equal to the input. For elements that are 1, the corresponding inputs are held until the hold signal returns to 0.
Flags#
- class andes.core.service.FlagCondition(u, func, flag=1, name=None, tex_name=None, info=None, cache=True)[source]
Class for flagging values based on a condition function.
By default, values whose condition function output equal that equal to True/1 will be flagged as 1. 0 otherwise.
- Parameters:
- u
Input parameter
- func
A condition function that returns True or False.
- flag1 by default, only 0 or 1 is accepted.
The flag for the inputs whose condition output is True.
Warning
This class is not ready.
FlagCondition can only be applied to BaseParam with cache=True. Applying to Service will fail unless cache is False (at a performance cost).
- class andes.core.service.FlagGreaterThan(u, value=0.0, flag=1, equal=False, name=None, tex_name=None, info=None, cache=True)[source]
Service for flagging parameters > or >= the given value element-wise.
Parameters that satisfy the comparison (u > or >= value) will flagged as flag (1 by default).
- class andes.core.service.FlagLessThan(u, value=0.0, flag=1, equal=False, name=None, tex_name=None, info=None, cache=True)[source]
Service for flagging parameters < or <= the given value element-wise.
Parameters that satisfy the comparison (u < or <= value) will flagged as flag (1 by default).
- class andes.core.service.FlagValue(u, value, flag=0, name=None, tex_name=None, info=None, cache=True)[source]
Class for flagging values that equal to the given value.
By default, values that equal to value will be flagged as 0. Non-matching values will be flagged as 1.
- Parameters:
- u
Input parameter
- value
Value to flag. Can be None, string, or a number.
- flag0 by default, only 0 or 1 is accepted.
The flag for the matched ones
Warning
FlagNotNone can only be applied to BaseParam with cache=True. Applying to Service will fail unless cache is False (at a performance cost).
Data Select#
- class andes.core.service.DataSelect(optional, fallback, name: str | None = None, tex_name: str | None = None, info: str | None = None)[source]
Class for selecting values for optional DataParam or NumParam.
This service is a v-provider that uses optional DataParam when available. Otherwise, use the fallback value.
DataParam will be tested for None, and NumParam will be tested with
np.isnan().Notes
An use case of DataSelect is remote bus. One can do
self.buss = DataSelect(option=self.busr, fallback=self.bus)
Then, pass
self.bussinstead ofself.busas indexer to retrieve voltages.Another use case is to allow an optional turbine rating. One can do
self.Tn = NumParam(default=None) self.Sg = ExtParam(...) self.Sn = DataSelect(Tn, Sg)
- class andes.core.service.NumSelect(optional, fallback, name: str | None = None, tex_name: str | None = None, info: str | None = None, ignore_cond: ~typing.Callable | None = functools.partial(<ufunc 'equal'>, 0))[source]
Class for selecting values for optional NumParam.
NumSelect works with internal and external parameters.
Any values equal to
np.nanwill always be ignored. If one needs to ignore values based on additional conditions, pass it throughignore_cond. For example, to ignore zero values, useignore_cond = partial(np.equal, 0).Examples
One use case is to allow an optional turbine rating. One can do
self.Tn = NumParam(default=None) self.Sg = ExtParam(...) self.Sn = DataSelect(Tn, Sg)
Miscellaneous#
- class andes.core.service.InitChecker(u, lower=None, upper=None, equal=None, not_equal=None, enable=True, error_out=False, **kwargs)[source]
Class for checking init values against known typical values.
Instances will be stored in Model.services_post and Model.services_icheck, which will be checked in Model.post_init_check() after initialization.
- Parameters:
- u
v-provider to be checked
- lowerfloat, BaseParam, BaseVar, BaseService
lower bound
- upperfloat, BaseParam, BaseVar, BaseService
upper bound
- equalfloat, BaseParam, BaseVar, BaseService
values that the value from v_str should equal
- not_equalfloat, BaseParam, BaseVar, BaseService
values that should not equal
- enablebool
True to enable checking
Examples
Let's say generator excitation voltages are known to be in the range of 1.6 - 3.0 per unit. One can add the following instance to GENBase
self._vfc = InitChecker(u=self.vf, info='vf range', lower=1.8, upper=3.0, )
lower and upper can also take v-providers instead of float values.
One can also pass float values from Config to make it adjustable as in our implementation of
GENBase._vfc.
- class andes.core.service.CurrentSign(bus, bus1, bus2, name=None, tex_name=None, info=None)[source]
Service for computing the sign of the current flowing through a series device.
With a given line connecting bus1 and bus2, one can compute the current flow using
(v1*exp(1j*a1) - v2*exp(1j*a2)) / (r + jx)whose value is the outflow on bus1.CurrentSign can be used to compute the sign to be multiplied depending on the observing bus. For each value in bus, the sign will be
+1if it appears in bus1 or-1otherwise.bus1 bus2 *------>>-----* bus(+) bus(-)
- class andes.core.service.RandomService(func=<bound method RandomState.rand of RandomState(MT19937)>, **kwargs)[source]
A service type for generating random numbers.
- Parameters:
- namestr
Name
- funcCallable
A callable for generating the random variable.
Warning
The value will be randomized every time it is accessed. Do not use it if the value needs to be stable for each simulation step.
- class andes.core.service.SwBlock(*, init, ns, blocks, ext_sel=None, name=None, tex_name=None, info=None)[source]
Service type for switched shunt blocks.
Common Patterns#
Per-Unit Conversion#
self.Zbase = ConstService(v_str='Vn**2 / Sn')
self.R_pu = ConstService(v_str='R_ohm / Zbase')
Inverse Calculation#
self.R = NumParam()
self.gain = ConstService(v_str='u / R')
Conditional Value#
self.K = ConstService(v_str='K1 * (mode == 1) + K2 * (mode == 2)')
See Also#
Atomic Types - v-provider concept
Parameters - Input parameters
Variables - DAE variables
Discrete Components - Discrete components