<jupyter_start><jupyter_text>Setup and Intro This worksheet assumes you are already familiar with at least the basics of using Python and the IPython/Jupyter notebook. It may help to have at least glanced through the "Getting Started" notebook.  **First step: From the "Cell" menu above, select "All Output", then "Clear"**. This will clear out all the output from this notebook and let you run it step-by-step yourself.Next is some basic setup and imports:<jupyter_code>%pylab inline
pylab.rcParams['figure.figsize'] = (12.0, 8.0)  # bigger plots by default. Adjust as desired
import numpy as np
import poppy
import gpipsfs<jupyter_output>Populating the interactive namespace from numpy and matplotlib<jupyter_text>Building up to the GPI Coronagraphic PSF Let's begin by creating a GPI simulator object, and making a direct mode (non-coronagraphic) PSF:<jupyter_code>gpi = gpipsfs.GPI(npix=512, satspots=False, lyot_tabs=False, display_before=True)<jupyter_output><empty_output><jupyter_text>For now we keep things simple and turn **off** the satellite spots and the bad-actuator masking tabs in this simulation (we'll add them back in later).  We then turn **on** an option for displaying before and after the occulter and Lyot planes (instead of the default which is just to show after).  We also decrease the number of pixels used for the pupil to (512,512) instead of the default (1024,1024) just to make things run a bit faster. This limits PSF fidelity and increases aliasing of high frequencies, so it's not ideal in general, but it's an OK tradeoff for rough or preliminary calculations like this.  Now let's make a simple direct mode PSF at a single wavelength. Feel free to change around the below options if you would like to experiment with another filter or wavelength. By setting the `verbose` option to `calcPSF`, we can get a printout of how much light remains after each step of the optical system, along with the display of the wavefront after each optic.<jupyter_code>gpi.obsmode ='H_direct'
wavelen = 1.6e-6  # wavelength is always given in meters
psf_direct= gpi.calc_psf(wavelength=wavelen, display=True, verbose=True)<jupyter_output>after Gemini South Primary,              total intensity = 1
after GPI Apodizer CLEAR,                total intensity = 1
after Before FPM,                        total intensity = 1
after GPI FPM SCIENCE,                   total intensity = 1
after Before Lyot,                       total intensity = 1
after GPI Lyot Open,                     total intensity = 1
before GPI lenslet array,                total intensity = 0.991<jupyter_text>(_You may note the intermediate focal planes and final focal planes above don't look precisely identical; this is mostly just a display issue because the intermediate planes aren't resampled onto the 14.1 mas/lenslet scale like the final focal plane is._) First step in blocking star light: let's add a circular coronagraph occulter, all by itself:<jupyter_code>gpi.occulter="H"
psf= gpi.calc_psf(wavelength=wavelen, display=True, verbose=True)<jupyter_output>after Gemini South Primary,              total intensity = 1
after GPI Apodizer CLEAR,                total intensity = 1
after Before FPM,                        total intensity = 1
after GPI FPM H,                         total intensity = 0.0935
after Before Lyot,                       total intensity = 0.0935
after GPI Lyot Open,                     total intensity = 0.0767
before GPI lenslet array,                total intensity = 0.0668<jupyter_text>_(As an aside, the default color tables use gray scale for pupil plane intensities, oranges and yellows ('heat' colormap) for image plane intensities, and reds and blues for positive and negative wavefront errors.  These are intended just as a convenience to help users intuitively keep track of what's what, and the color tables can be changed and customized if desired.)_ Look at where the remaining light is in the Lyot plane: lots of residual at the edges, center, and secondary supports. Let's try to block that by adding a Lyot stop:<jupyter_code>gpi.lyotmask="080m12_04"  # this happens to be the part number for the mask used in H. 
psf= gpi.calc_psf(wavelength=wavelen, display=True, verbose=True)<jupyter_output>after Gemini South Primary,              total intensity = 1
after GPI Apodizer CLEAR,                total intensity = 1
after Before FPM,                        total intensity = 1
after GPI FPM H,                         total intensity = 0.0935
after Before Lyot,                       total intensity = 0.0935
after GPI Lyot 080m12_04,                total intensity = 0.03
before GPI lenslet array,                total intensity = 0.0286<jupyter_text>Look at the PSFs, and also check the output text to see how much more the starlight was reduced. That worked pretty well. _(Note that each image is scaled independently for display, and the min/max for the after Lyot mask image is quite different than for the image just before!)_We can block even more light by using a Lyot mask which is more aggressively undersized:<jupyter_code>gpi.lyotmask="080m12_10" 
psf= gpi.calc_psf(monochromatic=wavelen, display=True, verbose=True)<jupyter_output>after Gemini South Primary,              total intensity = 1
after GPI Apodizer CLEAR,                total intensity = 1
after Before FPM,                        total intensity = 1
after GPI FPM H,                         total intensity = 0.0935
after Before Lyot,                       total intensity = 0.0935
after GPI Lyot 080m12_10,                total intensity = 0.0063
before GPI lenslet array,                total intensity = 0.00596<jupyter_text>That works as expected (but comes at a cost in lower throughput for off axis sources, so it's not ideal'). Let's try something different: an apodizer. First we will try one all by itself.<jupyter_code>gpi.lyotmask='Open'  
gpi.occulter='SCIENCE' 
gpi.apodizer="H" 
psf= gpi.calc_psf(wavelength=wavelen, display=True, verbose=True)<jupyter_output>after Gemini South Primary,              total intensity = 1
after GPI Apodizer H,                    total intensity = 0.462
after Before FPM,                        total intensity = 0.462
after GPI FPM SCIENCE,                   total intensity = 0.462
after Before Lyot,                       total intensity = 0.462
after GPI Lyot Open,                     total intensity = 0.462
before GPI lenslet array,                total intensity = 0.46<jupyter_text>The apodizer all by itself strongly attenuates most of the Airy rings. About half the starlight remains, but it's all concentrated pretty close to the middle. Now let's add the occulter back in:<jupyter_code>gpi.occulter='H'
psf= gpi.calc_psf(wavelength=wavelen, display=True, verbose=True)<jupyter_output>after Gemini South Primary,              total intensity = 1
after GPI Apodizer H,                    total intensity = 0.462
after Before FPM,                        total intensity = 0.462
after GPI FPM H,                         total intensity = 0.00287
after Before Lyot,                       total intensity = 0.00287
after GPI Lyot Open,                     total intensity = 0.00246
before GPI lenslet array,                total intensity = 0.00101<jupyter_text>And now we add the Lyot mask. Here at least we've assembled the H_coron mode!<jupyter_code>gpi.lyotmask="080m12_04"  
psf= gpi.calc_psf(monochromatic=wavelen, display=True, verbose=True)<jupyter_output>after Gemini South Primary,              total intensity = 1
after GPI Apodizer H,                    total intensity = 0.462
after Before FPM,                        total intensity = 0.462
after GPI FPM H,                         total intensity = 0.00287
after Before Lyot,                       total intensity = 0.00287
after GPI Lyot 080m12_04,                total intensity = 0.000107
before GPI lenslet array,                total intensity = 0.000106<jupyter_text>Look how much of the star light is blocked now! The stellar PSF is greatly reduced in the final image, and that's even with autoscaling of the display levels. In terms of total flux, only a very tiny amount of the initial PSF remains.  _(What's up with the display after the Lyot mask? The software is automatically rescaling the display min/max, so after the Lyot mask blocks the diffracted light on the secondary supports, you can see the diffuse residual much more.)_  Satellite Spots, of various types. Let's turn back on the satellite spots grid in the apodizer model. _(Just to be clear, in the real instrument you can't turn this on and off! Grids are printed permanently as part of each of the apodizers.)_<jupyter_code>gpi.satspots=True
psf_with_spots = gpi.calc_psf(wavelength=wavelen, display=True, verbose=True)<jupyter_output>after Gemini South Primary,              total intensity = 1
after GPI Apodizer H,                    total intensity = 0.45
after Before FPM,                        total intensity = 0.45
after GPI FPM H,                         total intensity = 0.00452
after Before Lyot,                       total intensity = 0.00452
after GPI Lyot 080m12_04,                total intensity = 0.0016
before GPI lenslet array,                total intensity = 0.000301<jupyter_text>In practice we see an additional set of spots which is due to the deformable mirror surface print-through.  To see this, we will need to turn on the deformable mirror part of the model. _(Disclaimer: This code has only a simplified, imperfect model of the GPI DMs. Don't use this for any work that requires high fidelity AO simulations!)_We'll turn on the DMs, then display the tweeter surface with a pretty hard stretch. There's about 15 nanometers of grid patterning due to details of the MEMS manufacturing.<jupyter_code>gpi.dms = True

plt.figure(figsize=(13,10))
gpi.tweeter.display(opd_vmax=5e-8, colorbar_orientation='vertical')<jupyter_output><empty_output><jupyter_text>Now let's do another optical calculation. Note that now there are some additional pupil planes shown for the two DMs, so let's make the default plot size a bit bigger. We do this calculation at a shorter wavelength so we can see the DM spots.<jupyter_code>pylab.rcParams['figure.figsize'] = (12.0, 11.0)  # Even bigger plot size now; adjust as desired

psf_with_spots = gpi.calc_psf(wavelength=1e-6, display=True, verbose=True)<jupyter_output>after Gemini South Primary,              total intensity = 1
after GPI Woofer,                        total intensity = 1
after GPI Tweeter,                       total intensity = 1
after GPI Apodizer H,                    total intensity = 0.45
after Before FPM,                        total intensity = 0.45
after GPI FPM H,                         total intensity = 0.0052
after Before Lyot,                       total intensity = 0.0052
after GPI Lyot 080m12_04,                total intensity = 0.00268
before GPI lenslet array,                total intensity = 0.000863<jupyter_text>That's pretty nice.   Bad actuators, and how to hide them Time to make it less nice!  Bring on the bad actuators.   Where are those?  The model includes a lookup table of the less-than-perfect actuators.<jupyter_code>gpi.tweeter.bad_actuators
plt.figure(figsize=(8,6))
gpipsfs.GeminiPrimary().display(colorbar=False)
gpi.tweeter.annotate()

# The red and dark red spots show bad (totally dead or very weak) actuators. 
# (magenta are flaky sometimes but generally OK, and 
# yellow are coupled actuator pairs but those are not as big a deal.)<jupyter_output><empty_output><jupyter_text>Let's imagine some of those dead actuators are stuck at large piston values, a few microns away from nominal. (The tweeter DM surface travel is a couple microns so this is plausible.)<jupyter_code>gpi.tweeter.set_actuator(12,7,   3000)  # actuator positions given in nanometers
gpi.tweeter.set_actuator(15,29, -3000)  # actuator positions given in nanometers


plt.figure(figsize=(8,6))
gpi.tweeter.display(colorbar_orientation='vertical')
psf_badact = gpi.calc_psf(wavelength=1e-6, display=True, verbose=True)<jupyter_output>after Gemini South Primary,              total intensity = 1
after GPI Woofer,                        total intensity = 1
after GPI Tweeter,                       total intensity = 1
after GPI Apodizer H,                    total intensity = 0.45
after Before FPM,                        total intensity = 0.45
after GPI FPM H,                         total intensity = 0.0119
after Before Lyot,                       total intensity = 0.0119
after GPI Lyot 080m12_04,                total intensity = 0.00933
before GPI lenslet array,                total intensity = 0.00509<jupyter_text>Yuuuuuck._(Fourier optics homework: Think about why the pattern of all that junky extra light that makes through the coronagraph has the particular apperance that it does.)_ Time for Lyot stop blocking tabs to the rescue!<jupyter_code>gpi.lyot_tabs = True
psf_blocked = gpi.calc_psf(wavelength=1e-6, display=True, verbose=True)<jupyter_output>after Gemini South Primary,              total intensity = 1
after GPI Woofer,                        total intensity = 1
after GPI Tweeter,                       total intensity = 1
after GPI Apodizer H,                    total intensity = 0.45
after Before FPM,                        total intensity = 0.45
after GPI FPM H,                         total intensity = 0.0119
after Before Lyot,                       total intensity = 0.0119
after GPI Lyot 080m12_04,                total intensity = 0.00302
before GPI lenslet array,                total intensity = 0.00123
{'fraction_non_alphanumeric': 0.05737463126843658, 'fraction_numerical': 0.03185840707964602, 'mean_word_length': 2.8901319563970165, 'pattern_counts': {'":': 0, '<': 49, '<?xml version=': 0, '>': 49, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 9, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '15', 'dir': 'jupyter-structured-clean-dedup', 'id': '307235', 'license': 'BSD-3-Clause', 'n_tokens_mistral': 4134, 'n_tokens_neox': 3640, 'n_words': 1608, 'path': 'docs/GPI Tutorial on Coronagraphs.ipynb', 'repo_name': 'brynickson/gpipsfs', 'size': '13560'}