Skip to content

Commit 0010200

Browse files
committed
continuous-time model for three-level NPC inverter, WIP
1 parent 8734bea commit 0010200

File tree

3 files changed

+183
-1
lines changed

3 files changed

+183
-1
lines changed

.vscode/settings.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,8 @@
1515
".vscode/*.py",
1616
"docs/*"
1717
],
18-
"esbonio.sphinx.confDir": "${workspaceFolder}/docs/source"
18+
"esbonio.sphinx.confDir": "${workspaceFolder}/docs/source",
19+
"cSpell.words": [
20+
"motulator"
21+
]
1922
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"""
2+
10-kVA converter
3+
================
4+
5+
This example simulates a grid-following-controlled converter connected to an L
6+
filter and a strong grid. The control system includes a phase-locked loop (PLL)
7+
to synchronize with the grid, a current reference generator, and a PI-based
8+
current controller.
9+
10+
"""
11+
12+
# %%
13+
import numpy as np
14+
15+
from motulator.grid import model, control
16+
from motulator.grid.utils import (
17+
BaseValues, ACFilterPars, NominalValues, plot)
18+
# from motulator.grid.utils import plot_voltage_vector
19+
20+
# %%
21+
# Compute base values based on the nominal values.
22+
23+
nom = NominalValues(U=400, I=14.5, f=50, P=10e3)
24+
base = BaseValues.from_nominal(nom)
25+
26+
# %%
27+
# Configure the system model.
28+
29+
# Filter and grid
30+
par = ACFilterPars(L_fc=.2*base.L)
31+
ac_filter = model.ACFilter(par)
32+
ac_source = model.ThreePhaseVoltageSource(w_g=base.w, abs_e_g=base.u)
33+
# Inverter with constant DC voltage
34+
converter = model.VoltageSourceConverter(u_dc=650, C_dc=1e-3)
35+
36+
# Create system model
37+
mdl = model.GridConverterSystem(converter, ac_filter, ac_source)
38+
mdl.pwm = model.CarrierComparison(
39+
return_complex=True) # Uncomment to enable the PWM model
40+
41+
# %%
42+
# Configure the control system.
43+
44+
cfg = control.GridFollowingControlCfg(
45+
L=.2*base.L, nom_u=base.u, nom_w=base.w, max_i=1.5*base.i)
46+
ctrl = control.GridFollowingControl(cfg)
47+
48+
# Add the DC-bus voltage controller to the control system
49+
ctrl.dc_bus_voltage_ctrl = control.DCBusVoltageController(
50+
C_dc=1e-3, alpha_dc=2*np.pi*30, max_p=base.p)
51+
52+
# %%
53+
# Set the time-dependent reference and disturbance signals.
54+
55+
# Set the references for DC-bus voltage and reactive power
56+
ctrl.ref.u_dc = lambda t: 650
57+
ctrl.ref.q_g = lambda t: (t > .04)*4e3
58+
59+
# Set the external current fed to the DC bus
60+
mdl.converter.i_dc = lambda t: (t > .06)*10
61+
62+
# Uncomment lines below to simulate an unbalanced fault (add negative sequence)
63+
# mdl.ac_source.par.abs_e_g = .75*base.u
64+
# mdl.ac_source.par.abs_e_g_neg = .25*base.u
65+
# mdl.ac_source.par.phi_neg = -np.pi/3
66+
67+
# %%
68+
# Create the simulation object and simulate it.
69+
70+
import time
71+
72+
start_time = time.time()
73+
74+
sim = model.Simulation(mdl, ctrl)
75+
sim.simulate(t_stop=.1)
76+
77+
print(time.time() - start_time)
78+
79+
# %%
80+
# Plot the results.
81+
82+
# By default results are plotted in per-unit values. By omitting the argument
83+
# `base` you can plot the results in SI units.
84+
85+
# Uncomment line below to plot locus of the grid voltage space vector
86+
# plot_voltage_vector(sim, base)
87+
plot(sim, base, plot_pcc_voltage=False)

motulator/common/model/_converter.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,98 @@ def post_process_with_inputs(self):
9999
data.i_dc_int = 1.5*np.real(data.q_cs*np.conj(data.i_cs))
100100

101101

102+
class ThreeLevelConverter(VoltageSourceConverter):
103+
"""
104+
Lossless three-phase, three-level voltage-source converter.
105+
106+
Parameters
107+
----------
108+
u_dc : float
109+
DC-bus voltage (V). This value is used as an initial condition for the
110+
voltage across both capacitors.
111+
C_dc1 : float
112+
DC-bus capacitance (F), positive side.
113+
C_dc2 : float
114+
DC-bus capacitance (F), negative side.
115+
i_dc : callable
116+
External current (A) fed to the DC bus.
117+
118+
"""
119+
120+
def __init__(self, u_dc, C_dc1, C_dc2, i_dc):
121+
super().__init__(u_dc)
122+
self.par = SimpleNamespace(C_dc1=C_dc1, C_dc2=C_dc2)
123+
self.state = SimpleNamespace(u_dc1=u_dc/2, u_dc2=u_dc/2)
124+
self.sol_states = SimpleNamespace(u_dc1=[], u_dc2=[])
125+
self.i_dc = i_dc
126+
self.inp.q_p, self.inp.q_o = []
127+
self.inp.i_dc = i_dc(0)
128+
129+
@property
130+
def u_dc(self):
131+
"""DC-bus voltage (V)."""
132+
return self.state.u_dc1.real + self.state.u_dc2.real
133+
134+
@property
135+
def u_cs(self):
136+
"""AC-side voltage (V)."""
137+
state, inp = self.state, self.inp
138+
u_a = (inp.q_p[0] + inp.q_o[0])*state.u_dc1 + inp.q_o[0]*state.u_dc2
139+
u_b = (inp.q_p[1] + inp.q_o[1])*state.u_dc1 + inp.q_o[1]*state.u_dc2
140+
u_c = (inp.q_p[2] + inp.q_o[2])*state.u_dc1 + inp.q_o[2]*state.u_dc2
141+
return abc2complex([u_a, u_b, u_c])
142+
143+
@property
144+
def i_p(self):
145+
"""Converter-side positive DC current (A)."""
146+
i_abc = complex2abc(self.inp.i_cs)
147+
q_p = self.inp.q_p
148+
return (q_p[0] == 1)*i_abc[0] + (q_p[1] == 1)*i_abc[1] + (
149+
q_p[2] == 1)*i_abc[2]
150+
151+
@property
152+
def i_o(self):
153+
"""Converter-side neutral-point current (A)."""
154+
i_abc = complex2abc(self.inp.i_cs)
155+
q_o = self.inp.q_o
156+
return (q_o[0] == .5)*i_abc[0] + (q_o[1] == .5)*i_abc[1] + (
157+
q_o[2] == .5)*i_abc[2]
158+
159+
def set_outputs(self, _):
160+
"""Set output variables."""
161+
self.out.u_cs = self.u_cs
162+
self.out.u_dc = self.u_dc
163+
164+
def set_inputs(self, t):
165+
"""Set input variables."""
166+
# External DC-bus current
167+
self.inp.i_dc = self.i_dc(t)
168+
# Switching state vectors for DC-bus positive and neutral points
169+
q_abc = self.inp.q_cs
170+
self.inp.q_p = [q_abc[0] == 1, q_abc[1] == 1, q_abc[2] == 1]
171+
self.inp.q_o = [q_abc[0] == .5, q_abc[1] == .5, q_abc[2] == .5]
172+
173+
def rhs(self):
174+
"""Compute the state derivatives."""
175+
d_u_dc1 = (self.inp.i_dc - self.i_p)/self.par.C_dc1
176+
d_u_dc2 = (self.inp.i_dc - self.i_p - self.i_o)/self.par.C_dc2
177+
return [d_u_dc1, d_u_dc2]
178+
179+
def post_process_states(self):
180+
"""Post-process data."""
181+
data = self.data
182+
data.u_dc1, data.u_dc2 = data.u_dc1.real, data.u_dc2.real
183+
u_a = (data.q_cs[0] == 1)*data.u_dc1 - (data.q_cs[0] == .5)*data.u_dc2
184+
u_b = (data.q_cs[1] == 1)*data.u_dc1 - (data.q_cs[1] == .5)*data.u_dc2
185+
u_c = (data.q_cs[2] == 1)*data.u_dc1 - (data.q_cs[2] == .5)*data.u_dc2
186+
data.u_cs = abc2complex([u_a, u_b, u_c])
187+
188+
def post_process_with_inputs(self):
189+
"""Post-process data with inputs."""
190+
data = self.data
191+
#data.i_o =
192+
193+
102194
# %%
103195
class FrequencyConverter(VoltageSourceConverter):
104196
"""

0 commit comments

Comments
 (0)