andes.core.model.Model#

class andes.core.model.Model(system=None, config=None)[source]#

Base class for power system DAE models.

After subclassing ModelData, subclass Model to complete a DAE model. Subclasses of Model define DAE variables, services, and other types of parameters, in the constructor __init__.

Notes

To modify parameters or services use set(), which writes directly to the given attribute, or alter(), which converts parameters to system base like that for input data.

Examples

Take the static PQ as an example, the subclass of Model, PQ, should look like

class PQ(PQData, Model):
def __init__(self, system, config):
PQData.__init__(self)
Model.__init__(self, system, config)


Since PQ is calling the base class constructors, it is meant to be the final class and not further derived. It inherits from PQData and Model and must call constructors in the order of PQData and Model. If the derived class of Model needs to be further derived, it should only derive from Model and use a name ending with Base. See andes.models.synchronous.GENBASE.

Next, in PQ.__init__, set proper flags to indicate the routines in which the model will be used

self.flags.update({'pflow': True})


Currently, flags pflow and tds are supported. Both are False by default, meaning the model is neither used in power flow nor time-domain simulation. A very common pitfall is forgetting to set the flag.

Next, the group name can be provided. A group is a collection of models with common parameters and variables. Devices' idx of all models in the same group must be unique. To provide a group name, use

self.group = 'StaticLoad'


The group name must be an existing class name in andes.models.group. The model will be added to the specified group and subject to the variable and parameter policy of the group. If not provided with a group class name, the model will be placed in the Undefined group.

Next, additional configuration flags can be added. Configuration flags for models are load-time variables, specifying the behavior of a model. They can be exported to an andes.rc file and automatically loaded when creating the System. Configuration flags can be used in equation strings, as long as they are numerical values. To add config flags, use

self.config.add(OrderedDict((('pq2z', 1), )))


It is recommended to use OrderedDict instead of dict, although the syntax is verbose. Note that booleans should be provided as integers (1 or 0), since True or False is interpreted as a string when loaded from the rc file and will cause an error.

Next, it's time for variables and equations! The PQ class does not have internal variables itself. It uses its bus parameter to fetch the corresponding a and v variables of buses. Equation wise, it imposes an active power and a reactive power load equation.

To define external variables from Bus, use

self.a = ExtAlgeb(model='Bus', src='a',
indexer=self.bus, tex_name=r'\theta')
self.v = ExtAlgeb(model='Bus', src='v',
indexer=self.bus, tex_name=r'V')


Refer to the subsection Variables for more details.

The simplest PQ model will impose constant P and Q, coded as

self.a.e_str = "u * p"
self.v.e_str = "u * q"


where the e_str attribute is the equation string attribute. u is the connectivity status. Any parameter, config, service or variable can be used in equation strings.

Three additional scalars can be used in equations: - dae_t for the current simulation time (can be used if the model has flag tds). - sys_f for system frequency (from system.config.freq). - sys_mva for system base mva (from system.config.mva).

The above example is overly simplified. Our PQ model wants a feature to switch itself to a constant impedance if the voltage is out of the range (vmin, vmax). To implement this, we need to introduce a discrete component called Limiter, which yields three arrays of binary flags, zi, zl, and zu indicating in-range, below lower-limit, and above upper-limit, respectively.

First, create an attribute vcmp as a Limiter instance

self.vcmp = Limiter(u=self.v, lower=self.vmin, upper=self.vmax,
enable=self.config.pq2z)


where self.config.pq2z is a flag to turn this feature on or off. After this line, we can use vcmp_zi, vcmp_zl, and vcmp_zu in other equation strings.

self.a.e_str = "u * (p0 * vcmp_zi + " \
"p0 * vcmp_zl * (v ** 2 / vmin ** 2) + " \
"p0 * vcmp_zu * (v ** 2 / vmax ** 2))"

self.v.e_str = "u * (q0 * vcmp_zi + " \
"q0 * vcmp_zl * (v ** 2 / vmin ** 2) + "\
"q0 * vcmp_zu * (v ** 2 / vmax ** 2))"


Note that PQ.a.e_str can use the three variables from vcmp even before defining PQ.vcmp, as long as PQ.vcmp is defined, because vcmp_zi is just a string literal in e_str.

The two equations above implement a piece-wise power injection equation. It selects the original power demand if within range, and uses the calculated power when out of range.

Finally, to let ANDES pick up the model, the model name needs to be added to models/__init__.py. Follow the examples in the OrderedDict, where the key is the file name, and the value is the class name.

Attributes
num_paramsOrderedDict

{name: instance} of numerical parameters, including internal and external ones

__init__(system=None, config=None)[source]#

Methods

 Reset addresses to empty and reset flags.address to False. alter(src, idx, value) Alter input parameter or service values. doc([max_width, export]) Retrieve model documentation as a string. Clear equation value arrays associated with all internal variables. Externalize internal data as a snapshot. f_numeric(**kwargs) Custom fcall functions. Evaluate differential equations. g_numeric(**kwargs) Custom gcall functions. Evaluate algebraic equations. get(src, idx[, attr, allow_none, default]) Get the value of an attribute of a model property. Get variable initialization order and send to logger.info. get_inputs([refresh]) Get an OrderedDict of the inputs to the numerical function calls. Return the md5 hash of concatenated equation strings. Get event switch_times from TimerParam. idx2uid(idx) Convert idx to the 0-indexed unique index. init(routine) Numerical initialization of a model. Internalize snapshot data. j_numeric(**kwargs) Custom numeric update functions. Update Jacobian elements. l_check_eq([init]) Call the check_eq method of discrete components to update equation-dependent flags. l_update_var(dae_t, *args[, niter, err]) Call the check_var method of discrete components to update the internal status flags. Convert all the value attributes v to NumPy arrays. Use mock data to fill the inputs. numba_jitify([parallel, cache, nopython]) Convert equation residual calls, Jacobian calls, and variable service calls into JIT compiled functions. Post init checking. Trigger numba compilation for this model. prepare([quick, pycode_path, yapf_pycode]) Symbolic processing and code generation. This is the helper function to refresh inputs. Refresh inputs for each function with individual argument list. register_debug_equation(var_name) Helper function to register a variable for debugging the initialization. s_numeric(**kwargs) Custom service value functions. s_numeric_var(**kwargs) Custom variable service value functions. Update service equation values. Update post-initialization services. Update values of andes.core.service.VarService. set(src, idx, attr, value) Set the value of an attribute of a model property. set_backref(name, from_idx, to_idx) Helper function for setting idx-es to BackRef. Set the in_use attribute. solve_iter(name, kwargs) Solve iterative initialization. solve_iter_single(name, inputs, pos) Solve iterative initialization for one given device. Store rows and columns of the non-zeros in the Jacobians for building the sparsity pattern. switch_action(dae_t) Call the switch actions. v_numeric(**kwargs) Custom variable initialization function.

Attributes

 class_name` Return the class name