OpenDSS: Python APIs#
An overview of Python APIs used to interact with the OpenDSS engine
OpenDSS can be used and controlled through many different approaches. Using Python together with the OpenDSS allows using thousands of packages in the Python ecosystem. This was the main motivator for the inception of the DSS-Extensions project.
This document provides an overview of typical usage with the official implementation and the three options available on DSS-Extensions:
See also the FAQ for more context.
If you are new to some classic OpenDSS API concepts, please check OpenDSS COM/classic APIs.
Official OpenDSS COM API (OpenDSSEngine.DLL
)#
When using the official COM implementation, the OpenDSS engine is used through OpenDSSEngine.DLL
, part of the OpenDSS distribution.
COM, “Component Object Model”, is a Windows-specific technology available since the 1990’s.
Usually, the required DLLs are automatically registered for COM interaction during the installation. If the user is manually installing, additional steps are required. There are 32-bit and a 64-bit versions of the engine, both are part of the official OpenDSS installation.
In Python, there are two main packages to interact with COM modules, win32com
(part of the pywin32
distribution) and comtypes
. Both packages are available both through pip
(PyPI, the Python Package Index) or through conda
(the Anaconda ecosystem, including the wonderful conda-forge). Since 2021, we recommend using comtypes
if possible, so the code snippets below will use that. This recommendation is due to both its better performance when compared to win32com
, and better/automatic handling of early-bindings code.
Installation#
To install it:
Install OpenDSS as an administrator. It will register the COM DLLs (both 32- and 64-bit versions) during installation.
Ensure either
comtypes
orpywin32
(win32com
is part of thepywin32
package) is installed. If required, both can be installed using eitherpip
(e.g.pip install comtypes
) orconda
(e.g.conda install comtypes
).
Minimal usage#
A minimal sample would be:
import comtypes.client
dss = comtypes.client.CreateObject('OpenDSSEngine.DSS')
# Check the engine version
print(dss.Version)
# Load a circuit
dss.Text.Command = 'redirect "c:/my_folder/my_circuit1/master.dss"'
# Get the circuit voltages
vmag = dss.ActiveCircuit.AllBusVmag
# ...
By default, both win32com
and comtypes
modules return tuples or lists, but comtypes
has optional NumPy interop. Check the comtypes
docs for details; this feature has some overhead but simplifies the final code, since in many scenarios the users would convert the data to NumPy anyway.
Handling the working directory (a.k.a. cd
, cwd
)#
When the OpenDSS is loaded, some options stored in the Windows registry from the previous execution are loaded, including the last working directory. For example, suppose you ran a redirect
command with a file in the folder c:\my_folder\my_circuit1
and exited OpenDSS — either through COM or through the GUI. By default, c:\my_folder\my_circuit1
will be saved to the registry and restored in the next execution. This can be confusing and lead to unexpected behavior for new users when running scripts from other folders.
To avoid the issue, a typical idiom is:
import os
import comtypes.client
# Save the current working directory path
old_cd = os.getcwd()
# OpenDSS may change the current working directory when loaded
dss = comtypes.client.CreateObject('OpenDSSEngine.DSS')
# Let's restore our working directory to the original,
# before OpenDSS was loaded
os.chdir(old_cd)
# Continue as usual...
# Check the engine version
print(dss.Version)
# Load a circuit
dss.Text.Command = 'redirect "c:/my_folder/my_circuit1/master.dss"'
# ...
DSS-Extensions: DSS-Python#
For DSS-Extensions, users are not required to install the official OpenDSS, although we recommend new users to do so, especially to make it easier to follow legacy training material. DSS-Extensions use a customized engine with very good compatibility with the official OpenDSS engine, but better performance in some scenarios and a lot of extra features.
DSS-Python packages the OpenDSS engine as implemented in DSS-Extensions to mimic the organization of the COM implementation. As such, users can easily migrate from the official COM implementation to DSS-Python without rewriting all the code. In fact, some users keep a toggle to use both implementations.
DSS-Python is available for Windows, Linux, and macOS (including Apple M1 ARM machines, or “Apple Silicon” in marketing speak), while the official OpenDSS is official supported only on MS Windows.
Installation#
Typically, using pip install dss-python
is enough for basic usage. To install recommended optional dependencies, including OpenDSSDirect.py
and AltDSS-Python
, use pip install dss-python[all]
.
Minimal usage#
Assuming recent DSS-Python versions (dss-python>=0.12.1
) are installed (e.g. through pip install dss-python
), you can use the following:
from dss import dss
# Check the engine version
print(dss.Version)
# Load a circuit
dss.Text.Command = 'redirect "c:/my_folder/my_circuit1/master.dss"'
# For modern DSS-Python versions, you can use this shortcut for Text.Command
dss('redirect "c:/my_folder/my_circuit1/master.dss"')
# Get the circuit voltages
vmag = dss.ActiveCircuit.AllBusVmag
# ...
Note that only the first two lines changed. Most of the official COM implementation is readily available otherwise. Some differences are inherit to the underlying difference OpenDSS engines. For example, plotting is different.
There are also a lot of extra features, some under development. These include multiple DSS engines in the same process, handling of ZIP files, and more. Please check https://dss-extensions.org/DSS-Python/.
More friendly types#
Since DSS-Python v0.13, we added an option to toggle “advanced types”. This feature is also available in OpenDSSDirect.py when the option to use NumPy arrays is enabled (check the docs).
Generally, the OpenDSS APIs return plain arrays. This happens even for data that represents matrices. Moreover, typically complex numbers are represented by pairs of floats, represented sequentially in a plain array. Since NumPy has good support for multi-dimensional arrays and complex numbers are simple in Python, we can use some new features of our DSS C-API library to provide user-friendly types with very low performance impact.
from dss import dss
# Check the engine version
print(dss.Version)
# Load a circuit
dss.Text.Command = 'redirect "c:/my_folder/my_circuit1/master.dss"'
dss.ActiveCircuit.Solution.Solve()
# Let's activate a CktElement
dss.ActiveCircuit.Vsources.First
CktElement = dss.ActiveCircuit.ActiveCktElement
# By default, for compatibility, these return a NumPy array with dtype=float64
volts_as_floats = dss.ActiveCircuit.AllBusVolts
yprim_as_floats = CktElement.Yprim
# It's simple to toggle the advanced types
dss.AdvancedTypes = True
# Now, this returns a NumPy array with dtype=complex128
volts = dss.ActiveCircuit.AllBusVolts
# And this returns a square complex matrix
yprim = CktElement.Yprim
Patching and Pythonizing the official COM module#
DSS-Python also ships with a small utility function to patch the COM implementations to provide some Pythonic extras and a few additional properties/functions. This patch works with both comtypes
and win32com
.
For example, most top-level APIs in DSS-Python support iteration and users can to this:
for b in dss.ActiveCircuit.ActiveBus:
print(b.Name, b.x, b.y)
To facilitate migration and cross-compatibility, users can call the function to patch and keep the same code with the COM-based implementation.
For a practical example, this fails:
import comtypes.client
dss = comtypes.client.CreateObject('OpenDSSEngine.DSS')
dss.Text.Command = r'redirect "C:\Program Files\OpenDSS\IEEETestCases\13Bus\IEEE13Nodeckt.dss"'
for b in dss.ActiveCircuit.ActiveBus:
print(b.Name, b.x, b.y)
for b in dss.ActiveCircuit.Loads:
print(l.Name, l.kW)
You should get a message like TypeError: 'POINTER(IBus)' object is not iterable
or TypeError: 'POINTER(ILoads)' object is not iterable
.
But this works:
from dss import patch_dss_com # import this
import comtypes.client
# Wrap the creation of the object
dss = patch_dss_com(comtypes.client.CreateObject('OpenDSSEngine.DSS'))
dss.Text.Command = r'redirect "C:\Program Files\OpenDSS\IEEETestCases\13Bus\IEEE13Nodeckt.dss"'
for b in dss.ActiveCircuit.ActiveBus:
print(b.Name, b.x, b.y)
for l in dss.ActiveCircuit.Loads:
print(l.Name, l.kW)
The output is what we expect:
sourcebus 200.0 400.0
650 200.0 350.0
rg60 200.0 300.0
633 350.0 250.0
634 400.0 250.0
671 200.0 100.0
645 100.0 250.0
646 0.0 250.0
692 250.0 100.0
675 400.0 100.0
611 0.0 100.0
652 100.0 0.0
670 200.0 200.0
632 200.0 250.0
680 200.0 0.0
684 100.0 100.0
671 1155.0
634a 160.0
634b 120.0
634c 120.0
645 170.0
646 230.0
692 170.0
675a 485.0
675b 68.0
675c 290.0
611 170.0
652 128.0
670a 17.0
670b 66.0
670c 117.0
Besides the iterators, a notable addition from this patch is adding the dss.ActiveCircuit.Monitors.AsMatrix()
. This function is a utility function for performance that returns all monitor channels as a matrix, avoiding multiple round trips and redundant processing to extract the channels individually.
Note that this patch doesn’t change the official engine, just adjusts the Python side to provide some convenience.
DSS-Extensions: OpenDSSDirect.py#
OpenDSSDirect.py (ODD.py for short) is a project that initially employed the official OpenDSSDirect.DLL
(a.k.a. “Direct Connection Shared Library”/DCSL or “Direct DLL”) to expose OpenDSS to Python users. When the DSS-Extensions project was created, ODD.py was migrated to use the lower-level tools from DSS-Python to expose the same Python API as before, but using the engine from DSS-Extensions, which employs a completely different low-level API.
Note
OpenDSSDirect.DLL
still exists and has been reworked in 2023 to remove COM-based variants. It bypasses some of the COM requirements, such as DLL registration, but the general performance should be the same as the COM DLL implementation — see, e.g., page 27 here (under “Myths and legends about user interfaces”), from Session 2 of the OpenDSS Virtual Training 2022. There is no officially supported module for Python that uses OpenDSSDirect.DLL
as of November 2022. Besides that, we only compare to the official COM implementation for conciseness.
DSS-Extensions plans to support OpenDSSDirect.DLL
usage in a limited capacity in the future.
Most of the features from DSS-Python are available in ODD.py, and it has extra utility functions.
Style-wise, the main difference from ODD.py and DSS-Python is that instead of Python properties, most access is done through plain function calls and the module/class organization is flatter.
More notes and API docs are available at https://dss-extensions.org/OpenDSSDirect.py/.
Installation#
Typically, using pip install opendssdirect.py
is enough for basic usage. To install recommended optional dependencies, including AltDSS-Python
and pandas
, use pip install opendssdirect.py[extras]
.
Minimal usage#
from opendssdirect import dss
# Check the engine version
print(dss.Basic.Version())
# Load a circuit
dss.Text.Command('redirect "c:/my_folder/my_circuit1/master.dss"')
# You can also use the shortcut to achieve the same
dss('redirect "c:/my_folder/my_circuit1/master.dss"')
# Get the circuit voltages
vmag = dss.Circuit.AllBusVMag()
# ...
DSS-Extensions: the new AltDSS-Python#
AltDSS-Python provides a new approach, representing a unified and extended API that allows manipulating directly in Python nearly all OpenDSS object types. Furthermore, it encapsulates all separate concepts in simple objects.
The new features can be used together with both DSS-Python and OpenDSSDirect.py, allowing users to complement existing code instead of having to rewrite everything.
Visit https://dss-extensions.org/AltDSS-Python/ for more information.
Installation#
Similar to DSS-Python, use either pip install altdss
or pip install altdss[all]
, depending whether you want all recommended packages or not.
Minimal usage#
from altdss import altdss
# Check the engine version
print(altdss.Version())
# Load a circuit
altdss('redirect "c:/my_folder/my_circuit1/master.dss"')
# Get the circuit voltages
vmag = dss.BusVMag()
Direct object and batch manipulation#
Most of the basic methods from the other APIs are available, but you can manipulate objects more efficiently without dropping down to the text interface, and can keep the objects for later use without worrying about what object is active.
# Get the default loadshape
ls_default = altdss.LoadShape['default']
# Get a batch object representing all 2-phase loads
loads_2ph = altdss.Load.batch(Phases=2)
# Set the Daily shape for these 2-phases loads to the default shape
loads_2ph.Daily = ls_default
# Grab a single load object
load = altdss.Load['load_name']
load.kW *= 1.5
# Run a snapshot solution
from dss import SolveModes
altdss.Solution.Mode = SolveModes.SnapShot
altdss.Solution.Solve()
# You can now inspect the load powers, using the same load object above
print(load.TotalPowers())
# Get a bus; most of the classic Bus interface methods are generalized and
# have similar implementations here
bus = altdss.Bus[2]
another_bus = altdss.Bus[3]
print(bus.puVMagAngle, another_bus.puVMagAngle)
# Run a daily solution
altdss.Solution.Mode = SolveModes.Daily
altdss.Solution.Solve()
All DSS objects and (non-redundant) properties are exposed, with the same names as used in the .DSS scripts, when possible.
This is just short sample, check the docs for more.
The commands and other default are also being refactored and will be exposed in a more Pythonic way in a future release.
General performance tips#
This list is mostly API agnostic.
Avoid using strings in general. Strings can be converted multiple times through the APIs (Python implementation, internal DSS API implementation, maybe OS specific details).
If something is known to be static — e.g. list of line names — try caching (keeping a copy) at Python level to avoid the API overhead. Lists of strings (like
All...Names
) and arrays are especially heavy since a lot of processing may be required in the interface between languages.Use
idx
where possible to avoid passing strings avoid and a hashmap lookup.idx
only passes a single integer through the APIs and activates the element directly, so it is fast.Name
needs all the string work and a hashmap (somewhat equivalent todict
in Python) lookup, then activates the target element. Although the official OpenDSS implementation does not includeidx
for all component types, DSS-Extensions do. See the next section for details.For reading multiple channels from a single Monitor object, prefer using the
ByteStream
property/function or theAsMatrix
extension (even with COM) as noted in a previous section.If you need to mutate an object or read a result and the target DSS class has a dedicated API, prefer using that instead of feeding strings through the
Text.Command
API. The dedicated APIs can change the target fields directly (this is especially true for DSS-Extensions), whileText.Command
feeds the DSS parser first. In the end, using the dedicated APIs can avoid string formatting/interpolation and later parsing back those to integers/floats/etc. altogether.If your analysis requires running multiple scenarios of the same circuit, do try to keep the circuit in memory instead of reloading it. For that, save the list of changes done in the previous analysis step to undo them later, or just restore everything to a known state. This can bring a good performance bonus with medium and large circuits. Tiny circuits may not be affected, but that depends on a lot of factors, including the processor of the machine running the DSS engine.
On Windows:
Avoid circuits fragmented in too many files.
Try to exclude the circuit folders from your antivirus scans (especially real-time scans).
Avoid disk operations in general since NTFS (the most common file system on Windows) is usually slow; if possible, use an API to read the results in memory instead of writing a text report and reading it back. This is not always possible, but many new users don’t explore the API before deciding to use the slow approach of using reports.
If you are analyzing thousands of circuits on DSS-Extensions, you can use the ZIP interface to load circuits from .ZIP-compressed files without requiring extraction.
Property and function table#
A common issue for new users (especially OpenDSSDirect.py users) is that the official OpenDSS documentation and examples usually represent the official COM implementation, as expected.
The following table shows how most (but not all) of the functions and properties are mapped. If the cell contains ()
, it means the field is exposed by a Python function, otherwise, it is a Python property. Each cell links to the relevant documentation page, which in turn can contain links to the official documentation, if available.
As a complement, we added the “API extension” column. If a check mark is present (“✓”), that means the function/property is only available in the DSS-Extensions implementation amd users should beware if cross-compatibility with the official EPRI version is required. If no check mark is present, the COM implementation includes the function or property. That is, if there is no check mark for a row, users can replace “DSS-Python” with “COM implementation” as the API is the same for those cases.
We hope this table can elucidate some common doubts.
Note: As of OpenDSS v9.8.0.1, the Storages
API is implemented, but it’s not exposed for the user (in dss.ActiveCircuit.Storages
), so it’s marked as an extension below.