Skip to content

Commit d2fd17f

Browse files
committed
Experimental support for OpenDSS-PM (at the moment, a custom patch is provided for FreePascal support) and port COM interface fixes (OpenDSS revision 2134)
1 parent 337d3a6 commit d2fd17f

File tree

10 files changed

+5840
-13
lines changed

10 files changed

+5840
-13
lines changed

README.md renamed to README

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,20 @@ Python bindings and misc tools for using OpenDSS (EPRI Distribution System Simul
66

77
If you are looking for the C API library, see [`dss_capi`](http://github.com/PMeira/dss-capi/).
88

9-
Version 0.9.1, based on [OpenDSS revision 2123](https://sourceforge.net/p/electricdss/code/2123/tree/).
9+
Version 0.9.2, based on [OpenDSS revision 2134](https://sourceforge.net/p/electricdss/code/2123/tree/).
1010
This is a work-in-progress but it's deemed stable enough to be made public.
11+
*Note that, while the interface with OpenDSS is stable (classic version), the OpenDSS-PM (actor-based parallel machine version) interface was integrated recently and is experimental.*
1112

1213
This module mimics the COM structure (as exposed via `win32com` or `comtypes`), effectively enabling multi-platform compatibility at Python level.
1314
Most of the COM documentation can be used as-is, but instead of returning tuples or lists, this modules returns/accepts NumPy arrays for numeric data exchange.
1415

1516
This module depends on CFFI, NumPy and, optionally, SciPy.Sparse for reading the sparse system admittance matrix.
1617

18+
Recent changes
19+
==============
20+
- 2018-02-08: First public release (OpenDSS revision 2123)
21+
- 2018-02-10: Experimental support for OpenDSS-PM (at the moment, a custom patch is provided for FreePascal support) and port COM interface fixes (OpenDSS revision 2134)
22+
1723

1824
Missing features and limitations
1925
================================
@@ -112,6 +118,14 @@ for i in range(len(voltages) // 2):
112118
If you do not need the mixed-cased handling, you can omit the call to `use_com_compat()` and use the casing used in this project.
113119

114120

121+
If you want to play with the experimental OpenDSS-PM interface, it is installed side-by-side and you can import it as:
122+
123+
```
124+
import dss.pm
125+
dss.pm.use_com_compat()
126+
dss_engine = dss.pm.DSS
127+
```
128+
115129
Testing
116130
=======
117131
Since the DLL is built using FreePascal, which is not officially supported by EPRI, the results are validated running sample networks provided in the official OpenDSS distribution. The only modifications are done directly by the script, removing interactive features and some minor other minor issues.

README.pdf

19.5 KB
Binary file not shown.

cffi/dsspm_capi_cffi.h

Lines changed: 1047 additions & 0 deletions
Large diffs are not rendered by default.

dss/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@
77
from .enums import *
88
from . import enums
99

10-
__version__ = '0.9.1'
10+
__version__ = '0.9.2'

dss/dss_capi.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
'''
66
from __future__ import absolute_import
77
from ._dss_capi import lib, ffi
8-
from ._cffi_api_util import *
8+
from ._cffi_api_util import * # one for each version (parallel, classic), already bound to the cffi module
99
import numpy as np
1010

1111
if not freeze:

dss/pm/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
'''A compatibility layer for DSSPM_CAPI that mimics the official OpenDSS COM interface.
2+
3+
Copyright (c) 2016-2018 Paulo Meira
4+
'''
5+
from __future__ import absolute_import
6+
from .dsspm_capi import *
7+
from ..enums import *
8+
from .. import enums
9+
10+
__version__ = '0.9.2'

dss/pm/_cffi_api_util.py

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
from __future__ import absolute_import
2+
from .._dsspm_capi import lib, ffi
3+
import numpy as np
4+
5+
freeze = True
6+
_case_insensitive = False
7+
codec = 'cp1252'
8+
9+
def use_com_compat(value=True):
10+
global _case_insensitive
11+
_case_insensitive = value
12+
13+
def prepare_com_compat(variables):
14+
global freeze
15+
16+
import inspect
17+
old_freeze = freeze
18+
try:
19+
freeze = False
20+
for v in variables.values():
21+
if inspect.isclass(v) and issubclass(v, FrozenClass) and v != FrozenClass:
22+
lowercase_map = {a.lower(): a for a in dir(v) if not a.startswith('_')}
23+
v._dss_atributes = lowercase_map
24+
25+
finally:
26+
freeze = old_freeze
27+
28+
29+
# workaround to make a __slots__ equivalent restriction compatible with Python 2 and 3
30+
class FrozenClass(object):
31+
_isfrozen = False
32+
33+
def __getattr__(self, key):
34+
if key.startswith('_'):
35+
return object.__getattribute__(self, key)
36+
37+
if _case_insensitive:
38+
key = self._dss_atributes.get(key.lower(), key)
39+
40+
return object.__getattribute__(self, key)
41+
42+
43+
def __setattr__(self, key, value):
44+
45+
if _case_insensitive:
46+
okey = key
47+
key = self._dss_atributes.get(key.lower(), None)
48+
if key is None:
49+
raise TypeError( "%r is a frozen class" % self)
50+
51+
if self._isfrozen and not hasattr(self, key):
52+
raise TypeError( "%r is a frozen class" % self )
53+
54+
object.__setattr__(self, key, value)
55+
56+
57+
def get_string(b):
58+
return ffi.string(b).decode(codec)
59+
60+
def get_float64_array(func, *args):
61+
ptr = ffi.new('double**')
62+
cnt = ffi.new('int32_t*')
63+
func(ptr, cnt, *args)
64+
if not cnt[0]:
65+
res = None
66+
else:
67+
res = np.frombuffer(ffi.buffer(ptr[0], cnt[0] * 8), dtype=np.float).copy()
68+
69+
lib.DSS_Dispose_PDouble(ptr)
70+
return res
71+
72+
73+
74+
def get_int32_array(func, *args):
75+
ptr = ffi.new('int32_t**')
76+
cnt = ffi.new('int32_t*')
77+
func(ptr, cnt, *args)
78+
if not cnt[0]:
79+
res = None
80+
else:
81+
res = np.frombuffer(ffi.buffer(ptr[0], cnt[0] * 4), dtype=np.int32).copy()
82+
83+
lib.DSS_Dispose_PInteger(ptr)
84+
return res
85+
86+
87+
def get_int8_array(func, *args):
88+
ptr = ffi.new('int8_t**')
89+
cnt = ffi.new('int32_t*')
90+
func(ptr, cnt, *args)
91+
if not cnt[0]:
92+
res = None
93+
else:
94+
res = np.frombuffer(ffi.buffer(ptr[0], cnt[0] * 1), dtype=np.int8).copy()
95+
96+
lib.DSS_Dispose_PByte(ptr)
97+
return res
98+
99+
100+
def get_string_array(func, *args):
101+
ptr = ffi.new('char***')
102+
cnt = ffi.new('int32_t*')
103+
func(ptr, cnt, *args)
104+
if not cnt[0]:
105+
res = []
106+
else:
107+
actual_ptr = ptr[0]
108+
if actual_ptr == ffi.NULL:
109+
res = []
110+
else:
111+
res = [(str(ffi.string(actual_ptr[i]).decode(codec)) if (actual_ptr[i] != ffi.NULL) else None) for i in range(cnt[0])]
112+
113+
lib.DSS_Dispose_PPAnsiChar(ptr, cnt[0])
114+
return res
115+
116+
117+
118+
def prepare_float64_array(value):
119+
if type(value) is not np.ndarray or value.dtype != np.float64:
120+
value = np.array(value, dtype=np.float64)
121+
122+
ptr = ffi.cast('double*', ffi.from_buffer(data.data))
123+
cnt = data.size
124+
return value, ptr, cnt
125+
126+
def prepare_int32_array(value):
127+
if type(value) is not np.ndarray or value.dtype != np.int32:
128+
value = np.array(value, dtype=np.int32)
129+
130+
ptr = ffi.cast('int32_t*', ffi.from_buffer(data.data))
131+
cnt = data.size
132+
return value, ptr, cnt
133+
134+
135+
def prepare_string_array(value):
136+
raise NotImplementedError
137+
138+
139+
# This might be useful for methods like Get_Channel that are exposed as Channel[] and Channel()
140+
# def arrayfunc(func):
141+
# class ArrayFuncWrapper:
142+
# def __call__(self, v):
143+
# return func(self, v)
144+
145+
# def __getitem__(self, v):
146+
# return func(self, v)
147+
148+
# return ArrayFuncWrapper()
149+

0 commit comments

Comments
 (0)