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:

[1]:
import opendssdirect as dss
[2]:
print('OpenDSSDirect.py version:', dss.__version__)
print('Engine information:', dss.Basic.Version())
OpenDSSDirect.py version: 0.7.0
Engine information: DSS C-API Library version 0.12.1 revision 7230c9ec1f69ee2f13ac11202ced49ab0b239ebb based on OpenDSS SVN 3460 [FPC 3.2.2] (64-bit build) MVMULT INCREMENTAL_Y CONTEXT_API PM 20220716135355; License Status: Open

Import a file

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

This first example should give an error:

[3]:
dss.Text.Command('Redirect this_file_does_not_exist.dss')
---------------------------------------------------------------------------
DSSException                              Traceback (most recent call last)
/home/meira/Projects/dss/oddpy/docs/notebooks/Example-OpenDSSDirect.py.ipynb Cell 7 in <cell line: 1>()
----> <a href='vscode-notebook-cell:/home/meira/Projects/dss/oddpy/docs/notebooks/Example-OpenDSSDirect.py.ipynb#X36sZmlsZQ%3D%3D?line=0'>1</a> dss.Text.Command('Redirect this_file_does_not_exist.dss')

File ~/bin/python/lib/python3.8/site-packages/opendssdirect/Text.py:17, in Command(*args)
     14     Value = Value.encode(codec)
     16 lib.Text_Set_Command(Value)
---> 17 CheckForError()

File ~/bin/python/lib/python3.8/site-packages/dss/_cffi_api_util.py:128, in Base._check_for_error(self, result)
    126     error_num = self._errorPtr[0]
    127     self._errorPtr[0] = 0
--> 128     raise DSSException(error_num, self._get_string(self._lib.Error_Get_Description()))
    130 return result

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

This second example uses a correct file path:

[4]:
dss.Text.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.

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:

[5]:
dss.Text.Command('? Load.634a.kW')
dss.Text.Result()
[5]:
'160'

Sometimes it might be useful to use the low-level interface. Beware that error checking needs to done by the user in this case.

[6]:
dss.dss_lib.Text_Set_Command('Redirect ./../../tests/data/13Bus/IEEE13Nodeckt.dss'.encode())

Each of the properties from the official COM implementation of OpenDSS are 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().

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.

[7]:
dss.Loads.Name()
[7]:
'670c'
[8]:
dss.dss_lib.Loads_Get_kW()
[8]:
117.0
[9]:
dss.dss_lib.Loads_Set_kW(120)
[10]:
dss.Loads.kW()
[10]:
120.0

Module Loads

The dss module has many submodules

[11]:
import types

import inspect

for name, module in inspect.getmembers(dss):
    if isinstance(module, types.ModuleType) 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.CmathLib
dss.CtrlQueue
dss.DSSCore
dss.DSSEvents
dss.DSSimComs
dss.Element
dss.Error
dss.Executive
dss.Fuses
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_lib
dss.utils

Each submodule has functions that can be called.

[12]:
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.CheckForError
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.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.get_float64_array
dss.Loads.get_string
dss.Loads.get_string_array
dss.Loads.kV
dss.Loads.kVABase
dss.Loads.kW
dss.Loads.kWh
dss.Loads.kWhDays
dss.Loads.kvar
dss.Loads.prepare_float64_array
dss.Loads.puSeriesRL
[13]:
dss.Loads.AllNames()
[13]:
['671',
 '634a',
 '634b',
 '634c',
 '645',
 '646',
 '692',
 '675a',
 '675b',
 '675c',
 '611',
 '652',
 '670a',
 '670b',
 '670c']
[14]:
dss.Loads.Name()
[14]:
'670c'
[15]:
dss.Loads.kW()
[15]:
120.0

To get all kW, one must iterate over all the loads

[16]:
dss.Loads.First()

while True:

    print(
        'Name={name} \t kW={kW}'.format(
            name=dss.Loads.Name(),
            kW=dss.Loads.kW()
        )
    )

    if not dss.Loads.Next() > 0:
        break
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=120.0

The utils module provides an Iterator class as a helper function

[17]:
from opendssdirect.utils import Iterator
[18]:
load_kW = [i() for i in Iterator(dss.Loads, 'kW')]
[19]:
load_kW
[19]:
[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,
 120.0]
[20]:
for i in Iterator(dss.Loads, 'Name'):

    print(
        'Name={name} \t kW={kW}'.format(
            name=i(),
            kW=dss.Loads.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=120.0