mtmm.pyx 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. from __future__ import division, print_function, absolute_import
  2. from numpy import cos, inf, zeros, array, exp, conj, nan, isnan, pi, sin
  3. import numpy as np
  4. import scipy as sp
  5. import sys
  6. EPSILON = sys.float_info.epsilon # typical floating-point calculation error
  7. def make_2x2_array(a, b, c, d, dtype=float):
  8. """
  9. Makes a 2x2 numpy array of [[a,b],[c,d]]
  10. Same as "numpy.array([[a,b],[c,d]], dtype=float)", but ten times faster
  11. """
  12. my_array = np.empty((2,2), dtype=dtype)
  13. my_array[0,0] = a
  14. my_array[0,1] = b
  15. my_array[1,0] = c
  16. my_array[1,1] = d
  17. return my_array
  18. def is_forward_angle(n, theta):
  19. """
  20. if a wave is traveling at angle theta from normal in a medium with index n,
  21. calculate whether or not this is the forward-traveling wave (i.e., the one
  22. going from front to back of the stack, like the incoming or outgoing waves,
  23. but unlike the reflected wave). For real n & theta, the criterion is simply
  24. -pi/2 < theta < pi/2, but for complex n & theta, it's more complicated.
  25. See https://arxiv.org/abs/1603.02720 appendix D. If theta is the forward
  26. angle, then (pi-theta) is the backward angle and vice-versa.
  27. """
  28. assert n.real * n.imag >= 0, ("For materials with gain, it's ambiguous which "
  29. "beam is incoming vs outgoing. See "
  30. "https://arxiv.org/abs/1603.02720 Appendix C.\n"
  31. "n: " + str(n) + " angle: " + str(theta))
  32. ncostheta = n * np.cos(theta)
  33. if abs(ncostheta.imag) > 100 * EPSILON:
  34. # Either evanescent decay or lossy medium. Either way, the one that
  35. # decays is the forward-moving wave
  36. answer = (ncostheta.imag > 0)
  37. else:
  38. # Forward is the one with positive Poynting vector
  39. # Poynting vector is Re[n cos(theta)] for s-polarization or
  40. # Re[n cos(theta*)] for p-polarization, but it turns out they're consistent
  41. # so I'll just assume s then check both below
  42. answer = (ncostheta.real > 0)
  43. # convert from numpy boolean to the normal Python boolean
  44. answer = bool(answer)
  45. # double-check the answer ... can't be too careful!
  46. # error_string = ("It's not clear which beam is incoming vs outgoing. Weird"
  47. # " index maybe?\n"
  48. # "n: " + str(n) + " angle: " + str(theta))
  49. # if answer is True:
  50. # assert ncostheta.imag > -100 * EPSILON, error_string
  51. # assert ncostheta.real > -100 * EPSILON, error_string
  52. # assert (n * cos(theta.conjugate())).real > -100 * EPSILON, error_string
  53. # else:
  54. # assert ncostheta.imag < 100 * EPSILON, error_string
  55. # assert ncostheta.real < 100 * EPSILON, error_string
  56. # assert (n * cos(theta.conjugate())).real < 100 * EPSILON, error_string
  57. return answer
  58. def snell(n_1, n_2, th_1):
  59. """
  60. return angle theta in layer 2 with refractive index n_2, assuming
  61. it has angle th_1 in layer with refractive index n_1. Use Snell's law. Note
  62. that "angles" may be complex!!
  63. """
  64. # Important that the arcsin here is scipy.arcsin, not numpy.arcsin! (They
  65. # give different results e.g. for arcsin(2).)
  66. th_2_guess = sp.arcsin(n_1*np.sin(th_1) / n_2)
  67. if is_forward_angle(n_2, th_2_guess):
  68. return th_2_guess
  69. else:
  70. return pi - th_2_guess
  71. def list_snell(n_list, th_0):
  72. """
  73. return list of angle theta in each layer based on angle th_0 in layer 0,
  74. using Snell's law. n_list is index of refraction of each layer. Note that
  75. "angles" may be complex!!
  76. """
  77. # Important that the arcsin here is scipy.arcsin, not numpy.arcsin! (They
  78. # give different results e.g. for arcsin(2).)
  79. angles = sp.arcsin(n_list[0]*np.sin(th_0) / n_list)
  80. # The first and last entry need to be the forward angle (the intermediate
  81. # layers don't matter, see https://arxiv.org/abs/1603.02720 Section 5)
  82. if not is_forward_angle(n_list[0], angles[0]):
  83. angles[0] = pi - angles[0]
  84. if not is_forward_angle(n_list[-1], angles[-1]):
  85. angles[-1] = pi - angles[-1]
  86. return angles
  87. def interface_r(polarization, n_i, n_f, th_i, th_f):
  88. """
  89. reflection amplitude (from Fresnel equations)
  90. polarization is either "s" or "p" for polarization
  91. n_i, n_f are (complex) refractive index for incident and final
  92. th_i, th_f are (complex) propegation angle for incident and final
  93. (in radians, where 0=normal). "th" stands for "theta".
  94. """
  95. if polarization == 's':
  96. return ((n_i * np.cos(th_i) - n_f * np.cos(th_f)) /
  97. (n_i * np.cos(th_i) + n_f * np.cos(th_f)))
  98. else:
  99. return ((n_f * np.cos(th_i) - n_i * np.cos(th_f)) /
  100. (n_f * np.cos(th_i) + n_i * np.cos(th_f)))
  101. def interface_t(polarization, n_i, n_f, th_i, th_f):
  102. """
  103. transmission amplitude (frem Fresnel equations)
  104. polarization is either "s" or "p" for polarization
  105. n_i, n_f are (complex) refractive index for incident and final
  106. th_i, th_f are (complex) propegation angle for incident and final
  107. (in radians, where 0=normal). "th" stands for "theta".
  108. """
  109. if polarization == 's':
  110. return 2 * n_i * np.cos(th_i) / (n_i * np.cos(th_i) + n_f * np.cos(th_f))
  111. else:
  112. return 2 * n_i * np.cos(th_i) / (n_f * np.cos(th_i) + n_i * np.cos(th_f))
  113. def coh_tmm(pol, n_list, d_list, float th_0, float lam_vac):
  114. """
  115. Main "coherent transfer matrix method" calc. Given parameters of a stack,
  116. calculates everything you could ever want to know about how light
  117. propagates in it. (If performance is an issue, you can delete some of the
  118. calculations without affecting the rest.)
  119. pol is light polarization, "s" or "p".
  120. n_list is the list of refractive indices, in the order that the light would
  121. pass through them. The 0'th element of the list should be the semi-infinite
  122. medium from which the light enters, the last element should be the semi-
  123. infinite medium to which the light exits (if any exits).
  124. th_0 is the angle of incidence: 0 for normal, pi/2 for glancing.
  125. Remember, for a dissipative incoming medium (n_list[0] is not real), th_0
  126. should be complex so that n0 sin(th0) is real (intensity is constant as
  127. a function of lateral position).
  128. d_list is the list of layer thicknesses (front to back). Should correspond
  129. one-to-one with elements of n_list. First and last elements should be "inf".
  130. lam_vac is vacuum wavelength of the light.
  131. * R--reflected wave power (as fraction of incident)
  132. """
  133. # Convert lists to numpy arrays if they're not already.
  134. n_list = array(n_list)
  135. d_list = array(d_list, dtype=float)
  136. cdef int num_layers
  137. num_layers = n_list.size
  138. th_list = list_snell(n_list, th_0)
  139. # kz is the z-component of (complex) angular wavevector for forward-moving
  140. # wave. Positive imaginary part means decaying.
  141. kz_list = 2 * np.pi * n_list * np.cos(th_list) / lam_vac
  142. # delta is the total phase accrued by traveling through a given layer.
  143. # Ignore warning about inf multiplication
  144. delta = kz_list * d_list
  145. # t_list[i,j] and r_list[i,j] are transmission and reflection amplitudes,
  146. # respectively, coming from i, going to j. Only need to calculate this when
  147. # j=i+1. (2D array is overkill but helps avoid confusion.)
  148. t_list = zeros((num_layers, num_layers), dtype=complex)
  149. r_list = zeros((num_layers, num_layers), dtype=complex)
  150. for i in range(num_layers-1):
  151. t_list[i,i+1] = interface_t(pol, n_list[i], n_list[i+1],
  152. th_list[i], th_list[i+1])
  153. r_list[i,i+1] = interface_r(pol, n_list[i], n_list[i+1],
  154. th_list[i], th_list[i+1])
  155. # My M is a bit different than Sernelius's, but Mtilde is the same.
  156. M_list = zeros((num_layers, 2, 2), dtype=complex)
  157. for i in range(1, num_layers-1):
  158. M_list[i] = (1/t_list[i,i+1]) * np.dot(
  159. make_2x2_array(np.exp(-1j*delta[i]), 0, 0, np.exp(1j*delta[i]), dtype=complex),
  160. make_2x2_array(1, r_list[i,i+1], r_list[i,i+1], 1, dtype=complex))
  161. Mtilde = make_2x2_array(1, 0, 0, 1)
  162. for i in range(1, num_layers-1):
  163. Mtilde = np.dot(Mtilde, M_list[i])
  164. Mtilde = np.dot(make_2x2_array(1, r_list[0,1], r_list[0,1], 1, dtype=complex)/t_list[0,1], Mtilde)
  165. # Net complex transmission and reflection amplitudes
  166. r = Mtilde[1,0]/Mtilde[0,0]
  167. # t = 1/Mtilde[0,0]
  168. # # vw_list[n] = [v_n, w_n]. v_0 and w_0 are undefined because the 0th medium
  169. # # has no left interface.
  170. # vw_list = zeros((num_layers, 2), dtype=complex)
  171. # vw = array([[t],[0]])
  172. # vw_list[-1,:] = np.transpose(vw)
  173. # for i in range(num_layers-2, 0, -1):
  174. # vw = np.dot(M_list[i], vw)
  175. # vw_list[i,:] = np.transpose(vw)
  176. return np.abs(r)**2