# System#

## Overview#

System is the top-level class for organizing power system models and orchestrating calculations. The full API reference of System is found at andes.system.System.

### Dynamic Imports#

System dynamically imports groups, models, and routines at creation. To add new models, groups or routines, edit the corresponding file by adding entries following examples.

andes.system.System.import_models(self)

Import and instantiate models as System member attributes.

Models defined in models/__init__.py will be instantiated sequentially as attributes with the same name as the class name. In addition, all models will be stored in dictionary System.models with model names as keys and the corresponding instances as values.

Examples

system.Bus stores the Bus object, and system.GENCLS stores the classical generator object,

system.models['Bus'] points the same instance as system.Bus.

andes.system.System.import_groups(self)

Import all groups classes defined in devices/group.py.

Groups will be stored as instances with the name as class names. All groups will be stored to dictionary System.groups.

andes.system.System.import_routines(self)

Import routines as defined in routines/__init__.py.

Routines will be stored as instances with the name as class names. All groups will be stored to dictionary System.groups.

Examples

System.PFlow is the power flow routine instance, and System.TDS and System.EIG are time-domain analysis and eigenvalue analysis routines, respectively.

### Code Generation#

Under the hood, all symbolically defined equations need to be generated into anonymous function calls for accelerating numerical simulations. This process is automatically invoked for the first time ANDES is run command line. It takes several seconds up to a minute to finish the generation.

Note

Code generation has been done if one has executed andes, andes selftest, or andes prepare.

Warning

When models are modified (such as adding new models or changing equation strings), code generation needs to be executed again for consistency. It can be more conveniently triggered from command line with andes prepare -i.

andes.system.System.prepare(self, quick=False, incremental=False, models=None, nomp=False, ncpu=1)

Generate numerical functions from symbolically defined models.

All procedures in this function must be independent of test case.

Parameters
quickbool, optional

True to skip pretty-print generation to reduce code generation time.

incrementalbool, optional

True to generate only for modified models, incrementally.

modelslist, OrderedDict, None

List or OrderedList of models to prepare

nompbool

True to disable multiprocessing

Warning

Generated lambda functions will be serialized to file, but pretty prints (SymPy objects) can only exist in the System instance on which prepare is called.

Notes

Option incremental compares the md5 checksum of all var and service strings, and only regenerate for updated models.

Examples

If one needs to print out LaTeX-formatted equations in a Jupyter Notebook, one need to generate such equations with

import andes
sys = andes.prepare()


Alternatively, one can explicitly create a System and generate the code

import andes
sys = andes.System()
sys.prepare()


Since the process is slow, generated numerical functions (Python Callable) will be serialized into a file for future speed up. The package used for serializing/de-serializing numerical calls is dill. System has a function called dill for serializing using the dill package.

andes.system.System.dill(self)

Serialize generated numerical functions in System.calls with package dill.

The serialized file will be stored to ~/.andes/calls.pkl, where ~ is the home directory path.

Notes

This function sets dill.settings['recurse'] = True to serialize the function calls recursively.

andes.system.System.undill(self)

Deserialize the function calls from ~/.andes/calls.pkl with dill.

If no change is made to models, future calls to prepare() can be replaced with undill() for acceleration.

## DAE Storage#

System.dae is an instance of the numerical DAE class.

andes.variables.dae.DAE(system)[source]

Class for storing numerical values of the DAE system, including variables, equations and first order derivatives (Jacobian matrices).

Variable values and equation values are stored as numpy.ndarray, while Jacobians are stored as kvxopt.spmatrix. The defined arrays and descriptions are as follows:

DAE Array

Description

x

Array for state variable values

y

Array for algebraic variable values

z

Array for 0/1 limiter states (if enabled)

f

Array for differential equation derivatives

Tf

Left-hand side time constant array for f

g

Array for algebraic equation mismatches

The defined scalar member attributes to store array sizes are

Scalar

Description

m

The number of algebraic variables/equations

n

The number of algebraic variables/equations

o

The number of limiter state flags

The derivatives of f and g with respect to x and y are stored in four kvxopt.spmatrix sparse matrices: fx, fy, gx, and gy, where the first letter is the equation name, and the second letter is the variable name.

Notes

DAE in ANDES is defined in the form of

$\begin{split}T \dot{x} = f(x, y) \\ 0 = g(x, y)\end{split}$

DAE does not keep track of the association of variable and address. Only a variable instance keeps track of its addresses.

## Model and DAE Values#

ANDES uses a decentralized architecture between models and DAE value arrays. In this architecture, variables are initialized and equations are evaluated inside each model. Then, System provides methods for collecting initial values and equation values into DAE, as well as copying solved values to each model.

The collection of values from models needs to follow protocols to avoid conflicts. Details are given in the subsection Variables.

andes.system.System.vars_to_dae(self, model)

Copy variables values from models to System.dae.

This function clears DAE.x and DAE.y and collects values from models.

andes.system.System.vars_to_models(self)

Copy variable values from System.dae to models.

andes.system.System._e_to_dae(self, eq_name: = ('f', 'g'))

Helper function for collecting equation values into System.dae.f and System.dae.g.

Parameters
eq_name'x' or 'y' or tuple

Equation type name

### Matrix Sparsity Patterns#

The largest overhead in building and solving nonlinear equations is the building of Jacobian matrices. This is especially relevant when we use the implicit integration approach which algebraized the differential equations. Given the unique data structure of power system models, the sparse matrices for Jacobians are built incrementally, model after model.

There are two common approaches to incrementally build a sparse matrix. The first one is to use simple in-place add on sparse matrices, such as doing

self.fx += spmatrix(v, i, j, (n, n), 'd')


Although the implementation is simple, it involves creating and discarding temporary objects on the right hand side and, even worse, changing the sparse pattern of self.fx.

The second approach is to store the rows, columns and values in an array-like object and construct the Jacobians at the end. This approach is very efficient but has one caveat: it does not allow accessing the sparse matrix while building.

ANDES uses a pre-allocation approach to avoid the change of sparse patterns by filling values into a known the sparse matrix pattern matrix. System collects the indices of rows and columns for each Jacobian matrix. Before in-place additions, ANDES builds a temporary zero-filled spmatrix, to which the actual Jacobian values are written later. Since these in-place add operations are only modifying existing values, it does not change the pattern and thus avoids memory copying. In addition, updating sparse matrices can be done with the exact same code as the first approach.

Still, this approach creates and discards temporary objects. It is however feasible to write a C function which takes three array-likes and modify the sparse matrices in place. This is feature to be developed, and our prototype shows a promising acceleration up to 50%.

andes.system.System.store_sparse_pattern(self, models: collections.OrderedDict)

Collect and store the sparsity pattern of Jacobian matrices.

This is a runtime function specific to cases.

Notes

For gy matrix, always make sure the diagonal is reserved. It is a safeguard if the modeling user omitted the diagonal term in the equations.

## Calling Model Methods#

System is an orchestrator for calling shared methods of models. These API methods are defined for initialization, equation update, Jacobian update, and discrete flags update.

The following methods take an argument models, which should be an OrderedDict of models with names as keys and instances as values.

andes.system.System.init(self, models: collections.OrderedDict, routine: str)

Initialize the variables for each of the specified models.

For each model, the initialization procedure is:

• Get values for all ExtService.

• Call the model init() method, which initializes internal variables.

• Copy variables to DAE and then back to the model.

andes.system.System.e_clear(self, models: collections.OrderedDict)

Clear equation arrays in DAE and model variables.

This step must be called before calling f_update or g_update to flush existing values.

andes.system.System.l_update_var(self, models: collections.OrderedDict, niter=None, err=None)

Update variable-based limiter discrete states by calling l_update_var of models.

This function is must be called before any equation evaluation.

andes.system.System.f_update(self, models: collections.OrderedDict)

Call the differential equation update method for models in sequence.

Notes

Updated equation values remain in models and have not been collected into DAE at the end of this step.

andes.system.System.l_update_eq(self, models: collections.OrderedDict, init=False)

Update equation-dependent limiter discrete components by calling l_check_eq of models. Force set equations after evaluating equations.

This function is must be called after differential equation updates.

andes.system.System.g_update(self, models: collections.OrderedDict)

Call the algebraic equation update method for models in sequence.

Notes

Like f_update, updated values have not collected into DAE at the end of the step.

andes.system.System.j_update(self, models: collections.OrderedDict, info=None)

Call the Jacobian update method for models in sequence.

The procedure is - Restore the sparsity pattern with andes.variables.dae.DAE.restore_sparse() - For each sparse matrix in (fx, fy, gx, gy), evaluate the Jacobian function calls and add values.

Notes

Updated Jacobians are immediately reflected in the DAE sparse matrices (fx, fy, gx, gy).

## Configuration#

System, models and routines have a member attribute config for model-specific or routine-specific configurations. System manages all configs, including saving to a config file and loading back.

andes.system.System.get_config(self)

Collect config data from models.

Returns
dict

a dict containing the config from devices; class names are keys and configs in a dict are values.

andes.system.System.save_config(self, file_path=None, overwrite=False)

Save all system, model, and routine configurations to an rc-formatted file.

Parameters
file_pathstr, optional

path to the configuration file default to ~/andes/andes.rc.

overwritebool, optional

If file exists, True to overwrite without confirmation. Otherwise prompt for confirmation.

Warning

Saved config is loaded back and populated at system instance creation time. Configs from the config file takes precedence over default config values.

Load config from an rc-formatted file.

Parameters
conf_pathNone or str

Path to the config file. If is None, the function body will not run.

Returns
configparse.ConfigParser

Warning

It is important to note that configs from files is passed to model constructors during instantiation. If one needs to modify config for a run, it needs to be done before instantiating System, or before running andes from command line. Directly modifying Model.config may not take effect or have side effect as for the current implementation.