femagtools 1.7.3__py3-none-any.whl → 1.7.5__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.
- femagtools/__init__.py +1 -1
- femagtools/airgap.py +11 -0
- femagtools/bch.py +32 -21
- femagtools/dxfsl/area.py +1 -0
- femagtools/dxfsl/fslrenderer.py +3 -2
- femagtools/dxfsl/geom.py +39 -1
- femagtools/fsl.py +8 -17
- femagtools/heat_source_network.py +403 -0
- femagtools/machine/pm.py +79 -4
- femagtools/machine/utils.py +109 -51
- femagtools/model.py +12 -1
- femagtools/parstudy.py +4 -5
- femagtools/plot/char.py +5 -2
- femagtools/templates/basic_modpar.mako +2 -0
- femagtools/templates/{therm-static.mako → therm_static.mako} +15 -13
- femagtools/windings.py +5 -2
- {femagtools-1.7.3.dist-info → femagtools-1.7.5.dist-info}/METADATA +2 -2
- {femagtools-1.7.3.dist-info → femagtools-1.7.5.dist-info}/RECORD +24 -22
- {femagtools-1.7.3.dist-info → femagtools-1.7.5.dist-info}/WHEEL +1 -1
- tests/test_heat_source_network.py +21 -0
- tests/test_machine.py +4 -4
- {femagtools-1.7.3.dist-info → femagtools-1.7.5.dist-info}/LICENSE +0 -0
- {femagtools-1.7.3.dist-info → femagtools-1.7.5.dist-info}/entry_points.txt +0 -0
- {femagtools-1.7.3.dist-info → femagtools-1.7.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,403 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
"""
|
3
|
+
heat source network analysis
|
4
|
+
|
5
|
+
@author: werner b. vetter
|
6
|
+
"""
|
7
|
+
|
8
|
+
import xml.etree.ElementTree as ET
|
9
|
+
import numpy as np
|
10
|
+
import json
|
11
|
+
|
12
|
+
|
13
|
+
class HeatSourceNetwork:
|
14
|
+
#Read the json-file with the heat source network definition
|
15
|
+
|
16
|
+
#If you modify the netlist, you must call the command "process()" again
|
17
|
+
|
18
|
+
#Parameter:
|
19
|
+
# filename (str): Filename of heat source network with extension (.hsn)
|
20
|
+
|
21
|
+
def __init__(self, netlist):
|
22
|
+
self.netlist = netlist
|
23
|
+
self.process()
|
24
|
+
|
25
|
+
def get_node_names(self):
|
26
|
+
return [n['Name'] for n in self.netlist['Nodes']]
|
27
|
+
|
28
|
+
def process(self):
|
29
|
+
#Set up independent admittance matrix and the source vector
|
30
|
+
|
31
|
+
self.nodes = []
|
32
|
+
for branch in self.netlist['Branches']:
|
33
|
+
branch_nodes = branch['Nodes']
|
34
|
+
for nd in range(len(branch_nodes)):
|
35
|
+
node = branch_nodes[nd]
|
36
|
+
if not(node in self.nodes):
|
37
|
+
self.nodes.append(node)
|
38
|
+
self.nodes.sort()
|
39
|
+
|
40
|
+
self.G = np.zeros((len(self.nodes)-1,len(self.nodes)-1))
|
41
|
+
self.P = np.zeros(len(self.nodes)-1)
|
42
|
+
|
43
|
+
for branch in self.netlist['Branches']:
|
44
|
+
ind1 = self.nodes.index(branch['Nodes'][0])-1
|
45
|
+
ind2 = self.nodes.index(branch['Nodes'][1])-1
|
46
|
+
if branch['Type'] == 'R_th':
|
47
|
+
if ind1 != -1:
|
48
|
+
self.G[ind1,ind1] = self.G[ind1,ind1] + 1/branch['val']
|
49
|
+
if ind2 != -1:
|
50
|
+
self.G[ind2,ind2] = self.G[ind2,ind2] + 1/branch['val']
|
51
|
+
if ind1 != -1 and ind2 != -1:
|
52
|
+
self.G[ind1,ind2] = self.G[ind1,ind2] - 1/branch['val']
|
53
|
+
self.G[ind2,ind1] = self.G[ind2,ind1] - 1/branch['val']
|
54
|
+
|
55
|
+
if branch['Type'] == 'Power_Source':
|
56
|
+
if ind1 == -1:
|
57
|
+
self.P[ind2] = self.P[ind2] + branch['val']
|
58
|
+
if ind2 == -1:
|
59
|
+
self.P[ind1] = self.P[ind1] + branch['val']
|
60
|
+
|
61
|
+
if branch['Type'] == 'Temperature_Source':
|
62
|
+
if ind1 == -1:
|
63
|
+
self.P[ind2] = self.P[ind2] + branch['T_val']/branch['R_val']
|
64
|
+
self.G[ind2,ind2] = self.G[ind2,ind2] + 1/branch['R_val']
|
65
|
+
if ind2 == -1:
|
66
|
+
self.P[ind1] = self.P[ind1] + branch['T_val']/branch['R_val']
|
67
|
+
self.G[ind1,ind1] = self.G[ind1,ind1] + 1/branch['R_val']
|
68
|
+
|
69
|
+
|
70
|
+
def solve(self):
|
71
|
+
#Solve the system of equations
|
72
|
+
|
73
|
+
self.T = np.linalg.solve(self.G, self.P)
|
74
|
+
return(self.T)
|
75
|
+
|
76
|
+
def draw(self,filename):
|
77
|
+
#Creates an xml file of the network that can be displayed with draw.io
|
78
|
+
|
79
|
+
#Parameter:
|
80
|
+
# filename (str): Filename of diagram with extension (.xml)
|
81
|
+
|
82
|
+
HeatSourceDiagram.draw(self.netlist,filename)
|
83
|
+
return()
|
84
|
+
|
85
|
+
|
86
|
+
def read(filename):
|
87
|
+
with open(filename) as fp:
|
88
|
+
netlist = json.load(fp)
|
89
|
+
return HeatSourceNetwork(netlist)
|
90
|
+
|
91
|
+
|
92
|
+
class HeatSourceDiagram:
|
93
|
+
#Init a diagram to create a diagram of a heat source netlist
|
94
|
+
def __init__(self):
|
95
|
+
self.mxfile = self.createFile()
|
96
|
+
self.diagram = self.addDiagram(self.mxfile)
|
97
|
+
self.graph = self.addGraphModel(self.diagram)
|
98
|
+
self.root = self.addRoot(self.graph)
|
99
|
+
mxCell_dict = {"id":"0"}
|
100
|
+
self.addCell(self.root,mxCell_dict)
|
101
|
+
mxCell_dict = {"id":"1", "parent":"0"}
|
102
|
+
self.addCell(self.root,mxCell_dict)
|
103
|
+
|
104
|
+
|
105
|
+
def writeFile(self,filename):
|
106
|
+
tree = ET.ElementTree(self.mxfile)
|
107
|
+
tree.write(filename, encoding="utf-8")
|
108
|
+
|
109
|
+
|
110
|
+
def createFile(self):
|
111
|
+
mxfile = ET.Element('mxfile')
|
112
|
+
mxfile.attrib = {"host":"Electron",
|
113
|
+
"modified":"2023-03-08T08:47:19.445Z",
|
114
|
+
"agent":"5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.8.16 Chrome/106.0.5249.199 Electron/21.4.0 Safari/537.36",
|
115
|
+
"etag":"5VmoL6vL6aGyHSSOdqAj",
|
116
|
+
"version":"20.8.16",
|
117
|
+
"type":"device"
|
118
|
+
}
|
119
|
+
return mxfile
|
120
|
+
|
121
|
+
def addDiagram(self,parent):
|
122
|
+
diagram = ET.SubElement(parent, 'diagram')
|
123
|
+
diagram.attrib = {"name":"Seite-1",
|
124
|
+
"id":"5XRmyqCaqz2rHzUy15r-"
|
125
|
+
}
|
126
|
+
return diagram
|
127
|
+
|
128
|
+
def addGraphModel(self,parent):
|
129
|
+
mxGraphModel = ET.SubElement(parent, 'mxGraphModel')
|
130
|
+
mxGraphModel.attrib = {"dx":"770",
|
131
|
+
"dy":"569",
|
132
|
+
"grid":"1",
|
133
|
+
"gridSize":"10",
|
134
|
+
"guides":"1",
|
135
|
+
"tooltips":"1",
|
136
|
+
"connect":"1",
|
137
|
+
"arrows":"1",
|
138
|
+
"fold":"1",
|
139
|
+
"page":"1",
|
140
|
+
"pageScale":"1",
|
141
|
+
"pageWidth":"827",
|
142
|
+
"pageHeight":"1169",
|
143
|
+
"math":"0",
|
144
|
+
"shadow":"0"
|
145
|
+
}
|
146
|
+
return mxGraphModel
|
147
|
+
|
148
|
+
def addRoot(self,parent):
|
149
|
+
root = ET.SubElement(parent, 'root')
|
150
|
+
root.attrib = {}
|
151
|
+
return root
|
152
|
+
|
153
|
+
def addCell(self,parent,cell_dict):
|
154
|
+
mxCell = ET.SubElement(parent, 'mxCell')
|
155
|
+
mxCell.attrib = cell_dict
|
156
|
+
return mxCell
|
157
|
+
|
158
|
+
def addGeometry(self,parent,geometry_dict):
|
159
|
+
mxGeometry = ET.SubElement(parent, 'mxGeometry')
|
160
|
+
mxGeometry.attrib = geometry_dict
|
161
|
+
return mxGeometry
|
162
|
+
|
163
|
+
def addPntArray(self,parent,pntArray):
|
164
|
+
array = ET.SubElement(parent, 'Array')
|
165
|
+
array.attrib = {"as":"points"}
|
166
|
+
mxPoint = ET.SubElement(array, 'mxPoint')
|
167
|
+
if isinstance(pntArray[0], list):
|
168
|
+
for i in range(len(pntArray)):
|
169
|
+
pnt = pntArray[i]
|
170
|
+
mxPoint.attrib = {"x":str(pnt[0]), "y":str(pnt[1])}
|
171
|
+
else:
|
172
|
+
pnt = pntArray
|
173
|
+
mxPoint.attrib = {"x":str(pnt[0]), "y":str(pnt[1])}
|
174
|
+
return array
|
175
|
+
|
176
|
+
def addNode(self,nodeKey,x,y):
|
177
|
+
mxCell_dict = {"id":"node_"+str(nodeKey),
|
178
|
+
"value":str(nodeKey),
|
179
|
+
"style":"shape=waypoint;sketch=0;fillStyle=solid;size=6;pointerEvents=1;points=[];fillColor=none;resizable=0;rotatable=0;perimeter=centerPerimeter;snapToPoint=1;verticalAlign=top;spacingBottom=0;spacingTop=-5;fontFamily=Verdana;fontSize=12;",
|
180
|
+
"vertex":"1",
|
181
|
+
"parent":"1"
|
182
|
+
}
|
183
|
+
cell = self.addCell(self.root,mxCell_dict)
|
184
|
+
mxGeometry_dict = {"x":str(x-20),
|
185
|
+
"y":str(y-20),
|
186
|
+
"width":"40",
|
187
|
+
"height":"40",
|
188
|
+
"as":"geometry"
|
189
|
+
}
|
190
|
+
geometry = self.addGeometry(cell,mxGeometry_dict)
|
191
|
+
|
192
|
+
return geometry
|
193
|
+
|
194
|
+
def addPowerSource(self,name,value,x,y,rotation=0):
|
195
|
+
mxCell_dict = {"id":name,
|
196
|
+
"value":"Name = "+name+"\nLosses = "+str(value),
|
197
|
+
"style":"shape=stencil(vVXhboMgEH4a/i4I8QEWur7AHqCh9DZJFcyJ7fb2Q8CtteJWs42YmPsOvrvv44yEi66SLRBGjWyA8A1hbCN2okcE43bPtkfls8xvqGK2oDTG5xizMZZdC8pF8CRRy32dTnYO7RHO+uAShzYVoHZDlj8R+uj3DA8XyhrjSbQ13VXmIu/JpDb+LH2LZKn8e4oeyhi3vkIDDjC1HdEvlQkgbPvjSsW6Smy+EhceyQnmYi/V8RVtbw6z/UVPla0teiC+QzXC+DasZX043FXOw/kLng5Ajjq2llOc0cXFi0VYKZiGtdxVK4fpu0mM6caeIOdHuUg9MtTaXDCU91AEYzINLvi5SlpxV2fz4j5H4j/UQV3rtstbez2txa9N6zp3J858U37e3inJX7gbjt18cgGNf4QAfAA=);whiteSpace=wrap;html=1;labelPosition=center;verticalLabelPosition=bottom;align=center;verticalAlign=top;rotation="+str(rotation)+";",
|
198
|
+
"vertex":"1",
|
199
|
+
"parent":"1"
|
200
|
+
}
|
201
|
+
cell = self.addCell(self.root,mxCell_dict)
|
202
|
+
mxGeometry_dict = {"x":str(x-60),
|
203
|
+
"y":str(y-30),
|
204
|
+
"width":"120",
|
205
|
+
"height":"60",
|
206
|
+
"as":"geometry"
|
207
|
+
}
|
208
|
+
geometry = self.addGeometry(cell,mxGeometry_dict)
|
209
|
+
|
210
|
+
return geometry
|
211
|
+
|
212
|
+
|
213
|
+
def addTemperatureSource(self,name,T,Rth,x,y,rotation=0):
|
214
|
+
mxCell_dict = {"id":name,
|
215
|
+
"value":"Name = "+name+"\nT = "+str(T)+"\nRth = "+str(Rth),
|
216
|
+
"style":"shape=stencil(vVXbboMwDP2avE65jO15YusPdO9TSt0RNSQohHb7+4UEtpaSbGUVERKynRz7HJuAWN6UvAZEseIVIPaMKH2FqgbDbWvgba1bU7gwdTvKECYYB/sYbDrYvKmhsMF54EbwjexPNtboPRzF1vYYQpVghO2i7AXhJ7ene1heaKUciNCqOYucxB0YF8qdxR8BrE//2Vt3WbAdB1GBBdOXHbw/NHsHoqs/ZyLzMtHpTCx3nhhhlm94sX83ulXbyfqCpoWW2jhHePtsiLKVX2l+putVTMPpBo8HIAYdSosxjvBi+U4bmEkY+5Wuqubd9F0EhnClDxDTI0tCDwhSqBOE7BoIL0ykwISes6iRqyqbJvc9EkuwAylF3cSlPZ9WcrNpnaXu4//FJWxBcXdCyoW+KTIamvsZ2txC3lEZDzfASFNJt6jrwK9tSl+pFzen94Y/u3d8AQ==);whiteSpace=wrap;html=1;verticalAlign=top;spacing=0;labelPosition=center;verticalLabelPosition=bottom;align=center;verticalAlign=top;rotation="+str(rotation)+";",
|
217
|
+
"vertex":"1",
|
218
|
+
"parent":"1"
|
219
|
+
}
|
220
|
+
cell = self.addCell(self.root,mxCell_dict)
|
221
|
+
mxGeometry_dict = {"x":str(x-60),
|
222
|
+
"y":str(y-30),
|
223
|
+
"width":"120",
|
224
|
+
"height":"60",
|
225
|
+
"as":"geometry"
|
226
|
+
}
|
227
|
+
geometry = self.addGeometry(cell,mxGeometry_dict)
|
228
|
+
|
229
|
+
return geometry
|
230
|
+
|
231
|
+
def addResistor(self,name,value,x,y,rotation=0):
|
232
|
+
mxCell_dict = {"id":name,
|
233
|
+
"value":"Name = "+name+"\nR = "+str(value),
|
234
|
+
"style":"shape=stencil(vVRtbsMgDD0NfycC6wEq1h5gN6Cpt6AmEAFtt9sXMOlniNpoGoqU+Dl5fs+2QrhwjeyBMKplB4R/EMY+wSnnjQ2PAW8QfKcYHjFkNMfS9VB7BA/SKrlpATPOW7ODo9r6TKF0A1b5mOUrQpfhnXhxURutA4ky2t1krvKBTCodvqU/SJbL/+bobYFxHyp04MEiXiF68ZYBwtZPV6rmVWLjlbgISMkwFxtZ776t2evtqD7saW3aOB2K91SNML5OZ9qfjbMq9XB8wHfzLzGjspLhgi0uvoyFmX5pOtOqehmX7yExpDtzgFI72CT1wNAqfcWweIUiNaYgcKKfs6xVLykbN3feiP9wd7uod+oHIXlVqz9d1YeNTCj+JRNwAg==);whiteSpace=wrap;html=1;labelPosition=center;verticalLabelPosition=bottom;align=center;verticalAlign=top;rotation="+str(rotation)+";",
|
235
|
+
"vertex":"1",
|
236
|
+
"parent":"1"
|
237
|
+
}
|
238
|
+
cell = self.addCell(self.root,mxCell_dict)
|
239
|
+
mxGeometry_dict = {"x":str(x-60),
|
240
|
+
"y":str(y-10),
|
241
|
+
"width":"120",
|
242
|
+
"height":"20",
|
243
|
+
"as":"geometry"
|
244
|
+
}
|
245
|
+
geometry = self.addGeometry(cell,mxGeometry_dict)
|
246
|
+
|
247
|
+
return geometry
|
248
|
+
|
249
|
+
def addCapacitor(self,name,value,x,y,rotation=0):
|
250
|
+
mxCell_dict = {"id":name,
|
251
|
+
"value":"Name = "+name+"\nC = "+str(value),
|
252
|
+
"style":"shape=stencil(zVXRUsMgEPwaXh0C+tBHB+1/UHoapgkwBFv9+wJHtaYhajqOMplJbjfs3d6RCeFiaKUDwqiRPRD+QBgT0kmlg/XxORItorcUwwOGjJZYDg5UQHAvvZabDpAZgrc7OOhtKBLatOB1SCx/JPQ+vpMuLpQ1Jopoa4ZPzBkfxaQ2cS99RbGS/q1EN3cYu5ihhwAe8QbRD3MFIGz97UzNskxsOhMXEakZ5mIj1e7Z2xeznawPe6psl6ZD8Z6zEcbXec3782lWtR5OD3g0/5oyVlYzXLHFxZP1sNAvzWu+KifT4bsgTnRv91BrB5uVPil02pwprH4ikRtTKXCmn4usNc315t5PxL9zN2r79YP76rD/3eAWeBtL/Iq5vO3iY84o/mEycAQ=);whiteSpace=wrap;html=1;labelPosition=center;verticalLabelPosition=bottom;align=center;verticalAlign=top;rotation="+str(rotation)+";",
|
253
|
+
"vertex":"1",
|
254
|
+
"parent":"1"
|
255
|
+
}
|
256
|
+
cell = self.addCell(self.root,mxCell_dict)
|
257
|
+
mxGeometry_dict = {"x":str(x-60),
|
258
|
+
"y":str(y-18.75),
|
259
|
+
"width":"120",
|
260
|
+
"height":"37.5",
|
261
|
+
"as":"geometry"
|
262
|
+
}
|
263
|
+
geometry = self.addGeometry(cell,mxGeometry_dict)
|
264
|
+
|
265
|
+
return geometry
|
266
|
+
|
267
|
+
def addConnection(self,name,startID,startPnt,endID,endPnt,pntArray=0):
|
268
|
+
# Pnt = [X,Y] gibt die relative Position des Anschlusspunktes an
|
269
|
+
# X=0 => links
|
270
|
+
# X=1 => rechts
|
271
|
+
# Y=0 => oben
|
272
|
+
# Y=1 => unten
|
273
|
+
mxCell_dict = {"id":name,
|
274
|
+
"value":"",
|
275
|
+
"style":"endArrow=none;entryX="+str(endPnt[0])+";entryY="+str(endPnt[1])+";exitX="+str(startPnt[0])+";exitY="+str(startPnt[1])+";html=1;rounded=0;entryDx=0;entryDy=0;entryPerimeter=0;exitDx=0;exitDy=0;",
|
276
|
+
"edge":"1",
|
277
|
+
"parent":"1",
|
278
|
+
"source":startID,
|
279
|
+
"target":endID
|
280
|
+
}
|
281
|
+
cell = self.addCell(self.root,mxCell_dict)
|
282
|
+
mxGeometry_dict = {"width":"50",
|
283
|
+
"height":"50",
|
284
|
+
"relative":"1",
|
285
|
+
"as":"geometry"
|
286
|
+
}
|
287
|
+
geometry = self.addGeometry(cell,mxGeometry_dict)
|
288
|
+
|
289
|
+
if isinstance(pntArray, list):
|
290
|
+
self.addPntArray(geometry,pntArray)
|
291
|
+
|
292
|
+
return
|
293
|
+
|
294
|
+
def draw(netlist,file):
|
295
|
+
#Creates an xml file of the netlist that can be displayed with draw.io
|
296
|
+
|
297
|
+
#Parameter:
|
298
|
+
# netlist (list): List of branches
|
299
|
+
# file (str): Filename of diagram with extension (.xml)
|
300
|
+
|
301
|
+
# list of nodes
|
302
|
+
nodes = []
|
303
|
+
for branch in netlist['Branches']:
|
304
|
+
branch_nodes = branch['Nodes']
|
305
|
+
for nd in range(len(branch_nodes)):
|
306
|
+
node = branch_nodes[nd]
|
307
|
+
if not(node in nodes):
|
308
|
+
nodes.append(node)
|
309
|
+
nodes.sort()
|
310
|
+
|
311
|
+
#list of connections
|
312
|
+
connections = [None] * len(nodes)
|
313
|
+
for node in nodes:
|
314
|
+
connection = []
|
315
|
+
for branch in netlist['Branches']:
|
316
|
+
branch_nodes = branch['Nodes']
|
317
|
+
if node in branch_nodes:
|
318
|
+
for nd in branch_nodes:
|
319
|
+
if nd!=node and not(nd in connection):
|
320
|
+
connection.append(nd)
|
321
|
+
i = nodes.index(node)
|
322
|
+
connections[i]=connection
|
323
|
+
|
324
|
+
diagram = HeatSourceDiagram()
|
325
|
+
|
326
|
+
#determine nodes position
|
327
|
+
nd_pos = []
|
328
|
+
xstep = 250
|
329
|
+
ystep = 250
|
330
|
+
xpos = 0
|
331
|
+
ypos = ystep*len(connections)
|
332
|
+
nd_pos.append([xpos,ypos])
|
333
|
+
def_nd = [0]
|
334
|
+
ypos = ypos-ystep
|
335
|
+
for i in range(len(connections)):
|
336
|
+
#new nodes in connection
|
337
|
+
new_nd = []
|
338
|
+
for nd in connections[i]:
|
339
|
+
if nd>i and not(nd in new_nd) and not(nd in def_nd) :
|
340
|
+
new_nd.append(nd)
|
341
|
+
xpos = -xstep*(1+(len(new_nd)-1)/2)
|
342
|
+
draw = False
|
343
|
+
for nd in new_nd:
|
344
|
+
xpos = xpos+xstep
|
345
|
+
nd_pos.append([xpos,ypos])
|
346
|
+
def_nd.append(nd)
|
347
|
+
draw = True
|
348
|
+
#next level
|
349
|
+
if draw:
|
350
|
+
ypos = ypos-ystep
|
351
|
+
|
352
|
+
|
353
|
+
#draw nodes
|
354
|
+
for nd in def_nd:
|
355
|
+
i = def_nd.index(nd)
|
356
|
+
diagram.addNode(nd,nd_pos[i][0],nd_pos[i][1])
|
357
|
+
|
358
|
+
# draw branches
|
359
|
+
for branch in netlist['Branches']:
|
360
|
+
#print(branch)
|
361
|
+
xb = 0
|
362
|
+
yb = 0
|
363
|
+
for nd in branch['Nodes']:
|
364
|
+
i = def_nd.index(nd)
|
365
|
+
xb = xb+nd_pos[i][0]/len(branch['Nodes'])
|
366
|
+
yb = yb+nd_pos[i][1]/len(branch['Nodes'])
|
367
|
+
if len(branch['Nodes'])==2:
|
368
|
+
i1 = def_nd.index(branch['Nodes'][0])
|
369
|
+
i2 = def_nd.index(branch['Nodes'][1])
|
370
|
+
dx = nd_pos[i1][0]-nd_pos[i2][0]
|
371
|
+
dy = nd_pos[i1][1]-nd_pos[i2][1]
|
372
|
+
angle = np.arctan2(dy,dx)*180/np.pi
|
373
|
+
else:
|
374
|
+
angle = 0
|
375
|
+
|
376
|
+
if branch['Type']=='R_th':
|
377
|
+
diagram.addResistor(branch['Name'],branch['val'],xb,yb,angle)
|
378
|
+
|
379
|
+
if branch['Type']=='C_th':
|
380
|
+
diagram.addCapacitor(branch['Name'],branch['val'],xb,yb,angle)
|
381
|
+
|
382
|
+
if branch['Type']=='Power_Source':
|
383
|
+
diagram.addPowerSource(branch['Name'],branch['val'],xb,yb,angle)
|
384
|
+
|
385
|
+
if branch['Type']=='Temperature_Source':
|
386
|
+
diagram.addTemperatureSource(branch['Name'],branch['T_val'],branch['R_val'],xb,yb,angle)
|
387
|
+
|
388
|
+
# draw connections
|
389
|
+
num_con = 0
|
390
|
+
for branch in netlist['Branches']:
|
391
|
+
num_con = num_con+1
|
392
|
+
branch_id = branch['Name']
|
393
|
+
y_offset = 0.5
|
394
|
+
diagram.addConnection("connect"+str(num_con),\
|
395
|
+
"node_"+str(branch['Nodes'][0]),[0,0],branch_id,[1,y_offset])
|
396
|
+
num_con = num_con+1
|
397
|
+
diagram.addConnection("connect"+str(num_con),\
|
398
|
+
"node_"+str(branch['Nodes'][1]),[0,0],branch_id,[0,y_offset])
|
399
|
+
|
400
|
+
|
401
|
+
diagram.writeFile(file)
|
402
|
+
|
403
|
+
return
|
femagtools/machine/pm.py
CHANGED
@@ -5,9 +5,10 @@ import logging
|
|
5
5
|
import warnings
|
6
6
|
import numpy as np
|
7
7
|
import numpy.linalg as la
|
8
|
-
from .utils import iqd, betai1, skin_resistance, dqparident, KTH
|
8
|
+
from .utils import iqd, betai1, skin_resistance, dqparident, KTH, K
|
9
9
|
import scipy.optimize as so
|
10
10
|
import scipy.interpolate as ip
|
11
|
+
import scipy.integrate as ig
|
11
12
|
from functools import partial
|
12
13
|
|
13
14
|
logger = logging.getLogger(__name__)
|
@@ -1392,6 +1393,11 @@ class PmRelMachinePsidq(PmRelMachine):
|
|
1392
1393
|
|
1393
1394
|
self._psid = ip.RectBivariateSpline(iq, id, psid).ev
|
1394
1395
|
self._psiq = ip.RectBivariateSpline(iq, id, psiq).ev
|
1396
|
+
# used for transient
|
1397
|
+
self.psid = psid
|
1398
|
+
self.psiq = psiq
|
1399
|
+
self.id = id
|
1400
|
+
self.iq = iq
|
1395
1401
|
try:
|
1396
1402
|
pfe = kwargs['losses']
|
1397
1403
|
if 'styoke_excess' in pfe and np.any(pfe['styoke_excess']):
|
@@ -1451,10 +1457,12 @@ class PmRelMachinePsidq(PmRelMachine):
|
|
1451
1457
|
return iqmax, np.max(self.idrange)
|
1452
1458
|
|
1453
1459
|
def iqd_plfe1(self, iq, id, f1):
|
1454
|
-
stator_losskeys = ['styoke_eddy', 'styoke_hyst',
|
1455
|
-
|
1460
|
+
stator_losskeys = [k for k in ['styoke_eddy', 'styoke_hyst',
|
1461
|
+
'stteeth_eddy', 'stteeth_hyst']
|
1462
|
+
if k in self._losses]
|
1456
1463
|
if self.bertotti:
|
1457
|
-
stator_losskeys += ['styoke_excess', 'stteeth_excess'
|
1464
|
+
stator_losskeys += [k for k in ('styoke_excess', 'stteeth_excess')
|
1465
|
+
if k in self._losses]
|
1458
1466
|
return np.sum([
|
1459
1467
|
self._losses[k](iq, id)*(f1/self.fo)**self.plexp[k] for
|
1460
1468
|
k in tuple(stator_losskeys)], axis=0)
|
@@ -1478,3 +1486,70 @@ class PmRelMachinePsidq(PmRelMachine):
|
|
1478
1486
|
|
1479
1487
|
def betai1_plmag(self, beta, i1, f1):
|
1480
1488
|
return self.iqd_plmag(*iqd(beta, i1), f1)
|
1489
|
+
|
1490
|
+
|
1491
|
+
### EXPERIMENTAL
|
1492
|
+
|
1493
|
+
def transient(self, u1, tload, speed,
|
1494
|
+
fault_type=3, # 'LLL', 'LL', 'LG',
|
1495
|
+
tend=0.1, nsamples=200):
|
1496
|
+
|
1497
|
+
tshort = 0
|
1498
|
+
w1 = 2*np.pi*self.p*speed
|
1499
|
+
i0 = self.iqd_torque(tload)
|
1500
|
+
res = so.minimize(
|
1501
|
+
np.linalg.norm, i0, method='SLSQP',
|
1502
|
+
constraints=(
|
1503
|
+
{'type': 'ineq',
|
1504
|
+
'fun': lambda iqd: self.tmech_iqd(*iqd, speed) - tload},
|
1505
|
+
{'type': 'ineq',
|
1506
|
+
'fun': lambda iqd: np.sqrt(2)*u1
|
1507
|
+
- la.norm(self.uqd(w1, *iqd))}))
|
1508
|
+
iqx, idx = res.x
|
1509
|
+
uq0, ud0 = self.uqd(w1, iqx, idx)
|
1510
|
+
logger.info("transient: Torque %f Nm, Speed %f rpm, Curr %f A",
|
1511
|
+
tload, speed*60, betai1(iqx, idx)[1])
|
1512
|
+
if fault_type == 3: # 3 phase short circuit
|
1513
|
+
USC = lambda t: np.zeros(2)
|
1514
|
+
else: # 2 phase short circuit
|
1515
|
+
#ustat = np.array([K(w1*x).dot((uq, ud)) for x in t])
|
1516
|
+
USC = lambda t: np.array([
|
1517
|
+
uq0/2*(1+np.cos(2*w1*t)),
|
1518
|
+
uq0/2*np.sin(2*w1*t)])
|
1519
|
+
U = lambda t: (uq0, ud0) if t < tshort else USC(t)
|
1520
|
+
|
1521
|
+
psid = ip.RectBivariateSpline(self.iq, self.id, self.psid, kx=3, ky=3)
|
1522
|
+
psiq = ip.RectBivariateSpline(self.iq, self.id, self.psiq, kx=3, ky=3)
|
1523
|
+
#ld = ip.RectBivariateSpline(iq, id, dqpars['psidq'][0]['ld'], kx=3, ky=3)
|
1524
|
+
#lq = ip.RectBivariateSpline(iq, id, dqpars['psidq'][0]['lq'], kx=3, ky=3)
|
1525
|
+
#psim = ip.RectBivariateSpline(iq, id, dqpars['psidq'][0]['psim'], kx=3, ky=3)
|
1526
|
+
#def didtl(t, iqd):
|
1527
|
+
# lqd = lq(*iqd)[0,0], ld(*iqd)[0,0]
|
1528
|
+
# return [
|
1529
|
+
# (uq-r1*iqd[0] -w1 * lqd[1]*iqd[1] - w1*psim(*iqd)[0,0])/lqd[0],
|
1530
|
+
# (ud-r1*iqd[1] +w1*lqd[0]*iqd[0])/lqd[1]]
|
1531
|
+
|
1532
|
+
def didt(t, iqd):
|
1533
|
+
uq, ud = U(t)
|
1534
|
+
ldd = psid(*iqd, dx=0, dy=1)[0,0]
|
1535
|
+
lqq = psiq(*iqd, dx=1, dy=0)[0,0]
|
1536
|
+
ldq = psid(*iqd, dx=1, dy=0)[0,0]
|
1537
|
+
lqd = psiq(*iqd, dx=0, dy=1)[0,0]
|
1538
|
+
psi = psid(*iqd)[0,0], psiq(*iqd)[0,0]
|
1539
|
+
return [
|
1540
|
+
(-ldd*psi[0]*w1 + ldd*(uq-self.r1*iqd[0])
|
1541
|
+
- lqd*psi[1]*w1 - lqd*(ud-self.r1*iqd[1]))/(ldd*lqq - ldq*lqd),
|
1542
|
+
(ldq*psi[0]*w1 - ldq*(uq-self.r1*iqd[0])
|
1543
|
+
+ lqq*psi[1]*w1 + lqq*(ud-self.r1*iqd[1]))/(ldd*lqq - ldq*lqd)]
|
1544
|
+
|
1545
|
+
t = np.linspace(0, tend, nsamples)
|
1546
|
+
Y0 = iqx, idx
|
1547
|
+
sol = ig.solve_ivp(didt, (t[0], t[-1]), Y0, dense_output=True)
|
1548
|
+
y = sol.sol(t).T
|
1549
|
+
|
1550
|
+
return {
|
1551
|
+
't': t.tolist(),
|
1552
|
+
'iq': y[:,0], 'id': y[:,1],
|
1553
|
+
'istat': np.array([K(w1*x[0]).dot((x[1][1], x[1][0]))
|
1554
|
+
for x in zip(t, y)]).T.tolist(),
|
1555
|
+
'torque': [self.torque_iqd(*iqd) for iqd in y]}
|