Usage#

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 it’s 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).

After the installation, you can just import the module and start using it without further steps:

from opendssdirect import dss
print('OpenDSSDirect.py and engine versions:', dss.Version())
OpenDSSDirect.py and engine versions: DSS C-API Library version 0.14.1 revision 8b3da5b576f4dedd16d2bfb88bfb911c0aca3783 based on OpenDSS SVN 3723 [FPC 3.2.2] (64-bit build) MVMULT INCREMENTAL_Y CONTEXT_API PM 20240217013457; License Status: Open 
DSS-Python version: 0.15.2
OpenDSSDirect.py version: 0.9.1

Import a file#

Use the high level interface for automatic error handling, mapping errors to Python exceptions.

This first example should give an error:

dss.Command('Redirect this_file_does_not_exist.dss')
---------------------------------------------------------------------------
DSSException                              Traceback (most recent call last)
Cell In[3], line 1
----> 1 dss.Command('Redirect this_file_does_not_exist.dss')

File /opt/python/lib/python3.12/site-packages/opendssdirect/Text.py:26, in IText.Command(self, *args)
     24 if not isinstance(Value, bytes):
     25     Value = Value.encode(self._api_util.codec)
---> 26 self._check_for_error(self._lib.Text_Set_Command(Value))

File /opt/python/lib/python3.12/site-packages/dss/_cffi_api_util.py:267, in Base._check_for_error(self, result)
    265     error_num = self._errorPtr[0]
    266     self._errorPtr[0] = 0
--> 267     raise DSSException(error_num, self._get_string(self._lib.Error_Get_Description()))
    269 return result

DSSException: (#243) Redirect file not found: "this_file_does_not_exist.dss"

This second example uses a correct file path:

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 multi-line strings.

If you need text output from the Text interface, you can use dss.Text.Result(). In general, if there is a dedicated API for a certain class of component, prefere to use that for performance and safety reasons. Otherwise, for example, you can query the DSS engine:

dss('? Load.634a.kW')
dss.Text.Result()
'160'

Module Loads#

The dss object has many attributes, many of them representing specific DSS interfaces. Check the the API reference for details.

import types

import inspect

for name, obj in inspect.getmembers(dss):
    if not isinstance(obj, (types.MethodType, types.FunctionType)) and not name.startswith('_'):
        print(f'dss.{name}')
dss.ActiveClass
dss.Basic
dss.Bus
dss.CNData
dss.CapControls
dss.Capacitors
dss.Circuit
dss.CktElement
dss.CtrlQueue
dss.DSSCore
dss.DSSException
dss.Element
dss.Error
dss.Executive
dss.Fuses
dss.GICSources
dss.Generators
dss.Isource
dss.LineCodes
dss.LineGeometries
dss.LineSpacings
dss.Lines
dss.LoadShape
dss.Loads
dss.Meters
dss.Monitors
dss.PDElements
dss.PVsystems
dss.Parallel
dss.Parser
dss.Progress
dss.Properties
dss.Reactors
dss.Reclosers
dss.ReduceCkt
dss.RegControls
dss.Relays
dss.Sensors
dss.Settings
dss.Solution
dss.Storages
dss.SwtControls
dss.TSData
dss.Text
dss.Topology
dss.Transformers
dss.Vsources
dss.WireData
dss.XYCurves
dss.YMatrix
dss.ZIP
dss.dss
dss.dss_ffi
dss.dss_lib
dss.utils

Each class or submodule has functions that can be called. Many of them mirror the properties exposed in the official COM interface. For those, the documentation tries to provide links to the official OpenDSS site. Many other functions are exclusive to DSS-Extensions and are marked as “API Extension” in the docs.

for name, function in inspect.getmembers(dss.Loads):
    if callable(function) and not name.startswith('_'):
        print(f'dss.Loads.{name}')
dss.Loads.AllNames
dss.Loads.AllocationFactor
dss.Loads.CFactor
dss.Loads.CVRCurve
dss.Loads.CVRvars
dss.Loads.CVRwatts
dss.Loads.Class
dss.Loads.Count
dss.Loads.Daily
dss.Loads.Duty
dss.Loads.First
dss.Loads.Growth
dss.Loads.Idx
dss.Loads.IsDelta
dss.Loads.Model
dss.Loads.Name
dss.Loads.Next
dss.Loads.NumCust
dss.Loads.PF
dss.Loads.PctMean
dss.Loads.PctStdDev
dss.Loads.Phases
dss.Loads.RelWeighting
dss.Loads.Rneut
dss.Loads.Sensor
dss.Loads.Spectrum
dss.Loads.Status
dss.Loads.Vmaxpu
dss.Loads.VminEmerg
dss.Loads.VminNorm
dss.Loads.Vminpu
dss.Loads.XfkVA
dss.Loads.Xneut
dss.Loads.Yearly
dss.Loads.ZipV
dss.Loads.kV
dss.Loads.kVABase
dss.Loads.kW
dss.Loads.kWh
dss.Loads.kWhDays
dss.Loads.kvar
dss.Loads.puSeriesRL

For example, Loads.kW is available in the official OpenDSS COM API…

? dss.Loads.kW

…but Loads.Phases is not:

? dss.Loads.Phases

It is important to understand that OpenDSSDirect.py uses function calls instead of Python properties to expose the API. So, to get the value of a property, just call the function without arguments:

dss.Loads.AllNames()
['671',
 '634a',
 '634b',
 '634c',
 '645',
 '646',
 '692',
 '675a',
 '675b',
 '675c',
 '611',
 '652',
 '670a',
 '670b',
 '670c']
dss.Loads.Name()
'670c'
dss.Loads.kW()
117.0

Now, if you want to set the value of a property, pass the value as argument:

dss.Loads.kW(125)
dss.Loads.kW()
125.0

To get all kW, one must iterate over all the loads. v0.9+ support Python iterators, so a simple for will do. Remember that OpenDSS API restrictions still apply – a simple object of this type must be active. Check AltDSS-Python for an alternative.

Iterators and a few other features are only available when using classes instead of submodules.

for load in dss.Loads:
    print(
        'Name={name} \t kW={kW}'.format(
            name=load.Name(), 
            kW=load.kW()
        )
    )
Name=671 	 kW=1155.0
Name=634a 	 kW=160.0
Name=634b 	 kW=120.0
Name=634c 	 kW=120.0
Name=645 	 kW=170.0
Name=646 	 kW=230.0
Name=692 	 kW=170.0
Name=675a 	 kW=485.0
Name=675b 	 kW=68.0
Name=675c 	 kW=290.0
Name=611 	 kW=170.0
Name=652 	 kW=128.0
Name=670a 	 kW=17.0
Name=670b 	 kW=66.0
Name=670c 	 kW=125.0

List comprehensions also work great:

[l.kW() for l in dss.Loads]
[1155.0,
 160.0,
 120.0,
 120.0,
 170.0,
 230.0,
 170.0,
 485.0,
 68.0,
 290.0,
 170.0,
 128.0,
 17.0,
 66.0,
 125.0]

The (now deprecated) Iterator class from the utils module provides a similar option in pre-v0.9 versions. As seen below, it has greater overhead, so prefer the usual Python iteration for new code.

from opendssdirect.utils import Iterator
%timeit load_kW = [i() for i in Iterator(dss.Loads, 'kW')]
<magic-timeit>:1: DeprecationWarning: OpenDSSDirect.py's Iterator is deprecated; you can use native Python iterators directly now.
5.44 µs ± 64.3 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
%timeit load_kW = [l.kW() for l in dss.Loads]
3.56 µs ± 8.4 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)

Low-level API (advanced topic)#

If you are a new OpenDSS user, you are not expected to use the low-level interface, i.e. the dss_lib and dss_ffi objects are for advanced users only, and may change without warning.

The low-level interface exposes DSS C-API as wrapped in DSS-Python. It’s useful to prototype low-level operations in Python before porting to C, which is not something most of the OpenDSS users will ever do.

Still, in some rare occasions, it might be useful to use the low-level interface. Beware that error checking needs to done by the user in this case. The reference for the low-level API is the source code of the DSS engine itself and the C header, e.g.

Advanced knowledge of DSS C-API, pointers, and the CFFI module are required.

dss.dss_lib.Text_Set_Command('Redirect ./../../tests/data/13Bus/IEEE13Nodeckt.dss'.encode())

Each of the properties from the official COM implementation of OpenDSS is implemented as a pair of functions. While in OpenDSSDirect.py one can use, e.g., dss.Loads.kW() to read the active load element kW and dss.Loads.kW(some_value) to set the kW value, the low-level interface exposes dss.dss_lib.Loads_Get_kW() and dss.dss_lib.Loads_Set_kW().

dss.Loads.Name()
'670c'
dss.dss_lib.Loads_Get_kW()
117.0
dss.dss_lib.Loads_Set_kW(120)
dss.Loads.kW()
120.0