Ejemplo n.º 1
0
def Subaru_frontend(empty_lamda, grid_size, PASSVALUE):
    """
    propagates instantaneous complex E-field thru Subaru from the primary through the AO188
        AO system in loop over wavelength range

    this function is called a 'prescription' by proper

    uses PyPROPER3 to generate the complex E-field at the source, then propagates it through atmosphere,
        then telescope, to the focal plane
    the AO simulator happens here
    this does not include the observation of the wavefront by the detector
    :returns spectral cube at instantaneous time in the focal_plane()
    """
    # print("Propagating Broadband Wavefront Through Subaru")

    # Initialize the Wavefront in Proper
    wfo = opx.Wavefronts()
    wfo.initialize_proper()

    # Atmosphere
    # atmos has only effect on phase delay, not intensity
    wfo.loop_collection(atmos.add_atmos,
                        PASSVALUE['iter'],
                        plane_name='atmosphere')

    # Defines aperture (baffle-before primary)
    # Obscurations (Secondary and Spiders)
    wfo.loop_collection(opx.add_obscurations,
                        d_primary=tp.d_nsmyth,
                        d_secondary=tp.d_secondary,
                        legs_frac=0.05)
    wfo.loop_collection(proper.prop_circular_aperture,
                        **{'radius':
                           tp.entrance_d / 2})  # clear inside, dark outside
    wfo.loop_collection(
        proper.prop_define_entrance,
        plane_name='entrance_pupil')  # normalizes abs intensity

    if ap.companion:
        # Must do this after all calls to prop_define_entrance
        wfo.loop_collection(opx.offset_companion)
        wfo.loop_collection(proper.prop_circular_aperture,
                            **{'radius': tp.entrance_d / 2
                               })  # clear inside, dark outside

    # Test Sampling
    if sp.verbose:
        opx.check_sampling(PASSVALUE['iter'],
                           wfo,
                           "initial",
                           getframeinfo(stack()[0][0]),
                           units='mm')
    # Testing Primary Focus (instead of propagating to focal plane)
    # wfo.loop_collection(opx.prop_pass_lens, tp.flen_nsmyth, tp.flen_nsmyth)  # test only going to prime focus

    ########################################
    # Subaru Propagation
    #######################################
    # Effective Primary
    # CPA from Effective Primary
    wfo.loop_collection(aber.add_aber,
                        step=PASSVALUE['iter'],
                        lens_name='ao188-OAP1')
    # Zernike Aberrations- Low Order
    # wfo.loop_collection(aber.add_zern_ab, tp.zernike_orders, aber.randomize_zern_values(tp.zernike_orders))
    wfo.loop_collection(opx.prop_pass_lens, tp.flen_nsmyth, tp.dist_nsmyth_ao1)

    ########################################
    # AO188 Propagation
    ########################################
    # # AO188-OAP1
    wfo.loop_collection(aber.add_aber,
                        step=PASSVALUE['iter'],
                        lens_name='ao188-OAP1')
    wfo.loop_collection(opx.prop_pass_lens, tp.fl_ao1, tp.dist_ao1_dm)

    # AO System
    if tp.use_ao:
        WFS_map = ao.open_loop_wfs(wfo)
        wfo.loop_collection(
            ao.deformable_mirror,
            WFS_map,
            PASSVALUE['iter'],
            plane_name='woofer',
            debug=sp.debug
        )  # don't use PASSVALUE['WFS_map'] here because open loop
    # ------------------------------------------------
    wfo.loop_collection(proper.prop_propagate, tp.dist_dm_ao2)

    # AO188-OAP2
    wfo.loop_collection(aber.add_aber,
                        step=PASSVALUE['iter'],
                        lens_name='ao188-OAP2')
    # wfo.loop_collection(aber.add_zern_ab, tp.zernike_orders, aber.randomize_zern_values(tp.zernike_orders)/2)
    wfo.loop_collection(opx.prop_pass_lens, tp.fl_ao2, tp.dist_oap2_focus)

    ########################################
    # Focal Plane
    # #######################################
    # Check Sampling in focal plane
    if sp.verbose:
        wfo.loop_collection(opx.check_sampling,
                            PASSVALUE['iter'],
                            "focal plane",
                            getframeinfo(stack()[0][0]),
                            units='nm')

    # wfo.focal_plane fft-shifts wfo from Fourier Space (origin==lower left corner) to object space (origin==center)
    cpx_planes, sampling = wfo.focal_plane()

    print(f"Finished datacube at timestep = {PASSVALUE['iter']}")

    return cpx_planes, sampling
Ejemplo n.º 2
0
def SCExAO_DM(empty_lamda, grid_size, PASSVALUE):
    """
    propagates instantaneous complex E-field thru Subaru from the DM through SCExAO

    this function is called a 'prescription' by proper

    uses PyPROPER3 to generate the complex E-field at the pupil plane, then propagates it through SCExAO 50x50 DM,
        then coronagraphope, to the focal plane
    :returns spectral cube at instantaneous time in the focal_plane()
    """
    # print("Propagating Broadband Wavefront Through Subaru")

    # Initialize the Wavefront in Proper
    wfo = opx.Wavefronts(sp.debug)
    wfo.initialize_proper()


    # Defines aperture (baffle-before primary)
    wfo.loop_collection(proper.prop_circular_aperture,
                        **{'radius': tp.entrance_d / 2})  # clear inside, dark outside
    wfo.loop_collection(proper.prop_define_entrance, plane_name='entrance_pupil')  # normalizes abs intensity
    wfo.loop_collection(opx.SubaruPupil, plane_name='SubaruPupil')

    # Test Sampling
    if sp.verbose:
        wfo.loop_collection(opx.check_sampling, PASSVALUE['iter'], "Telescope Aperture",
                            getframeinfo(stack()[0][0]), units='mm')
    # Testing Primary Focus (instead of propagating to focal plane)
    # wfo.loop_collection(opx.prop_pass_lens, tp.flen_nsmyth, tp.flen_nsmyth)  # test only going to prime focus

    ########################################
    # SCExAO
    # #######################################
    # AO System
    if tp.use_ao:
        WFS_map = ao.open_loop_wfs(wfo)
        wfo.loop_collection(ao.deformable_mirror, WFS_map, PASSVALUE['iter'], apodize=False,
                            plane_name='tweeter', debug=sp.verbose)
    # ------------------------------------------------
    wfo.loop_collection(proper.prop_propagate, tp.fl_SxOAPG)  # from tweeter-DM to OAP2

    # SXExAO Reimaging 2
    wfo.loop_collection(aber.add_aber, step=PASSVALUE['iter'], lens_name='SxOAP2')
    wfo.loop_collection(aber.add_zern_ab, tp.zernike_orders, aber.randomize_zern_values(tp.zernike_orders)/2)
    wfo.loop_collection(opx.prop_pass_lens, tp.fl_SxOAP2, tp.fl_SxOAP2, plane_name='post-DM-focus')  #tp.dist_sl2_focus

    # wfo.loop_collection(opx.check_sampling, PASSVALUE['iter'], "post-DM-focus",
    #                     getframeinfo(stack()[0][0]), units='nm')

    # Coronagraph
    # settings should be put into tp, and are not implicitly passed here
    wfo.loop_collection(cg.coronagraph, occulter_mode=tp.cg_type, plane_name='coronagraph')

    ########################################
    # Focal Plane
    # #######################################
    # Check Sampling in focal plane
    # wfo.focal_plane fft-shifts wfo from Fourier Space (origin==lower left corner) to object space (origin==center)
    cpx_planes, sampling = wfo.focal_plane()

    if sp.verbose:
        wfo.loop_collection(opx.check_sampling, PASSVALUE['iter'], "focal plane",
                            getframeinfo(stack()[0][0]), units='nm')
        # opx.check_sampling(PASSVALUE['iter'], wfo, "focal plane", getframeinfo(stack()[0][0]), units='arcsec')

    if sp.verbose:
        print(f"Finished datacube at timestep = {PASSVALUE['iter']}")

    return cpx_planes, sampling
Ejemplo n.º 3
0
def general_telescope(empty_lamda, grid_size, PASSVALUE):
    """
    #TODO pass complex datacube for photon phases

    propagates instantaneous complex E-field through the optical system in loop over wavelength range

    this function is called as a 'prescription' by proper

    uses PyPROPER3 to generate the complex E-field at the source, then propagates it through atmosphere, then telescope, to the focal plane
    currently: optics system "hard coded" as single aperture and lens
    the AO simulator happens here
    this does not include the observation of the wavefront by the detector
    :returns spectral cube at instantaneous time
    """
    # print("Propagating Broadband Wavefront Through Telescope")

    # Parameters-import statements won't work through the function
    passpara = PASSVALUE['params']
    ap.__dict__ = passpara[0].__dict__
    tp.__dict__ = passpara[1].__dict__
    iop.__dict__ = passpara[2].__dict__
    sp.__dict__ = passpara[3].__dict__

    # datacube = []
    wfo = opx.Wavefronts()
    wfo.initialize_proper()

    ###################################################
    # Aperture, Atmosphere, and Secondary Obscuration
    ###################################################
    # Defines aperture (baffle-before primary)
    wfo.loop_collection(proper.prop_circular_aperture,
                        **{'radius': tp.entrance_d / 2})
    # wfo.loop_collection(proper.prop_define_entrance)  # normalizes the intensity

    # Obscure Baffle
    if tp.obscure:
        wfo.loop_collection(opx.add_obscurations,
                            M2_frac=1 / 8,
                            d_primary=tp.entrance_d,
                            legs_frac=tp.legs_frac)

    # Pass through a mini-atmosphere inside the telescope baffle
    #  The atmospheric model used here (as of 3/5/19) uses different scale heights,
    #  wind speeds, etc to generate an atmosphere, but then flattens it all into
    #  a single phase mask. The phase mask is a real-valued delay lengths across
    #  the array from infinity. The delay length thus corresponds to a different
    #  phase offset at a particular frequency.
    # quicklook_wf(wfo.wf_collection[0,0])
    if tp.use_atmos:
        wfo.loop_collection(atmos.add_atmos,
                            PASSVALUE['iter'],
                            plane_name='atmosphere')
        # quicklook_wf(wfo.wf_collection[0, 0])

    # quicklook_wf(wfo.wf_collection[0,0])
    #TODO rotate atmos not yet implementid in 2.0
    # if tp.rotate_atmos:
    #     wfo.loop_collection(aber.rotate_atmos, *(PASSVALUE['iter']))

    # Both offsets and scales the companion wavefront
    if wfo.wf_collection.shape[1] > 1:
        wfo.loop_collection(opx.offset_companion)
        wfo.loop_collection(proper.prop_circular_aperture,
                            **{'radius': tp.entrance_d / 2
                               })  # clear inside, dark outside

    # TODO rotate atmos not yet implementid in 2.0
    # if tp.rotate_sky:
    #     wfo.loop_collection(opx.rotate_sky, *PASSVALUE['iter'])

    ########################################
    # Telescope Primary-ish Aberrations
    #######################################
    # Abberations before AO
    if tp.use_CPA:
        wfo.loop_collection(aber.add_aber,
                            tp.entrance_d,
                            tp.aber_params,
                            tp.aber_vals,
                            step=PASSVALUE['iter'],
                            lens_name='CPA')
    # wfo.loop_collection(proper.prop_circular_aperture, **{'radius': tp.entrance_d / 2})
    # wfo.wf_collection = aber.abs_zeros(wfo.wf_collection)

    #######################################
    # AO
    #######################################
    if tp.use_ao:
        if not sp.closed_loop:
            WFS_map = ao.open_loop_wfs(wfo)
            # tiptilt = np.zeros((sp.grid_size,sp.grid_size))
        else:
            #TODO Rupert-CPA maps are no longer generated. Not sure what you want to do here. The CPA map is stored
            # as a fits file at f"{iop.aberdir}/t{iter}_CPA.fits"
            #
            WFS_map = PASSVALUE['CPA_maps']
            # tiptilt = PASSVALUE['tiptilt']

        # if tp.include_tiptilt:
        #     CPA_maps, PASSVALUE['tiptilt'] = ao.tiptilt(wfo, CPA_maps, tiptilt)

        if tp.include_dm:
            wfo.loop_collection(ao.deformable_mirror, WFS_map,
                                PASSVALUE['theta'])

########################################
# Post-AO Telescope Distortions
# #######################################
# Abberations after the AO Loop
    if tp.use_NCPA:
        aber.add_aber(wfo,
                      tp.f_lens,
                      tp.aber_params,
                      tp.aber_vals,
                      PASSVALUE['iter'],
                      lens_name='NCPA')
        wfo.loop_collection(proper.prop_circular_aperture,
                            **{'radius': tp.entrance_d / 2})
        # TODO does this need to be here?
        # wfo.loop_collection(opx.add_obscurations, tp.entrance_d/4, legs=False)
        # wfo.wf_collection = aber.abs_zeros(wfo.wf_collection)

    # Low-order aberrations
    if tp.use_zern_ab:
        wfo.loop_collection(aber.add_zern_ab)

    if tp.use_apod:
        wfo.loop_collection(apodization, True)

    # First Focusing Optics
    wfo.loop_collection(opx.prop_pass_lens, tp.f_lens, tp.f_lens)

    ########################################
    # Coronagraph
    ########################################
    # there are additional un-aberated optics in the coronagraph module
    if tp.use_coronagraph:
        wfo.loop_collection(
            coronagraph,
            *(tp.f_lens, tp.occulter_type, tp.occult_loc, tp.entrance_d))

    ########################################
    # Focal Plane
    ########################################
    cpx_planes, sampling = wfo.focal_plane()

    print(f"Finished datacube at timestep = {PASSVALUE['iter']}")

    return cpx_planes, wfo.plane_sampling
Ejemplo n.º 4
0
def Subaru_SCExAO(empty_lamda, grid_size, PASSVALUE):
    """
    propagates instantaneous complex E-field thru Subaru from the primary through SCExAO

    this function is called a 'prescription' by proper

    uses PyPROPER3 to generate the complex E-field at the source, then propagates it through atmosphere,
        then telescope, to the focal plane
    the AO simulator happens here
    this does not include the observation of the wavefront by the detector
    :returns spectral cube at instantaneous time in the focal_plane()
    """
    # print("Propagating Broadband Wavefront Through Subaru")

    # Initialize the Wavefront in Proper
    wfo = opx.Wavefronts(sp.debug)
    wfo.initialize_proper()

    # Atmosphere
    # atmos has only effect on phase delay, not intensity
    wfo.loop_collection(atmos.add_atmos,
                        PASSVALUE['iter'],
                        plane_name='atmosphere')

    # Defines aperture (baffle-before primary)
    # wfo.loop_collection(opx.add_obscurations, d_primary=tp.entrance_d, d_secondary=tp.d_secondary, legs_frac=0.05)
    wfo.loop_collection(opx.SubaruPupil, plane_name='SubaruPupil')
    wfo.loop_collection(proper.prop_circular_aperture,
                        **{'radius':
                           tp.entrance_d / 2})  # clear inside, dark outside
    wfo.loop_collection(
        proper.prop_define_entrance,
        plane_name='entrance_pupil')  # normalizes abs intensity

    if ap.companion:
        # Must do this after all calls to prop_define_entrance
        wfo.loop_collection(opx.offset_companion)
        wfo.loop_collection(proper.prop_circular_aperture,
                            **{'radius': tp.entrance_d / 2
                               })  # clear inside, dark outside

    # Test Sampling
    if sp.verbose:
        wfo.loop_collection(opx.check_sampling,
                            PASSVALUE['iter'],
                            "Telescope Aperture",
                            getframeinfo(stack()[0][0]),
                            units='mm')
    # Testing Primary Focus (instead of propagating to focal plane)
    # wfo.loop_collection(opx.prop_pass_lens, tp.flen_nsmyth, tp.flen_nsmyth)  # test only going to prime focus

    ########################################
    # Subaru Propagation
    #######################################
    # Effective Primary
    # CPA from Effective Primary
    wfo.loop_collection(aber.add_aber,
                        step=PASSVALUE['iter'],
                        lens_name='effective-primary')  # high order
    wfo.loop_collection(aber.add_zern_ab, tp.zernike_orders,
                        aber.randomize_zern_values(
                            tp.zernike_orders))  # low order
    wfo.loop_collection(opx.prop_pass_lens, tp.flen_nsmyth, tp.dist_nsmyth_ao1)
    ########################################
    # AO188 Propagation
    ########################################
    # # AO188-OAP1
    wfo.loop_collection(aber.add_aber,
                        step=PASSVALUE['iter'],
                        lens_name='ao188-OAP1')  # high order
    wfo.loop_collection(opx.prop_pass_lens, tp.fl_ao1, tp.dist_ao1_dm)

    # AO System
    if tp.use_ao:
        WFS_map = ao.open_loop_wfs(wfo)
        wfo.loop_collection(
            ao.deformable_mirror,
            WFS_map,
            PASSVALUE['iter'],
            apodize=True,
            plane_name='woofer',
            debug=sp.verbose
        )  # don't use PASSVALUE['WFS_map'] here because open loop
    # ------------------------------------------------
    wfo.loop_collection(proper.prop_propagate, tp.dist_dm_ao2)

    # AO188-OAP2
    wfo.loop_collection(aber.add_aber,
                        step=PASSVALUE['iter'],
                        lens_name='ao188-OAP2')  # high order CPA
    wfo.loop_collection(aber.add_zern_ab, tp.zernike_orders,
                        aber.randomize_zern_values(tp.zernike_orders) /
                        2)  # low order CPA
    wfo.loop_collection(opx.prop_pass_lens, tp.fl_ao2, tp.dist_oap2_focus)

    ########################################
    # SCExAO
    # #######################################
    # SXExAO Reimaging 1
    wfo.loop_collection(aber.add_aber,
                        step=PASSVALUE['iter'],
                        lens_name='SxOAPG')  # high order CPA
    wfo.loop_collection(proper.prop_propagate,
                        tp.fl_SxOAPG)  # from AO188 focus to S-OAP1
    wfo.loop_collection(opx.prop_pass_lens, tp.fl_SxOAPG,
                        tp.fl_SxOAPG)  # from SxOAP1 to tweeter-DM
    #
    # AO System
    if tp.use_ao:
        # WFS_map = ao.open_loop_wfs(wfo)
        wfo.loop_collection(ao.deformable_mirror,
                            WFS_map,
                            PASSVALUE['iter'],
                            apodize=True,
                            plane_name='tweeter',
                            debug=sp.verbose)
    # ------------------------------------------------
    wfo.loop_collection(proper.prop_propagate,
                        tp.fl_SxOAPG)  # from tweeter-DM to OAP2

    # SXExAO Reimaging 2
    wfo.loop_collection(aber.add_aber,
                        step=PASSVALUE['iter'],
                        lens_name='SxOAP2')  # high order NCPA
    wfo.loop_collection(aber.add_zern_ab, tp.zernike_orders,
                        aber.randomize_zern_values(tp.zernike_orders) /
                        2)  # low order NCPA
    wfo.loop_collection(opx.prop_pass_lens,
                        tp.fl_SxOAP2,
                        tp.fl_SxOAP2,
                        plane_name='post-DM-focus')  #tp.dist_sl2_focus

    # wfo.loop_collection(opx.check_sampling, PASSVALUE['iter'], "post-DM-focus",
    #                     getframeinfo(stack()[0][0]), units='nm')

    # Coronagraph
    # settings should be put into tp, and are not implicitly passed here
    wfo.loop_collection(cg.coronagraph,
                        occulter_mode=tp.cg_type,
                        plane_name='coronagraph')

    ########################################
    # Focal Plane
    # #######################################
    # Check Sampling in focal plane
    # wfo.focal_plane fft-shifts wfo from Fourier Space (origin==lower left corner) to object space (origin==center)
    cpx_planes, sampling = wfo.focal_plane()

    if sp.verbose:
        wfo.loop_collection(opx.check_sampling,
                            PASSVALUE['iter'],
                            "focal plane",
                            getframeinfo(stack()[0][0]),
                            units='nm')
        # opx.check_sampling(PASSVALUE['iter'], wfo, "focal plane", getframeinfo(stack()[0][0]), units='arcsec')

    if sp.verbose:
        print(f"Finished datacube at timestep = {PASSVALUE['iter']}")

    return cpx_planes, sampling
Ejemplo n.º 5
0
def Hubble_frontend(empty_lamda, grid_size, PASSVALUE):
    """
    propagates instantaneous complex E-field tSubaru from the primary through the AO188
        AO system in loop over wavelength range

    this function is called a 'prescription' by proper

    uses PyPROPER3 to generate the complex E-field at the source, then propagates it through atmosphere,
        then telescope, to the focal plane
    the AO simulator happens here
    this does not include the observation of the wavefront by the detector
    :returns spectral cube at instantaneous time, sampling of the wavefront at final location

    """
    # print("Propagating Broadband Wavefront Through Hubble Telescope")

    # Getting Parameters-import statements weren't working--RD
    passpara = PASSVALUE['params']
    ap.__dict__ = passpara[0].__dict__
    tp.__dict__ = passpara[1].__dict__
    iop.__dict__ = passpara[2].__dict__
    sp.__dict__ = passpara[3].__dict__

    datacube = []

    # Initialize the Wavefront in Proper
    wfo = opx.Wavefronts()
    wfo.initialize_proper()

    ########################################
    # Hubble Propagation
    #######################################
    # Defines aperture (baffle-before primary)
    wfo.loop_collection(proper.prop_circular_aperture,
                        **{'radius':
                           tp.entrance_d / 2})  # clear inside, dark outside
    # Obscurations
    wfo.loop_collection(opx.add_obscurations,
                        d_primary=tp.entrance_d,
                        d_secondary=tp.d_secondary,
                        legs_frac=0.01)
    wfo.loop_collection(
        proper.prop_define_entrance)  # normalizes the intensity

    # Test Sampling
    if PASSVALUE['iter'] == 1:
        initial_sampling = proper.prop_get_sampling(wfo.wf_collection[0, 0])
        dprint(f"initial sampling is {initial_sampling:.4f}")

    # Primary
    # CPA from Effective Primary
    # aber.add_aber(wfo.wf_collection, tp.entrance_d, step=PASSVALUE['iter'], lens_name='primary')

    # wfo.loop_collection(opx.prop_pass_lens, tp.flen_primary, tp.flen_primary)
    wfo.loop_collection(opx.prop_pass_lens, tp.flen_primary,
                        tp.dist_pri_second)

    # Secondary
    # aber.add_aber(wfo.wf_collection, tp.d_secondary, step=PASSVALUE['iter'], lens_name='second')
    # # Zernike Aberrations- Low Order
    # wfo.loop_collection(aber.add_zern_ab, tp.zernike_orders, tp.zernike_vals)
    wfo.loop_collection(opx.prop_pass_lens, tp.flen_secondary,
                        tp.dist_second_focus)

    ########################################
    # Focal Plane
    # #######################################
    # Converting Array of Arrays (wfo) into 3D array
    #  wavefront array is now (number_wavelengths x number_astro_bodies x sp.grid_size x sp.grid_size)
    #  prop_end moves center of the wavefront from lower left corner (Fourier space) back to the center
    #    ^      also takes square modulus of complex values, so gives units as intensity not field
    shape = wfo.wf_collection.shape
    for iw in range(shape[0]):
        for io in range(shape[1]):
            if sp.maskd_size != sp.grid_size:
                wframes = np.zeros((sp.maskd_size, sp.maskd_size))
                (wframe,
                 sampling) = proper.prop_end(wfo.wf_collection[iw, io],
                                             EXTRACT=np.int(sp.maskd_size))
            else:
                wframes = np.zeros((sp.grid_size, sp.grid_size))
                (wframe, sampling) = proper.prop_end(wfo.wf_collection[
                    iw, io])  # Sampling returned by proper is in m
            wframes += wframe  # adds 2D wavefront from all astro_bodies together into single wavefront, per wavelength
        # dprint(f"sampling in focal plane at wavelength={iw} is {sampling} m")
        datacube.append(
            wframes)  # puts each wavlength's wavefront into an array
        # (number_wavelengths x sp.grid_size x sp.grid_size)

    datacube = np.array(datacube)
    datacube = np.roll(np.roll(datacube, tp.pix_shift[0], 1), tp.pix_shift[1],
                       2)  # cirshift array for off-axis observing
    datacube = np.abs(datacube)  # get intensity from datacube

    # Interpolating spectral cube from ap.n_wvl_init discreet wavelengths to ap.n_wvl_final
    if ap.interp_wvl and ap.n_wvl_init > 1 and ap.n_wvl_init < ap.n_wvl_final:
        wave_samps = np.linspace(0, 1, ap.n_wvl_init)
        f_out = interp1d(wave_samps, datacube, axis=0)
        new_heights = np.linspace(0, 1, ap.n_wvl_final)
        datacube = f_out(new_heights)

    print('Finished datacube at single timestep')

    return datacube, sampling
Ejemplo n.º 6
0
def general_telescope(empty_lamda, grid_size, PASSVALUE):
    """
    #TODO pass complex datacube for photon phases

    propagates instantaneous complex E-field through the optical system in loop over wavelength range

    this function is called as a 'prescription' by proper

    uses PyPROPER3 to generate the complex E-field at the source, then propagates it through atmosphere, then telescope, to the focal plane
    currently: optics system "hard coded" as single aperture and lens
    the AO simulator happens here
    this does not include the observation of the wavefront by the detector
    :returns spectral cube at instantaneous time
    """
    # print("Propagating Broadband Wavefront Through Telescope")

    wfo = opx.Wavefronts(debug=sp.debug)
    wfo.initialize_proper(set_up_beam=True)

    ###################################################
    # Atmosphere, and Secondary Obscuration
    ###################################################

    # Pass through a mini-atmosphere inside the telescope baffle
    #  The atmospheric model used here (as of 3/5/19) uses different scale heights,
    #  wind speeds, etc to generate an atmosphere, but then flattens it all into
    #  a single phase mask. The phase mask is a real-valued delay lengths across
    #  the array from infinity. The delay length thus corresponds to a different
    #  phase offset at a particular frequency.
    if tp.use_atmos:
        wfo.loop_collection(atmos.add_atmos,
                            PASSVALUE['iter'],
                            (iop.atmosdir, sp.sample_time, atmp.model),
                            spatial_zoom=True,
                            plane_name='atmosphere')

    #TODO rotate atmos not yet implementid in 2.0
    # if tp.rotate_atmos:
    #     wfo.loop_collection(aber.rotate_atmos, *(PASSVALUE['iter']))

    # Both offsets and scales the companion wavefront
    if wfo.wf_collection.shape[1] > 1:
        wfo.loop_collection(opx.offset_companion, step=PASSVALUE['iter'])
        wfo.loop_collection(proper.prop_circular_aperture,
                            **{'radius': tp.entrance_d / 2
                               })  # clear inside, dark outside

    # TODO rotate atmos not yet implementid in 2.0
    # if tp.rotate_sky:
    #     wfo.loop_collection(opx.rotate_sky, *PASSVALUE['iter'])

    ########################################
    # Telescope Primary-ish Aberrations
    #######################################
    # Abberations before AO

    wfo.loop_collection(aber.add_aber,
                        iop.aberdir,
                        PASSVALUE['iter'],
                        lens_name='CPA')

    # wfo.loop_collection(proper.prop_circular_aperture, **{'radius': tp.entrance_d / 2})
    # wfo.wf_collection = aber.abs_zeros(wfo.wf_collection)

    #######################################
    # AO
    #######################################
    if tp.use_ao:

        if sp.closed_loop:
            previous_output = ao.retro_wfs(
                PASSVALUE['AO_field'], wfo,
                plane_name='wfs')  # unwrap a previous steps phase map
            wfo.loop_collection(ao.deformable_mirror,
                                WFS_map=None,
                                iter=PASSVALUE['iter'],
                                previous_output=previous_output,
                                plane_name='deformable mirror')
        elif sp.ao_delay > 0:
            WFS_map = ao.retro_wfs(
                PASSVALUE['WFS_field'], wfo,
                plane_name='wfs')  # unwrap a previous steps phase map
            wfo.loop_collection(ao.deformable_mirror,
                                WFS_map,
                                iter=PASSVALUE['iter'],
                                previous_output=None,
                                plane_name='deformable mirror')
        else:
            WFS_map = ao.open_loop_wfs(
                wfo,
                plane_name='wfs')  # just uwraps this steps measured phase_map
            wfo.loop_collection(ao.deformable_mirror,
                                WFS_map,
                                iter=PASSVALUE['iter'],
                                previous_output=None,
                                plane_name='deformable mirror')

    # Obscure Baffle
    if tp.obscure:
        wfo.loop_collection(opx.add_obscurations,
                            M2_frac=1 / 8,
                            d_primary=tp.entrance_d,
                            legs_frac=tp.legs_frac)

########################################
# Post-AO Telescope Distortions
# #######################################
# Abberations after the AO Loop

    wfo.loop_collection(aber.add_aber,
                        iop.aberdir,
                        PASSVALUE['iter'],
                        lens_name='NCPA')
    wfo.loop_collection(proper.prop_circular_aperture,
                        **{'radius': tp.entrance_d / 2})
    # TODO does this need to be here?
    # wfo.loop_collection(opx.add_obscurations, tp.entrance_d/4, legs=False)
    # wfo.wf_collection = aber.abs_zeros(wfo.wf_collection)

    wfo.loop_collection(opx.prop_pass_lens,
                        tp.lens_params[0]['focal_length'],
                        tp.lens_params[0]['focal_length'],
                        plane_name='pre_coron')

    ########################################
    # Coronagraph
    ########################################
    # there are additional un-aberated optics in the coronagraph module

    wfo.loop_collection(coronagraph,
                        occulter_mode=tp.cg_type,
                        plane_name='coronagraph')

    ########################################
    # Focal Plane
    ########################################
    cpx_planes, sampling = wfo.focal_plane()

    print(f"Finished datacube at timestep = {PASSVALUE['iter']}")

    return cpx_planes, wfo.plane_sampling