Authored by David Mayerich
1 parent da132b67

### Added a coupled wave library containing code to implement Bryn's homogeneous layered model

Showing 2 changed files with 384 additions and 17 deletions
python/coupledwave.py 0 → 100644

 1 +# -*- coding: utf-8 -*- 2 +""" 3 +Created on Thu Sep 6 22:14:36 2018 4 + 5 +@author: david 6 +""" 7 + 8 +import numpy as np 9 +import matplotlib.pyplot as plt 10 + 11 +#evaluate a plane wave for all points in R = [Rx, Ry, Rz] 12 +# E0 [Ex, Ey, Ez] is the electric field vector 13 +# k is the k vector 14 +# n is the refractive index of the material 15 +# R is a list of coordinates in x, y, and z 16 +def planewave2field(E0, k, n, R): 17 + knr = n * (k[0] * R[0] + k[1] * R[1] + k[2] + R[2]) 18 + exp_2pknr = np.exp(1j * 2 * np.pi * knr) 19 + Ex = E0[0] * exp_2pknr 20 + Ey = E0[1] * exp_2pknr 21 + Ez = E0[2] * exp_2pknr 22 + 23 + return [Ex, Ey, Ez] 24 + 25 +def orthogonalize(E, k): 26 + s = np.cross(E, k) 27 + return np.cross(k, s) 28 + 29 +class layersample: 30 + 31 + #generate a layered sample based on a list of layer refractive indices 32 + # layer_ri is a list of complex refractive indices for each layer 33 + # z is the list of boundary positions along z 34 + def __init__(self, layer_ri, z): 35 + self.n = np.array(layer_ri).astype(np.complex128) 36 + self.z = np.array(z) 37 + 38 + #calculate the index of the field component associated with a layer 39 + # l is the layer index [0, L) 40 + # c is the component (x=0, y=1, z=2) 41 + # d is the direction (0 = transmission, 1 = reflection) 42 + def idx(self, l, c, d): 43 + i = l * 6 + d * 3 + c - 3 44 + print(i) 45 + return i 46 + 47 + #create a matrix for a single plane wave specified by k and E 48 + def solve(self, k, E): 49 + 50 + #calculate the wavenumber 51 + kn = np.linalg.norm(k) 52 + 53 + #s is the plane wave direction scaled by the refractive index 54 + s = k / kn * self.n[0] 55 + 56 + #allocate space for the matrix 57 + L = len(self.n) 58 + M = np.zeros((6*(L-1), 6*(L-1)), dtype=np.complex128) 59 + 60 + #allocate space for the RHS vector 61 + b = np.zeros(6*(L-1), dtype=np.complex128) 62 + 63 + #initialize a counter for the equation number 64 + ei = 0 65 + 66 + #calculate the sz component for each layer 67 + self.sz = np.zeros(L, dtype=np.complex128) 68 + for l in range(L): 69 + self.sz[l] = np.sqrt(self.n[l]**2 - s[0]**2 - s[1]**2) 70 + 71 + #set constraints based on Gauss' law 72 + for l in range(0, L): 73 + #sz = np.sqrt(self.n[l]**2 - s[0]**2 - s[1]**2) 74 + 75 + #set the upward components for each layer 76 + # note that layer L-1 does not have a downward component 77 + # David I, Equation 7 78 + if l != L-1: 79 + M[ei, self.idx(l, 0, 1)] = s[0] 80 + M[ei, self.idx(l, 1, 1)] = s[1] 81 + M[ei, self.idx(l, 2, 1)] = -self.sz[l] 82 + ei = ei+1 83 + 84 + #set the downward components for each layer 85 + # note that layer 0 does not have a downward component 86 + # Davis I, Equation 6 87 + if l != 0: 88 + M[ei, self.idx(l, 0, 0)] = s[0] 89 + M[ei, self.idx(l, 1, 0)] = s[1] 90 + M[ei, self.idx(l, 2, 0)] = self.sz[l] 91 + ei = ei+1 92 + 93 + #enforce a continuous field across boundaries 94 + for l in range(1, L): 95 + sz0 = self.sz[l-1] 96 + sz1 = self.sz[l] 97 + A = np.exp(1j * kn * sz0 * (self.z[l] - self.z[l-1])) 98 + if l < L - 1: 99 + B = np.exp(-1j * kn * sz1 * (self.z[l] - self.z[l+1])) 100 + 101 + 102 + #if this is the second layer, use the simplified equations that account for the incident field 103 + if l == 1: 104 + M[ei, self.idx(0, 0, 1)] = 1 105 + M[ei, self.idx(1, 0, 0)] = -1 106 + if L > 2: 107 + M[ei, self.idx(1, 0, 1)] = -B 108 + 109 + b[ei] = -A * E[0] 110 + ei = ei + 1 111 + 112 + M[ei, self.idx(0, 1, 1)] = 1 113 + M[ei, self.idx(1, 1, 0)] = -1 114 + if L > 2: 115 + M[ei, self.idx(1, 1, 1)] = -B 116 + b[ei] = -A * E[1] 117 + ei = ei + 1 118 + 119 + M[ei, self.idx(0, 2, 1)] = s[1] 120 + M[ei, self.idx(0, 1, 1)] = sz0 121 + M[ei, self.idx(1, 2, 0)] = -s[1] 122 + M[ei, self.idx(1, 1, 0)] = sz1 123 + if L > 2: 124 + M[ei, self.idx(1, 2, 1)] = -B*s[1] 125 + M[ei, self.idx(1, 1, 1)] = -B*sz1 126 + b[ei] = A * sz0 * E[1] - A * s[1]*E[2] 127 + ei = ei + 1 128 + 129 + M[ei, self.idx(0, 0, 1)] = -sz0 130 + M[ei, self.idx(0, 2, 1)] = -s[0] 131 + M[ei, self.idx(1, 0, 0)] = -sz1 132 + M[ei, self.idx(1, 2, 0)] = s[0] 133 + if L > 2: 134 + M[ei, self.idx(1, 0, 1)] = B*sz1 135 + M[ei, self.idx(1, 2, 1)] = B*s[0] 136 + b[ei] = A * s[0] * E[2] - A * sz0 * E[0] 137 + ei = ei + 1 138 + 139 + #if this is the last layer, use the simplified equations that exclude reflections from the last layer 140 + elif l == L-1: 141 + M[ei, self.idx(l-1, 0, 0)] = A 142 + M[ei, self.idx(l-1, 0, 1)] = 1 143 + M[ei, self.idx(l, 0, 0)] = -1 144 + ei = ei + 1 145 + 146 + M[ei, self.idx(l-1, 1, 0)] = A 147 + M[ei, self.idx(l-1, 1, 1)] = 1 148 + M[ei, self.idx(l, 1, 0)] = -1 149 + ei = ei + 1 150 + 151 + M[ei, self.idx(l-1, 2, 0)] = A*s[1] 152 + M[ei, self.idx(l-1, 1, 0)] = -A*sz0 153 + M[ei, self.idx(l-1, 2, 1)] = s[1] 154 + M[ei, self.idx(l-1, 1, 1)] = sz0 155 + M[ei, self.idx(l, 2, 0)] = -s[1] 156 + M[ei, self.idx(l, 1, 0)] = sz1 157 + ei = ei + 1 158 + 159 + M[ei, self.idx(l-1, 0, 0)] = A*sz0 160 + M[ei, self.idx(l-1, 2, 0)] = -A*s[0] 161 + M[ei, self.idx(l-1, 0, 1)] = -sz0 162 + M[ei, self.idx(l-1, 2, 1)] = -s[0] 163 + M[ei, self.idx(l, 0, 0)] = -sz1 164 + M[ei, self.idx(l, 2, 0)] = s[0] 165 + ei = ei + 1 166 + #otherwise use the full set of boundary conditions 167 + else: 168 + M[ei, self.idx(l-1, 0, 0)] = A 169 + M[ei, self.idx(l-1, 0, 1)] = 1 170 + M[ei, self.idx(l, 0, 0)] = -1 171 + M[ei, self.idx(l, 0, 1)] = -B 172 + ei = ei + 1 173 + 174 + M[ei, self.idx(l-1, 1, 0)] = A 175 + M[ei, self.idx(l-1, 1, 1)] = 1 176 + M[ei, self.idx(l, 1, 0)] = -1 177 + M[ei, self.idx(l, 1, 1)] = -B 178 + ei = ei + 1 179 + 180 + M[ei, self.idx(l-1, 2, 0)] = A*s[1] 181 + M[ei, self.idx(l-1, 1, 0)] = -A*sz0 182 + M[ei, self.idx(l-1, 2, 1)] = s[1] 183 + M[ei, self.idx(l-1, 1, 1)] = sz0 184 + M[ei, self.idx(l, 2, 0)] = -s[1] 185 + M[ei, self.idx(l, 1, 0)] = sz1 186 + M[ei, self.idx(l, 2, 1)] = -B*s[1] 187 + M[ei, self.idx(l, 1, 1)] = -B*sz1 188 + ei = ei + 1 189 + 190 + M[ei, self.idx(l-1, 0, 0)] = A*sz0 191 + M[ei, self.idx(l-1, 2, 0)] = -A*s[0] 192 + M[ei, self.idx(l-1, 0, 1)] = -sz0 193 + M[ei, self.idx(l-1, 2, 1)] = -s[0] 194 + M[ei, self.idx(l, 0, 0)] = -sz1 195 + M[ei, self.idx(l, 2, 0)] = s[0] 196 + M[ei, self.idx(l, 0, 1)] = B*sz1 197 + M[ei, self.idx(l, 2, 1)] = B*s[0] 198 + ei = ei + 1 199 + 200 + #store the matrix and RHS vector (for debugging) 201 + self.M = M 202 + self.b = b 203 + 204 + #evaluate the linear system 205 + P = np.linalg.solve(M, b) 206 + 207 + #save the results (also for debugging) 208 + self.P = P 209 + 210 + #store the coefficients for each layer 211 + self.Ptrans = [] 212 + self.Prefl = [] 213 + for l in range(L): 214 + if l == 0: 215 + self.Ptrans.append((E[0], E[1], E[2])) 216 + else: 217 + px = P[self.idx(l, 0, 0)] 218 + py = P[self.idx(l, 1, 0)] 219 + pz = P[self.idx(l, 2, 0)] 220 + self.Ptrans.append((px, py, pz)) 221 + 222 + if l == L-1: 223 + self.Prefl.append((0, 0, 0)) 224 + else: 225 + px = P[self.idx(l, 0, 1)] 226 + py = P[self.idx(l, 1, 1)] 227 + pz = P[self.idx(l, 2, 1)] 228 + self.Prefl.append((px, py, pz)) 229 + 230 + #this converts the coefficients into a NumPy array for convenience later on 231 + self.Ptrans = np.array(self.Ptrans) 232 + self.Prefl = np.array(self.Prefl) 233 + 234 + #store values required for evaluation 235 + #store k 236 + self.k = kn 237 + 238 + #store sx and sy 239 + self.s = np.array([s[0], s[1]]) 240 + 241 + self.solved = True 242 + 243 + #evaluate a solved homogeneous substrate 244 + def evaluate(self, X, Y, Z): 245 + 246 + if not self.solved: 247 + print("ERROR: the layered substrate hasn't been solved") 248 + return 249 + 250 + #this code is a bit cumbersome and could probably be optimized 251 + # Basically, it vectorizes everything by creating an image 252 + # such that the value at each pixel is the corresponding layer 253 + # that the pixel resides in. That index is then used to calculate 254 + # the field within the layer 255 + 256 + #allocate space for layer indices 257 + LI = np.zeros(Z.shape, dtype=np.int) 258 + 259 + #find the layer index for each sample point 260 + L = len(self.z) 261 + LI[Z < self.z[0]] = 0 262 + for l in range(L-1): 263 + idx = np.logical_and(Z > self.z[l], Z <= self.z[l+1]) 264 + LI[idx] = l 265 + LI[Z > self.z[-1]] = L - 1 266 + 267 + #calculate the appropriate phase shift for the wave transmitted through the layer 268 + Ph_t = np.exp(1j * self.k * self.sz[LI] * (Z - self.z[LI])) 269 + 270 + #calculate the appropriate phase shift for the wave reflected off of the layer boundary 271 + LIp = LI + 1 272 + LIp[LIp >= L] = 0 273 + Ph_r = np.exp(-1j * self.k * self.sz[LI] * (Z - self.z[LIp])) 274 + Ph_r[LI >= L-1] = 0 275 + 276 + #calculate the phase shift based on the X and Y positions 277 + Ph_xy = np.exp(1j * self.k * (self.s[0] * X + self.s[1] * Y)) 278 + 279 + #apply the phase shifts 280 + Et = self.Ptrans[LI] * Ph_t[:, :, None] 281 + Er = self.Prefl[LI] * Ph_r[:, :, None] 282 + 283 + #add everything together coherently 284 + E = (Et + Er) * Ph_xy[:, :, None] 285 + 286 + #return the electric field 287 + return E 288 + 289 + 290 +#This sample code produces a field similar to Figure 2 in Davis et al., 2010 291 +#set the material properties 292 +depths = [-50, -15, 15, 40] 293 +n = [1.0, 1.4+1j*0.05, 1.4, 1.0] 294 + 295 +#create a layered sample 296 +layers = layersample(n, depths) 297 + 298 +#set the input light parameters 299 +d = np.array([0.2, 0, 1]) 300 +d = d / np.linalg.norm(d) 301 +l = 5.0 302 +k = 2 * np.pi / l * d 303 +E0 = [0, 1, 0] 304 +E0 = orthogonalize(E0, d) 305 + 306 +#solve for the substrate field 307 +layers.solve(k, E0) 308 + 309 +#set the simulation domain 310 +N = 512 311 +M = 1024 312 +D = [-50, 80] 313 + 314 +x = np.linspace(D[0], D[1], N) 315 +z = np.linspace(D[0], D[1], M) 316 +[X, Z] = np.meshgrid(x, z) 317 +Y = np.zeros(X.shape) 318 +E = layers.evaluate(X, Y, Z) 319 + 320 +Er = np.real(E) 321 +I = Er[..., 0] ** 2 + Er[..., 1] **2 + Er[..., 2] ** 2 322 +plt.figure() 323 +plt.set_cmap("afmhot") 324 +plt.imshow(Er[..., 1]) ... ...
stim/image/image.h

 ... ... @@ -33,6 +33,7 @@ class image{ 33 33 34 34 T* img; //pointer to the image data (interleaved RGB for color) 35 35 size_t R[3]; 36 + bool interleaved = true; //by default the data is interleaved 36 37 37 38 inline size_t X() const { return R[1]; } 38 39 inline size_t Y() const { return R[2]; } ... ... @@ -73,13 +74,24 @@ class image{ 73 74 74 75 #ifdef USING_OPENCV 75 76 int cv_type(){ 76 - if(typeid(T) == typeid(unsigned char)) return CV_MAKETYPE(CV_8U, (int)C()); 77 - if(typeid(T) == typeid(char)) return CV_MAKETYPE(CV_8S, (int)C()); 78 - if(typeid(T) == typeid(unsigned short)) return CV_MAKETYPE(CV_16U, (int)C()); 79 - if(typeid(T) == typeid(short)) return CV_MAKETYPE(CV_16S, (int)C()); 80 - if(typeid(T) == typeid(int)) return CV_MAKETYPE(CV_32S, (int)C()); 81 - if(typeid(T) == typeid(float)) return CV_MAKETYPE(CV_32F, (int)C()); 82 - if(typeid(T) == typeid(double)) return CV_MAKETYPE(CV_64F, (int)C()); 77 + if (C() == 1 || C() == 3) { //if the image has 1 or 3 channels, this will work 78 + if (typeid(T) == typeid(unsigned char)) return CV_MAKETYPE(CV_8U, (int)C()); 79 + if (typeid(T) == typeid(char)) return CV_MAKETYPE(CV_8S, (int)C()); 80 + if (typeid(T) == typeid(unsigned short)) return CV_MAKETYPE(CV_16U, (int)C()); 81 + if (typeid(T) == typeid(short)) return CV_MAKETYPE(CV_16S, (int)C()); 82 + if (typeid(T) == typeid(int)) return CV_MAKETYPE(CV_32S, (int)C()); 83 + if (typeid(T) == typeid(float)) return CV_MAKETYPE(CV_32F, (int)C()); 84 + if (typeid(T) == typeid(double)) return CV_MAKETYPE(CV_64F, (int)C()); 85 + } 86 + else if (C() == 4) { //OpenCV only supports saving BGR images - 4-channel isn't supported 87 + if (typeid(T) == typeid(unsigned char)) return CV_MAKETYPE(CV_8U, 3); 88 + if (typeid(T) == typeid(char)) return CV_MAKETYPE(CV_8S, 3); 89 + if (typeid(T) == typeid(unsigned short)) return CV_MAKETYPE(CV_16U, 3); 90 + if (typeid(T) == typeid(short)) return CV_MAKETYPE(CV_16S, 3); 91 + if (typeid(T) == typeid(int)) return CV_MAKETYPE(CV_32S, 3); 92 + if (typeid(T) == typeid(float)) return CV_MAKETYPE(CV_32F, 3); 93 + if (typeid(T) == typeid(double)) return CV_MAKETYPE(CV_64F, 3); 94 + } 83 95 84 96 std::cout<<"ERROR in stim::image::cv_type - no valid data type found"<