OpenDSS COM/classic APIs#

Main concepts of the classic OpenDSS APIs

This document is intended to expose some concepts that are typically overlook by many users when using the classic OpenDSS API.

Traditionally, all OpenDSS interfaces and bindings use the same concepts of interaction with the DSS engine. That includes most of the projects developed under DSS-Extensions,

On DSS-Extensions, an alternative API is being developed to complement and/or provide a difference experience. Currently, the main results are represented in the AltDSS-Python project.

To avoid generating confusion, we will use the term “classic API” to indicate a set of APIs that follow the concepts developed in the official OpenDSS COM implementation, and “Alt” or alternative API for whatever is being developed in AltDSS-Python and similar projects, such as dss.hpp.

OpenDSS has models for many electric components and controllers, plus general data classes. Besides those, there are several interfaces to interact with specific aspects of the engine. The official documentation and other training material usually covers those in detail.

API Hierarchy#

The API tree as seen from walking from the main interface class IDSS, which is exposed from OpenDSSengine.DSS in the COM interface, is as follows.

For EPRI’s OpenDSS implementation, the API materialize in the OpenDSSengine.DSS COM object exposes the OpenDSS engine, which is a global singleton in the engine side. That means that the same engine is exposed even if you create another COM object. On DSS-Extensions, the default instance is also created by default for compatibility with the global singleton, but users can create multiple engines by using the dss.NewContext() method.

Items marked with “API Extension” are not present in the COM API, but available as extras on DSS-Extensions.

  • dss (IDSS)

    • (dss.)ActiveClass (IActiveClass): Manipulate DSS classes directly. For example, if a class doesn’t have a dedicated API, this can be used to access the element properties. This is the same as “dss.ActiveCircuit.ActiveClass”.

    • (dss.)DSSProgress (IDSSProgress): Manipulate the progress status, GUI etc. Using this is uncommon. Not fully supported on DSS-Extensions without custom callbacks.

    • (dss.)DSSim_Coms (IDSSimComs): Using this is uncommon. Also exposed as “dss.ActiveCircuit.DSSim_Coms”.

    • (dss.)Error (IError): The error interface lists error numbers and descriptions. On the official OpenDSS, users need to check this manually if AllowForms is disabled. On DSS-Extensions, most errors are directly mapped to errors or exceptions, depending on the programming language.

    • (dss.)Events (IDSSEvents): The event interface allows using event callbacks that are triggered in certain points of the solution process. Using this is uncommon.

    • (dss.)Executive (IDSS_Executive): Lists the available commands and options, help strings. Allow manipulating options. Using this is uncommon.

    • (dss.)Parser (IParser): Parsing utilities. Using this is uncommon.

    • (dss.)Text (IText): Pass DSS commands and get results from the DSS engine.

    • (dss.)ZIP (IZIP) (API Extension): Allow loading circuits from ZIP files, without extracting files to the disk.

    • (dss.)ActiveCircuit (ICircuit): this is effectively the same as Circuits (omitted). Contains many properties and methods to access the circuit state, plus the interfaces below.

      • General engine and circuit operations

      • (dss.ActiveCircuit.)CtrlQueue (ICtrlQueue): Inspect or manipulate the control event queue.

      • (dss.ActiveCircuit.)Parallel (IParallel): OpenDSS Parallel-machine features. Using this is uncommon. On DSS-Extensions, prefer native multithreading couple with dss.NewContext() instead.

      • (dss.ActiveCircuit.)ReduceCkt (IReduceCkt): Circuit reduction operations.

      • (dss.ActiveCircuit.)Topology (ITopology): Get general information like loops and isolated elements; iterate over the circuit graph. Requires an EnergyMeter.

      • (dss.ActiveCircuit.)Settings (ISettings): Exposes some of the settings for the circuit, some for the whole engine. Some more options/settings are also directly in the main IDSS class.

      • (dss.ActiveCircuit.)Solution (ISolution): Solution manipulation, including settings and general state.

      • Bus operations

      • (dss.ActiveCircuit.)ActiveBus (IBus): Manipulate a specific bus. This is effectively the same as Buses (omitted).

      • Base element operations

      • (dss.ActiveCircuit.)ActiveCktElement (ICktElement): This is effectively the same as ActiveElement and CktElements (omitted).

        • (dss.ActiveCircuit.ActiveCktElement.)Properties (IDSSProperty): Manipulate the DSS properties of the active element.

      • (dss.ActiveCircuit.)ActiveClass (IActiveClass): Same as “dss.ActiveClass”.

      • (dss.ActiveCircuit.)ActiveDSSElement (IDSSElement)

        • (dss.ActiveCircuit.ActiveDSSElement.)Properties (IDSSProperty)

      • (dss.ActiveCircuit.)PDElements (IPDElements)

      • Dedicated interfaces for specific elements

      • (dss.ActiveCircuit.)CNData (ICNData) (API Extension)

      • (dss.ActiveCircuit.)CapControls (ICapControls)

      • (dss.ActiveCircuit.)Capacitors (ICapacitors)

      • (dss.ActiveCircuit.)Fuses (IFuses)

      • (dss.ActiveCircuit.)GICSources (IGICSources)

      • (dss.ActiveCircuit.)Generators (IGenerators)

      • (dss.ActiveCircuit.)Isources (IISources)

      • (dss.ActiveCircuit.)LineCodes (ILineCodes)

      • (dss.ActiveCircuit.)LineGeometries (ILineGeometries) (API Extension)

      • (dss.ActiveCircuit.)LineSpacings (ILineSpacings) (API Extension)

      • (dss.ActiveCircuit.)Lines (ILines)

      • (dss.ActiveCircuit.)LoadShapes (ILoadShapes)

      • (dss.ActiveCircuit.)Loads (ILoads)

      • (dss.ActiveCircuit.)Meters (IMeters)

      • (dss.ActiveCircuit.)Monitors (IMonitors)

      • (dss.ActiveCircuit.)PVSystems (IPVSystems)

      • (dss.ActiveCircuit.)Reactors (IReactors) (API Extension)

      • (dss.ActiveCircuit.)Reclosers (IReclosers)

      • (dss.ActiveCircuit.)RegControls (IRegControls)

      • (dss.ActiveCircuit.)Relays (IRelays)

      • (dss.ActiveCircuit.)Sensors (ISensors)

      • (dss.ActiveCircuit.)Storages (IStorages) (API Extension)

      • (dss.ActiveCircuit.)SwtControls (ISwtControls)

      • (dss.ActiveCircuit.)TSData (ITSData) (API Extension)

      • (dss.ActiveCircuit.)Transformers (ITransformers)

      • (dss.ActiveCircuit.)Vsources (IVsources)

      • (dss.ActiveCircuit.)WireData (IWireData) (API Extension)

      • (dss.ActiveCircuit.)XYCurves (IXYCurves)

The projects OpenDSSDirect.jl and OpenDSSDirect.py follow a slightly different organization (see also Python APIs), but the concepts listed below still apply.

General layout#

Internally, OpenDSS uses object-oriented concepts and, in the API, different classes in the inheritance tree are exposed by different interface classes.

For a more complete view of the classes, as implemented in AltDSS/DSS C-API, see this class diagram, generated from the Pascal code.

A simplified diagram for the Load object follows. In the actual source code, there is TLoad and TLoadObj. The first one is the container/manager class, while the second is the actual individual object.

classDiagram TDSSObject <|-- TDSSCktElement TDSSCktElement <|-- TPCElement TPCElement <|-- TLoadObj

In the classic API, there is no direct inheritance, instead the components of the class hierarchy are exposed as separate interfaces. That is, the ILoads interface (exposed as dss.ActiveCircuit.Loads) does not expose inherited methods and properties from the base interfaces and, consequently, a user could be required to use multiple interfaces in order to access all required properties or methods.

Referring to hierarchy listed in the previous section, for loads one could use:

  • (dss.ActiveCircuit.)Loads (ILoads): Exposes load-specific data and state, somewhat equivalent to the TLoadObj in the class diagram, but ILoads mixes elements from multiple classes.

  • (dss.ActiveCircuit.)ActiveCktElement (ICktElement): This would be equivalent to TDSSCktElement in the diagram.

  • (dss.ActiveCircuit.)ActiveDSSElement (IDSSElement): This would be equivalent to TDSSObject in the diagram.

Note that TPCElement is not exposed in a dedicated interface, but there is (dss.ActiveCircuit.)PDElements for TPDElement.

The “Active…” paradigm#

What is activation? In the context of OpenDSS, activation usually means select a certain object for manipulation at certain collections. The paradigm is used both in the classic API and in the internal code of OpenDSS, although the implementation on DSS-Extensions has been slowly moving away from that concept for the internal code.

One important aspect to consider is that each class with a dedicated interface tracks an active object, and activating one object from the dedicated interfaces also activates it in the parent interfaces.

There is one exception. The Lines API, as implemented in EPRI’s OpenDSS distribution, does not track the lines by itself. Instead, it relies on the more general “ActiveCktElement”. Unfortunately, any operation that changes the active circuit element then results in the Lines interface losing track of the current line. This was changed on DSS-Extensions to make the Lines API work like the other APIs, but there is a compatibility flag that could be used if required.

For example, activating a Load by name (dss.ActiveCircuit.Loads.Name = "target_load_name"), iteration (dss.ActiveCircuit.Loads.First, dss.ActiveCircuit.Loads.Next) or index (dss.ActiveCircuit.Loads.idx = a_valid_integer_index) through the Loads interface:

  • Sets the target as the active load in the Loads interface.

  • Does not set “ActiveDSSClass” to the Load class.

  • Sets the target as the active circuit element (“ActiveCktElement”).

  • Sets the target as the active DSS object (“ActiveDSSElement”).

But activating a Load from a more general interface does not activate it on all interfaces.

Using dss.ActiveCircuit.SetActiveElement("Load.target_load_name") or the Select DSS command (dss.Text.Command = "Select Load.target_load_name"):

  • Does not set the target as the active load in the Loads interface.

  • Sets “ActiveDSSClass” to the Load class.

  • Sets the target as the active circuit element (“ActiveCktElement”).

  • Sets the target as the active DSS object (“ActiveDSSElement”).

SetActiveElement is specific to circuit elements, other general elements like LoadShape and Spectrum objects cannot be used with it. The Select DSS command, on the other hand, can be used with any DSS object.

If “ActiveDSSClass” is set to the Load class, activating an element (dss.ActiveCircuit.ActiveClass.Name = "target_load_name"):

  • Does not set the target as the active load in the Loads interface.

  • The “ActiveDSSClass” is kept as the Load class.

  • Sets the target as the active circuit element (“ActiveCktElement”).

  • Sets the target as the active DSS object (“ActiveDSSElement”).

Important takeaways:

  • Only when activating the element through the dedicated interfaces activates it there.

  • Most ways to activate elements sets “ActiveCktElement” and “ActiveDSSElement”. A user can only interact with an element at a time through the more general interfaces.

  • With the exception of Lines in EPRI’s official OpenDSS, one can safely use the dedicated interfaces to access some of the data/state. For example, it is safe to use the Loads interface and the Transformers interface in succession without reactivating the elements.

  • The PDElements interface uses the current ActiveCktElement, so anything that changes that will affect the PDElements interface too.

  • Other DSS commands can iterate through the internal collections and can change the active elements to point to something else.

Since what is activated changes depending on the method used, if the object has a dedicated collection interface, it is recommended that users activate the object through this interface. Sometimes it also useful to activate the collection class using dss.Circuit.SetActiveClass("load") or equivalent to complement it, especially for classes that do not expose all relevant properties.

The “ActiveDSSClass” interface does not track the active element as a dedicated reference. Instead, it uses the current “ActiveDSSObject”, which can lead to some ill-defined scenarios. For example, it the “Capacitor” class is set active and then a Load is activated, the “ActiveClassName” can still be help as “Capacitor”, but the element name return from “ActiveDSSClass.Name” is the Load name. See the code snippets for an example.

Buses#

Like the DSS objects, only a single bus can be activated and references cannot be help since the bus interfaces always reference to the active bus.

Code snippets#

Here are some code snippets in Python for illustration.

# If you wish to use comtypes and the official COM DLL, uncomment this block and comment the others in this cell.
# import comtypes.client
# dss = comtypes.client.CreateObject("OpenDSSengine.DSS")

# If you wish to use win32com and the official COM DLL, uncomment this block and comment the others in this cell.
# import win32com.client
# dss = win32com.client.gencache.EnsureDispatch("OpenDSSengine.DSS")

# If you wish to use DSS-Python, uncomment this block and comment the others in this cell.
from dss import dss
# Download the examples
from dss.examples import download_repo_snapshot
BASE_PATH = download_repo_snapshot('.', repo_name='electricdss-tst', use_version=False)
CIRCUIT_DSS = BASE_PATH / 'Version8/Distrib/EPRITestCircuits/ckt24/master_ckt24.dss'
assert CIRCUIT_DSS.exists()
dss.Text.Command = f"redirect '{CIRCUIT_DSS}'"

Let’s create a function to print what’s active:

def print_active(dss):
    print(f"{dss.ActiveCircuit.Loads.Name=}")
    print(f"{dss.ActiveCircuit.ActiveClass.ActiveClassName=}")
    print(f"{dss.ActiveCircuit.ActiveClass.Name=}")
    print(f"{dss.ActiveCircuit.ActiveDSSElement.Name=}")
    print(f"{dss.ActiveCircuit.ActiveCktElement.Name=}")
    print(f"{dss.ActiveCircuit.ActiveBus.Name=}")

Initially, the last element that was created is kept active:

print_active(dss)
dss.ActiveCircuit.Loads.Name='other_feeders'
dss.ActiveCircuit.ActiveClass.ActiveClassName='EnergyMeter'
dss.ActiveCircuit.ActiveClass.Name='fdr_05410'
dss.ActiveCircuit.ActiveDSSElement.Name='EnergyMeter.fdr_05410'
dss.ActiveCircuit.ActiveCktElement.Name='EnergyMeter.fdr_05410'
dss.ActiveCircuit.ActiveBus.Name='sourcebus'

Let’s activate something else:

_  = dss.ActiveCircuit.Capacitors.First
print_active(dss)
dss.ActiveCircuit.Loads.Name='other_feeders'
dss.ActiveCircuit.ActiveClass.ActiveClassName='EnergyMeter'
dss.ActiveCircuit.ActiveClass.Name='cap_g2100pl6500'
dss.ActiveCircuit.ActiveDSSElement.Name='Capacitor.cap_g2100pl6500'
dss.ActiveCircuit.ActiveCktElement.Name='Capacitor.cap_g2100pl6500'
dss.ActiveCircuit.ActiveBus.Name='sourcebus'

Activating the first capacitor, we can see that the previous active load is kept, but the ActiveCktElement has changed.

Let’s also activate the Capacitor class:

dss.ActiveCircuit.SetActiveClass("Capacitor")
print_active(dss)
dss.ActiveCircuit.Loads.Name='other_feeders'
dss.ActiveCircuit.ActiveClass.ActiveClassName='Capacitor'
dss.ActiveCircuit.ActiveClass.Name='cap_g2100pl6500'
dss.ActiveCircuit.ActiveDSSElement.Name='Capacitor.cap_g2100pl6500'
dss.ActiveCircuit.ActiveCktElement.Name='Capacitor.cap_g2100pl6500'
dss.ActiveCircuit.ActiveBus.Name='sourcebus'

We will use this clear_active function to make the effects unambiguous:

def clear_active(dss):
    _  = dss.ActiveCircuit.Capacitors.First
    dss.ActiveCircuit.SetActiveClass("Capacitor")
    dss.ActiveCircuit.Loads.Name = 'other_feeders'
    dss.ActiveCircuit.SetActiveBusi(0)

Now let’s activate one of the loads.

print(dss.ActiveCircuit.Loads.AllNames[:10])
['278071200', '559661200', '928661200', '931661200', '475882200', '710035200', '942206200', '306450100', '804302100', '805302100']

Using either SetActiveElement or select Load.{target_name} leave the previous Load active in the dedicated interface. If users are not aware of this, they can inspect the incorrect object through the Loads interface:

clear_active(dss) # just to make the effects more obvious, not used in real life
dss.ActiveCircuit.SetActiveElement('Load.805302100')
print_active(dss)
dss.ActiveCircuit.Loads.Name='other_feeders'
dss.ActiveCircuit.ActiveClass.ActiveClassName='Load'
dss.ActiveCircuit.ActiveClass.Name='805302100'
dss.ActiveCircuit.ActiveDSSElement.Name='Load.805302100'
dss.ActiveCircuit.ActiveCktElement.Name='Load.805302100'
dss.ActiveCircuit.ActiveBus.Name='sourcebus'
clear_active(dss) # just to make the effects more obvious, not used in real life
dss.Text.Command = 'select Load.805302100'
print_active(dss)
dss.ActiveCircuit.Loads.Name='other_feeders'
dss.ActiveCircuit.ActiveClass.ActiveClassName='Load'
dss.ActiveCircuit.ActiveClass.Name='805302100'
dss.ActiveCircuit.ActiveDSSElement.Name='Load.805302100'
dss.ActiveCircuit.ActiveCktElement.Name='Load.805302100'
dss.ActiveCircuit.ActiveBus.Name='g2000sk5500_n274137_sec_3'
clear_active(dss) # just to make the effects more obvious, not used in real life
dss.ActiveCircuit.SetActiveClass("Load")
dss.ActiveCircuit.ActiveClass.Name = "805302100"
print_active(dss)
dss.ActiveCircuit.Loads.Name='other_feeders'
dss.ActiveCircuit.ActiveClass.ActiveClassName='Load'
dss.ActiveCircuit.ActiveClass.Name='805302100'
dss.ActiveCircuit.ActiveDSSElement.Name='Load.805302100'
dss.ActiveCircuit.ActiveCktElement.Name='Load.805302100'
dss.ActiveCircuit.ActiveBus.Name='sourcebus'

And using the dedicated interface:

clear_active(dss) # just to make the effects more obvious, not used in real life
dss.ActiveCircuit.Loads.Name = "805302100"
print_active(dss)
dss.ActiveCircuit.Loads.Name='805302100'
dss.ActiveCircuit.ActiveClass.ActiveClassName='Capacitor'
dss.ActiveCircuit.ActiveClass.Name='805302100'
dss.ActiveCircuit.ActiveDSSElement.Name='Load.805302100'
dss.ActiveCircuit.ActiveCktElement.Name='Load.805302100'
dss.ActiveCircuit.ActiveBus.Name='sourcebus'

Finally, using both the dedicated interface and the ActiveClass interface, everything is set to the target object. Unless they are using all the related interfaces, users don’t need to do this (there is an overhead), but they should be aware of the behavior.

clear_active(dss) # just to make the effects more obvious, not used in real life
dss.ActiveCircuit.Loads.Name = "805302100"
dss.ActiveCircuit.SetActiveClass("Load")
print_active(dss)
dss.ActiveCircuit.Loads.Name='805302100'
dss.ActiveCircuit.ActiveClass.ActiveClassName='Load'
dss.ActiveCircuit.ActiveClass.Name='805302100'
dss.ActiveCircuit.ActiveDSSElement.Name='Load.805302100'
dss.ActiveCircuit.ActiveCktElement.Name='Load.805302100'
dss.ActiveCircuit.ActiveBus.Name='sourcebus'

Some commands will iterate over the elements and change the active elements:

dss.Text.Command = 'export yprim'
print_active(dss)
dss.ActiveCircuit.Loads.Name='805302100'
dss.ActiveCircuit.ActiveClass.ActiveClassName='Load'
dss.ActiveCircuit.ActiveClass.Name='fdr_05410'
dss.ActiveCircuit.ActiveDSSElement.Name='EnergyMeter.fdr_05410'
dss.ActiveCircuit.ActiveCktElement.Name='EnergyMeter.fdr_05410'
dss.ActiveCircuit.ActiveBus.Name='sourcebus'

Performance#

Although the previous examples use names for activating objects, it is recommended to activate by index whenever possible, since it is faster, especially on DSS-Extensions. Rerun the cell below on your machine to get an idea, but beware that the performance profile may change on high CPU loads. When in doubt, experiment with some variations on scenarios closer to actual workloads.

After a circuit is populated, the indices are stable and can be cached. The performance profile for activating elements by name might get better in future releases of both DSS-Extensions engine (AltDSS/DSS C-API) and the official OpenDSS distribution.

Loads = dss.ActiveCircuit.Loads
SetActiveElement = dss.ActiveCircuit.SetActiveElement
CktElements = dss.ActiveCircuit.CktElements
Text = dss.Text
%timeit Loads.Name = "805302100"
413 ns ± 1.79 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
Loads.Name, Loads.idx, SetActiveElement("Load.805302100")
('805302100', 10, 6079)
%timeit Loads.idx = 10
158 ns ± 1.14 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
%timeit CktElements(6079)
211 ns ± 0.367 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
%timeit SetActiveElement("Load.805302100")
512 ns ± 5.08 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
%timeit Text.Command = "select Load.805302100"
1.32 µs ± 9.78 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

Caching indices#

Here’s an example of caching in Python:

  • Create simple dicts mapping the names to the indices

  • Use those to map names to indices

  • Use the indices in the API

%%time
cename_to_idx = {n: i for (i, n) in zip(range(1, dss.ActiveCircuit.NumCktElements), dss.ActiveCircuit.AllElementNames)}
loadname_to_idx = {n: i for (i, n) in zip(range(1, dss.ActiveCircuit.Loads.Count), dss.ActiveCircuit.Loads.AllNames)}
CPU times: user 4.98 ms, sys: 0 ns, total: 4.98 ms
Wall time: 4.8 ms
%timeit CktElements(cename_to_idx['Load.805302100'])
221 ns ± 0.977 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
%timeit Loads.idx = loadname_to_idx['805302100']
178 ns ± 3.43 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)

Comparing the runtime numbers, one can notice that activating using Loads.idx using the index from the dict is a lot faster than using the names directly.

There are many reasons for that, including:

  • String are the most compute-intensive types to pass through the API, involving potential encoding conversions, copies, etc.

  • Python dicts are quite optimized.

  • Python string can cache hashes.

Again, testing in a real script is important in order to decide if this kind of mechanism is worth the extra code.

Alternative API#

As noted in the introduction, an alternative API is being developed on on DSS-Extensions to complement and/or provide a difference experience. Currently, the main results are represented in the AltDSS-Python project (check the link for more information).

# Run "pip install altdss" if you don't have it installed.
from altdss import altdss
altdss(f"""
    redirect '{CIRCUIT_DSS}'
    solve mode=snapshot
""")

Let’s grab a load:

load = altdss.Load['805302100']

With this reference, we don’t need to worry about what is the active element as in the classic API.

Moreover, the alternative API tries to encapsulate all relevant methods in this object, reproducing some aspects of the internal OpenDSS class hierarchy, as shown in the diagram in the section “General layout”.

Here, we can see that the Load type incorporates both a CircuitElementMixin and a PCElementMixin, besides base DSSObj and the actual Load:

import inspect
inspect.getmro(type(load))
(altdss.Load.Load,
 altdss.DSSObj.DSSObj,
 dss._cffi_api_util.Base,
 altdss.CircuitElement.CircuitElementMixin,
 altdss.PCElement.PCElementMixin,
 object)

One can access the common methods:

load.Voltages(), load.Currents(), load.MaxCurrent(1), load.Powers()
(array([-227.06921598-89.7899522j,    0.         +0.j       ]),
 array([-21.73555919-3.70169494j,  21.73555919+3.70169494j]),
 22.048516476228425,
 array([5.2678514+1.11109385j, 0.       +0.j        ]))

Interact with all DSS properties specific to the loads:

load.Conn, load.Spectrum, load.Bus1, load.kW
(<Connection.wye: 0>,
 <Spectrum.defaultload>,
 'g2000sk5500_n274137_sec_3.2',
 5.195630864681321)

And use the API Extensions:

import json
json.loads(load.to_json())
{'Name': '805302100',
 'Phases': 1,
 'kV': 0.24,
 'Bus1': 'g2000sk5500_n274137_sec_3.2',
 'Status': 'Variable',
 'Model': 4,
 'CVRWatts': 0.8,
 'CVRVars': 3.0,
 'Conn': 'wye',
 'VMinpu': 0.7,
 'Yearly': 'ls_phaseb',
 'AllocationFactor': 1.6463,
 'XfkVA': 3.22035118,
 'PF': 0.98}

Although using names are still valid as an option in many parts of the Alt API, objects can also be manipulated directly:

# this is just the name of the associated spectrum object as string
print(load.Spectrum_str)
defaultload
# This is the actual object
spec = load.Spectrum
spec.Harmonic, spec.Angle
(array([ 1.,  3.,  5.,  7.,  9., 11., 13.]),
 array([  0., 180., 180., 180., 180., 180., 180.]))
load.Daily = 'default'
load.Daily.PMult
array([0.677  , 0.6256 , 0.6087 , 0.5833 , 0.58028, 0.6025 , 0.657  ,
       0.7477 , 0.832  , 0.88   , 0.94   , 0.989  , 0.985  , 0.98   ,
       0.9898 , 0.999  , 1.     , 0.958  , 0.936  , 0.913  , 0.876  ,
       0.876  , 0.828  , 0.756  ])

Finally, this Alt API allows manipulating batches of objects and other features:

loads_3ph = altdss.Load.batch(Phases=3)
loads_3ph.Phases()
array([3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
       3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
       3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
       3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
       3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
       3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
       3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
       3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
       3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
       3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
       3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
       3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], dtype=int32)
print(loads_3ph.Powers())
[1.47390882e-01+3.20781834e-02j 1.47458379e-01+3.21332307e-02j
 1.47771846e-01+3.23898394e-02j ... 7.76960599e+03+9.88751954e+02j
 7.76960408e+03+9.88739143e+02j 0.00000000e+00+0.00000000e+00j]

Conclusion#

The classic API implements the “active object” paradigm, requiring some attention to avoid using the wrong data, especially when multiple objects are involved.

The time profile of some activation-related methods and functions may be important for performance critical code.

On DSS-Extensions, an alternative API is being developed to provide a different experience, perhaps complementary since it exposes all DSS objects and can be used together with the classic API, as shown in DSS-Python and OpenDSSDirect.py.

The classic API will be supported on DSS-Extensions as long as required, either to allow better compatibility with EPRI’s OpenDSS, or to allow long-term support of existing third-party software that depend on this API.