Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tx line in DP formulation #525

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added andes/cases/smib/SMIB_DPLine.xlsx
Binary file not shown.
13 changes: 11 additions & 2 deletions andes/core/symprocessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,19 +118,26 @@ def generate_symbols(self):
self.tex_names[name] = sp.Symbol(tex_name)
# -----------------------------------------------------------

# `all_params_names` include parameters, services, exports from blocks, etc.
for var in self.cache.all_params_names:
is_real = True

if var in self.parent.services:
if self.parent.services[var].vtype == complex:
is_real = False

self.inputs_dict[var] = sp.Symbol(var)

for var in self.cache.all_vars_names:
tmp = sp.Symbol(var)
tmp = sp.Symbol(var, real=True) # all DAE variables are real
self.vars_dict[var] = tmp
self.inputs_dict[var] = tmp
if var in self.cache.vars_int:
self.vars_int_dict[var] = tmp

# store tex names defined in `self.config`
for key in self.config.as_dict():
tmp = sp.Symbol(key)
tmp = sp.Symbol(key, real=True) # not expecting complex numbers in config
self.inputs_dict[key] = tmp
if key in self.config.tex_names:
self.tex_names[tmp] = sp.Symbol(self.config.tex_names[key])
Expand Down Expand Up @@ -411,6 +418,8 @@ def generate_jacobians(self, diag_eps=1e-8):
self.calls.append_ijv(f'{var.e_code}{var.v_code}c', e_idx, v_idx, eps)
self.calls.need_diag_eps.append(var.name)

self.calls.need_diag_eps = sorted(list(set(self.calls.need_diag_eps)))

def generate_pretty_print(self):
"""
Generate pretty print variables and equations.
Expand Down
2 changes: 1 addition & 1 deletion andes/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
('static', ['PQ', 'PV', 'Slack']),
('shunt', ['Shunt', "ShuntTD", 'ShuntSw']),
('interface', ['Fortescue']),
('line', ['Line', 'Jumper']),
('line', ['Line', 'Jumper', 'DPLine']),
('area', ['Area', 'ACE', 'ACEc']),
('dynload', ['ZIP', 'FLoad']),
('synchronous', ['GENCLS', 'GENROU', 'PLBVFU1']),
Expand Down
1 change: 1 addition & 0 deletions andes/models/line/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@

from andes.models.line.line import Line # noqa
from andes.models.line.jumper import Jumper # noqa
from andes.models.line.dpline import DPLine # noqa
145 changes: 145 additions & 0 deletions andes/models/line/dpline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
"""
AC transmission line in dynamic phasor (shift frequency) formulation.
"""


from andes.core import (ModelData, IdxParam,
Model, ExtAlgeb, ConstService, ExtParam, State)


class DPLineData(ModelData):
"""
Data for Line.
"""

def __init__(self):
super().__init__()

self.line = IdxParam(info='Line index', model='Line')


class DPLine(DPLineData, Model):
"""
WIP

AC transmission line model in dynamic phasor (shift frequency) formulation.

The connectivity `u` logic is as follows:
- `u = 0` means the line is not replaced by DPLine. The line remains in the
phasor model, and the `u` of the phasor Line device applies.
- `u = 1` means the line is replaced by DPLine. Still, the connectivity
status of the DP line depends on the `u` of the phasor Line device.

"""

def __init__(self, system=None, config=None):
DPLineData.__init__(self)
Model.__init__(self, system, config)
self.group = 'ACLine'
self.flags.pflow = False
self.flags.tds = True
self.flags.tds_init = True

self.bus1 = ExtParam(model='Line', src='bus1', indexer=self.line, export=False)
self.bus2 = ExtParam(model='Line', src='bus2', indexer=self.line, export=False)

self.a1 = ExtAlgeb(model='Bus', src='a', indexer=self.bus1, tex_name='a_1',
info='phase angle of the from bus',
ename='Pij',
tex_ename='P_{ij}',
)
self.a2 = ExtAlgeb(model='Bus', src='a', indexer=self.bus2, tex_name='a_2',
info='phase angle of the to bus',
ename='Pji',
tex_ename='P_{ji}',
)
self.v1 = ExtAlgeb(model='Bus', src='v', indexer=self.bus1, tex_name='v_1',
info='voltage magnitude of the from bus',
ename='Qij',
tex_ename='Q_{ij}',
)
self.v2 = ExtAlgeb(model='Bus', src='v', indexer=self.bus2, tex_name='v_2',
info='voltage magnitude of the to bus',
ename='Qji',
tex_ename='Q_{ji}',
)

self.ul = ExtParam(model='Line', src='u', indexer=self.line, tex_name='u', export=False)
self.g1 = ExtParam(model='Line', src='g1', indexer=self.line, tex_name='g_1', export=False)
self.g2 = ExtParam(model='Line', src='g2', indexer=self.line, tex_name='g_2', export=False)
self.b1 = ExtParam(model='Line', src='b1', indexer=self.line, tex_name='b_1', export=False)
self.b2 = ExtParam(model='Line', src='b2', indexer=self.line, tex_name='b_2', export=False)
self.r = ExtParam(model='Line', src='r', indexer=self.line, tex_name='r', export=False)
self.x = ExtParam(model='Line', src='x', indexer=self.line, tex_name='x', export=False)
self.g = ExtParam(model='Line', src='g', indexer=self.line, tex_name='g', export=False)
self.b = ExtParam(model='Line', src='b', indexer=self.line, tex_name='b', export=False)
self.tap = ExtParam(model='Line', src='tap', indexer=self.line, tex_name='tap', export=False)

self.gh = ConstService(tex_name='g_h')
self.bh = ConstService(tex_name='b_h')
self.gk = ConstService(tex_name='g_k')
self.bk = ConstService(tex_name='b_k')

self.yh = ConstService(tex_name='y_h', vtype=complex)
self.yk = ConstService(tex_name='y_k', vtype=complex)
self.yhk = ConstService(tex_name='y_{hk}', vtype=complex)

self.ghk = ConstService(tex_name='g_{hk}')
self.bhk = ConstService(tex_name='b_{hk}')

self.itap = ConstService(tex_name='1/t_{ap}')
self.itap2 = ConstService(tex_name='1/t_{ap}^2')

self.Leq = ConstService(v_str='x/(2*pi*60)')

self.ue = ConstService(v_str='ul * u')

self.gh.v_str = 'g1 + 0.5 * g'
self.bh.v_str = 'b1 + 0.5 * b'
self.gk.v_str = 'g2 + 0.5 * g'
self.bk.v_str = 'b2 + 0.5 * b'

self.yh.v_str = 'u * (gh + 1j * bh)'
self.yk.v_str = 'u * (gk + 1j * bk)'
self.yhk.v_str = 'u/((r+1e-8) + 1j*(x+1e-8))'

self.ghk.v_str = 're(yhk)'
self.bhk.v_str = 'im(yhk)'

self.itap.v_str = '1/tap'
self.itap2.v_str = '1/tap/tap'

self.r2x2 = ConstService(v_str='r*r + x*x')

# in implicit form but has the same dq-axis alignment as in ANDES implementation)
self.idd = State(info='real current',
tex_name='idd',
v_str='ue * (r*v1*sin(a1)/r2x2 - r*v2*sin(a2)/r2x2 - v1*x*cos(a1)/r2x2 +'
' v2*x*cos(a2)/r2x2)',
e_str='ue * (-x*iqq - r*idd - v2*sin(a2) + v1*sin(a1)) + (1-ue) * idd',
t_const=self.Leq,
)

self.iqq = State(info='real current',
tex_name='iqq',
v_str='ue * (r*v1*cos(a1)/r2x2 - r*v2*cos(a2)/r2x2 + v1*x*sin(a1)/r2x2 -'
' v2*x*sin(a2)/r2x2)',
e_str='ue * (x*idd - r*iqq - v2*cos(a2) + v1*cos(a1)) + (1-ue) * iqq',
t_const=self.Leq,
)

self.a1.e_str = 'ue * (idd*v1*sin(a1) + iqq*v1*cos(a1))'

self.v1.e_str = 'ue * (-idd*v1*cos(a1) + iqq*v1*sin(a1))'

self.a2.e_str = 'ue * (-idd*v2*sin(a2) - iqq*v2*cos(a2))'

self.v2.e_str = 'ue * (idd*v2*cos(a2) - iqq*v2*sin(a2))'

def v_numeric(self, **kwargs):
"""
Disable phasor lines that have been replaced by DP lines.
"""

mask_idx = [self.line.v[i] for i in range(self.n) if self.u.v[i] == 1]
self.system.groups['ACLine'].set(src='u', idx=mask_idx, attr='v', value=0)
2 changes: 1 addition & 1 deletion andes/routines/tds.py
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,7 @@ def do_switch(self):

# check system connectivity after a switching
if ret is True and self.config.check_conn == 1:
system.connectivity(info=False)
system.connectivity(info=False, routine='tds')

return ret

Expand Down
7 changes: 6 additions & 1 deletion andes/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -1196,7 +1196,7 @@ def vars_to_models(self):
if var.n > 0:
var.v[:] = self.dae.x[var.a]

def connectivity(self, info=True):
def connectivity(self, info=True, routine='pflow'):
"""
Perform connectivity check for system.

Expand Down Expand Up @@ -1225,6 +1225,11 @@ def connectivity(self, info=True):
to.extend(self.Line.a2.a.tolist())
u.extend(self.Line.u.v.tolist())

if routine == 'tds':
fr.extend(self.DPLine.a1.a.tolist())
to.extend(self.DPLine.a2.a.tolist())
u.extend(self.DPLine.u.v.tolist())

# collect from Jumper
fr.extend(self.Jumper.a1.a.tolist())
to.extend(self.Jumper.a2.a.tolist())
Expand Down
6 changes: 6 additions & 0 deletions docs/source/release-notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ The APIs before v3.0.0 are in beta and may change without prior notice.
v1.9 Notes
==========

v1.9.3 (2024-04-XX)
-------------------
- In symbolic processor, most variables are assumed to be real, except some
services that are specified as complex. This will allow generating simplified
expressions.

v1.9.2 (2024-03-25)
-------------------
- Improve PSS/E parser for the `wmod` field in the static generator
Expand Down
Loading