egrid
Purpose
Egrid is the outsourced first part of a power flow calculation for an electric, balanced, distribution network. Instances of Model provide a structure for calculating current and power flow through lines and into consumers (using the voltage vector which is the result of a power-flow-calculation).
Function make_model(*args) creates an instance of Model from arguments of type
- Slacknode
- Branch (line, series capacitor, transformer winding, transformer,
closed switch)
- Injection (consumer, shunt capacitor, PQ/PV-generator, battery)
- Output (indicates that measured flow (I, P or Q) or a part thereof
flows through the referenced terminal (device or node+device))
- PValue (measured active power)
- QValue (measured reactive power)
- IValue (measured electric current)
- Vvalue (measured voltage or setpoint)
- Defk/Deft (definition of a scaling/termional factor, for estimation)
- Defvl (definition of voltage limits)
- Klink/Tlink (associates a factor to an injection or terminal of a branch)
- Defoterm (term for objective function)
including tuples, lists and iterables thereof (for a power-flow-calculation just Slacknode ... Injection are necessary). Additionally, make_model can consume network descriptions as multiline strings if package 'graphparser' is installed. This method is intended to input very small electric networks using a text editor.
Most fields of Model instances provide pandas.DataFrame instances. Electric values are stored per unit. Branch models are PI-equivalent circuits. Active and reactive power of injections have a dedicated voltage exponent.
Details of egrid.model.Model
Fields of egrid.model.Model
nodes: pandas.DataFrame (id of node)
* .idx, int, index of power-flow-calculation node
slacks: pandas.DataFrame
* .id_of_node, str, id of connection node
* .V, complex, given voltage at this slack
* .index_of_node, int, index of power-flow-calculation node
injections: pandas.DataFrame
* .id, str, unique identifier of injection
* .id_of_node, str, unique identifier of connected node
* .P10, float, active power at voltage magnitude 1.0 pu
* .Q10, float, reactive power at voltage magnitude 1.0 pu
* .Exp_v_p, float, voltage dependency exponent of active power
* .Exp_v_q, float, voltage dependency exponent of reactive power
* .index_of_node, int, index of connected power-flow-calculation node
terminal_to_branch: numpy.array
* [0, br] indices of terminal A
* [1, br] indices of terminal B
index of branch is the column index
branchterminals: pandas.DataFrame
* .index_of_branch, int, index of branch
* .id_of_branch, str, unique idendifier of branch
* .id_of_node, str, unique identifier of connected node
* .id_of_other_node, str, unique identifier of node connected
at other side of the branch
* .index_of_node, int, index of connected power-flow-calculation node
* .index_of_other_node, int, index of power-flow-calculation node connected
at other side of the branch
* .y_lo, complex, longitudinal branch admittance
* .y_tr_half, complex, half of transversal branch admittance
* .g_lo, float, longitudinal conductance
* .b_lo, float, longitudinal susceptance
* .g_tr_half, float, transversal conductance of branch devided by 2
* .b_tr_half, float, transversal susceptance of branch devided by 2
* .side, str, 'A' | 'B', side of branch, first or second
bridgeterminals: pandas.DataFrame
* .index_of_branch, int, index of branch
* .id_of_branch, str, unique idendifier of branch
* .id_of_node, str, unique identifier of connected node
* .id_of_other_node, str, unique identifier of node connected
at other side of the branch
* .index_of_node, int, index of connected power-flow-calculation node
* .index_of_other_node, int, index of power-flow-calculation node connected
at other side of the branch
* .y_lo, complex, longitudinal branch admittance
* .y_tr_half, complex, half of transversal branch admittance
* .g_lo, float, longitudinal conductance
* .b_lo, float, longitudinal susceptance
* .g_tr_half, float, transversal conductance of branch devided by 2
* .b_tr_half, float, transversal susceptance of branch devided by 2
* .side, str, 'A' | 'B', side of branch, first or second
branchoutputs: pandas.DataFrame
* .id_of_batch, str, unique identifier of measurement batch
* .id_of_node, str, id of node connected to branch terminal
* .id_of_branch, str, unique identifier of branch
* .index_of_node, int, index of power-flow-calculation node connected
to branch terminal
* .index_of_branch, int, index of branch
injectionoutputs: pandas.DataFrame
* .id_of_batch, str, unique identifier of measurement batch
* .id_of_injection, str, unique identifier of injection
* .index_of_injection, str, index of injection
pvalues: pandas.DataFrame
* .id_of_batch, str, unique identifier of measurement batch
* .P, float, active power
* .direction, float, -1: from device into node, 1: from node into device
qvalues: pandas.DataFrame
* .id_of_batch, str, unique identifier of measurement batch
* .Q, float, reactive power
* .direction, float, -1: from device into node, 1: from node into device
ivalues: pandas.DataFrame
* .id_of_batch, str, unique identifier of measurement batch
* .I, float, electric current
vvalues: pandas.DataFrame
* .id_of_node, str, unique identifier of node voltage is given for
* .V, float, magnitude of voltage
* .index_of_node, index of node voltage is given for
shape_of_Y: tuple (int, int)
shape of admittance matrix for power flow calculation
count_of_slacks: int
count_of_slacks
factors: egrid.factors.Factors (namedtuple)
* .gen_factordata, pandas.DataFrame (index: ('step','id'))
* .step, -1
* .type, 'var'|'const', type of factor decision variable or parameter
* .id_of_source, str, id of factor (previous optimization step)
for initialization
* .value, float, used by initialization if no source factor in previous
optimization step
* .min, float
smallest possible value
* .max, float
greatest possible value
* .is_discrete, bool
just 0 digits after decimal point if True, input for solver,
accepted by MINLP solvers
* .m, float,
multiplier,
the effective value of the factor is a linear function
of var/const (mx + n)
* .n, float,
value of factor when var/const is 0,
the effective value of the factor is a linear function
of var/const (mx + n)
* .index_of_symbol, int
* .cost, float, cost of changing (for Volt-Var-control)
* .gen_injfactor, pandas.DataFrame (index: ('id_of_injection', 'part'))
* .step, -1 (int)
* id, str, ID of factor
* .terminalfactors, pandas.DataFrame
* .id, str, identifier of factor
* .index_of_terminal, int
* .index_of_other_terminal, int
* .type, 'var'|'const'
* .id_of_source, str
* .value, float
* .min, float
* .max, float
* .is_discrete, bool
* .m, float
* .n, float
* .index_of_symbol, int
* .cost, float, cost of changing (for Volt-Var-control)
* .get_groups: function (iterable_of_int) -> (pandas.DataFrame)
pandas.DataFrame(index: ('step', 'id'))
* .type, 'var'|'const', type of factor decision
variable or parameter
* .id_of_source, str, id of factor (previous optimization step)
for initialization
* .value, float, used by initialization if no source factor
in previous optimization step
* .min, float
smallest possible value
* .max, float
greatest possible value
* .is_discrete, bool
just 0 digits after decimal point if True, input for solver,
accepted by MINLP solvers
* .m, float
increase of multiplier with respect to change of var/const
the effective multiplier is a linear
function of var/const (mx + n)
* .n, float
multiplier when var/const is 0.
the effective multiplier is a linear function of
var/const (mx + n)
* .cost, float, cost of changing (for Volt-Var-control)
* .get_injfactorgroups: function (iterable_of_int) -> (pandas.DataFrame)
pandas.DataFrame (index: ('step', 'id_of_injection', 'part'))
* .id, str, ID of factor
mnodeinj: scipy.sparse.csc_matrix
converts a vector ordered according to injection indices to a vector
ordered according to power flow calculation nodes (adding values of
injections for each node) by calculating 'mnodeinj @ vector'
terms: pandas.DataFrame
* .id, str, unique identifier
* .args, list of strings, arguments for function
* .fn, str, identifier of function
* .weight, float, multiplier for term in objective function
* .step, int
messages: pandas.DataFrame
* .message, str, message on reason of error
* .level, int, 0 - information, 1 - warning. 2 - error
Making a Model
Function model_from_frames consumes a dictionary of pandas.DataFrames. model_from_frames aggregates nodes connected without impedance, creates indices, arranges data per branch-terminal from branch-data, calculates values of branches from admittances.
Function make_model generates a model from network device objects defined in egrid.builder.
Example - 3 nodes, 2 lines, 1 consumer:
node: 0 1 2
| line | line |
+-----=====-----+-----=====-----+
| | |
\|/ consumer
Python code for example, suitable input for function egrid.make_model (Branchtap is for demo only, it is used with transformers, however, transformers/transformerwindings are modeled using class Branch too.):
from egrid.builder import (
Slacknode, PValue, QValue, IValue, Output, Branch,
Injection, Defk, Deft, Klink, Tlink)
example = [
Slacknode(id_of_node='n_0', V=1.+0.j),
PValue(
id_of_batch='pq_line_0',
P=30.),
QValue(
id_of_batch='pq_line_0',
Q=8.),
Output(
id_of_batch='pq_line_0',
id_of_node='n_0',
id_of_device='line_0'),
IValue(
id_of_batch='i_line_0',
I=40.0),
Output(
id_of_batch='i_line_0',
id_of_node='n_0',
id_of_device='line_0'),
Branch(
id='line_0',
id_of_node_A='n_0',
id_of_node_B='n_1',
y_lo=1e3-1e3j,
y_tr=1e-6+1e-6j),
Branchtaps(
id='taps_0',
id_of_node='n_0',
id_of_branch='line_0',
Vstep=.1/16,
positionmin=-16,
positionneutral=0,
positionmax=16,
position=0),
Branch(
id='line_1',
id_of_node_A='n_1',
id_of_node_B='n_2',
y_lo=1e3-1e3j,
y_tr=1e-6+1e-6j),
Output(
id_of_batch='pq_consumer_0',
id_of_device='consumer_0'),
Output(
id_of_batch='i_consumer_0',
id_of_device='consumer_0'),
Injection(
id='consumer_0',
id_of_node='n_2',
P10=30.0,
Q10=10.0,
Exp_v_p=2.0,
Exp_v_q=2.0),
Defk(step=(0, 1, 2), id=('kp', 'kq')),
Klink(
step=(0, 1, 2), objid='consumer_0', part=('p', 'q'), id=('kp', 'kq'))]
Valid input to make_model is a multiline pseudo graphic string e.g. this one:
y_tr=1e-6+1e-6j y_tr=1e-6+1e-6j
slack=True y_lo=1e3-1e3j y_lo=1e3-1e3j
n0(---------- line_0 ----------)n1(---------- line_1 ----------)n2
| |
n1->> load0_1_ _load1 <<-n2->> load1_1_
| P10=30.0 P10=20.7 P10=4.3
| Q10=5 Q10=5.7 Q10=2
|
| y_lo=1e3-1e3j
| y_tr=1e-6+1e-6j
n1(---------- line_2 ----------)n3
|
_load2 <<-n3->> load2_1_
P10=20.7 P10=20
Q10=5.7 Q10=5.7