Skip to content

Commit 85783cf

Browse files
committed
Initial Oddie integration, including updates to tests
1 parent e4c8dfb commit 85783cf

13 files changed

+514
-118
lines changed

dss/IBus.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,13 @@ def __call__(self, index: Union[int, str]) -> IBus:
463463
return self.__getitem__(index)
464464

465465
def __iter__(self) -> Iterator[IBus]:
466+
if self._api_util._is_odd:
467+
for i in range(self._lib.Circuit_Get_NumBuses()):
468+
self._check_for_error(self._lib.Circuit_SetActiveBusi(i))
469+
yield self
470+
471+
return
472+
466473
n = self._check_for_error(self._lib.Circuit_SetActiveBusi(0))
467474
while n == 0:
468475
yield self

dss/ICircuit.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -206,14 +206,14 @@ def __init__(self, api_util):
206206
self.PVSystems = IPVSystems(api_util)
207207
self.Vsources = IVsources(api_util)
208208
self.LineCodes = ILineCodes(api_util)
209-
self.LineGeometries = ILineGeometries(api_util)
210-
self.LineSpacings = ILineSpacings(api_util)
211-
self.WireData = IWireData(api_util)
212-
self.CNData = ICNData(api_util)
213-
self.TSData = ITSData(api_util)
214-
self.Reactors = IReactors(api_util)
209+
self.LineGeometries = ILineGeometries(api_util) if not api_util._is_odd else None
210+
self.LineSpacings = ILineSpacings(api_util) if not api_util._is_odd else None
211+
self.WireData = IWireData(api_util) if not api_util._is_odd else None
212+
self.CNData = ICNData(api_util) if not api_util._is_odd else None
213+
self.TSData = ITSData(api_util) if not api_util._is_odd else None
214+
self.Reactors = IReactors(api_util) if not api_util._is_odd else None
215215
self.ReduceCkt = IReduceCkt(api_util) #: Circuit Reduction Interface
216-
self.Storages = IStorages(api_util)
216+
self.Storages = IStorages(api_util) if not api_util._is_odd else None
217217
self.GICSources = IGICSources(api_util)
218218

219219
if hasattr(api_util.lib, 'Parallel_CreateActor'):

dss/IDSS.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,14 +134,14 @@ def __init__(self, api_util):
134134
self.Executive = IDSS_Executive(api_util)
135135

136136
#: Kept for compatibility.
137-
self.Events = IDSSEvents(api_util)
137+
self.Events = IDSSEvents(api_util) if not api_util._is_odd else None
138138

139139
#: Kept for compatibility.
140140
self.Parser = IParser(api_util)
141141

142142
#: Kept for compatibility. Apparently was used for DSSim-PC (now OpenDSS-G), a
143143
#: closed-source software developed by EPRI using LabView.
144-
self.DSSim_Coms = IDSSimComs(api_util)
144+
self.DSSim_Coms = IDSSimComs(api_util) if not api_util._is_odd else None
145145

146146
#: The YMatrix interface provides advanced access to the internals of
147147
#: the DSS engine. The sparse admittance matrix of the system is also
@@ -157,7 +157,7 @@ def __init__(self, api_util):
157157
#: and run scripts inside the ZIP, without creating extra files on disk.
158158
#:
159159
#: **(API Extension)**
160-
self.ZIP = IZIP(api_util)
160+
self.ZIP = IZIP(api_util) if not api_util._is_odd else None
161161

162162
Base.__init__(self, api_util)
163163

@@ -444,6 +444,10 @@ def NewContext(self) -> IDSS:
444444
445445
**(API Extension)**
446446
'''
447+
448+
if self._api_util._is_odd:
449+
raise NotImplementedError("NewContext is not supported for the official OpenDSS engine.")
450+
447451
ffi = self._api_util.ffi
448452
lib = self._api_util.lib_unpatched
449453
new_ctx = ffi.gc(lib.ctx_New(), lib.ctx_Dispose)

dss/Oddie.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
from __future__ import annotations
2+
from typing import Optional
3+
from ._cffi_api_util import CffiApiUtil
4+
from .IDSS import IDSS
5+
from enum import Flag
6+
7+
class OddieOptions(Flag):
8+
MapErrors = 0x01
9+
10+
11+
class IOddieDSS(IDSS):
12+
r'''
13+
The OddieDSS class exposes the official OpenDSSDirect.DLL binary,
14+
as distributed by EPRI, with the same API as the DSS-Python and
15+
the official COM interface object on Windows. It uses AltDSS Oddie
16+
to achieve this.
17+
18+
**Note:** This class requires the backend for Oddie to be included in
19+
the `dss_python_backend` package. If it is not available, an import
20+
error should occur when trying to use this.
21+
22+
AltDSS Oddie wraps OpenDSSDirect.DLL, providing a minimal compatiliby layer
23+
to expose it with the same API as AltDSS/DSS C-API. With it, we can
24+
just reuse most of the tools from the other projects on DSS-Extensions
25+
without too much extra work.
26+
27+
Note that many functions from DSS-Extensions will not be available and/or
28+
will return errors, and this is expected. There are some issues and/or
29+
limitations from OpenDSSDirect.DLL that may or may not affect specific
30+
use cases; check the documentation on https://dss-extensions.org for
31+
more information.
32+
33+
:param library_path: The name or full path of the target dynamic library to
34+
load. Defaults to trying to load "OpenDSSDirect" from `c:\Program Files\OpenDSS\x64`,
35+
followed by trying to load it from the current path.
36+
37+
:param load_flags: Optional, flags to feed the [`LoadLibrary`](https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexa)
38+
(on Windows) or `dlopen` (on Linux and macOS, whenever EPRI supports it). If not provided, a default set will be used.
39+
40+
:param oddie_options: Optional, Oddie configuration flags. If none passed,
41+
the default settings (recommended) will be used. For advanced users.
42+
'''
43+
44+
def __init__(self, library_path: str = '', load_flags: Optional[int] = None, oddie_options: Optional[OddieOptions] = None):
45+
from dss_python_backend import _altdss_oddie_capi
46+
lib = _altdss_oddie_capi.lib
47+
ffi = _altdss_oddie_capi.ffi
48+
NULL = ffi.NULL
49+
50+
c_load_flags = NULL
51+
if load_flags is not None:
52+
c_load_flags = ffi.new('uint32_t*', load_flags)
53+
54+
if library_path:
55+
library_path = library_path.encode()
56+
lib.Oddie_SetLibOptions(library_path, c_load_flags)
57+
ctx = lib.ctx_New()
58+
else:
59+
# Try the default install folder
60+
library_path = rb'C:\Program Files\OpenDSS\x64\OpenDSSDirect.dll'
61+
lib.Oddie_SetLibOptions(library_path, c_load_flags)
62+
ctx = lib.ctx_New()
63+
if ctx == NULL:
64+
# Try from the general path, let the system resolve it
65+
library_path = rb'OpenDSSDirect.dll'
66+
lib.Oddie_SetLibOptions(library_path, c_load_flags)
67+
ctx = lib.ctx_New()
68+
69+
if ctx == NULL:
70+
raise RuntimeError("Could not load the target library.")
71+
72+
if lib.ctx_DSS_Start(ctx, 0) != 1:
73+
raise RuntimeError("DSS_Start call was not successful.")
74+
75+
if oddie_options is not None:
76+
lib.Oddie_SetOptions(oddie_options)
77+
78+
ctx = ffi.gc(ctx, lib.ctx_Dispose)
79+
api_util = CffiApiUtil(ffi, lib, ctx, is_odd=True)
80+
api_util._library_path = library_path
81+
IDSS.__init__(self, api_util)
82+
83+
84+
__all__ = ['IOddieDSS', 'OddieOptions']

dss/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
lib.DSS_SetPropertiesMO(_properties_mo.encode())
1818

1919
from ._cffi_api_util import CffiApiUtil, DSSException, set_case_insensitive_attributes
20-
# from .altdss import Edit
2120
from .IDSS import IDSS
21+
from .Oddie import IOddieDSS, OddieOptions
2222
from .enums import *
2323

2424
DssException = DSSException
@@ -44,4 +44,4 @@
4444
except:
4545
__version__ = '0.0dev'
4646

47-
__all__ = ['dss', 'DSS', 'DSS_GR', 'prime_api_util', 'api_util', 'DSSException', 'patch_dss_com', 'set_case_insensitive_attributes', 'enums', 'Edit']
47+
__all__ = ['dss', 'DSS', 'DSS_GR', 'prime_api_util', 'api_util', 'DSSException', 'patch_dss_com', 'set_case_insensitive_attributes', 'enums', 'IOddieDSS', 'OddieOptions']

dss/_cffi_api_util.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,8 @@ class CffiApiUtil(object):
334334
'''
335335
_ctx_to_util = WeakKeyDictionary()
336336

337-
def __init__(self, ffi, lib, ctx=None):
337+
def __init__(self, ffi, lib, ctx=None, is_odd=False):
338+
self._is_odd = is_odd
338339
self.owns_ctx = True
339340
self.codec = codec
340341
self.ctx = ctx
@@ -466,18 +467,26 @@ def clear_callback(self, step: int):
466467

467468

468469
def register_callbacks(self):
470+
if self._is_odd:
471+
return
472+
469473
mgr = get_manager_for_ctx(self.ctx)
470474
# if multiple calls, the extras are ignored
471475
mgr.register_func(AltDSSEvent.Clear, altdss_python_util_callback)
472476
mgr.register_func(AltDSSEvent.ReprocessBuses, altdss_python_util_callback)
473477

474478
def unregister_callbacks(self):
479+
if self._is_odd:
480+
return
475481
mgr = get_manager_for_ctx(self.ctx)
476482
mgr.unregister_func(AltDSSEvent.Clear, altdss_python_util_callback)
477483
mgr.unregister_func(AltDSSEvent.ReprocessBuses, altdss_python_util_callback)
478484

479485
# The context will die, no need to do anything else currently.
480486
def __del__(self):
487+
if self._is_odd:
488+
return
489+
481490
self.clear_callback(0)
482491
self.clear_callback(1)
483492
self.unregister_callbacks()

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ packages = ["dss"]
2424
name = "dss-python"
2525
dynamic = ["version"]
2626
dependencies = [
27-
"dss_python_backend==0.14.5",
27+
"dss_python_backend==0.14.6a1",
2828
"numpy>=1.21.0",
2929
"typing_extensions>=4.5,<5",
3030
]

tests/_settings.py

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
1+
12
import sys, os
23

4+
import faulthandler
5+
faulthandler.disable()
6+
from dss import DSS, IOddieDSS
7+
faulthandler.enable()
8+
9+
org_dir = os.getcwd()
10+
11+
USE_ODDIE = os.getenv('DSS_PYTHON_ODDIE', None)
12+
if USE_ODDIE:
13+
# print("Using Oddie:", USE_ODDIE)
14+
if USE_ODDIE != '1':
15+
DSS = IOddieDSS(USE_ODDIE)
16+
else:
17+
DSS = IOddieDSS()
18+
19+
os.chdir(org_dir)
20+
21+
322
WIN32 = (sys.platform == 'win32')
423
if os.path.exists('../../electricdss-tst/'):
524
BASE_DIR = os.path.abspath('../../electricdss-tst/')
@@ -25,6 +44,21 @@
2544
#"L!Distrib/IEEETestCases/4wire-Delta/Kersting4wireIndMotor.dss",
2645

2746
test_filenames = '''
47+
Version8/Distrib/Examples/MemoryMappingLoadShapes/ckt24/master_ckt24-mm-csv-p.dss
48+
Version8/Distrib/Examples/MemoryMappingLoadShapes/ckt24/master_ckt24-mm-csv-pq.dss
49+
Version8/Distrib/Examples/MemoryMappingLoadShapes/ckt24/master_ckt24-mm-dbl-p.dss
50+
Version8/Distrib/Examples/MemoryMappingLoadShapes/ckt24/master_ckt24-mm-sng-p.dss
51+
Version8/Distrib/Examples/MemoryMappingLoadShapes/ckt24/master_ckt24-mm-txt-p.dss
52+
Version8/Distrib/Examples/MemoryMappingLoadShapes/ckt24/master_ckt24-mm-txt-pq.dss
53+
Version8/Distrib/Examples/MemoryMappingLoadShapes/ckt24/master_ckt24-nomm.dss
54+
Version8/Distrib/IEEETestCases/4Bus-DY-Bal/4Bus-DY-Bal.DSS
55+
Version8/Distrib/IEEETestCases/4Bus-GrdYD-Bal/4Bus-GrdYD-Bal.DSS
56+
Version8/Distrib/IEEETestCases/4Bus-OYOD-Bal/4Bus-OYOD-Bal.DSS
57+
Version8/Distrib/IEEETestCases/4Bus-OYOD-UnBal/4bus-OYOD-UnBal.dss
58+
Version8/Distrib/IEEETestCases/4Bus-YD-Bal/4Bus-YD-Bal.DSS
59+
Version8/Distrib/IEEETestCases/4Bus-YY-Bal/4Bus-YY-Bal.DSS
60+
Version8/Distrib/IEEETestCases/4Bus-YYD/YYD-Master.DSS
61+
Version8/Distrib/IEEETestCases/13Bus/IEEE13Nodeckt.dss
2862
Test/CapControlFollow.dss
2963
Test/CapacitorConfigs.dss
3064
Test/ReactorConfigs.dss
@@ -47,14 +81,6 @@
4781
L!Version8/Distrib/IEEETestCases/123Bus/IEEE123Master.dss
4882
Version8/Distrib/IEEETestCases/37Bus/ieee37.dss
4983
Version8/Distrib/IEEETestCases/IEEE 30 Bus/Master.dss
50-
Version8/Distrib/IEEETestCases/4Bus-DY-Bal/4Bus-DY-Bal.DSS
51-
Version8/Distrib/IEEETestCases/4Bus-GrdYD-Bal/4Bus-GrdYD-Bal.DSS
52-
Version8/Distrib/IEEETestCases/4Bus-OYOD-Bal/4Bus-OYOD-Bal.DSS
53-
Version8/Distrib/IEEETestCases/4Bus-OYOD-UnBal/4bus-OYOD-UnBal.dss
54-
Version8/Distrib/IEEETestCases/4Bus-YD-Bal/4Bus-YD-Bal.DSS
55-
Version8/Distrib/IEEETestCases/4Bus-YY-Bal/4Bus-YY-Bal.DSS
56-
Version8/Distrib/IEEETestCases/4Bus-YYD/YYD-Master.DSS
57-
Version8/Distrib/IEEETestCases/13Bus/IEEE13Nodeckt.dss
5884
Test/IEEE13_LineSpacing.dss
5985
Test/IEEE13_LineGeometry.dss
6086
Test/IEEE13_Assets.dss
@@ -105,6 +131,9 @@
105131
L!Version8/Distrib/Examples/Microgrid/GridFormingInverter/GFM_IEEE8500/Run_8500Node_GFMDailySmallerPV.dss
106132
L!Version8/Distrib/Examples/Microgrid/GridFormingInverter/GFM_IEEE8500/Run_8500Node_GFMSnap.dss
107133
L!Version8/Distrib/Examples/Microgrid/GridFormingInverter/GFM_IEEE8500/Run_8500Node_Unbal.dss
134+
L!Version8/Distrib/Examples/Microgrid/GridFormingInverter/GFM_AmpsLimit_123/Run_IEEE123Bus_GFMDaily.DSS
135+
L!Version8/Distrib/Examples/Microgrid/GridFormingInverter/GFM_AmpsLimit_123/Run_IEEE123Bus_GFMDailySwapRef.DSS
136+
L!Version8/Distrib/Examples/Microgrid/GridFormingInverter/GFM_AmpsLimit_123/Run_IEEE123Bus_GFMSnap.DSS
108137
L!Version8/Distrib/IEEETestCases/123Bus/RevRegTest.dss
109138
L!Version8/Distrib/Examples/IBRDynamics_Cases/GFM_IEEE123/Run_IEEE123Bus_GFMDaily.DSS
110139
L!Version8/Distrib/Examples/IBRDynamics_Cases/GFM_IEEE123/Run_IEEE123Bus_GFMDaily_CannotPickUpLoad.DSS

0 commit comments

Comments
 (0)