def NuFFT(image, coils, k_traj, w, n, osf, wg): # pad image with zeros to the oversampling size pw = pad_width = int( n*(osf-1)/2 ) image = pad(image, ([pw,pw], [pw,pw], [0,0]), 'constant' ) # Kernel computation kw = wg / osf kosf = floor(0.91 / (osf*1e-3)) kwidth = osf*kw / 2 beta = pi*sqrt((kw*(osf-0.5))**2-0.8) # compute kernel om = arange( floor(kosf*kwidth)+1 ) / floor(kosf*kwidth) p = besseli(0, beta*sqrt(1-om*om)) p /= p[0] p[-1] = 0 # deapodize x = arange(-osf*n/2, osf*n/2, dtype=complex) / n sqa = sqrt(pi*pi * kw*kw * x*x - beta*beta) dax = sin(sqa) / sqa dax /= dax[ int(osf*n/2) ] da = outer(dax.H, dax) image /= da[:,:,newaxis] m = np.zeros((len(k_traj), coils), dtype=complex) # convert k-space trajectory to matrix indices nx = n*osf/2 + osf*n*k_traj[:,0] ny = n*osf/2 + osf*n*k_traj[:,1] # Do the NuFFT for each coil for j in range(coils): # Compute FFT of each coil image kspace = fftc(image[:,:,j]) # loop over samples in kernel at grid spacing for lx in arange(-kwidth, kwidth+1): for ly in arange(-kwidth, kwidth+1): # find nearest samples nxt = around(nx+lx) nyt = around(ny+ly) # separable kernel value kkr = minimum( np.round(kosf * sqrt(abs(nx-nxt)**2+abs(ny-nyt)**2)), floor(kosf * kwidth) ).astype(int) kwr = p[kkr] # if data falls outside matrix, put it at the edge, zero out below nxt = nxt.clip(0,osf*n-1).astype(int) nyt = nyt.clip(0,osf*n-1).astype(int) vector = np.array([kspace[x,y] for x, y in zip(nxt, nyt)]) # accumulate gridded data m[:,j] += vector * kwr return m * sqrt(w)
def NuFFT(image, coils, k_traj, w, n, osf, wg): # pad image with zeros to the oversampling size pw = pad_width = int(n * (osf - 1) / 2) image = pad(image, ([pw, pw], [pw, pw], [0, 0]), 'constant') # Kernel computation kw = wg / osf kosf = floor(0.91 / (osf * 1e-3)) kwidth = osf * kw / 2 beta = pi * sqrt((kw * (osf - 0.5))**2 - 0.8) # compute kernel om = arange(floor(kosf * kwidth) + 1) / floor(kosf * kwidth) p = besseli(0, beta * sqrt(1 - om * om)) p /= p[0] p[-1] = 0 # deapodize x = arange(-osf * n / 2, osf * n / 2, dtype=complex) / n sqa = sqrt(pi * pi * kw * kw * x * x - beta * beta) dax = sin(sqa) / sqa dax /= dax[int(osf * n / 2)] da = outer(dax.H, dax) image /= da[:, :, newaxis] m = np.zeros((len(k_traj), coils), dtype=complex) # convert k-space trajectory to matrix indices nx = n * osf / 2 + osf * n * k_traj[:, 0] ny = n * osf / 2 + osf * n * k_traj[:, 1] # Do the NuFFT for each coil for j in range(coils): # Compute FFT of each coil image kspace = fftc(image[:, :, j]) # loop over samples in kernel at grid spacing for lx in arange(-kwidth, kwidth + 1): for ly in arange(-kwidth, kwidth + 1): # find nearest samples nxt = around(nx + lx) nyt = around(ny + ly) # separable kernel value kkr = minimum( np.round(kosf * sqrt(abs(nx - nxt)**2 + abs(ny - nyt)**2)), floor(kosf * kwidth)).astype(int) kwr = p[kkr] # if data falls outside matrix, put it at the edge, zero out below nxt = nxt.clip(0, osf * n - 1).astype(int) nyt = nyt.clip(0, osf * n - 1).astype(int) vector = np.array([kspace[x, y] for x, y in zip(nxt, nyt)]) # accumulate gridded data m[:, j] += vector * kwr return m * sqrt(w)
def trapes_analytical(h): N=int(round(1.0/h)) # number of unknowns, assuming the RHS boundary value is known x=np.arange(N+1)*h # x = np.linspace(0, 1, N) z0 = 4*sqrt(2) z1 = 8 g = sqrt(2)/8 k1z0, i0z1 = besselk(1, z0), besseli(0, z1) k0z1, i1z0 = besselk(0, z1), besseli(1, z0) k0z0, i0z0 = besselk(0, z0), besseli(0, z0) J = k1z0*i0z1 + k0z1*i1z0 + g*(k0z0*i0z1 - k0z1*i0z0) A = (k1z0 + g*k0z0)/J B = (i1z0 - g*i0z0)/J z = sqrt(32.*(1 + x)) theta = A* besseli(0, z) + B* besselk(0, z) dtheta = 16*(A*besseli(1, z) - B*besselk(1, z))/z return x, theta, dtheta
def set_qpabsb_eff(self, l_fin, h_fin, loverlap, l_TES, eff_absb=1.22e-4): ci = 2 * l_TES ri = ci / (2 * np.pi) a = (l_fin + l_TES) / 2 b = l_fin co1 = 2 * l_TES + 2 * np.pi * l_fin h = ((a - b) / (a + b)) ** 2 co = np.pi * (a + b) * (1 + 3 * h / (10 + np.sqrt(4 - 3 * h))) ro = co1 / (2 * np.pi) # -------- Relevant length scales # Diffusion length ld = 567 * h_fin # µm # Surface impedance length la = 1 /eff_absb*h_fin**2/loverlap # µm la_chk = (1e6 + 1600 / (900 ** 2) * 5) * (h_fin ** 2) # µm # -------- Dimensionless Scales rhoi = ri / ld rhoo = ro / ld lambdaA = la / ld # QP collection coefficient """fQP = 2 * rhoi / (rhoo ** 2 - rhoi ** 2) * \ (iv(1, rhoo) * kv(1, rhoi) - iv(1, rhoi) * kv(1, rhoo)) / \ (iv(1, rhoo) * kv(0, rhoi) + lambdaA * kv(1, rhoi) + (iv(0, rhoi) - lambdaA * iv(1, rhoi)) * kv(1, rhoo)) fQP = 2 * rhoi / (rhoo**2 - rhoi**2) * (iv(1, rhoo) * kv(1, rhoi) - iv(1, rhoi) * kv(1, rhoo)) / \ (iv(1, rhoo) * (kv(0, rhoi) + lambdaA * kv(1, rhoi)) + (iv(0, rhoi) - lambdaA*iv(1, rhoi))*kv(1, rhoo))""" fQP = 2 * rhoi / (rhoo ** 2 - rhoi ** 2) \ *(besseli(1, rhoo) * besselk(1, rhoi) - besseli(1, rhoi) * besselk(1, rhoo)) \ / (besseli(1, rhoo) * (besselk(0, rhoi) + lambdaA * besselk(1, rhoi)) + (besseli(0, rhoi) - lambdaA * besseli(1, rhoi)) * besselk(1, rhoo)) self._eQPabsb = fQP
def pdf(d, x=None, tol=1e-4, n_bins=1e4): n_bins = int(n_bins) x0 = x xmax = chi2.ppf(1 - tol, 1) * sum(d) dx = xmax / n_bins x = np.array(range(n_bins - 20)) * dx nu = len(d) if nu < 3: # Exact solution for 1 or 2 degrees of freedom if x0 is not None: x = x0 if nu == 1: p = chi2.pdf(x / d[0], 1) / d[0] else: a = d[0] b = d[1] p = 1 / (2 * np.sqrt(a * b)) * np.exp(-(a + b) * x / (4 * a * b)) * besseli(0, (a - b) * x / (4 * a * b)) else: # Numerical solution for arbitrary degrees of freedom du = 2 * np.pi / xmax u = np.array(range(n_bins)) * du ug, dg = np.meshgrid(u, d) phi = 1 / np.sqrt(1 - 2 * 1j * ug * dg) phi = np.prod(phi, axis=0) p = (du / np.pi) * np.real(fft.fft(phi)) p = p[:(n_bins - 20)] - min(p) if x0 is not None: p = np.interp(x0, x, p) x = x0 if x0 is None: return p, x else: return p
def NuFFT_adj(data, coils, k_traj, w, n, osf, wg): # width of the kernel on the original grid kw = wg / osf # preweight dw = data * sqrt(w) # compute kernel, assume e1 is 0.001, assuming nearest neighbor kosf = floor(0.91/(osf*1e-3)) # half width in oversampled grid units kwidth = osf * kw / 2 # beta from the Beatty paper beta = pi * sqrt((kw*(osf-0.5))**2 - 0.8) # compute kernel om = arange(floor(kosf*kwidth)+1, dtype=complex) / (kosf*kwidth) p = besseli(0, beta*sqrt(1-om*om)) p /= p[0] p[-1] = 0 # convert k-space trajectory to matrix indices nx = n*osf/2 + osf*n*k_traj[:,0] ny = n*osf/2 + osf*n*k_traj[:,1] im = zeros((osf*n, osf*n, coils), dtype=complex) for j in range(coils): ksp = np.zeros_like(im[:,:,j]) for lx in arange(-kwidth, kwidth+1): for ly in arange(-kwidth, kwidth+1): # find nearest samples nxt = around(nx + lx) nyt = around(ny + ly) # seperable kernel value kkr = minimum( around(kosf * sqrt(abs(nx-nxt)**2 + abs(ny-nyt)**2)), floor(kosf * kwidth) ).astype(int) kwr = p[kkr] # if data falls outside matrix, put it at the edge, zero out below nxt = nxt.round().clip(0,osf*n-1).astype(int) nyt = nyt.round().clip(0,osf*n-1).astype(int) # accumulate gridded data ksp += coo_matrix((dw[:,j]*kwr.H, (nxt, nyt)), shape=(osf*n,osf*n)) # zero out data at edges, which is probably due to data outside mtx ksp[:,0] = ksp[:,-1] = ksp[0,:] = ksp[-1,:] = 0 # do the FFT and deapodize im[:,:,j] = ifftc( ksp ) # deapodize x = arange(-osf*n/2, osf*n/2, dtype=complex) / n sqa = sqrt(pi*pi * kw*kw * x*x - beta**2) dax = sin(sqa) / sqa dax /= dax[ int(osf*n/2) ] da = outer(dax.H, dax) im /= da[:,:,newaxis] # trim the images q = int((osf-1)*n/2) return im[q:n+q, q:n+q]
def NuFFT_adj(data, coils, k_traj, w, n, osf, wg): # width of the kernel on the original grid kw = wg / osf # preweight dw = data * sqrt(w) # compute kernel, assume e1 is 0.001, assuming nearest neighbor kosf = floor(0.91 / (osf * 1e-3)) # half width in oversampled grid units kwidth = osf * kw / 2 # beta from the Beatty paper beta = pi * sqrt((kw * (osf - 0.5))**2 - 0.8) # compute kernel om = arange(floor(kosf * kwidth) + 1, dtype=complex) / (kosf * kwidth) p = besseli(0, beta * sqrt(1 - om * om)) p /= p[0] p[-1] = 0 # convert k-space trajectory to matrix indices nx = n * osf / 2 + osf * n * k_traj[:, 0] ny = n * osf / 2 + osf * n * k_traj[:, 1] im = zeros((osf * n, osf * n, coils), dtype=complex) for j in range(coils): ksp = np.zeros_like(im[:, :, j]) for lx in arange(-kwidth, kwidth + 1): for ly in arange(-kwidth, kwidth + 1): # find nearest samples nxt = around(nx + lx) nyt = around(ny + ly) # seperable kernel value kkr = minimum( around(kosf * sqrt(abs(nx - nxt)**2 + abs(ny - nyt)**2)), floor(kosf * kwidth)).astype(int) kwr = p[kkr] # if data falls outside matrix, put it at the edge, zero out below nxt = nxt.round().clip(0, osf * n - 1).astype(int) nyt = nyt.round().clip(0, osf * n - 1).astype(int) # accumulate gridded data ksp += coo_matrix((dw[:, j] * kwr.H, (nxt, nyt)), shape=(osf * n, osf * n)) # zero out data at edges, which is probably due to data outside mtx ksp[:, 0] = ksp[:, -1] = ksp[0, :] = ksp[-1, :] = 0 # do the FFT and deapodize im[:, :, j] = ifftc(ksp) # deapodize x = arange(-osf * n / 2, osf * n / 2, dtype=complex) / n sqa = sqrt(pi * pi * kw * kw * x * x - beta**2) dax = sin(sqa) / sqa dax /= dax[int(osf * n / 2)] da = outer(dax.H, dax) im /= da[:, :, newaxis] # trim the images q = int((osf - 1) * n / 2) return im[q:n + q, q:n + q]
def calculateEigenPair(a, c0, f, azimuthal_m, radial_n, mode_type, sign=1): if mode_type not in {"Kelvin", "Poincare"}: raise ValueError("Unknown mode_type: '" + mode_type + "'") if sign not in {-1, 1}: raise ValueError("Invalid sign: '" + str(sign) + "'") if mode_type == 'Kelvin' and sign == 1: raise ValueError( "Invalid parameter selection. Kelvin modes with clockwise phase propagation do not exist" ) bracket_factor = 2e-2 eps = 1e-7 PoincareEqn = lambda sig: (a / np.sqrt(c0**2 / (sig**2 - f**2))) * ( (1 / azimuthal_m) * besselj(azimuthal_m - 1, a / np.sqrt(c0**2 / ( sig**2 - f**2))) / (a / np.sqrt(c0**2 / (sig**2 - f**2))) * besselj( azimuthal_m, a / np.sqrt(c0**2 / (sig**2 - f**2)))) - 1 + (f / sig) KelvinEqn = lambda sig: (a / np.sqrt(c0**2 / (f**2 - sig**2))) * ( (1 / azimuthal_m) * besseli(azimuthal_m - 1, a / np.sqrt(c0**2 / ( f**2 - sig**2))) / (a / np.sqrt(c0**2 / (f**2 - sig**2))) * besseli( azimuthal_m, a / np.sqrt(c0**2 / (f**2 - sig**2)))) - 1 + (f / sig) # Calculate sigma if mode_type == 'Poincare' and sign == 1: sig = f for n in range(0, radial_n): rt = root(PoincareEqn, method="bisect", bracket=[(1 + eps) * sig, (1 + bracket_factor) * sig]) if rt.root is None: print( "Did not find an eigenvalue in expected range for Poincare mode with n=" + str(n)) sig = rt.root elif mode_type == 'Poincare' and sign == -1: sig = -f for n in range(0, radial_n): rt = root(PoincareEqn, method="bisect", bracket=[(1 + bracket_factor) * sig, (1 + eps) * sig]) if rt.root is None: print( "Did not find an eigenvalue in expected range for Poincare mode with n=" + str(n)) sig = rt.root else: sig = -f for n in range( 0, radial_n ): # Can also restrict n's on kelvin mode using Csanady's restriction on S. rt = root(KelvinEqn, method="bisect", bracket=[(1 - eps) * sig, -eps]) if rt.root is None: print( "Did not find an eigenvalue in expected range for Kelvin mode with n=" + str(n)) sig = rt.root Theta = lambda theta: np.exp(1j * azimuthal_m * theta) if mode_type == "Kelvin": R = lambda r: besseli(azimuthal_m, np.sqrt((f**2 - sig**2) / c0**2) * r) else: #Poincare R = lambda r: besselj(azimuthal_m, np.sqrt((sig**2 - f**2) / c0**2) * r) eigenfunction = lambda r, theta: R(r) * Theta(theta) return (sig, (lambda r, theta: eigenfunction(r, theta)))
def set_qpabsb_eff_matt(self): """ Calculate the QP collection efficiciency using Matt's method. Sets class atribute slef.eQPabsb Parameters: ----------- eff_absb : float, optional W/Al transmition/trapping probability """ # From Effqp_2D_moffatt.m in Matt's dropbox # Here we are using Robert Moffatt's full QP model. There are some pretty big assumptions: # 1) Diffusion length scales with Al thickness (trapping surface dominated and diffusion thickness limited) # 2) Absorption length scales with Al thickness**2/l_overlap # Future: scale boundary impedance with W thickness # INPUTS: # 1) fin length [um] # 2) fin height [um] # 3) W/Al overlap [um] # 4) TES length [um] # 5) W/Al transmition/trapping probability # OUTPUTS: # 1) Quasi-Particle Collection Efficiency # 2) Diffusion Length # 3) W/Al Surface Absorption Length # 4) W/Al Transmission Probability # https://www.stanford.edu/~rmoffatt/Papers/Diffusion%20and%20Absorption%20with%201D%20and%202D%20Solutions.pdf # DOI: 10.1007/s10909-015-1406-7 # ------------------------------------------------------------------------------------------------------------- l_tes = self.TES.l * 1e6 #convert to [um] l_fin = self.l_fin * 1e6 #convert to [um] h_fin = self.h_fin * 1e6 #convert to [um] l_overlap = self.TES.l_overlap * 1e6 #convert to [um] n_fin = self.TES.n_fin eff_absb = self.eff_absb # We assume pie shaped QP collection fins ci = 2 * l_tes # inner circle circumferance ri = ci / (2 * np.pi) # inner radius # Outer circumferance of "very simplified" ellipse co1 = 2 * l_tes + 2 * np.pi * l_fin # Another approximation... a = (l_fin + l_tes) / 2 b = l_fin # https://www.mathsisfun.com/geometry/ellipse-perimeter.html h = ((a - b) / (a + b))**2 co = np.pi * (a + b) * (1 + 3 * h / (10 + np.sqrt(4 - 3 * h))) ro = co1 / (2 * np.pi) # -------- Relevant length scales ------------ # Diffusion length ld = 567 * h_fin # [µm] this is the fit in Jeff's published LTD 16 data self.ld = ld # Surface impedance length la = (1 / eff_absb) * (h_fin**2 / l_overlap ) # [µm] these match the values used by Noah la_chk = (1e6 + 1600 / (900**2) * 5) * (h_fin**2) # µm self.la = la # -------- Dimensionless Scales ------- rhoi = ri / ld rhoo = ro / ld lambdaA = la / ld # QP collection coefficient fQP = (2 * rhoi / (rhoo ** 2 - rhoi ** 2)) \ *(besseli(1, rhoo) * besselk(1, rhoi) - besseli(1, rhoi) * besselk(1, rhoo)) \ / (besseli(1, rhoo) * (besselk(0, rhoi) + lambdaA * besselk(1, rhoi)) + (besseli(0, rhoi) - lambdaA * besseli(1, rhoi)) * besselk(1, rhoo)) self.eQPabsb = fQP