def fpm_inf_cube_3x3(dm): print('Computing datacube of FPM influence functions... ') # Compute sampling of the pupil. Assume that it is square. dm.dx_dm = dm.dxi # Default to being centered on a pixel (FFT-style) if not hasattr(dm, 'centering'): dm.centering = 'pixel' # Compute coordinates of original influence function Ninf0 = dm.inf0.shape[0] x_inf0 = np.arange(-(Ninf0-1)/2, (Ninf0+1)/2) * dm.dx_inf0 # True for even- or odd-sized influence function maps as long as they are centered on the array. [Xinf0, Yinf0] = np.meshgrid(x_inf0, x_inf0) # Compute list of initial actuator center coordinates (in actuator widths). # Square grid [dm.Xact, dm.Yact] = np.meshgrid(np.arange(0, dm.Nact)-dm.xcent_dm, np.arange(0, dm.Nact)-dm.ycent_dm) # in actuator widths x_vec = dm.Xact.flatten() y_vec = dm.Yact.flatten() dm.NactTotal = x_vec.size # Total number of actuators in the 2-D array dm.infMaster = dm.inf0 Nbox = dm.inf0.shape[0] dm.Nbox = Nbox print('FPM influence function size =\t%dx%d ' % (Nbox, Nbox)) dm.xy_cent_act = np.vstack((x_vec, y_vec)) # in actuator widths # Pad the pupil to at least the size of the DM(s) surface(s) to allow all # actuators to be located outside the pupil. # (Same for both DMs) # Find actuator farthest from center: dm.r_cent_act = np.sqrt(dm.xy_cent_act[0, :]**2 + dm.xy_cent_act[1, :]**2) dm.rmax = np.max(np.abs(dm.r_cent_act)) dm.absxymax = np.max(np.abs(dm.xy_cent_act)) NpixPerActWidth = dm.dm_spacing / dm.dx_dm dm.NdmPad = ceil_even((dm.Nact+2)*NpixPerActWidth) # prevent indexing outside the array # Compute coordinates (in meters) of the full DM array if dm.centering in 'pixel': dm.x_pupPad = np.arange(-dm.NdmPad/2, dm.NdmPad/2)*dm.dx_dm # meters, coords for the full DM arrays. Origin is centered on a pixel else: dm.x_pupPad = np.arange(-(dm.NdmPad-1)/2, (dm.NdmPad+1)/2)*dm.dx_dm # meters, coords for the full DM arrays. Origin is centered between pixels for an even-sized array dm.y_pupPad = dm.x_pupPad # DM: (use NboxPad-sized postage stamps, # Find the locations of the postage stamps arrays in the larger pupilPad array dm.xy_cent_act_inPix = dm.xy_cent_act*(dm.dm_spacing/dm.dx_dm) # Convert units to pupil-file pixels if not dm.centering in 'pixel': raise ValueError('Not adapted for non-pixel centering.') dm.xy_cent_act_box = np.round(dm.xy_cent_act_inPix) # Center locations of the postage stamps (in between pixels), in actuator widths dm.xy_cent_act_box_inM = dm.xy_cent_act_box*dm.dx_dm # now in meters if Nbox % 2 == 0: dm.xy_box_lowerLeft = dm.xy_cent_act_box + (dm.NdmPad-Nbox)/2 # indices of pixel in lower left of the postage stamp within the whole pupilPad array else: dm.xy_box_lowerLeft = dm.xy_cent_act_box + (dm.NdmPad)/2 - np.floor(Nbox/2) # indices of pixel in lower left of the postage stamp within the whole pupilPad array # Starting coordinates (in actuator widths) for updated influence function. This is # interpixel centered, so do not translate! dm.x_box0 = np.arange(-(Nbox-1)/2, (Nbox+1)/2) * dm.dx_dm [dm.Xbox0, dm.Ybox0] = np.meshgrid(dm.x_box0, dm.x_box0) # meters, interpixel-centered coordinates for the master influence function # Limit the actuators used to those within 1 actuator width of the pupil r_cent_act_box_inM = np.sqrt(dm.xy_cent_act_box_inM[0, :]**2 + dm.xy_cent_act_box_inM[1, :]**2) # Compute and store all the influence functions: dm.inf_datacube = np.zeros((Nbox, Nbox, dm.NactTotal)) # initialize array of influence function "postage stamps" dm.act_ele = np.array([]) # Indices of nonzero-ed actuators for iact in range(dm.NactTotal): dm.act_ele = np.append(dm.act_ele, iact) # Add actuator index to the keeper list dm.inf_datacube[:, :, iact] = dm.inf0 print('done.')
def gen_fpm_poke_cube(dm, mp, dx_dm, **kwargs): """ Compute the datacube of each influence function. Influence functions are cropped down or padded up to the best size for angular spectrum propagation. Parameters ---------- dm : ModelParameters Structure containing parameter values for the DM mp: falco.config.ModelParameters Structure of model parameters dx_dm : float Pixel width [meters] at the DM plane Other Parameters ---------------- NOCUBE : bool Switch that tells function not to compute the datacube of influence functions. Returns ------- None modifies structure "dm" by reference """ if type(mp) is not falco.config.ModelParameters: raise TypeError('Input "mp" must be of type ModelParameters') real_positive_scalar(dx_dm, 'dx_dm', TypeError) if "NOCUBE" in kwargs and kwargs["NOCUBE"]: flagGenCube = False else: flagGenCube = True # Define this flag if it doesn't exist in the older code for square actuator arrays only if not hasattr(dm, 'flag_hex_array'): dm.flag_hex_array = False # Set the order of operations XYZ = True if(hasattr(dm, 'flagZYX')): if(dm.flagZYX): XYZ = False # Compute sampling of the pupil. Assume that it is square. dm.dx_dm = dx_dm dm.dx = dx_dm # Default to being centered on a pixel if not specified if not(hasattr(dm, 'centering')): dm.centering = 'pixel' # Compute coordinates of original influence function Ninf0 = dm.inf0.shape[0] # Number of points across inf func at native res x_inf0 = np.linspace(-(Ninf0-1)/2., (Ninf0-1)/2., Ninf0)*dm.dx_inf0 # True for even- or odd-sized influence function maps as long as they are # centered on the array. [Xinf0, Yinf0] = np.meshgrid(x_inf0, x_inf0) # Number of points across the DM surface at native inf func resolution Ndm0 = falco.util.ceil_even(Ninf0 + (dm.Nact - 1)*(dm.dm_spacing/dm.dx_inf0)) # Number of points across the (un-rotated) DM surface at new, desired res. dm.NdmMin = falco.util.ceil_even(Ndm0*(dm.dx_inf0/dm.dx))+2. # Number of points across the array to fully contain the DM surface at new # desired resolution and z-rotation angle. dm.Ndm = int(ceil_even((abs(np.array([np.sqrt(2.)*np.cos(radians(45.-dm.zrot)), np.sqrt(2.)*np.sin(radians(45.-dm.zrot))])).max())*Ndm0*(dm.dx_inf0/dm.dx))+2) # Compute list of initial actuator center coordinates (in actutor widths). if(dm.flag_hex_array): # Hexagonal, hex-packed grid raise ValueError('flag_hex_array option not implemented yet.') # Nrings = dm.Nrings; # x_vec = []; # y_vec = []; # % row number (rowNum) is 1 for the center row and 2 is above it, etc. # % Nacross is the total number of segments across that row # for rowNum = 1:Nrings # Nacross = 2*Nrings - rowNum # Number of actuators across at that row (for hex tiling in a hex shape) # yval = sqrt(3)/2*(rowNum-1); # bx = Nrings - (rowNum+1)/2 # x offset from origin # # xs = (0:Nacross-1).' - bx # x values are 1 apart # ys = yval*ones(Nacross,1) # same y-value for the entire row # # if(rowNum==1) # x_vec = [x_vec;xs]; # y_vec = [y_vec;ys]; # else # x_vec = [x_vec;xs;xs]; # y_vec = [y_vec;ys;-ys] # rows +/-n have +/- y coordinates # end # end else: # Square grid [actuator widths] [dm.Xact, dm.Yact] = np.meshgrid(np.arange(dm.Nact) - dm.xc, np.arange(dm.Nact)-dm.yc) # # Use order='F' to compare the final datacube to Matlab's output. # # Otherwise, use C ordering for Python FALCO. # x_vec = dm.Xact.reshape(dm.Nact*dm.Nact,order='F') # y_vec = dm.Yact.reshape(dm.Nact*dm.Nact,order='F') x_vec = dm.Xact.reshape(dm.Nact*dm.Nact) y_vec = dm.Yact.reshape(dm.Nact*dm.Nact) dm.NactTotal = x_vec.shape[0] # Total number of actuators in the 2-D array dm.xy_cent_act = np.zeros((2, dm.NactTotal)) # Initialize # Compute the rotation matrix to apply to the influence function and # actuator center locations tlt = np.zeros(3) tlt[0] = radians(dm.xtilt) tlt[1] = radians(dm.ytilt) tlt[2] = radians(-dm.zrot) sa = sin(tlt[0]) ca = cos(tlt[0]) sb = sin(tlt[1]) cb = cos(tlt[1]) sg = sin(tlt[2]) cg = cos(tlt[2]) if XYZ: Mrot = np.array([[cb * cg, sa * sb * cg - ca * sg, ca * sb * cg + sa * sg, 0.0], [cb * sg, sa * sb * sg + ca * cg, ca * sb * sg - sa * cg, 0.0], [-sb, sa * cb, ca * cb, 0.0], [0.0, 0.0, 0.0, 1.0]]) else: Mrot = np.array([ [cb * cg, -cb * sg, sb, 0.0], [ca * sg + sa * sb * cg, ca * cg - sa * sb * sg, -sa * cb, 0.0], [sa * sg - ca * sb * cg, sa * cg + ca * sb * sg, ca * cb, 0.0], [0.0, 0.0, 0.0, 1.0]]) # Compute the actuator center coordinates in units of actuator spacings for iact in range(dm.NactTotal): xyzVals = np.array([x_vec[iact], y_vec[iact], 0., 1.]) xyzValsRot = Mrot @ xyzVals dm.xy_cent_act[0, iact] = xyzValsRot[0].copy() dm.xy_cent_act[1, iact] = xyzValsRot[1].copy() N0 = dm.inf0.shape[0] Npad = falco.util.ceil_odd(np.sqrt(2.)*N0) inf0pad = np.zeros((Npad, Npad)) inf0pad[int(np.ceil(Npad/2.)-np.floor(N0/2.)-1):int(np.ceil(Npad/2.)+np.floor(N0/2.)), int(np.ceil(Npad/2.)-np.floor(N0/2.)-1):int(np.ceil(Npad/2.)+np.floor(N0/2.))] = dm.inf0 ydim = inf0pad.shape[0] xdim = inf0pad.shape[1] xd2 = np.fix(xdim / 2.) + 1 yd2 = np.fix(ydim / 2.) + 1 cx = np.arange(xdim) + 1. - xd2 cy = np.arange(ydim) + 1. - yd2 [Xs0, Ys0] = np.meshgrid(cx, cy) xsNewVec = np.zeros(xdim*xdim) ysNewVec = np.zeros(ydim*ydim) Xs0Vec = Xs0.reshape(xdim*xdim) Ys0Vec = Ys0.reshape(ydim*ydim) for ii in range(Xs0.size): xyzVals = np.array([Xs0Vec[ii], Ys0Vec[ii], 0., 1.]) xyzValsRot = Mrot @ xyzVals xsNewVec[ii] = xyzValsRot[0] ysNewVec[ii] = xyzValsRot[1] # Calculate the interpolated DM grid at the new resolution # (set extrapolated values to 0.0) dm.infMaster = griddata((xsNewVec, ysNewVec), inf0pad.reshape(Npad*Npad), (Xs0, Ys0), method='cubic', fill_value=0.) # Crop down the influence function until it has no zero padding left infSum = np.sum(dm.infMaster) infDiff = 0. counter = 0 while(abs(infDiff) <= 1e-7): counter = counter + 2 infDiff = infSum - np.sum(abs(dm.infMaster[int(counter/2):int(-counter/2), int(counter/2):int(-counter/2)])) # Subtract an extra 2 to negate the extra step that overshoots. counter = counter - 2 Ninf0pad = dm.infMaster.shape[0]-counter if counter == 0: infMaster2 = dm.infMaster.copy() else: # The cropped-down influence function infMaster2 = dm.infMaster[int(counter/2):int(-counter/2), int(counter/2):int(-counter/2)].copy() dm.infMaster = infMaster2 Npad = Ninf0pad # True for even- or odd-sized influence function maps as long as they are # centered on the array. x_inf0 = np.linspace(-(Npad-1)/2, (Npad-1)/2., Npad)*dm.dx_inf0 [Xinf0, Yinf0] = np.meshgrid(x_inf0, x_inf0) # Translate and resample the master influence function to be at each # actuator's location in the pixel grid # Compute the size of the postage stamps. # Number of points across the influence function array at the DM plane's # resolution. Want as even Nbox = falco.util.ceil_even(Ninf0pad*dm.dx_inf0/dx_dm) dm.Nbox = Nbox # Also compute their padded sizes for the angular spectrum (AS) propagation # between P2 and DM1 or between DM1 and DM2 # Minimum number of points across for accurate angular spectrum propagation Nmin = falco.util.ceil_even(np.max(mp.sbp_centers)*np.max(np.abs(np.array( [mp.d_P2_dm1, mp.d_dm1_dm2, (mp.d_P2_dm1+mp.d_dm1_dm2)])))/dx_dm**2) # Use a larger array if the max sampling criterion for angular spectrum # propagation is violated dm.NboxAS = np.max(np.array([Nbox, Nmin])) # Pad the pupil to at least the size of the DM(s) surface(s) to allow all # actuators to be located outside the pupil. # (Same for both DMs) # Find actuator farthest from center: dm.r_cent_act = np.sqrt(dm.xy_cent_act[0, :]**2 + dm.xy_cent_act[1, :]**2) dm.rmax = np.max(np.abs(dm.r_cent_act)) NpixPerAct = dm.dm_spacing/dx_dm if(dm.flag_hex_array): # padded 2 actuators past the last actuator center to avoid trying to # index outside the array dm.NdmPad = falco.util.ceil_even((2.*(dm.rmax+2))*NpixPerAct + 1) else: # DM surface array padded by the width of the padded influence function # to prevent indexing outside the array. # The 1/2 term is because the farthest actuator center is still half an # actuator away from the nominal array edge. dm.NdmPad = falco.util.ceil_even((dm.NboxAS + 2.0*(1 + (np.max( np.abs(dm.xy_cent_act.reshape(2*dm.NactTotal)))+0.5)*NpixPerAct))) # Compute coordinates (in meters) of the full DM array if(dm.centering == 'pixel'): # meters, coords for the full DM arrays. Origin is centered on a pixel dm.x_pupPad = np.linspace(-dm.NdmPad/2., (dm.NdmPad/2. - 1), dm.NdmPad)*dx_dm else: # meters, coords for the full DM arrays. Origin is interpixel centered dm.x_pupPad = np.linspace(-(dm.NdmPad-1)/2., (dm.NdmPad-1)/2., dm.NdmPad)*dx_dm dm.y_pupPad = dm.x_pupPad # Make NboxPad-sized postage stamps for each actuator's influence function if(flagGenCube): if not dm.flag_hex_array: print(" Influence function padded from %d to %d points for A.S. propagation." % (Nbox,dm.NboxAS)) print('Computing datacube of DM influence functions... ', end='') # Find the locations of the postage stamps arrays in the larger pupilPad array dm.xy_cent_act_inPix = dm.xy_cent_act*(dm.dm_spacing/dx_dm) # Convert units to pupil-plane pixels dm.xy_cent_act_inPix = dm.xy_cent_act_inPix + 0.5 # For the half-pixel offset if pixel centered. dm.xy_cent_act_box = np.round(dm.xy_cent_act_inPix) # Center locations of the postage stamps (in between pixels), in actuator widths dm.xy_cent_act_box_inM = dm.xy_cent_act_box*dx_dm # now in meters dm.xy_box_lowerLeft = dm.xy_cent_act_box + (dm.NdmPad-Nbox)/2 - 0 # index of pixel in lower left of the postage stamp within the whole pupilPad array. +0 for Python, +1 for Matlab # Starting coordinates (in actuator widths) for updated master influence function. # This is interpixel centered, so do not translate! dm.x_box0 = np.linspace(-(Nbox-1)/2., (Nbox-1)/2., Nbox)*dx_dm [dm.Xbox0, dm.Ybox0] = np.meshgrid(dm.x_box0, dm.x_box0) # meters, interpixel-centered coordinates for the master influence function # (Allow for later) Limit the actuators used to those within 1 actuator width of the pupil r_cent_act_box_inM = np.sqrt(dm.xy_cent_act_box_inM[0, :]**2 + dm.xy_cent_act_box_inM[1, :]**2) # Compute and store all the influence functions: dm.inf_datacube = np.zeros((Nbox, Nbox, dm.NactTotal)) # initialize array of influence function "postage stamps" dm.act_ele = np.arange(dm.NactTotal) # Initialize as including all actuators inf_datacube = np.zeros((dm.NactTotal, Nbox, Nbox)) interp_spline = RectBivariateSpline(x_inf0, x_inf0, dm.infMaster) # RectBivariateSpline is faster in 2-D than interp2d # Refer to https://scipython.com/book/chapter-8-scipy/examples/two-dimensional-interpolation-with-scipyinterpolaterectbivariatespline/ for iact in range(dm.NactTotal): xbox = dm.x_box0 - (dm.xy_cent_act_inPix[0, iact]-dm.xy_cent_act_box[0, iact])*dx_dm # X = X0 -(x_true_center-x_box_center) ybox = dm.x_box0 - (dm.xy_cent_act_inPix[1, iact]-dm.xy_cent_act_box[1, iact])*dx_dm # Y = Y0 -(y_true_center-y_box_center) dm.inf_datacube[:, :, iact] = interp_spline(ybox, xbox) inf_datacube[iact, :, :] = interp_spline(ybox, xbox) print('done.') else: dm.act_ele = np.arange(dm.NactTotal)
def setup_fpm_cosine(mp): if mp.F3.full.res != mp.F3.compact.res: raise ValueError('Resolution at F3 must be same for cosine basis set.') # Centering of DM surfaces on array mp.dm8.centering = mp.centering mp.dm9.centering = mp.centering mp.dm9.compact = mp.dm9 mp.dm9.dxi = (mp.fl*mp.lambda0/mp.P2.D)/mp.F3.full.res # width of a pixel at the FPM in the full model (meters) mp.dm9.compact.dxi = (mp.fl*mp.lambda0/mp.P2.D)/mp.F3.compact.res # width of a pixel at the FPM in the compact model (meters) drCos = 1/mp.dm9.actres # Width and double-separation of the cosine rings [lambda0/D] Nrad = int(np.ceil(2*mp.dm9.actres*mp.F3.Rin)) # Generate datacube of influence functions, which are rings with radial cosine profile # Compact model mp.dm9.compact.NdmPad = ceil_even(1+2*mp.F3.Rin*mp.F3.compact.res) NbeamCompact = mp.dm9.compact.NdmPad mp.dm9.NdmPad = NbeamCompact mp.dm9.compact.Nbox = mp.dm9.compact.NdmPad # the modes take up the full array. # Normalized coordinates: Compact model if mp.centering == 'pixel': xc = np.arange(-mp.dm9.compact.NdmPad/2, mp.dm9.compact.NdmPad/2)/mp.F3.compact.res elif mp.centering == 'interpixel': xc = np.arange(-(mp.dm9.compact.NdmPad-1)/2, (mp.dm9.compact.NdmPad+1)/2)/mp.F3.compact.res [Xc, Yc] = np.meshgrid(xc, xc) Rc, THETAc = cart2pol(Xc, Yc) # Hyper-gaussian rolloff at edge of occulter hg_expon = 44 # Found empirically apRad = mp.F3.Rin/(mp.F3.Rin+0.1) # Found empirically OD = 1 mask = Rc <= mp.F3.Rin windowFull = mask*np.exp(-(Rc/mp.F3.Rin/(apRad*OD))**hg_expon) drSep = drCos/2 min_azimSize = mp.min_azimSize_dm9 # [microns] pow_arr = np.arange(2, 62, 2)*6 numdivCos = 2 countCos = 0 start_rad = int(np.floor(mp.dm9.actres/2))+1 for ri in range(start_rad, Nrad+1): for _iter in range(numdivCos+1): countCos += 1 numdivSin = 3 countSin = 0 start_rad = int(np.floor(mp.dm9.actres/2))+1 for ri in range(start_rad, Nrad+1): for _iter in range(numdivSin+1): countSin += 1 mp.dm9.NactTotal = Nrad + countCos + countSin mp.dm9.compact.inf_datacube = np.zeros((mp.dm9.compact.NdmPad, mp.dm9.compact.NdmPad, mp.dm9.NactTotal)) # Compute the ring influence functions for ri in range(1, Nrad+1): modeTemp = windowFull * (1 + (-1)**((ri+1) % 2) * np.cos(2*np.pi*(Rc*mp.dm9.actres-0.5)))/2 rMin = drSep*(ri - 1) rMax = drSep*(ri + 1) if ri == 1: # Fill in the center modeTemp[Rc < drSep] = 1 else: modeTemp[Rc < rMin] = 0 modeTemp[Rc > rMax] = 0 mp.dm9.compact.inf_datacube[:, :, ri-1] = modeTemp # for ri in range(1, Nrad+1): # infFunc = mp.dm9.compact.inf_datacube[:, :, ri-1] # plt.imshow(infFunc); plt.colorbar(); plt.pause(0.05) beamRad = NbeamCompact/2 if mp.centering == 'pixel': x = np.arange(-beamRad, beamRad) elif mp.centering == 'interpixel': x = np.arange((NbeamCompact-1)/2, (NbeamCompact+1)/2) X, Y = np.meshgrid(x, x) RHO, THETA = cart2pol(X, Y) THETA2 = THETA+np.pi/3*2 THETA3 = THETA+np.pi/3*4 THETA4 = THETA+np.pi/3 THETA5 = THETA+np.pi/3*3 THETA6 = np.fliplr(THETA) apRad = mp.F3.Rin/(mp.F3.Rin+0.1) # Found empirically OD = 1 mask = Rc <= mp.F3.Rin # Cosine basis numdiv = 2 count = 0 # index counter for ri in np.arange(start_rad, Nrad+1): modeTemp = windowFull * (1 + (-1)**((ri+1) % 2) * np.cos(2*np.pi*(Rc*mp.dm9.actres-0.5)))/2 rMin = drSep*(ri - 1) rMax = drSep*(ri + 1) if ri == 1: # Fill in the center modeTemp[Rc < drSep] = 1 else: modeTemp[Rc < rMin] = 0 modeTemp[Rc > rMax] = 0 for II in range(numdiv+1): # Choose power for number of lobes powmin = 2 * np.pi * rMin / min_azimSize * 18 aux = pow_arr - powmin aux[aux < 0] = np.Inf ind_mi = np.argmin(aux) power = pow_arr[ind_mi] # cosFull = np.cos(THETA*power) + 1 numdiv = int(power/6) dth = 2*np.pi/power th_arr = np.linspace(np.pi/2, np.pi/2+np.pi/3, numdiv+1) th_rev_arr = np.linspace(np.pi/2+np.pi/3, np.pi/2, numdiv+1) th = th_arr[II] th_rev = th_rev_arr[II] ind = np.logical_and(THETA < (th+dth/2), THETA > (th-dth/2)) ind_rev = np.logical_and(THETA4 < (th_rev+dth/2), THETA4 > (th_rev-dth/2)) ind2 = np.logical_and(THETA2 < (th+dth/2), THETA2 > (th-dth/2)) ind2_rev = np.logical_and(THETA5 < (th_rev+dth/2), THETA5 > (th_rev-dth/2)) ind3 = np.logical_and(THETA3 < (th+dth/2), THETA3 > (th-dth/2)) ind3_rev = np.logical_and(THETA6 < (th+dth/2-np.pi/2-np.pi/3/2), THETA6 > (th-dth/2-np.pi/2-np.pi/3/2)) indTot = np.logical_or(ind, ind2) indTot = np.logical_or(indTot, ind3) indTot = np.logical_or(indTot, ind_rev) indTot = np.logical_or(indTot, ind2_rev) indTot = np.logical_or(indTot, ind3_rev) cosII = cosFull * indTot * modeTemp / 2 mp.dm9.compact.inf_datacube[:, :, Nrad+count] = cosII count += 1 # Sin basis numdiv = 3 for ri in np.arange(start_rad, Nrad+1): modeTemp = windowFull * (1 + (-1)**((ri+1) % 2) * np.cos(2*np.pi*(Rc*mp.dm9.actres-0.5)))/2 rMin = drSep*(ri - 1) rMax = drSep*(ri + 1) if ri == 1: # Fill in the center modeTemp[Rc < drSep] = 1 else: modeTemp[Rc < rMin] = 0 modeTemp[Rc > rMax] = 0 for II in range(numdiv+1): # Choose power for number of lobes powmin = 2*np.pi*rMin/min_azimSize*18 aux = pow_arr - powmin aux[aux < 0] = np.Inf ind_mi = np.argmin(aux) power = pow_arr[ind_mi] # disp(['Number of lobes',num2str(pow)]) cosFull = -np.cos(THETA*power)+1 dth = 2*np.pi/power th_arr = np.linspace(np.pi/2, np.pi/2+np.pi/3+np.pi/6, numdiv+1) th = th_arr[II] + np.pi/12 if th < np.pi: ind = np.logical_and(THETA < (th+dth/2), THETA > (th-dth/2)) else: ind = np.fliplr(np.logical_and(THETA < (np.pi-th+dth/2), THETA > (np.pi-th-dth/2))) ind2 = np.logical_and(THETA2 < (th+dth/2), THETA2 > (th-dth/2)) ind3 = np.logical_and(THETA3 < (th+dth/2), THETA3 > (th-dth/2)) indTotsin = np.logical_or(ind, ind2) indTotsin = np.logical_or(indTotsin, ind3) cosII = cosFull * indTotsin * modeTemp / 2 mp.dm9.compact.inf_datacube[:, :, Nrad+count] = cosII count += 1 # numdiv = int(power/6) # dth = 2*np.pi/power # th_arr = np.linspace(np.pi/2-np.pi/2/power, np.pi/2+np.pi/3-np.pi/2/power, numdiv+1) # th_rev_arr = np.linspace(np.pi/2+np.pi/3+np.pi/2/power, np.pi/2+np.pi/2/power, numdiv+1) # th = th_arr[II] # th_rev = th_rev_arr[II] # ind = np.logical_and((THETA)<(th+dth/2), (THETA)>(th-dth/2)) # ind_rev = np.logical_and((THETA4)<(th_rev+dth/2), # (THETA4)>(th_rev-dth/2)) # ind2 = np.logical_and((THETA2)<(th+dth/2), (THETA2)>(th-dth/2)) # ind2_rev = np.logical_and((THETA5)<(th_rev+dth/2), # (THETA5)>(th_rev-dth/2)) # ind3 = np.logical_and((THETA3)<(th+dth/2), (THETA3)>(th-dth/2)) # ind3_rev = np.logical_and((THETA6)<(th+dth/2-np.pi/2-np.pi/3/2), # (THETA6)>(th-dth/2-np.pi/2-np.pi/3/2)) # indTot0 = np.logical_or(ind, ind2) # indTot0 = np.logical_or(indTot0, ind3); # indTot_rev = np.logical_or(ind_rev, ind2_rev) # indTot_rev = np.logical_or(indTot_rev, ind3_rev) # # % cosII = zeros(N); # sinII = sinFull*indTot0*modeTemp + sinFull_rev*indTot_rev*modeTemp # # % figure(102);imagesc(sinII);axis image; set(gca,'YDir', 'normal') # # % pause(0.1) # mp.dm9.compact.inf_datacube[:, :, Nrad+count] = sinII # count += 1 mp.dm9.inf_datacube = mp.dm9.compact.inf_datacube mp.dm9.NactTotal = mp.dm9.inf_datacube.shape[2] mp.dm9.VtoH = mp.dm9.VtoHavg*np.ones(mp.dm9.NactTotal) # for ri in range(mp.dm9.NactTotal): # infFunc = mp.dm9.compact.inf_datacube[:, :, ri] # plt.imshow(infFunc); plt.colorbar(); plt.pause(0.01) # infSum = np.sum(mp.dm9.inf_datacube[:, :, Nrad+countCos:-1], 2) # sin # infSum = np.sum(mp.dm9.inf_datacube[:, :, Nrad:Nrad+countCos], 2) # cos # infSum = np.sum(mp.dm9.inf_datacube[:, :, 0:Nrad], 2) # rings # infSum = np.sum(mp.dm9.inf_datacube[:, :, :], 2) # all # plt.figure(); plt.imshow(infSum); plt.colorbar(); plt.pause(1) # Lower-left pixel coordinates are all (1,1) since the Zernikes take up the full array. mp.dm9.xy_box_lowerLeft = np.zeros((2, mp.dm9.NactTotal)) mp.dm9.compact.xy_box_lowerLeft = np.zeros((2, mp.dm9.NactTotal)) mp.dm9.compact.Nbox = NbeamCompact mp.dm9.Nbox = NbeamCompact # Coordinates for the full FPM array [meters] if mp.centering == 'pixel': mp.dm9.compact.x_pupPad = np.arange(-mp.dm9.compact.NdmPad/2, (mp.dm9.compact.NdmPad/2)) * \ mp.dm9.compact.dxi elif mp.centering == 'interpixel': mp.dm9.compact.x_pupPad = np.arange(-(mp.dm9.compact.NdmPad-1)/2, (mp.dm9.compact.NdmPad+1)/2) * \ mp.dm9.compact.dxi mp.dm9.compact.y_pupPad = mp.dm9.compact.x_pupPad # Initial DM9 voltages if not hasattr(mp.dm9, 'V'): mp.dm9.V = np.zeros(mp.dm9.NactTotal) mp.dm9.V[0:Nrad] = mp.dm9.V0coef * np.ones(Nrad) else: mp.dm9.V = mp.DM9V0 mp.dm9.Vmin = np.min(mp.t_diel_nm_vec) # minimum thickness of FPM dielectric layer (nm) mp.dm9.Vmax = np.max(mp.t_diel_nm_vec) # maximum thickness (from one actuator, not of the facesheet) of FPM dielectric layer (nm) # OPTIONS FOR DEFINING DM8 (FPM Metal) mp.dm8.VtoHavg = 1e-9 # gain of DM8 (meters/Volt) mp.dm8.Vmin = np.min(mp.t_metal_nm_vec) # minimum thickness of FPM metal layer (nm) mp.dm8.Vmax = np.max(mp.t_metal_nm_vec) # maximum thickness (from one actuator, not of the facesheet) of FPM metal layer (nm) # DM8 Option 2: Set basis as a single nickel disk. mp.dm8.NactTotal = 1 mp.dm8.act_ele = 1 print('%d actuators in DM8.' % mp.dm8.NactTotal) mp.dm8.VtoH = mp.dm8.VtoHavg * np.ones(mp.dm8.NactTotal) # Gains: volts to meters in surface height; mp.dm8.xy_box_lowerLeft = np.array([0, 0]).reshape((2, 1)) mp.dm8.compact = mp.dm8 if not hasattr(mp.dm8, 'V'): # Initial DM8 voltages mp.dm8.V = mp.dm8.V0coef * np.ones(mp.dm8.NactTotal) else: mp.dm8.V = mp.DM8V0 # Don't define extra actuators and time: if not mp.F3.Rin == mp.F3.RinA: raise ValueError('Change mp.F3.Rin and mp.F3.RinA to be equal to avoid wasting time.') # Copy over some common values from DM9: mp.dm8.dxi = mp.dm9.dxi # Width of a pixel at the FPM in full model (meters) mp.dm8.NdmPad = mp.dm9.NdmPad mp.dm8.Nbox = mp.dm8.NdmPad mp.dm8.compact.dxi = mp.dm9.compact.dxi # Width of a pixel at the FPM in compact model (meters) mp.dm8.compact.NdmPad = mp.dm9.compact.NdmPad mp.dm8.compact.Nbox = mp.dm8.compact.NdmPad # Make or read in DM8 disk for the full model FPMgenInputs = {} FPMgenInputs['pixresFPM'] = mp.F3.full.res # pixels per lambda_c/D FPMgenInputs['rhoInner'] = mp.F3.Rin # radius of inner FPM amplitude spot (in lambda_c/D) FPMgenInputs['rhoOuter'] = np.Inf # radius of outer opaque FPM ring (in lambda_c/D) FPMgenInputs['FPMampFac'] = 0 # amplitude transmission of inner FPM spot FPMgenInputs['centering'] = mp.centering diskFull = np.round(pad_crop(1-falco.mask.gen_annular_FPM(FPMgenInputs), mp.dm8.NdmPad)) mp.dm8.inf_datacube = np.zeros((diskFull.shape[0], diskFull.shape[1], 1)) mp.dm8.inf_datacube[:, :, 0] = diskFull # Make or read in DM8 disk for the compact model FPMgenInputs['pixresFPM'] = mp.F3.compact.res # pixels per lambda_c/D diskCompact = np.round(pad_crop(1-falco.mask.gen_annular_FPM(FPMgenInputs), mp.dm8.compact.NdmPad)) mp.dm8.compact.inf_datacube = np.zeros((diskCompact.shape[0], diskCompact.shape[1], 1)) mp.dm8.compact.inf_datacube[:, :, 0] = diskCompact pass
def setup_fpm(mp): mp.dm9.Nact = ceil_even(2*mp.F3.Rin*mp.dm9.actres) # number of actuators across DM9 (if not in a hex grid) if mp.dm9.inf0name.lower() in '3x3': mp.dm9.inf0 = 1/4 * np.array([[1, 2, 1], [2, 4, 2], [1, 2, 1]]) # influence function mp.dm9.dx_inf0_act = 1/2 # number of inter-actuator widths per pixel # FPM resolution (pixels per lambda0/D) in the compact and full models. mp.F3.compact.res = mp.dm9.actres / mp.dm9.dx_inf0_act mp.F3.full.res = mp.dm9.actres / mp.dm9.dx_inf0_act elif mp.dm9.inf0name.lower() in 'lanczos3': N = 91 xs = np.arange(-(N-1)/2, (N+1)/2)/N*10*0.906 #(-(N-1)/2:(N-1)/2)/N *10*0.906 a = 3 Lx0 = a*np.sin(np.pi*xs)*np.sin(np.pi*xs/a)/(np.pi*xs)**2 Lx0[xs == 0] = 1 Lx = Lx0 Lx[xs >= a] = 0 Lx[xs <= -a] = 0 Lxy = Lx.T @ Lx # The 2-D Lanczos kernel Nhalf = np.ceil(N/2) Nrad = 30 LxyCrop = Lxy[Nhalf-Nrad-1:Nhalf+Nrad, Nhalf-Nrad-1:Nhalf+Nrad] mp.dm9.inf0 = LxyCrop # influence function mp.dm9.dx_inf0_act = 1/10 # number of inter-actuator widths per pixel elif mp.dm9.inf0name.lower() in 'xinetics': mp.dm9.inf0 = 1*fits.getdata('influence_dm5v2.fits') mp.dm9.dx_inf0_act = 1/10 # number of inter-actuator widths per pixel # DM8 and DM9 (Optimizable FPM) Setup # Centering of DM surfaces on array mp.dm8.centering = mp.centering mp.dm9.centering = mp.centering mp.dm9.compact = mp.dm9 if hasattr(mp, 'flagDM9inf3x3'): mp.dm9.xcent_dm = mp.dm9.Nact/2 - 1/2 mp.dm9.ycent_dm = mp.dm9.Nact/2 - 1/2 if mp.centering in 'interpixel': raise ValueError('The 3x3 influence function for DM9 requires a pixel-centered coordinate system.') else: mp.dm9.xcent_dm = mp.dm9.Nact/2 - 1/2 mp.dm9.ycent_dm = mp.dm9.Nact/2 - 1/2 mp.dm9.dm_spacing = 1/mp.dm9.actres*(mp.fl*mp.lambda0/mp.P2.D) # meters, pitch of DM actuators mp.dm9.compact = mp.dm9 mp.dm9.compact.dm_spacing = mp.dm9.dm_spacing # meters, pitch of DM actuators mp.dm9.dx_inf0 = (mp.dm9.dx_inf0_act)*mp.dm9.dm_spacing # meters, sampling of the influence function mp.dm9.compact.dx_inf0 = (mp.dm9.compact.dx_inf0_act)*mp.dm9.compact.dm_spacing # meters, sampling of the influence function mp.dm9.dxi = (mp.fl*mp.lambda0/mp.P2.D)/mp.F3.full.res # width of a pixel at the FPM in the full model (meters) mp.dm9.compact.dxi = (mp.fl*mp.lambda0/mp.P2.D)/mp.F3.compact.res # width of a pixel at the FPM in the compact model (meters) if mp.dm9.inf0.shape[0] == 3: fpm_inf_cube_3x3(mp.dm9) fpm_inf_cube_3x3(mp.dm9.compact) else: fpm_inf_cube(mp.dm9) fpm_inf_cube(mp.dm9.compact) # Zero out DM9 actuators too close to the outer edge (within mp.dm9.FPMbuffer lambda0/D of edge) r_cent_lam0D = mp.dm9.r_cent_act*mp.dm9.dm_spacing/(mp.dm9.dxi)/mp.F3.full.res ## mp.F3.RinA_inds = np.array([], dtype=int) mp.F3.RinAB_inds = np.array([], dtype=int) for ii in range(mp.dm9.NactTotal): # Zero out FPM actuators beyond the allowed radius (mp.F3.Rin) if r_cent_lam0D[ii] > mp.F3.Rin-mp.dm9.FPMbuffer: mp.dm9.inf_datacube[:, :, ii] = np.zeros_like(mp.dm9.inf_datacube[:, :, ii]) mp.dm9.compact.inf_datacube[:, :, ii] = np.zeros_like(mp.dm9.compact.inf_datacube[:, :, ii]) # Get the indices for the actuators within radius mp.F3.RinA if r_cent_lam0D[ii] <= mp.F3.RinA-mp.dm9.FPMbuffer: mp.F3.RinA_inds = np.append(mp.F3.RinA_inds, ii) else: # Get the indices for the actuators between radii mp.F3.RinA and mp.F3.Rin mp.F3.RinAB_inds = np.append(mp.F3.RinAB_inds, ii) print('%d actuators in DM9.' % mp.dm9.NactTotal) mp.dm9.ABfac = 1 # Gain factor between inner and outer FPM regions mp.dm9.VtoHavg = 1e-9 # gain of DM9 (meters/Volt) mp.dm9.VtoH = mp.dm9.VtoHavg * np.ones(mp.dm9.NactTotal) # Gains: volts to meters in surface height; mp.dm9.VtoH[mp.F3.RinAB_inds] = mp.dm9.ABfac * mp.dm9.VtoH[mp.F3.RinAB_inds] if not hasattr(mp.dm9, 'V'): # Initial DM9 voltages mp.dm9.V = np.zeros(mp.dm9.NactTotal) mp.dm9.V[mp.F3.RinA_inds] = mp.dm9.V0coef else: mp.dm9.V = mp.DM9V0 mp.dm9.Vmin = np.min(mp.t_diel_nm_vec) # minimum thickness of FPM dielectric layer (nm) mp.dm9.Vmax = np.max(mp.t_diel_nm_vec) # maximum thickness (from one actuator, not of the facesheet) of FPM dielectric layer (nm) # -OPTIONS FOR DEFINING DM8 (FPM Metal) mp.dm8.VtoHavg = 1e-9 # gain of DM8 (meters/Volt) mp.dm8.Vmin = np.min(mp.t_metal_nm_vec) # minimum thickness of FPM metal layer (nm) mp.dm8.Vmax = np.max(mp.t_metal_nm_vec) # maximum thickness (from one actuator, not of the facesheet) of FPM metal layer (nm) # DM8 Option 2: Set basis as a single nickel disk. mp.dm8.NactTotal = 1 mp.dm8.act_ele = 1 print('%d actuators in DM8.' % mp.dm8.NactTotal) mp.dm8.VtoH = mp.dm8.VtoHavg * np.ones(mp.dm8.NactTotal) # Gains: volts to meters in surface height; mp.dm8.xy_box_lowerLeft = np.array([0, 0]).reshape((2, 1)) mp.dm8.compact = mp.dm8 if not hasattr(mp.dm8, 'V'): # Initial DM8 voltages mp.dm8.V = mp.dm8.V0coef * np.ones(mp.dm8.NactTotal) else: mp.dm8.V = mp.DM8V0 # Don't define extra actuators and time: if not mp.F3.Rin == mp.F3.RinA: raise ValueError('Change mp.F3.Rin and mp.F3.RinA to be equal to avoid wasting time.') # Copy over some common values from DM9: mp.dm8.dxi = mp.dm9.dxi # Width of a pixel at the FPM in full model (meters) mp.dm8.NdmPad = mp.dm9.NdmPad mp.dm8.Nbox = mp.dm8.NdmPad mp.dm8.compact.dxi = mp.dm9.compact.dxi # Width of a pixel at the FPM in compact model (meters) mp.dm8.compact.NdmPad = mp.dm9.compact.NdmPad mp.dm8.compact.Nbox = mp.dm8.compact.NdmPad # Make or read in DM8 disk for the full model FPMgenInputs = {} FPMgenInputs['pixresFPM'] = mp.F3.full.res # pixels per lambda_c/D FPMgenInputs['rhoInner'] = mp.F3.Rin # radius of inner FPM amplitude spot (in lambda_c/D) FPMgenInputs['rhoOuter'] = np.Inf # radius of outer opaque FPM ring (in lambda_c/D) FPMgenInputs['FPMampFac'] = 0 # amplitude transmission of inner FPM spot FPMgenInputs['centering'] = mp.centering diskFull = np.round(pad_crop(1-falco.mask.gen_annular_FPM(FPMgenInputs), mp.dm8.NdmPad)) mp.dm8.inf_datacube = np.zeros((diskFull.shape[0], diskFull.shape[1], 1)) mp.dm8.inf_datacube[:, :, 0] = diskFull # Make or read in DM8 disk for the compact model FPMgenInputs['pixresFPM'] = mp.F3.compact.res # pixels per lambda_c/D diskCompact = np.round(pad_crop(1-falco.mask.gen_annular_FPM(FPMgenInputs), mp.dm8.compact.NdmPad)) mp.dm8.compact.inf_datacube = np.zeros((diskCompact.shape[0], diskCompact.shape[1], 1)) mp.dm8.compact.inf_datacube[:, :, 0] = diskCompact # Zero out parts of DM9 actuators that go outside the nickel disk. # Also apply the grayscale edge. DM8windowFull = diskFull DM8windowCompact = diskCompact for iact in range(mp.dm9.NactTotal): if np.sum(np.abs(mp.dm9.inf_datacube[:, :, iact])) >= 1e-8 and np.abs(mp.dm9.VtoH[iact]) >= 1e-13: y_box_ind = np.arange(mp.dm9.xy_box_lowerLeft[0, iact], mp.dm9.xy_box_lowerLeft[0, iact]+mp.dm9.Nbox, dtype=int) # x-indices in pupil arrays for the box x_box_ind = np.arange(mp.dm9.xy_box_lowerLeft[1, iact], mp.dm9.xy_box_lowerLeft[1, iact]+mp.dm9.Nbox, dtype=int) # y-indices in pupil arrays for the box mp.dm9.inf_datacube[:, :, iact] = DM8windowFull[np.ix_(x_box_ind, y_box_ind)] * mp.dm9.inf_datacube[:, :, iact] y_box_ind = np.arange(mp.dm9.compact.xy_box_lowerLeft[0, iact], mp.dm9.compact.xy_box_lowerLeft[0, iact]+mp.dm9.compact.Nbox, dtype=int) # x-indices in pupil arrays for the box x_box_ind = np.arange(mp.dm9.compact.xy_box_lowerLeft[1, iact], mp.dm9.compact.xy_box_lowerLeft[1, iact]+mp.dm9.compact.Nbox, dtype=int) # y-indices in pupil arrays for the box mp.dm9.compact.inf_datacube[:, :, iact] = DM8windowCompact[np.ix_(x_box_ind, y_box_ind)] * mp.dm9.compact.inf_datacube[:, :, iact] pass
def mft_p2v2p(pupilPre, charge, beamRadius, inVal, outVal): """ Propagate from the pupil plane before a vortex FPM to pupil plane after it. Compute a radial Tukey window for propagating through a vortex coroangraph. Parameters ---------- pupilPre : array_like 2-D E-field at pupil plane before the vortex focal plane mask charge : int, float Charge of the vortex mask beamRadius : float Beam radius at pupil plane. Units of pixels. inVal : float Ask Gary outVal : float Ask Gary Returns ------- pupilPost : array_like 2-D E-field at pupil plane after the vortex focal plane mask """ check.twoD_array(pupilPre, 'pupilPre', TypeError) check.scalar_integer(charge, 'charge', TypeError) check.real_positive_scalar(beamRadius, 'beamRadius', TypeError) check.real_positive_scalar(inVal, 'inVal', TypeError) check.real_positive_scalar(outVal, 'outVal', TypeError) # showPlots2debug = False D = 2.0*beamRadius lambdaOverD = 4. # samples per lambda/D NA = pupilPre.shape[1] NB = util.ceil_even(lambdaOverD*D) # [X,Y] = np.meshgrid(np.arange(-NB/2., NB/2., dtype=float),np.arange(-NB/2., NB/2., dtype=float)) # [RHO,THETA] = util.cart2pol(Y,X) RHO = util.radial_grid(np.arange(-NB/2., NB/2., dtype=float)) windowKnee = 1.-inVal/outVal windowMask1 = gen_tukey_for_vortex(2*outVal*lambdaOverD, RHO, windowKnee) windowMask2 = gen_tukey_for_vortex(NB, RHO, windowKnee) # DFT vectors x = np.arange(-NA/2,NA/2,dtype=float)/D #(-NA/2:NA/2-1)/D u1 = np.arange(-NB/2,NB/2,dtype=float)/lambdaOverD #(-NB/2:NB/2-1)/lambdaOverD u2 = np.arange(-NB/2,NB/2,dtype=float)*2*outVal/NB # (-NB/2:NB/2-1)*2*outVal/N FPM = falco_gen_vortex_mask(charge, NB) #if showPlots2debug; figure;imagesc(abs(pupilPre));axis image;colorbar; title('pupil'); end; ## Low-sampled DFT of entire region FP1 = 1/(1*D*lambdaOverD)*np.exp(-1j*2*np.pi*np.outer(u1,x)) @ pupilPre @ np.exp(-1j*2*np.pi*np.outer(x,u1)) #if showPlots2debug; figure;imagesc(log10(abs(FP1).^2));axis image;colorbar; title('Large scale DFT'); end; LP1 = 1/(1*D*lambdaOverD)*np.exp(-1j*2*np.pi*np.outer(x,u1)) @ (FP1*FPM*(1-windowMask1)) @ np.exp(-1j*2*np.pi*np.outer(u1,x)) #if showPlots2debug; figure;imagesc(abs(FP1.*(1-windowMask1)));axis image;colorbar; title('Large scale DFT (windowed)'); end; ## Fine sampled DFT of innter region FP2 = 2*outVal/(1*D*NB)*np.exp(-1j*2*np.pi*np.outer(u2,x)) @ pupilPre @ np.exp(-1j*2*np.pi*np.outer(x,u2)) #if showPlots2debug; figure;imagesc(log10(abs(FP2).^2));axis image;colorbar; title('Fine sampled DFT'); end; FPM = falco_gen_vortex_mask(charge, NB) LP2 = 2.0*outVal/(1*D*NB)*np.exp(-1j*2*np.pi*np.outer(x,u2)) @ (FP2*FPM*windowMask2) @ np.exp(-1j*2*np.pi*np.outer(u2,x)) #if showPlots2debug; figure;imagesc(abs(FP2.*windowMask2));axis image;colorbar; title('Fine sampled DFT (windowed)'); end; pupilPost = LP1 + LP2; #if showPlots2debug; figure;imagesc(abs(pupilPost));axis image;colorbar; title('Lyot plane'); end; return pupilPost