fieldplot.py 16 KB

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