Getting Started#

OpenDSSDirect.py is a package in the DSS-Extensions project. As such, it doesn’t require EPRI’s OpenDSS to be installed. OpenDSSDirect.py provides its own customized engine through DSS-Python, which in turn enables us to run the DSS engine on Windows, Linux and macOS (including newer Apple ARM processors, a.k.a. “Apple Silicon”).

For a comparison of the general Python-level API, including a list of our extra functions, please check DSS-Extensions — OpenDSS: Overview of Python APIs. That documents introduces and compares DSS-Python, OpenDSSDirect.py, and the official COM implementation.

To use OpenDSSDirect.py, open a Python interpreter and type the following command:

from opendssdirect import dss

In previous versions, dss was a module here, organized with submodules. Nowadays, dss is an instance of the OpenDSSDirect class.

Just like the previous implementation, the dss instance has attributes following the original submodules. These act as interfaces to various components in OpenDSS. The full list is shown below.

dss.ActiveClass
dss.Basic
dss.Bus
dss.CapControls
dss.Capacitors
dss.Circuit
dss.CktElement
dss.CtrlQueue
dss.DSSCore
dss.Element
dss.Error
dss.Executive
dss.Fuses
dss.Generators
dss.GICSources
dss.Isource
dss.LineCodes
dss.Lines
dss.LoadShape
dss.Loads
dss.Meters
dss.Monitors
dss.PDElements
dss.PVsystems
dss.Parallel
dss.Parser
dss.Progress
dss.Properties
dss.Reclosers
dss.RegControls
dss.Relays
dss.Sensors
dss.Settings
dss.Solution
dss.Storages
dss.SwtControls
dss.Text
dss.Topology
dss.Transformers
dss.Vsources
dss.XYCurves
dss.YMatrix
dss.CNData
dss.LineGeometries
dss.LineSpacings
dss.Reactors
dss.ReduceCkt
dss.TSData
dss.WireData
dss.ZIP

For backwards compatibility, pre-made instances are exposed as modules. Prefer migrating to the instances as recommended in “Upgrading to OpenDSSDirect.py v0.9+”.

These interfaces are the higher level interface to OpenDSS. The higher level interface uses the lower level interface to call the appropriate functions, adding error checking and Python features. For the most part, an OpenDSSDirect.py user will not need to use the lower level interface, but knowing that it exists can be useful.

dss.Command('Redirect "../../tests/data/13Bus/IEEE13Nodeckt.dss"')

👉 In previous versions of this document, we used to recommend dss.run_command(). Unfortunately the error-checking from run_command can be confusing and we cannot change it, for historical and backwards-compatibility reasons.

dss.Command itself is a shortcut to dss.Text.Command, the interface function for dispatching single DSS commands. Since v0.9, when importing the dss instance (from opendssdirect import dss), one can also use just dss('Redirect "../../tests/data/13Bus/IEEE13Nodeckt.dss"') instead of dss.Command('Redirect "../../tests/data/13Bus/IEEE13Nodeckt.dss"') for an even shorter version. As an added feature, calling dss(script_string) also allows passing multiple commands through a multi-line string.

We can print all bus names using the following.

for i in dss.Circuit.AllBusNames():
    print(i)
sourcebus
650
rg60
633
634
671
645
646
692
675
611
652
670
632
680
684

Use the enums#

Instead of using magic numbers like in dss.Solution.Mode(1), use the enums. Import the whole enums module, or separate enums you use. You can import the enums from the dss package (which is DSS-Python) for short:

from opendssdirect import dss, enums
dss.Solution.Mode(enums.SolveModes.Daily)

from opendssdirect.enums import SolveModes
dss.Solution.Mode(SolveModes.Daily)

from dss import SolveModes
dss.Solution.Mode(SolveModes.Daily)

Using magic numbers is bad for multiple reasons:

  • Harder to read for other users. Each user has to each memorize every value or constantly check the reference.

  • The values can actually change throughout the releases. It already happened in the past in OpenDSS and some bugs persistent for about 15 years!

  • Using the provided enum classes ensure, in most cases, that you are passing a valid value. Currently, OpenDSSDirect.py or most Python APIs for OpenDSS do not enforce or check the values, so using the correct enum can reduce the chance of accidents.

See the list of enumerations, including important comments, in opendssdirect.enums page.

Migrating from the COM implementation#

Sometimes users have code written for the official COM API, in which case dss.ActiveCircuit.Loads.kW = 125 is used to set the value, and reading is done directly via dss.ActiveCircuit.Loads.kW (instead of using function calls). If that is your case, you can use DSS-Python in conjunction with OpenDSSDirect.py in order to minimize the code changes, and/or maintain compatibility. Note that OpenDSSDirect.py and DSS-Python are effectively the same thing and are maintained by the same team.

For example, suppose you had the following code running in the official OpenDSS via COM, using win32com or comtypes (Windows only, etc.):

# Load OpenDSS
import win32com.client
dss = win32com.client.Dispatch('OpenDSSengine.DSS')

# Run a DSS script to load a circuit
# (NOTE: typically you would use either the full path here since the official OpenDSS implementation changes the current working directory of the process)
dss.Text.Command = 'Redirect "../../tests/data/13Bus/IEEE13Nodeckt.dss"'

# Select a load and update its kW
dss.ActiveCircuit.Loads.Name = "675c"
dss.ActiveCircuit.Loads.kW = 320

# Solve
dss.ActiveCircuit.Solution.Solve()

# Get the voltage magnitudes
voltages = dss.ActiveCircuit.AllBusVmag

You could fully port it to OpenDSSDirect.py, but it would require rewriting most of the code:

# Load OpenDSS
from opendssdirect import dss

# Run a DSS script to load a circuit
dss('Redirect "../../tests/data/13Bus/IEEE13Nodeckt.dss"')
# or dss.Text.Command('Redirect "./../../tests/data/13Bus/IEEE13Nodeckt.dss"')

# Select a load and update its kW
dss.Loads.Name("675c")
dss.Loads.kW(320)

# Solve
dss.Solution.Solve()

# Get the voltage magnitudes
voltages = dss.Circuit.AllBusVMag()

Alternatively, you could use DSS-Python to keep the code with a few changes, and use OpenDSSDirect.py to complement some functionally. In this case, we recommend importing OpenDSSDirect.py as odd to make it distinct:

# Load OpenDSSDirect.py
from opendssdirect import dss as odd
# Get the equivalent DSS-Python instance for the engine. For the default DSS engine, this would be equivalent to `from dss import dss`
dss = odd.to_dss_python()

# Run a DSS script to load a circuit
dss.Text.Command = 'Redirect "../../tests/data/13Bus/IEEE13Nodeckt.dss"'

# Select a load and update its kW
dss.ActiveCircuit.Loads.Name = "675c"
dss.ActiveCircuit.Loads.kW = 320

# Solve
dss.ActiveCircuit.Solution.Solve()

# Get the voltage magnitudes
voltages = dss.ActiveCircuit.AllBusVmag

Only the first lines of the original code were changed. From this point, you can use OpenDSSDirect.py:

# Get the voltage magnitudes using OpenDSSDirect.py
voltages2 = odd.Circuit.AllBusVMag()

list(voltages) == list(voltages2)
True

See DSS-Python’s Getting Started guide for more notes and features that could make the migration easier.

Returning NumPy arrays#

By default, OpenDSSDirect.py returns lists of elements instead of NumPy arrays, for backwards compatibility. DSS-Python always returns NumPy arrays.

Since version 0.9, there is an option to return NumPy arrays. This is generally done by setting the environment variable OPENDSSDIRECT_PY_USE_NUMPY=1.

A future version might return NumPy arrays by default, depending on the community feedback.

If you want to try using arrays, you can create a separate instance:

from opendssdirect.OpenDSSDirect import OpenDSSDirect

# This will still be bound to the default DSS engine
dss_np = OpenDSSDirect(prefer_lists=False)

dss_np.Circuit.AllBusVMag()[:10]
array([66393.52789449, 66394.73908962, 66391.69346686,  2401.55779273,
        2401.70729944,  2401.5975313 ,  2536.35096825,  2491.56939617,
        2536.37098399,  2431.55474957])

Native Python iterators#

Like in DSS-Python, version 0.9+ supports Pythonic iterators when imported as class:

from opendssdirect import dss

for line in dss.Lines:
    print(line.Name(), line.Bus1(), line.Bus2(), sep='\t')
650632	rg60.1.2.3	632.1.2.3
632670	632.1.2.3	670.1.2.3
670671	670.1.2.3	671.1.2.3
671680	671.1.2.3	680.1.2.3
632633	632.1.2.3	633.1.2.3
632645	632.3.2	645.3.2
645646	645.3.2	646.3.2
692675	692.1.2.3	675.1.2.3
671684	671.1.3	684.1.3
684611	684.3	611.3
684652	684.1	652.1
671692	671	692
print(len(dss.Lines))
12

General OpenDSS caveats related to the classic OpenDSS API, which only allows one active object for each class, still apply.

Multiple engines in the same process#

Follow the notebook for an example: Multiple DSS engines and multithreading.

Plotting and notebook integration#

For examples on how to use the plot commands from OpenDSS, including an extensive gallery, see Integrated plotting in Python.

JSON exports and imports#

Another on-going effort is JSON export and import: https://dss-extensions.org/DSS-Python/examples/JSON

Using the pandas interface#

The utils module implements helper functions that make it easier to use the interface.

An optional pandas interface is also provided in the package, so making it easier to get data from OpenDSS. See installation instructions for how to install the requirements for this interface.

All load data can be attained using the loads_to_dataframe function.

dss.utils.loads_to_dataframe()
Name Idx Phases Class Model NumCust IsDelta Rneut Xneut PF ... kV kW kVABase kvar kWh kWhDays AllocationFactor XfkVA puSeriesRL Sensor
671 671 1 3 1 1 1 True -1.0 0.0 0.868243 ... 4.16 1155.0 1330.272528 660.0 0.0 30.0 0.5 0.0 50.0
634a 634a 2 1 1 1 1 False -1.0 0.0 0.824042 ... 0.277 160.0 194.164878 110.0 0.0 30.0 0.5 0.0 50.0
634b 634b 3 1 1 1 1 False -1.0 0.0 0.8 ... 0.277 120.0 150.0 90.0 0.0 30.0 0.5 0.0 50.0
634c 634c 4 1 1 1 1 False -1.0 0.0 0.8 ... 0.277 120.0 150.0 90.0 0.0 30.0 0.5 0.0 50.0
645 645 5 1 1 1 1 False -1.0 0.0 0.805651 ... 2.4 170.0 211.009478 125.0 0.0 30.0 0.5 0.0 50.0
646 646 6 1 1 2 1 True -1.0 0.0 0.867313 ... 4.16 230.0 265.186727 132.0 0.0 30.0 0.5 0.0 50.0
692 692 7 1 1 5 1 True -1.0 0.0 0.747652 ... 4.16 170.0 227.378539 151.0 0.0 30.0 0.5 0.0 50.0
675a 675a 8 1 1 1 1 False -1.0 0.0 0.931101 ... 2.4 485.0 520.888664 190.0 0.0 30.0 0.5 0.0 50.0
675b 675b 9 1 1 1 1 False -1.0 0.0 0.749838 ... 2.4 68.0 90.686272 60.0 0.0 30.0 0.5 0.0 50.0
675c 675c 10 1 1 1 1 False -1.0 0.0 0.807289 ... 2.4 320.0 396.388356 233.931034 0.0 30.0 0.5 0.0 50.0
611 611 11 1 1 5 1 False -1.0 0.0 0.904819 ... 2.4 170.0 187.882942 80.0 0.0 30.0 0.5 0.0 50.0
652 652 12 1 1 2 1 False -1.0 0.0 0.83005 ... 2.4 128.0 154.207652 86.0 0.0 30.0 0.5 0.0 50.0
670a 670a 13 1 1 1 1 False -1.0 0.0 0.861934 ... 2.4 17.0 19.723083 10.0 0.0 30.0 0.5 0.0 50.0
670b 670b 14 1 1 1 1 False -1.0 0.0 0.866622 ... 2.4 66.0 76.157731 38.0 0.0 30.0 0.5 0.0 50.0
670c 670c 15 1 1 1 1 False -1.0 0.0 0.864582 ... 2.4 117.0 135.325533 68.0 0.0 30.0 0.5 0.0 50.0

15 rows × 38 columns

Similarly, all transformer data can be attained using the transformers_to_dataframe function

dss.utils.transformers_to_dataframe()
Name Idx XfmrCode IsDelta CoreType NumWindings Wdg NumTaps MinTap MaxTap ... R Xhl Xht Xlt Rneut Xneut RdcOhms WdgCurrents WdgVoltages LossesByType
sub sub 1 False 0 2 2 32 0.9 1.1 ... 0.0005 0.008 4.0 4.0 -1.0 0.0 0.000005 [10.877067360936053, -5.963620603957679, -10.8... [2401.557747950874, -0.46374119916828, -1201.2... [33.09125445736572, 268.8962846386712, 33.0912...
reg1 reg1 2 False 0 2 2 32 0.9 1.1 ... 0.005 0.01 35.0 30.0 -1.0 0.0 0.000147 [520.8070706627332, -285.5448633637279, -520.8... [2536.3509029025226, -0.5757412504234813] [121.96806245902553, 123.73248070548289, 121.9...
reg2 reg2 3 False 0 2 2 32 0.9 1.1 ... 0.005 0.01 35.0 30.0 -1.0 0.0 0.000147 [-339.3029943173751, -271.81222396553494, 339.... [-1246.2649814386443, -2157.485029368464] [65.34740648663137, 67.07937129359925, 65.3474...
reg3 reg3 4 False 0 2 2 32 0.9 1.1 ... 0.005 0.01 35.0 30.0 -1.0 0.0 0.000147 [-31.894151246873662, 642.8710100436583, 31.89... [-1267.5669269740297, 2196.9187181280263] [143.23943321383558, 145.00389700813685, 143.2...
xfm1 xfm1 5 False 0 2 2 32 0.9 1.1 ... 0.55 2.0 1.0 1.0 -1.0 0.0 0.000718 [64.61651916490291, -50.16306989128452, -64.61... [273.4231632248873, -15.834059501756999, -148.... [5555.031997120503, 10100.560748497694, 5555.0...

5 rows × 23 columns

A full list of available functions is listed below:

dss.utils.capacitors_to_dataframe
dss.utils.fuses_to_dataframe
dss.utils.generators_to_dataframe
dss.utils.isource_to_dataframe
dss.utils.lines_to_dataframe
dss.utils.loads_to_dataframe
dss.utils.loadshape_to_dataframe
dss.utils.meters_to_dataframe
dss.utils.monitors_to_dataframe
dss.utils.pvsystems_to_dataframe
dss.utils.reclosers_to_dataframe
dss.utils.regcontrols_to_dataframe
dss.utils.relays_to_dataframe
dss.utils.sensors_to_dataframe
dss.utils.transformers_to_dataframe
dss.utils.vsources_to_dataframe
dss.utils.xycurves_to_dataframe

Basically, these functions iterate through all the elements of a certain class of objects, and extract the values as exposed in the API. There is the class_to_dataframe function to do a similar operation using the DSS properties instead, but it is now being deprecated due to its limitations (lack of types and performance). Follow the link to JSON features in the previous section for a modern and customizable approach.