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.