fieldplot.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  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. import scattnlay
  44. from scattnlay import fieldnlay
  45. from scattnlay import scattnlay
  46. import numpy as np
  47. import cmath
  48. def unit_vector(vector):
  49. """ Returns the unit vector of the vector. """
  50. return vector / np.linalg.norm(vector)
  51. def angle_between(v1, v2):
  52. """ Returns the angle in radians between vectors 'v1' and 'v2'::
  53. >>> angle_between((1, 0, 0), (0, 1, 0))
  54. 1.5707963267948966
  55. >>> angle_between((1, 0, 0), (1, 0, 0))
  56. 0.0
  57. >>> angle_between((1, 0, 0), (-1, 0, 0))
  58. 3.141592653589793
  59. """
  60. v1_u = unit_vector(v1)
  61. v2_u = unit_vector(v2)
  62. angle = np.arccos(np.dot(v1_u, v2_u))
  63. if np.isnan(angle):
  64. if (v1_u == v2_u).all():
  65. return 0.0
  66. else:
  67. return np.pi
  68. return angle
  69. ###############################################################################
  70. def GetFlow3D(x0, y0, z0, max_length, max_angle, x, m, pl):
  71. # Initial position
  72. flow_x = [x0]
  73. flow_y = [y0]
  74. flow_z = [z0]
  75. max_step = x[-1] / 3
  76. min_step = x[0] / 2000
  77. # max_step = min_step
  78. step = min_step * 2.0
  79. if max_step < min_step:
  80. max_step = min_step
  81. coord = np.vstack(([flow_x[-1]], [flow_y[-1]], [flow_z[-1]])).transpose()
  82. terms, E, H = fieldnlay(np.array([x]), np.array([m]), coord, pl=pl)
  83. Ec, Hc = E[0, 0, :], H[0, 0, :]
  84. S = np.cross(Ec, Hc.conjugate()).real
  85. Snorm_prev = S / np.linalg.norm(S)
  86. Sprev = S
  87. length = 0
  88. dpos = step
  89. count = 0
  90. while length < max_length:
  91. count = count + 1
  92. if (count > 4000): # Limit length of the absorbed power streamlines
  93. break
  94. if step < max_step:
  95. step = step * 2.0
  96. r = np.sqrt(flow_x[-1]**2 + flow_y[-1]**2 + flow_z[-1]**2)
  97. while step > min_step:
  98. # Evaluate displacement from previous poynting vector
  99. dpos = step
  100. dx = dpos * Snorm_prev[0]
  101. dy = dpos * Snorm_prev[1]
  102. dz = dpos * Snorm_prev[2]
  103. # Test the next position not to turn\chang size for more than
  104. # max_angle
  105. coord = np.vstack(([flow_x[-1] + dx], [flow_y[-1] + dy],
  106. [flow_z[-1] + dz])).transpose()
  107. terms, E, H = fieldnlay(np.array([x]), np.array([m]), coord, pl=pl)
  108. Ec, Hc = E[0, 0, :], H[0, 0, :]
  109. Eth = max(np.absolute(Ec)) / 1e10
  110. Hth = max(np.absolute(Hc)) / 1e10
  111. for i in xrange(0, len(Ec)):
  112. if abs(Ec[i]) < Eth:
  113. Ec[i] = 0 + 0j
  114. if abs(Hc[i]) < Hth:
  115. Hc[i] = 0 + 0j
  116. S = np.cross(Ec, Hc.conjugate()).real
  117. if not np.isfinite(S).all():
  118. break
  119. Snorm = S / np.linalg.norm(S)
  120. diff = (S - Sprev) / max(np.linalg.norm(S), np.linalg.norm(Sprev))
  121. if np.linalg.norm(diff) < max_angle:
  122. # angle = angle_between(Snorm, Snorm_prev)
  123. # if abs(angle) < max_angle:
  124. break
  125. step = step / 2.0
  126. # 3. Save result
  127. Sprev = S
  128. Snorm_prev = Snorm
  129. dx = dpos * Snorm_prev[0]
  130. dy = dpos * Snorm_prev[1]
  131. dz = dpos * Snorm_prev[2]
  132. length = length + step
  133. flow_x.append(flow_x[-1] + dx)
  134. flow_y.append(flow_y[-1] + dy)
  135. flow_z.append(flow_z[-1] + dz)
  136. return np.array(flow_x), np.array(flow_y), np.array(flow_z)
  137. ###############################################################################
  138. def GetField(crossplane, npts, factor, x, m, pl, mode_n=-1, mode_type=-1, inner_only = False):
  139. """
  140. crossplane: XZ, YZ, XY, or XYZ (half is XZ, half is YZ)
  141. npts: number of point in each direction
  142. factor: ratio of plotting size to outer size of the particle
  143. x: size parameters for particle layers
  144. m: relative index values for particle layers
  145. """
  146. scan = np.linspace(-factor*x[-1], factor*x[-1], npts)
  147. zero = np.zeros(npts*npts, dtype = np.float64)
  148. if crossplane=='XZ':
  149. coordX, coordZ = np.meshgrid(scan, scan)
  150. coordX.resize(npts * npts)
  151. coordZ.resize(npts * npts)
  152. coordY = zero
  153. coordPlot1 = coordX
  154. coordPlot2 = coordZ
  155. elif crossplane == 'YZ':
  156. coordY, coordZ = np.meshgrid(scan, scan)
  157. coordY.resize(npts * npts)
  158. coordZ.resize(npts * npts)
  159. coordX = zero
  160. coordPlot1 = coordY
  161. coordPlot2 = coordZ
  162. elif crossplane == 'XY':
  163. coordX, coordY = np.meshgrid(scan, scan)
  164. coordX.resize(npts * npts)
  165. coordY.resize(npts * npts)
  166. coordZ = zero
  167. coordPlot1 = coordY
  168. coordPlot2 = coordX
  169. elif crossplane=='XYZ':
  170. coordX, coordZ = np.meshgrid(scan, scan)
  171. coordY, coordZ = np.meshgrid(scan, scan)
  172. coordPlot1, coordPlot2 = np.meshgrid(scan, scan)
  173. coordPlot1.resize(npts * npts)
  174. coordPlot2.resize(npts * npts)
  175. half=npts//2
  176. # coordX = np.copy(coordX)
  177. # coordY = np.copy(coordY)
  178. coordX[:,:half]=0
  179. coordY[:,half:]=0
  180. coordX.resize(npts*npts)
  181. coordY.resize(npts*npts)
  182. coordZ.resize(npts*npts)
  183. else:
  184. print("Unknown crossplane")
  185. import sys
  186. sys.exit()
  187. coord = np.vstack((coordX, coordY, coordZ)).transpose()
  188. terms, E, H = fieldnlay(np.array([x]), np.array([m]), coord, pl=pl
  189. , mode_n=mode_n, mode_type=mode_type
  190. )
  191. Ec = E[0, :, :]
  192. Hc = H[0, :, :]
  193. if inner_only:
  194. mask = [ [np.sum(c**2)>x[-1]**2]*3 for c in coord]
  195. np.place(Ec, mask, 0)
  196. np.place(Hc, mask, 0)
  197. P = []
  198. P = np.array(map(lambda n: np.linalg.norm(np.cross(Ec[n], Hc[n])).real,
  199. range(0, len(E[0]))))
  200. # for n in range(0, len(E[0])):
  201. # P.append(np.linalg.norm( np.cross(Ec[n], np.conjugate(Hc[n]) ).real/2 ))
  202. return Ec, Hc, P, coordPlot1, coordPlot2
  203. ###############################################################################
  204. def fieldplot(fig, ax, x, m, WL, comment='', WL_units=' ', crossplane='XZ',
  205. field_to_plot='Pabs', npts=101, factor=2.1, flow_total=11,
  206. is_flow_extend=True, pl=-1, outline_width=1, subplot_label=' ',
  207. mode_n=-1, mode_type=-1, isStream = False,minlength=0.5 , inner_only = False):
  208. print ("WL=",WL," xm:", x,m)
  209. Ec, Hc, P, coordX, coordZ = GetField(crossplane, npts, factor, x, m, pl, mode_n, mode_type, inner_only)
  210. Er = np.absolute(Ec)
  211. Hr = np.absolute(Hc)
  212. try:
  213. from matplotlib import cm
  214. from matplotlib.colors import LogNorm
  215. if field_to_plot == 'Pabs':
  216. Eabs_data = np.resize(P, (npts, npts)).T
  217. label = r'$\operatorname{Re}(E \times H)$'
  218. elif field_to_plot == 'Eabs':
  219. Eabs = np.sqrt(Er[:, 0]**2 + Er[:, 1]**2 + Er[:, 2]**2)
  220. label = r'$|E|$'
  221. # Eabs = np.real(Hc[:, 0])
  222. # label = r'$Re(H_x)$'
  223. # Eabs = np.real(Hc[:, 1])
  224. # label = r'$Re(H_y)$'
  225. # Eabs = np.real(Ec[:, 1])
  226. # label = r'$Re(E_y)$'
  227. # Eabs = np.real(Ec[:, 0])
  228. # label = r'$Re(E_x)$'
  229. Eabs_data = np.resize(Eabs, (npts, npts)).T
  230. #Eabs_data = np.flipud(np.resize(Eabs, (npts, npts)))
  231. elif field_to_plot == 'Habs':
  232. Habs = np.sqrt(Hr[:, 0]**2 + Hr[:, 1]**2 + Hr[:, 2]**2)
  233. Eabs_data = np.resize(Habs, (npts, npts)).T
  234. label = r'$|H|$'
  235. elif field_to_plot == 'angleEx':
  236. Eangle = np.angle(Ec[:, 0]) / np.pi * 180
  237. Eabs_data = np.resize(Eangle, (npts, npts)).T
  238. label = r'$arg(E_x)$'
  239. elif field_to_plot == 'angleHy':
  240. Hangle = np.angle(Hc[:, 1]) / np.pi * 180
  241. Eabs_data = np.resize(Hangle, (npts, npts)).T
  242. label = r'$arg(H_y)$'
  243. # Rescale to better show the axes
  244. scale_x = np.linspace(
  245. min(coordX) * WL / 2.0 / np.pi, max(coordX) * WL / 2.0 / np.pi, npts)
  246. scale_z = np.linspace(
  247. min(coordZ) * WL / 2.0 / np.pi, max(coordZ) * WL / 2.0 / np.pi, npts)
  248. # Define scale ticks
  249. min_tick = np.amin(Eabs_data[~np.isnan(Eabs_data)])
  250. #min_tick = 0.1
  251. max_tick = np.amax(Eabs_data[~np.isnan(Eabs_data)])
  252. #max_tick = 60
  253. scale_ticks = np.linspace(min_tick, max_tick, 5)
  254. #scale_ticks = np.power(10.0, np.linspace(np.log10(min_tick), np.log10(max_tick), 6))
  255. #scale_ticks = [0.1,0.3,1,3,10, max_tick]
  256. # Interpolation can be 'nearest', 'bilinear' or 'bicubic'
  257. #ax.set_title(label)
  258. # build a rectangle in axes coords
  259. ax.annotate(subplot_label, xy=(0.0, 1.1), xycoords='axes fraction', # fontsize=10,
  260. horizontalalignment='left', verticalalignment='top')
  261. # ax.text(right, top, subplot_label,
  262. # horizontalalignment='right',
  263. # verticalalignment='bottom',
  264. # transform=ax.transAxes)
  265. cax = ax.imshow(Eabs_data
  266. #, interpolation='nearest'
  267. , interpolation='none'
  268. #, interpolation='quadric'
  269. , cmap=cm.jet,
  270. origin='lower', vmin=min_tick, vmax=max_tick, extent=(min(scale_x), max(scale_x), min(scale_z), max(scale_z))
  271. # ,norm = LogNorm()
  272. )
  273. ax.axis("image")
  274. # Add colorbar
  275. cbar = fig.colorbar(cax, ticks=[a for a in scale_ticks], ax=ax
  276. #,fraction=0.45
  277. )
  278. # vertically oriented colorbar
  279. if 'angle' in field_to_plot:
  280. cbar.ax.set_yticklabels(['%3.0f' % (a) for a in scale_ticks])
  281. else:
  282. cbar.ax.set_yticklabels(['%g' % (a) for a in scale_ticks])
  283. # pos = list(cbar.ax.get_position().bounds)
  284. #fig.text(pos[0] - 0.02, 0.925, '|E|/|E$_0$|', fontsize = 14)
  285. lp2 = -10.0
  286. lp1 = -1.0
  287. if crossplane == 'XZ':
  288. ax.set_xlabel('Z, ' + WL_units, labelpad=lp1)
  289. ax.set_ylabel('X, ' + WL_units, labelpad=lp2)
  290. elif crossplane == 'YZ':
  291. ax.set_xlabel('Z, ' + WL_units, labelpad=lp1)
  292. ax.set_ylabel('Y, ' + WL_units, labelpad=lp2)
  293. elif crossplane=='XYZ':
  294. ax.set_xlabel(r'$Z,$'+WL_units)
  295. ax.set_ylabel(r'$Y:X,$'+WL_units)
  296. elif crossplane == 'XY':
  297. ax.set_xlabel('X, ' + WL_units, labelpad=lp1)
  298. ax.set_ylabel('Y, ' + WL_units, labelpad=lp2)
  299. # # This part draws the nanoshell
  300. from matplotlib import patches
  301. from matplotlib.path import Path
  302. for xx in x:
  303. r = xx * WL / 2.0 / np.pi
  304. s1 = patches.Arc((0, 0), 2.0 * r, 2.0 * r, angle=0.0, zorder=1.8,
  305. theta1=0.0, theta2=360.0, linewidth=outline_width*1.8, color='black')
  306. ax.add_patch(s1)
  307. #
  308. # for flow in range(0,flow_total):
  309. # flow_x, flow_z = GetFlow(scale_x, scale_z, Ec, Hc,
  310. # min(scale_x)+flow*(scale_x[-1]-scale_x[0])/(flow_total-1),
  311. # min(scale_z),
  312. # #0.0,
  313. # npts*16)
  314. # verts = np.vstack((flow_z, flow_x)).transpose().tolist()
  315. # #codes = [Path.CURVE4]*len(verts)
  316. # codes = [Path.LINETO]*len(verts)
  317. # codes[0] = Path.MOVETO
  318. # path = Path(verts, codes)
  319. # patch = patches.PathPatch(path, facecolor='none', lw=1, edgecolor='yellow')
  320. # ax.add_patch(patch)
  321. if (not crossplane == 'XY') and flow_total > 0:
  322. from matplotlib.path import Path
  323. scanSP = np.linspace(-factor * x[-1], factor * x[-1], npts)
  324. min_SP = -factor * x[-1]
  325. step_SP = 2.0 * factor * x[-1] / (flow_total - 1)
  326. x0, y0, z0 = 0, 0, 0
  327. max_length = factor * x[-1] * 10
  328. # max_length=factor*x[-1]*5
  329. max_angle = np.pi / 160
  330. if is_flow_extend:
  331. rg = range(0, flow_total * 5 + 1)
  332. else:
  333. rg = range(0, flow_total)
  334. for flow in rg:
  335. if is_flow_extend:
  336. f = min_SP*2 + flow*step_SP
  337. else:
  338. f = min_SP + flow*step_SP
  339. if crossplane=='XZ':
  340. x0 = f
  341. elif crossplane=='YZ':
  342. y0 = f
  343. elif crossplane=='XYZ':
  344. x0 = 0
  345. y0 = 0
  346. if f > 0:
  347. x0 = f
  348. else:
  349. y0 = f
  350. z0 = min_SP
  351. # x0 = x[-1]/20
  352. flow_xSP, flow_ySP, flow_zSP = GetFlow3D(
  353. x0, y0, z0, max_length, max_angle, x, m, pl)
  354. if crossplane == 'XZ':
  355. flow_z_plot = flow_zSP * WL / 2.0 / np.pi
  356. flow_f_plot = flow_xSP * WL / 2.0 / np.pi
  357. elif crossplane == 'YZ':
  358. flow_z_plot = flow_zSP * WL / 2.0 / np.pi
  359. flow_f_plot = flow_ySP * WL / 2.0 / np.pi
  360. elif crossplane=='XYZ':
  361. if f > 0:
  362. flow_z_plot = flow_zSP*WL/2.0/np.pi
  363. flow_f_plot = flow_xSP*WL/2.0/np.pi
  364. else:
  365. flow_z_plot = flow_zSP*WL/2.0/np.pi
  366. flow_f_plot = flow_ySP*WL/2.0/np.pi
  367. verts = np.vstack(
  368. (flow_z_plot, flow_f_plot)).transpose().tolist()
  369. codes = [Path.LINETO] * len(verts)
  370. codes[0] = Path.MOVETO
  371. path = Path(verts, codes)
  372. #patch = patches.PathPatch(path, facecolor='none', lw=0.2, edgecolor='white',zorder = 2.7)
  373. patch = patches.PathPatch(
  374. path, facecolor='none', lw=outline_width, edgecolor='white', zorder=1.9, alpha=0.7)
  375. # patch = patches.PathPatch(
  376. # path, facecolor='none', lw=0.7, edgecolor='white', zorder=1.9, alpha=0.7)
  377. ax.add_patch(patch)
  378. # ax.plot(flow_z_plot, flow_f_plot, 'x', ms=2, mew=0.1,
  379. # linewidth=0.5, color='k', fillstyle='none')
  380. finally:
  381. terms, Qext, Qsca, Qabs, Qbk, Qpr, g, Albedo, S1, S2 = scattnlay(
  382. np.array([x]), np.array([m]))
  383. print("Qsca = " + str(Qsca))
  384. if isStream == True and field_to_plot in ["Eabs", "Habs"]:
  385. if field_to_plot == "Eabs": field = np.real(Ec)
  386. else: field = np.real(Hc)
  387. V = field[:,2].reshape((npts, npts)).T # z-component
  388. if crossplane == "XZ": U = field[:,0].reshape((npts, npts)).T
  389. if crossplane == "YZ": U = field[:,1].reshape((npts, npts)).T
  390. #print(U[:,0])
  391. X = coordX.reshape((npts, npts))[0,:]*WL/2.0/np.pi
  392. Z = coordZ.reshape((npts, npts))[:,0]*WL/2.0/np.pi
  393. lw = np.sqrt((V**2+U**2)/(np.max(V**2+U**2)))
  394. pts2 = npts*3.0
  395. ax.streamplot(X, Z, V, U, color="white", linewidth=lw*4.0+1.2,
  396. #cmap=plt.cm.inferno,
  397. density=0.96502, arrowstyle='->', arrowsize=1.5
  398. ,minlength=minlength
  399. ,maxlength=25
  400. , zorder=1.3
  401. )
  402. #ax.quiver(X[::15], Z[::15], V[::15,::15], U[::15,::15], units='width',color="white")
  403. #