コード例 #1
0
def test_roman_pupil_against_file():

    localpath = os.path.dirname(os.path.abspath(__file__))
    fn = os.path.join(localpath, 'testdata',
                      'pupil_CGI-20200513_8k_binary_noF.png')
    pupil0 = imread(fn).astype(float)

    pupilFromFile = pupil0 / np.max(pupil0)
    Narray = pupilFromFile.shape[1]
    downSampFac = 16
    Nbeam = 2 * 4027.25
    NbeamDS = Nbeam / downSampFac
    NarrayDS = int(Narray / downSampFac)
    pupilFromFileDS = bin_downsample(pupilFromFile, downSampFac)

    # Generate pupil representation in FALCO
    shift = downSampFac / 2 - 0.5
    changes = {
        "xShear": -0.5 / Nbeam - shift / Nbeam,
        "yShear": -52.85 / Nbeam - shift / Nbeam
    }
    pupilFromFALCODS = falco_gen_pupil_Roman_CGI_20200513(
        NbeamDS, 'pixel', changes)
    pupilFromFALCODS = pad_crop(pupilFromFALCODS, NarrayDS)
    diff = pupilFromFileDS - pupilFromFALCODS

    percentvalue = np.sum(np.abs(diff)) / np.sum(pupilFromFileDS) * 100
    assert percentvalue < 0.1
コード例 #2
0
def test_translation_and_rotation():

    Nbeam = 100
    centering = 'pixel'
    pupil = falco_gen_pupil_Roman_CGI_20200513(Nbeam, centering)

    # Translation test
    changes = {"xShear": -11 / 100, "yShear": 19 / 100}
    pupilOffset = falco_gen_pupil_Roman_CGI_20200513(Nbeam, centering, changes)

    # Test rotation (and translation)
    changes["clock_deg"] = 90
    pupilRotOffset = falco_gen_pupil_Roman_CGI_20200513(
        Nbeam, centering, changes)
    pupilRotRecentered = np.roll(
        pupilRotOffset,
        [int(-Nbeam * changes["yShear"]),
         int(-Nbeam * changes["xShear"])],
        axis=(0, 1))

    pupilRot = np.zeros_like(pupil)
    pupilRot[1::, 1::] = np.rot90(pupil[1::, 1::], -1)

    diff = pad_crop(pupilRot, pupilOffset.shape) - pupilRotRecentered

    assert np.max(np.abs(diff)) < 1e-4
コード例 #3
0
def test_translation():
    Nbeam = 200
    inputs = {"Nbeam": Nbeam}
    pupil = falco_gen_pupil_LUVOIR_A_final(inputs)

    inputs.update({"xShear": -11 / 100, "yShear": 19 / 100})
    pupilOffset = falco_gen_pupil_LUVOIR_A_final(inputs)
    pupilRecentered = np.roll(
        pupilOffset,
        [int(-Nbeam * inputs["yShear"]),
         int(-Nbeam * inputs["xShear"])],
        axis=(0, 1))
    diff = pad_crop(pupil, pupilOffset.shape) - pupilRecentered
    assert np.max(np.abs(diff)) <= 1 / 33
コード例 #4
0
def test_translation():
    Nbeam = 100
    centering = 'pixel'
    pupil = falco_gen_pupil_Roman_CGI_20200513(Nbeam, centering)

    changes = {"xShear": -11 / 100, "yShear": 19 / 100}
    pupilOffset = falco_gen_pupil_Roman_CGI_20200513(Nbeam, centering, changes)
    pupilRecentered = np.roll(
        pupilOffset,
        [int(-Nbeam * changes["yShear"]),
         int(-Nbeam * changes["xShear"])],
        axis=(0, 1))
    diff = pad_crop(pupil, pupilOffset.shape) - pupilRecentered

    assert np.sum(np.abs(diff)) < 1e-8
コード例 #5
0
def test_lyot_stop_translation():
    Nbeam = 200
    inputs = {"Nbeam": Nbeam, "flagLyot": True, "ID": 0.30, "OD": 0.80}
    pupil = falco_gen_pupil_LUVOIR_A_final(inputs)

    # Translation test
    inputs.update({"xShear": -11 / 100, "yShear": 19 / 100})
    pupilOffset = falco_gen_pupil_LUVOIR_A_final(inputs)
    pupilRecentered = np.roll(
        pupilOffset,
        [int(-Nbeam * inputs["yShear"]),
         int(-Nbeam * inputs["xShear"])],
        axis=(0, 1))
    diff = pad_crop(pupil, pupilOffset.shape) - pupilRecentered
    assert np.sum(np.abs(diff)) < 1e-8
コード例 #6
0
def test_lyot_stop_translation_and_rotation():
    Nbeam = 200
    inputs = {"Nbeam": Nbeam, "flagLyot": True, "ID": 0.30, "OD": 0.80}
    pupil = falco_gen_pupil_LUVOIR_A_final(inputs)
    pupilRot = np.zeros_like(pupil)
    pupilRot[1::, 1::] = np.rot90(pupil[1::, 1::], -1)

    # Translation test
    inputs.update({"xShear": -11 / 100, "yShear": 19 / 100, "clock_deg": 90})
    pupilRotOffset = falco_gen_pupil_LUVOIR_A_final(inputs)
    pupilRotRecentered = np.roll(
        pupilRotOffset,
        [int(-Nbeam * inputs["yShear"]),
         int(-Nbeam * inputs["xShear"])],
        axis=(0, 1))
    diff = pad_crop(pupilRot, pupilRotOffset.shape) - pupilRotRecentered
    assert np.max(np.abs(diff)) < 1 / 33
コード例 #7
0
def test_translation_annulus():
    res = 6
    inputs = {"pixresFPM": res,
              "rhoInner": 3,
              "rhoOuter": 10,
              }
    fpm = gen_annular_fpm(inputs)

    inputs.update({"xOffset": 5.5, "yOffset": -10})
    fpmOffset = gen_annular_fpm(inputs)

    fpmRecentered = np.roll(fpmOffset,
                            [int(-res*inputs["yOffset"]),
                             int(-res*inputs["xOffset"])],
                            axis=(0, 1))
    fpmPad = pad_crop(fpm, fpmRecentered.shape)
    assert np.allclose(fpmPad, fpmRecentered, atol=1e-7)
コード例 #8
0
def test_translation():
    res = 6
    inputs = {
        "pixresFPM": res,
        "rhoInner": 2.6,
        "rhoOuter": 9.4,
        "ang": 65,
        "clocking": 20,
    }
    fpm = gen_bowtie_fpm(inputs)

    inputs.update({"xOffset": 5.5, "yOffset": -10})
    fpmOffset = gen_bowtie_fpm(inputs)

    fpmRecentered = np.roll(
        fpmOffset,
        [int(-res * inputs["yOffset"]),
         int(-res * inputs["xOffset"])],
        axis=(0, 1))
    fpmPad = pad_crop(fpm, fpmRecentered.shape)
    assert np.allclose(fpmPad, fpmRecentered, atol=1e-7)
コード例 #9
0
def test_rotation():
    res = 6
    inputs = {
        "pixresFPM": res,
        "rhoInner": 2.6,
        "rhoOuter": 9.4,
        "ang": 65,
    }
    fpm = gen_bowtie_fpm(inputs)
    fpmRot = np.zeros_like(fpm)
    fpmRot[1::, 1::] = np.rot90(fpm[1::, 1::], -1)

    inputs.update({"xOffset": 5.5, "yOffset": -10, "clocking": 90})
    fpmRotOffset = gen_bowtie_fpm(inputs)

    fpmRotRecentered = np.roll(
        fpmRotOffset,
        [int(-res * inputs["yOffset"]),
         int(-res * inputs["xOffset"])],
        axis=(0, 1))
    fpmRotPad = pad_crop(fpmRot, fpmRotRecentered.shape)
    assert np.allclose(fpmRotPad, fpmRotRecentered, atol=1e-4)
コード例 #10
0
def full(mp, modvar, isNorm=True):
    """
    Truth model used to generate images in simulation.

    Truth model used to generate images in simulation. Can include
    aberrations/errors that are unknown to the estimator and controller. This
    function is the wrapper for full models of any coronagraph type.

    Parameters
    ----------
    mp : ModelParameters
        Structure containing optical model parameters
    modvar : ModelVariables
        Structure containing temporary optical model variables
    isNorm : bool
        If False, return an unnormalized image. If True, return a
        normalized image with the currently stored norm value.

    Returns
    -------
    Eout : numpy ndarray
        2-D electric field in final focal plane
    """
    if type(mp) is not falco.config.ModelParameters:
        raise TypeError('Input "mp" must be of type ModelParameters')

    if hasattr(modvar, 'sbpIndex'):
        normFac = mp.Fend.full.I00[modvar.sbpIndex, modvar.wpsbpIndex]
        # Value to normalize the PSF. Set to 0 when finding the norm factor

    # Optional Keyword arguments
    if not isNorm:
        normFac = 0

    # Set the wavelength
    if (hasattr(modvar, 'wvl')):  # For FALCO or standalone use of full model
        wvl = modvar.wvl
    elif (hasattr(modvar, 'sbpIndex')):  # For use in FALCO
        wvl = mp.full.lambdasMat[modvar.sbpIndex, modvar.wpsbpIndex]
    else:
        raise ValueError('Need to specify value or indices for wavelength.')
    """ Input E-fields """
    if modvar.whichSource.lower() == 'offaxis':  # Use for thput calculations
        TTphase = (-1.) * (2 * np.pi * (modvar.x_offset * mp.P2.full.XsDL +
                                        modvar.y_offset * mp.P2.full.YsDL))
        Ett = np.exp(1j * TTphase * mp.lambda0 / wvl)
        Ein = Ett * np.squeeze(mp.P1.full.E[:, :, modvar.wpsbpIndex,
                                            modvar.sbpIndex])

    else:  # Default to using the starlight
        Ein = np.squeeze(mp.P1.full.E[:, :, modvar.wpsbpIndex,
                                      modvar.sbpIndex])

    # Shift the source off-axis to compute the intensity normalization value.
    # This replaces the previous way of taking the FPM out in the optical model
    if normFac == 0:
        source_x_offset = mp.source_x_offset_norm  # source offset in lambda0/D
        source_y_offset = mp.source_y_offset_norm  # source offset in lambda0/D
        TTphase = (-1) * (2 * np.pi * (source_x_offset * mp.P2.full.XsDL +
                                       source_y_offset * mp.P2.full.YsDL))
        Ett = np.exp(1j * TTphase * mp.lambda0 / wvl)
        Ein = Ett * np.squeeze(mp.P1.full.E[:, :, modvar.wpsbpIndex,
                                            modvar.sbpIndex])

    # Apply a Zernike (in amplitude) at input pupil if specified
    if not (hasattr(modvar, 'zernIndex')):
        modvar.zernIndex = 1
    if not modvar.zernIndex == 1:
        indsZnoll = modvar.zernIndex  # Just send in 1 Zernike mode
        zernMat = np.squeeze(
            falco.zern.gen_norm_zern_maps(mp.P1.full.Nbeam, mp.centering,
                                          indsZnoll))
        zernMat = pad_crop(zernMat, mp.P1.full.Narr)
        Ein = Ein * zernMat * (
            2 * np.pi / wvl) * mp.jac.Zcoef[mp.jac.zerns == modvar.zernIndex]

    # %% Pre-compute the FPM first for HLC
    if mp.layout.lower() == 'fourier' or mp.layout.lower() == 'proper':
        # ilam = (modvar.sbpIndex-1)*mp.Nwpsbp + modvar.wpsbpIndex
        if mp.coro.upper() == 'HLC':
            mp.F3.full.mask = falco.hlc.gen_fpm_from_LUT(
                mp, modvar.sbpIndex, modvar.wpsbpIndex, 'full')
    elif mp.layout.lower() == 'fpm_scale':
        if mp.coro.upper() == 'HLC':
            if mp.Nsbp > 1 and mp.Nwpsbp > 1:
                # Weird indexing is because interior wavelengths at
                # edges of sub-bands are the same, and the fpmCube
                # contains only the minimal set of masks.
                ilam = (modvar.sbpIndex-2)*mp.Nwpsbp + modvar.wpsbpIndex + \
                    (mp.Nsbp-modvar.sbpIndex+1)
            elif mp.Nsbp == 1 and mp.Nwpsbp > 1:
                ilam = modvar.wpsbpIndex
            elif mp.Nwpsbp == 1:
                ilam = modvar.sbpIndex

            mp.F3.full.mask = mp.full.fpmCube[:, :, ilam]

    # %% Select which optical layout's full model to use.
    if mp.layout.lower() == 'fourier':
        Eout = full_Fourier(mp, wvl, Ein, normFac)

    elif mp.layout.lower() == 'fpm_scale':  # FPM scales with wavelength
        Eout = full_Fourier(mp, wvl, Ein, normFac, flagScaleFPM=True)

    elif mp.layout.lower() == 'proper':

        optval = copy.copy(vars(mp.full))

        if any(mp.dm_ind == 1):
            optval['use_dm1'] = True
            optval['dm1'] = mp.dm1.V * mp.dm1.VtoH + mp.full.dm1FlatMap

        if any(mp.dm_ind == 2):
            optval['use_dm2'] = True
            optval['dm2'] = mp.dm2.V * mp.dm2.VtoH + mp.full.dm2FlatMap

        if normFac == 0:
            optval['xoffset'] = -mp.source_x_offset_norm
            optval['yoffset'] = -mp.source_y_offset_norm
            if mp.coro.upper() in ('VORTEX', 'VC', 'AVC'):
                optval['use_fpm'] = False
                optval['xoffset'] = 0
                optval['yoffset'] = 0
                pass

        # wavelength needs to be in microns instead of meters for PROPER
        [Eout, sampling_m] = proper.prop_run(mp.full.prescription,
                                             wvl * 1e6,
                                             mp.P1.full.Narr,
                                             QUIET=True,
                                             PASSVALUE=optval)
        if not normFac == 0:
            Eout = Eout / np.sqrt(normFac)

        del optval

    elif mp.layout.lower() in ('wfirst_phaseb_proper', 'roman_phasec_proper'):
        optval = copy.copy(vars(mp.full))
        optval['use_dm1'] = True
        optval['use_dm2'] = True
        optval['dm1_m'] = mp.dm1.V * mp.dm1.VtoH + mp.full.dm1.flatmap
        optval['dm2_m'] = mp.dm2.V * mp.dm2.VtoH + mp.full.dm2.flatmap
        if normFac == 0:
            optval['source_x_offset'] = -mp.source_x_offset_norm
            optval['source_y_offset'] = -mp.source_y_offset_norm

        if mp.layout.lower() == 'wfirst_phaseb_proper':
            fn_proper = 'wfirst_phaseb'
        elif mp.layout.lower() == 'roman_phasec_proper':
            fn_proper = 'roman_phasec'

        [Eout,
         sampling] = proper.prop_run(fn_proper,
                                     wvl * 1e6,
                                     int(2**falco.util.nextpow2(mp.Fend.Nxi)),
                                     QUIET=True,
                                     PASSVALUE=optval)
        Eout = pad_crop(Eout, (mp.Fend.Nxi, mp.Fend.Nxi))

        if not normFac == 0:
            Eout = Eout / np.sqrt(normFac)

    return Eout
コード例 #11
0
def compact_general(mp, wvl, Ein, normFac, flagEval, flagScaleFPM=False):
    """
    Compact model with a general-purpose optical layout.

    Used by estimator and controller.

    Simplified (aka compact) model used by estimator and controller. Does not
    include unknown aberrations of the full, "truth" model. This has a general
    optical layout that should work for most applications.

    Parameters
    ----------
    mp : ModelParameters
        Structure containing optical model parameters
    wvl : float
        Wavelength of light [meters]
    Ein : numpy ndarray
        2-D input electric field
    normFac : float
        Intensity normalization factor
    flagEval : bool
        Whether to use a higher resolution in final image plane for evaluation.
    flagScaleFPM : bool, optional
        Whether to scale the diameter of the FPM inversely with wavelength.

    Returns
    -------
    Eout : numpy ndarray
        2-D electric field in final focal plane

    """
    if type(mp) is not falco.config.ModelParameters:
        raise TypeError('Input "mp" must be of type ModelParameters')
    check.is_bool(flagEval, 'flagEval')
    check.is_bool(flagScaleFPM, 'flagScaleFPM')

    mirrorFac = 2.  # Phase change is twice the DM surface height.
    NdmPad = int(mp.compact.NdmPad)

    if mp.flagRotation:
        NrelayFactor = 1
    else:
        NrelayFactor = 0  # zero out the number of relays

    if flagScaleFPM:
        fpmScaleFac = wvl / mp.lambda0
    else:
        fpmScaleFac = 1.0

    if (flagEval):  # Higher resolution at final focal plane for eval
        dxi = mp.Fend.eval.dxi
        Nxi = mp.Fend.eval.Nxi
        deta = mp.Fend.eval.deta
        Neta = mp.Fend.eval.Neta
    else:  # Otherwise use the detector resolution
        dxi = mp.Fend.dxi
        Nxi = mp.Fend.Nxi
        deta = mp.Fend.deta
        Neta = mp.Fend.Neta
    """ Masks and DM surfaces """
    # ompute the DM surfaces for the current DM commands
    if any(mp.dm_ind == 1):
        DM1surf = falco.dm.gen_surf_from_act(mp.dm1, mp.dm1.compact.dx, NdmPad)
    else:
        DM1surf = np.zeros((NdmPad, NdmPad))
    if any(mp.dm_ind == 2):
        DM2surf = falco.dm.gen_surf_from_act(mp.dm2, mp.dm2.compact.dx, NdmPad)
    else:
        DM2surf = np.zeros((NdmPad, NdmPad))

    pupil = pad_crop(mp.P1.compact.mask, NdmPad)
    Ein = pad_crop(Ein, NdmPad)

    if (mp.flagDM1stop):
        DM1stop = pad_crop(mp.dm1.compact.mask, NdmPad)
    else:
        DM1stop = np.ones((NdmPad, NdmPad))
    if (mp.flagDM2stop):
        DM2stop = pad_crop(mp.dm2.compact.mask, NdmPad)
    else:
        DM2stop = np.ones((NdmPad, NdmPad))

    # if mp.useGPU:
    #     log.warning('GPU support not yet implemented. Proceeding without GPU.')

    # This block is for BMC surface error testing
    if mp.flagDMwfe:
        if any(mp.dm_ind == 1):
            Edm1WFE = np.exp(
                2 * np.pi * 1j / wvl *
                pad_crop(mp.dm1.compact.wfe, NdmPad, 'extrapval', 0))
        else:
            Edm1WFE = np.ones((NdmPad, NdmPad))
        if any(mp.dm_ind == 2):
            Edm2WFE = np.exp(
                2 * np.pi * 1j / wvl *
                pad_crop(mp.dm2.compact.wfe, NdmPad, 'extrapval', 0))
        else:
            Edm2WFE = np.ones((NdmPad, NdmPad))
    else:
        Edm1WFE = np.ones((NdmPad, NdmPad))
        Edm2WFE = np.ones((NdmPad, NdmPad))
    """Propagation"""

    # Define pupil P1 and Propagate to pupil P2
    EP1 = pupil * Ein  # E-field at pupil plane P1
    EP2 = falco.prop.relay(EP1, NrelayFactor * mp.Nrelay1to2, mp.centering)

    # Propagate from P2 to DM1, and apply DM1 surface and aperture stop
    if not (abs(mp.d_P2_dm1) == 0):  # E-field arriving at DM1
        Edm1 = falco.prop.ptp(EP2, mp.P2.compact.dx * NdmPad, wvl, mp.d_P2_dm1)
    else:
        Edm1 = EP2
    # E-field leaving DM1
    Edm1b = Edm1 * Edm1WFE * DM1stop * np.exp(
        mirrorFac * 2 * np.pi * 1j * DM1surf / wvl)

    # Propagate from DM1 to DM2, and apply DM2 surface and aperture stop
    Edm2 = falco.prop.ptp(Edm1b, mp.P2.compact.dx * NdmPad, wvl, mp.d_dm1_dm2)
    Edm2 *= Edm2WFE * DM2stop * np.exp(
        mirrorFac * 2 * np.pi * 1j * DM2surf / wvl)

    # Back-propagate to pupil P2
    if (mp.d_P2_dm1 + mp.d_dm1_dm2 == 0):
        EP2eff = Edm2
    else:
        EP2eff = falco.prop.ptp(Edm2, mp.P2.compact.dx * NdmPad, wvl,
                                -1 * (mp.d_dm1_dm2 + mp.d_P2_dm1))

    # Re-image to pupil P3
    EP3 = falco.prop.relay(EP2eff, NrelayFactor * mp.Nrelay2to3, mp.centering)

    # Apply apodizer mask.
    if (mp.flagApod):
        EP3 = mp.P3.compact.mask * pad_crop(EP3, mp.P3.compact.Narr)
    """  Select propagation based on coronagraph type   """
    if mp.coro.upper() in ('LC', 'APLC', 'HLC', 'RODDIER'):
        # MFT from SP to FPM (i.e., P3 to F3)
        # E-field incident upon the FPM
        EF3inc = falco.prop.mft_p2f(EP3, mp.fl, wvl, mp.P2.compact.dx,
                                    fpmScaleFac * mp.F3.compact.dxi,
                                    mp.F3.compact.Nxi,
                                    fpmScaleFac * mp.F3.compact.deta,
                                    mp.F3.compact.Neta, mp.centering)
        # Apply (1-FPM) for Babinet's principle later
        if mp.coro.upper() == 'RODDIER':
            pass
        elif mp.coro.upper() == 'HLC':
            FPM = mp.F3.compact.mask  # Complex transmission of the FPM
            transOuterFPM = FPM[0, 0]  # Complex trans of points outside FPM
            EF3 = (transOuterFPM - FPM) * EF3inc
            # transOuterFPM instead of 1 because of the complex transmission of
            # the glass as well as the arbitrary phase shift.
        else:
            EF3 = (1. - mp.F3.compact.mask) * EF3inc
        # Use Babinet's principle at the Lyot plane.
        EP4noFPM = falco.prop.relay(EP3, NrelayFactor * mp.Nrelay3to4,
                                    mp.centering)
        EP4noFPM = pad_crop(EP4noFPM, mp.P4.compact.Narr)
        if mp.coro.upper() == 'HLC':
            EP4noFPM = transOuterFPM * EP4noFPM
        # MFT from FPM to Lyot Plane (i.e., F3 to P4)
        # Subtrahend term for Babinet's principle
        EP4sub = falco.prop.mft_f2p(EF3, mp.fl, wvl,
                                    fpmScaleFac * mp.F3.compact.dxi,
                                    fpmScaleFac * mp.F3.compact.deta,
                                    mp.P4.compact.dx, mp.P4.compact.Narr,
                                    mp.centering)
        EP4subRelay = falco.prop.relay(EP4sub,
                                       NrelayFactor * mp.Nrelay3to4 - 1,
                                       mp.centering)
        # Babinet's principle at P4
        EP4 = (EP4noFPM - EP4subRelay)

    elif mp.coro.upper() == 'FLC' or mp.coro.upper() == 'SPLC':
        # MFT from SP to FPM (i.e., P3 to F3)
        # E-field incident upon the FPM
        EF3inc = falco.prop.mft_p2f(EP3, mp.fl, wvl, mp.P2.compact.dx,
                                    mp.F3.compact.dxi, mp.F3.compact.Nxi,
                                    mp.F3.compact.deta, mp.F3.compact.Neta,
                                    mp.centering)

        # Apply FPM
        EF3 = mp.F3.compact.mask * EF3inc

        # MFT from FPM to Lyot Plane (i.e., F3 to P4)
        EP4 = falco.prop.mft_f2p(EF3, mp.fl, wvl, mp.F3.compact.dxi,
                                 mp.F3.compact.deta, mp.P4.compact.dx,
                                 mp.P4.compact.Narr, mp.centering)
        EP4 = falco.prop.relay(EP4, NrelayFactor * mp.Nrelay3to4 - 1,
                               mp.centering)

    elif mp.coro.upper() in ('VORTEX', 'VC', 'AVC'):

        # Get FPM charge
        if isinstance(mp.F3.VortexCharge, np.ndarray):
            # Passing an array for mp.F3.VortexCharge with
            # corresponding wavelengths mp.F3.VortexCharge_lambdas
            # represents a chromatic vortex FPM
            if mp.F3.VortexCharge.size == 1:
                charge = mp.F3.VortexCharge
            else:
                np.interp(wvl, mp.F3.VortexCharge_lambdas, mp.F3.VortexCharge,
                          'linear', 'extrap')

        elif isinstance(mp.F3.VortexCharge, (int, float)):
            # single value indicates fully achromatic mask
            charge = mp.F3.VortexCharge
        else:
            raise TypeError("mp.F3.VortexCharge must be int, float or numpy\
                            ndarray.")
            pass
        EP4 = falco.prop.mft_p2v2p(EP3, charge, mp.P1.compact.Nbeam / 2., 0.3,
                                   5)
        EP4 = pad_crop(EP4, mp.P4.compact.Narr)

        # Undo the rotation inherent to falco.prop.mft_p2v2p.m
        if not mp.flagRotation:
            EP4 = falco.prop.relay(EP4, -1, mp.centering)

    else:
        raise ValueError("Value of mp.coro not recognized.")
        pass

    # Remove FPM completely if normalization value is being found for vortex
    if normFac == 0:
        if mp.coro.upper() in ('VORTEX', 'VC', 'AVC'):
            EP4 = falco.prop.relay(EP3, NrelayFactor * mp.Nrelay3to4,
                                   mp.centering)
            EP4 = pad_crop(EP4, mp.P4.compact.Narr)
            pass
        pass
    """  Back to common propagation any coronagraph type   """
    # Apply the Lyot stop
    EP4 = mp.P4.compact.croppedMask * EP4

    # MFT to camera
    EP4 = falco.prop.relay(EP4, NrelayFactor * mp.NrelayFend, mp.centering)
    EFend = falco.prop.mft_p2f(EP4, mp.fl, wvl, mp.P4.compact.dx, dxi, Nxi,
                               deta, Neta, mp.centering)

    # Don't apply FPM if normalization value is being found
    if normFac == 0:
        Eout = EFend  # Don't normalize if normalization value is being found
    else:
        Eout = EFend / np.sqrt(normFac)  # Apply normalization

    return Eout
コード例 #12
0
def compact(mp, modvar, isNorm=True, isEvalMode=False):
    """
    Simplified (aka compact) model used by estimator and controller.

    Simplified (aka compact) model used by estimator and controller. Does not
    include unknown aberrations of the full, "truth" model. This function is
    the wrapper for compact models of any coronagraph type.

    Parameters
    ----------
    mp : ModelParameters
        Structure containing optical model parameters
    modvar : ModelVariables
        Structure containing temporary optical model variables
    isNorm : bool
        If False, return an unnormalized image. If True, return a
        normalized image with the currently stored norm value.
    isEvalMode : bool
        If set, uses a higher resolution in the focal plane for
        measuring performance metrics such as throughput.

    Returns
    -------
    Eout : array_like
        2-D electric field in final focal plane

    """
    if type(mp) is not falco.config.ModelParameters:
        raise TypeError('Input "mp" must be of type ModelParameters')

    # Set default values of input parameters
    normFac = mp.Fend.compact.I00[modvar.sbpIndex]  # Value to normalize PSF.
    flagEval = False  # use a different res at final focal plane for eval
    modvar.wpsbpIndex = -1  # Dummy index since not needed in compact model

    # Optional Keyword arguments
    if not isNorm:
        normFac = 0.
    if isEvalMode:
        flagEval = True

    # Normalization factor for compact evaluation model
    if (isNorm and isEvalMode):
        normFac = mp.Fend.eval.I00[modvar.sbpIndex]
        # Value to normalize the PSF. Set to 0 when finding the norm factor

    # Set the wavelength
    if hasattr(modvar, 'wvl'):
        wvl = modvar.wvl
    else:
        wvl = mp.sbp_centers[modvar.sbpIndex]
    """ Input E-fields """

    # Include the tip/tilt in the input wavefront
    #    if(hasattr(mp,'ttx')):
    #         %--Scale by wvl/lambda0 because ttx and tty are in lambda0/D
    #         x_offset = mp.ttx(modvar.ttIndex)*(mp.lambda0/wvl);
    #         y_offset = mp.tty(modvar.ttIndex)*(mp.lambda0/wvl);
    #
    #         TTphase = (-1)*(2*np.pi*(x_offset*mp.P2.compact.XsDL +
    #                    y_offset*mp.P2.compact.YsDL));
    #         Ett = exp(1j*TTphase*mp.lambda0/wvl);
    #         Ein = Ett.*mp.P1.compact.E(:,:,modvar.sbpIndex)
    if modvar.whichSource.lower() == 'offaxis':  # Use for throughput calc
        TTphase = (-1) * (2 * np.pi * (modvar.x_offset * mp.P2.compact.XsDL +
                                       modvar.y_offset * mp.P2.compact.YsDL))
        Ett = np.exp(1j * TTphase * mp.lambda0 / wvl)
        Ein = Ett * mp.P1.compact.E[:, :, modvar.sbpIndex]
    else:  # Backward compatible with code without tip/tilt offsets in Jacobian
        Ein = mp.P1.compact.E[:, :, modvar.sbpIndex]

    # Shift the source off-axis to compute the intensity normalization value.
    # This replaces the previous way of taking the FPM out in optical model.
    if normFac == 0:
        # source offset in lambda0/D for normalization
        source_x_offset = mp.source_x_offset_norm
        source_y_offset = mp.source_y_offset_norm
        TTphase = (-1.) * (2 * np.pi * (source_x_offset * mp.P2.compact.XsDL +
                                        source_y_offset * mp.P2.compact.YsDL))
        Ett = np.exp(1j * TTphase * mp.lambda0 / wvl)
        Ein = Ett * mp.P1.compact.E[:, :, modvar.sbpIndex]

    # Apply a Zernike (in amplitude) at input pupil if specified
    if not hasattr(modvar, 'zernIndex'):
        modvar.zernIndex = 1

    # Only used for Zernike sensitivity control, which requires the perfect
    # E-field of the differential Zernike term.
    if not (modvar.zernIndex == 1):
        indsZnoll = modvar.zernIndex  # Just send in 1 Zernike mode
        zernMat = np.squeeze(
            falco.zern.gen_norm_zern_maps(mp.P1.compact.Nbeam, mp.centering,
                                          indsZnoll))
        zernMat = pad_crop(zernMat, mp.P1.compact.Narr)
        Ein = Ein * zernMat * (2 * np.pi * 1j / wvl) * mp.jac.Zcoef[
            mp.jac.zerns == modvar.zernIndex]

    # Define what the complex-valued FPM is if the coro is some type of HLC.
    if mp.layout.lower() == 'fourier':
        if mp.coro.upper() in ('HLC', ):
            mp.F3.compact.mask = falco.hlc.gen_fpm_from_LUT(
                mp, modvar.sbpIndex, -1, 'compact')
    elif mp.layout.lower() in ('wfirst_phaseb_proper', 'roman_phasec_proper'):
        if mp.coro.upper() in ('HLC', ):
            mp.F3.compact.mask = mp.compact.fpmCube[:, :, modvar.sbpIndex]

    # Select which optical layout's compact model to use and get E-field
    if mp.layout.lower() == 'fourier' or mp.layout.lower() == 'proper':
        Eout = compact_general(mp, wvl, Ein, normFac, flagEval)

    elif mp.layout.lower() == 'fpm_scale':
        if mp.coro.upper() in ('HLC', 'SPLC'):
            Eout = compact_general(mp,
                                   wvl,
                                   Ein,
                                   normFac,
                                   flagEval,
                                   flagScaleFPM=True)

    elif mp.layout.lower() in ('wfirst_phaseb_proper', 'roman_phasec_proper'):
        if mp.coro.upper() == 'HLC':
            Eout = compact_general(mp,
                                   wvl,
                                   Ein,
                                   normFac,
                                   flagEval,
                                   flagScaleFPM=True)
        elif 'SP' in mp.coro.upper():
            Eout = compact_general(mp, wvl, Ein, normFac, flagEval)
    return Eout
コード例 #13
0
def lyot(mp, im, idm):
    """
    Differential model used to compute the ctrl Jacobian for Lyot coronagraph.

    Specialized compact model used to compute the DM response matrix, aka the
    control Jacobian for a Lyot coronagraph. Can include an apodizer, making it
    an apodized pupil Lyot coronagraph (APLC). Does not include unknown
    aberrations of the full, "truth" model. This model propagates the
    first-order Taylor expansion of the phase from the poke of each actuator
    of the deformable mirror.

    Parameters
    ----------
    mp : ModelParameters
        Structure containing optical model parameters

    Returns
    -------
    Gzdl : numpy ndarray
        Complex-valued, 2-D array containing the Jacobian for the
        specified Zernike mode, DM number, and wavelength.

    """
    modvar = falco.config.Object()  # Initialize the new structure
    modvar.sbpIndex = mp.jac.sbp_inds[im]
    modvar.zernIndex = mp.jac.zern_inds[im]

    wvl = mp.sbp_centers[modvar.sbpIndex]
    mirrorFac = 2.  # Phase change is twice the DM surface height.
    NdmPad = int(mp.compact.NdmPad)

    if mp.flagRotation:
        NrelayFactor = 1
    else:
        NrelayFactor = 0  # zero out the number of relays

    if mp.coro.upper() in ('LC', 'APLC', 'FLC', 'SPLC'):
        fpm = mp.F3.compact.mask
        transOuterFPM = 1.  # transmission of points outside the FPM.
    elif mp.coro.upper() in ('HLC', ):
        fpm = np.squeeze(mp.compact.fpmCube[:, :, modvar.sbpIndex])  # complex
        # Complex transmission of the points outside the FPM (just fused silica
        # with optional dielectric and no metal).
        transOuterFPM = fpm[0, 0]
    """Input E-fields"""
    Ein = np.squeeze(mp.P1.compact.E[:, :, modvar.sbpIndex])
    # Apply a Zernike (in amplitude) at input pupil
    # Used only for Zernike sensitivity control, which requires the perfect
    # E-field of the differential Zernike term.
    if not (modvar.zernIndex == 1):
        indsZnoll = modvar.zernIndex  # Just send in 1 Zernike mode
        zernMat = np.squeeze(
            falco.zern.gen_norm_zern_maps(mp.P1.compact.Nbeam, mp.centering,
                                          indsZnoll))
        zernMat = pad_crop(zernMat, mp.P1.compact.Narr)
        Ein = Ein * zernMat * (
            2 * np.pi / wvl) * mp.jac.Zcoef[mp.jac.zerns == modvar.zernIndex]
    """ Masks and DM surfaces """
    pupil = pad_crop(mp.P1.compact.mask, NdmPad)
    Ein = pad_crop(Ein, NdmPad)

    # Re-image the apodizer from pupil P3 back to pupil P2.
    if (mp.flagApod):
        apodReimaged = pad_crop(mp.P3.compact.mask, NdmPad)
        apodReimaged = fp.relay(apodReimaged, NrelayFactor * mp.Nrelay2to3,
                                mp.centering)
    else:
        apodReimaged = np.ones((NdmPad, NdmPad))

    # Compute the DM surfaces for the current DM commands
    if any(mp.dm_ind == 1):
        DM1surf = pad_crop(mp.dm1.compact.surfM, NdmPad)
        # DM1surf = falco.dm.gen_surf_from_act(mp.dm1, mp.dm1.compact.dx, NdmPad)
    else:
        DM1surf = np.zeros((NdmPad, NdmPad))
    if any(mp.dm_ind == 2):
        DM2surf = pad_crop(mp.dm2.compact.surfM, NdmPad)
        # DM2surf = falco.dm.gen_surf_from_act(mp.dm2, mp.dm2.compact.dx, NdmPad)
    else:
        DM2surf = np.zeros((NdmPad, NdmPad))

    if mp.flagDM1stop:
        DM1stop = pad_crop(mp.dm1.compact.mask, NdmPad)
    else:
        DM1stop = np.ones((NdmPad, NdmPad))
    if (mp.flagDM2stop):
        DM2stop = pad_crop(mp.dm2.compact.mask, NdmPad)
    else:
        DM2stop = np.ones((NdmPad, NdmPad))

    # This block is for BMC surface error testing
    if mp.flagDMwfe:  # if(mp.flagDMwfe && (mp.P1.full.Nbeam==mp.P1.compact.Nbeam))
        if any(mp.dm_ind == 1):
            Edm1WFE = np.exp(
                2 * np.pi * 1j / wvl *
                pad_crop(mp.dm1.compact.wfe, NdmPad, 'extrapval', 0))
        else:
            Edm1WFE = np.ones((NdmPad, NdmPad))
        if any(mp.dm_ind == 2):
            Edm2WFE = np.exp(
                2 * np.pi * 1j / wvl *
                pad_crop(mp.dm2.compact.wfe, NdmPad, 'extrapval', 0))
        else:
            Edm2WFE = np.ones((NdmPad, NdmPad))
    else:
        Edm1WFE = np.ones((NdmPad, NdmPad))
        Edm2WFE = np.ones((NdmPad, NdmPad))
    """Propagation"""
    # Define pupil P1 and Propagate to pupil P2
    EP1 = pupil * Ein  # E-field at pupil plane P1
    EP2 = fp.relay(EP1, NrelayFactor * mp.Nrelay1to2, mp.centering)

    # Propagate from P2 to DM1, and apply DM1 surface and aperture stop
    if not abs(mp.d_P2_dm1) == 0:
        Edm1 = fp.ptp(EP2, mp.P2.compact.dx * NdmPad, wvl, mp.d_P2_dm1)
    else:
        Edm1 = EP2
    Edm1out = Edm1 * Edm1WFE * DM1stop * np.exp(
        mirrorFac * 2 * np.pi * 1j * DM1surf / wvl)
    """ ---------- DM1 ---------- """
    if idm == 1:
        Gzdl = np.zeros((mp.Fend.corr.Npix, mp.dm1.Nele), dtype=complex)

        # Two array sizes (at same resolution) of influence functions for MFT
        # and angular spectrum
        NboxPad1AS = int(
            mp.dm1.compact.NboxAS
        )  # array size for FFT-AS propagations from DM1->DM2->DM1
        # Adjust the sub-array location of the influence function for the added zero padding
        mp.dm1.compact.xy_box_lowerLeft_AS = mp.dm1.compact.xy_box_lowerLeft -\
            (mp.dm1.compact.NboxAS-mp.dm1.compact.Nbox)/2.

        if any(mp.dm_ind == 2):
            DM2surf = pad_crop(DM2surf, mp.dm1.compact.NdmPad)
        else:
            DM2surf = np.zeros((mp.dm1.compact.NdmPad, mp.dm1.compact.NdmPad))
        if (mp.flagDM2stop):
            DM2stop = pad_crop(DM2stop, mp.dm1.compact.NdmPad)
        else:
            DM2stop = np.ones((mp.dm1.compact.NdmPad, mp.dm1.compact.NdmPad))
        apodReimaged = pad_crop(apodReimaged, mp.dm1.compact.NdmPad)

        Edm1pad = pad_crop(Edm1out, mp.dm1.compact.NdmPad
                           )  # Pad or crop for expected sub-array indexing
        Edm2WFEpad = pad_crop(Edm2WFE, mp.dm1.compact.NdmPad
                              )  # Pad or crop for expected sub-array indexing

        # Propagate each actuator from DM1 through the optical system
        Gindex = 0  # initialize index counter
        for iact in mp.dm1.act_ele:
            # Compute only for influence functions that are not zeroed out
            if np.sum(np.abs(mp.dm1.compact.inf_datacube[:, :, iact])) > 1e-12:

                # x- and y- coordinate indices of the padded influence function in the full padded pupil
                x_box_AS_ind = np.arange(
                    mp.dm1.compact.xy_box_lowerLeft_AS[0, iact],
                    mp.dm1.compact.xy_box_lowerLeft_AS[0, iact] + NboxPad1AS,
                    dtype=int)  # x-indices in pupil arrays for the box
                y_box_AS_ind = np.arange(
                    mp.dm1.compact.xy_box_lowerLeft_AS[1, iact],
                    mp.dm1.compact.xy_box_lowerLeft_AS[1, iact] + NboxPad1AS,
                    dtype=int)  # y-indices in pupil arrays for the box
                indBoxAS = np.ix_(y_box_AS_ind, x_box_AS_ind)
                # x- and y- coordinates of the UN-padded influence function in the full padded pupil
                x_box = mp.dm1.compact.x_pupPad[
                    x_box_AS_ind]  # full pupil x-coordinates of the box
                y_box = mp.dm1.compact.y_pupPad[
                    y_box_AS_ind]  # full pupil y-coordinates of the box

                # Propagate from DM1 to DM2, and then back to P2
                dEbox = (mirrorFac * 2 * np.pi * 1j / wvl) * pad_crop(
                    (mp.dm1.VtoH.reshape(mp.dm1.Nact**2)[iact]) * np.squeeze(
                        mp.dm1.compact.inf_datacube[:, :, iact]), NboxPad1AS
                )  # Pad influence function at DM1 for angular spectrum propagation.
                dEbox = fp.ptp(
                    dEbox * Edm1pad[np.ix_(y_box_AS_ind, x_box_AS_ind)],
                    mp.P2.compact.dx * NboxPad1AS, wvl, mp.d_dm1_dm2
                )  # forward propagate to DM2 and apply DM2 E-field
                dEP2box = fp.ptp(
                    dEbox * Edm2WFEpad[np.ix_(y_box_AS_ind, x_box_AS_ind)] *
                    DM2stop[np.ix_(y_box_AS_ind, x_box_AS_ind)] *
                    np.exp(mirrorFac * 2 * np.pi * 1j / wvl *
                           DM2surf[np.ix_(y_box_AS_ind, x_box_AS_ind)]),
                    mp.P2.compact.dx * NboxPad1AS, wvl,
                    -1 * (mp.d_dm1_dm2 + mp.d_P2_dm1))  # back-propagate to DM1
                #                dEbox = fp.ptp_inf_func(dEbox*Edm1pad[np.ix_(y_box_AS_ind,x_box_AS_ind)], mp.P2.compact.dx*NboxPad1AS,wvl, mp.d_dm1_dm2, mp.dm1.dm_spacing, mp.propMethodPTP) # forward propagate to DM2 and apply DM2 E-field
                #                dEP2box = fp.ptp_inf_func(dEbox.*Edm2WFEpad[np.ix_(y_box_AS_ind,x_box_AS_ind)]*DM2stop(y_box_AS_ind,x_box_AS_ind).*exp(mirrorFac*2*np.pi*1j/wvl*DM2surf(y_box_AS_ind,x_box_AS_ind)), mp.P2.compact.dx*NboxPad1AS,wvl,-1*(mp.d_dm1_dm2 + mp.d_P2_dm1), mp.dm1.dm_spacing, mp.propMethodPTP ) # back-propagate to DM1
                #
                # To simulate going forward to the next pupil plane (with the apodizer) most efficiently,
                # First, back-propagate the apodizer (by rotating 180-degrees) to the previous pupil.
                # Second, negate the coordinates of the box used.
                dEP2box = apodReimaged[
                    indBoxAS] * dEP2box  # Apply 180deg-rotated SP mask.
                dEP3box = np.rot90(
                    dEP2box, k=NrelayFactor * 2 * mp.Nrelay2to3
                )  # Forward propagate the cropped box by rotating 180 degrees mp.Nrelay2to3 times.
                # Negate and reverse coordinate values to effectively rotate by 180 degrees. No change if 360 degree rotation.
                if np.mod(NrelayFactor * mp.Nrelay2to3, 2) == 1:
                    x_box = -1 * x_box[::-1]
                    y_box = -1 * y_box[::-1]

                # Matrices for the MFT from the pupil P3 to the focal plane mask
                rect_mat_pre = (np.exp(
                    -2 * np.pi * 1j * np.outer(mp.F3.compact.etas, y_box) /
                    (wvl * mp.fl))) * np.sqrt(
                        mp.P2.compact.dx * mp.P2.compact.dx) * np.sqrt(
                            mp.F3.compact.dxi * mp.F3.compact.deta) / (wvl *
                                                                       mp.fl)
                rect_mat_post = (np.exp(-2 * np.pi * 1j *
                                        np.outer(x_box, mp.F3.compact.xis) /
                                        (wvl * mp.fl)))

                EF3inc = rect_mat_pre @ dEP3box @ rect_mat_post  # MFT to FPM

                if mp.coro.upper() in ('LC', 'APLC', 'HLC'):
                    # Propagate through (1 - FPM) for Babinet's principle
                    EF3 = (transOuterFPM - fpm) * EF3inc

                    # MFT to LS ("Sub" name for Subtrahend part of the Lyot-plane E-field)
                    EP4sub = fp.mft_f2p(EF3, mp.fl, wvl, mp.F3.compact.dxi,
                                        mp.F3.compact.deta, mp.P4.compact.dx,
                                        mp.P4.compact.Narr, mp.centering)
                    EP4sub = fp.relay(EP4sub, NrelayFactor * mp.Nrelay3to4 - 1,
                                      mp.centering)

                    # Full Lyot plane pupil (for Babinet)
                    EP4noFPM = np.zeros(
                        (mp.dm1.compact.NdmPad, mp.dm1.compact.NdmPad),
                        dtype=complex)
                    EP4noFPM[
                        indBoxAS] = dEP2box  # Propagating the E-field from P2 to P4 without masks gives the same E-field.
                    EP4noFPM = fp.relay(
                        EP4noFPM,
                        NrelayFactor * (mp.Nrelay2to3 + mp.Nrelay3to4),
                        mp.centering)  # Get the correct orientation
                    EP4noFPM = pad_crop(
                        EP4noFPM, mp.P4.compact.Narr
                    )  # Crop down to the size of the Lyot stop opening
                    EP4 = transOuterFPM * EP4noFPM - EP4sub  # Babinet's principle to get E-field at Lyot plane

                elif mp.coro.upper() in ('FLC', 'SPLC'):
                    EF3 = fpm * EF3inc  # Apply FPM

                    # MFT to Lyot plane
                    EP4 = fp.mft_f2p(EF3, mp.fl, wvl, mp.F3.compact.dxi,
                                     mp.F3.compact.deta, mp.P4.compact.dx,
                                     mp.P4.compact.Narr, mp.centering)
                    EP4 = fp.relay(EP4, NrelayFactor * mp.Nrelay3to4 - 1,
                                   mp.centering)  # Get the correct orientation

                EP4 *= mp.P4.compact.croppedMask  # Apply Lyot stop

                # MFT to camera
                EP4 = fp.relay(
                    EP4, NrelayFactor * mp.NrelayFend, mp.centering
                )  # Rotate the final image 180 degrees if necessary
                EFend = fp.mft_p2f(EP4, mp.fl, wvl, mp.P4.compact.dx,
                                   mp.Fend.dxi, mp.Fend.Nxi, mp.Fend.deta,
                                   mp.Fend.Neta, mp.centering)

                Gzdl[:, Gindex] = EFend[mp.Fend.corr.maskBool] / np.sqrt(
                    mp.Fend.compact.I00[modvar.sbpIndex])

            Gindex += 1
    """ ---------- DM2 ---------- """
    if idm == 2:
        Gzdl = np.zeros((mp.Fend.corr.Npix, mp.dm2.Nele), dtype=complex)

        # Two array sizes (at same resolution) of influence functions for MFT and angular spectrum
        NboxPad2AS = int(mp.dm2.compact.NboxAS)
        mp.dm2.compact.xy_box_lowerLeft_AS = mp.dm2.compact.xy_box_lowerLeft - (
            NboxPad2AS - mp.dm2.compact.Nbox
        ) / 2  # Account for the padding of the influence function boxes

        apodReimaged = pad_crop(apodReimaged, mp.dm2.compact.NdmPad)
        DM2stopPad = pad_crop(DM2stop, mp.dm2.compact.NdmPad)
        Edm2WFEpad = pad_crop(Edm2WFE, mp.dm2.compact.NdmPad)

        # Propagate full field to DM2 before back-propagating in small boxes
        Edm2inc = pad_crop(
            fp.ptp(Edm1out, mp.compact.NdmPad * mp.P2.compact.dx, wvl,
                   mp.d_dm1_dm2),
            mp.dm2.compact.NdmPad)  # E-field incident upon DM2
        Edm2inc = pad_crop(Edm2inc, mp.dm2.compact.NdmPad)
        Edm2 = DM2stopPad * Edm2WFEpad * Edm2inc * np.exp(
            mirrorFac * 2 * np.pi * 1j / wvl *
            pad_crop(DM2surf, mp.dm2.compact.NdmPad)
        )  # Initial E-field at DM2 including its own phase contribution

        # Propagate each actuator from DM2 through the rest of the optical system
        Gindex = 0  # initialize index counter
        for iact in mp.dm2.act_ele:
            if np.sum(
                    np.abs(mp.dm2.compact.inf_datacube[:, :, iact])
            ) > 1e-12:  # Only compute for acutators specified for use or for influence functions that are not zeroed out

                # x- and y- coordinates of the padded influence function in the full padded pupil
                x_box_AS_ind = np.arange(
                    mp.dm2.compact.xy_box_lowerLeft_AS[0, iact],
                    mp.dm2.compact.xy_box_lowerLeft_AS[0, iact] + NboxPad2AS,
                    dtype=int)  # x-indices in pupil arrays for the box
                y_box_AS_ind = np.arange(
                    mp.dm2.compact.xy_box_lowerLeft_AS[1, iact],
                    mp.dm2.compact.xy_box_lowerLeft_AS[1, iact] + NboxPad2AS,
                    dtype=int)  # y-indices in pupil arrays for the box
                indBoxAS = np.ix_(y_box_AS_ind, x_box_AS_ind)
                # x- and y- coordinates of the UN-padded influence function in the full padded pupil
                x_box = mp.dm2.compact.x_pupPad[
                    x_box_AS_ind]  # full pupil x-coordinates of the box
                y_box = mp.dm2.compact.y_pupPad[
                    y_box_AS_ind]  # full pupil y-coordinates of the box

                dEbox = (mp.dm2.VtoH.reshape(mp.dm2.Nact**2)[iact]) * (
                    mirrorFac * 2 * np.pi * 1j / wvl) * pad_crop(
                        np.squeeze(mp.dm2.compact.inf_datacube[:, :, iact]),
                        NboxPad2AS)  # the padded influence function at DM2
                dEP2box = fp.ptp(
                    dEbox * Edm2[indBoxAS], mp.P2.compact.dx * NboxPad2AS, wvl,
                    -1 *
                    (mp.d_dm1_dm2 + mp.d_P2_dm1))  # back-propagate to pupil P2
                #                dEP2box = ptp_inf_func(dEbox.*Edm2(y_box_AS_ind,x_box_AS_ind), mp.P2.compact.dx*NboxPad2AS,wvl,-1*(mp.d_dm1_dm2 + mp.d_P2_dm1), mp.dm2.dm_spacing, mp.propMethodPTP); # back-propagate to pupil P2

                # To simulate going forward to the next pupil plane (with the apodizer) most efficiently,
                # First, back-propagate the apodizer (by rotating 180-degrees) to the previous pupil.
                # Second, negate the coordinates of the box used.
                dEP2box = apodReimaged[
                    indBoxAS] * dEP2box  # Apply 180deg-rotated SP mask.
                dEP3box = np.rot90(
                    dEP2box, k=2 * NrelayFactor * mp.Nrelay2to3
                )  # Forward propagate the cropped box by rotating 180 degrees mp.Nrelay2to3 times.
                # Negate and rotate coordinates to effectively rotate by 180 degrees. No change if 360 degree rotation.
                if np.mod(NrelayFactor * mp.Nrelay2to3, 2) == 1:
                    x_box = -1 * x_box[::-1]
                    y_box = -1 * y_box[::-1]

                # Matrices for the MFT from the pupil P3 to the focal plane mask
                rect_mat_pre = np.exp(
                    -2 * np.pi * 1j * np.outer(mp.F3.compact.etas, y_box) /
                    (wvl * mp.fl)) * np.sqrt(
                        mp.P2.compact.dx * mp.P2.compact.dx) * np.sqrt(
                            mp.F3.compact.dxi * mp.F3.compact.deta) / (wvl *
                                                                       mp.fl)
                rect_mat_post = np.exp(-2 * np.pi * 1j *
                                       np.outer(x_box, mp.F3.compact.xis) /
                                       (wvl * mp.fl))

                EF3inc = rect_mat_pre @ dEP3box @ rect_mat_post  # MFT to FPM

                if mp.coro.upper() in ('LC', 'APLC', 'HLC'):
                    # Propagate through (1 - fpm) for Babinet's principle
                    EF3 = (transOuterFPM - fpm) * EF3inc

                    # MFT to LS ("Sub" name for Subtrahend part of the Lyot-plane E-field)
                    EP4sub = fp.mft_f2p(
                        EF3, mp.fl, wvl, mp.F3.compact.dxi, mp.F3.compact.deta,
                        mp.P4.compact.dx, mp.P4.compact.Narr, mp.centering
                    )  # Subtrahend term for the Lyot plane E-field
                    EP4sub = fp.relay(
                        EP4sub, NrelayFactor * mp.Nrelay3to4 - 1,
                        mp.centering)  # Get the correct orientation

                    EP4noFPM = np.zeros(
                        (mp.dm2.compact.NdmPad, mp.dm2.compact.NdmPad),
                        dtype=complex)
                    EP4noFPM[
                        indBoxAS] = dEP2box  # Propagating the E-field from P2 to P4 without masks gives the same E-field.
                    EP4noFPM = fp.relay(
                        EP4noFPM,
                        NrelayFactor * (mp.Nrelay2to3 + mp.Nrelay3to4),
                        mp.centering
                    )  # Get the number or re-imaging relays between pupils P3 and P4.
                    EP4noFPM = pad_crop(
                        EP4noFPM, mp.P4.compact.Narr
                    )  # Crop down to the size of the Lyot stop opening
                    EP4 = transOuterFPM * EP4noFPM - EP4sub  # Babinet's principle to get E-field at Lyot plane

                elif mp.coro.upper() in ('FLC', 'SPLC'):

                    EF3 = fpm * EF3inc  # Apply FPM

                    # MFT to LS ("Sub" name for Subtrahend part of the Lyot-plane E-field)
                    EP4 = fp.mft_f2p(EF3, mp.fl, wvl, mp.F3.compact.dxi,
                                     mp.F3.compact.deta, mp.P4.compact.dx,
                                     mp.P4.compact.Narr, mp.centering)
                    EP4 = fp.relay(EP4, NrelayFactor * mp.Nrelay3to4 - 1,
                                   mp.centering)

                EP4 *= mp.P4.compact.croppedMask  # Apply Lyot stop

                # MFT to detector
                EP4 = fp.relay(
                    EP4, NrelayFactor * mp.NrelayFend, mp.centering
                )  # Rotate the final image 180 degrees if necessary
                EFend = fp.mft_p2f(EP4, mp.fl, wvl, mp.P4.compact.dx,
                                   mp.Fend.dxi, mp.Fend.Nxi, mp.Fend.deta,
                                   mp.Fend.Neta, mp.centering)

                Gzdl[:, Gindex] = EFend[mp.Fend.corr.maskBool] / np.sqrt(
                    mp.Fend.compact.I00[modvar.sbpIndex])

            Gindex += 1
    """ ---------- DM9 (HLC only) ---------- """
    if idm == 9:
        Gzdl = np.zeros((mp.Fend.corr.Npix, mp.dm9.Nele), dtype=complex)
        Nbox9 = int(mp.dm9.compact.Nbox)

        # Adjust the step size in the Jacobian, then divide back out. Used for
        # helping counteract effect of discretization.
        if not hasattr(mp.dm9, 'stepFac'):
            stepFac = 20
        else:
            stepFac = mp.dm9.stepFac

        # Propagate from DM1 to DM2, and apply DM2 surface and aperture stop
        Edm2 = Edm2WFE * DM2stop * np.exp(mirrorFac*2*np.pi*1j*DM2surf/wvl) * \
            fp.ptp(Edm1out, mp.P2.compact.dx*NdmPad, wvl, mp.d_dm1_dm2)

        # Back-propagate to pupil P2
        dz2 = mp.d_P2_dm1 + mp.d_dm1_dm2
        if dz2 < 10 * wvl:
            EP2eff = Edm2
        else:
            EP2eff = fp.ptp(Edm2, mp.P2.compact.dx * NdmPad, wvl, -dz2)

        # Rotate 180 degrees mp.Nrelay2to3 times to go from pupil P2 to P3
        EP3 = fp.relay(EP2eff, NrelayFactor * mp.Nrelay2to3, mp.centering)

        # Apply apodizer mask
        if mp.flagApod:
            EP3 = mp.P3.compact.mask * pad_crop(EP3, mp.P1.compact.Narr)

        # MFT from pupil P3 to FPM (at focus F3)
        EF3inc = fp.mft_p2f(EP3, mp.fl, wvl, mp.P2.compact.dx,
                            mp.F3.compact.dxi, mp.F3.compact.Nxi,
                            mp.F3.compact.deta, mp.F3.compact.Neta,
                            mp.centering)
        EF3inc = pad_crop(EF3inc, mp.dm9.compact.NdmPad)
        # Coordinates for metal thickness and dielectric thickness
        DM8transIndAll = falco.hlc.discretize_fpm_surf(
            mp.dm8.surf, mp.t_metal_nm_vec, mp.dt_metal_nm)  # All of the mask

        # Propagate each actuator from DM2 through the rest of the optical system
        Gindex = 0  # initialize index counter
        for iact in mp.dm9.act_ele:
            if np.sum(
                    np.abs(mp.dm9.compact.inf_datacube[:, :, iact])
            ) > 1e-12:  # Only compute for acutators specified for use or for influence functions that are not zeroed out

                # xi- and eta- coordinates in the full FPM portion of the focal plane
                xyLL = mp.dm9.compact.xy_box_lowerLeft[:, iact]
                xi_box_ind = np.arange(
                    xyLL[0], xyLL[0] + Nbox9,
                    dtype=int)  # xi-indices in focal arrays for the box
                eta_box_ind = np.arange(
                    xyLL[1], xyLL[1] + Nbox9,
                    dtype=int)  # eta-indices in focal arrays for the box
                indBox = np.ix_(eta_box_ind, xi_box_ind)
                xi_box = mp.dm9.compact.x_pupPad[xi_box_ind]
                eta_box = mp.dm9.compact.y_pupPad[eta_box_ind]

                # Obtain values for the "poked" FPM's complex transmission (only in the sub-array where poked)
                Nxi = Nbox9
                Neta = Nbox9
                DM9surfCropNew = stepFac * mp.dm9.VtoH[
                    iact] * mp.dm9.compact.inf_datacube[:, :, iact] + mp.dm9.surf[
                        indBox]  # New DM9 surface profile in the poked region (meters)
                DM9transInd = falco.hlc.discretize_fpm_surf(
                    DM9surfCropNew, mp.t_diel_nm_vec, mp.dt_diel_nm)
                DM8transInd = DM8transIndAll[
                    indBox]  # Cropped region of the FPM.

                # Look up table to compute complex transmission coefficient of the FPM at each pixel
                fpmPoked = np.zeros(
                    (Neta, Nxi), dtype=complex
                )  # Initialize output array of FPM's complex transmission
                for ix in range(Nxi):
                    for iy in range(Neta):
                        ind_metal = DM8transInd[iy, ix]
                        ind_diel = DM9transInd[iy, ix]
                        fpmPoked[iy,
                                 ix] = mp.complexTransCompact[ind_diel,
                                                              ind_metal,
                                                              modvar.sbpIndex]

                dEF3box = (
                    (transOuterFPM - fpmPoked) -
                    (transOuterFPM - fpm[indBox])) * EF3inc[
                        indBox]  # Delta field (in a small region) at the FPM

                # Matrices for the MFT from the FPM stamp to the Lyot stop
                rect_mat_pre = np.exp(-2*np.pi*1j*np.outer(mp.P4.compact.ys, eta_box)/(wvl*mp.fl)) *\
                    np.sqrt(mp.P4.compact.dx*mp.P4.compact.dx)*np.sqrt(mp.F3.compact.dxi*mp.F3.compact.deta)/(wvl*mp.fl)
                rect_mat_post = np.exp(-2 * np.pi * 1j *
                                       np.outer(xi_box, mp.P4.compact.xs) /
                                       (wvl * mp.fl))

                # MFT from FPM to Lyot stop (Nominal term transOuterFPM*EP4noFPM subtracts out to 0 since it ignores the FPM change).
                EP4 = 0 - rect_mat_pre @ dEF3box @ rect_mat_post  # MFT from FPM (F3) to Lyot stop plane (P4)
                EP4 = fp.relay(EP4, NrelayFactor * mp.Nrelay3to4 - 1,
                               mp.centering)
                EP4 = mp.P4.compact.croppedMask * EP4  # Apply Lyot stop

                # MFT to final focal plane
                EP4 = fp.relay(EP4, NrelayFactor * mp.NrelayFend, mp.centering)
                EFend = fp.mft_p2f(EP4, mp.fl, wvl, mp.P4.compact.dx,
                                   mp.Fend.dxi, mp.Fend.Nxi, mp.Fend.deta,
                                   mp.Fend.Neta, mp.centering)

                Gzdl[:,
                     Gindex] = mp.dm9.act_sens / stepFac * mp.dm9.weight * EFend[
                         mp.Fend.corr.maskBool] / np.sqrt(
                             mp.Fend.compact.I00[modvar.sbpIndex])

            Gindex += 1

    return Gzdl
コード例 #14
0
def vortex(mp, im, idm):
    """
    Differential model used to compute ctrl Jacobian for vortex coronagraph.

    Specialized compact model used to compute the DM response matrix, aka the
    control Jacobian for a vortex coronagraph. Can include an apodizer, making
    it an apodized vortex coronagraph (AVC). Does not include unknown
    aberrations of the full, "truth" model. This model propagates the
    first-order Taylor expansion of the phase from the poke of each actuator
    of the deformable mirror.

    Parameters
    ----------
    mp : ModelParameters
        Structure containing optical model parameters

    Returns
    -------
    Gzdl : numpy ndarray
        Complex-valued, 2-D array containing the Jacobian for the
        specified Zernike mode, DM number, and wavelength.

    """
    modvar = falco.config.Object()  # Initialize the new structure
    modvar.sbpIndex = mp.jac.sbp_inds[im]
    modvar.zernIndex = mp.jac.zern_inds[im]

    wvl = mp.sbp_centers[modvar.sbpIndex]
    mirrorFac = 2.  # Phase change is twice the DM surface height.
    NdmPad = int(mp.compact.NdmPad)

    if mp.flagRotation:
        NrelayFactor = 1
    else:
        NrelayFactor = 0  # zero out the number of relays

    # Minimum FPM resolution for Jacobian calculations (in pixels per lambda/D)
    minPadFacVortex = 8

    # Get FPM charge
    if type(mp.F3.VortexCharge) == np.ndarray:
        # Passing an array for mp.F3.VortexCharge with
        # corresponding wavelengths mp.F3.VortexCharge_lambdas
        # represents a chromatic vortex FPM
        if mp.F3.VortexCharge.size == 1:
            charge = mp.F3.VortexCharge
        else:
            np.interp(wvl, mp.F3.VortexCharge_lambdas, mp.F3.VortexCharge,
                      'linear', 'extrap')
    elif type(mp.F3.VortexCharge) == int or type(mp.F3.VortexCharge) == float:
        # single value indicates fully achromatic mask
        charge = mp.F3.VortexCharge
    else:
        raise TypeError(
            "mp.F3.VortexCharge must be an int, float, or numpy ndarray.")
    """Input E-fields"""
    Ein = np.squeeze(mp.P1.compact.E[:, :, modvar.sbpIndex])

    # Apply a Zernike (in amplitude) at input pupil
    # Used only for Zernike sensitivity control, which requires the perfect
    # E-field of the differential Zernike term.
    if not modvar.zernIndex == 1:
        indsZnoll = modvar.zernIndex  # Just send in 1 Zernike mode
        zernMat = np.squeeze(
            falco.zern.gen_norm_zern_maps(mp.P1.compact.Nbeam, mp.centering,
                                          indsZnoll))
        zernMat = pad_crop(zernMat, mp.P1.compact.Narr)
        Ein = Ein*zernMat*(2*np.pi/wvl) * \
            mp.jac.Zcoef[mp.jac.zerns == modvar.zernIndex]
    """ Masks and DM surfaces """
    pupil = pad_crop(mp.P1.compact.mask, NdmPad)
    Ein = pad_crop(Ein, NdmPad)

    # Re-image the apodizer from pupil P3 back to pupil P2.
    if (mp.flagApod):
        apodReimaged = pad_crop(mp.P3.compact.mask, NdmPad)
        apodReimaged = fp.relay(apodReimaged, NrelayFactor * mp.Nrelay2to3,
                                mp.centering)
    else:
        apodReimaged = np.ones((NdmPad, NdmPad))

    # Compute the DM surfaces for the current DM commands
    if any(mp.dm_ind == 1):
        DM1surf = pad_crop(mp.dm1.compact.surfM, NdmPad)
        # DM1surf = falco.dm.gen_surf_from_act(mp.dm1, mp.dm1.compact.dx, NdmPad)
    else:
        DM1surf = np.zeros((NdmPad, NdmPad))
    if any(mp.dm_ind == 2):
        DM2surf = pad_crop(mp.dm2.compact.surfM, NdmPad)
        # DM2surf = falco.dm.gen_surf_from_act(mp.dm2, mp.dm2.compact.dx, NdmPad)
    else:
        DM2surf = np.zeros((NdmPad, NdmPad))

    if (mp.flagDM1stop):
        DM1stop = pad_crop(mp.dm1.compact.mask, NdmPad)
    else:
        DM1stop = np.ones((NdmPad, NdmPad))
    if (mp.flagDM2stop):
        DM2stop = pad_crop(mp.dm2.compact.mask, NdmPad)
    else:
        DM2stop = np.ones((NdmPad, NdmPad))

    # This block is for BMC surface error testing
    if (mp.flagDMwfe):
        if any(mp.dm_ind == 1):
            Edm1WFE = np.exp(
                2 * np.pi * 1j / wvl *
                pad_crop(mp.dm1.compact.wfe, NdmPad, 'extrapval', 0))
        else:
            Edm1WFE = np.ones((NdmPad, NdmPad))
        if any(mp.dm_ind == 2):
            Edm2WFE = np.exp(
                2 * np.pi * 1j / wvl *
                pad_crop(mp.dm2.compact.wfe, NdmPad, 'extrapval', 0))
        else:
            Edm2WFE = np.ones((NdmPad, NdmPad))
    else:
        Edm1WFE = np.ones((NdmPad, NdmPad))
        Edm2WFE = np.ones((NdmPad, NdmPad))
    """Propagation"""
    # Define pupil P1 and Propagate to pupil P2
    EP1 = pupil * Ein  # E-field at pupil plane P1
    EP2 = fp.relay(EP1, NrelayFactor * mp.Nrelay1to2, mp.centering)

    # Propagate from P2 to DM1, and apply DM1 surface and aperture stop
    if not (abs(mp.d_P2_dm1) == 0):  # E-field arriving at DM1
        Edm1 = fp.ptp(EP2, mp.P2.compact.dx * NdmPad, wvl, mp.d_P2_dm1)
    else:
        Edm1 = EP2
    Edm1out = Edm1 * Edm1WFE * DM1stop * np.exp(
        mirrorFac * 2 * np.pi * 1j * DM1surf / wvl)
    """ ---------- DM1 ---------- """
    if idm == 1:
        Gzdl = np.zeros((mp.Fend.corr.Npix, mp.dm1.Nele), dtype=complex)

        # Array size for planes P3, F3, and P4
        Nfft1 = int(2**falco.util.nextpow2(
            np.max(
                np.array([
                    mp.dm1.compact.NdmPad,
                    minPadFacVortex * mp.dm1.compact.Nbox
                ]))))  # Don't crop--but do pad if necessary.

        # Generate vortex FPM with fftshift already applied
        fftshiftVortex = fftshift(
            falco.mask.falco_gen_vortex_mask(charge, Nfft1))

        # Two array sizes (at same resolution) of influence functions for MFT and angular spectrum
        NboxPad1AS = int(
            mp.dm1.compact.NboxAS
        )  # array size for FFT-AS propagations from DM1->DM2->DM1
        mp.dm1.compact.xy_box_lowerLeft_AS = mp.dm1.compact.xy_box_lowerLeft - (
            mp.dm1.compact.NboxAS - mp.dm1.compact.Nbox
        ) / 2.  # Adjust the sub-array location of the influence function for the added zero padding

        if any(mp.dm_ind == 2):
            DM2surf = pad_crop(DM2surf, mp.dm1.compact.NdmPad)
        else:
            DM2surf = np.zeros((mp.dm1.compact.NdmPad, mp.dm1.compact.NdmPad))
        if (mp.flagDM2stop):
            DM2stop = pad_crop(DM2stop, mp.dm1.compact.NdmPad)
        else:
            DM2stop = np.ones((mp.dm1.compact.NdmPad, mp.dm1.compact.NdmPad))
        apodReimaged = pad_crop(apodReimaged, mp.dm1.compact.NdmPad)

        Edm1pad = pad_crop(Edm1out, mp.dm1.compact.NdmPad
                           )  # Pad or crop for expected sub-array indexing
        Edm2WFEpad = pad_crop(Edm2WFE, mp.dm1.compact.NdmPad
                              )  # Pad or crop for expected sub-array indexing

        # Propagate each actuator from DM1 through the optical system
        Gindex = 0  # initialize index counter
        for iact in mp.dm1.act_ele:
            # Compute only for influence functions that are not zeroed out
            if np.sum(np.abs(mp.dm1.compact.inf_datacube[:, :, iact])) > 1e-12:

                # x- and y- coordinate indices of the padded influence function in the full padded pupil
                x_box_AS_ind = np.arange(
                    mp.dm1.compact.xy_box_lowerLeft_AS[0, iact],
                    mp.dm1.compact.xy_box_lowerLeft_AS[0, iact] + NboxPad1AS,
                    dtype=int)  # x-indices in pupil arrays for the box
                y_box_AS_ind = np.arange(
                    mp.dm1.compact.xy_box_lowerLeft_AS[1, iact],
                    mp.dm1.compact.xy_box_lowerLeft_AS[1, iact] + NboxPad1AS,
                    dtype=int)  # y-indices in pupil arrays for the box
                indBoxAS = np.ix_(y_box_AS_ind, x_box_AS_ind)
                # x- and y- coordinates of the UN-padded influence function in the full padded pupil
                x_box = mp.dm1.compact.x_pupPad[
                    x_box_AS_ind]  # full pupil x-coordinates of the box
                y_box = mp.dm1.compact.y_pupPad[
                    y_box_AS_ind]  # full pupil y-coordinates of the box

                # Propagate from DM1 to DM2, and then back to P2
                dEbox = (mirrorFac * 2 * np.pi * 1j / wvl) * pad_crop(
                    (mp.dm1.VtoH.reshape(mp.dm1.Nact**2)[iact]) * np.squeeze(
                        mp.dm1.compact.inf_datacube[:, :, iact]), NboxPad1AS
                )  # Pad influence function at DM1 for angular spectrum propagation.
                dEbox = fp.ptp(
                    dEbox * Edm1pad[indBoxAS], mp.P2.compact.dx * NboxPad1AS,
                    wvl, mp.d_dm1_dm2
                )  # forward propagate to DM2 and apply DM2 E-field
                dEP2box = fp.ptp(
                    dEbox * Edm2WFEpad[indBoxAS] * DM2stop[indBoxAS] * np.exp(
                        mirrorFac * 2 * np.pi * 1j / wvl * DM2surf[indBoxAS]),
                    mp.P2.compact.dx * NboxPad1AS, wvl,
                    -1 * (mp.d_dm1_dm2 + mp.d_P2_dm1))  # back-propagate to DM1
                #                dEbox = fp.ptp_inf_func(dEbox*Edm1pad[np.ix_(y_box_AS_ind,x_box_AS_ind)], mp.P2.compact.dx*NboxPad1AS,wvl, mp.d_dm1_dm2, mp.dm1.dm_spacing, mp.propMethodPTP) # forward propagate to DM2 and apply DM2 E-field
                #                dEP2box = fp.ptp_inf_func(dEbox.*Edm2WFEpad[np.ix_(y_box_AS_ind,x_box_AS_ind)]*DM2stop(y_box_AS_ind,x_box_AS_ind).*exp(mirrorFac*2*np.pi*1j/wvl*DM2surf(y_box_AS_ind,x_box_AS_ind)), mp.P2.compact.dx*NboxPad1AS,wvl,-1*(mp.d_dm1_dm2 + mp.d_P2_dm1), mp.dm1.dm_spacing, mp.propMethodPTP ) # back-propagate to DM1
                #
                # To simulate going forward to the next pupil plane (with the apodizer) most efficiently,
                # First, back-propagate the apodizer (by rotating 180-degrees) to the previous pupil.
                # Second, negate the coordinates of the box used.
                dEP2boxEff = apodReimaged[
                    indBoxAS] * dEP2box  # Apply 180deg-rotated apodizer mask.
                # dEP3box = np.rot90(dEP2box,k=2*mp.Nrelay2to3) # Forward propagate the cropped box by rotating 180 degrees mp.Nrelay2to3 times.
                # # Negate and reverse coordinate values to effectively rotate by 180 degrees. No change if 360 degree rotation.

                # Re-insert the window around the influence function back into the full beam array.
                EP2eff = np.zeros(
                    (mp.dm1.compact.NdmPad, mp.dm1.compact.NdmPad),
                    dtype=complex)
                EP2eff[indBoxAS] = dEP2boxEff

                # Forward propagate from P2 (effective) to P3
                EP3 = fp.relay(EP2eff, NrelayFactor * mp.Nrelay2to3,
                               mp.centering)

                # Pad pupil P3 for FFT
                EP3pad = pad_crop(EP3, Nfft1)

                # FFT from P3 to Fend.and apply vortex
                EF3 = fftshiftVortex * fft2(fftshift(EP3pad)) / Nfft1

                # FFT from Vortex FPM to Lyot Plane
                EP4 = fftshift(fft2(EF3)) / Nfft1
                EP4 = fp.relay(
                    EP4, NrelayFactor * mp.Nrelay3to4 - 1,
                    mp.centering)  # Add more re-imaging relays if necessary
                if (Nfft1 > mp.P4.compact.Narr):
                    EP4 = mp.P4.compact.croppedMask * pad_crop(
                        EP4, mp.P4.compact.Narr
                    )  # Crop EP4 and then apply Lyot stop
                else:
                    EP4 = pad_crop(
                        mp.P4.compact.croppedMask,
                        Nfft1) * EP4  # Crop the Lyot stop and then apply it.
                    pass

                # MFT to camera
                EP4 = fp.relay(
                    EP4, NrelayFactor * mp.NrelayFend, mp.centering
                )  # Rotate the final image 180 degrees if necessary
                EFend = fp.mft_p2f(EP4, mp.fl, wvl, mp.P4.compact.dx,
                                   mp.Fend.dxi, mp.Fend.Nxi, mp.Fend.deta,
                                   mp.Fend.Neta, mp.centering)

                Gzdl[:, Gindex] = EFend[mp.Fend.corr.maskBool] / np.sqrt(
                    mp.Fend.compact.I00[modvar.sbpIndex])

            Gindex += 1
    """ ---------- DM2 ---------- """
    if idm == 2:
        Gzdl = np.zeros((mp.Fend.corr.Npix, mp.dm2.Nele), dtype=complex)

        # Array size for planes P3, F3, and P4
        Nfft2 = int(2**falco.util.nextpow2(
            np.max(
                np.array([
                    mp.dm2.compact.NdmPad,
                    minPadFacVortex * mp.dm2.compact.Nbox
                ]))))  # Don't crop--but do pad if necessary.

        # Generate vortex FPM with fftshift already applied
        fftshiftVortex = fftshift(
            falco.mask.falco_gen_vortex_mask(charge, Nfft2))

        # Two array sizes (at same resolution) of influence functions for MFT and angular spectrum
        NboxPad2AS = int(mp.dm2.compact.NboxAS)
        mp.dm2.compact.xy_box_lowerLeft_AS = mp.dm2.compact.xy_box_lowerLeft - (
            NboxPad2AS - mp.dm2.compact.Nbox
        ) / 2  # Account for the padding of the influence function boxes

        apodReimaged = pad_crop(apodReimaged, mp.dm2.compact.NdmPad)
        DM2stopPad = pad_crop(DM2stop, mp.dm2.compact.NdmPad)
        Edm2WFEpad = pad_crop(Edm2WFE, mp.dm2.compact.NdmPad)

        # Propagate full field to DM2 before back-propagating in small boxes
        Edm2inc = pad_crop(
            fp.ptp(Edm1out, mp.compact.NdmPad * mp.P2.compact.dx, wvl,
                   mp.d_dm1_dm2),
            mp.dm2.compact.NdmPad)  # E-field incident upon DM2
        Edm2inc = pad_crop(Edm2inc, mp.dm2.compact.NdmPad)
        Edm2 = DM2stopPad * Edm2WFEpad * Edm2inc * np.exp(
            mirrorFac * 2 * np.pi * 1j / wvl *
            pad_crop(DM2surf, mp.dm2.compact.NdmPad)
        )  # Initial E-field at DM2 including its own phase contribution

        # Propagate each actuator from DM2 through the rest of the optical system
        Gindex = 0  # initialize index counter
        for iact in mp.dm2.act_ele:
            # Only compute for acutators specified for use or for influence functions that are not zeroed out
            if np.sum(np.abs(mp.dm2.compact.inf_datacube[:, :, iact])) > 1e-12:

                # x- and y- coordinates of the padded influence function in the full padded pupil
                x_box_AS_ind = np.arange(
                    mp.dm2.compact.xy_box_lowerLeft_AS[0, iact],
                    mp.dm2.compact.xy_box_lowerLeft_AS[0, iact] + NboxPad2AS,
                    dtype=int)  # x-indices in pupil arrays for the box
                y_box_AS_ind = np.arange(
                    mp.dm2.compact.xy_box_lowerLeft_AS[1, iact],
                    mp.dm2.compact.xy_box_lowerLeft_AS[1, iact] + NboxPad2AS,
                    dtype=int)  # y-indices in pupil arrays for the box
                indBoxAS = np.ix_(y_box_AS_ind, x_box_AS_ind)
                #               # x- and y- coordinates of the UN-padded influence function in the full padded pupil
                #                x_box = mp.dm2.compact.x_pupPad[x_box_AS_ind] # full pupil x-coordinates of the box
                #                y_box = mp.dm2.compact.y_pupPad[y_box_AS_ind] # full pupil y-coordinates of the box

                dEbox = (mp.dm2.VtoH.reshape(mp.dm2.Nact**2)[iact]) * (
                    mirrorFac * 2 * np.pi * 1j / wvl) * pad_crop(
                        np.squeeze(mp.dm2.compact.inf_datacube[:, :, iact]),
                        NboxPad2AS)  # the padded influence function at DM2
                dEP2box = fp.ptp(
                    dEbox * Edm2[indBoxAS], mp.P2.compact.dx * NboxPad2AS, wvl,
                    -1 *
                    (mp.d_dm1_dm2 + mp.d_P2_dm1))  # back-propagate to pupil P2
                #                dEP2box = ptp_inf_func(dEbox.*Edm2(y_box_AS_ind,x_box_AS_ind), mp.P2.compact.dx*NboxPad2AS,wvl,-1*(mp.d_dm1_dm2 + mp.d_P2_dm1), mp.dm2.dm_spacing, mp.propMethodPTP); # back-propagate to pupil P2

                # To simulate going forward to the next pupil plane (with the apodizer) most efficiently,
                # First, back-propagate the apodizer (by rotating 180-degrees) to the previous pupil.
                # Second, negate the coordinates of the box used.
                dEP2boxEff = apodReimaged[indBoxAS] * dEP2box
                #                dEP3box = np.rot90(dEP2box,k=2*mp.Nrelay2to3) # Forward propagate the cropped box by rotating 180 degrees mp.Nrelay2to3 times.
                #                # Negate and rotate coordinates to effectively rotate by 180 degrees. No change if 360 degree rotation.
                #                if np.mod(mp.Nrelay2to3,2)==1:
                #                    x_box = -1*x_box[::-1]
                #                    y_box = -1*y_box[::-1]

                EP2eff = np.zeros(
                    (mp.dm2.compact.NdmPad, mp.dm2.compact.NdmPad),
                    dtype=complex)
                EP2eff[indBoxAS] = dEP2boxEff

                # Forward propagate from P2 (effective) to P3
                EP3 = fp.relay(EP2eff, NrelayFactor * mp.Nrelay2to3,
                               mp.centering)

                # Pad pupil P3 for FFT
                EP3pad = pad_crop(EP3, Nfft2)

                # FFT from P3 to Fend.and apply vortex
                EF3 = fftshiftVortex * fft2(fftshift(EP3pad)) / Nfft2

                # FFT from Vortex FPM to Lyot Plane
                EP4 = fftshift(fft2(EF3)) / Nfft2
                EP4 = fp.relay(EP4, NrelayFactor * mp.Nrelay3to4 - 1,
                               mp.centering)

                if (Nfft2 > mp.P4.compact.Narr):
                    EP4 = mp.P4.compact.croppedMask * pad_crop(
                        EP4, mp.P4.compact.Narr)
                else:
                    EP4 = pad_crop(mp.P4.compact.croppedMask, Nfft2) * EP4

                # MFT to detector
                EP4 = fp.relay(EP4, NrelayFactor * mp.NrelayFend, mp.centering)
                EFend = fp.mft_p2f(EP4, mp.fl, wvl, mp.P4.compact.dx,
                                   mp.Fend.dxi, mp.Fend.Nxi, mp.Fend.deta,
                                   mp.Fend.Neta, mp.centering)

                Gzdl[:, Gindex] = EFend[mp.Fend.corr.maskBool] / \
                    np.sqrt(mp.Fend.compact.I00[modvar.sbpIndex])

            Gindex += 1

    return Gzdl
コード例 #15
0
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
コード例 #16
0
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
コード例 #17
0
def full_Fourier(mp, wvl, Ein, normFac, flagScaleFPM=False):
    """
    Truth model with a simple layout used to generate images in simulation.

    Truth model used to generate images in simulation. Can include
    aberrations/errors that are unknown to the estimator and controller.
    This function uses the simplest model (FTs except for angular spectrum
    between DMs) for several coronagraph types.

    Parameters
    ----------
    mp : ModelParameters
        Structure containing optical model parameters
    wvl : Wavelength
        Scalar value for the wavelength of the light in meters
    Ein : Electric field input
        2-D electric field in the input pupil
    normFac : Normalization factor
        Scalar value of the PSF peak normalization factor to apply to the whole
        image.
    flagScaleFPM : bool, optional
        Whether to scale the diameter of the FPM inversely with wavelength.

    Returns
    -------
    Eout : numpy ndarray
        2-D electric field in final focal plane

    """
    check.is_bool(flagScaleFPM, 'flagScaleFPM')

    mirrorFac = 2  # Phase change is twice the DM surface height in reflection
    NdmPad = int(mp.full.NdmPad)

    if mp.flagRotation:
        NrelayFactor = 1
    else:
        NrelayFactor = 0  # zero out the number of relays

    if flagScaleFPM:
        fpmScaleFac = wvl / mp.lambda0
    else:
        fpmScaleFac = 1.0
    """ Masks and DM surfaces """
    if any(mp.dm_ind == 1):
        DM1surf = falco.dm.gen_surf_from_act(mp.dm1, mp.dm1.dx, NdmPad)
#        try:
#            DM1surf = pad_crop(mp.dm1.surfM, NdmPad)
#        except AttributeError:  # No surfM parameter exists, create DM surface
#            DM1surf = falco.dm.gen_surf_from_act(mp.dm1, mp.dm1.dx, NdmPad)
    else:
        DM1surf = np.zeros((NdmPad, NdmPad))

    if any(mp.dm_ind == 2):
        DM2surf = falco.dm.gen_surf_from_act(mp.dm2, mp.dm2.dx, NdmPad)


#        try:
#            DM2surf = pad_crop(mp.dm2.surfM, NdmPad)
#        except AttributeError:  # No surfM parameter exists, create DM surface
#            DM2surf = falco.dm.gen_surf_from_act(mp.dm2, mp.dm2.dx, NdmPad)
    else:
        DM2surf = np.zeros((NdmPad, NdmPad))

    pupil = pad_crop(mp.P1.full.mask, NdmPad)
    Ein = pad_crop(Ein, NdmPad)

    if mp.flagDM1stop:
        DM1stop = pad_crop(mp.dm1.full.mask, NdmPad)
    else:
        DM1stop = np.ones((NdmPad, NdmPad))

    if mp.flagDM2stop:
        DM2stop = pad_crop(mp.dm2.full.mask, NdmPad)
    else:
        DM2stop = np.ones((NdmPad, NdmPad))

    if mp.flagDMwfe:
        pass
        # if(any(mp.dm_ind==1));  Edm1WFE = exp(2*np.pi*1j/wvl*pad_crop(mp.dm1.wfe,NdmPad,'extrapval',0)); else; Edm1WFE = ones(NdmPad); end
        # if(any(mp.dm_ind==2));  Edm2WFE = exp(2*np.pi*1j/wvl*pad_crop(mp.dm2.wfe,NdmPad,'extrapval',0)); else; Edm2WFE = ones(NdmPad); end
    else:
        Edm1WFE = np.ones((NdmPad, NdmPad))
        Edm2WFE = np.ones((NdmPad, NdmPad))
    """ Propagation: entrance pupil, 2 DMs, (optional) apodizer, FPM, LS,
    and final focal plane """

    # Define pupil P1 and Propagate to pupil P2
    EP1 = pupil * Ein  # E-field at pupil plane P1
    EP2 = falco.prop.relay(EP1, NrelayFactor * mp.Nrelay1to2, mp.centering)

    # Propagate from P2 to DM1, and apply DM1 surface and aperture stop
    if not abs(mp.d_P2_dm1) == 0:
        Edm1 = falco.prop.ptp(EP2, mp.P2.full.dx * NdmPad, wvl, mp.d_P2_dm1)
    else:
        Edm1 = EP2  # E-field arriving at DM1
    Edm1b = Edm1 * Edm1WFE * DM1stop * np.exp(
        mirrorFac * 2 * np.pi * 1j * DM1surf / wvl)

    # Propagate from DM1 to DM2, and apply DM2 surface and aperture stop
    Edm2 = falco.prop.ptp(Edm1b, mp.P2.full.dx * NdmPad, wvl, mp.d_dm1_dm2)
    Edm2 *= Edm2WFE * DM2stop * np.exp(
        mirrorFac * 2 * np.pi * 1j * DM2surf / wvl)

    # Back-propagate to pupil P2
    if (mp.d_P2_dm1 + mp.d_dm1_dm2 == 0):
        EP2eff = Edm2  # Do nothing if zero distance
    else:
        EP2eff = falco.prop.ptp(Edm2, mp.P2.full.dx * NdmPad, wvl,
                                -1 * (mp.d_dm1_dm2 + mp.d_P2_dm1))

    # Re-image to pupil P3
    EP3 = falco.prop.relay(EP2eff, NrelayFactor * mp.Nrelay2to3, mp.centering)

    # Apply the apodizer mask (if there is one)
    if (mp.flagApod):
        EP3 = mp.P3.full.mask * pad_crop(EP3, mp.P3.full.Narr)

    # Propagations Specific to the Coronagraph Type
    if mp.coro.upper() in ('LC', 'APLC', 'RODDIER'):
        # MFT from apodizer plane to FPM (i.e., P3 to F3)
        EF3inc = falco.prop.mft_p2f(EP3, mp.fl, wvl, mp.P2.full.dx,
                                    fpmScaleFac * mp.F3.full.dxi,
                                    mp.F3.full.Nxi,
                                    fpmScaleFac * mp.F3.full.deta,
                                    mp.F3.full.Neta, mp.centering)
        # Apply (1-FPM) for Babinet's principle later
        if mp.coro.upper() == 'RODDIER':
            FPM = mp.F3.full.mask * np.exp(
                1j * 2 * np.pi / wvl *
                (mp.F3.n(wvl) - 1) * mp.F3.t * mp.F3.full.mask.phzSupport)
            EF3 = (1. -
                   FPM) * EF3inc  # Apply (1-FPM) for Babinet's princ. later
        else:
            EF3 = (1. - mp.F3.full.mask) * EF3inc
        #  Use Babinet's principle at the Lyot plane.
        EP4noFPM = falco.prop.relay(EP3, NrelayFactor * mp.Nrelay3to4,
                                    mp.centering)
        #  MFT from FPM to Lyot Plane (i.e., F3 to P4)
        EP4subtrahend = falco.prop.mft_f2p(EF3, mp.fl, wvl,
                                           fpmScaleFac * mp.F3.full.dxi,
                                           mp.F3.full.deta,
                                           fpmScaleFac * mp.P4.full.dx,
                                           mp.P4.full.Narr, mp.centering)
        # Babinet's principle at P4
        EP4 = pad_crop(EP4noFPM, mp.P4.full.Narr) - EP4subtrahend

    elif mp.coro.upper() == 'HLC':
        # Complex transmission of the points outside the FPM (just fused silica
        # with optional dielectric and no metal).
        t_Ti_base = 0
        t_Ni_vec = [0]
        t_PMGI_vec = [1e-9 * mp.t_diel_bias_nm]  # [meters]
        pol = 2
        transOuterFPM, rCoef = falco.thinfilm.calc_complex_occulter(
            wvl, mp.aoi, t_Ti_base, t_Ni_vec, t_PMGI_vec, wvl * mp.F3.d0fac,
            pol)

        # MFT from apodizer plane to FPM (i.e., P3 to F3)
        EF3inc = falco.prop.mft_p2f(EP3, mp.fl, wvl, mp.P2.full.dx,
                                    mp.F3.full.dxi, mp.F3.full.Nxi,
                                    mp.F3.full.deta, mp.F3.full.Neta,
                                    mp.centering)
        # Apply (transOuterFPM-FPM) for Babinet's principle later
        EF3 = (transOuterFPM - mp.F3.full.mask) * EF3inc
        # Use Babinet's principle at the Lyot plane.
        # Propagate forward another pupil plane
        EP4noFPM = falco.prop.relay(EP3, NrelayFactor * mp.Nrelay3to4,
                                    mp.centering)
        # Apply the change from the FPM's outer complex transmission.
        EP4noFPM = transOuterFPM * pad_crop(EP4noFPM, mp.P4.full.Narr)
        # MFT from FPM to Lyot Plane (i.e., F3 to P4)
        # Subtrahend term for Babinet's principle
        EP4subtra = falco.prop.mft_f2p(EF3, mp.fl, wvl, mp.F3.full.dxi,
                                       mp.F3.full.deta, mp.P4.full.dx,
                                       mp.P4.full.Narr, mp.centering)
        # Babinet's principle at P4
        EP4 = EP4noFPM - EP4subtra

    elif (mp.coro.upper() == 'SPLC' or mp.coro.upper() == 'FLC'):
        # MFT from apodizer plane to FPM (i.e., P3 to F3)
        EF3inc = falco.prop.mft_p2f(EP3, mp.fl, wvl, mp.P2.full.dx,
                                    fpmScaleFac * mp.F3.full.dxi,
                                    mp.F3.full.Nxi,
                                    fpmScaleFac * mp.F3.full.deta,
                                    mp.F3.full.Neta, mp.centering)
        EF3 = mp.F3.full.mask * EF3inc

        # MFT from FPM to Lyot Plane (i.e., F3 to P4)
        EP4 = falco.prop.mft_f2p(EF3, mp.fl, wvl, fpmScaleFac * mp.F3.full.dxi,
                                 fpmScaleFac * mp.F3.full.deta, mp.P4.full.dx,
                                 mp.P4.full.Narr, mp.centering)
        EP4 = falco.prop.relay(EP4, NrelayFactor * mp.Nrelay3to4 - 1,
                               mp.centering)

    elif mp.coro.upper() in ('VORTEX', 'VC', 'AVC'):
        # Get FPM charge
        if isinstance(mp.F3.VortexCharge, np.ndarray):
            # Passing an array for mp.F3.VortexCharge with
            # corresponding wavelengths mp.F3.VortexCharge_lambdas
            # represents a chromatic vortex FPM
            if mp.F3.VortexCharge.size == 1:
                charge = mp.F3.VortexCharge
            else:
                np.interp(wvl, mp.F3.VortexCharge_lambdas, mp.F3.VortexCharge,
                          'linear', 'extrap')

        elif isinstance(mp.F3.VortexCharge, (int, float)):
            # single value indicates fully achromatic mask
            charge = mp.F3.VortexCharge
        else:
            raise TypeError("mp.F3.VortexCharge must be an int, float, or\
                            numpy ndarray.")
            pass
        EP4 = falco.prop.mft_p2v2p(EP3, charge, mp.P1.full.Nbeam / 2., 0.3, 5)
        EP4 = pad_crop(EP4, mp.P4.full.Narr)

        # Undo the rotation inherent to falco.prop.mft_p2v2p.m
        if not mp.flagRotation:
            EP4 = falco.prop.relay(EP4, -1, mp.centering)

    else:
        log.warning('The chosen coronagraph type is not included yet.')
        raise ValueError("Value of mp.coro not recognized.")
        pass

    # Remove FPM completely if normalization value being found for vortex
    if normFac == 0:
        if mp.coro.upper() in ('VORTEX', 'VC', 'AVC'):
            EP4 = falco.prop.relay(EP3, NrelayFactor * mp.Nrelay3to4,
                                   mp.centering)
            EP4 = pad_crop(EP4, mp.P4.full.Narr)
            pass
        pass
    """ Back to common propagation any coronagraph type """
    # Apply the (cropped-down) Lyot stop
    EP4 *= mp.P4.full.croppedMask

    # MFT from Lyot Stop to final focal plane (i.e., P4 to Fend)
    EP4 = falco.prop.relay(EP4, NrelayFactor * mp.NrelayFend, mp.centering)
    EFend = falco.prop.mft_p2f(EP4, mp.fl, wvl, mp.P4.full.dx, mp.Fend.dxi,
                               mp.Fend.Nxi, mp.Fend.deta, mp.Fend.Neta,
                               mp.centering)

    # Don't apply FPM if normalization value is being found
    if normFac == 0:
        Eout = EFend  # Don't normalize if normalization value is being found
    else:
        Eout = EFend / np.sqrt(normFac)  # Apply normalization

    return Eout