fieldplot.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. #!/usr/bin/env python
  2. # -*- coding: UTF-8 -*-
  3. #
  4. # Copyright (C) 2009-2015 Ovidio Peña Rodríguez <ovidio@bytesfall.com>
  5. # Copyright (C) 2013-2015 Konstantin Ladutenko <kostyfisik@gmail.com>
  6. #
  7. # This file is part of python-scattnlay
  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, either version 3 of the License, or
  12. # (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. See the
  17. # GNU General Public License for more details.
  18. #
  19. # The only additional remark is that we expect that all publications
  20. # describing work using this software, or all commercial products
  21. # using it, cite the following reference:
  22. # [1] O. Pena and U. Pal, "Scattering of electromagnetic radiation by
  23. # a multilayered sphere," Computer Physics Communications,
  24. # vol. 180, Nov. 2009, pp. 2348-2354.
  25. #
  26. # You should have received a copy of the GNU General Public License
  27. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  28. # Several functions to plot field and streamlines (power flow lines).
  29. #
  30. # To have nice streamline you may want to tweak streamplot.py from
  31. # your python site-packages of matplotlib.
  32. #
  33. # After:
  34. # lc = mcollections.LineCollection(
  35. # streamlines, transform=transform, **line_kw)
  36. # Add:
  37. # lc.set_capstyle('round')
  38. #
  39. # And modify:
  40. # maxds = min(1. / dmap.mask.nx, 1. / dmap.mask.ny, 0.1)
  41. # To:
  42. # maxds = 0.1*min(1. / dmap.mask.nx, 1. / dmap.mask.ny, 0.1)
  43. from matplotlib import cm
  44. from matplotlib import patches
  45. from matplotlib.colors import LogNorm
  46. from matplotlib.path import Path
  47. from scattnlay import fieldnlay
  48. from scattnlay import scattnlay
  49. import cmath
  50. import numpy as np
  51. import sys
  52. def unit_vector(vector):
  53. """ Returns the unit vector of the vector. """
  54. return vector / np.linalg.norm(vector)
  55. def angle_between(v1, v2):
  56. """ Returns the angle in radians between vectors 'v1' and 'v2'::
  57. >>> angle_between((1, 0, 0), (0, 1, 0))
  58. 1.5707963267948966
  59. >>> angle_between((1, 0, 0), (1, 0, 0))
  60. 0.0
  61. >>> angle_between((1, 0, 0), (-1, 0, 0))
  62. 3.141592653589793
  63. """
  64. v1_u = unit_vector(v1)
  65. v2_u = unit_vector(v2)
  66. angle = np.arccos(np.dot(v1_u, v2_u))
  67. if np.isnan(angle):
  68. if (v1_u == v2_u).all():
  69. return 0.0
  70. else:
  71. return np.pi
  72. return angle
  73. ###############################################################################
  74. def GetFlow3D(x0, y0, z0, max_length, max_angle, x, m, pl):
  75. # Initial position
  76. flow_x = [x0]
  77. flow_y = [y0]
  78. flow_z = [z0]
  79. max_step = x[-1] / 3
  80. min_step = x[0] / 2000
  81. # max_step = min_step
  82. step = min_step * 2.0
  83. if max_step < min_step:
  84. max_step = min_step
  85. coord = np.vstack(([flow_x[-1]], [flow_y[-1]], [flow_z[-1]])).transpose()
  86. terms, E, H = fieldnlay(np.array([x]), np.array([m]), coord, pl=pl)
  87. Ec, Hc = E[0, 0, :], H[0, 0, :]
  88. S = np.cross(Ec, Hc.conjugate()).real
  89. Snorm_prev = S / np.linalg.norm(S)
  90. Sprev = S
  91. length = 0
  92. dpos = step
  93. count = 0
  94. while length < max_length:
  95. count = count + 1
  96. if (count > 4000): # Limit length of the absorbed power streamlines
  97. break
  98. if step < max_step:
  99. step = step * 2.0
  100. r = np.sqrt(flow_x[-1]**2 + flow_y[-1]**2 + flow_z[-1]**2)
  101. while step > min_step:
  102. # Evaluate displacement from previous poynting vector
  103. dpos = step
  104. dx = dpos * Snorm_prev[0]
  105. dy = dpos * Snorm_prev[1]
  106. dz = dpos * Snorm_prev[2]
  107. # Test the next position not to turn\chang size for more than
  108. # max_angle
  109. coord = np.vstack(([flow_x[-1] + dx], [flow_y[-1] + dy],
  110. [flow_z[-1] + dz])).transpose()
  111. terms, E, H = fieldnlay(np.array([x]), np.array([m]), coord, pl=pl)
  112. Ec, Hc = E[0, 0, :], H[0, 0, :]
  113. Eth = max(np.absolute(Ec)) / 1e10
  114. Hth = max(np.absolute(Hc)) / 1e10
  115. for i in range(0, len(Ec)):
  116. if abs(Ec[i]) < Eth:
  117. Ec[i] = 0 + 0j
  118. if abs(Hc[i]) < Hth:
  119. Hc[i] = 0 + 0j
  120. S = np.cross(Ec, Hc.conjugate()).real
  121. if not np.isfinite(S).all():
  122. break
  123. Snorm = S / np.linalg.norm(S)
  124. diff = (S - Sprev) / max(np.linalg.norm(S), np.linalg.norm(Sprev))
  125. if np.linalg.norm(diff) < max_angle:
  126. # angle = angle_between(Snorm, Snorm_prev)
  127. # if abs(angle) < max_angle:
  128. break
  129. step = step / 2.0
  130. # 3. Save result
  131. Sprev = S
  132. Snorm_prev = Snorm
  133. dx = dpos * Snorm_prev[0]
  134. dy = dpos * Snorm_prev[1]
  135. dz = dpos * Snorm_prev[2]
  136. length = length + step
  137. flow_x.append(flow_x[-1] + dx)
  138. flow_y.append(flow_y[-1] + dy)
  139. flow_z.append(flow_z[-1] + dz)
  140. return np.array(flow_x), np.array(flow_y), np.array(flow_z)
  141. ###############################################################################
  142. def Get3DField(npts, factor, x, m, pl, mode_n=-1, mode_type=-1):
  143. """
  144. npts: number of point in each direction
  145. factor: ratio of linear size to outer size of the particle
  146. x: size parameters for particle layers
  147. m: relative index values for particle layers
  148. mode_n: second column from table
  149. all -1
  150. dipole 1 -- 2
  151. quad 2 -- 4
  152. octo 3 -- 8
  153. hex 4 -- 16
  154. 32 5 -- 32
  155. mode_type:
  156. -1 -- all modes
  157. 0 -- only electric modes
  158. 1 -- only magnetic modes
  159. """
  160. scan = np.linspace(-factor*x[-1], factor*x[-1], npts)
  161. zero = np.zeros(npts*npts, dtype = np.float64)
  162. coordX, coordY, coordZ = np.meshgrid(scan, scan, scan)
  163. coordX.resize(npts * npts * npts)
  164. coordY.resize(npts * npts * npts)
  165. coordZ.resize(npts * npts * npts)
  166. coordPlot1 = coordX
  167. coordPlot2 = coordZ
  168. coord = np.vstack((coordX, coordY, coordZ)).transpose()
  169. print(x,m)
  170. terms, E, H = fieldnlay(np.array([x]), np.array([m]), coord, pl=pl
  171. , mode_n=mode_n, mode_type=mode_type
  172. )
  173. P = np.array(map(lambda n: np.linalg.norm(np.cross(E[n], H[n])).real,
  174. range(0, len(E[0]))))
  175. return coordX, coordY, coordZ, E, H, P
  176. ###############################################################################
  177. def GetField(crossplane, npts, factor, x, m, pl, mode_n=-1, mode_type=-1, inner_only = False):
  178. """
  179. crossplane: XZ, YZ, XY, or XYZ (half is XZ, half is YZ)
  180. npts: number of point in each direction
  181. factor: ratio of plotting size to outer size of the particle
  182. x: size parameters for particle layers
  183. m: relative index values for particle layers
  184. mode_n: second column from table
  185. all -1
  186. dipole 1 -- 2
  187. quad 2 -- 4
  188. octo 3 -- 8
  189. hex 4 -- 16
  190. 32 5 -- 32
  191. mode_type:
  192. -1 -- all modes
  193. 0 -- only electric modes
  194. 1 -- only magnetic modes
  195. """
  196. scan = np.linspace(-factor*x[-1], factor*x[-1], npts)
  197. zero = np.zeros(npts*npts, dtype = np.float64)
  198. if crossplane=='XZ':
  199. coordX, coordZ = np.meshgrid(scan, scan)
  200. coordX.resize(npts * npts)
  201. coordZ.resize(npts * npts)
  202. coordY = zero
  203. coordPlot1 = coordX
  204. coordPlot2 = coordZ
  205. elif crossplane == 'YZ':
  206. coordY, coordZ = np.meshgrid(scan, scan)
  207. coordY.resize(npts * npts)
  208. coordZ.resize(npts * npts)
  209. coordX = zero
  210. coordPlot1 = coordY
  211. coordPlot2 = coordZ
  212. elif crossplane == 'XY':
  213. coordX, coordY = np.meshgrid(scan, scan)
  214. coordX.resize(npts * npts)
  215. coordY.resize(npts * npts)
  216. coordZ = zero
  217. coordPlot1 = coordY
  218. coordPlot2 = coordX
  219. elif crossplane=='XYZ':
  220. coordX, coordZ = np.meshgrid(scan, scan)
  221. coordY, coordZ = np.meshgrid(scan, scan)
  222. coordPlot1, coordPlot2 = np.meshgrid(scan, scan)
  223. coordPlot1.resize(npts * npts)
  224. coordPlot2.resize(npts * npts)
  225. half=npts//2
  226. # coordX = np.copy(coordX)
  227. # coordY = np.copy(coordY)
  228. coordX[:,:half]=0
  229. coordY[:,half:]=0
  230. coordX.resize(npts*npts)
  231. coordY.resize(npts*npts)
  232. coordZ.resize(npts*npts)
  233. else:
  234. print("Unknown crossplane")
  235. sys.exit()
  236. coord = np.vstack((coordX, coordY, coordZ)).transpose()
  237. terms, E, H = fieldnlay(np.array([x]), np.array([m]), coord, pl=pl
  238. , mode_n=mode_n, mode_type=mode_type
  239. )
  240. Ec = E[0, :, :]
  241. Hc = H[0, :, :]
  242. if inner_only:
  243. mask = [ [np.sum(c**2)>x[-1]**2]*3 for c in coord]
  244. np.place(Ec, mask, 0)
  245. np.place(Hc, mask, 0)
  246. P = []
  247. P = np.array(map(lambda n: np.linalg.norm(np.cross(Ec[n], Hc[n])).real,
  248. range(0, len(E[0]))))
  249. # for n in range(0, len(E[0])):
  250. # P.append(np.linalg.norm( np.cross(Ec[n], np.conjugate(Hc[n]) ).real/2 ))
  251. return Ec, Hc, P, coordPlot1, coordPlot2
  252. ###############################################################################
  253. def fieldplot(fig, ax, x, m, WL, comment='', WL_units=' ', crossplane='XZ',
  254. field_to_plot='Pabs', npts=101, factor=2.1, flow_total=11,
  255. is_flow_extend=True, pl=-1, outline_width=1, subplot_label=' ',
  256. mode_n=-1, mode_type=-1, isStream = False,minlength=0.5 , inner_only = False):
  257. print ("WL=",WL," xm:", x,m)
  258. Ec, Hc, P, coordX, coordZ = GetField(crossplane, npts, factor, x, m, pl, mode_n, mode_type, inner_only)
  259. Er = np.absolute(Ec)
  260. Hr = np.absolute(Hc)
  261. try:
  262. if field_to_plot == 'Pabs':
  263. Eabs_data = np.resize(P, (npts, npts)).T
  264. label = r'$\operatorname{Re}(E \times H)$'
  265. elif field_to_plot == 'Eabs':
  266. Eabs = np.sqrt(Er[:, 0]**2 + Er[:, 1]**2 + Er[:, 2]**2)
  267. label = r'$|E|$'
  268. # Eabs = np.real(Hc[:, 0])
  269. # label = r'$Re(H_x)$'
  270. # Eabs = np.real(Hc[:, 1])
  271. # label = r'$Re(H_y)$'
  272. # Eabs = np.real(Ec[:, 1])
  273. # label = r'$Re(E_y)$'
  274. # Eabs = np.real(Ec[:, 0])
  275. # label = r'$Re(E_x)$'
  276. Eabs_data = np.resize(Eabs, (npts, npts)).T
  277. #Eabs_data = np.flipud(np.resize(Eabs, (npts, npts)))
  278. elif field_to_plot == 'Habs':
  279. Habs = np.sqrt(Hr[:, 0]**2 + Hr[:, 1]**2 + Hr[:, 2]**2)
  280. Eabs_data = 376.73031346177*np.resize(Habs, (npts, npts)).T # multiplied by free space impedance
  281. #Eabs_data = np.resize(Habs, (npts, npts)).T
  282. label = r'$|H|$'
  283. elif field_to_plot == 'angleEx':
  284. Eangle = np.angle(Ec[:, 0]) / np.pi * 180
  285. Eabs_data = np.resize(Eangle, (npts, npts)).T
  286. label = r'$arg(E_x)$'
  287. elif field_to_plot == 'angleHy':
  288. Hangle = np.angle(Hc[:, 1]) / np.pi * 180
  289. Eabs_data = np.resize(Hangle, (npts, npts)).T
  290. label = r'$arg(H_y)$'
  291. # Rescale to better show the axes
  292. scale_x = np.linspace(
  293. min(coordX) * WL / 2.0 / np.pi, max(coordX) * WL / 2.0 / np.pi, npts)
  294. scale_z = np.linspace(
  295. min(coordZ) * WL / 2.0 / np.pi, max(coordZ) * WL / 2.0 / np.pi, npts)
  296. # Define scale ticks
  297. min_tick = np.amin(Eabs_data[~np.isnan(Eabs_data)])
  298. #min_tick = 0.1
  299. max_tick = np.amax(Eabs_data[~np.isnan(Eabs_data)])
  300. #max_tick = 60
  301. scale_ticks = np.linspace(min_tick, max_tick, 5)
  302. #scale_ticks = np.power(10.0, np.linspace(np.log10(min_tick), np.log10(max_tick), 6))
  303. #scale_ticks = [0.1,0.3,1,3,10, max_tick]
  304. # Interpolation can be 'nearest', 'bilinear' or 'bicubic'
  305. #ax.set_title(label)
  306. # build a rectangle in axes coords
  307. ax.annotate(subplot_label, xy=(0.0, 1.1), xycoords='axes fraction', # fontsize=10,
  308. horizontalalignment='left', verticalalignment='top')
  309. # ax.text(right, top, subplot_label,
  310. # horizontalalignment='right',
  311. # verticalalignment='bottom',
  312. # transform=ax.transAxes)
  313. cax = ax.imshow(Eabs_data
  314. #, interpolation='nearest'
  315. , interpolation='none'
  316. #, interpolation='quadric'
  317. , cmap=cm.jet,
  318. origin='lower', vmin=min_tick, vmax=max_tick, extent=(min(scale_x), max(scale_x), min(scale_z), max(scale_z))
  319. # ,norm = LogNorm()
  320. )
  321. ax.axis("image")
  322. # # Add colorbar
  323. # cbar = fig.colorbar(cax, ticks=[a for a in scale_ticks], ax=ax
  324. # #,fraction=0.45
  325. # )
  326. # # vertically oriented colorbar
  327. # if 'angle' in field_to_plot:
  328. # cbar.ax.set_yticklabels(['%3.0f' % (a) for a in scale_ticks])
  329. # else:
  330. # cbar.ax.set_yticklabels(['%g' % (a) for a in scale_ticks])
  331. # pos = list(cbar.ax.get_position().bounds)
  332. #fig.text(pos[0] - 0.02, 0.925, '|E|/|E$_0$|', fontsize = 14)
  333. lp2 = -10.0
  334. lp1 = -1.0
  335. if crossplane == 'XZ':
  336. ax.set_xlabel('Z, ' + WL_units, labelpad=lp1)
  337. ax.set_ylabel('X, ' + WL_units, labelpad=lp2)
  338. elif crossplane == 'YZ':
  339. ax.set_xlabel('Z, ' + WL_units, labelpad=lp1)
  340. ax.set_ylabel('Y, ' + WL_units, labelpad=lp2)
  341. elif crossplane=='XYZ':
  342. ax.set_xlabel(r'$Z,$'+WL_units)
  343. ax.set_ylabel(r'$Y:X,$'+WL_units)
  344. elif crossplane == 'XY':
  345. ax.set_xlabel('X, ' + WL_units, labelpad=lp1)
  346. ax.set_ylabel('Y, ' + WL_units, labelpad=lp2)
  347. # # This part draws the nanoshell
  348. for xx in x:
  349. r = xx * WL / 2.0 / np.pi
  350. s1 = patches.Arc((0, 0), 2.0 * r, 2.0 * r, angle=0.0, zorder=1.8,
  351. theta1=0.0, theta2=360.0, linewidth=outline_width*1.8, color='black')
  352. ax.add_patch(s1)
  353. #
  354. # for flow in range(0,flow_total):
  355. # flow_x, flow_z = GetFlow(scale_x, scale_z, Ec, Hc,
  356. # min(scale_x)+flow*(scale_x[-1]-scale_x[0])/(flow_total-1),
  357. # min(scale_z),
  358. # #0.0,
  359. # npts*16)
  360. # verts = np.vstack((flow_z, flow_x)).transpose().tolist()
  361. # #codes = [Path.CURVE4]*len(verts)
  362. # codes = [Path.LINETO]*len(verts)
  363. # codes[0] = Path.MOVETO
  364. # path = Path(verts, codes)
  365. # patch = patches.PathPatch(path, facecolor='none', lw=1, edgecolor='yellow')
  366. # ax.add_patch(patch)
  367. if (not crossplane == 'XY') and flow_total > 0:
  368. scanSP = np.linspace(-factor * x[-1], factor * x[-1], npts)
  369. min_SP = -factor * x[-1]
  370. step_SP = 2.0 * factor * x[-1] / (flow_total - 1)
  371. x0, y0, z0 = 0, 0, 0
  372. max_length = factor * x[-1] * 10
  373. # max_length=factor*x[-1]*5
  374. max_angle = np.pi / 160
  375. if is_flow_extend:
  376. rg = range(0, flow_total * 5 + 1)
  377. else:
  378. rg = range(0, flow_total)
  379. for flow in rg:
  380. if is_flow_extend:
  381. f = min_SP*2 + flow*step_SP
  382. else:
  383. f = min_SP + flow*step_SP
  384. if crossplane=='XZ':
  385. x0 = f
  386. elif crossplane=='YZ':
  387. y0 = f
  388. elif crossplane=='XYZ':
  389. x0 = 0
  390. y0 = 0
  391. if f > 0:
  392. x0 = f
  393. else:
  394. y0 = f
  395. z0 = min_SP
  396. # x0 = x[-1]/20
  397. flow_xSP, flow_ySP, flow_zSP = GetFlow3D(
  398. x0, y0, z0, max_length, max_angle, x, m, pl)
  399. if crossplane == 'XZ':
  400. flow_z_plot = flow_zSP * WL / 2.0 / np.pi
  401. flow_f_plot = flow_xSP * WL / 2.0 / np.pi
  402. elif crossplane == 'YZ':
  403. flow_z_plot = flow_zSP * WL / 2.0 / np.pi
  404. flow_f_plot = flow_ySP * WL / 2.0 / np.pi
  405. elif crossplane=='XYZ':
  406. if f > 0:
  407. flow_z_plot = flow_zSP*WL/2.0/np.pi
  408. flow_f_plot = flow_xSP*WL/2.0/np.pi
  409. else:
  410. flow_z_plot = flow_zSP*WL/2.0/np.pi
  411. flow_f_plot = flow_ySP*WL/2.0/np.pi
  412. verts = np.vstack(
  413. (flow_z_plot, flow_f_plot)).transpose().tolist()
  414. codes = [Path.LINETO] * len(verts)
  415. codes[0] = Path.MOVETO
  416. path = Path(verts, codes)
  417. #patch = patches.PathPatch(path, facecolor='none', lw=0.2, edgecolor='white',zorder = 2.7)
  418. patch = patches.PathPatch(
  419. path, facecolor='none', lw=outline_width, edgecolor='white', zorder=1.9, alpha=0.7)
  420. # patch = patches.PathPatch(
  421. # path, facecolor='none', lw=0.7, edgecolor='white', zorder=1.9, alpha=0.7)
  422. ax.add_patch(patch)
  423. # ax.plot(flow_z_plot, flow_f_plot, 'x', ms=2, mew=0.1,
  424. # linewidth=0.5, color='k', fillstyle='none')
  425. finally:
  426. terms, Qext, Qsca, Qabs, Qbk, Qpr, g, Albedo, S1, S2 = scattnlay(np.array([x]), np.array([m]))
  427. print("Qsca = " + str(Qsca))
  428. if isStream == True and field_to_plot in ["Eabs", "Habs"]:
  429. if field_to_plot == "Eabs": field = np.real(Ec)
  430. else: field = np.real(Hc)
  431. V = field[:,2].reshape((npts, npts)).T # z-component
  432. if crossplane == "XZ": U = field[:,0].reshape((npts, npts)).T
  433. if crossplane == "YZ": U = field[:,1].reshape((npts, npts)).T
  434. #print(U[:,0])
  435. X = coordX.reshape((npts, npts))[0,:]*WL/2.0/np.pi
  436. Z = coordZ.reshape((npts, npts))[:,0]*WL/2.0/np.pi
  437. lw = np.sqrt((V**2+U**2)/(np.max(V**2+U**2)))
  438. pts2 = npts*3.0
  439. ax.streamplot(X, Z, V, U, color="white", linewidth=lw*4.0+1.2,
  440. #cmap=plt.cm.inferno,
  441. density=0.96502, arrowstyle='->', arrowsize=1.5
  442. ,minlength=minlength
  443. ,maxlength=25
  444. , zorder=1.3
  445. )
  446. #ax.quiver(X[::15], Z[::15], V[::15,::15], U[::15,::15], units='width',color="white")
  447. #