ViewVC Help
View File | Revision Log | Show Annotations | View Changeset | Root Listing
root/group/trunk/multipole/VectorFieldPlot.py
Revision: 4411
Committed: Tue Apr 5 21:44:16 2016 UTC (9 years ago) by gezelter
Content type: text/x-python
File size: 53028 byte(s)
Log Message:
new figures and tables

File Contents

# User Rev Content
1 gezelter 4407 #!/opt/local/bin/python
2    
3     '''
4     VectorFieldPlot - plots electric and magnetic fieldlines in svg
5     http://commons.wikimedia.org/wiki/User:Geek3/VectorFieldPlot
6    
7     Copyright (C) 2010 Geek3
8    
9     This program is free software; you can redistribute it and/or modify
10     it under the terms of the GNU General Public License as published by
11     the Free Software Foundation;
12     either version 3 of the License, or (at your option) any later version.
13    
14     This program is distributed in the hope that it will be useful,
15     but WITHOUT ANY WARRANTY; without even the implied warranty of
16     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17     See the GNU General Public License for more details.
18    
19     You should have received a copy of the GNU General Public License
20     along with this program; if not, see http://www.gnu.org/licenses/
21     '''
22    
23     version = '1.3'
24    
25    
26     from math import *
27     from lxml import etree
28     import scipy as sc
29     import scipy.optimize as op
30     import scipy.integrate as ig
31     import bisect
32    
33    
34    
35     # some helper functions
36     def vabs(x):
37     '''
38     euclidian vector norm for any kind of vector
39     '''
40     return sqrt(sum([i**2 for i in x]))
41    
42     def vnorm(x):
43     '''
44     vector normalisation
45     '''
46     d = vabs(x)
47     if d != 0.: return sc.array(x) / vabs(x)
48     return sc.array(x)
49    
50     def rot(xy, phi):
51     '''
52     2D vector rotation
53     '''
54     s = sin(phi); c = cos(phi)
55     return sc.array([c * xy[0] - s * xy[1], c * xy[1] + s * xy[0]])
56    
57     def cosv(v1, v2):
58     '''
59     find the cosine of the angle between two vectors
60     '''
61     d1 = sum(v1**2); d2 = sum(v2**2)
62     if d1 != 1.: d1 = sqrt(d1)
63     if d2 != 1.: d2 = sqrt(d2)
64     if d1 * d2 == 0.: return 1.
65     return sc.dot(v1, v2) / (d1 * d2)
66    
67     def sinv(v1, v2):
68     '''
69     find the sine of the angle between two vectors
70     '''
71     d1 = sum(v1**2); d2 = sum(v2**2)
72     if d1 != 1.: d1 = sqrt(d1)
73     if d2 != 1.: d2 = sqrt(d2)
74     if d1 * d2 == 0.: return 0.
75     return (v1[0] * v2[1] - v1[1] * v2[0]) / (d1 * d2)
76    
77     def angle_dif(a1, a2):
78     return ((a2 - a1 + pi) % (2. * pi)) - pi
79    
80     def list_interpolate(l, t):
81     n = max(0, bisect.bisect_right(l, t) - 1)
82     s = None
83     if t < l[0]:
84     if l[1] - l[0] == 0.:
85     s = 0.
86     else:
87     s = (t - l[0]) / float(l[1] - l[0])
88     elif t >= l[-1]:
89     n = len(l) - 2
90     if l[-1] - l[-2] == 0.:
91     s = 1.
92     else:
93     s = (t - l[-2]) / float(l[-1] - l[-2])
94     else:
95     s = (t - l[n]) / (l[n+1] - l[n])
96     return n, s
97    
98     def pretty_vec(p):
99     return '{0:> 9.5f},{1:> 9.5f}'.format(p[0], p[1])
100    
101    
102     class FieldplotDocument:
103     '''
104     creates a svg document structure using lxml.etree
105     '''
106     def __init__ (self, name, width=800, height=600, digits=3.5, unit=100,
107     center=None, licence='GFDL-cc', commons=False):
108     self.name = name
109     self.width = float(width)
110     self.height = float(height)
111     self.digits = float(digits)
112     self.unit = float(unit)
113     self.licence = licence
114     self.commons = commons
115     if center == None: self.center = [width / 2., height / 2.]
116     else: self.center = [float(i) for i in center]
117    
118     # create document structure
119     self.svg = etree.Element('svg',
120     nsmap={None: 'http://www.w3.org/2000/svg',
121     'xlink': 'http://www.w3.org/1999/xlink'})
122     self.svg.set('version', '1.1')
123     self.svg.set('baseProfile', 'full')
124     self.svg.set('width', str(int(width)))
125     self.svg.set('height', str(int(height)))
126    
127     # title
128     self.title = etree.SubElement(self.svg, 'title')
129     self.title.text = self.name
130    
131     # description
132     self.desc = etree.SubElement(self.svg, 'desc')
133     self.desc.text = ''
134     self.desc.text += self.name + '\n'
135     self.desc.text += 'created with VectorFieldPlot ' + version + '\n'
136     self.desc.text += 'http://commons.wikimedia.org/wiki/User:Geek3/VectorFieldPlot\n'
137     if commons:
138     self.desc.text += """
139     about: http://commons.wikimedia.org/wiki/File:{0}.svg
140     """.format(self.name)
141     if self.licence == 'GFDL-cc':
142     self.desc.text += """rights: GNU Free Documentation license,
143     Creative Commons Attribution ShareAlike license\n"""
144     self.desc.text += ' '
145    
146     # background
147     self.background = etree.SubElement(self.svg, 'rect')
148     self.background.set('id', 'background')
149     self.background.set('x', '0')
150     self.background.set('y', '0')
151     self.background.set('width', str(width))
152     self.background.set('height', str(height))
153     self.background.set('fill', '#ffffff')
154    
155     # image elements
156     self.content = etree.SubElement(self.svg, 'g')
157     self.content.set('id', 'image')
158     self.content.set('transform',
159     'translate({0},{1}) scale({2},-{2})'.format(
160     self.center[0], self.center[1], self.unit))
161    
162     self.arrow_geo = {'x_nock':0.3,'x_head':3.8,'x_tail':-2.2,'width':4.5}
163    
164     def __get_defs(self):
165     if 'defs' not in dir(self):
166     self.defs = etree.Element('defs')
167     self.desc.addnext(self.defs)
168     return self.defs
169    
170     def __check_fieldlines(self, linecolor='#000000', linewidth=1.):
171     if 'fieldlines' not in dir(self):
172     self.fieldlines = etree.SubElement(self.content, 'g')
173     self.fieldlines.set('id', 'fieldlines')
174     self.fieldlines.set('fill', 'none')
175     self.fieldlines.set('stroke', linecolor)
176     self.fieldlines.set('stroke-width',
177     str(linewidth / self.unit))
178     self.fieldlines.set('stroke-linejoin', 'round')
179     self.fieldlines.set('stroke-linecap', 'round')
180     if 'count_fieldlines' not in dir(self): self.count_fieldlines = 0
181    
182     def __check_symbols(self):
183     if 'symbols' not in dir(self):
184     self.symbols = etree.SubElement(self.content, 'g')
185     self.symbols.set('id', 'symbols')
186     if 'count_symbols' not in dir(self): self.count_symbols = 0
187    
188     def __check_whitespot(self):
189     if 'whitespot' not in dir(self):
190     self.whitespot = etree.SubElement(
191     self.__get_defs(), 'radialGradient')
192     self.whitespot.set('id', 'white_spot')
193     for attr, val in [['cx', '0.65'], ['cy', '0.7'], ['r', '0.75']]:
194     self.whitespot.set(attr, val)
195     for col, of, op in [['#ffffff', '0', '0.7'],
196     ['#ffffff', '0.1', '0.5'], ['#ffffff', '0.6', '0'],
197     ['#000000', '0.6', '0'], ['#000000', '0.75', '0.05'],
198     ['#000000', '0.85', '0.15'], ['#000000', '1', '0.5']]:
199     stop = etree.SubElement(self.whitespot, 'stop')
200     stop.set('stop-color', col)
201     stop.set('offset', of)
202     stop.set('stop-opacity', op)
203    
204     def __get_arrowname(self, fillcolor='#000000'):
205     if 'arrows' not in dir(self):
206     self.arrows = {}
207     if fillcolor not in self.arrows.iterkeys():
208     arrow = etree.SubElement(self.__get_defs(), 'path')
209     self.arrows[fillcolor] = arrow
210     arrow.set('id', 'arrow' + str(len(self.arrows)))
211     arrow.set('stroke', 'none')
212     arrow.set('fill', fillcolor)
213     arrow.set('transform', 'scale({0})'.format(1. / self.unit))
214     arrow.set('d',
215     'M {0},0 L {1},{3} L {2},0 L {1},-{3} L {0},0 Z'.format(
216     self.arrow_geo['x_nock'], self.arrow_geo['x_tail'],
217     self.arrow_geo['x_head'], self.arrow_geo['width'] / 2.))
218     return self.arrows[fillcolor].get('id')
219    
220     def draw_charges(self, field, scale=1.):
221     if 'monopoles' not in field.elements: return
222     charges = field.elements['monopoles']
223     self.__check_symbols()
224     self.__check_whitespot()
225    
226     for charge in charges:
227     c_group = etree.SubElement(self.symbols, 'g')
228     self.count_symbols += 1
229     c_group.set('id', 'charge{0}'.format(self.count_symbols))
230     c_group.set('transform',
231     'translate({0},{1}) scale({2},{2})'.format(
232     charge[0], charge[1], float(scale) / self.unit))
233    
234     #### charge drawing ####
235     c_bg = etree.SubElement(c_group, 'circle')
236     c_shade = etree.SubElement(c_group, 'circle')
237     c_symb = etree.SubElement(c_group, 'path')
238     if charge[2] >= 0.: c_bg.set('style', 'fill:#ff0000; stroke:none')
239     else: c_bg.set('style', 'fill:#0000ff; stroke:none')
240     for attr, val in [['cx', '0'], ['cy', '0'], ['r', '14']]:
241     c_bg.set(attr, val)
242     c_shade.set(attr, val)
243     c_shade.set('style',
244     'fill:url(#white_spot); stroke:#000000; stroke-width:2')
245     # plus sign
246     if charge[2] >= 0.:
247     c_symb.set('d', 'M 2,2 V 8 H -2 V 2 H -8 V -2'
248     + ' H -2 V -8 H 2 V -2 H 8 V 2 H 2 Z')
249     # minus sign
250     else: c_symb.set('d', 'M 8,2 H -8 V -2 H 8 V 2 Z')
251     c_symb.set('style', 'fill:#000000; stroke:none')
252    
253     def draw_currents(self, field, scale=1.):
254     if ('wires' not in field.elements
255     and 'ringcurrents' not in field.elements):
256     return
257     self.__check_symbols()
258     self.__check_whitespot()
259     currents = []
260     if 'wires' in field.elements:
261     for i in field.elements['wires']:
262     currents.append(i)
263     if 'ringcurrents' in field.elements:
264     for i in field.elements['ringcurrents']:
265     currents.append(list(i[:2] + rot([0., i[3]], i[2])) + [i[-1]])
266     currents.append(list(i[:2] - rot([0., i[3]], i[2])) + [-i[-1]])
267    
268     for cur in currents:
269     c_group = etree.SubElement(self.symbols, 'g')
270     self.count_symbols += 1
271     if cur[-1] >= 0.: direction = 'out'
272     else: direction = 'in'
273     c_group.set('id',
274     'current_{0}{1}'.format(direction, self.count_symbols))
275     c_group.set('transform',
276     'translate({0},{1}) scale({2},{2})'.format(
277     cur[0], cur[1], float(scale) / self.unit))
278    
279     #### current drawing ####
280     c_bg = etree.SubElement(c_group, 'circle')
281     c_shade = etree.SubElement(c_group, 'circle')
282     c_bg.set('style', 'fill:#b0b0b0; stroke:none')
283     for attr, val in [['cx', '0'], ['cy', '0'], ['r', '14']]:
284     c_bg.set(attr, val)
285     c_shade.set(attr, val)
286     c_shade.set('style',
287     'fill:url(#white_spot); stroke:#000000; stroke-width:2')
288     if cur[-1] >= 0.: # dot
289     c_symb = etree.SubElement(c_group, 'circle')
290     c_symb.set('cx', '0')
291     c_symb.set('cy', '0')
292     c_symb.set('r', '4')
293     else: # cross
294     c_symb = etree.SubElement(c_group, 'path')
295     c_symb.set('d', 'M {1},-{0} L {0},-{1} L {2},{3} L {0},{1} \
296     L {1},{0} {3},{2} L -{1},{0} L -{0},{1} L -{2},{3} L -{0},-{1} L -{1},-{0} \
297     L {3},-{2} L {1},-{0} Z'.format(11.1, 8.5, 2.6, 0))
298     c_symb.set('style', 'fill:#000000; stroke:none')
299    
300     def draw_magnets(self, field):
301     if 'coils' not in field.elements: return
302     coils = field.elements['coils']
303     self.__check_symbols()
304    
305     for coil in coils:
306     m_group = etree.SubElement(self.symbols, 'g')
307     self.count_symbols += 1
308     m_group.set('id', 'magnet{0}'.format(self.count_symbols))
309     m_group.set('transform',
310     'translate({0},{1}) rotate({2})'.format(
311     coil[0], coil[1], degrees(coil[2])))
312    
313     #### magnet drawing ####
314     r = coil[3]; l = coil[4]
315     colors = ['#00cc00', '#ff0000']
316     SN = ['S', 'N']
317     if coil[5] < 0.:
318     colors.reverse()
319     SN.reverse()
320     m_defs = etree.SubElement(m_group, 'defs')
321     m_gradient = etree.SubElement(m_defs, 'linearGradient')
322     m_gradient.set('id', 'magnetGrad{0}'.format(self.count_symbols))
323     for attr, val in [['x1', '0'], ['x2', '0'], ['y1', str(coil[3])],
324     ['y2', str(-coil[3])], ['gradientUnits', 'userSpaceOnUse']]:
325     m_gradient.set(attr, val)
326     for col, of, opa in [['#000000', '0', '0.125'],
327     ['#ffffff', '0.07', '0.125'], ['#ffffff', '0.25', '0.5'],
328     ['#ffffff', '0.6', '0.2'], ['#000000', '1', '0.33']]:
329     stop = etree.SubElement(m_gradient, 'stop')
330     stop.set('stop-color', col)
331     stop.set('offset', of)
332     stop.set('stop-opacity', opa)
333     for i in [0, 1]:
334     rect = etree.SubElement(m_group, 'rect')
335     for attr, val in [['x', [-l, 0][i]], ['y', -r],
336     ['width', [2*l, l][i]], ['height', 2 * r],
337     ['style', 'fill:{0}; stroke:none'.format(colors[i])]]:
338     rect.set(attr, str(val))
339     rect = etree.SubElement(m_group, 'rect')
340     for attr, val in [['x', -l], ['y', -r],
341     ['width', 2 * l], ['height', 2 * r],
342     ['style', 'fill:url(#magnetGrad{0}); stroke-width:{1}; stroke-linejoin:miter; stroke:#000000'.format(self.count_symbols, 4. / self.unit)]]:
343     rect.set(attr, str(val))
344     for i in [0, 1]:
345     text = etree.SubElement(m_group, 'text')
346     for attr, val in [['text-anchor', 'middle'], ['y', -r],
347     ['transform', 'translate({0},{1}) scale({2},-{2})'.format(
348     [-0.65, 0.65][i] * l, -0.44 * r, r / 100.)],
349     ['style', 'fill:#000000; stroke:none; ' +
350     'font-size:120px; font-family:Bitstream Vera Sans']]:
351     text.set(attr, str(val))
352     text.text = SN[i]
353    
354     def draw_line(self, fieldline, maxdist=10., linewidth=2.,
355     linecolor='#000000', attributes=[], arrows_style=None):
356     '''
357     draws a calculated fieldline from a FieldLine object
358     to the FieldplotDocument svg image
359     '''
360     self.__check_fieldlines(linecolor, linewidth)
361     self.count_fieldlines += 1
362    
363     bounds = {}
364     bounds['x0'] = -(self.center[0] + 0.5 * linewidth) / self.unit
365     bounds['y0'] = -(self.height - self.center[1] +
366     0.5 * linewidth) / self.unit
367     bounds['x1'] = (self.width - self.center[0] +
368     0.5 * linewidth) / self.unit
369     bounds['y1'] = (self.center[1] + 0.5 * linewidth) / self.unit
370    
371     # fetch the polyline from the fieldline object
372     polylines = fieldline.get_polylines(self.digits, maxdist, bounds)
373     if len(polylines) == 0: return
374    
375     line = etree.Element('path')
376     if self.fieldlines.get('stroke') != linecolor:
377     line.set('stroke', linecolor)
378     if self.fieldlines.get('stroke-width') != str(linewidth / self.unit):
379     line.set('stroke-width', str(linewidth / self.unit))
380     for attr, val in attributes:
381     line.set(attr, val)
382    
383     #### line drawing ####
384     path_data = []
385     for polyline in polylines:
386     line_points = polyline['path']
387     for i, p in enumerate(line_points):
388     # go through all points, draw them if line segment is visible
389     ptext = '{1:.{0}f},{2:.{0}f}'.format(
390     int(ceil(self.digits)), p[0], p[1])
391     if i == 0: path_data.append('M ' + ptext)
392     else: path_data.append('L ' + ptext)
393     # close path if possible
394     if (vabs(polylines[0]['path'][0] - polylines[-1]['path'][-1])
395     < .1**self.digits):
396     closed = True
397     if len(polylines) == 1:
398     path_data.append('Z')
399     elif len(polylines) > 1:
400     # rearrange array cyclic
401     path_data.pop(0)
402     while path_data[0][0] != 'M':
403     path_data.append(path_data.pop(0))
404     else: closed = False
405    
406     path = ' '.join(path_data)
407     line.set('d', path)
408    
409     if arrows_style == None:
410     # include path directly into document structure
411     line.set('id', 'fieldline{0}'.format(self.count_fieldlines))
412     self.fieldlines.append(line)
413     else:
414     line_and_arrows = etree.SubElement(self.fieldlines, 'g')
415     line_and_arrows.set('id', 'fieldline' + str(self.count_fieldlines))
416     line_and_arrows.append(line)
417     line_and_arrows.append(self.__draw_arrows(
418     arrows_style, linewidth, polylines, linecolor, closed))
419    
420     def __draw_arrows(self, arrows_style, linewidth, polylines,
421     linecolor='#000000', closed=False):
422     '''
423     draws arrows on polylines.
424     options in "arrows_style":
425     min_arrows: minimum number of arrows per segment
426     max_arrows: maximum number of arrows per segment (None: no limit)
427     dist: optimum distance between arrows
428     scale: relative size of arrows to linewidth
429     offsets [start_offset, mid_end, mid_start, end_offset]
430     fixed_ends [True, False, False, True]:
431     make first/last arrow distance invariable
432     '''
433     min_arrows = 1; max_arrows = None; arrows_dist = 1.; scale = linewidth
434     offsets = 4 * [0.5]; fixed_ends = 4 * [False]
435     if 'min_arrows' in arrows_style:
436     min_arrows = arrows_style['min_arrows']
437     if 'max_arrows' in arrows_style:
438     max_arrows = arrows_style['max_arrows']
439     if 'dist' in arrows_style:
440     arrows_dist = arrows_style['dist']
441     if 'scale' in arrows_style:
442     scale *= arrows_style['scale']
443     if 'offsets' in arrows_style:
444     offsets = arrows_style['offsets']
445     if 'fixed_ends' in arrows_style:
446     fixed_ends = arrows_style['fixed_ends']
447     if scale == 1.: scaletext = ''
448     else: scaletext = ' scale({0})'.format(scale)
449    
450     arrows = etree.Element('g')
451     arrows.set('id', 'arrows' + str(self.count_fieldlines))
452     for j, polyline in enumerate(polylines):
453     line_points = polyline['path']
454     mina = min_arrows
455     maxa = max_arrows
456     # measure drawn path length
457     lines_dist = [0.]
458     for i in range(1, len(line_points)):
459     lines_dist.append(lines_dist[-1]
460     + vabs(line_points[i] - line_points[i-1]))
461    
462     offs = [offsets[2], offsets[1]]
463     fixed = [fixed_ends[2], fixed_ends[1]]
464     if polyline['start']:
465     offs[0] = offsets[0]
466     fixed[0] = fixed_ends[0]
467     if polyline['end']:
468     offs[1] = offsets[3]
469     fixed[1] = fixed_ends[3]
470    
471     d01 = [0., lines_dist[-1]]
472     for i in [0, 1]:
473     if fixed[i]:
474     d01[i] += offs[i] * arrows_dist * [1., -1.][i]
475     mina -= 1
476     if maxa != None: maxa -= 1
477     if d01[1] - d01[0] < 0.: break
478     elif d01[1] - d01[0] == 0.: d_list = [d01[0]]
479     else:
480     d_list = []
481     if fixed[0]: d_list.append(d01[0])
482     if maxa > 0 or maxa == None:
483     number_intervals = (d01[1] - d01[0]) / arrows_dist
484     number_offsets = 0.
485     for i in [0, 1]:
486     if fixed[i]: number_offsets += .5
487     else: number_offsets += offs[i] - .5
488     n = int(number_intervals - number_offsets + 0.5)
489     n = max(n, mina)
490     if maxa != None: n = min(n, maxa)
491     if n > 0:
492     d = (d01[1] - d01[0]) / float(n + number_offsets)
493     if fixed[0]: d_start = d01[0] + d
494     else: d_start = offs[0] * d
495     for i in range(n):
496     d_list.append(d_start + i * d)
497     if fixed[1]: d_list.append(d01[1])
498    
499     geo = self.arrow_geo # shortcut
500     #### arrow drawing ####
501     for d1 in d_list:
502     # calculate arrow position and direction
503     if d1 < 0. or d1 > lines_dist[-1]: continue
504     d0 = d1 + (geo['x_nock'] * scale + 2.5*linewidth *
505     (geo['x_tail'] - geo['x_nock']) / geo['width']) / self.unit
506     if closed and d0 < 0.: d0 = lines_dist[-1] + d0
507     d2 = d1 + (geo['x_head'] * scale + linewidth *
508     (geo['x_tail'] - geo['x_head']) / geo['width']) / self.unit
509     if closed and d2 > lines_dist[-1]: d1 -= lines_dist[-1]
510     i0, s0 = list_interpolate(lines_dist, d0)
511     i1, s1 = list_interpolate(lines_dist, d1)
512     i2, s2 = list_interpolate(lines_dist, d2)
513     p0 = line_points[i0] + s0 * (line_points[i0+1]-line_points[i0])
514     p1 = line_points[i1] + s1 * (line_points[i1+1]-line_points[i1])
515     p2 = line_points[i2] + s2 * (line_points[i2+1]-line_points[i2])
516     p = None; angle = None
517     if vabs(p2-p1) <= .1**self.digits or (d2 <= d0 and not closed):
518     v = line_points[i1+1] - line_points[i1]
519     p = p1
520     angle = atan2(v[1], v[0])
521     else:
522     v = p2 - p0
523     p = p0 + sc.dot(p1 - p0, v) * v / vabs(v)**2
524     angle = atan2(v[1], v[0])
525    
526     arrow = etree.SubElement(arrows, 'use')
527     arrow.set('{http://www.w3.org/1999/xlink}href',
528     '#' + self.__get_arrowname(linecolor))
529     arrow.set('transform', ('translate({0:.'
530     + str(int(ceil(self.digits))) + 'f},{1:.'
531     + str(int(ceil(self.digits)))
532     + 'f}) rotate({2:.2f})').format(p[0], p[1],
533     degrees(angle)) + scaletext)
534     return arrows
535    
536     def draw_object(self, name, params, group=None):
537     '''
538     Draw arbitraty svg object.
539     Params must be a dictionary of valid svg parameters.
540     '''
541     self.__check_symbols()
542     if group == None:
543     obj = etree.SubElement(self.symbols, name)
544     else:
545     obj = etree.SubElement(group, name)
546     for i, j in params.iteritems():
547     obj.set(str(i), str(j))
548     return obj
549    
550     def write(self, filename=None):
551     # put symbols on top
552     if 'content' in dir(self):
553     for element in self.content:
554     if element.get('id').startswith('symbols'):
555     self.content.append(element)
556    
557     # write content to file
558     if filename == None: filename = self.name
559     outfile = open(filename + '.svg', 'w')
560     outfile.write(etree.tostring(self.svg, xml_declaration=True,
561     pretty_print=True, encoding='utf-8'))
562     outfile.close()
563     print 'image written to', filename + '.svg'
564    
565    
566    
567     class FieldLine:
568     '''
569     calculates field lines
570     '''
571     def __init__(self, field, start_p, start_v=None, start_d=None,
572     directions='forward', maxn=1000, maxr=300.0, hmax=1.0,
573     pass_dipoles=0, path_close_tol=5e-3, bounds_func=None,
574     stop_funcs=[None, None]):
575     '''
576     field: a field in which the line exists
577     start_p: [x0, y0]: where the line starts
578     start_v: [vx0, vy0]: optional start direction
579     start_d: [dx0, dy0]: optional dipole start direction (slope to x=1)
580     directions: forward, backward, both: bidirectional
581     unit: estimation for the scale of the scene
582     maxn: maximum number of steps
583     maxr: maximum number of units to depart from start
584     hmax: maximum number of units for stepsize
585     pass_dipoles: number of dipoles to be passed through (-1 = infinite)
586     '''
587     self.field = field
588     self.p_start = sc.array(start_p)
589     self.first_point = self.p_start
590     self.bounds_func = bounds_func
591     self.stop_funcs = stop_funcs
592     if start_v == None: self.v_start = None
593     else: self.v_start = sc.array(start_v)
594     if start_d == None: self.d_start = None
595     else: self.d_start = sc.array(start_d)
596     self.__create_nodes(directions, maxn, maxr, hmax,
597     pass_dipoles, path_close_tol)
598    
599     def __get_nearest_pole(self, p, v=None):
600     '''
601     returns distance to nearest pole
602     '''
603     p_near = self.first_point
604     d_near = vabs(self.first_point - p)
605     if v != None: d_near *= 1.3 - cosv(v, self.first_point - p)
606     type_near = 'start'
607     mon = []
608     for ptype, poles in self.field.elements.iteritems():
609 gezelter 4411 if ptype not in ['monopoles', 'dipoles', 'quadrupoles'] or len(poles) == 0:
610 gezelter 4407 continue
611     for pole in poles:
612     d = vabs(pole[:2] - p)
613     if v != None: d *= 1.3 - cosv(v, pole[:2] - p)
614     if d < d_near:
615     d_near = d
616     p_near = pole
617     type_near = ptype
618     return sc.array(p_near), type_near
619    
620     def __rkstep(self, p, v, f, h):
621     '''
622     fourth order Runge Kutta step
623     '''
624     k1 = h * v
625     k2 = h * f(p + k1 / 2.)
626     k3 = h * f(p + k2 / 2.)
627     k4 = h * f(p + k3)
628     p1 = p + (k1 + 2. * (k2 + k3) + k4) / 6.
629     return p1
630    
631     def __create_nodes_part(self, sign, maxn, maxr, hmax,
632     pass_dipoles, path_close_tol):
633     '''
634     executes integration from startpoint to one end
635     '''
636     # p is always the latest position
637     # v is always the latest normalized velocity
638     # h is always the latest step size
639     # l is always the summarized length
640     err = 5e-8 # error tolerance for integration
641     f = None
642     if sign >= 0.: f = self.field.Fn
643     else: f = lambda r: -self.field.Fn(r)
644     # first point
645     p = self.p_start
646     if self.v_start != None:
647     v = vnorm(self.v_start) * sign
648     else:
649     v = f(p)
650     nodes = [{'p':p.copy(), 'v_in':None}]
651     xtol = 20. * err; ytol = path_close_tol
652     # initialize loop
653     h = (sqrt(5) - 1.) / 10.; h_old = h
654     l = 0.; i = 0
655     while i < maxn and l < maxr:
656     i += 1
657     if len(nodes) == 1 and self.d_start != None:
658     # check for start from a dipole
659     h = vabs(self.d_start)
660     p = p + self.d_start
661     v = f(p)
662     nodes[-1]['v_out'] = h * vnorm(2.0 * vnorm(self.d_start) - v)
663     nodes.append({'p':p.copy(), 'v_in':h * v})
664     elif len(nodes) > 1:
665     # check for special cases
666     nearest_pole, pole_type = self.__get_nearest_pole(p, v)
667     vpole = nearest_pole[:2] - p
668     dpole = vabs(vpole)
669     vpole /= dpole
670    
671     cv = cosv(v, vpole); sv = sinv(v, vpole)
672     if ((dpole < 0.1 or h >= dpole)
673     and (cv > 0.9 or dpole < ytol)):
674     # heading for some known special point
675     if pole_type == 'start':
676     # is the fieldline about to be closed?
677     if ((dpole * abs(sv) < ytol) and
678     (dpole * abs(cv) < xtol) and (l > 1e-3)):
679     # path is closed
680     nodes[-1]['v_out'] = None
681     print 'closed at', pretty_vec(p)
682     break
683     elif (h > 0.99 * dpole and (cv > 0.9 or
684     (cv > 0. and dpole * abs(sv) < ytol))):
685     # slow down
686     h = max(4.*err, dpole*cv * max(.9, 1-.1*dpole*cv))
687    
688     if (pole_type == 'monopoles' and
689     dpole < 0.01 and cv > .996):
690     # approaching a monopole: end line with x**3 curve
691     nodes[-1]['v_out'] = vnorm(v) * dpole
692     v = vnorm(1.5 * vnorm(vpole) -
693     .5 * vnorm(nodes[-1]['v_out']))
694     nodes.append({'p':nearest_pole[:2].copy(),
695     'v_in':v * dpole, 'v_out':None})
696     l += h
697     break
698    
699     if (pole_type == 'dipoles' and
700     dpole < 0.01 and cv > .996):
701     # approaching a dipole
702     m = sign * vnorm(nearest_pole[2:4])
703     p = nodes[-1]['p'] + 2. * sc.dot(m, vpole) * m * dpole
704     # approximation by a y=x**1.5 curve
705     nodes[-1]['v_out'] = 2. * vnorm(v) * dpole
706     nodes.append({'p':nearest_pole[:2].copy(),
707     'v_in':sc.zeros(2), 'v_out':sc.zeros(2)})
708     l += h
709     # check if the path is being closed
710     v_end = self.first_point - p
711     if ((dpole * abs(sinv(v, v_end)) < ytol) and
712     (dpole * abs(cosv(v, v_end)) < xtol) and l > 1e-3):
713     # path is closed
714     nodes[-1]['v_out'] = None
715     break
716     if pass_dipoles == 0:
717     nodes[-1]['v_out'] = None
718     break
719     if pass_dipoles > 0:
720     pass_dipoles -= 1
721     v = f(p)
722     nodes.append({'p':p.copy(), 'v_in':2.*vnorm(v)*dpole})
723     l += h
724     continue
725    
726     # buckle detection at unknown places
727     elif h < 0.01:
728     # check change rate of curvature
729     hh = h * 3.
730     v0 = f(p + hh / 2. * v)
731     v1 = f(p + hh * v)
732     angle0 = atan2(v[1], v[0])
733     angle1 = atan2(v0[1], v0[0])
734     angle2 = atan2(v1[1], v1[0])
735     a0 = angle_dif(angle1, angle0)
736     a1 = angle_dif(angle2, angle1)
737     adif = angle_dif(a1, a0)
738     corner_limit = 1e4
739     if abs(adif) / hh**2 > corner_limit:
740     # assume a corner here
741     if abs(a0) >= abs(a1):
742     h0 = 0.; h1 = hh / 2.
743     vm = vnorm(vnorm(v) + vnorm(v0))
744     else:
745     h0 = hh / 2.; h1 = hh
746     vm = vnorm(vnorm(v0) + vnorm(v1))
747     if vabs(vm)==0.: vm = vnorm(sc.array([v0[1], -v0[0]]))
748     hc = op.brentq(lambda hc: sinv(f(p+hc*v), vm), h0, h1)
749     v2 = f(p + hc / 2. * v)
750     if sinv(f(p), vm) * sinv(f(p + 2.*hc*v2), vm) <= 0.:
751     hc = op.brentq(lambda hc: sinv(f(p + hc * v2),
752     vm), 0., 2. * hc)
753     nodes[-1]['v_out'] = vnorm(nodes[-1]['v_in']) * hc
754     # create a corner
755     # use second-order formulas instead of runge-kutta
756     p += hc * v2
757     print 'corner at', pretty_vec(p)
758     v = vnorm(2. * v2 - v)
759     nodes.append({'p':p.copy(),'v_in':v*hc,'corner':True})
760     l += h
761     # check if the path is being closed
762     v_end = self.first_point - p
763     if ((dpole * abs(sinv(v, v_end)) < ytol) and
764     (dpole * abs(cosv(v, v_end)) < xtol) and l > 1e-3):
765     # path is closed
766     nodes[-1]['v_out'] = None
767     break
768     # check area after the corner
769     # lengths are chosen to ensure corner detection
770     p0 = p + hh * .2 * f(p + hh * .2 * v1); va0 = f(p0)
771     p1 = p0 + hh * .4 * va0; va1 = f(p1)
772     p2 = p1 + hh * .4 * va1; va2 = f(p2)
773     angle0 = atan2(va0[1], va0[0])
774     angle1 = atan2(va1[1], va1[0])
775     angle2 = atan2(va2[1], va2[0])
776     a0 = angle_dif(angle1, angle0)
777     a1 = angle_dif(angle2, angle1)
778     adif = angle_dif(a1, a0)
779     if (abs(adif) / (.8*hh)**2 > corner_limit or
780     abs(a0) + abs(a1) >= pi / 2.):
781     print 'end edge at', pretty_vec(p)
782     # direction after corner changes again -> end line
783     nodes[-1]['v_out'] = None
784     break
785     vm = vnorm(1.25 * va1 - 0.25 * va2)
786     v = f(p + hh * vm)
787     nodes[-1]['v_out'] = vnorm(2. * vm - v) * hh
788     p += vm * hh
789     nodes.append({'p':p.copy(), 'v_in':v * hh})
790     l += h
791    
792     # make single and double runge-kutta step
793     p11 = self.__rkstep(p, vnorm(v), f, h)
794     p21 = self.__rkstep(p, vnorm(v), f, h / 2.)
795     p22 = self.__rkstep(p21, f(p21), f, h / 2.)
796     diff = vabs(p22 - p11)
797     if diff < 2. * err:
798     # accept step
799     p = (16. * p22 - p11) / 15.
800     nodes[-1]['v_out'] = vnorm(v) * h
801     v = f(p)
802     if vabs(v) == 0.:
803     # field is zero, line is stuck -> end line
804     nodes[-1]['v_out'] = None
805     break
806     if (len(nodes) >= 2
807     and vabs(nodes[-1]['p'] - nodes[-2]['p']) == 0.):
808     if h > 2. * err: h /= 7.
809     else:
810     # point doesn_t move, line is stuck -> end line
811     nodes = nodes[:-1]
812     nodes[-1]['v_out'] = None
813     break
814     nodes.append({'p':p.copy(), 'v_in':v * h})
815     l += h
816    
817     # stop at the prohibited area
818     if self.stop_funcs != None and self.stop_funcs != [None, None]:
819     stop_fct = self.stop_funcs[{-1.0:0, 1.0:1}[sign]]
820     if stop_fct(nodes[-1]['p']) > 0.0:
821     while len(nodes) > 1 and stop_fct(nodes[-2]['p']) > 0.0:
822     nodes = nodes[:-1]
823     if len(nodes) > 1:
824     p, p1 = nodes[-2]['p'], nodes[-1]['p']
825     t = op.brentq(lambda t: stop_fct(p + t * (p1 - p)),
826     0.0, 1.0)
827     nodes[-1]['p'] = p + t * (p1 - p)
828     h = vabs(nodes[-1]['p'] - p)
829     nodes[-2]['v_out'] = f(nodes[-2]['p']) * h
830     nodes[-1]['v_in'] = f(nodes[-1]['p']) * h
831     print 'stopped at', pretty_vec(nodes[-1]['p'])
832     break
833    
834     # adapt step carefully
835     if diff > 0.:
836     factor = (err / diff) ** .25
837     if h < h_old: h_new = min((h + h_old) / 2., h * factor)
838     else: h_new = h * max(0.5, factor)
839     h_old = h
840     h = h_new
841     else:
842     h_old = h
843     h *= 2.
844     h = min(hmax, max(err, h))
845    
846     nodes[-1]['v_out'] = None
847     if i == maxn:
848     print maxn, 'integration steps exceeded at', pretty_vec(p)
849     if l >= maxr:
850     print 'integration boundary',str(maxr),'exceeded at',pretty_vec(p)
851     return nodes
852    
853     def __is_loop(self, nodes, path_close_tol):
854     if vabs(nodes[0]['p'] - nodes[-1]['p']) > max(5e-4, path_close_tol):
855     return False
856     length = 0.
857     for i in range(1, len(nodes)):
858     length += vabs(nodes[i]['p'] - nodes[i-1]['p'])
859     if length > 5e-3:
860     return True
861     return False
862    
863     def __create_nodes(self, directions,
864     maxn, maxr, hmax, pass_dipoles, path_close_tol):
865     '''
866     creates self.nodes from one or two parts
867     wrapper for __self.create_nodes_part
868     '''
869     closed = False
870     if (directions == 'forward'):
871     self.nodes = self.__create_nodes_part(
872     1., maxn, maxr, hmax, pass_dipoles, path_close_tol)
873     else:
874     nodes1 = self.__create_nodes_part(
875     -1., maxn, maxr, hmax, pass_dipoles, path_close_tol)
876     # reverse nodes1
877     nodes1.reverse()
878     for node in nodes1:
879     v_out = node['v_out']
880     if node['v_in'] == None: node['v_out'] = None
881     else: node['v_out'] = -node['v_in']
882     if v_out == None: node['v_in'] = None
883     else: node['v_in'] = -v_out
884     self.nodes = nodes1
885     if len(self.nodes) > 0: self.first_point = self.nodes[0]['p']
886     if directions != 'backward':
887     # is it already a closed loop?
888     if not self.__is_loop(self.nodes, path_close_tol):
889     nodes2 = self.__create_nodes_part(
890     1., maxn, maxr, hmax, pass_dipoles, path_close_tol)
891     self.nodes[-1]['v_out'] = nodes2[0]['v_out']
892     self.nodes += nodes2[1:]
893    
894     # append accumulated normalized sum
895     self.nodes[0]['t'] = 0.
896     for i in range(1, len(self.nodes)):
897     self.nodes[i]['t'] = (self.nodes[i-1]['t']
898     + vabs(self.nodes[i-1]['p'] - self.nodes[i]['p']))
899     length = self.nodes[-1]['t']
900     if length != 0.:
901     for i in range(1, len(self.nodes)):
902     self.nodes[i]['t'] /= length
903     # add corner tag to all nodes
904     for i, node in enumerate(self.nodes):
905     if not node.has_key('corner'):
906     self.nodes[i]['corner'] = False
907    
908     def get_position(self, t):
909     '''
910     dense output routine
911     t: parameter, 0 <= t <= 1
912     '''
913     nodes = self.nodes
914     if len(nodes) == 1:
915     return nodes[0]['p']
916     if len(nodes) <= 0:
917     return sc.zeros(2)
918     if t != 1.: t = t % 1.
919     n, p = list_interpolate([i['t'] for i in nodes], t)
920     p0, v0 = nodes[n]['p'], nodes[n]['v_out']
921     p1, v1 = nodes[n+1]['p'], nodes[n+1]['v_in']
922     # cubic bezier interpolation (hermite interpolation)
923     q = 1. - p
924     xy = q*p0 + p*p1 + p * q * ((p - q) * (p1 - p0) + (q*v0 - p*v1))
925     return xy
926    
927     def __bending(self, p0, p3, t0, t3):
928     # calculate two extra points on intervall
929     p1 = self.get_position((2.*t0 + t3) / 3.)
930     p2 = self.get_position((t0 + 2.*t3) / 3.)
931     # d1, d2: point distances from straight line
932     d1 = (p1 - p0)[0] * (p3 - p0)[1] - (p1 - p0)[1] * (p3 - p0)[0]
933     d1 /= vabs(p3 - p0)
934     d2 = (p2 - p0)[0] * (p3 - p0)[1] - (p2 - p0)[1] * (p3 - p0)[0]
935     d2 /= vabs(p3 - p0)
936     dsum, ddif = d1 + d2, d1 - d2
937     d = 0.
938     if abs(ddif) < 1e-5:
939     d = 10. / 9. * (abs(d1) + abs(d2)) / 2.
940     else:
941     # calculate line bending as max distance of a deg-3 polynomial:
942     y = lambda x: 13.5 * x * (1.-x) * (d1 * (2./3.-x) + d2 * (x-1./3.))
943     # all the factors come from the quadratic formula
944     xm = .5 + dsum / (18. * ddif)
945     xd = sqrt(27. * ddif**2 + dsum**2) / (18. * ddif)
946     x1, x2 = min(xm + xd, xm - xd), max(xm + xd, xm - xd)
947     if x1 > 0.:
948     d = max(d, abs(y(x1)))
949     if x2 < 1.:
950     d = max(d, abs(y(x2)))
951     return d
952    
953     def __get_polyline(self, t0, t1, digits=3.5, maxdist=10., mindist=4e-4):
954     '''
955     returns points of an adapted polyline,
956     representing the fieldline to an accuracy of digits.
957     no corner should be between t0 and t1.
958     '''
959     f = self.get_position
960     t_list = sc.linspace(t0, t1, 10)
961     value_list = [f(t) for t in t_list]
962    
963     # adapt t_list
964     num = 0; num_success = 0
965     while len(t_list) > 2:
966     ratios = []; delta_t = []
967     N_old = len(t_list) - 1
968     success = True
969     # get bending
970     for i in range(N_old):
971     bend = self.__bending(value_list[i], value_list[i+1],
972     t_list[i], t_list[i + 1])
973     d = vabs(value_list[i+1] - value_list[i])
974     # keep point distance smaller than maxdist
975     ratio = d / maxdist
976     if num > 10: exponent = 1. / (num - 8.)
977     else: exponent = 0.5
978     # find best ratio, assuming bending is proportional to d**2
979     if bend != 0.:
980     ratio = max(ratio, (bend / 0.1 ** digits)**exponent)
981     ratio = min(ratio, d / mindist)
982     if ratio > 1.1: # 1 + 0.1 for termination safety
983     success = False
984     ratio = min(max(.25, ratio), 4.) # prevent too big changes
985     ratios.append(ratio)
986     delta_t.append(t_list[i + 1] - t_list[i])
987    
988     n = sum(ratios)
989     N = max(1, ceil(n)) # new intervall number must be an integer
990     num += 1
991     # check if we all intervalls are good enough and we are finished
992     if success == True: num_success += 1
993     else: num_success = 0
994     if num_success > 2 and N < N_old: num_success = 2
995     if num_success >= 3: break
996     if num >= 25:
997     print 'polyline creation did not converge after', num, 'tries!'
998     break
999     ratios = [ratio * N / n for ratio in ratios]
1000    
1001     # rearrange t_list
1002     t_list = [t0] # initialize again
1003     N0 = 0; Nt = 0.; N1 = 0.; t = t0
1004     for i in range(N_old):
1005     N1 += ratios[i]
1006     while N1 - N0 >= 1.:
1007     N0 += 1
1008     t += delta_t[i] * (N0 - Nt) / ratios[i]
1009     Nt = N0
1010     if len(t_list) == N:
1011     break
1012     t_list.append(t)
1013     t += delta_t[i] * (N1 - Nt) / ratios[i]
1014     Nt = N1
1015     t_list.append(t1)
1016     value_list = [f(t) for t in t_list]
1017     return value_list, t_list
1018    
1019     def __out_of_bounds(self, p, bounds):
1020     '''
1021     returns a points distance to the drawing area
1022     >0: outside; <=0: inside
1023     '''
1024     if self.bounds_func != None:
1025     s = self.bounds_func(p)
1026     if s > 0.: return s
1027     if bounds == None: return -1.
1028     if (p[0] < bounds['x0'] or p[1] < bounds['y0']
1029     or p[0] > bounds['x1'] or p[1] > bounds['y1']):
1030     return sqrt((p[0] - bounds['x0'])**2 + (p[1] - bounds['y0'])**2
1031     + (bounds['x1'] - p[0])**2 + (bounds['y1'] - p[1])**2)
1032     else:
1033     return max(bounds['x0'] - p[0], bounds['y0'] - p[1],
1034     p[0] - bounds['x1'], p[1] - bounds['y1'])
1035    
1036     def get_polylines(self, digits=3.5, maxdist=10., bounds=None):
1037     '''
1038     returns polyline segments that are inside of bounds.
1039     the path is represented as a set of adapted line segments
1040     which are cut at the image bounds and at edges.
1041     '''
1042     if len(self.nodes) <= 1: return []
1043    
1044     # search for all corners
1045     corners = []
1046     for node in self.nodes:
1047     if node['corner']: corners.append(node['t'])
1048     if len(corners) == 0 or corners[0] != 0.: corners.insert(0, 0.)
1049     if corners[-1] != 1.: corners.append(1.)
1050    
1051     # search for points where line intersects bounds
1052     edges = []; parts_outside = False; inside1 = False; t1 = 0.
1053     if self.__out_of_bounds(self.nodes[0]['p'], bounds) <= 0.:
1054     inside1 = True
1055     edges.append({'t0':0.})
1056     for i in range(1, len(self.nodes)):
1057     t0 = t1; t1 = self.nodes[i]['t']
1058     p1 = self.nodes[i]['p']
1059     inside0 = inside1
1060     inside1 = (self.__out_of_bounds(p1, bounds) <= 0.)
1061     if inside1:
1062     if not inside0:
1063     edges.append({'t0':op.brentq(lambda t:
1064     self.__out_of_bounds(self.get_position(t),
1065     bounds), t0, t1)})
1066     if i == len(self.nodes) - 1:
1067     edges[-1]['t1'] = 1.
1068     else:
1069     parts_outside = True
1070     if inside0:
1071     edges[-1]['t1'] = (op.brentq(lambda t:
1072     self.__out_of_bounds(self.get_position(t),
1073     bounds), t0, t1))
1074    
1075     # all points are outside the drawing area
1076     if len(edges) == 0: return []
1077    
1078     # join first and last segment
1079     if (len(edges) > 1 and
1080     edges[0]['t0'] == 0. and edges[-1]['t1'] == 1. and
1081     vabs(self.get_position(1.) - self.get_position(0.)) <= 1e-5):
1082     edges[0]['t0'] = edges[-1]['t0'] - 1.
1083     edges = edges[:-1]
1084    
1085     # insert corners to all segments
1086     for edge in edges:
1087     edge['corners'] = []
1088     for c in corners:
1089     if edge['t0'] < c and c < edge['t1']:
1090     edge['corners'].append(c)
1091    
1092     # create final polylines
1093     polyline = []
1094     for interval in edges:
1095     line = []
1096     t_list = [interval['t0']] + interval['corners'] + [interval['t1']]
1097     for i in range(1, len(t_list)):
1098     pl = self.__get_polyline(t_list[i-1], t_list[i],
1099     digits, maxdist)[0]
1100     if i == 1: line += pl
1101     else: line += pl[1:]
1102     if len(line) >= 2:
1103     polyline.append({'path':line,
1104     'start':(interval['t0']==0.), 'end':(interval['t1']==1.)})
1105     return polyline
1106    
1107    
1108    
1109     class Field:
1110     '''
1111     represents an electromagnetic field together with
1112     charges, potential, setup etc.
1113     '''
1114     def __init__ (self, elements={}):
1115     self.elements = {}
1116     for name, params in elements.iteritems():
1117     self.add_element(name, params)
1118    
1119     '''
1120     possible types:
1121     'homogeneous': [Fx, Fy]
1122     'gradient': [g, Ax, Ay, Bx, By]
1123     'monopoles': [x, y, charge]
1124     'dipoles': [x, y, phi, q]
1125     'quadrupoles': [x, y, phi, q]
1126     'wires': [x, y, I]
1127     'charged_planes': [x0, y0, x1, y1, charge]
1128     'ringcurrents': [x0, y0, phi, R, I]
1129     'coils': [x0, y0, phi, R, Lhalf, I]
1130     'custom': user defined function
1131     '''
1132    
1133     def add_element(self, name, params):
1134     if len(params) >= 1 and str(type(params[0])) == "<type 'function'>":
1135     if name in self.elements: self.elements[name] += params
1136     else: self.elements[name] = params
1137     else:
1138     el = [sc.array(param, dtype='float') for param in params]
1139     if name in self.elements: self.elements[name] += el
1140     else: self.elements[name] = el
1141    
1142     def get_elements(self, name):
1143     if name in self.elements: return self.elements[name]
1144     else: return []
1145    
1146     def F(self, xy):
1147     '''
1148     returns the field force as a vector
1149     '''
1150     Fxy = sc.zeros(2)
1151    
1152     # homogeneous: homogeneus field in a given direction
1153     for hom in self.get_elements('homogeneous'):
1154     Fxy += hom
1155    
1156     # gradient: gradient field in a given direction
1157     for gra in self.get_elements('gradient'):
1158     g = gra[0]
1159     A = sc.array(gra[1:3])
1160     B = sc.array(gra[3:5])
1161     cpsi = sc.dot( A, B )
1162     r = xy
1163     Fxy[0] += g*(2.0*(A[0]*B[0]-cpsi/2.0)*r[0] + (A[0]*B[1]+A[1]*B[0])*r[1])/2.0
1164     Fxy[1] += g*((A[0]*B[1]+A[1]*B[0])*r[0] + 2.0*(A[1]*B[1]-cpsi/2.0)*r[1])/2.0
1165    
1166     # monopoles: electric charges and magnetic monopoles
1167     for mon in self.get_elements('monopoles'):
1168     r = xy - sc.array(mon[:2])
1169     d = vabs(r)
1170     if d != 0.:
1171     Fxy += mon[-1] * r / d**3
1172    
1173     # dipoles: pointlike electric or magnetic dipole
1174     for dip in self.get_elements('dipoles'):
1175     r = xy - sc.array(dip[:2])
1176     d = vabs(r)
1177     if d != 0.:
1178     p = sc.array(dip[2:4])
1179     Fxy += (3. * sc.dot(p, r) * r - sc.dot(r, r) * p) / (4.*pi*d**5)
1180     else:
1181     # the sign of this is unphysical, but more useful
1182     return dip[2:4]
1183    
1184     # quadrupoles: pointlike electric or magnetic quadrupoles
1185     for quad in self.get_elements('quadrupoles'):
1186     r = xy - sc.array(quad[:2])
1187     d = vabs(r)
1188     r /= d
1189     if d != 0.:
1190     p = rot([0,1], quad[2])
1191     pr = sc.dot(p, r)
1192     Fxy += (((5.*pr**2 - 1.) * r - 2.*pr * p) *
1193     3.*quad[3] / (4.*pi * d**4))
1194     else:
1195     return quad[2:4]
1196    
1197     # wires: infinite straight wire perpendicular to image plane
1198     for wire in self.get_elements('wires'):
1199     r = xy - sc.array(wire[:2])
1200     Fxy += wire[-1]/(2*pi) * sc.array([-r[1], r[0]]) / (r[0]**2 + r[1]**2)
1201    
1202     # charged_planes: rectangular plane with edges [x0,y0] and [x1,y1]
1203     # perpendicular to image plane and infinite in z-direction
1204     for plane in self.get_elements('charged_planes'):
1205     r = xy - .5 * (plane[2:4] + plane[0:2])
1206     p = .5 * (plane[2:4] - plane[0:2])
1207     rsq = r[0]**2 + r[1]**2
1208     psq = p[0]**2 + p[1]**2
1209     X = r[0] * p[1] - p[0] * r[1]
1210     Y = r[1] * p[1] + p[0] * r[0]
1211     if X == 0.: Fa = 0.
1212     else: Fa = atan((psq - Y) / X) + atan((psq + Y) / X)
1213     Fb = atanh(2. * Y / (rsq + psq))
1214     Fxy += (plane[4] / (4*pi*psq)
1215     * sc.array([Fa*p[1]+Fb*p[0], Fb*p[1]-Fa*p[0]]))
1216    
1217     # ringcurrents: round currentloop perpendicular to image plane
1218     # caution: slow because of numerical integration
1219     for ring in self.get_elements('ringcurrents'):
1220     r = xy - sc.array(ring[:2])
1221     # change into a relative coordinate system with a and b
1222     a, b = rot(r, -ring[2]) / ring[3]
1223     c = 1. + a**2 + b**2
1224     def fa(t): h=cos(t); return (1.-b*h) / sqrt((c-2.*b*h)**3)
1225     def fb(t): h=cos(t); return (a * h) / sqrt((c-2.*b*h)**3)
1226     Fa = ig.quad(fa, 0., pi, epsrel=0., full_output=True)[0]
1227     Fb = ig.quad(fb, 0., pi, epsrel=0., full_output=True)[0]
1228     # backtransform
1229     Fxy += rot([Fa, Fb], ring[2]) * (ring[-1] / ring[3])
1230    
1231     # coil: dense cylinder coil or cylinder magnet
1232     # caution: slow because of numerical integration
1233     for coil in self.get_elements('coils'):
1234     r = xy - sc.array(coil[:2])
1235     # change into a relative coordinate system with a and b
1236     a, b = rot(r, -coil[2]) / coil[3]
1237     c = 1. + b**2
1238     l = coil[4] / coil[3]
1239     am = a - l; am2 = am ** 2
1240     ap = a + l; ap2 = ap ** 2
1241     def fa(t):
1242     h = cos(t);
1243     d = c - 2. * b * h
1244     return (1. - b*h) / d * (ap / sqrt(d + ap2)
1245     - am / sqrt(d + am2))
1246     def fb(t):
1247     h = cos(t);
1248     d = c - 2. * b * h
1249     return h / sqrt(d + am2) - h / sqrt(d + ap2)
1250     Fa = ig.quad(fa, 0., pi, full_output=True)[0]
1251     Fb = ig.quad(fb, 0., pi, full_output=True)[0]
1252     # backtransform
1253     Fxy += rot([Fa, Fb], coil[2]) * (coil[-1] / (2. * coil[4]))
1254    
1255     # custom: user defined function
1256     for cust in self.get_elements('custom'):
1257     Fxy += cust(xy)
1258    
1259     return Fxy
1260    
1261     def Fn(self, xy):
1262     '''
1263     returns the normalized field force, i.e. direction of field lines
1264     '''
1265     force = self.F(xy)
1266     d = vabs(force)
1267     if (d != 0.): return force / d
1268     return sc.array([0., 0.])
1269    
1270     ### append your specific field creation here ###
1271     # see http://commons.wikimedia.org/wiki/File:VFPt_charges_plus_minus.svg for an example

Properties

Name Value
svn:executable *