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

Ep/dynamic voltage support #134

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
2 changes: 2 additions & 0 deletions PyDSS/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class ControllerType(enum.Enum):
STORAGE_CONTROLLER = "StorageController"
THERMOSTATIC_LOAD_CONTROLLER = "ThermostaticLoad"
XMFR_CONTROLLER = "xmfrController"
DYNAMIC_VOLTAGE_SUPPORT = "DynamicVoltageSupport"

CONTROLLER_TYPES = tuple(x.value for x in ControllerType)
CONFIG_EXT = ".toml"
Expand All @@ -73,6 +74,7 @@ def filename_from_enum(obj):
STORAGE_CONTROLLER_FILENAME = filename_from_enum(ControllerType.STORAGE_CONTROLLER)
THERMOSTATIC_LOAD_CONTROLLER_FILENAME = filename_from_enum(ControllerType.THERMOSTATIC_LOAD_CONTROLLER)
XMFR_CONTROLLER_FILENAME = filename_from_enum(ControllerType.XMFR_CONTROLLER)
DYNAMIC_VOLTAGE_SUPPORT = filename_from_enum(ControllerType.DYNAMIC_VOLTAGE_SUPPORT)

TIMESERIES_PLOT_FILENAME = filename_from_enum(VisualizationType.TIMESERIES_PLOT)
EXPORT_BY_CLASS_FILENAME = filename_from_enum(ExportMode.BY_CLASS)
Expand Down
10 changes: 5 additions & 5 deletions PyDSS/dssInstance.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,12 +204,12 @@ def _CreateControllers(self, ControllerDict):
Controller = pyControllers.pyController.Create(ElmName, ControllerType, SettingsDict, self._dssObjects,
self._dssInstance, self._dssSolver)
if Controller != -1:
controller_name = 'Controller.' + ElmName
controller_name = 'Controller.'+ ControllerType + '.' + ElmName
self._pyControls[controller_name] = Controller
class_name, element_name = Controller.ControlledElement().split(".")
if controller_name not in self._pyControls_types:
self._pyControls_types[controller_name] = class_name
self._Logger.info('Created pyController -> Controller.' + ElmName)
self._pyControls_types[controller_name] = (class_name, ControllerType)
self._Logger.info('Created pyController -> Controller.' + ControllerType + ElmName)
return

def _CreatePlots(self, PlotsDict):
Expand Down Expand Up @@ -271,12 +271,12 @@ def _UpdateControllers(self, Priority, Time, Iteration, UpdateResults):
maxError = 0
_pyControls_types = set(self._pyControls_types.values())

for class_name in _pyControls_types:
for class_name, controller_type in _pyControls_types:
self._dssInstance.Basic.SetActiveClass(class_name)
elm = self._dssInstance.ActiveClass.First()
while elm:
element_name = self._dssInstance.CktElement.Name()
controller_name = 'Controller.' + element_name
controller_name = 'Controller.' +controller_type+'.' + element_name
if controller_name in self._pyControls:
controller = self._pyControls[controller_name]
error = controller.Update(Priority, Time, UpdateResults)
Expand Down
350 changes: 350 additions & 0 deletions PyDSS/pyControllers/Controllers/DynamicVoltageSupport.py

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions PyDSS/pyControllers/Controllers/PvDynamic.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def __init__(self, VSCObj, Settings, dssInstance, ElmObjectList, dssSolver):
rated_power_ac_va = Settings["RATED_POWER_AC_VA"]
rated_power_dc_watts = Settings["RATED_POWER_DC_WATTS"]
der_verbosity = 'DEBUG'
config_file = r"C:\Users\alatif\Documents\GitHub\pvder\config_der.json"
config_file = r"C:\Users\epohl\Desktop\PROJECT_FILES\NAERM\Modeling\PyDSS_Protection_Modeling\Dynamic_PV_Model\DER_config_files\config_der.json"
if self.n_phases == 1:
self._Va = self.Voltages()
self._Vrms = abs(self._Va )/math.sqrt(2)
Expand All @@ -74,8 +74,8 @@ def __init__(self, VSCObj, Settings, dssInstance, ElmObjectList, dssSolver):
self._Vrms = abs(self._Va ) / math.sqrt(2)
self._pv_model = DERModel(
modelType= 'ThreePhaseUnbalanced',
powerRating = 250000,
Sinverter_rated = 250000,
powerRating = rated_power_dc_watts,
Sinverter_rated = rated_power_ac_va,
events=self.events1,
configFile=config_file,
Vrmsrated = self._Vrms,
Expand Down
59 changes: 41 additions & 18 deletions PyDSS/pyControllers/Controllers/PvVoltageRideThru.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,22 @@ def __init__(self, PvObj, Settings, dssInstance, ElmObjectList, dssSolver):

# Initializing the model
PvObj.SetParameter('kvar', 0)
self.__Srated = float(PvObj.SetParameter('kva', Settings['kVA']))
self.__Prated = float(PvObj.SetParameter('kW', Settings['maxKW']))
self.__minQ = float(PvObj.SetParameter('minkvar', -Settings['KvarLimit']))
self.__maxQ = float(PvObj.SetParameter('maxkvar', Settings['KvarLimit']))
self.__Srated = float(PvObj.GetParameter('kva'))
self.__Prated = float(PvObj.GetParameter('kw'))
self._ControlledElm.SetParameter('Class', 0) #ECMP
self.__minQ = -self.__Srated*0.44
self.__maxQ = self.__Srated*0.44

# MISC settings
self.__cutin = Settings['%PCutin']
self.__cutout = Settings['%PCutout']
self.__trip_deadtime_sec = Settings['Reconnect deadtime - sec']
self.__Time_to_Pmax_sec = Settings['Reconnect Pmax time - sec']
self.__Prated = Settings['maxKW']
self.__priority = Settings['Priority']
self.__enablePFlimit = Settings['Enable PF limit']
self.__minPF = Settings['pfMin']
self.__UcalcMode = Settings['UcalcMode']
self.__use_with_dynamic_voltage_support = Settings['Use with Dynamic Voltage Support']
# initialize deadtimes and other variables
self.__initializeRideThroughSettings()
if self.__Settings["Follow standard"] == "1547-2003":
Expand Down Expand Up @@ -246,9 +247,10 @@ def __CreateOperationRegions(self):
print("User defined setting outside of IEEE 1547 acceptable range.")
assert False

self._ControlledElm.SetParameter('Model', '7')
self._ControlledElm.SetParameter('Vmaxpu', V[0])
self._ControlledElm.SetParameter('Vminpu', V[1])
if not self.__use_with_dynamic_voltage_support:
self._ControlledElm.SetParameter('Model', '7')
self._ControlledElm.SetParameter('Vmaxpu', V[0])
self._ControlledElm.SetParameter('Vminpu', V[1])

ContineousPoints = [Point(V[0], 0), Point(V[0], tMax), Point(V[1], tMax), Point(V[1], 0)]
ContineousRegion = Polygon([[p.y, p.x] for p in ContineousPoints])
Expand Down Expand Up @@ -300,12 +302,11 @@ def __CreateOperationRegions(self):
self.TripRegion = unary_union([OVtripRegion, UVtripRegion])
else:
self.CurrLimRegion = MandatoryRegion

self.MomentarySucessionRegion = unary_union([PermissiveOVRegion, PermissiveUVRegion])
self.TripRegion = unary_union([OVtripRegion, UVtripRegion, MayTripRegion])

self.NormalRegion = ContineousRegion



return V, T

def Update(self, Priority, Time, Update):
Expand Down Expand Up @@ -374,7 +375,12 @@ def Trip(self, uIn):
"""
if uIn < 0.88:
if self.__isConnected:
self.__Trip(30.0, 0.4, False)
self.__Trip(
Deadtime=30,
Time2Pmax=0.4,
forceTrip=False,
permissive_to_trip=False
)
return

def VoltageRideThrough(self, uIn):
Expand Down Expand Up @@ -426,7 +432,12 @@ def __Connect(self):
if not self.__isConnected:
uIn = self._ControlledElm.GetVariable('VoltagesMagAng')[::2]
uBase = self._ControlledElm.sBus[0].GetVariable('kVBase') * 1000
uIn = max(uIn) / uBase if self.__UcalcMode == 'Max' else sum(uIn) / (uBase * len(uIn))
if self.__UcalcMode == 'Max':
uIn = max(uIn) / uBase
elif self.__UcalcMode == 'Min':
uIn = min(uIn) / uBase
else:
uIn = sum(uIn) / (uBase * len(uIn))
if self.useAvgVoltage:
self.voltage = self.voltage[1:] + self.voltage[:1]
self.voltage[0] = uIn
Expand All @@ -435,6 +446,7 @@ def __Connect(self):
if uIn < self.__rVs[0] and uIn > self.__rVs[1] and deadtime >= self.__TrippedDeadtime:

self._ControlledElm.SetParameter('enabled', True)
self._ControlledElm.SetParameter('Class', 0)
self.__isConnected = True
self._ControlledElm.SetParameter('kw', 0)
self.__ReconnStartTime = self.__dssSolver.GetDateTime()
Expand All @@ -454,17 +466,23 @@ def __Trip(self, Deadtime, Time2Pmax, forceTrip, permissive_to_trip=False):
#if self.Time >1:

if self.__isConnected or forceTrip:
print(self._ControlledElm.GetInfo(), uIn, uBase)
self._ControlledElm.SetParameter('enabled', False)

self._ControlledElm.SetParameter('kw', 0)
self._ControlledElm.SetParameter('kvar', 0)
self._ControlledElm.SetParameter('Class', 1)


self.__isConnected = False
self.__TrippedStartTime = self.__dssSolver.GetDateTime()
self.__TrippedPmaxDelay = Time2Pmax
self.__TrippedDeadtime = Deadtime

elif permissive_to_trip:
print(self._ControlledElm.GetInfo(), uIn, uBase)
self._ControlledElm.SetParameter('enabled', False)

self._ControlledElm.SetParameter('kw', 0)
self._ControlledElm.SetParameter('kvar', 0)
self._ControlledElm.SetParameter('Class', 1)


self.__isConnected = False
self.__TrippedStartTime = self.__dssSolver.GetDateTime()
Expand All @@ -475,7 +493,12 @@ def __Trip(self, Deadtime, Time2Pmax, forceTrip, permissive_to_trip=False):
def __UpdateViolatonTimers(self):
uIn = self._ControlledElm.GetVariable('VoltagesMagAng')[::2]
uBase = self._ControlledElm.sBus[0].GetVariable('kVBase') * 1000
uIn = max(uIn) / uBase if self.__UcalcMode == 'Max' else sum(uIn) / (uBase * len(uIn))
if self.__UcalcMode == 'Max':
uIn = max(uIn) / uBase
elif self.__UcalcMode == 'Min':
uIn = min(uIn) / uBase
else:
uIn = sum(uIn) / (uBase * len(uIn))
if self.useAvgVoltage:
self.voltage = self.voltage[1:] + self.voltage[:1]
self.voltage[0] = uIn
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[DVS_test]
"Trv" = 0.001
"Tinv" = 0.001
"dbd1" = -0.1
"dbd2" = 0.1
"Kqv" = 100
"iqh1" = 1.1
"kvar_max" = 0.44
"kvar_min" = -0.44
"capacitive_support" = true
"inductive_support" = true
"post_fault_reset" = 0
"overvoltage_kva_limited" = true
"current_limited_error_tolerance" = 0.001
"Use with Voltage Ride Through" = false

[DVS_VRT_test]
"Trv" = 0.0005
"Tinv" = 0.0005
"dbd1" = -0.1
"dbd2" = 0.1
"Kqv" = 100
"iqh1" = 1.1
"kvar_max" = 0.44
"kvar_min" = -0.44
"capacitive_support" = true
"inductive_support" = true
"post_fault_reset" = 1
"overvoltage_kva_limited" = true
"current_limited_error_tolerance" = 0.001
"Use with Voltage Ride Through" = true
83 changes: 83 additions & 0 deletions PyDSS/pyControllers/Controllers/Settings/VoltageRideThru.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
[NO_VRT_DVS_test]
"Category" = "test"
"kVA" = 4.0
"maxKW" = 4.0
"KvarLimit" = 1.76
"%PCutin" = 10.0
"%PCutout" = 10.0
"UcalcMode" = "Max"
"Priority" = "Equal"
"Enable PF limit" = false
"pfMin" = 0.95
"Follow standard" = "1547-2003"
"Ride-through Category" = "Category I"
"OV2 - p.u." = 1.2
"OV2 CT - sec" = 0.16
"OV1 - p.u." = 1.2
"OV1 CT - sec" = 2.0
"UV1 - p.u." = 0.8
"UV1 CT - sec" = 2.0
"UV2 - p.u." = 0.5
"UV2 CT - sec" = 0.16
"Reconnect deadtime - sec" = 3000.0
"Reconnect Pmax time - sec" = 300.0
"Permissive operation" = "Current limited"
"May trip operation" = "Trip"
"Multiple disturbances" = "Trip"
"Use with Dynamic Voltage Support" = true

[1547_CAT_III_test]
"Category" = "test"
"kVA" = 4.0
"maxKW" = 4.0
"KvarLimit" = 1.76
"%PCutin" = 0
"%PCutout" = 0
"UcalcMode" = "Max"
"Priority" = "Equal"
"Enable PF limit" = false
"pfMin" = 0.95
"Follow standard" = "1547-2018"
"Ride-through Category" = "Category III"
"OV2 - p.u." = 1.2
"OV2 CT - sec" = 0.16
"OV1 - p.u." = 1.1
"OV1 CT - sec" = 13.0
"UV1 - p.u." = 0.88
"UV1 CT - sec" = 21.0
"UV2 - p.u." = 0.5
"UV2 CT - sec" = 2.0
"Reconnect deadtime - sec" = 3000.0
"Reconnect Pmax time - sec" = 300.0
"Permissive operation" = "Current limited"
"May trip operation" = "Trip"
"Multiple disturbances" = "Trip"
"Use with Dynamic Voltage Support" = false

[1547_CAT_III_DVS_test]
"Category" = "test"
"kVA" = 4.0
"maxKW" = 4.0
"KvarLimit" = 1.76
"%PCutin" = 0
"%PCutout" = 0
"UcalcMode" = "Max"
"Priority" = "Equal"
"Enable PF limit" = false
"pfMin" = 0.95
"Follow standard" = "1547-2018"
"Ride-through Category" = "Category III"
"OV2 - p.u." = 1.2
"OV2 CT - sec" = 0.16
"OV1 - p.u." = 1.1
"OV1 CT - sec" = 13.0
"UV1 - p.u." = 0.88
"UV1 CT - sec" = 21.0
"UV2 - p.u." = 0.5
"UV2 CT - sec" = 2.0
"Reconnect deadtime - sec" = 3000.0
"Reconnect Pmax time - sec" = 300.0
"Permissive operation" = "Current limited"
"May trip operation" = "Trip"
"Multiple disturbances" = "Trip"
"Use with Dynamic Voltage Support" = true
40 changes: 38 additions & 2 deletions PyDSS/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,51 @@
"PyDSS/pyControllers/Controllers/Settings/PvControllers.toml"
),
},
],
ControllerType.PV_VOLTAGE_RIDETHROUGH.value: [

],
ControllerType.SOCKET_CONTROLLER.value: [],
ControllerType.STORAGE_CONTROLLER.value: [],
ControllerType.XMFR_CONTROLLER.value: [],
ControllerType.MOTOR_STALL.value: [],
ControllerType.MOTOR_STALL_SIMPLE.value: [],
ControllerType.PV_VOLTAGE_RIDETHROUGH.value: [
{
"name": "NO_VRT_DVS_test",
"filename": os.path.join(
os.path.dirname(getattr(PyDSS, "__path__")[0]),
"PyDSS/pyControllers/Controllers/Settings/VoltageRideThru.toml"
),
},
{
"name": "1547_CAT_III_test",
"filename": os.path.join(
os.path.dirname(getattr(PyDSS, "__path__")[0]),
"PyDSS/pyControllers/Controllers/Settings/VoltageRideThru.toml"
),
},
{
"name": "1547_CAT_III_DVS_test",
"filename": os.path.join(
os.path.dirname(getattr(PyDSS, "__path__")[0]),
"PyDSS/pyControllers/Controllers/Settings/VoltageRideThru.toml"
),
},],
ControllerType.FAULT_CONTROLLER.value: [],
ControllerType.PV_DYNAMIC.value: [],
ControllerType.DYNAMIC_VOLTAGE_SUPPORT.value:[{
"name": "DVS_test",
"filename": os.path.join(
os.path.dirname(getattr(PyDSS, "__path__")[0]),
"PyDSS/pyControllers/Controllers/Settings/DynamicVoltageSupport.toml"
),
},
{
"name": "DVS_VRT_test",
"filename": os.path.join(
os.path.dirname(getattr(PyDSS, "__path__")[0]),
"PyDSS/pyControllers/Controllers/Settings/DynamicVoltageSupport.toml"
),
},],
},
}

Expand Down
4 changes: 0 additions & 4 deletions docs/source/Creating custom controls.rst

This file was deleted.

12 changes: 12 additions & 0 deletions docs/source/controllers.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
###########
Controllers
###########

Documentation for pydss controllers can be found here

.. toctree::
:maxdepth: 2

dynamic_voltage_support


Loading