cloudpss 4.0.0a7__py3-none-any.whl → 4.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,120 @@
1
+ import logging
2
+ from .jobReceiver import JobReceiver
3
+ import os
4
+ from urllib.parse import urlparse
5
+ import websocket
6
+ import pytz
7
+ import threading
8
+ import time
9
+ utc_tz = pytz.timezone('UTC')
10
+
11
+ from ..utils.IO import IO
12
+
13
+
14
+ class Message(object):
15
+
16
+ def __init__(self, id, token):
17
+ self.id = id
18
+ self.token = token
19
+
20
+
21
+
22
+ class MessageStreamReceiver(JobReceiver):
23
+
24
+ def __init__(self, job, dev=False):
25
+ super().__init__()
26
+ self.job = job
27
+ self.dev = dev
28
+ self.origin = os.environ.get('CLOUDPSS_API_URL',
29
+ 'https://cloudpss.net/')
30
+ self.__hasOpen=False
31
+
32
+ def receive(self, id, fr0m, on_open, on_message, on_error, on_close):
33
+ """
34
+ 读取消息流中的数据
35
+ id: 消息流id
36
+ fr0m: 从哪个位置开始读取,如果为0则从头开始读取
37
+ on_open: 连接建立时的回调函数
38
+ on_message: 收到消息时的回调函数
39
+ on_error: 发生错误时的回调函数
40
+ on_close: 连接关闭时的回调函数
41
+ """
42
+ if id is None:
43
+ raise Exception('id is None')
44
+ u = list(urlparse(self.origin))
45
+ head = 'wss' if u[0] == 'https' else 'ws'
46
+
47
+ path = head + '://' + str(u[1]) + '/api/streams/id/' + id
48
+ if fr0m is not None:
49
+ path = path + '&from=' + str(fr0m)
50
+ logging.info(f"receive data from websocket: {path}")
51
+ ws = websocket.WebSocketApp(path,
52
+ on_open=on_open,
53
+ on_message=on_message,
54
+ on_error=on_error,
55
+ on_close=on_close)
56
+
57
+ return ws
58
+
59
+ ###下面是兼容Receiver部分功能实现
60
+
61
+ def on_message(self, ws, message):
62
+ data = IO.deserialize(message, 'ubjson')
63
+ msg = IO.deserialize(data['data'], 'ubjson')
64
+ if msg.get('type', None) == 'log' and self.dev:
65
+ print(msg)
66
+ self.messages.append(msg)
67
+ # if msg and type(msg) is dict and msg.get('type', None) == 'terminate':
68
+ # self.close(ws)
69
+
70
+ def on_error(self, ws, error):
71
+ logging.info("MessageStreamReceiver error")
72
+ msg = {
73
+ 'type': 'log',
74
+ 'verb': 'create',
75
+ 'version': 1,
76
+ 'data': {
77
+ 'level': 'error',
78
+ 'content': "websocket error",
79
+ },
80
+ }
81
+ self.messages.append(msg)
82
+
83
+ def on_close(self, *args, **kwargs):
84
+ logging.info("MessageStreamReceiver close")
85
+ self._status = 0
86
+ msg = {
87
+ 'type': 'log',
88
+ 'verb': 'create',
89
+ 'version': 1,
90
+ 'data': {
91
+ 'level': 'error',
92
+ 'content': "websocket closed",
93
+ },
94
+ }
95
+ self.messages.append(msg)
96
+
97
+ def on_open(self, ws):
98
+ self._status = 1
99
+ self.__hasOpen=True
100
+ pass
101
+
102
+ def close(self, ws):
103
+ self._status = 0
104
+ ws.close()
105
+
106
+ @property
107
+ def end(self):
108
+ return not self._status
109
+
110
+ def connect(self):
111
+ self._status = 0
112
+ self.ws = self.receive(self.job.output, None, self.on_open,
113
+ self.on_message, self.on_error, self.on_close)
114
+
115
+
116
+ thread = threading.Thread(target=self.ws.run_forever, args=(None , None , 6, 3))
117
+ thread.setDaemon(True)
118
+ thread.start()
119
+ while not self.__hasOpen:
120
+ time.sleep(0.2)
@@ -0,0 +1,74 @@
1
+ import sys,os
2
+ import threading
3
+ from urllib.parse import urlparse
4
+ sys.path.append(os.path.join(os.path.dirname(__file__), '../'))
5
+
6
+ import websocket
7
+
8
+ from cloudpss.utils.IO import IO
9
+ import time
10
+ import logging
11
+
12
+ class MessageStreamSender():
13
+
14
+ def __init__(self, job, dev=False):
15
+ super().__init__()
16
+ self.job = job
17
+ self.dev = dev
18
+ self.origin = os.environ.get('CLOUDPSS_API_URL',
19
+ 'https://cloudpss.net/')
20
+
21
+ ###下面是兼容Receiver部分功能实现
22
+
23
+ def on_message(self, ws, message):
24
+ logging.info('on_message',message)
25
+
26
+ def on_error(self, ws, error):
27
+ logging.info('on_error')
28
+
29
+ def on_close(self, *args, **kwargs):
30
+ time.sleep(0.5)
31
+ self._status = 0
32
+
33
+ logging.info('on_close')
34
+
35
+ def on_open(self, ws):
36
+ self._status = 1
37
+ logging.info('on_open')
38
+ pass
39
+
40
+ def close(self):
41
+
42
+ self._status = 0
43
+ self.ws.close()
44
+
45
+ @property
46
+ def status(self):
47
+ return self._status
48
+
49
+ def write(self, message):
50
+ data = IO.serialize(message, 'ubjson',None)
51
+ self.ws.send(data, websocket.ABNF.OPCODE_BINARY)
52
+
53
+ def connect(self):
54
+ logging.info('connect')
55
+ self._status = 0
56
+ if self.job.input is None:
57
+ raise Exception('id is None')
58
+ u = list(urlparse(self.origin))
59
+ head = 'wss' if u[0] == 'https' else 'ws'
60
+
61
+ path = head + '://' + str(u[1]) + '/api/streams/token/' + self.job.input
62
+ logging.info(f"receive data from websocket: {path}")
63
+
64
+ self.ws = websocket.WebSocketApp(path,
65
+ on_open=self.on_open,
66
+ on_message=self.on_message,
67
+ on_error=self.on_error,
68
+ on_close=self.on_close)
69
+
70
+ thread = threading.Thread(target=self.ws.run_forever, args=(None , None , 6, 3))
71
+ thread.setDaemon(True)
72
+ thread.start()
73
+ while self.status != 1:
74
+ time.sleep(0.2)
@@ -0,0 +1,216 @@
1
+ import copy
2
+ import uuid
3
+
4
+ from .view import View
5
+ class EMTView(View):
6
+ """
7
+ 电磁暂态结果视图,
8
+
9
+ 提供快捷 plot 数据的接口函数,获取到的 plot 数据为合并后的数据格式,不在是接收时分段的数据
10
+
11
+ 该类只提供 EMT 仿真使用
12
+
13
+ """
14
+
15
+ def getPlots(self):
16
+ '''
17
+ 获取所有的 plots 数据
18
+
19
+ >>> result.getPlots()
20
+ {...}
21
+ '''
22
+ for val in self._receiver:
23
+ if val['type'] == 'plot':
24
+ key = val['key']
25
+ if self.result.get(key, None) is None:
26
+ self.result[key] = copy.deepcopy(val)
27
+ else:
28
+ traces = val['data']['traces']
29
+ for i in range(len(traces)):
30
+ v = traces[i]
31
+ self.result[key]['data']['traces'][i]['x'].extend(
32
+ v['x'])
33
+ self.result[key]['data']['traces'][i]['y'].extend(
34
+ v['y'])
35
+ return self.result.values()
36
+
37
+ def getPlot(self, index: int):
38
+ '''
39
+ 获取指定序号的数据分组
40
+
41
+ :params: index 图表位置
42
+
43
+ >>> result.getPlot(0)
44
+ {...}
45
+ '''
46
+ self.getPlots()
47
+ if self.result is not None:
48
+ return self.result.get('plot-{0}'.format(int(index)), None)
49
+
50
+ def getPlotChannelNames(self, index):
51
+ '''
52
+ 获取一组输出分组下的所有通道名称
53
+
54
+ :params: index 输出通道位置
55
+
56
+ :return: 通道名称数组
57
+
58
+ >>>names= result.getPlotChannelNames(0)
59
+ []
60
+ '''
61
+ plot = self.getPlot(int(index))
62
+ if plot is None:
63
+ return None
64
+
65
+ return [val['name'] for val in plot['data']['traces']]
66
+
67
+ def getPlotChannelData(self, index, channelName):
68
+ '''
69
+ 获取一组输出分组下指定通道名称的数据
70
+
71
+ :params: index 输出通道位置
72
+ :params: channelName 输出通道名称
73
+
74
+ :return: 通道数据, 一个 trace 数据
75
+
76
+ >>>channel= result.getPlotChannelData(0,'')
77
+ {...}
78
+ '''
79
+ plot = self.getPlot(int(index))
80
+ if plot is None:
81
+ return None
82
+ for val in plot['data']['traces']:
83
+ if val['name'] == channelName:
84
+ return val
85
+ return val
86
+
87
+ def next(self):
88
+ """
89
+ 前进一个时步
90
+ """
91
+
92
+ self.goto(-1)
93
+
94
+ def goto(self,step):
95
+ """
96
+ 前进到指定时步
97
+ """
98
+ if self._sender is not None:
99
+ self._sender .write({'type': 'debug', 'step': step})
100
+ else:
101
+ raise Exception('sender is None')
102
+
103
+ def writeShm(self,path,buffer,offset):
104
+ """
105
+ 写内存接口
106
+ """
107
+ if self._sender is not None:
108
+ self._sender.write({'type': 'memory', 'path': path,'value':buffer,'offset':offset})
109
+ else:
110
+ raise Exception('transmitter is None')
111
+
112
+ def _writeEvent(self,eventType,eventTime,eventTimeType,defaultApp):
113
+ if self._sender is None:
114
+ raise Exception('transmitter is None')
115
+ event = {
116
+ 'eventType': eventType,
117
+ 'eventTime': eventTime,
118
+ 'eventTimeType':eventTimeType,
119
+ "defaultApp": defaultApp
120
+ }
121
+ self._sender.write({'type': 'eventchain', 'event': [event]})
122
+
123
+ def stopSimulation(self):
124
+ """
125
+ 停止仿真
126
+ """
127
+ param = {
128
+ "ctrl_type": "0",
129
+ "uuid": str(uuid.uuid1()),
130
+ 'message': {
131
+ 'log': '停止任务 ',
132
+ }
133
+ }
134
+ eventData = {}
135
+ eventData = {'SimuCtrl': param}
136
+
137
+ self._writeEvent('time','-1','1',{'SimuCtrl': eventData})
138
+
139
+
140
+ def _snapshotControl(self,ctrlType,snapshotNumber,log):
141
+ """
142
+ 断面控制
143
+ """
144
+ param = {
145
+ "ctrl_type": ctrlType,
146
+ "snapshot_number": snapshotNumber,
147
+ "uuid": str(uuid.uuid1()),
148
+ 'message': {
149
+ 'log': log
150
+ },
151
+ }
152
+ eventData = {}
153
+ eventData = {'SnapshotCtrl': param}
154
+ self._writeEvent('time','-1','1',{'SnapshotCtrl': eventData})
155
+
156
+ def saveSnapshot(self,snapshotNumber,log='保存断面成功'):
157
+ """
158
+ 通过事件链保存断面
159
+ """
160
+ self._snapshotControl('0',snapshotNumber,log)
161
+ def loadSnapshot(self,snapshotNumber,log='加载断面成功'):
162
+ """
163
+ 通过事件链加载断面
164
+ """
165
+ self._snapshotControl('1',snapshotNumber,log)
166
+
167
+
168
+ def control(self,controlParam,eventTime='-1',eventTimeType='1'):
169
+ """
170
+ 控制仿真
171
+ """
172
+
173
+ if type(controlParam) is not list:
174
+ controlParam=[controlParam]
175
+ para={}
176
+
177
+ for param in controlParam:
178
+ para[param['key']]={
179
+ 'Value': {
180
+ 'value': param['value'],
181
+ 'uuid': param['uuid'] if param.get('uuid',None) is not None else str(uuid.uuid1()),
182
+ 'cmd': 'add',
183
+ 'message': param.get('message') if param.get('message',None) is not None else{
184
+ 'log': param.get('log') if param.get('log',None) is not None else '值变化到 '+str(param['value']),
185
+ },
186
+ }
187
+ }
188
+ self._writeEvent('time',eventTime,eventTimeType,{'para': para})
189
+ pass
190
+
191
+ def monitor(self,monitorParam,eventTime='-1',eventTimeType='1'):
192
+
193
+ if type(controlParam) is not list:
194
+ controlParam=[controlParam]
195
+ para={}
196
+ for param in controlParam:
197
+ para[param['key']]={
198
+ 'a': {
199
+ 'uuid':param['uuid'] if param.get('uuid',None) is not None else str(uuid.uuid1()),
200
+ 'function':param['function'],
201
+ 'cmd':'add',
202
+ 'period':param['period'],
203
+ 'value':param['value'],
204
+ 'key':param['key'],
205
+ 'freq':param['freq'],
206
+ 'condition':param['condition'],
207
+ 'cycle':param['cycle'],
208
+ 'nCount':param['nCount'],
209
+ 'message': param.get('message') if param.get('message',None) is not None else{
210
+ 'log': param.get('log') if param.get('log',None) is not None else '消息达到阈值 '+str(param['value']),
211
+ },
212
+ }
213
+ }
214
+ self._writeEvent('time',eventTime,eventTimeType,{'para': para})
215
+
216
+ pass
@@ -0,0 +1,5 @@
1
+ from .IESView import IESView
2
+
3
+
4
+ class IESLabSimulationView(IESView):
5
+ pass
@@ -0,0 +1,27 @@
1
+ from .IESView import IESView
2
+
3
+ class IESLabTypicalDayView(IESView):
4
+ def GetTypicalDayNum():
5
+ '''
6
+ 获取当前result的典型日数量
7
+
8
+ :return: int类型,代表典型日数量
9
+ '''
10
+ def GetTypicalDayInfo(dayID):
11
+ '''
12
+ 获取dayID对应典型日的基础信息
13
+
14
+ :params: dayID int类型,表示典型日的ID,数值位于 0~典型日数量 之间
15
+
16
+ :return: dict类型,代表典型日的基础信息,包括典型日所代表的日期范围、典型日的名称等
17
+ '''
18
+ def GetTypicalDayCurve(dayID, dataType):
19
+ '''
20
+ 获取dayID对应典型日下dataType参数的时序曲线
21
+
22
+ :params: dayID int类型,表示典型日的ID,数值位于 0~典型日数量 之间
23
+ :params: dataType enum类型,标识辐照强度、环境温度、土壤温度、建筑物高度风速、风机高度风速、电负荷、热负荷、冷负荷的参数类型
24
+
25
+ :return: list<float>类型,代表以1h为时间间隔的该参数的日内时序曲线
26
+ '''
27
+ pass
@@ -0,0 +1,103 @@
1
+ import copy
2
+ import collections
3
+
4
+ class IESView(object):
5
+ """
6
+ 综合能源结果视图,
7
+
8
+ 提供快捷 plot 数据的接口函数,获取到的 plot 数据为合并后的数据格式,不在是接收时分段的数据
9
+
10
+ 该类只提供 IES 仿真使用
11
+
12
+ """
13
+
14
+
15
+ def __init__(self,receiver) -> None:
16
+
17
+ self.result = {'Sankey': []}
18
+ self.__receiver = receiver
19
+
20
+
21
+ def __readPlotResult(self):
22
+ for val in self.__receiver:
23
+ if val['type'] == 'plot':
24
+ key = val['key']
25
+ if key == 'Sankey':
26
+ self.result['Sankey'].append(copy.deepcopy(val))
27
+ else:
28
+ if self.result.get(key, None) is None:
29
+ self.result[key] = copy.deepcopy(val)
30
+ else:
31
+ traces = val['data']['traces']
32
+ for i in range(len(traces)):
33
+ v = traces[i]
34
+ self.result[key]['data']['traces'][i][
35
+ 'x'].extend(v['x'])
36
+ self.result[key]['data']['traces'][i][
37
+ 'y'].extend(v['y'])
38
+
39
+ def getPlotData(self, compID, labelName, traceName='all', index=-1):
40
+ '''
41
+ 获取元件ID为compID的元件,对应标签为labelName、图例名称为traceName的plot 数据的第index项
42
+
43
+ :params: compID string类型,代表元件的标识符
44
+ :params: labelName string类型,代表plot曲线的分组标签
45
+ :params: traceName string类型,代表Plot曲线对应分组下的图例名称,当为'all'时,返回所有图例的数据
46
+ :params: index int类型,代表对应图例时序数据中的第index项,当小于0时,返回该图例所有的时序数据
47
+
48
+ :return: dict类型
49
+ '''
50
+ self.__readPlotResult()
51
+ key = compID + '_' + labelName
52
+ if key not in self.result.keys():
53
+ raise Exception('未找到元件标志为{0},对应label为{1}的plot数据'.format(
54
+ compID, labelName))
55
+
56
+ traceData = self.result[key]['data']['traces']
57
+ if traceName != 'all':
58
+ traceNameList = [traceName]
59
+ else:
60
+ traceNameList = [
61
+ traceData[i]['name'] for i in range(len(traceData))
62
+ ]
63
+
64
+ startIndex = 0
65
+ endIndex = len(traceData[0]['x'])
66
+ if index >= 0:
67
+ startIndex = index
68
+ endIndex = index + 1
69
+
70
+ plotData = collections.defaultdict(lambda: {})
71
+ for tName in traceNameList:
72
+ for i in range(len(traceData)):
73
+ dataLen = len(traceData[i]['x'])
74
+ if traceData[i]['name'] == tName:
75
+ if endIndex > dataLen:
76
+ raise Exception('请求的index超过了plot数据序列的长度')
77
+ plotData[tName]['x'] = traceData[i]['x'][
78
+ startIndex:endIndex]
79
+ plotData[tName]['y'] = traceData[i]['y'][
80
+ startIndex:endIndex]
81
+
82
+ return plotData
83
+
84
+
85
+ def getSankey(self, index):
86
+ '''
87
+ 获取第index个桑基图数据
88
+
89
+ >>> result.getSankey(index)
90
+ {...}
91
+ '''
92
+ self.__readPlotResult()
93
+ if index >= len(self.result['Sankey']):
94
+ raise Exception('index超过了桑基图数据序列的长度')
95
+ return self.result['Sankey'][index]
96
+ def getSankeyNum(self):
97
+ '''
98
+ 获取桑基图数据序列的长度
99
+
100
+ >>> result.getSankeyNum()
101
+ '''
102
+ self.__readPlotResult()
103
+ return len(self.result['Sankey'])
@@ -0,0 +1,80 @@
1
+ import copy
2
+ import re
3
+
4
+ from .view import View
5
+
6
+
7
+ class PowerFlowView(View):
8
+ """
9
+ 潮流结果视图,
10
+
11
+ 提供快速获取 buses 和 branches 的接口,并提供潮流写入项目的接口
12
+
13
+ 该类只提供潮流仿真时使用
14
+
15
+ """
16
+
17
+ def getBuses(self):
18
+ """
19
+ 获取所有的 buses 数据
20
+
21
+ >>> view.getBuses()
22
+ [...]
23
+ """
24
+ result = []
25
+ pattern = re.compile("value=\"(.*)\"")
26
+ data = self.getMessagesByKey('buses-table')
27
+ if len(data) == 0:
28
+ return []
29
+ table = copy.deepcopy(data[0])
30
+ columns = table['data']['columns']
31
+ for column in columns:
32
+ if column['type'] == 'html':
33
+ data = column['data']
34
+ for i in range(len(data)):
35
+ if data[i] != '':
36
+ s = pattern.findall(data[i])
37
+ data[i] = s[0].replace('/', '')
38
+
39
+ result.append(table)
40
+ return result
41
+
42
+
43
+ def getBranches(self):
44
+ """
45
+ 获取潮流结果 branches 数据
46
+
47
+ >>> view.getBranches()
48
+ [...]
49
+ """
50
+
51
+ result = []
52
+ pattern = re.compile("value=\"(.*)\"")
53
+ data = self.getMessagesByKey('branches-table')
54
+ if len(data) == 0:
55
+ return []
56
+ table = copy.deepcopy(data[0])
57
+
58
+ columns = table['data']['columns']
59
+ for column in columns:
60
+ if column['type'] == 'html':
61
+ data = column['data']
62
+ for i in range(len(data)):
63
+ if data[i] != '':
64
+ s = pattern.findall(data[i])
65
+ data[i] = s[0].replace('/', '')
66
+
67
+ result.append(table)
68
+ return result
69
+
70
+ def powerFlowModify(self, model):
71
+ """
72
+ 潮流数据写入 model
73
+
74
+ >>> view.powerFlowModify(model)
75
+ """
76
+
77
+ data = self.getMessagesByKey('power-flow-modify')
78
+ if len(data) == 0:
79
+ raise Exception('未找到到数据')
80
+ self.modify(data[0], model)
@@ -0,0 +1,42 @@
1
+ from .IESView import IESView
2
+ from .view import View
3
+ from .EMTView import EMTView
4
+ from .PowerFlowView import PowerFlowView
5
+ from .IESLabSimulationView import IESLabSimulationView
6
+ from .IESLabTypicalDayView import IESLabTypicalDayView
7
+ from ..messageStreamReceiver import MessageStreamReceiver
8
+ from ..messageStreamSender import MessageStreamSender
9
+ __all__ = [
10
+ 'View','EMTView','PowerFlowView','IESLabSimulationView','IESView','IESLabTypicalDayView'
11
+ ]
12
+
13
+ VIEW = {
14
+ 'function/CloudPSS/emtp': EMTView,
15
+ 'function/CloudPSS/emtps': EMTView,
16
+ 'function/CloudPSS/sfemt': EMTView,
17
+ 'function/CloudPSS/power-flow': PowerFlowView,
18
+ 'function/CloudPSS/ies-simulation': IESView,
19
+ 'function/CloudPSS/ies-optimization': IESView,
20
+ 'function/ies/ies-optimization': IESView,
21
+ 'function/CloudPSS/three-phase-powerFlow': PowerFlowView,
22
+ 'function/ies/ies-simulation': IESLabSimulationView,
23
+ 'function/ies/ies-gmm':IESLabTypicalDayView,
24
+ 'function/CloudPSS/ieslab-simulation': IESLabSimulationView,
25
+ 'function/CloudPSS/ieslab-gmm':IESLabTypicalDayView,
26
+ 'function/CloudPSS/ieslab-optimization': IESView,
27
+ }
28
+
29
+
30
+ def getViewClass(rid: str) -> View:
31
+ """
32
+ 获取仿真结果视图
33
+
34
+ :param rid: 仿真任务的 rid
35
+ :param db: 仿真任务的数据库
36
+
37
+ :return: 仿真结果视图
38
+
39
+ >>> view = get_view('function/CloudPSS/emtp', db)
40
+ >>> view.getPlots()
41
+ """
42
+ return VIEW.get(rid, View)