PROP_v3.0x PROPER Manual V3.0c

User Manual:

Open the PDF directly: View PDF PDF.
Page Count: 194

DownloadPROP_v3.0x PROPER Manual V3.0c
Open PDF In BrowserView PDF
PROPER
An Optical Propagation Library for
IDL, Python, & Matlab

Available from proper-library.sourceforge.net
Developed by John Krist
Matlab conversion by Gary Gutt
Python conversion by Navtej Saini with Nikta Amiri and Luis
Marchen
NASA Jet Propulsion Laboratory
California Institute of Technology

Version 3.0c
May 24, 2018

Acknowledgements
The author wishes to thank the following for discussions, suggestions, nagging, etc., that helped make this code
possible: (JPL) Dwight Moody, Karl Stapelfeldt, John Trauger, Joe Green, David Palacios, Phil Dumont, Stuart
Shaklan; (STScI) Anand Sivaramakrishnan; (Space Telescope Science Institute) Russ Makidon; (LLNL) Lisa
Poyneer; Christian Marois, Luc Gilles (TMT). Thanks to Volker Tolls (Smithsonian Astrophysical Observatory) for
help with the Windows installation instructions and provided DLLs. Thanks to James Tappan, Sunip Mukherjee,
Shannon Zareh, Bryn Jeffries, AJ Riggs, and Roser Juanola for bug reports (and even suggested fixes).
Development of PROPER was funded by the NASA Terrestrial Planet Finder Coronagraph project at JPL.
Conversions to Matlab and Python were funded by the NASA WFIRST coronagraph project at JPL.
“Code V” is a trademark of Optical Research Associates, Inc. “ZEMAX” is a trademark of ZEMAX Development
Corporation. “GLAD” is a trademark of Applied Optics Research, Inc. “Interactive Data Language” (a.k.a. IDL) is
a trademark of Exelis Visual Information Solutions Inc. “Matlab” is a trademark of The Mathworks, Inc.

Legal Notices
© 2006-2018. California Institute of Technology ("Caltech"). This software, including source and object code, and any
accompanying documentation ("Software") is owned by Caltech. Caltech has designated this Software as Technology and Software
Publicly Available ("TSPA"), which means that this Software is publicly available under U.S. Export Laws. With the TSPA
designation, a user may use and distribute the Software on a royalty-free basis with the understanding that:
(1) THIS SOFTWARE AND ANY RELATED MATERIALS WERE CREATED BY THE CALIFORNIA INSTITUTE OF
TECHNOLOGY (CALTECH) UNDER A U.S. GOVERNMENT CONTRACT WITH THE NATIONAL AERONAUTICS AND
SPACE ADMINISTRATION (NASA). THE SOFTWARE IS TECHNOLOGY AND SOFTWARE PUBLICLY AVAILABLE
UNDER U.S. EXPORT LAWS AND IS PROVIDED "AS-IS" TO THE RECIPIENT WITHOUT WARRANTY OF ANY KIND,
INCLUDING ANY WARRANTIES OF PERFORMANCE OR MERCHANTABILITY OR FITNESS FOR A PARTICULAR
USE OR PURPOSE (AS SET FORTH IN UNITED STATES UCC §2312-§2313) OR FOR ANY PURPOSE WHATSOEVER,
FOR THE SOFTWARE AND RELATED MATERIALS, HOWEVER USED.
IN NO EVENT SHALL CALTECH, ITS JET PROPULSION LABORATORY, OR NASA BE LIABLE FOR ANY DAMAGES
AND/OR COSTS, INCLUDING, BUT NOT LIMITED TO, INCIDENTAL OR CONSEQUENTIAL DAMAGES OF ANY
KIND, INCLUDING ECONOMIC DAMAGE OR INJURY TO PROPERTY AND LOST PROFITS, REGARDLESS OF
WHETHER CALTECH, JPL, OR NASA BE ADVISED, HAVE REASON TO KNOW, OR, IN FACT, SHALL KNOW OF THE
POSSIBILITY.
RECIPIENT BEARS ALL RISK RELATING TO QUALITY AND PERFORMANCE OF THE SOFTWARE AND ANY
RELATED MATERIALS, AND AGREES TO INDEMNIFY CALTECH AND NASA FOR ALL THIRD-PARTY CLAIMS
RESULTING FROM THE ACTIONS OF RECIPIENT IN THE USE OF THE SOFTWARE; and
(2) Caltech is under no obligation to provide technical support for the Software; and
(3) all copies of the Software released by user must be marked with this marking language, inclusive of the copyright statement,
TSPA designation and user understandings.
This software is not ITAR controlled and is classified as EAR 99 under the DOC/DIS Export Administration regulations. Export
of this may require a license or exemption for delivery to a foreign person or entity. If such export is needed please contact the
JPL Export Compliance Office (818-393-7790). This software is controlled for Anti Terrorism, AT 1. Export or transfer of this
software or its technology to a Foreign Person or Foreign entity may require an export license or exemption issued by the
U.S. Department of Commerce prior to the export or transfer. Diversion contrary to U.S. law is prohibited. If export of this
software is required, contact the Office of Export Compliance for assistance. For many destinations, this may not require a license,
but request assistance.

2

Table of Contents
Optical Propagation ___________________________________________________________ 8
Propagation Codes ________________________________________________________________ 8
Free and Easy Propagation with PROPER ____________________________________________ 8
Propagating the PROPER Way _____________________________________________________ 8
Representing the Wavefront ________________________________________________________________9
Propagating in the Near and Far Fields ________________________________________________________ 9

Conventions Assumed by the PROPER Routines ______________________________________ 10
Comparing PROPER Results with Those from Other Programs _________________________ 10
Verification of PROPER’s algorithms _______________________________________________ 11
Conventions Used in this Manual ___________________________________________________ 11
Getting Help from within the Environment ___________________________________________ 12

Changes in Version 3.0c of PROPER ____________________________________________ 13
Changes in Version 3.0b of PROPER ____________________________________________ 13
Changes in Version 3.0a of PROPER ____________________________________________ 13
Changes in Version 3.0 of PROPER _____________________________________________ 13
Changes in Version 2.0b,c of PROPER __________________________________________ 13
Changes in Version 2.0a of PROPER ____________________________________________ 13
Changes in Version 2.0 of PROPER _____________________________________________ 13
Changes in Version 1.1 of PROPER _____________________________________________ 14
Changes in Version 1.0 of PROPER _____________________________________________ 14
Installing and Setting Up the PROPER Package ___________________________________ 16
IDL ____________________________________________________________________________ 16
Installing under UNIX/Linux/MacOS IDL ____________________________________________________ 16
Installing under MS Windows IDL __________________________________________________________ 17

Python _________________________________________________________________________ 18
Path setup _____________________________________________________________________________ 18

Matlab _________________________________________________________________________ 19
Increasing Speed using FFTW or the Intel Math Library (IDL, Python)___________________ 20
PROPER and FFTW Wisdom Optimizations __________________________________________________ 20
Installing the PROPER FFTW interface in IDL ________________________________________________ 21
Windows ______________________________________________________________________________ 21
Unix/Linux/MacOS ______________________________________________________________________ 21
Installing the PROPER Intel FFT Interface in IDL ______________________________________________ 22
Installing the PROPER FFTW interface in Python ______________________________________________ 23

PROPER Routines by Category ________________________________________________ 24
Prescription Definition and Execution Routines _______________________________________ 24

3

Wavefront Phase and Amplitude Modifying Routines __________________________________ 24
Query Functions _________________________________________________________________ 24
Shape Drawing, Aperture & Obscuration Pattern Routines _____________________________ 25
Error Map Input & Output Routines ________________________________________________ 25
Utility Routines __________________________________________________________________ 25
Detector Modeling Routines _______________________________________________________ 25
Other Routines __________________________________________________________________ 25

Defining and Running a PROPER Prescription of a System _________________________ 26
Definition Requirements __________________________________________________________ 26
IDL __________________________________________________________________________________ 26
Matlab ________________________________________________________________________________ 28
Python ________________________________________________________________________________ 30

Fundamental PROPER Routines: PROP_LENS & PROP_PROPAGATE ________________ 32
A Simple Example Prescription ____________________________________________________ 32
Running the Prescription __________________________________________________________ 34
Some Things to Note in IDL _______________________________________________________ 35
When Things Crash in IDL… ______________________________________________________________ 35
The Wavefront Array Structure_____________________________________________________________ 35
Sampling ______________________________________________________________________________ 36
Polychromatic Imaging ___________________________________________________________________ 37
PROPER Accuracy ______________________________________________________________________ 37

Running multiple instances of a prescription in parallel with PROP_RUN_MULTI ______ 38
Using PROP_RUN_MULTI _______________________________________________________ 38
Examples _______________________________________________________________________ 39
Limitations _____________________________________________________________________ 43
Running PROP_RUN_MULTI remotely (Unix IDL before v8.3) _________________________ 44

Save States _________________________________________________________________ 45
Apertures and Obscurations ___________________________________________________ 48
Overview _______________________________________________________________________ 48
Examples _______________________________________________________________________ 48

Lenses and Mirrors __________________________________________________________ 51
Aberrations _________________________________________________________________ 51
Zernike Polynomials ______________________________________________________________ 51
User-Created or Measured Error Maps ______________________________________________ 51
The Deformable Mirror ___________________________________________________________ 52
Inclination and Rotation of the Deformable Mirror _____________________________________________ 53

Phase and Amplitude Error Maps Defined by Power Spectral Densities ___________________ 55
Power Spectral Density ___________________________________________________________________ 55

4

An Example Using PSD-Defined Error Maps _________________________________________________ 57
Defining Amplitude Errors Using PSDs ______________________________________________________ 61
PSD-Defined Maps for Inclined Surfaces _____________________________________________________ 63
Limitations of PSD-Defined Error Maps _____________________________________________________ 63
Notes Regarding PROP_PSD_ERRORMAP __________________________________________________ 64

Examples __________________________________________________________________ 65
A Simple Telescope _______________________________________________________________ 65
The Hubble Space Telescope _______________________________________________________ 68
Changing the Focus (and a detour discussion on errors and sampling) ______________________________ 71

The Talbot Effect ________________________________________________________________ 74
A Simple Microscope (and Objects at Finite Distances) _________________________________ 79
A Stellar Coronagraph ____________________________________________________________ 83
A Simple Coronagraph with Selectable Occulters ______________________________________________ 84
A Simple Coronagraph with a Telescope Having Optical Surface Errors ____________________________ 91
A Simple Coronagraph: Wavefront Correction with a Deformable Mirror ___________________________ 93

PROPER Routine Reference Manual ____________________________________________ 99
PROP_8TH_ORDER_MASK _________________________________________________ 100
PROP_ADD_PHASE________________________________________________________ 103
PROP_BEGIN _____________________________________________________________ 104
PROP_CIRCULAR_OBSCURATION __________________________________________ 108
PROP_COMPILE_FFTI (IDL, Python) ________________________________________ 110
PROP_COMPILE_FFTW (IDL, Python) _______________________________________ 111
PROP_DEFINE_ENTRANCE ________________________________________________ 112
PROP_DIVIDE ____________________________________________________________ 113
PROP_DM ________________________________________________________________ 114
PROP_ELLIPSE ___________________________________________________________ 117
PROP_ELLIPTICAL_APERTURE ____________________________________________ 119
PROP_ELLIPTICAL_OBSCURATION ________________________________________ 121
PROP_END _______________________________________________________________ 123
PROP_END_SAVESTATE ___________________________________________________ 125
PROP_ERRORMAP ________________________________________________________ 126
PROP_FFTW_WISDOM (IDL, Python) ________________________________________ 129
PROP_FIT_ZERNIKES _____________________________________________________ 130
PROP_GET_AMPLITUDE___________________________________________________ 132
PROP_GET_BEAMRADIUS _________________________________________________ 133
PROP_GET_DISTANCETOFOCUS ___________________________________________ 134

5

PROP_GET_FRATIO _______________________________________________________ 135
PROP_GET_GRIDSIZE _____________________________________________________ 136
PROP_GET_NYQUISTSAMPLING ___________________________________________ 137
PROP_GET_PHASE ________________________________________________________ 138
PROP_GET_REFRADIUS ___________________________________________________ 139
PROP_GET_SAMPLING ____________________________________________________ 140
PROP_GET_SAMPLING_ARCSEC ___________________________________________ 141
PROP_GET_SAMPLING_RADIANS __________________________________________ 142
PROP_GET_WAVEFRONT __________________________________________________ 143
PROP_GET_WAVELENGTH ________________________________________________ 144
PROP_HEX_WAVEFRONT _________________________________________________ 145
PROP_INIT_SAVESTATE ___________________________________________________ 149
PROP_IRREGULAR_POLYGON _____________________________________________ 150
PROP_IS_STATESAVED ____________________________________________________ 151
PROP_LENS ______________________________________________________________ 152
PROP_MAGNIFY __________________________________________________________ 153
PROP_MULTIPLY _________________________________________________________ 155
PROP_NOLL_ZERNIKES ___________________________________________________ 156
PROP_PIXELLATE ________________________________________________________ 157
PROP_POLYGON __________________________________________________________ 158
PROP_PRINT_ZERNIKES __________________________________________________ 160
PROP_PROPAGATE _______________________________________________________ 161
PROP_PSD_ERRORMAP ___________________________________________________ 163
PROP_RADIUS ____________________________________________________________ 167
PROP_READMAP__________________________________________________________ 169
PROP_RECTANGLE _______________________________________________________ 171
PROP_RECTANGULAR_APERTURE _________________________________________ 173
PROP_RECTANGULAR_OBSCURATION _____________________________________ 175
PROP_RESAMPLEMAP ____________________________________________________ 177
PROP_ROTATE ___________________________________________________________ 178
PROP_ROUNDED_RECTANGLE ____________________________________________ 180
PROP_RUN _______________________________________________________________ 181
PROP_RUN_MULTI ________________________________________________________ 184
6

PROP_SHIFT_CENTER ____________________________________________________ 186
PROP_STATE _____________________________________________________________ 187
PROP_USE_FFTI (IDL, Python) _____________________________________________ 188
PROP_USE_FFTW _________________________________________________________ 189
PROP_WRITEMAP_________________________________________________________ 190
PROP_ZERNIKES _________________________________________________________ 192

7

Optical Propagation
Modeling the propagation of light through an optical system is usually done in one of two ways: (1) calculating the
path of individual beams though the optical components (ray tracing), or (2) calculating the changes in the
electromagnetic field as it travels (physical optics propagation, or POP). Ray tracing is typically used to design the
system and determine its basic optical properties, such as magnification, aberrations, vignetting, etc. However, it
cannot predict the effects of diffraction. Conversely, POP is completely concerned with how the electromagnetic
wavefront is diffracted as it travels, but it does not determine things like aberration changes caused by a shifted
component. Hybrid codes exist (e.g. MACOS) that combine the two methods – ray tracing to determine aberrations,
beam sizes, and the like, and POP to compute the diffraction effects. Alternative propagation algorithms also exist
that combine the two, such as the beam propagation method used in the commercial GLAD and Code V software.

Propagation Codes
A number of optical propagation codes exist. Many well-known commercial ray-tracing programs now include
physical optics propagation (POP) calculations, including Code V and Zemax. Their POP systems are in addition to
the simple far-field (Fraunhoffer) calculations they have always made. However, these packages are costly, have
steep learning curves, and are not easy to integrate into exploratory modeling systems (e.g. wavefront control
algorithm testing). Many POP programs have been developed by research institutions and companies for internal use
and are typically not available to or designed for use by the public (BeamWarrior, MACOS, etc.). Free, publiclyavailable codes are few. Among these are LightPipes, which is a set of individual C programs that can be chained
together to propagate a wavefront through a system. Another is Arroyo, a C++ library with an emphasis on
atmospheric propagation for adaptive optics modeling.

Free and Easy Propagation with PROPER
PROPER is a library of optical propagation procedures and functions for the IDL (Interactive Data Language), Python,
and Matlab environments. PROPER is intended for exploring diffraction effects in optical systems. It is a set of
wavefront propagation tools – it is not a ray tracing system and thus is not suitable for detailed design work. An
optical system is described by a series of PROPER library function and procedure calls within a user-written routine
(hereafter called the prescription). These calls may be interleaved with additional user-written code, taking advantage
of the wide range of mathematical, array processing, file input/output, and graphical routines available in the
programming environment, providing a versatile system for modeling. PROPER includes procedures to create
apertures and obscurations (circular, elliptical, rectangular, polygonal, and hexagonal arrays) and apply aberrations
(low-order Zernikes polynomials, error maps defined by power-spectrum-density profiles, deformable mirrors, and
user-defined wavefront error maps).
The PROPER routines are provided as source code, so you can see what they are actually doing, debug your (and
perhaps the PROPER) procedures, and see ways to improve things (let the author know, please!). Because it is
possible for someone to make modifications to the source code, it is important to grab the official, “clean” version
from the proper-library.sourceforge.net website.

Propagating the PROPER Way
The PROPER routines implement common Fourier transform algorithms (angular spectrum & Fresnel approximation)
to propagate a wavefront in near-field and far-field conditions. The procedures automatically determine which
algorithm to use depending on the properties of the pilot beam, an on-axis Gaussian beam that is analytically traced
through the system. This process follows the method described by Lawrence (Applied Optics and Optical
Engineering, V. 11 [1992]), which is also used by other programs (e.g. ZEMAX and GLAD). The ZEMAX manual

8

(available with the ZEMAX demo from www.zemax.com) contains a concise chapter on physical optics propagation
that describes this method as well. It is very briefly described here, without the mathematics, so that the user will be
familiar with the constraints imposed by the PROPER routines.

Representing the Wavefront
A propagating electromagnetic field is described as a wavefront with varying phase and amplitude across its surface.
A perfectly collimated, unaberrated beam has uniform phase across its wavefront as measured in a plane perpendicular
to the direction of propagation, and the phase advances with the distance traveled. In this case, the surface of constant
phase is a plane. After this beam passes through a lens or is reflected by a curved mirror, the phase is no longer
constant across the wavefront if measured in a plane. The surface of constant phase is now curved, or in other words,
the wavefront now has a radius of curvature (at least as measured near the optical axis, also called the paraxial region).
For example, a planar wavefront (collimated beam) reflected by a concave parabolic mirror will become a spherical
wavefront (converging beam) with some radius of curvature.
A wavefront’s curvature can create problems when trying to represent the phase across it using a sampled grid in the
computer. At some propagation distance the phase will change by more than one wave (2π radians) between two
adjacent samples when measured in a planar grid (Figure 1). This causes numerical aliasing of the phase that leads to
incorrect results. This problem can be reduced by the choice of propagators.

+y

} Dy
Dz

+z

Figure 1. Schematic illustration of the problem of representing a
wavefront with a curved surface of constant phase with a planar
grid. In this example, a spherical wavefront, seen in crosssection, is expanding outward along the +z direction. The
separation between sample points on the grid is Δy. Between two
particular adjacent grid points in a plane, the phase of the
wavefront changes by 2πΔz/λ. If this phase change is greater
than 2π radians, then the actual difference will be aliased to an
erroneous value in the grid. Note that the degree of phase error
depends on both the wavelength and the curvature of the
wavefront.

Propagating in the Near and Far Fields
The goal of the methods used by PROPER is to choose a propagation algorithm that best accounts for the curvature
of the wavefront to prevent phase aliasing. In an unaberrated system the wavefront is planar at the waist of the beam,
which is at the pupil when the beam is collimated or at the focus when it is converging or diverging. Within some
distance near the waist (the near field), the amount of wavefront curvature is low and the phase can be represented
relative to a plane without significant aliasing. Away from the waist (into the far field), the wavefront gains curvature.
At some point, it can no longer be sampled on a planar grid in the computer without aliasing.

9

The distance from the beam waist that defines where the near field ends and the far field begins must be chosen to
prevent aliasing. There are various methods for choosing this distance, including using the Fresnel number. However,
PROPER uses the Rayleigh distance, as described by Lawrence, which is determined using a Gaussian pilot beam that
is analytically propagated through the system. A Gaussian beam is a convenient surrogate for the actual beam (at least
in reasonably well-corrected systems) because its radius of curvature after propagation or after passing through a lens
can be easily computed, as well as the diameter and location of its beam waist. These define the Rayleigh distance
from the beam waist that specifies the boundary of the near and far fields, and thus which reference surface type
(planar or spherical) is best used to minimize aliasing. Within the Rayleigh distance of the waist, the wavefront can
usually be fit best with a planar reference surface, while outside it is best fit with a curved surface. The radius of a
reference sphere is equal to the distance from the current position to the beam waist.
When propagating from one location to another within the near field, PROPER utilizes the angular spectrum algorithm
(plane-to-plane, PTP, transform). To propagate from the beam waist in the near field to a location in the far, or viceversa, the Fresnel method is used (spherical-to-waist (STW) or waist-to-spherical (WTS) wavefront transforms).
Propagation between two points in the far field is done by first propagating to the beam waist (STW) and then to the
new location (WTS). The scale between sample points in the wavefront grid remains constant when PTP is used. It
changes proportionally with the distance propagated when STW or WTS is used, in order to maintain nearly-constant
sampling of the beam. Thus, the scale will vary considerably during propagation though a multi-element system.

Conventions Assumed by the PROPER Routines
The PROPER routines propagate an electric field through an unfolded system in which all components lie on a straight
line. A positive distance indicates that the field is being propagated forward through the system. A curved mirror
will alter the phase distribution of the field but will not change the direction of the beam. The field phase also does
not change sign due to reflection by a mirror, nor does the coordinate system. A positive value in an aberration map
indicates advancement of the phase at that location relative to the default phase (i.e. the aberration map is added to the
wavefront).
The PROPER coordinate system assumes that the first element of the wavefront array is in the lower left corner when
the array is viewed. +X is towards the right and +Y is up. The wavefront origin is at the center of the array. Zernike
aberrations are defined in azimuth from the +X axis to the +Y.
PROPER by default does not add a phase offset to the wavefront as it is propagated. Thus, the wavefront maintains a
zero-phase reference value. This can be altered by specifying the PHASE_OFFSET switch in the call to PROP_RUN
or PROP_RUN_MULTI. This may be important when modeling the separate arms of an interferometer with a path
difference between the two.

Comparing PROPER Results with Those from Other Programs
The conventions used by PROPER may differ from those of other programs that do diffraction calculations, such as
Zemax, Code V, GLAD, and others. So far, PROPER results have been compared to those only from Zemax. Some
differences between the two are described here.
In Zemax (and most other optical design programs) an optical system is described by transmitting or reflecting surfaces
that may be tilted or offset from the optical axis, forming a three-dimensional layout in which the beam can go back
and forth through space (i.e. a reflection changes the direction of the beam). The PROPER routines instead assume
an unfolded, linear layout, and a curved mirror does not change the direction of the beam but only its phase distribution.
Zemax utilizes ray tracing to compute the aberrations at each surface, be it a mirror or lens, including the effects of
various surface shapes (conic, aspheric, anamorphic, etc.). PROPER routines assume that a lens introduces a purely
radially-quadratic phase change. In Zemax, the phase of a wavefront changes sign after a reflection, while in PROPER
the sign remains the same. Zemax uses ray tracing to compute the best-fit radius for the reference surface, while

10

PROPER simply assumes that it is equal to the distance from the current position of the wavefront in the system to
the beam waist. Zemax draws binary obscurations (either 1 or 0), while PROPER draws obscurations with antialiased
edges. In Zemax (and possibly other programs), the wavefront propagated using physical optical propagation methods
includes a phase offset equal to the distance propagated (and is possibly phase wrapped). For instance, if the wavefront
is propagated by ½ the wavelength, then the phase term at all points will include a π offset. PROPER (v1.1 and later)
does not include this offset and maintains a zero phase wavefront offset (unless the PHASE_OFFSET switch is set in
PROP_RUN).

Verification of PROPER’s algorithms
As part of a NASA study on modeling coronagraphs, the accuracy of PROPER was compared for test cases against
more mathematically rigorous computational methods, such as Rayleigh-Sommerfeld diffraction. The results show
excellent agreement. A copy of the report is available at:
https://exoplanets.nasa.gov/exep/files/exep/krist_TDEM_milestone1_report_revised.pdf

Conventions Used in this Manual
IDL specific information is noted in blue, Python in red, and Matlab in green.
When the calling syntax of a PROPER routine is shown, optional parameters are enclosed in square brackets:
routine_name, required_parameter [, optional_parameter]
Routines may also have optional keywords and/or switches. A keyword is assigned a value; for example:
[, OPTIONAL_KEYWORD=value]
In IDL a switch is set like so:
[, /OPTIONAL_SWITCH]
which is equivalent to:
[, OPTIONAL_SWITCH=1]
or in Python
[, OPTIONAL_SWITCH=True]
In Matlab, switches are indicated by the name without any associated value. For example, to create an amplitude
(rather than phase) error using Zernike polynomials normalized over a 0.05 meter radius, one might do this:
wf = prop_zernikes( wf, znum, zval, 'RADIUS', 0.05, 'AMPLITUDE' );
In this example, AMPLITUDE is a switch and RADIUS is an assigned-value keyword.
Be sure not to confuse optional parameters which are in italic square brackets with non-italic brackets that are part of
the Matlab syntax for a vector of values.

11

Keywords and switches may be specified in any order in IDL and Python, but other parameters must be in the order
shown in the calling syntax.
In Matlab all keywords and switches must come after the positionally-dependent parameters, and case is unimportant.
Python PROPER routines may return multiple parameters that can be accessed using a tuple. In such cases these are
denoted using parentheses, which should not be confused with square brackets that indicate optional values:
( wavefront, sampling ) = proper.prop_run( … )
Matlab routines may return multiple parameters in brackets. For example, one must always accept the wavefront
structure return value from prop_zernikes:
wf = prop_zernikes( wf, znum, zval );
but the routine also returns the wavefront map that is the sum of the Zernikes, if the user wishes to use it:
[ wf, zmap ] = prop_zernikes( wf, znum, zval );

Getting Help from within the Environment
The calling sequence of a PROPER routine can be obtained within the execution environment
using:

12

IDL:

doc_library, 'prop_circular_aperture'

Python:

help(prop_circular_aperture)

Matlab:

help prop_circular_aperture

Changes in Version 3.0c of PROPER
Bugs in the Python PROPER interpolation routines (PROP_CUBIC_CONV, PROP_MAGNIFY) that caused
significant errors were fixed, along with a PROP_HEX_ZERNIKE bug (used by PROP_HEX_WAVEFRONT) that
caused assignment of aberrations to the wrong segments. Thanks to Roser Parramon (GSFC) for reporting those errors.
The output of the Python version of PROP_RUN_MULTI was inconsistent with that of PROP_RUN when the
NOABS flag was set and the output was not complex, so this was fixed (thanks to Bryn Jeffries for reporting this and
suggesting the fix). When using the Python version with PROP_RUN_MULTI and FFTW, a conflict would arise with
multiple processes trying to write to the same FFTW wisdom file. This has been fixed (thanks to Bryn Jeffries for
pointing this out). The Matlab version of PROP_MAGNIFY was fixed to prevent crashing when the output array size
was not specified (thanks to A.J. Riggs at JPL for reporting this). The manual entry for PROP_HEX_WAVEFRONT
was fixed to show the correct array ordering in Matlab for the Zernikes (that is, [segment_number, zernike_number]).

Changes in Version 3.0b of PROPER
Bugs in the Python version were fixed (IDL to Python conversion errors). PROP_DM was causing a memory
overload due to poor formatting of arrays. PROP_MAGNIFY, PROP_CUBIC_CONV, and
PROP_RESAMPLE_MAP were fixed to match corrected cubic convolution interpolation parameters, along
with cubic_conv*.c codes.

Changes in Version 3.0a of PROPER
Bugs in the Matlab version of the examples were fixed, along with formatting. The manual entry for
PROP_HEX_WAVEFRONT was modified to include a description of the segment numbering scheme. Legal
notices were added to the codes.

Changes in Version 3.0 of PROPER
This is the introduction of the Python and Matlab versions of PROPER. PROP_FIT_ZERNIKES was changed to
use least squares rather than iterative fitting, and a bug fixed that returned the fitted wavefront at the sampling of the
shrunken wavefront rather than the original wavefront. MAX_FREQUENCY option was added to
PROP_PSD_ERRORMAP to limit the spatial frequencies used in creating an aberration map. Fix to
prop_rectangle to prevent crashing if the rectangle is beyond the array dimensions.

Changes in Version 2.0b,c of PROPER
The orthographic projection of a tilted and/or rotated deformable mirror can now be included using PROP_DM and
with the XTILT, YTILT, and ZTILT keywords. Version 2.0c fixes a bug in PROP_DM that caused
N_ACT_ACROSS_PUPIL to be ignored (thanks to Gary Gutt for finding this).

Changes in Version 2.0a of PROPER
IDL v8.4 introduced a new function called “lambda” that caused problems with the variable of the same name in
PROP_RUN. This has been fixed.

Changes in Version 2.0 of PROPER
A new routine, PROP_RUN_MULTI, enables running multiple instances of a prescription in parallel.

13

A number of routines have been optimized for improved speed.
The FFTW interface has changed. Local optimization of a FFTW plan (i.e., wisdom) is not done automatically. If a
local plan has not been generated using PROP_FFTW_WISDOM, then the default FFTW estimated plan will be used.
The threaded FFTW routines are used by default. The FFTW interface now supports the Intel Math Kernel Library
FFT.
The PROP_MAGNIFY routine now handles complex values and a /QUICK switch has been added that provides
faster results using IDL’s built-in cubic convolution method.
A number of bug fixes have been made (thanks to Lisa Poyneer and Christian Marois for pointing them out).

Changes in Version 1.1 of PROPER
The default behavior has changed regarding the wavefront phase offset that occurs during propagation. Prior to v1.1,
a constant phase offset would be applied to the entire wavefront every time it was propagated some distance. For
instance, if a wavefront was propagated over a distance equal to ½ the wavelength, then the phase at each point in the
wavefront would be increased by a value of π radians. Because of phase wrapping, this offset would range over ±π.
This offset has caused confusion, especially since it was not documented. In most cases, it appears that not including
this offset would be preferable, as it keeps the reference level of the wavefront at zero phase. Exceptions may include
modeling systems in which the difference in length between two separate paths is important, like the arms of an
interferometer. In versions 1.1 and later, the default action will be to NOT add the phase offset. In those situations
where it is desired, the /PHASE_OFFSET switch can be specified in the call to PROP_RUN.
Up to 22 Zernike polynomials are now available in PROP_HEX_WAVEFRONT, compared to the previous 11.
Bug fixes were made.

Changes in Version 1.0 of PROPER
The CONIC and ASPHERIC lens options were removed. They produced unpredictable results, likely caused by the
creation of a wavefront whose phase cannot be well fit with a sphere. They may be reintroduced at a later date.
The PROP_HEX_APERTURE function was replaced with PROP_HEX_WAVEFRONT, which adds the capability
to include aberrated segments.
PROP_FIT_ZERNIKES now allows fitting an arbitrary number of unobscured Zernike polynomials.
PROP_ZERNIKES can now multiply the wavefront by an amplitude error map comprising Zernike polynomials.
Changed the default action in PROP_DM from smoothing to no smoothing, and changed the parameter switch from
/NO_SMOOTH to /SMOOTH. The previous default was not sufficiently reliable.
For those routines for which the amplitude error level was specified using the AMPLITUDE=value keyword, the
meaning of value has changed. Previously, it specified the mean amplitude level. Now, it specifies the maximum
amplitude level.
The /NO_APPLY switch was added to some routines; this tells the routine to do whatever it does but not to modify
the wavefront. This is useful for those instances when one wants the error map created by some routine without
messing with the wavefront, for instance. For some routines, the /NOADD was replaced with /NO_APPLY.

14

Users (especially under Windows) do not have to compile the external C routines to use the PROPER library. A
version of the damped sinc interpolator is now provided written purely in IDL, and it will be used if the C version is
not present. The C version, however, is about three times faster, though this is probably not an issue with most users.

15

Installing and Setting Up the PROPER Package
IDL
Two IDL libraries need to be installed, PROPER and the IDL Astronomy User’s Library, which provides the routines
for FITS file input and output. The Astronomy Library can be downloaded from idlastro.gsfc.nasa.gov. Both libraries
should be installed in directories of their own. If the user already has a directory dedicated to other IDL routine
libraries, then they can be in subdirectories there. It is recommended that PROPER be installed separately by each
user and not be in a system directory.
Except for one or two PROPER routines written in C that need to be compiled during installation, all of the procedures
and functions in the PROPER and Astronomy User’s libraries are written in IDL and distributed as source code. It is
assumed that the user already has IDL installed and functioning properly.

Installing under UNIX/Linux/MacOS IDL
NOTE: The following instructions assume IDL version 6 or later has been installed. Earlier versions may require
changes to the way the C routines are compiled and how the IDL_PATH environment variable is defined.
Go to step 2 if the Astronomy User’s Library is already installed.
(1) Install the IDL Astronomy User’s Library. Create a directory (or subdirectory in the user’s IDL library directory)
called astrolib and download the library into there. The library is distributed as a gzipped compressed tar file in one
of two forms: all routines in one directory (astron.tar.gz) or routines in individual directories sorted by category
(astron.dir.tar.gz) – it makes no difference in execution which is chosen. Uncompress and then untar the file:
gunzip astron.dir.tar.gz
tar xvf astron.dir.tar

(2) Create a directory (or subdirectory in the user’s IDL library directory) for the PROPER library and download it
into there. Uncompress and untar the file:
gunzip proper.tar.gz
tar xvf proper.tar

There is one file written in C that needs to be compiled on the user’s machine. This contains code for damped sinc
interpolation that cannot be efficiently performed in IDL. To compile it, enter the PROPER library directory, start up
IDL, and issue the following commands:
.run prop_compile_c
prop_compile_c

This should compile the code on the most common UNIX and Linux platforms. If it does not, the user should consult
the MAKE_DLL procedure description in the IDL Reference Manual and make the necessary changes described
there to the code in prop_compile_c.pro. A version of this routine written in IDL is also provided and will be
used if the user is unable to successfully compile the C one, but the IDL version is slower.
(3) Following the form of the examples below (replacing /directory/idl_libs with the appropriate path), add the
directories containing the Astronomy Users’ and PROPER libraries to the IDL search path and define the
PROPER_LIB_DIR environment variable. If both libraries are in subdirectories of the same directory, then that top
level directory can be specified with a “+” preceding it to instruct IDL to search any of its subdirectories. If not, then
each directory needs to be specified, separated by colons.

16

If using the C shell, add to the .cshrc file:
setenv IDL_PATH ”+/directory/idl_libs:”
setenv PROPER_LIB_DIR ”/directory/idl_libs/proper”

If using the Bourne shell, add to the .bash_profile file:
IDL_PATH=”+/directory/idl_libs:”
PROPER_LIB_DIR=/directory/idl_libs/proper
export IDL_PATH PROPER_LIB_DIR

The user’s environment needs to be reinitialized for these settings to take effect. The cleanest way is to log out and
log back in again.
In IDL v6.2 and later you can set the IDL library path permanently from within IDL using the PREF_SET routine,
rather than specifying it in your startup file:
pref_set, ’IDL_PATH’, ’+/directory/idl_libs:’, /COMMIT
If you use this, you still need to define the PROP_LIB_DIR path in your .cshrc or .bash_profile file.

Installing under MS Windows IDL
NOTE: The author does not have access to a Windows machine running IDL, so everything here may be wrong or out
of date!
The installation process under Windows is not as clean as it is on Unix-based systems. Currently, support for calling
the external C routines provided with the PROPER library is not provided on Windows-based systems. There are two
such routines: an interface to the FFTW or Intel math libraries to provide faster Fourier transforms than IDL’s and a
routine that does damped sinc interpolation. In the first case, PROPER will use the much slower IDL Fourier transform
on Windows systems. In the second, a damped sinc interpolator routine written in IDL will be used that is slower than
the C-based one. Functionality is not lost under Windows – execution is simply slower than it would be on a system
using the fast FFTs and the C interpolator. NOTE: The author does not have access to IDL on a Windows machine,
and thus cannot provide any support for such.
NOTE: The following instructions assume IDL version 6 or later has been installed. Earlier versions may require
changes to the way the C routines are compiled and how the IDL_PATH environment variable is defined.
(1) Install the IDL Astronomy User’s Library. Create a subdirectory in the user’s IDL library directory. The library
is distributed as a zip-compressed archive file, astron.zip. Using a zip uncompressor (or Window’s built-in
uncompressor), unzip the file’s contents into the astrolib directory.
(2) Create a subdirectory for the PROPER library and download it into there. The library is distributed as a zipcompressed archive file. Using a zip uncompressor (or Window’s built-in uncompressor), unzip the file’s contents
into the PROPER directory.
(3) Set the environment variables to point to the library directory. Go to Start->Control Panel and double-click on
System. The Systems Properties window will pop up. Select Advanced System Settings and then the Advanced
tab and click on the Environment variables button. Under System Variables, click on New to add a new variable.
In the new window, in the Variable name box enter PROPER_LIB_DIR and in the Variable value box enter the
full path to the library directory (e.g. C:\RSI\user contributed\proper or whatever your equivalent directory is).
Press Ok to dismiss the window, then press Ok in the Environment Variables window, and kill the Control Panel
window. If you are running IDL, save all your files and restart IDL so that the next instance of IDL can incorporate
the new information.

17

Python
There are separate distributions of Python PROPER, one for Python v2.7 and another for Python v3.x. Python
PROPER requires the following external packages: numpy (≥1.8), pyfits (≥3.0), and scipy (≥0.14). Also, if you want
to run some of the demos that display images to the screen, you will need matplotlib as well. Consult your software
distribution’s documents about obtaining these packages.
Create a directory (or subdirectory in the user’s directory) for the PROPER library and download it into there. Choose
the file appropriate for your Python version. Uncompress and untar the file, or in Windows extract the files from the
corresponding zip archive:

Python 2.7

Python 3.x

gunzip proper_v3.0_python2.7.tar.gz
tar xvf proper_v3.0_python2.7.tar

gunzip proper_v3.0_python.tar.gz
tar xvf proper_v3.0_python.tar

Path setup
Option 1
This is the preferred method, as it also compiles some additional C codes that otherwise would not be compiled with
the 2nd option.
To set up PROPER execute the following command in the terminal window while in the PROPER library directory
(you may need to specify python3 to use v3.x if your OS defaults to v2.7 and you are using the v3.x distribution):
python setup.py install
This will install Python PROPER in the user’s site packages directory. These are:
Linux & MacOS X (non-framework)
MacOS X (framework)
Windows

~/.local
~/Library/Python/X.Y
%APPDATA%\Python

The user, rather than system, location used because compiled shared libraries and some temporary files are written to
the PROPER directory.

Option 2
Alternatively, PROPER can be used by running the following commands within Python:
import sys
sys.path.insert(0, '/path/to/PROPER')
prop_compile_c()
Then, in your program import PROPER like so:
import proper

18

Matlab
Installing PROPER under Matlab is fairly straightforward. The PROPER library directory can be added to the search
path using the Set Path option under the Environment tab in Matlab’s integrated development environment. Once this
is done the PROPER routines can be used in any Matlab program.
There is one file written in C that needs to be compiled on the user’s machine. This contains code for damped sinc
interpolation that cannot be efficiently performed in Matlab. To compile it, enter the PROPER library directory, start
up Matlab, and issue the following command:
mex prop_szoom_c.c

This should compile the code on the most common platforms, as long as you have a C compiler installed.
Matlab array indices start with 1, while in IDL and Python they start at 0.

19

Increasing Speed using FFTW or the Intel Math Library (IDL, Python)
The PROPER routines rely heavily on the Fast Fourier Transform to propagate the wavefront. When repeatedly
running through a prescription (modeling iterative wavefront control with a deformable mirror, for instance), much or
most of the time is spent doing FFTs, especially when large arrays are used. The default FFT algorithms in IDL and
Numpy are not very fast compared to some others, notably FFTW and the Intel Math Kernel Library (MKL). If the
user will be making heavy use of PROPER, especially on large arrays, it is strongly advised that one of these packages
be used. Neither is distributed with PROPER and must be downloaded separately. Matlab uses a version of FFTW
that is fast, so there are no additional PROPER interfaces provided for other FFTs for it. Note that activating the
FFTW or Intel FFTs will only affect PROPER routines.
FFTW (Fastest Fourier Transform in the West) is a free library containing efficient and optimized FFT functions. It
is used by PROPER if the PROP_USE_FFTW routine has been called (which permanently sets PROPER to use
FFTW, even over different sessions). In IDL the FFTW library is called via some interface C code and the
CALL_EXTERNAL function. Note that FFTW can be optimized further in IDL for a given machine by using
“wisdom” (see the FFTW section below for more details). In Python the pyFFTW package is used. Calling
PROP_USE_FFTW with the DISABLE switch will revert back to the default FFT (IDL’s or Numpy’s).
Even faster than FFTW is the FFT in Intel’s MKL. While it used to be available only for purchase, there is now a free
version of MKL available from software.intel.com. It will be used if the PROP_USE_FFTI has been called to enable
it. In IDL the Intel math library is called via some interface C code and the CALL_EXTERNAL function. Python
PROPER uses the ctypes package to call the library. Calling PROP_USE_FFTI with the DISABLE switch will revert
back to the default FFT (IDL’s or Numpy’s).
As a demonstration of the improvement provided by the FFTW and MKL over IDL’s built-in function, the FFTs of
double-precision, complex arrays of different dimensions were computed with each on a dual Xeon (E5-2680, 2.7
GHZ, 8 cores/16 threads per CPU) workstation with 96 GB of RAM. Shown below are the elapsed time in seconds
per array (based on 10 FFTs). Note that the speed of FFTW improves considerably for larger arrays when the plan
(wisdom) is pre-computed. (Note: the no-wisdom FFTW actually is faster for 8192 arrays than for 4096 ones).
Dimension
1024 x 1024
2048 x 2048
4096 x 4096
8192 x 8192

IDL
FFT
0.095 s
0.804 s
2.820 s
9.956 s

FFTW
(no wisdom)
0.028 s
0.115 s
0.592 s
0.365 s

FFTW
(wisdom)
0.018 s
0.028 s
0.079 s
0.274 s

Intel MKL
FFT
0.002 s
0.012 s
0.046 s
0.183 s

PROPER and FFTW Wisdom Optimizations
NOTICE: The appropriate optimization (“wisdom”) used by FFTW for a given system can
change if a system library or the FFTW library is updated. The wisdom file produced with
PROP_FFTW_WISDOM using the previous libraries may not be appropriate for the new
environment and could potentially cause significant degradations in speed or accuracy. A new
wisdom file needs to be generated for each grid size after such updates.
The FFTW routines do not automatically know the most efficient way to compute an FFT for a given situation (array
size, threads, processor type, etc.). To do so, they first compute a few FFTs to decide which algorithm to use, creating
a plan or, as it is sometimes described, “gathering wisdom”. Once wisdom is attained for a particular situation, calls
to the FFT routines will immediately result in the optimal method being used as long as the program is running. Once
the program stops running, this wisdom will be lost unless it is saved. Unsaved wisdom would result in the overhead
of determining the optimal method each time the program executes. NOTE: It can take many minutes to compute

20

the wisdom for each grid size for the first time. On the author’s reasonably fast machine it took 22 minutes for
2K x 2K and 71 minutes for 4K x 4K.
Once the FFTW interface has been installed, wisdom for a given array size can be obtained using the PROPER routine
PROP_FFTW_WISDOM. The wisdom is saved to a file with a rootname defined by the
PROPER_FFTW_WISDOM_FILE environment variable. Separate wisdom must be obtained for each grid size, which
will generate a separate wisdom file. The only parameter to the routine is the grid size (one dimension of it); for
example, to generate wisdom for a 2048 × 2048 grid:
IDL:

prop_fftw_wisdom, 2048

Python:

proper.prop_fftw_wisdom( 2048 )

The wisdom files are only valid for a particular system. When a new computer is used, a new wisdom file should be
generated. Likewise, if new libraries are installed, it may also be necessary to recompile the FFTW package and delete
the previous wisdom files.

Installing the PROPER FFTW interface in IDL
Windows
The FFTW interface to IDL is not supported under Windows in this version of PROPER (the author does not have
IDL on a Windows machine and so cannot test it).

Unix/Linux/MacOS
The FFTW library can be downloaded from www.fftw.org. The PROPER interface requires FFTW Version 3 or later.
Instructions are provided in the FFTW documentation regarding the library compilation and installation procedure,
which consists of running a configuration script, making the library, and then installing it. You may want to install
and link to a version of FFTW in your own personal directory rather than replacing the system’s. How to do so is
beyond the scope of this manual, but if you do this, be sure to modify PROP_COMPILE_FFTW.PRO in the PROPER
library directory and change the link paths for the FFTW libraries.
When running the FFTW configure script, certain options should be enabled in order to work with PROPER and
to optimize the speed of the FFT. By default, FFTW is compiled for double precision, which is what PROPER expects;
PROPER will not work with a single-precision FFTW library. Shared libraries need to be created using the “-enable-shared” option (by default, shared libraries are not created). If the code will be running on a
multiprocessor computer, then “--enable-threads” needs to be specified as well. If the code will be running on
processors that support SSE2 extensions, then “--enable-sse2” should also be included (plain SSE extensions
will not help because they do not support double precision reals). An example is:
./configure --enable-threads --enable-shared --enable-sse2
The FFTW library functions are called by a few C routines provided in the PROPER distribution and which are
accessed using the PROP_FFTW procedure (which only accesses the FFTW complex-to-complex transforms).
These C routines must be compiled prior to use using the PROP_COMPILE_FFTW command (note that this only
compiles the interface routines, not the FFTW library itself).
On the IDL command line, simply type (while in the PROPER library directory)
prop_compile_fftw

21

Next, the PROPER_FFTW_WISDOM_FILE environment variable needs to be defined. This specifies the name of a
file (including the full directory path) that will be created by the FFTW library to contain the “wisdom” accumulated
for optimizing the FFT for a particular system (see the next section for more on FFTW wisdom). The directory that
will contain this file must be writable by the user.
If using the C shell, add to the .cshrc file:
setenv PROPER_FFTW_WISDOM_FILE ”/directory/filename”

If using the Bourne shell, add to the .bash_profile file:
PROPER_FFTW_WISDOM_FILE=/directory/filename
export PROPER_FFTW_WISDOM_FILE

The user’s environment needs to be reinitialized for these settings to take effect. The cleanest way is to log out and
log back in again.
The PROPER routines also need to be told to use the FFTW library FFTs rather than IDL’s. This is done by calling
PROP_USE_FFTW in IDL:
prop_use_fftw
which will create a tiny file in the directory pointed to by PROPER_LIB_DIR (the user must have write permission
in that directory). Whenever PROP_RUN is called, it checks for that file to determine which FFT to use. This action
is permanent until it is disabled:
prop_use_fftw, /disable

Installing the PROPER Intel FFT Interface in IDL
The Intel MKL FFT library functions are accessed via C routines using the PROP_FFTI procedure (which only
accesses the FFT complex-to-complex transforms). These routines must be compiled prior to use using the
PROP_COMPILE_FFTI command. With the MKL installed, on the IDL command line simply type (while in the
PROPER library directory)
prop_compile_ffti
Once these routines have been successfully compiled, PROPER needs to be instructed to use them. To do so, on the
IDL command line type:
prop_use_ffti
To disable using them, use:
prop_use_ffti, /disable
NOTE: If both the FFTW and MKL FFT routines are enabled, then the MKL routine will be used.

22

Installing the PROPER FFTW interface in Python
pyFFTW is a wrapper for FFTW in Python. More information about this package and installation instruction can be
obtained from https://www.github.com/pyFFTW/pyFFTW. PROP_USE_FFTW enables pyFFTW in PROPER:
proper.prop_use_fftw()

Once FFTW is enabled, the wisdom file for the appropriate array size can be created using PROP_FFTW_WISDOM.
For example, to generate wisdom file for a 2048 × 2048 grid:
proper.prop_fftw_wisdom(2048)

Note that the wisdom files are only valid for a particular system. When a new computer is used, a new wisdom file
should be generated. The FFTW interface first checks whether the wisdom file for a specific gridsize exists in the path
(e.g. for an array size of 512 it checks if ".512pix_wisdomfile" exists in the PROPER library). If the wisdom file
exists, it is imported.

Installing the PROPER Intel MKL FFT interface in Python
Python PROPER uses the ctypes package to import the Intel library functions. PROP_USE_FFTI enables the MKL
FFT libraries in PROPER. The libraries loaded are:
Linux:
MacOS:
Windows:

ctypes.cdll.LoadLibrary('libmkl_rt.so')
ctypes.cdll.LoadLibrary("libmkl_rt.dylib")
ctypes.cdll.LoadLibrary("mk2_rt.dll")

23

PROPER Routines by Category
Note: Some “behind-the-scenes” routines are not listed here

Prescription Definition and Execution Routines
prop_begin
prop_define_entrance
prop_end
prop_end_savestate
prop_init_savestate
prop_is_statesaved
prop_run
prop_run_multi
prop_state

Define initial beam properties and create wavefront array
Define entrance pupil and renormalize wavefront to unit intensity
Terminate propagation sequence
Terminate saving state information and clean up state files
Initialize state saving system
Check if state applicable to current run exists
Execute a prescription
Execute multiple instances of a prescription in parallel
Read in saved state if it exists, else save current state

Wavefront Phase and Amplitude Modifying Routines
The primary intended use of the PROPER package is simulating the sensing and control of wavefront errors. Thus,
there are a number of routines that involve modifying the phase and amplitude of the wavefront (aperture and
obscuration mask routines are listed in another category):
prop_add_phase
prop_divide
prop_dm
prop_errormap
prop_hex_wavefront
prop_lens
prop_multiply
prop_propagate
prop_psd_errormap
prop_zernikes

Add a phase error map to the current wavefront
Divide the wavefront amplitude by a value or 2D array
Modify the wavefront using a deformable mirror
Read in an error map from a file and apply it to the current wavefront
Create hexagonal array of aberrated hexagonal segments
Alter wavefront curvature due to a lens or mirror
Multiply the wavefront amplitude by a value or 2D array
Propagate the wavefront a specified distance
Create an error map defined by a power spectral density profile
Add phase aberrations defined by Zernike polynomials

Query Functions
A number of functions are available for determining the current characteristics of the wavefront being propagated and
the current propagation state. Note that the results from some of these routines are only valid at the focus of an
unaberrated system, as indicated in their function descriptions in the PROPER Routines Reference Section.
prop_get_amplitude
prop_get_beamradius
prop_get_distancetofocus
prop_get_fratio
prop_get_gridsize
prop_get_nyquistsampling
prop_get_phase
prop_get_refradius
prop_get_sampling
prop_get_sampling_arcsec
prop_get_sampling_radians
prop_get_wavefront
prop_get_wavelength
prop_is_statesaved

24

Return the amplitude portion of the current wavefront
Get the current radius of the pilot beam
Get the distance from the current location to the focus (beam waist)
Get the current focal ratio of the pilot beam
Get the size of the wavefront array grid
Get the Nyquist sampling criterion for the current wavefront
Return the phase portion of the current wavefront
Get the current reference surface radius
Get the sampling of the wavefront in meters
Get the sampling of the wavefront in arcseconds
Get the sampling of the wavefront in radians
Return the complex-valued wavefront array
Get the wavelength of the propagation in meters
Check if state applicable to current run is saved

Shape Drawing, Aperture & Obscuration Pattern Routines
Routines are provided that return an image containing a filled shape or multiply the wavefront by a mask with a certain
shape. The edges of the shapes are antialiased (the value of a pixel along the edge of a shape is proportional to the
area of the pixel covered by that shape, ranging from 0.0 to 1.0).
prop_circular_aperture
prop_circular_obscuration
prop_ellipse
prop_elliptical_aperture
prop_elliptical_obscuration
prop_hex_wavefront
prop_irregular_polygon
prop_polygon
prop_rectangle
prop_rectangular_aperture
prop_rectangular_obscuration
prop_rounded_rectangle

Multiply the wavefront by a circular aperture (dark outside)
Multiply the wavefront by a circular obscuration (dark inside)
Return an image containing a filled ellipse
Multiply the wavefront by an elliptical aperture (dark outside)
Multiply the wavefront by an elliptical obscuration (dark inside)
Create hexagonal array of aberrated hexagonal segments
Return an image containing a filled convex irregular polygon
Return an image containing a filled polygon
Return an image containing a filled rectangle
Multiply the wavefront by a rectangular aperture (dark outside)
Multiply the wavefront by a rectangular obscuration (dark inside)
Return an image containing a rounded rectangle aperture mask

Error Map Input & Output Routines
IDL has a wide array of file input/output routines, and the Astronomy User’s Library has a number of procedures for
reading and writing images in commonly used scientific formats (e.g. FITS). The PROPER routines listed here are
expressly intended for reading and writing wavefront error maps.
prop_errormap
prop_readmap
prop_writemap

Read an error map from a FITS file and apply it to the wavefront
Read an error map from a FITS file and return it as an image
Write out a phase or amplitude error map to a FITS file

Utility Routines
prop_compile_ffti
prop_compile_fftw
prop_fftw_wisdom
prop_fit_zernikes
prop_magnify
prop_noll_zernikes
prop_print_zernikes
prop_radius
prop_resamplemap
prop_rotate
prop_shift_center
prop_use_ffti
prop_use_fftw

Compile the IDL interface to the Intel MKL library
Compile the IDL interface to the FFTW library
Generate an FFTW wisdom file for a given grid size
Fit Zernike polynomials to an error map
Resize an image using damped sinc interpolation
Generate a table of Noll-ordered Zernike polynomials
Print a table of Noll-ordered Zernike polynomials
Return array of distances of wavefront array elements from optical axis
Resample an error map using cubic convolution interpolation
Rotate and/or shift an image via interpolation
Shift the center of an array to the lower left of the array
Enables/disables use of Intel MKL FFT routines
Enables/disables use of FFTW routines

Detector Modeling Routines
prop_pixellate

Integrate a sampled image onto square pixels

Other Routines
prop_8th_order_mask

Multiply the wavefront by an 8th-order occulting mask

25

Defining and Running a PROPER Prescription of a System
Definition Requirements
The convention used in this manual is that a prescription is a user-written procedure that contains a series of calls to
PROPER library routines that describe propagation through an optical system. The prescription must follow all of the
rules of the language for a routine definition (e.g. in IDL it must begin with a “pro routine_name” declaration
and conclude with an end statement, comments begin with a semicolon, etc.). The routine can also take advantage of
all of the facilities that the language provides, like math, file input/output, and graphics functions. The prescription
itself is not intended to be called directly by the user, but rather only by PROP_RUN or PROP_RUN_MULTI.
The fundamental data set that the PROPER routines modify is the complex-valued wavefront array that is created and
initialized by calling PROP_BEGIN. As described in a following section, this array is contained within a structure
that includes information regarding the state of the wavefront. The wavefront array should only be accessed through
PROPER routines; direct modification of it by the user could result in unexpected errors, and the structure members
may change names or purpose between different versions of the software.
At a minimum, the prescription must include the following procedure calls (see the PROPER Routines Reference
section for more details on each routine):
PROP_BEGIN
Initiates the PROPER routines. The user passes the wavelength at which to propagate, the
initial diameter of the beam in meters, the size of the computational grid (n, an integer that
is preferably a power of two, so that the wavefront array has dimensions of n by n
elements), and the ratio of the initial beam diameter to the grid diameter (see the section
on sampling to see how this affects the propagation results). It then creates a wavefront
array with all elements set to 1.0 (uniform amplitude, no phase error).
PROP_DEFINE_ENTRANCE
Establishes that the current wavefront array represents the wavefront at the entrance
aperture. This is typically called immediately after the blank wavefront initialized by
PROP_BEGIN is multiplied by the entrance aperture pattern. The wavefront array is
renormalized to have a total intensity of 1.0.
PROP_END
Concludes the propagation sequence by computing the intensity of the wavefront array (or
returning the complex amplitude if the NOABS switch is set); unless the amplitude of the
wavefront was reduced during propagation (via additional apertures or modification of the
amplitude), the total intensity should be 1.0. PROP_END also returns the sampling of the
result in meters, which must be passed back to PROP_RUN as described later.

IDL
Prescription routine declaration
All IDL prescription routines must have the same parameter declaration sequence (no more nor fewer parameters are
allowed):

26

pro routine_name, wavefront, wavelength, gridsize, sampling, PASSVALUE=variable

Here routine_name is name of the prescription routine and can be anything that is a valid IDL routine name. The
routine name and the root name of the file containing the routine must be the same (e.g. if the routine name is
telescope then its code must be in a file called telescope.pro). wavefront is a variable in which the final
propagation result is returned as a two-dimensional, double-precision array. wavelength is the wavelength in meters
at which to propagate (NOTE: even though the wavelength provided to PROP_RUN or PROP_RUN_MULTI is
in microns, it is internally converted to meters before being passed to the prescription). gridsize is the size of
the wavefront array grid (gridsize by gridsize elements), which must be a power of two (e.g., 512, 1024, 2048).
sampling is a variable in which the sampling in meters is to be returned to PROP_RUN (this value is returned by
PROP_END and the user is responsible for ensuring that it is passed back). The PASSVALUE keyword is described
below.
Here are some example prescription routine declarations:
pro telescope, wavefront, wavelength, gridsize, sampling, PASSVALUE=optval
pro camera, psf, lambda, n, sampling, PASSVALUE=passvalue

Prescription return values
The prescription function must finish by calling PROP_END to extract the wavefront array (either real or complexvalued, depending on the prescription) from the wavefront structure and obtain its sampling in meters. Both are then
returned; for example, something like this:
…
prop_end, wavefront, sampling_m
return
end

Optional prescription parameters using PASSVALUE
The PASSVALUE keyword must be included in the prescription routine declaration, but its use is optional when calling
the routine. It allows the user to pass an additional parameter to the prescription by assigning a value to the keyword
when calling PROP_RUN (common blocks may be used instead, but these do not work when running in parallel
using PROP_RUN_MULTI). In the prescription routine declaration, PASSVALUE is set to a variable that can be
accessed within the routine. Multiple values may be passed via this method using a structure. Here is an example
prescription declaration:
pro simple_prescription, wavefront, wavelength, gridsize, sampling, PASSVALUE=optval

In this case, the prescription will access the passed values through the variable optval. The name of the variable can
be any valid IDL name, optdiam rather than optval, for instance. If PASSVALUE was not assigned a value in the call
to PROP_RUN, then its corresponding variable in the prescription will be undefined. If, for instance, the optional
passed value is not used in the prescription, the PASSVALUE keyword can be omitted in the call to PROP_RUN, e.g.:
prop_run, ’simple_prescription’, psf, lambda, gridsize

Before trying to use a passed value, the IDL n_elements function can be used to check if the assigned variable actually
has been assigned a value. Here is an altered snippet of the example above that allows the diameter of the entrance
aperture to be optionally defined by optval, otherwise it defaults to 1 meter:
…
if ( n_elements(optval) eq 0 ) then diam = 1.0d else diam = optval
…

This routine can then be passed the diameter (2.0 meters in this case) during the call to PROP_RUN:

27

prop_run, ’simple_prescription’, psf, lambda, sampling, PASSVALUE=2.0

Suppose that both the entrance aperture diameter and the focal ratio of the lens should be optionally passed to the
prescription. This can be done using a multiple-member structure:
pars = {diam:2.0d, focal_ratio:25.0d}
prop_run, ’simple_prescription’, psf, lambda, sampling, PASSVALUE=pars

The code in simple_prescription would then look like this:
…
if ( n_elements(optval) eq 0 ) then begin
diam = 1.0d
focal_ratio = 15.0d
endif else begin
diam = optval.diam
focal_ratio = optval.focal_ratio
endelse

Matlab
Prescription routine declaration
All Matlab prescription functions must have the same parameter declaration sequence (no more nor fewer parameters
are allowed):
function [wavefront, sampling] = routine_name( wavelength, gridsize, optval )

routine_name is the name of the prescription function and can be anything that is a valid Matlab name. The name and
the root name of the file containing the routine must be the same (e.g. if the routine name is telescope then its
code must be in a file called telescope.m). wavelength is the wavelength in meters at which to propagate (NOTE:
even though the wavelength provided to PROP_RUN or PROP_RUN_MULTI is in microns, it is internally
converted to meters before being passed to the prescription). gridsize is the size of the wavefront array grid
(gridsize by gridsize pixels). The optval optional parameters are described below.
Here are some example prescription routine declarations:
function [wavefront, sampling] = telescope( wavelength, gridsize, optval )
function [psf, sampling] = camera( lambda, n, other_pars )

Prescription return values
The prescription function must finish by calling PROP_END to extract the wavefront array (either real or complexvalued, depending on the prescription) from the wavefront structure and obtain its sampling in meters. These are
returned as the wavefront array and wavefront sampling in meters.

Optional prescription parameters
The third parameter in a prescription’s declaration is for receiving optional parameters that were passed to
PROP_RUN or PROP_RUN_MULTI using the PASSVALUE keyword/value pair. Multiple values may be passed
via this method using a structure. Here is an example prescription declaration:
function [psf, sampling] = simple_prescription( wavelength, gridsize, pars )

28

In this case, the prescription will access the optional parameter through the variable pars. The name of the variable
can be any valid Matlab name. If the optional parameter is not provided in the call to PROP_RUN or
PROP_RUN_MULTI, then its corresponding variable in the prescription routine will be undefined.
The optional parameters are provided to the prescription in PROP_RUN like so:
pars.diam = 2.0;
pars.focal_ratio = 25.0;
psf = prop_run( ’simple_prescription’, lambda, sampling, 'PASSVALUE', pars );

Note that the PASSVALUE keyword is used to indicate that the next parameter in the call is a structure to pass to the
prescription. If the optional parameter is not used in the prescription, it can be omitted in the call to PROP_RUN,
e.g.:
[psf, sampling] = prop_run( ’simple_prescription’, lambda, gridsize );

Before trying to use an optional passed value, the Matlab nargin function can be used to check if three parameters
have been provided to the prescription (one less than was provided to PROP_RUN because the filename is not passed
to the prescription). Here is an altered snippet of the example above that allows the diameter of the entrance aperture
to be optionally defined by optval, otherwise it defaults to 1 meter:
if nargin ~= 3
diam = 1.0;
focal_ratio = 14.0;
else
diam = pars.diam;
focal_ratio = pars.focal_ratio;
end

A Note on Matlab Parameter Passing
Matlab does not have named keywords or switches as IDL or Python does, so the Matlab PROPER routines have a
mixture of positionally-dependent parameters (required and optional), along with parameter name/value pairs. For
example, PROP_ZERNIKES has three positionally-depended parameters along with some named optional
parameters:
wavestruct = prop_zernikes( wavestruct, zernike_num, zernike_val [, 'AMPLITUDE']
[, 'EPS', obscuration_ratio ] [ , 'NAME', string] [, 'NO_APPLY'] [, 'RADIUS', value] );
[ wavestruct, map_array ] = prop_zernikes( wavestruct, zernike_num, zernike_val [, 'AMPLITUDE']
[, 'EPS', obscuration_ratio] [ ,' NAME', string ]
[, 'NO_APPLY'] [, 'RADIUS', value ] );
The three required parameters are the wavefront structure (wavestruct), an array of Zernike polynomial indices
(zernike_num), and a corresponding array of Zernike polynomial coefficients (zernike_val). If obscured Zernike
polynomials are to be used, the central obscuration ratio must be provided using the EPS keyword and value, but must
do so only after the first 3 parameters are given. Some of these parameters are switches (flags) that do not need to be
set to any value. For example, to specify that the aberration map should be returned but not applied to the wavefront,
one might have a call that looks like this:
[wf, zermap] = prop_zernikes( wf, znum, zval, 'NO_APPLY' );

29

Python
Prescription routine declaration
All Python prescription functions must have the same parameter declaration sequence (no more nor fewer parameters
are allowed):
def routine_name( wavelength, gridsize, optval ):

routine_name is the name of the prescription function and can be anything that is a valid Python name. The name and
the root name of the file containing the routine must be the same (e.g. if the routine name is telescope then its code
must be in a file called telescope.py). wavelength is the wavelength in meters at which to propagate (NOTE: even
though the wavelength provided to PROP_RUN or PROP_RUN_MULTI is in microns, it is internally
converted to meters before being passed to the prescription, for historical reasons). gridsize is the size of the
wavefront array grid (gridsize by gridsize pixels), which must be a power of two (e.g., 512, 1024, 2048). The optval
optional parameter is described below in the PASSVALUE section.
Here are some example prescription routine declarations:
def telescope( wavelength, gridsize, optval ):
def camera( lambda, n, other_pars ):

Prescription return values
The prescription function must finish by calling PROP_END to extract the wavefront array (either real or complexvalued numpy array, depending on the prescription) from the wavefront structure and obtain its sampling in meters.
Both are then returned as a tuple; for example, something like this:
…
wavefront, sampling_m = proper.prop_end( wavefront_struct )
return ( wavefront, sampling_m )

Optional prescription parameters using PASSVALUE
The third parameter in a prescription’s declaration is for receiving an optional parameter that may be provided in the
call to PROP_RUN or PROP_RUN_MULTI via the PASSVALUE keyword. Multiple values may be passed using a
dictionary (or list of dictionaries for PROP_RUN_MULTI). Suppose we have a prescription in which the diameter
of a circular mask can optionally be specified at run time instead of using its default value. Its declaration might look
like this:
def simple_prescription( wavelength, gridsize, PASSVALUE={'mask_radius':0.02} ):

Running this prescription with the mask radius set to 0.01 would then be done like so, using PASSVALUE:
(psf, sampling) = prop_run( ’simple_prescription’, lambda, gridsize,
PASSVALUE={'mask_radius':0.01} )

In the prescription the value of the parameter passed by PASSVALUE is accessed like so:
wavefront = proper.prop_circular_aperture( wavefront, PASSVALUE['mask_radius'] )

Note that values can only be passed down to the prescription via this method, and not returned back up to PROP_RUN.

30

If the optional parameter is not provided in the call to PROP_RUN or PROP_RUN_MULTI via the PASSVALUE
variable then a default value should be defined. For instance, the optional passed value is not passed, e.g.:
(psf, sampling) = prop_run( ’simple_prescription’, lambda, gridsize )

In this case, the default value for the mask radius will be used.
Suppose instead we wanted to be able to specify the inner and outer radii of a mask. We could pass these two values
in a dictionary. Our modified routine would then have the following definition:
def simple_prescription( wavelength, gridsize, PASSVALUE={'inner_radius':0.01,
'outer_radius':0.8} ):

and running it with the inner and outer mask radii specified would look like this:
(psf, sampling) = prop_run( 'simple_prescription', lambda, gridsize,
PASSVALUE={'inner_radius':0.005, 'outer_radius':0.5} )

31

Fundamental PROPER Routines: PROP_LENS & PROP_PROPAGATE
The two procedures that will do most of the work in typical PROPER prescriptions are PROP_PROPAGATE and
PROP_LENS. PROP_PROPAGATE causes the wavefront to travel a specified distance in meters. It decides,
depending on the properties of the pilot beam, which propagation method to use (angular spectrum or Fresnel). Its
calling sequence is
IDL

prop_propagate, wavefront, dz [, label] [, /TO_PLANE]

Python

proper.prop_propagate( wavefront, dz [, label] [, TO_PLANE=True/False] )

Matlab

wavefront_out = prop_propagate( wavefront_in, dz [, 'SURFACE_NAME', label ]
[, 'TO_PLANE'] );

where wavefront is the variable containing the wavefront structure, dz is the distance to propagate the wavefront, and
the optional label will be printed if specified (e.g. “Propagating to secondary mirror”). The propagation distance is
positive to proceed forward through the system or is negative to back up. Note that it makes no sense to propagate the
wavefront before applying an aperture function; otherwise the edge of the array will act as the edge of the aperture.
PROPER has a number of routines that can create and apply aperture masks of various shapes.
As the name suggests, PROP_LENS alters the phase of the wavefront as would a thin lens or curved mirror. Its
calling sequence is
IDL

prop_lens, wavefront, focal_length [, label]

Python

proper.prop_lens( wavefront, focal_length [, label] )

Matlab

wavefront_out = prop_lens( wavefront_in, focal_length [, label] );

where wavefront is the variable containing the wavefront structure, focal_length is the focal length of the lens or mirror
in meters, and the optional parameter label is a string containing the name of the lens. If label is defined, for instance,
to be ‘primary mirror’, then the message “Applying lens at primary mirror’ will be printed as the lens is applied to the
wavefront (unless the QUIET switch was set in the call to PROP_RUN). A convex lens (equivalent to a concave
mirror) will have a positive focal length, while a concave lens (convex mirror) has a negative one.

A Simple Example Prescription
Here is an example prescription that simply creates a 1 meter diameter circular aperture and lens and propagates the
wavefront to the focus. The incoming wavefront is assumed to be collimated (uniformly flat). The prescription returns
the modulus-squared of the wavefront at the focus (e.g. the point spread function). In this prescription, the focal length
of the lens is 15 meters. The entrance pupil diameter (e.g. the initial beam size) is set to occupy half of the wavefront
grid diameter (beam_ratio=0.5). The code is included in the examples subdirectory in the PROPER directory.

32

IDL:
pro simple_prescription, wf, wavelength, gridsize, sampling, PASSVALUE=optval
diam = 1.0d
focal_ratio = 15.0d
focal_length = diam * focal_ratio
beam_ratio = 0.5

;-- entrance aperture diameter in meters

prop_begin, wf, diam, wavelength, gridsize, beam_ratio
prop_circular_aperture, wf, diam/2
prop_define_entrance, wf
prop_lens, wf, focal_length

;-- 0.5 meter radius circular aperture

prop_propagate, wf, focal_length
prop_end, wf, sampling
return
end

Python:
import proper
def simple_prescription(wavelength, gridsize):
diam = 1.0
focal_ratio = 15.0
focal_length = diam * focal_ratio
beam_ratio = 0.5
wfo = proper.prop_begin(diam, wavelength, gridsize, beam_ratio)
proper.prop_circular_aperture(wfo, diam/2)
proper.prop_define_entrance(wfo)
proper.prop_lens(wfo, focal_length)
proper.prop_propagate(wfo, focal_length)
(wfo, sampling) = proper.prop_end(wfo)
return (wfo, sampling)

Matlab:
function [wf, sampling] = simple_prescription( wavelength, gridsize, optval )
diam = 1.0;
focal_ratio = 15.0;
focal_length = diam * focal_ratio;
beam_ratio = 0.5;
wf = prop_begin( diam, wavelength, gridsize, beam_ratio );
wf = prop_circular_aperture( wf, diam/2 );
wf = prop_define_entrance( wf );
wf = prop_lens( wf, focal_length );
wf = prop_propagate( wf, focal_length );
[wf, sampling] = prop_end( wf );
end

33

Running the Prescription
A prescription is executed with the PROP_RUN or PROP_RUN_MULTI command. Note that every time these are
called they compile the prescription code file. This may lead to unexpected results if the user is editing the
prescription’s code while that prescription is being executed multiple times by looping calls to PROP_RUN; the next
iteration through the loop will compile the latest version of the file. PROP_RUN will be described here. See the
section on PROP_RUN_MULTI for details on running a prescription in parallel.
The calling sequence for PROP_RUN is:
IDL:

prop_run, prescription, result, wavelength, gridsize [, sampling_m]
[, PASSVALUE=value] [, /PHASE_OFFSET] [, /PRINT_INTENSITY]
[, /QUIET] [, /TABLE] [, /VERBOSE]

Python: ( result, sampling_m ) = proper.prop_run( prescription, wavelength, gridsize
[, PASSVALUE=value] [, PHASE_OFFSET=True/False]
[, PRINT_INTENSITY=True/False] [, QUIET=True/False]
[, TABLE=True/False] [, VERBOSE=True/False] )
Matlab: result = - OR [ result, sampling_m ] =
prop_run( prescription, wavelength, gridsize [, 'PASSVALUE', value]
[, 'PHASE_OFFSET'] [, 'PRINT_INTENSITY'] [, 'QUIET']
[, 'TABLE'] [, 'VERBOSE'] );
In this call, prescription_name is a string specifying the name of the prescription, result is a variable in which the
result of the propagation is returned, wavelength is the wavelength in microns at which to run the prescription, gridsize
is the dimension of the wavefront grid array (gridsize by gridsize, where gridsize is a power of two), and sampling is
a variable in which the sampling of the result in meters is returned (sampling is optional and will only contain a value
if the prescription bothers to return one). Use of the optional PASSVALUE keyword was described in an earlier
section. The other parameters are detailed in the PROP_RUN entry in the Routines Reference.
NOTICE: The wavelength must be specified in microns when calling PROP_RUN.
PROP_RUN will convert it to meters when calling the prescription. The prescription must
assume that it is called with the wavelength specified in meters.
To execute the example routine shown in the previous section, call PROP_RUN like this:
IDL:

prop_run, ’simple_prescription’, psf, 0.5, 512, dx

Python:

(psf, dx) = proper.prop_run( ’simple_prescription’, 0.5, 512 )

Matlab:

[psf, dx] = prop_run( ’simple_prescription’, 0.5, 512 );

In this particular example, the wavelength is set to 0.5 μm, the wavefront grid size to 512 by 512 pixels, the wavefront
intensity is returned in the variable psf, and the sampling in meters is returned in dx. The result of running this
prescription is a 512 by 512 array containing the point spread function at the focus of a circular lens.

34

Some Things to Note in IDL
When it is necessary to be as accurate as possible, distances (propagation distances, focal lengths, wavelengths, etc.)
should be specified as double precision values. In IDL, such values are denoted by using a “d” at the end of the
number or instead of “e” for scientific notation exponents. Examples are:
1.5d
3.44d-2
4.2d10

IDL will get confused when “d” is used without proper spacing in equations (e.g. 3.1d-2-5). In such cases, spaces
are required to make the intent clear (e.g. 3.1d-2 – 5).
It may be useful to force execution to stop at some point within a prescription so that variables, including the
wavefront, can be examined interactively. For example:
…
stop
…
IDL> phase = prop_get_phase(wavefront)
IDL> plot, phase(*,256)

When Things Crash in IDL…
If a prescription crashes during execution, including within a PROPER library routine, by default IDL will halt at the
code line where the error occurred. This allows the user to investigate why the routine crashed by interactively
checking variable values. Note that many routines from other libraries, including those distributed with IDL, will
instead return to the caller if an error occurs because they have set on_error or on_ioerror.
If the user chooses to rerun a prescription after it has crashed without first exiting IDL, be sure to issue the IDL
command “retall”, which will return the system all the way back to the main IDL program level. Be sure to check
for any open files that need to be closed (with the command “help, /files”). If a save state (described in a
following section) is active when a program crashes, the user should exit IDL, delete the temporary file(s) the save
state system created, and restart.

The Wavefront Array Structure
The common parameter to all of the PROPER routines that propagate or modify the wavefront is the wavefront
structure. This is a structure created by PROP_BEGIN that contains the double-precision, complex-valued, twodimensional wavefront array (member name wavefront). The center of the wavefront is always at the lower-left corner
of the array, consistent with the location of the zero-spatial-frequency element assumed by the Fourier transform
routine. Modifications to the wavefront array using the PROPER library routines take this offset into account, shifting
the center of any user-defined map (wavefront center assumed to be at the center of the map) to the corner before
adding-to or multiplying-by the wavefront array. PROP_END shifts the wavefront origin to the center of the array.
The wavefront structure also has a number of members that contain information related to the wavefront array and the
current propagation state, such as the current sampling, wavelength, reference surface characteristics, and so on. The
values that may be of interest to the user can be obtained using the PROPER query library functions. It is important
that the user avoid making any modifications or references to these member values directly. Altering these values can

35

lead to erroneous results, and there is no guarantee that the member names or types will remain constant in future
versions of the PROPER library.

Sampling
Figuring out the wavefront array and initial beam dimensions necessary to obtain adequate sampling at a given surface
(an intermediate one or the final image plane) is one of the more problematic issues when using a combination of
near-field and far-field diffraction propagators, as is done in the PROPER routines. In the near-field (near the focus
or, in a nearly-collimated beam near a pupil), the angular spectrum propagation method is used, and the sampling of
the wavefront remains constant with propagation distance. In the far-field, away from focus, the Fresnel propagator
is used, and the sampling changes proportionally with propagation distance to maintain a nearly-constant number of
samples across the beam. Note that the wavelength affects where the switch is made between near and far-field
propagators.
The wavefront is sampled using a finite square grid with dimensions specified by the user when PROP_BEGIN is
called. The user must also specify the ratio of the initial beam diameter to the grid width (the default is 0.5). The
beam should occupy only a portion of the wavefront array, with sufficient zero padding between its edges and the
array’s to reduce numerical artifacts introduced by the Fourier transforms. The combination of the grid and beam
sizes, along with the wavelength and the location of the surface in the system, determines the sampling. Note that by
changing the wavelength just a little, it is possible under some circumstances to drastically change the sampling,
usually when propagating near the focus.
Setting the TABLE switch when calling PROP_RUN will cause it to print out a table listing the sampling at each
surface.
In a well-corrected, conventional system (e.g. a telescope), the sampling, Δ, at the focus can be easily determined:

D

F D
N

where D is the entrance pupil diameter and N is the grid width (both in the same units – meters, pixels, whatever), λ
is the wavelength, and F is the focal ratio of the beam when approaching the focus. Increasing the beam diameter
relative to the grid size (e.g. improving sampling of the beam) results in coarser sampling of the image at the focus
(though the image extends to a greater angle), and vice-versa. Ideally, the image at the focus should be sampled at,
or more finely than, the Nyquist criterion, which theoretically (in the absence of numerical artifacts and with a
distribution of infinite extent), allows the value of a point at any location between the samples to be perfectly
determined using sinc interpolation. The required sampling to meet this is

D Nyquist 

F
2

For astronomers, the equivalent spacing in arcseconds is (for entrance pupil diameter D):

D"Nyquist 

  360  3600
4D

This sampling is achieved when the entrance pupil diameter is half of the grid width. In reality, numerical artifacts
and the limited number of samples can result in interpolation errors when the image is Nyquist sampled; interpolation
using a slightly finer image sampling can reduce these errors, especially near the sharp core of a point spread function.
If an aperture contains thin obscurations (e.g. vanes that hold the secondary mirror in a reflecting telescope, or gaps
between mirror segments), care must be taken to ensure that those structures are sufficiently sampled. This can be

36

done by specifying a combination of adequately large grid and initial beam sizes. It is advisable to have at least two
samples across an obscuration or subaperture.

Polychromatic Imaging
A prescription routine computes a purely monochromatic result, which is fine if a laser or extremely narrow bandpass
filter is used. To simulate a polychromatic result, say the image seen with a white-light source and broad bandpass
filter, multiple monochromatic images must be generated using separate calls to PROP_RUN or use the
PROP_RUN_MULTI routine to generate them in parallel. The images can then be added together with weights
corresponding to the transmission of the system.
The issue of sampling is important when simulating a polychromatic image. Unless the user specifies a beam
diameter/grid width ratio when calling PROP_BEGIN, the PROPER routines will compute a monochromatic image
that is Nyquist-sampled for its particular wavelength. Adding a bunch of Nyquist-sampled, multi-wavelength images
together will create an invalid result. The monochromatic images must, in the end, have the same sampling (in terms
of meters per pixel). This can be achieved in two ways. The first is to specify, for each wavelength, a beam
diameter/grid width ratio when calling PROP_BEGIN that provides the desired sampling. If this method is used,
then the focal plane sampling should be chosen to be equal-to or finer-than the Nyquist sampling criterion at the
shortest wavelength. The diameter of the entrance aperture pattern relative to the grid should not grow too large,
which would result in wrap-around numerical artifacts. The alternative method is to compute a Nyquist-sampled (or
finer) image at each wavelength and resample each one to a common grid. This can be done using PROP_MAGNIFY
(with the CONSERVE_FLUX switch set) or PROP_PIXELLATE. This has some computational overhead due to the
resampling, but the author considers this the safest solution.

PROPER Accuracy
As part of a NASA study of the modeling of coronagraphs, the accuracy of PROPER was determined relative to the
results produced using a more rigorous (and substantially slower) algorithms. Please refer to the Milestone 1 results
report of the NASA TDEM study “Assessing the performance limits of internal coronagraphs through end-to-end
modeling” by Krist et al. (available at http://exep.jpl.nasa.gov/technology).

37

Running multiple instances of a prescription in parallel with
PROP_RUN_MULTI
By default, PROPER runs a single instance of a prescription. If, for example, you want to propagate a wavefront at
different wavelengths through a system, you would run PROPER separately using PROP_RUN for each wavelength
in series. In another case, you may want to see how poking different deformable mirror actuators changes the field in
the final plane, so again you would run the simulation multiple times, once per poke. With current computers having
multiple CPUs and/or cores, this does not take full advantage of the parallel processing capabilities that can speed
execution by multiple factors. While a number of IDL, Python, and Matlab routines (along with FFTW and the Intel
Math Library FFT) take advantage of multithreading for parallel operations, there are usually some unused processing
cycles left waiting to be exploited. Of course, one could always run multiple sessions at once, but starting the runs and
then combining the results could be cumbersome. Also, on IDL and Matlab licenses with a limited number of sessions,
this could eat up all the slots and prevent other users from running.
PROP_RUN_MULTI can be used in place of PROP_RUN to execute multiple instances of a prescription at once
from within a single session. It can run a simulation simultaneously at different wavelengths or with different optional
parameters, as specified by an array of PASSVALUE entries. In general, most prescriptions need little or no
modification to make use of this capability.
As an example of the potential savings in run time, a prescription was run for 1, 5, and 9 wavelengths on a Linux
workstation with dual Xeon CPUs (10 cores/20 threads per CPU), 96 GB of RAM, and using the Intel Math Library
FFT. A single wavelength took 17.9 seconds. Running each sequentially would take 89.5 sec to do 5 wavelengths and
161.1 sec to do 9. Running 5 in parallel using PROP_RUN_MULTI took 33.5 sec (6.7 sec/wavelength) and 9 took
51.3 sec (5.7 sec/wavelength), speed improvements of 2.7x and 3.1x, respectively. Running even more wavelengths
would eventually reduce the speedup factors.
PROP_RUN_MULTI makes use of the IDL_IDLBRIDGE object in IDL, the multiprocessing package in Python, and
the parpool function in Matlab. Note that it requires the Parallel Processing Toolbox in Matlab, which is an extra-cost
option.

Using PROP_RUN_MULTI
The calling sequence to PROP_RUN_MULTI is very similar to that of PROP_RUN:
IDL:

prop_run_multi, prescription, result, wavelength, gridsize [, sampling_m]
[, /NO_SHARED_MEMORY] [, PASSVALUE=value] [, /PHASE_OFFSET] [, /QUIET]

Python: ( result, sampling_m ) = proper.prop_run_multi( prescription, wavelength, gridsize
[, NCPUS=value] [, PASSVALUE=value] [, PHASE_OFFSET=True/False]
[, QUIET=True/False] )
Matlab: result = - OR [ result, sampling_m ] =
prop_run_multi( prescription, wavelength, gridsize [, 'PASSVALUE', value]
[, 'PHASE_OFFSET'] [, 'QUIET'] );
Note that the TABLE and VERBOSE options that are available for PROP_RUN are not allowed here. The wavelength
may be an array of wavelengths (in microns) and/or optional_value may be an array of optional parameters, including
an array of structures. If both wavelength and optional_values are arrays, then both must have the same number of
entries; in this case optional_value[i] is used for wavelength[i]. All entries will be run simultaneously in parallel. The
wavefront variable will be a 3-D array with the 3rd dimension corresponding to the respective wavelength and/or
parameter value entries. The sampling variable contains the returned final plane samplings as a vector (it is dependent
on the user to return these values).

38

Examples
The following contrived example prescription begins at a 48 mm diameter deformable mirror with an array of 48 ×
48 actuators spaced by 1 mm and a circular aperture. A lens is located at the plane of the DM to focus the wavefront.
The array of DM strokes is an element of the optval structure that is passed using the PASSVALUE keyword. This
structure also includes use_dm, which specifies when to use the DM. This code and that which follows are available
in the examples subdirectory of the PROPER directory.

IDL:
pro multi_example, wavefront, lambda_m, n, sampling, PASSVALUE=optval
diam = 0.048d
pupil_ratio = 0.25
fl_lens = 0.48d
n_actuators = 48

;-- number of DM actuators in each dimension

prop_begin, wavefront, diam, lambda_m, n, pupil_ratio
prop_circular_aperture, wavefront, diam/2
prop_define_entrance, wavefront
if ( optval.use_dm ) then begin
dm_xc = n_actuators / 2.0
dm_yc = n_actuators / 2.0
dm_spacing = 1.0e-3
prop_dm, wavefront, optval.dm, dm_xc, dm_yc, dm_spacing
endif
prop_lens, wavefront, fl_lens
prop_propagate, wavefront, fl_lens
prop_end, wavefront, sampling, /NOABS
return
end

Python:
import proper
import numpy as np
def multi_example(lambda_m, n, PASSVALUE = {'use_dm': False, 'dm': np.zeros([48,48],
dtype = np.float64)}):
diam = 0.048
pupil_ratio = 0.25
fl_lens = 0.48
n_actuators = 48

# number of DM actuators in each dimension

wfo = proper.prop_begin(diam, lambda_m, n, pupil_ratio)
proper.prop_circular_aperture(wfo, diam/2)
proper.prop_define_entrance(wfo)
if PASSVALUE['use_dm']:
dm_xc = n_actuators/2.
dm_yc = n_actuators/2.
dm_spacing = 1.e-3
proper.prop_dm(wfo, PASSVALUE['dm'], dm_xc, dm_yc, dm_spacing)
proper.prop_lens(wfo, fl_lens)
(wfo, sampling) = proper.prop_end(wfo, NOABS = True)
return (wfo, sampling)

39

Matlab:
function [wavefront, sampling] = multi_example(lambda_m, n, optval)
diam = 0.048;
fl_lens = 0.48;
n_actuators = 48;
pupil_ratio = 0.25;
wavefront = prop_begin(diam, lambda_m, n, pupil_ratio);
wavefront = prop_circular_aperture(wavefront, diam / 2.0);
wavefront = prop_define_entrance(wavefront);
if (nargin > 2) & (optval.use_dm == 1)
dm_xc = fix(n_actuators / 2.0);
dm_yc = fix(n_actuators / 2.0);
dm_spacing = 1.0d-3;
wavefront = prop_dm(wavefront, optval.dm, dm_xc, dm_yc, dm_spacing);
end
wavefront = prop_lens(wavefront, fl_lens);
wavefront = prop_propagate(wavefront, fl_lens);
[wavefront, sampling] = prop_end(wavefront, 'noabs');
end

Suppose we want to create a polychromatic point spread function over λ = 0.5 – 0.7 m. We can do this by combining
monochromatic PSFs generated in parallel at wavelengths spanning the passband. Because the final field is at focus,
where the image scale is proportional to wavelength, we will need to resample each field to the same physical scale
(here, 1.5 m / pixel). We also poke a couple of actuators on the DM, just for fun.
IDL:
;-- program testmulti1.pro
lambda_min = 0.5
lambda_max = 0.7
nlambda = 9
gridsize = 1024
npsf = 256
final_sampling = 1.5e-6
;-- generate array of wavelengths
lambda_um = dindgen(nlambda) / (nlambda-1) * (lambda_max - lambda_min) + lambda_min
;-- create DM pattern (a couple of 0.1 micron pokes)
optval = {use_dm:1, dm:dblarr(48,48)}
optval.dm(20,20) = 0.2e-6
optval.dm(25,15) = 0.2e-6
;-- generate monochromatic fields in parallel
prop_run_multi, 'multi_example', fields, lambda_um, gridsize, sampling, PASSVALUE=optval
;-- resample fields to same scale, convert to PSFs
psfs = dblarr(npsf,npsf,nlambda)
for i = 0, nlambda-1 do begin
mag = sampling(i) / final_sampling
field = prop_magnify(fields(*,*,i), mag, npsf, /CONSERVE)
psfs(0,0,i) = abs(field)^2
endfor

40

;-- add PSFs together
psf = total(psfs,3) / nlambda
end

Python:
import proper
import numpy as np
def testmulti1():
lambda_min = 0.5
lambda_max = 0.7
nlambda = 9
gridsize = 256
npsf = 256
final_sampling = 1.5e-6
# generate array of wavelengths
wavelength = np.arange(nlambda) / (nlambda - 1.) * (lambda_max - lambda_min) + lambda_min
# Create DM pattern
optval = {'use_dm':
optval['dm'][20,20]
optval['dm'][15,25]

(a couple of 0.1 micron pokes)
True, 'dm': np.zeros([48,48], dtype = np.float64)}
= 0.2e-6
= 0.2e-6

# generate monchromatic fields in parallel
(fields, sampling) = proper.prop_run_multi('multi_example', wavelength, gridsize,
PASSVALUE = optval)
# resample fields to same scale, convert to PSFs
psfs = np.zeros([nlambda, npsf, npsf], dtype = np.float64)
for i in range(nlambda):
mag = sampling[i] / final_sampling
field = proper.prop_magnify(fields[i,:,:], mag, npsf, CONSERVE = True)
psfs[i,:,:] = np.abs(field)**2
# add PSFs together
psf = np.sum(psfs, axis = 0) / nlambda
return
if __name__ == '__main__':
testmulti1()

Matlab:
final_sampling = 1.5d-6;
gridsize = 1024;
npsf = 256;
nlambda = 9;
lambda_min = 0.5;
lambda_max = 0.7;
% Generate array of wavelengths (um)
lambda_um = lambda_min + (lambda_max-lambda_min) * [0:(nlambda-1)]/(nlambda-1);
% Create Deformable Mirror pattern (a couple of 0.1 micron pokes)
dm = zeros(48, 48);
dm(21, 21) = 0.2d-6;
dm(16, 26) = 0.2d-6;
use_dm = 1;
optval = struct('use_dm', use_dm, 'dm', dm);

41

% Generate monochromatic fields in parallel
[fields,sampling] = prop_run_multi('multi_example',lambda_um,gridsize,'passvalue',optval);
% Resample fields to same scale, convert to Point Spread Functions
psfs = zeros(npsf, npsf, nlambda);
for i = 1 : nlambda
mag = sampling(i) / final_sampling;
field = prop_magnify(fields(:, :, i), mag, 'size_out', npsf, 'conserve');
psfs(1:npsf, 1:npsf, i) = abs(field).^2;
end
% Add PSFs together
psf = sum(psfs, 3) / nlambda;

Note that the complex-valued electric field at each wavelength is resampled onto a 256 × 256 grid using
PROP_MAGNIFY. The interpolation tends to be more accurate when used on the complex field rather than the
intensity of that field. The CONSERVE switch tells PROP_MAGNIFY to conserve energy (intensity). The
monochromatic PSFs are averaged together at the end to create the broadband PSF.
We could have also specified a different DM pattern for each wavelength, or just have different DM patterns for one
wavelength. In either case, we would not want to combine them into a single PSF. Here’s a similar code, but this
time with one wavelength and different DM patterns (cosine ripples with different periods). Note that optval is an
array of structures, each containing the use_dm flag and the DM actuator stroke array dm.

IDL:
;-- program testmulti2.pro
lambda = 0.6
gridsize = 1024
;-- create different DM ripple patterns (50 nm amplitude)
npatterns = 3
optval = replicate({use_dm:1, dm:dblarr(48,48)}, npatterns)
x = dindgen(48) / 47 * (2*!pi) # replicate(1,48)
for i = 1, npatterns do optval(i-1).dm = 5.0e-8 * cos(4*x*i)
;-- generate monochromatic fields in parallel
prop_run_multi, 'multi_example', fields, lambda, gridsize, sampling, PASSVALUE=optval
end

42

Python:
import proper
import numpy as np
def testmulti2():
wavelength = 0.6
gridsize = 256
# create different DM ripple patterns (50 nm amplitude)
npatterns = 3
optval = np.repeat({'use_dm': True, 'dm': np.zeros([48,48], dtype=np.float64)}, 3)
x = np.dot((np.arange(48.)/47 * (2*np.pi)).reshape(48,1), np.ones([1,48],
dtype = np.float64))
for i in range(npatterns):
optval[i]['dm'] = 5.e-8 * np.cos(4*x*(i+1))
# generate monochromatic field in parallel
(fields, sampling) = proper.prop_run_multi('multi_example', wavelength, gridsize,
PASSVALUE = optval)
return
if __name__ == '__main__':
testmulti2()

Matlab:
gridsize = 1024;
npatterns = 3;
lambda = 0.6;
% Create different Deformable Mirror ripple patterns (50 nm amplitude)
dm = zeros(48, 48);
[x, y] = meshgrid(2.0d0 * pi * [0 : 47] / 47.0, ones(1, 48));
use_dm = 1;
optval = struct('use_dm', use_dm, 'dm', dm);
for i = 1 : npatterns
optval(i).dm = 5.0d-8 * cos(4.0d0 * x * i);
optval(i).use_dm = 1;
end
% Generate monochromatic fields in parallel
[fields,sampling] = prop_run_multi('multi_example',lambda,gridsize,'passvalue',optval);

Limitations


Optional parameters can be passed to the prescription using the PASSVALUE keyword, but the
prescription cannot return values to the caller via the same variables (PROP_RUN does allow this).



Each instance of the prescription runs in a separate process, and memory, including common blocks, is
not shared. Any changes to system variables apply only within each instance.

43



The number of simultaneous processes should be less than (probably no more than half) the number of
available processors. At some point the overheads involved defeat any advantages of parallelizing more
instances. Error messages about unable to allocate resources means you are using too many instances.



The runs may execute and finish in a different order than they were called. Any screen output produced
by them may be randomly interleaved. The returned values are in the same order as the input values,
however.



The SAVESTATE features do not work properly when running in parallel.



IDL: Unix (Linux, Mac OSx, etc.) versions of IDL prior to v8.3 require an active X Windows session to
run the IDL_IDLBRIDGE objects used by PROP_RUN_MULTI. X windows must be running even if
the machine has just a terminal interface. In order to run on such systems without an X display, or where
X forwarding is not possible over the connection, it is necessary to create a virtual display. The procedure
for doing this is described later. IDL versions later than v8.3 do not require X Windows for
IDL_IDLBRIDGE to work.



IDL: On Unix, parallel runs cannot be halted using Control-C in IDL. The only way to stop them is to
do a Control-Z to stop IDL, and kill the IDL job, or wait until they finish. All prescriptions should be
debugged first using PROP_RUN.

Running PROP_RUN_MULTI remotely (Unix IDL before v8.3)
As noted before, Unix versions of IDL before v8.3 require an active X Windows session to run the IDL_IDLBRIDGE
objects that PROP_RUN_MULTI uses to implement parallel processing. This is no problem if you are logged into
your own workstation, but if you log into another machine remotely (e.g., via ssh) you must also enable X forwarding
(e.g., ssh –Y remote.machine.edu) even if you are not planning on producing any graphical output.
If you cannot do X forwarding (e.g., your firewall is not set up for it, perhaps), you will need to set up a virtual X
display on that machine using Xvfb. This is not installed by default on many systems, so you must first get it. On
Fedora, for instance, you can do this (requires root access or as sudo):
yum install Xvfb
You can then create a virtual X Windows frame buffer and execute IDL with one command:
xvfb-run idl
You can also use xvfb-run to execute jobs in the background or in batch.

44

Save States
NOTE: Save states must not be used when running in parallel with PROP_RUN_MULTI.
Suppose you are modeling a telescope with multiple mirrors, a deformable mirror (DM) for wavefront control, and a
camera, and you are looking at how the image changes as you modify the DM actuator settings. The only optical
surface that is varying in this case is the DM. If you are continually looping through the prescription (trying to
iteratively correct a static wavefront error, for instance), it may be computationally wasteful, especially if a large grid
size is used, to propagate the wavefront all the way through the entire system every time you change the DM settings
if nothing else prior to the DM changes. A better solution would be to save the wavefront to a file just before
encountering the DM when you first propagate through the system. During later iterations, rather than starting from
the beginning, you can instead read in the wavefront, modify it with the DM, and continue propagating through the
rest of the system.
PROPER has a simple system called save states that makes it relatively easy to do this, handling the bookkeeping that
would otherwise make this a cumbersome process. The procedure outline of save state usage goes as follows:
1) In the top-level program containing the loop that repeatedly calls PROP_RUN to execute the
prescription routine, the save state system is initialized by calling PROP_INIT_SAVESTATE
before the loop.
2) At some point in the prescription routine, after PROP_BEGIN has been called and before the
wavefront is first propagated, a call to PROP_IS_STATESAVED determines if a save state for
the current wavelength has already been generated. If one has, then the user can bypass the initial
bit of propagation and jump (via a goto or an if-then block) to the point where things begin to
change.
3) In the prescription routine, the point where the wavefront should be saved, or read in if the state
was previously saved, is identified by a call to PROP_STATE. After this, the propagation
through the rest of the code occurs as usual.
4) After the looping PROP_RUN calls have finished in the top-level program, a call to
PROP_END_SAVESTATE turns off the save state system and deletes the wavefront files that
were written.
Here is a simple example of using save states. Suppose that we have a prescription routine that describes a system
with two identical lenses positioned so that the first lens forms an image at an intermediate focus, where we might
fiddle with the wavefront in some way. To avoid recomputing propagation through the first lens, we use save states
(the user may be afraid of goto’s, but the author is not…):
IDL:
pro example_system, wavefront, wavelength, gridsize, sampling, PASSVALUE=optval
diam = 1.0d
lens_fl = 20.0d
beam_ratio = 0.5
prop_begin, wavefront, diam, wavelength, gridsize, beam_ratio
if ( prop_is_statesaved(wavefront) ne 0 ) then goto, skip_first_lens
prop_circular_aperture, wavefront, diam/2
prop_define_entrance, wavefront
prop_lens, wavefront, lens_fl, ’1st lens’
prop_propagate, wavefront, lens_fl, ’intermediate focus’

45

skip_first_lens:
prop_state, wavefront
;-- we are now at the intermediate focus, so pretend that
;-- we do something to the wavefront here and continue on
prop_propagate, wavefront, lens_fl, ’second lens’
prop_lens, wavefront, lens_fl, ’second lens’
prop_end, wavefront
return
end

Python:
import proper
def example_system(wavelength, gridsize):
diam = 1.
lens_fl = 20.
beam_ratio = 0.5
# Define the wavefront
wfo = proper.prop_begin(diam, wavelength, gridsize, beam_ratio)
if proper.prop_is_statesaved(wfo) == False:
proper.prop_circular_aperture(wfo, diam/2)
proper.prop_define_entrance(wfo)
proper.prop_lens(wfo, lens_fl, '1st lens')
proper.prop_propagate(wfo, lens_fl, 'intermediate focus')
proper.prop_state(wfo)
# we are now at the intermediate focus, so pretend that
# we do something to the wavefront here and continue on
proper.prop_propagate(wfo, lens_fl, 'second lens')
proper.prop_lens(wfo, lens_fl, 'second lens')
(wfo, sampling) = proper.prop_end(wfo)
return (wfo, sampling)

Matlab:
function [wavefront, sampling] = example_system(wavelength, gridsize)
diam = 1.0;
lens_fl = 20.0;
beam_ratio = 0.5;
wavefront = prop_begin(diam, wavelength, gridsize, beam_ratio);
if (prop_is_statesaved(wavefront) == 0)
wavefront = prop_circular_aperture(wavefront, diam / 2.0 );
wavefront = prop_define_entrance(wavefront);
wavefront = prop_lens(wavefront, lens_fl, '1st lens');
wavefront = prop_propagate(wavefront, lens_fl, 'surface_name', 'intermediate focus');
end
wavefront

= prop_state(wavefront);

% We are now at the intermediate focus, so pretend that
% we do something to the wavefront here and continue on.
wavefront = prop_propagate(wavefront, lens_fl, 'surface_name', 'second lens');
wavefront = prop_lens(wavefront, lens_fl, 'second lens');
[wavefront, sampling] = prop_end(wavefront);
end

46

We can now have a routine that repeatedly calls example_system:
IDL:
pro run_example, wavelength, gridsize
prop_init_savestate
for i = 0, 10 do begin
prop_run, ’example_system’, psf, wavelength, gridsize
;-- let us pretend that we now do something useful with
;-- this iteration’s PSF and then compute another
endfor
prop_end_savestate
return
end

Python:
import proper
def run_example(wavelength, gridsize):
proper.prop_init_savestate()
for i in range(11):
(psf, sampling) = proper.prop_run('example_system', wavelength, gridsize)
#-- let us pretend that we now do something useful with
#-- this iteration's PSF and then compute another
proper.prop_end_savestate()
if __name__ == '__main__':
run_example(0.5, 512)

Matlab:
function run_example( wavelength, gridsize )
prop_init_savestate;
for it = 0 : 10
psf = prop_run( 'example_system', wavelength, gridsize );

end

% Let us pretend that we now do something useful with
% this iteration's PSF and then compute another.

prop_end_savestate;
end

When PROP_STATE is first called, it writes the current wavefront structure and ancillary information to a file. A
separate file is created for each propagation wavelength, and all of the files are deleted when
PROP_END_SAVESTATE is called. The filenames begin with the wavelength, followed by random numbers, and
ending with “_prop_savestate” (e.g. “550000000_4547_prop_savestate”).
If the user exits before
PROP_END_SAVESTATE is called, then these files will stay around until the user manually deletes them.

47

Apertures and Obscurations
Overview
The PROPER library contains procedures that create aperture and obscuration masks for a variety of shapes. Circles,
rectangles, ellipses, and polygons can be drawn. Some routines multiply the wavefront by the apertures while others
return an image of the aperture that can be further modified by the user and then multiplied by the wavefront using
PROP_MULTIPLY. A series of calls to these aperture functions can create complex patterns.
The edges of the shapes are antialiased. The value of a pixel along the edge is set to fraction of the pixel area covered
by the shape. This reduces high-frequency noise that would be created if these pixels were set to just one or zero.
For most PROPER shape-drawing routines, the size of the shape can be specified in meters or as a fraction of the size
of the beam at that location if the /NORM switch is set. Note that the beam size is determined from the Gaussian pilot
beam, which provides a good diameter estimate for an unaberrated system but will be increasingly inaccurate as
aberrations increase.
Note that in IDL and Python, which have (0,0) starting array indices, the center of the wavefront is at (x,y) = (fix(n)/2,
fix(n)/2). In Matlab, which starts at (1,1), the center is at (x,y) = (fix(n)/2+1, fix(n)/2+1).

Examples
Multiply the current wavefront by a circular aperture with a diameter equal to the beam diameter at the current location:
IDL:
Python:
Matlab:

prop_circular_aperture, wavefront, prop_get_beamradius(wavefront)*2
proper.prop_circular_aperture( wavefront, prop_get_beamradius(wavefront)*2 )
wavefront = prop_circular_aperture( wavefront, prop_get_beamradius(wavefront)*2 );

The same thing as above can be done using the NORM switch:
IDL:
Python:
Matlab:

prop_circular_aperture, wavefront, 1.0, /NORM
proper.prop_circular_aperture( wavefront, 1.0, NORM=True )
wavefront = prop_circular_aperture( wavefront, 1.0, 'NORM' );

Doing it the hard way, a circle can be drawn in an image that then multiplies the wavefront:
IDL:
Python:
Matlab:

circle = prop_ellipse( wavefront, 1.0, 1.0, /NORM )
prop_multiply, wavefront, circle
circle = proper.prop_ellipse( wavefront, 1.0, 1.0, NORM=True )
proper.prop_multiply( wavefront, circle )
circle = prop_ellipse( wavefront, 1.0, 1.0, 'NORM' );
wavefront = prop_multiply( wavefront, circle );

For another example, multiply the wavefront by a circular aperture (diam = 2.4 meters) that has a circular central
obscuration (diam = 0.79 meters), such as the shadow of the secondary mirror in a Cassegrain reflector telescope like
Hubble. The secondary mirror is held by four vanes, which are represented here by two overlapping rectangles
arranged perpendicularly to each other. The lengths of the rectangles are greater than the aperture diameter to ensure
that they extended completely across the opening.
IDL:

48

prop_circular_aperture, wavefront, 2.4/2
prop_circular_obscuration, wavefront, 0.79/2
prop_rectangular_obscuration, wavefront, 0.0264, 2.5
prop_rectangular_obscuration, wavefront, 2.5, 0.0264

Now, let’s add three small (r = 7.8 cm) circular obscurations (almost) equally spaced from each other near the edge of
the aperture:
IDL:

prop_circular_obscuration, wavefront, 0.078, -0.9066, -0.5538
prop_circular_obscuration, wavefront, 0.078, 0.0, 1.0705
prop_circular_obscuration, wavefront, 0.078, 0.9127, -0.5477

The above statements creates the pattern shown in Figure 2. This happens to be the obscuration pattern of the Hubble
Space Telescope. The three circular obscurations are pads that hold the primary mirror in place.

Figure 2. Obscuration pattern of the Hubble Space
Telescope. The central obscuration is the shadow of the
secondary mirror which is supported above the primary
mirror by four vanes. The three small circles are pads
that hold the primary mirror in place.

The drawing functions can be used to create complex shapes with varying transmissions:
IDL:
circle = prop_ellipse( wf, 1.0, 1.0, /NORM )
rectangle = prop_rectangle( wf, 0.5, 0.5, /NORM, /DARK ) * 0.5 + 0.5
aspect = 0.65
ring = prop_ellipse( wf, 0.85, 0.85*aspect, /NORM, /DARK )
ring = ring + prop_ellipse( wf, 0.70, 0.70*aspect, /NORM )
mask = circle * rectangle * ring

Python:
circle = proper.prop_ellipse( wf, 1.0, 1.0, NORM=True )
rectangle = proper.prop_rectangle( wf, 0.5, 0.5, NORM=True, DARK=True ) * 0.5 + 0.5
aspect = 0.65
ring = proper.prop_ellipse( wf, 0.85, 0.85*aspect, NORM=True, DARK=True )
ring = ring + prop_ellipse( wf, 0.70, 0.70*aspect, NORM=True )
mask = circle * rectangle * ring

Matlab:
circle = prop_ellipse( wf, 1.0, 1.0, 'NORM' );
rectangle = prop_rectangle( wf, 0.5, 0.5, 'NORM', 'DARK' ) * 0.5 + 0.5;
aspect = 0.65;
ring = prop_ellipse( wf, 0.85, 0.85*aspect, 'NORM', 'DARK' );
ring = ring + prop_ellipse( wf, 0.70, 0.70*aspect, 'NORM' );
mask = circle .* rectangle .* ring;

49

These statements create a clear circle with a square at the center that is ½ the amplitude of the circle (Figure 3). The
square is surrounded by an elliptical ring with an aspect ratio of 0.65. When creating apertures with non-zero and
non-unity values, remember that they represent amplitude, not intensity (square of amplitude), transmission.

Figure 3. A more complex aperture function

50

Lenses and Mirrors
A lens or curved mirror alters the wavefront by introducing a radially-dependent phase change, causing the wavefront
to converge, diverge, or become collimated. The routine PROP_LENS is used to reproduce the phase change created
by a thin lens or curved mirror. A thin lens has a negligible thickness, so the difference in the indices of refraction
between the lens material and the external medium are insignificant, there are no chromatic effects, and a ray enters
and exits the lens at the same height. The lens assumed by PROP_LENS will take a collimated beam and create a
spherically converging or diverging beam, or vice-versa. If the lens has a positive focal length, it is a convex lens (or
the equivalent concave mirror) and will cause an incident collimated beam to converge. If the focal length is negative,
it is a concave lens (convex mirror) that will cause a collimated beam to diverge as if it emanated from a point in front
of the lens. In most cases, this default lens can be used when modeling well-corrected systems.
PROP_LENS makes no distinction between a lens and mirror. It simply creates a phase transformation that would
be produced by an optical element with power. Positive focal lengths always indicate convex lenses and concave
mirrors. Positive distances always indicate forward propagation through the system, from one surface (either real or
virtual) to the next. These conventions are unlike those used in ray-tracing programs like Zemax or Code V where
the sign signifying forward propagation changes after a reflection and the shape of a lens or mirror is defined relative
to absolute spatial coordinates.

Aberrations
Zernike Polynomials
Classical low-order aberrations are often defined by Zernike polynomials. These form an orthogonal set of aberrations
that include wavefront tilt, defocus, coma, astigmatism, spherical aberration, and others. The polynomials are
functions of radius and azimuth angle within the aperture. It is common in optical modeling to use Zernike
polynomials that have been normalized over the aperture (i.e. have a root-mean-square of 1.0 relative to a mean
wavefront phase of 0.0). The coefficients to each polynomial specify the RMS amount of that aberration.
The PROPER routine PROP_ZERNIKES adds Zernike aberrations to the current wavefront, either as phase or
amplitude errors. The user specifies which polynomials to add and the coefficient for each. The numbering scheme
follows that established by Noll (J. Opt. Soc. Am., 66, 207 (1976)). Unobscured Zernikes up to an arbitrary number
or the first 22 obscured Zernikes (those normalized over a centrally-obscured aperture) can be added. The routine
PROP_HEX_WAVEFRONT can be used to add a hexagonal array of aberrated hexagonal segments.
The unobscured Zernike polynomial equations can be printed using PROP_PRINT_ZERNIKES. Zernike
polynomials can be fit to a user-provided error map and aperture function using PROP_FIT_ZERNIKES.

User-Created or Measured Error Maps
The user may wish to apply an error map (either phase or amplitude) to the wavefront that was created on-the-fly (e.g.
using an equation) or is an actual measurement of a surface (from an interferometer, for instance).
PROP_ADD_PHASE takes a 2D array representing the wavefront or surface error in meters, converts it to phase
error, and adds it to the phase term of the current wavefront. PROP_MULTIPLY and PROP_DIVIDE take an
amplitude error map (with values ranging from 0.0 to 1.0) and multiply or divide the current wavefront by it.
These routines assume that the error map has the same size and sampling as the current wavefront array. The relevant
values
can
be
obtained
using
PROP_GET_GRIDSIZE,
PROP_GET_SAMPLING,
and
PROP_GET_BEAMRADIUS. PROP_ROTATE can be used to rotate and/or shift a map and PROP_MAGNIFY
to resample it. The optical axis is assumed to be at the center of the central pixel of the map.

51

PROPER also has routines that will read in an error map from a FITS file and resample it to match the sampling of
the current wavefront array. PROP_READMAP will read in, shift (if necessary), and resample a map, but it will not
apply it to the wavefront. PROP_ERRORMAP does all these as well as rotation and then applies the map to the
wavefront (adding if wavefront or surface error, multiplying if amplitude error). PROP_WRITEMAP can be used
to write an error map file containing header information used by PROP_ERRORMAP.

The Deformable Mirror
An optical system may use a deformable mirror (DM) to alter the phase of a wavefront, usually to correct for
aberrations induced by the atmosphere, the optics themselves, or even the eye. Most major astronomical telescopes
utilize DMs to correct for blurring caused by the atmosphere, sensing and correcting for low spatial frequency
aberrations (currently <10 cycles/D, though progress is being made towards correcting higher frequency errors). In
space, where atmospheric interference is not a problem, DMs will be used on telescopes designed for high-contrast
imaging, such as the Terrestrial Planet Finder. To detect the light from a faint planet near a bright star, the wavefront
must be as near-perfect as possible, so the errors in the optics due to manufacturing defects and thermally-induced
variations need to be corrected. DMs can also be used to control amplitude errors by means of the Talbot effect.
The PROPER package includes a deformable mirror modeling routine, PROP_DM, which allows for realistic
simulation of wavefront control. It assumes that the DM is a square array of regularly-spaced actuators that alter the
height of a thin-facesheet mirror. DMs exist that have non-rectangular, irregularly-spaced actuators, and some have
individual mirror segments for each actuator, such as micromirror arrays, but those are not modeled by PROP_DM.
The facesheet is a semi-rigid surface that exerts forces beyond the region controlled by a single actuator. It can pull
up or down on neighboring actuators, resulting in surface heights that are not equal to the commanded displacements.
Between actuators, facesheet tension can alter the surface in complicated ways. The influence function describes the
deformation of the facesheet surface when a single actuator is pistoned above surrounding actuators. The function’s
shape depends on a number of factors, including the thickness and stiffness of the face sheet.
The influence function (Figure 4) used in PROP_DM is for a Xinetics Photonex module actuator and was derived
from measurements at the Jet Propulsion Laboratory and provided by John Trauger. It is sampled at 1/10 th of the
spacing between actuators. To reproduce the DM surface, a set of delta functions whose heights are equal to the values
provided by the user in the DM z piston array are placed in a grid at the same sampling as the 1/10 th subsampled
influence function. This array is then convolved with the influence function and then resampled via interpolation onto
the wavefront array. This has been demonstrated to represent the behavior of a real DM well via testbed measurements.
The DM z piston values specify the DM facesheet surface height at the center of the actuator when a single actuator
is poked, so a value of 1.0 would produce a maximum surface deformation of 1.0. However, due to the effects of the
neighboring actuators and the rigidity of the facesheet, when multiple adjacent actuators are pistoned the net surface
deformation will be greater or lower than the specified z piston value. Actuators will pull down or up on the others.
Figure 5 shows the DM surface when all but the edge actuators are pistoned by 1.0. The central flat region has a
deflection of 1.43 because neighboring actuators are not pulling down those adjacent to them.
The pistons needed to achieve the desired surface can be derived by iteratively fitting the map of required surface
heights with the influence-function-convolved actuator array. This can be done by providing the requested surface
heights to PROP_DM and setting the FIT switch.

52

Normalized Surface Height

1.0
0.8
0.6
0.4
0.2
0.0
-0.2
-3

-2

-1

0
Actuator

1

2

3

Figure 4. The measured average influence function for a Xinetics Photonex deformable mirror
actuator. This represents the surface height when one actuator is pistoned above the local
actuators

Figure 5. The simulated surface of a 20 x 20
actuator deformable mirror when the inner
actuators are pistoned by 1.0 while the actuators
along the outer edge are set to 0.0. The raised fence
along the top edge and the depression along the
bottom are caused by the rigidity of the facesheet
and are what would be seen in a real DM. These
effects are reproduced due to application of the
influence function. Note that the mean deflection
in the interior region is ~1.43, not 1.0.

Inclination and Rotation of the Deformable Mirror
Deformable mirrors are typically not used at normal incidence but are usually inclined with respect to the incoming
beam. If the beam is collimated and circular then it will have an elliptical footprint on the DM surface, with more
actuators across the beam along the major axis than in the perpendicular one. When projected onto the wavefront that
is sampled equally in X and Y, the DM actuators will appear elongated. PROP_DM allows for the three-dimensional
rotation of the DM surface about the wavefront central point and projects the surface orthographically (i.e., no
perspective effect) onto the wavefront array. The optional XTILT, YTILT, and ZTILT keyword parameters allow the
user to specify the rotations in degrees. The coordinate system is left handed with the unrotated DM surface in the XY plane with the first pixel in the lower left and the Z axis towards the observer. By default the rotations are done in
X, Y, then Z; the ZYX switch can be used to reverse the order. Examples of the rotated surfaces are shown in Figure
6.

53

XTILT = 10
YTILT = 30
ZTILT = 20
XYZ

XTILT = -15
YTILT = -20
ZTILT = 10
ZYX

XTILT = 40
YTILT = 0
ZTILT = 0
XYZ

Figure 6. Surface maps (returned using the MAP keyword to PROP_DM) for different rotations of a 20 × 20
actuator DM with alternating actuator pistons. A circular beam is shown superposed for reference. Note that
the central case was done using the reverse rotation order (using the ZYX switch).

54

Phase and Amplitude Error Maps Defined by Power Spectral Densities
Power Spectral Density
When modeling the propagation of light through an optical system, it is not enough to simply know that some given
surface introduces an RMS wavefront error of x. If all of the errors are at low spatial frequencies, like spherical
aberration or coma or astigmatism, the diffraction effects will be different than if they are all at higher ones (like
surface roughness or pitting). Low spatial frequency errors (those of less than a few cycles across the surface, such
as figuring errors) are often described by Zernike polynomials. Higher spatial frequency aberrations, which scatter
light to larger angles, require many higher-order Zernike terms, often too many to be practically specified. If an
aberration map is available for a given optic, say from an interferometer measurement, then it can be used to represent
the errors up to the resolution limit of the map. In most instances, however, such maps are not available or they have
insufficient resolution to represent the optic at higher spatial frequencies. A statistical description of the errors relative
to the spatial scales of concern must instead be established. Using this, a two-dimensional map with similar statistics
as the measured or expected surface can be generated. In PROPER, this can be done using the routine
PROP_PSD_ERRORMAP.
A commonly used statistical descriptor, the power spectral density (PSD), specifies the power density (square of the
amplitude per spatial frequency) of the error that exists at each spatial frequency. In essence, it says that a y amount
of error with a spatial frequency of x cycles per diameter exists in the surface. If you take the Fourier transform of a
one-dimensional profile measurement of a surface and then take the modulus square of the result, what you get is the
one-dimensional PSD of that profile (ignoring things like windowing effects, etc.). Because the phase and the sign of
the amplitude are lost when taking the modulus of the transform, a PSD can only represent the spatial frequency
content of the errors and not their distribution across the surface.
One-dimensional PSDs represent the errors along a one-dimensional surface profile. However, most surfaces are
considered two-dimensional structures, so there are two-dimensional PSDs as well. The 2D PSD can be obtained by
taking the modulus square of the Fourier transform of a 2D surface map (again, excluding windowing effects). If the
surface errors have spatially-random distributions (i.e. isotropic structure) or are circular and concentric, then the
power distribution in the 2D PSD will be fully symmetric about the origin. In this case, a one-dimensional profile can
be used to describe the mean radial profile of the 2D PSD (this is often the case). You can usually tell from the units
whether a PSD profile is 1D or 2D. 1D PSDs are typically specified in units of height2 × [distance/cycle] (e.g. nm3)
and 2D PSDs in height2 × [distance/cycle]2 (e.g. nm4). From this point on, a “2D PSD” will refer to a one-dimensional
profile representing the mean 2D PSD radial distribution. If a surface is isotropic such that a 1D surface height profile
along any direction has essentially the same 1D PSD, then the mean 1D PSD can be converted to a 2D PSD (Church,
Takacs, & Leonard in SPIE Proceedings, v. 1165, 136 (1989); Elson & Bennett in Applied Optics, v. 34, 201 (1995)).
Real-world optics are often described by PSDs derived by combining interferometric, profile-stylus, and scatterometer
measurements, spanning a wide range of spatial scales.
The majority of the errors in most optics are in the low spatial frequency range due to limitations of the figuring
process. Beyond a certain spatial frequency (often related to the size of the figuring & polishing tools), the surface
errors rapidly decrease in power. As an example, let us look at the surface errors in a real mirror (Figure 7). In this
case, 1D PSDs for many 1D radial surface profiles were computed to create the mean 1D PSD profile shown (this
surface is, for the most part, radially isotropic but not generally isotropic, so this is not strictly accurate to some degree,
but is easier to understand). The profile shows that the largest errors are at low spatial frequencies, and the error level
decreases toward higher ones. At some frequency, the aberration level begins to drop more rapidly as figuring errors
give way to polishing errors.

55

1-D Surface PSD (nm 3 )

108
107
106
105
104
103
102
1

10
Cycles / Diameter

100

Figure 7. (Left) The surface error map for a 0.2 m, ultra-high-precision mirror; (Right) The mean 1-D PSD
profile derived from line profiles across the mirror, shown as a dashed line. A fitted PSD curve is overplotted
as a solid line. The fitted PSD matches well at high spatial frequencies, but it under-represents the amount of
low-order aberrations.

A PSD profile is often characterized by fractal-like spectrum parameters, which is how PSDs are specified in
PROP_PSD_ERRORMAP. By default, the parameterized PSD is assumed to have the K-correlation form described
by Church, Takacs, & Leonard in SPIE Proceedings, v. 1165, 136 (1989):

a

PSD 2 D ( k ) 

 

1  k 2 
b 


c 1
2

where k is the spatial frequency (cycles/meter) and a is the power at low spatial frequencies (m4). The value b is the
correlation length; b/(2π) is where the lines describing the flat spectrum at the low spatial frequencies and the power
law falloff at higher ones intersect (in cycles/meter) – it essentially indicates where the PSD curve bends. The power
law falloff is defined by c. An alternative form used by the Terrestrial Planet Finder project is assumed when the
/TPF switch is set in PROP_PSD_ERRORMAP:

PSD 2 D (k ) 

a

 

1  k c 
b 


Note that for the TPF form, the bend in the PSD occurs at b rather than b/(2π). As shown in the example in Figure 7,
the parameterized PSD profile matches this particular measured PSD at higher spatial frequencies but not at lower
ones. This is a constraint of the flat low-frequency distribution assumed by the parameterized PSD. If we create an
error map using these parameters, its low-order aberration content will be below that of the actual surface.
Let us now try to force the real surface error map to have a PSD more like those assumed by the above equations.
This is done only to demonstrate certain aspects and limitations of the PSD specification that the user needs to take
into account when modeling a system (and it demonstrates another PROPER routine along the way). Because our
map is a bit heavy in low-order errors, we can try to get rid of some them by fitting and subtracting Zernike polynomial
aberrations. PROP_FIT_ZERNIKES can be used to fit Zernike polynomials to an error map, and it returns a map
containing just those polynomials, as shown in Figure 8. Subtracting the Zernike map from the surface clearly gets
rid of a large fraction of the low-order errors, though it leaves behind those that do not match the Zernike polynomial
patterns.

56

Surface Map

Fitted Zernike Map

Surface Map – Zernike Map

Figure 8. (Left) The measured surface error map; (Middle) The sum of the Zernike polynomials fit to
the original map using PROP_FIT_ZERNIKES; (Right) The original map after subtraction of a
limited set of Zernike aberration polynomials.

1-D Surface PSD (nm3)

10 8
10 7
10 6
10 5
10 4
10 3
10 2
1

10
Cycles / Diameter

100

Figure 9. PSD profile derived from the surface map after subtraction of 22 Zernike polynomials.

The PSD of this new, Zernike-subtracted map is shown in Figure 9. This more closely matches the profile shape
assumed by the parameterized PSD. We can now take the fitted PSD parameters and plug them into
PROP_PSD_ERRORMAP to create a realization of the PSD (a map having the same spatial frequency error content
as the Zernike-subtracted real map). By running models using the Zernike-subtracted and PSD-realization maps, we
can compare how well the two maps agree in terms of diffraction effects.

An Example Using PSD-Defined Error Maps
A good system to use for this example is a stellar coronagraph, which is discussed in a later section. A coronagraph
suppresses the light from a point source (a star) that is diffracted by edges in a system (entrance aperture, support
vanes, etc.), but it does not reduce the light scattered by mid-and-high spatial frequency surface errors like those that

57

are characterized by a PSD. Because the bulk of the diffraction structures (Airy rings) are removed, it is easier to see
the effects of these surface errors in the simulated image. We use here the simple Lyot coronagraph detailed in the
Examples section, applying either the Zernike-subtracted measured map or the PSD-realized map to the telescope lens
(note that errormap.fits is not distributed with PROPER).
IDL:
pro psdtest, wavefront, wavelength, gridsize, sampling, PASSVALUE=usepsdmap
lens_diam = 0.212d
lens_fl = 24.0 * lens_diam
beam_width_ratio = 0.5

;-- 0.212 meter lens diameter
;-- focal length (f/24 focal ratio)

prop_begin, wavefront, lens_diam, wavelength, gridsize, beam_width_ratio
;-- create circular entrance aperture
prop_circular_aperture, wavefront, lens_diam/2
prop_define_entrance, wavefront
;-;-;-;-;--

If the variable usepsdmap is not defined (via optional passed value),
read in and use the map which represents the wavefront error (in this case,
it’s in nanometers, hence we need to multiply it by 1e-9 to convert it to meters)
and has a sampling of 0.4 mm/pixel. If usepsdmap is defined, then generate
and use a PSD-defined map. The maps have an RMS of about 1.0 nm.

if ( n_elements(usepsdmap) eq 0 ) then begin
prop_errormap, wavefront, 'errormap.fits', SAMPLING=0.0004, MULTIPLY=1e-9, /WAVEFRONT
endif else begin
a = 3.29d-23
;-- low-freq power in m^4
b = 212.26
;-- correlation length (cycles/m)
c = 7.8
;-- high-frequency falloff (r^-c)
prop_psd_errormap, wavefront, a, b, c
endelse
prop_lens, wavefront, lens_fl, 'telescope lens'
prop_propagate, wavefront, lens_fl, 'intermediate focus'
;-- multiply field by occulting mask with 4*lam/D HWHM transmission
prop_8th_order_mask, wavefront, 4, /CIRCULAR
prop_propagate, wavefront, lens_fl, 'pupil imaging lens'
prop_lens, wavefront, lens_fl, 'pupil imaging lens'
prop_propagate, wavefront, lens_fl, 'lyot stop'
prop_circular_aperture, wavefront, 0.53, /NORM
;-- Lyot stop
prop_propagate, wavefront, lens_fl, 'reimaging lens'
prop_lens, wavefront, lens_fl, 'reimaging lens'
prop_propagate, wavefront, prop_get_distancetofocus(wavefront), 'final focus'
prop_end, wavefront, sampling
return
end

58

Python:
import proper
def psdtest(wavelength, gridsize, PASSVALUE = {'usepsdmap': False}):
lens_diam = 0.212
lens_fl = 24. * lens_diam
beam_width_ratio = 0.5

# 0.212 meter lean diameter
# focal length (f/24 focal ratio)

wfo = proper.prop_begin(lens_diam, wavelength, gridsize, beam_width_ratio)
# Create circular entrance aperture
proper.prop_circular_aperture(wfo, lens_diam/2)
proper.prop_define_entrance(wfo)
#-#-#-#-#--

If the variable usepsdmap is not defined (via optional passed value),
read in and use the map which represents the wavefront error (in this case,
it's in nanometers, hence we need to multiply it by 1e9 to convert it to meters)
and has a sampling of 0.4 mm/pixel. If usepsdmap is defined, then generate
and use a PSD-defined map. The maps have an RMS of about 1.0 nm.

if PASSVALUE['usepsdmap']:
a = 3.29e-23
# low-freq power in m^4
b = 212.26
# correlation length (cycles/m)
c = 7.8
# high-freq falloff (r^-c)
proper.prop_psd_errormap(wfo, a, b, c)
else:
proper.prop_errormap(wfo, 'errormap.fits', SAMPLING = 0.0004,
MULTIPLY = 1e-9, WAVEFRONT = True)
proper.prop_lens(wfo, lens_fl, 'telescope lens')
proper.prop_propagate(wfo, proper.prop_get_distancetofocus(wfo), 'intermediate focus')
# multiply field by occulting mask with 4*lam/D HWHM transmission
mask = proper.prop_8th_order_mask(wfo, 4, CIRCULAR = True, MASK = True)
proper.prop_propagate(wfo, lens_fl, 'pupil imaging lens')
proper.prop_lens(wfo, lens_fl, 'pupil imaging lens')
proper.prop_propagate(wfo, lens_fl, 'lyot stop')
proper.prop_circular_aperture(wfo, 0.53, NORM = True) # Lyot stop
proper.prop_propagate(wfo, lens_fl, 'reimaging lens')
proper.prop_lens(wfo, lens_fl, 'reimaging lens')
proper.prop_propagate(wfo, proper.prop_get_distancetofocus(wfo), 'final focus')
(wfo, sampling) = proper.prop_end(wfo)
return (wfo, sampling)

Matlab:
function [wavefront, sampling] = psdtest(wavelength, gridsize, usepsdmap)
if nargin < 3
usepsdmap = 0;
end
lens_diam = 0.212;
lens_fl =
24.0 * lens_diam;
beam_width_ratio = 0.5;

% telescope diameter (m)
% focal length (m) (f/24 focal ratio)
% beam diameter fraction

wavefront = prop_begin(lens_diam, wavelength, gridsize, beam_width_ratio);

59

% Create circular entrance aperture
wavefront = prop_circular_aperture(wavefront, lens_diam / 2.0);
wavefront = prop_define_entrance(wavefront);
%
%
%
%
%

If the variable usepsmap is not set, read in and use the map which
represents the wavefront error (in this case, it's in nanometers,
hence we need to multiply it by 1e-9 to convert it to meters) and has
a sampling of 0.4 mm/pixel. If usepsdmap is set, then generate and
use a PSD-defined map. The maps have an RMS of about 1.0 nm.

if usepsdmap == 0
wavefront = prop_errormap(wavefront, 'errormap.fits', 'wavefront', ...
'sampling', 0.0004, 'multiply', 1.0d-9)
else
a = 3.290d-23;
% low spatial frequency power (m^4)
b = 212.26;
% correlation length parameter (cycles / meter)
c = 7.8;
% high frequency falloff power law exponent
wavefront = prop_psd_errormap(wavefront, a, b, c);
end
wavefront = prop_lens(wavefront, lens_fl, 'telescope lens');
dif = prop_get_distancetofocus(wavefront);
% distance to focus (m)
wavefront = prop_propagate(wavefront, dif, 'snm', 'intermediate focus');
% Multiply field by occulting mask with 4*wavelength/lens_diam HWHM transmission
[wavefront, mask] = prop_8th_order_mask(wavefront, 4.0, 'circular');
wavefront
wavefront
wavefront
wavefront

=
=
=
=

prop_propagate(wavefront, lens_fl, 'snm', 'pupil imaging lens');
prop_lens(wavefront, lens_fl, 'pupil imaging lens');
prop_propagate(wavefront, lens_fl, 'snm', 'lyot stop');
prop_circular_aperture(wavefront, 0.53d0, 'norm'); % Lyot stop

wavefront = prop_propagate(wavefront, lens_fl, 'snm', 'reimaging lens');
wavefront = prop_lens(wavefront, lens_fl, 'reimaging lens');
dff = prop_get_distancetofocus(wavefront);
% distance to focus (m)
wavefront = prop_propagate(wavefront, dff, 'snm', 'final focus');
[wavefront, sampling] = prop_end(wavefront);
end

Figure 10 shows the “real” map and the simulated one generated by PROP_PSD_ERRORMAP using the PSD of
the “real” one. The overall scales of the structures in simulated map matches those in the real map fairly well at
moderate spatial frequencies. However, the small ripples on the real surface are arranged in concentric patterns about
the center, a result of the figuring and polishing processes, while they are randomly distributed on the simulated
surface. This is a result of the lack of phase information being carried over into the PSD. There are also some lowerorder, annular zones in the real map that are not reproduced in the simulation.
Figure 11 shows the results of using each map. The speckles are created by the surface errors (the image would be

almost completely dark without these aberrations). Their sizes and intensities relative to their radius from the center
are similar in both, indicating that the simulated map is reproducing the spatial frequency content well. This is also
evident in the radial profiles shown in Figure 12. However, near the center, where lower-spatial-frequency aberrations
are dominant, the circular rings appear in the “real” image that are not apparent in the “fake” one. This is due to the
low-to-mid-spatial-frequency errors that were not subtracted out. This mismatch at lower frequencies is also seen in
the radial intensity profiles.

60

Surface Map – Zernike Map

Map from PSD

Figure 10. (Left) Surface error map after fit and subtraction of Zernike polynomials; (Right)
Simulated surface error map (multiplied by the aperture shape) generated using the PSD of
the map on the left.

Defining Amplitude Errors Using PSDs
At some level an optic will have non-uniform transmittance or reflectance over its area due to unavoidable
manufacturing errors, such as irregular coating deposition. These alter the wavefront’s amplitude spatial distribution
and can create effects similar to phase errors, such as speckles (though amplitude-induced speckles have different
wavelength dependencies than phase-induced speckles). In the large majority of optical systems such amplitude errors
are not important, as phase errors are usually orders of magnitude larger in effect. However, for some systems in
which the phase errors are corrected to a high degree with deformable mirrors, such as space-based planet-finding
stellar coronagraphs, amplitude errors may be the primary residual wavefront aberration.
The spatial distributions and ranges of coating amplitude errors do not appear to be well known, especially at subpercent levels. Presumably, amplitude errors can be described using PSDs like phase errors (though it is not known
to the author whether this is used by industry or not). PROP_PSD_ERRORMAP can generate an amplitude error
map when the AMPLITUDE keyword is defined using the same PSD profile specifications used for phase errors. The
value of the AMPLITUDE keyword sets the maximum amplitude level. Because intensity is the square of amplitude,
AMPLITUDE=0.9 would result in a maximum transmission of 0.81. The amp parameter in
PROP_PSD_ERRORMAP specifies the RMS amplitude variation of the entire map about the mean amplitude (thus
the RMS switch is ignored when AMPLITUDE is specified). The parameters b and c define the PSD profile shape in
the same way as they do for phase errors. The wavefront is multiplied by the amplitude map.

61

Figure 11. The simulated image (monochromatic light) of a point source observed using a coronagraph
with a circular, 8th-order occulting mask with a 4λ/D half-width-half-max transmission profile. The left
side shows the image computed using the measured, Zernike-subtracted surface map to represent the
errors in the telescope lens. The right shows the result of the same propagation sequence but using the
map generated from the PSD of the “real” map. Both are displayed with identical, nearly-logarithmic
intensity scaling. The image is 200 λ/D on each side. An aberration with a spatial scale of one cycle over
the pupil diameter (D) would create a speckle at λ/D.

10-4

Intensity

10-6

10-8

10-10
10-12
0

50

100
Radius ( / D)

150

200

Figure 12. Mean radial intensity profiles of the coronagraphic images shown in Figure 11. The solid
line is from the image based on the measured map and the dashed is from that based the PSD-derived
map.

62

PSD-Defined Maps for Inclined Surfaces
When modeling a surface that has errors defined by a PSD, the inclination of the surface relative to the direction of
the incoming beam must be taken into account. The projection of the beam onto the surface results in unequal beam
diameters along the orthogonal reference axes. For example, consider the elliptical mirror shown in the left side of
Figure 13 that has circular polishing errors. This mirror is then inclined 45º relative to an incident, circular, collimated
beam, bending the beam 90º. The projection of the circular beam onto the mirror leads to a difference in the spatial
frequency distribution of the errors in terms of cycles/diameter for each axis, as shown in the right side of the figure.
As a result, the diffraction pattern at the image plane will have light scattered to greater angles along the direction
corresponding to the major axis of the elliptical mirror than along the other.
The INCLINATION keyword in PROP_PSD_ERRORMAP can be set to the inclination in degrees of the surface
relative to the direction of the beam (e.g. at INCLINATION=0, the beam direction is perpendicular to the surface).
The inclination angle is measured in the Y-Z plane relative to the Y axis. The sign of the inclination is not significant.
The ROTATION keyword can be set to an angle to rotate the map after projection onto the inclined surface.

Figure 13. (Left) Looking perpendicularly to an
elliptical surface containing errors of a certain spatial
frequency; (Right) The projection of the surface
errors in a collimated beam with a 45º incidence angle
to the elliptical mirror. In the inclined case, the
spatial frequency of the errors is higher relative to the
beam diameter in the Y direction.

Limitations of PSD-Defined Error Maps
An error map generated by PROP_PSD_ERRORMAP provides a useful method for simulating the aberrations in an
optical component over a broad range of spatial scales. However, it cannot fully encompass the entire range of errors
that may be present. As previously shown, a PSD cannot specify the spatial distribution of errors (e.g. whether they
occur in rings), which can lead to a lack of spatially-correlated structure in the diffraction pattern.
The error map is generated by taking the Fourier transform of the 2D PSD (mapped onto a 2D array with the same
dimensions as the wavefront grid) with random phases. Because the PSD array has discrete sampling, not all spatial
frequencies can be represented. The lowest spatial frequency is approximately ½ cycle over the beam diameter (piston
is not included by PROP_PSD_ERRORMAP). This means that wavefront tilt, for instance, would not appear in
these maps. More advanced methods of error map generation may be implemented in the future that would include
lower-frequency terms. Lower-order terms can be added using PROP_ZERNIKES.
The aberration map is not guaranteed to have the expected RMS error over the illuminated portion of the surface due
to the method used to generate it. If the PSD is grossly dominated by low-spatial-frequency errors, then there can be
localized regions in the error map with significantly different mean deviations, and so the RMS error within the
illuminated portion can depend strongly on the location of the beam with respect to these regions. The PSD is best
used to define aberrations of a few cycles or higher.

63

Notes Regarding PROP_PSD_ERRORMAP
To avoid recomputing a PSD-defined error map every time the same prescription is run, the generated map can be
written to a file by specifying a filename with the FILE keyword. The next time the prescription is run, the routine
will look first to see if the map file exists, in which case it will read it in rather than generate a new map.
If a PSD-defined map file already exists for one grid size and a propagation is performed at a larger size, an error
message indicating this will be displayed when PROP_PSD_ERRORMAP tries to read in the file, and execution
will stop. If you wish to use the same map regardless of the grid dimensions, do an initial propagation using the largest
grid size that might be used in the future. You can then perform the propagation again with a smaller size and
PROP_PSD_ERRORMAP will resample the larger map to match the smaller grid. NOTE: This method is definitely
not optimal, and it is strongly suggested that a map is used only for the grid size at which it was generated.
The map generated by PROP_PSD_ERRORMAP can be returned using the MAP keyword.
If want to generate an error map but do not want to apply it to the wavefront, then use the NO_APPLY switch.
Multiple PSD-defined error maps can be applied to the same surface by successively calling the routine.
Because of the way the maps are generated, aberrations with spatial frequencies of less than ~½ cycle/D will not be
included. Such aberrations may be included by adding in Zernike polynomials.
When generating an amplitude error map, the map is divided by the maximum value in the entire array (not just the
illuminated portion) and then multiplied by the value specified by the AMPLITUDE keyword.

64

Examples
A Simple Telescope
A very simple telescope consists of an objective lens and an eyepiece. The objective can be a convex lens (for a
refractive telescope) or a concave mirror (a reflecting telescope). The eyepiece is a simple lens that, in combination
with the eye, magnifies the image produced at the focus of the objective. The telescope here has a 60 mm diameter
objective with a focal ratio of f/15, and the eyepiece has a 21 mm focal length. The system is focused at infinity for
astronomical observations. The eye is represented by a lens with a 22 mm focal length that focuses onto the retina
(the final focus of the system). The lens of the eye is located at the exit pupil of the system. The exit pupil is where
the eyepiece forms an image of the objective, and its location is easily derived in this case using the lens law. This
prescription will return the point spread function of the telescope (i.e. the image of a star) as seen by the eye. Because
the beam starts off with half of the width of the computational grid (beam_ratio=0.5), the final PSF is Nyquistsampled. Note that the optional passed value is not used here, but it must still be included in the procedure’s parameter
declarations.
This PSF is shown in the left image of Figure 14. The expected Airy pattern should be perfectly circularly
symmetrical, but the simulated PSF shows obvious computational artifacts. The cross-shaped pattern and streaks
along the diagonals and the image axes are caused by wrap-around aliasing errors that are a consequence of using
Fourier methods with finite grids. Increasing the grid size reduces many of these artifacts (right image in Figure 14),
at the expense of increased computation. The level of these errors is very small (they are enhanced in the images by
using a logarithmic intensity stretch), and are probably not significant when modeling most systems, especially if
surface errors are included that scatter light into the wings.

512 x 512

1024 x 1024 (cropped)

Figure 14. Nyquist-sampled point spread functions for a circular aperture computed using 512 2 and 10242 grids (the
PSF from the larger grid has been cropped). The images are shown with logarithmic intensity stretches to emphasize
the low-level features.

The example above produced a Nyquist-sampled PSF. Theoretically, the PSF at any finer resolution can be derived
from this PSF using sinc interpolation (using PROP_MAGNIFY, for instance). In reality, computational artifacts
introduce noise that create interpolation errors. It is sometimes better to create an image sampled slightly finer than
Nyquist. To create a PSF that is sampled 1.2 times finer than Nyquist in the example, beam_ratio can be decreased
20% (beam_ratio=0.4).

65

Using PROP_MAGNIFY() to resample both the Nyquist and Nyquist/1.2 sampled PSFs to 2x finer than Nyquist, it
is evident in Figure 15 that the result of using the more finely sampled source PSF has fewer interpolation artifacts
(black pixels that indicated erroneously negative values).

Figure 15. Central portions of PSFs computed at Nyquist (left) and 1.2x better
than Nyquist sampling (right), resampled to 2x better than Nyquist using
PROP_MAGNIFY().

IDL:
pro simple_telescope, wavefront, wavelength, gridsize, sampling, PASSVALUE=optval
d_objective = 0.060d
fl_objective = 15.0 * d_objective
fl_eyepiece = 0.021d
fl_eye = 0.022d
beam_ratio = 0.5

;-;-;-;-;--

objective diameter in meters
objective focal length in meters
eyepiece focal length
human eye focal length
initial beam width/grid width

prop_begin, wavefront, d_objective, wavelength, gridsize, beam_ratio
prop_circular_aperture, wavefront, d_objective/2.0
prop_define_entrance, wavefront
prop_lens, wavefront, fl_objective, 'objective'
prop_propagate, wavefront, fl_objective+fl_eyepiece, 'eyepiece'
prop_lens, wavefront, fl_eyepiece, 'eyepiece'
exit_pupil_distance = fl_eyepiece / (1.0 - fl_eyepiece/(fl_objective+fl_eyepiece))
prop_propagate, wavefront, exit_pupil_distance, 'exit pupil at eye lens'
prop_lens, wavefront, fl_eye, 'eye'
prop_propagate, wavefront, fl_eye, 'retina'
prop_end, wavefront, sampling
return
end

To execute this prescription at a wavelength of 0.5 μm using a grid size of 512 by 512 elements, issue the command:
prop_run, ’simple_telescope’, psf, 0.5, 512

66

Python:
import proper
def simple_telescope(wavelength, gridsize):
d_objective = 0.060
fl_objective = 15.0 * d_objective
fl_eyepiece = 0.021
fl_eye = 0.022
beam_ratio = 0.5

#
#
#
#
#

objective diameter in meters
objective focal length in meters
eyepiece focal length
human eye focal length
initial beam width/grid width

wfo = proper.prop_begin(d_objective, wavelength, gridsize, beam_ratio)
proper.prop_circular_aperture(wfo, d_objective/2)
proper.prop_define_entrance(wfo)
proper.prop_lens(wfo, fl_objective, "objective")
proper.prop_propagate(wfo, fl_objective+fl_eyepiece, "eyepiece")
proper.prop_lens(wfo, fl_eyepiece, "eyepiece")
exit_pupil_distance = fl_eyepiece / (1 - fl_eyepiece/(fl_objective+fl_eyepiece))
proper.prop_propagate(wfo, exit_pupil_distance, "exit pupil at eye lens")
proper.prop_lens(wfo, fl_eye, "eye")
proper.prop_propagate(wfo, fl_eye, "retina")
(wfo, sampling) = proper.prop_end(wfo)
return (wfo, sampling)

To execute this prescription at a wavelength of 0.5 μm using a grid size of 512 by 512 elements, issue the command:
(psf, sampling) = proper.prop_run( ’simple_telescope’, 0.5, 512 )

Matlab:
function [wavefront, sampling] = simple_telescope(wavelength, gridsize)
d_objective = 0.06;
% objective diameter (m)
fl_objective = 15.0 * d_objective; % focal length objective (m)
fl_eyepiece = 0.021;
% focal length eyepiece (m)
fl_eye = 0.022;
% focal length human eye (m)
beam_ratio = 0.5;
% initial beam width/grid width
wavefront
wavefront
wavefront
wavefront

=
=
=
=

wavefront

= prop_propagate(wavefront, fl_objective + fl_eyepiece, 'surface_name', …
'eyepiece');
= prop_lens(wavefront, fl_eyepiece, 'eyepiece');

wavefront

prop_begin(d_objective, wavelength, gridsize, beam_ratio);
prop_circular_aperture(wavefront, d_objective / 2.0);
prop_define_entrance(wavefront);
prop_lens(wavefront, fl_objective, 'objective');

exit_pupil_distance = fl_eyepiece / (1.0d0 - fl_eyepiece / (fl_objective + fl_eyepiece));
wavefront
= prop_propagate(wavefront, exit_pupil_distance, 'surface_name', …
'exit pupil at eye lens');
wavefront
= prop_lens(wavefront, fl_eye, 'eye');
wavefront

= prop_propagate(wavefront, fl_eye, 'surface_name', 'retina');

[wavefront, sampling] = prop_end(wavefront);
end

67

To execute this prescription at a wavelength of 0.5 μm using a grid size of 512 by 512 elements, issue the command:
[psf, sampling] = prop_run( ’simple_telescope’, 0.5, 512 )

The Hubble Space Telescope
The Hubble Space Telescope has a 2.4 m diameter primary mirror and smaller secondary mirror in a Ritchey-Chretien
configuration (a variant of the Cassegrain design). As just about everyone knows, the primary was figured to the
wrong shape due to errors in the measurement setup, causing significant spherical aberration. This problem was not
discovered until images of stars were taken on-orbit. Optics installed by astronauts during later servicing missions
compensated for the error and allowed Hubble to operate as it was intended.
The Hubble telescope prescription below includes the primary and secondary mirrors, represented by simple lenses
(not conic mirrors, like in the real telescope). In this example, the system does not have the spherical aberration
problem. The aperture function includes the shadows of the secondary mirror and support vanes and the primary
mirror pads (see Figure 2).
Hubble is focused by moving the secondary mirror away or towards the primary, keeping the distance between the
back of the primary mirror and the focal plane fixed. This method is reproduced here using the variable delta_sec.
Moving the secondary increases the path length between it and the primary and also between it and the focal plane.
delta_sec is defined by the optional PASSVALUE keyword, defaulting to zero if it is not specified in the call to
PROP_RUN.
Note that because HST in reality uses conic optics, the effects of defocus, etc., will actually be different than presented
here (which are merely for demonstration purposes).

Figure 16. Central 100 x 100 pixel region of a simulated in-focus
PSF generated using the simple Hubble Space Telescope
prescription, displayed with a logarithmic intensity stretch.

68

IDL:
pro hubble_simple, wavefront, wavelength, grid_n, sampling, PASSVALUE=delta_sec
diam = 2.4d

;-- telescope diameter in meters

fl_pri = 5.52085d
d_pri_sec = 4.907028205d
fl_sec = -0.6790325d
d_sec_to_focus = 6.3919974d

;-;-;-;--

HST primary focal length (m)
primary to secondary separation (m)
HST secondary focal length (m)
nominal distance from secondary to focus

beam_ratio = 0.5
;-- delta_sec = additional primary-to-secondary separation offset (m)
if ( n_elements(delta_sec) eq 0 ) then delta_sec = 0.0
prop_begin, wavefront, diam, wavelength, grid_n, beam_ratio
;-- create entrance aperture pattern
prop_circular_aperture, wavefront, diam/2
prop_circular_obscuration, wavefront, 0.396
prop_rectangular_obscuration, wavefront, 0.0264, 2.5
prop_rectangular_obscuration, wavefront, 2.5, 0.0264
prop_circular_obscuration, wavefront, 0.078, -0.9066, -0.5538
prop_circular_obscuration, wavefront, 0.078, 0., 1.0705
prop_circular_obscuration, wavefront, 0.078, 0.9127, -0.5477

;-;-;-;-;-;-;--

primary mirror
secondary obscuration
secondary vane (vert)
secondary vane (horiz)
primary mirror pad 1
primary mirror pad 2
primary mirror pad 3

prop_define_entrance, wavefront
prop_lens, wavefront, fl_pri, 'primary'

;-- primary mirror

prop_propagate, wavefront, d_pri_sec+delta_sec, 'secondary'
prop_lens, wavefront, fl_sec, 'secondary'

;-- secondary mirror

prop_propagate, wavefront, d_sec_to_focus+delta_sec, 'HST focus'
prop_end, wavefront, sampling
return
end

To execute this prescription at λ=0.5 μm using a 512 x 512 grid, the call to PROP_RUN looks like this:
prop_run, ’hubble_simple’, psf, 0.5, 512, sampling

Python:
import proper
def hubble_simple(wavelength, gridsize, PASSVALUE = {'delta_sec': 0.}):
diam = 2.4
# telescope diameter in meters
fl_pri = 5.52085
# HST primary focal length (m)
d_pri_sec = 4.907028205
# primary to secondary separation (m)
fl_sec = -0.6790325
# HST secondary focal length (m)
d_sec_to_focus = 6.3919974
# nominal distance from secondary to focus
beam_ratio = 0.5
# initial beam width/grid width
# delta_sec = additional primary-to-secondary separation offset (m)
delta_sec = PASSVALUE['delta_sec']

69

wfo = proper.prop_begin(diam, wavelength, gridsize, beam_ratio)
proper.prop_circular_aperture(wfo, diam/2)
proper.prop_circular_obscuration(wfo, 0.396)
proper.prop_rectangular_obscuration(wfo, 0.0264, 2.5)
proper.prop_rectangular_obscuration(wfo, 2.5, 0.0264)
proper.prop_circular_obscuration(wfo, 0.078, -0.9066, -0.5538)
proper.prop_circular_obscuration(wfo, 0.078, 0., 1.0705)
proper.prop_circular_obscuration(wfo, 0.078, 0.9127, -0.5477)

#
#
#
#
#
#
#

primary mirror
secondary obscuration
secondary vane (vert)
secondary vane (horiz)
primary mirror pad 1
primary mirror pad 2
primary mirror pad 3

proper.prop_define_entrance(wfo)
proper.prop_lens(wfo, fl_pri, "primary")

# primary mirror

proper.prop_propagate(wfo, d_pri_sec+delta_sec, "secondary")
proper.prop_lens(wfo, fl_sec, "secondary")
proper.prop_propagate(wfo, d_sec_to_focus+delta_sec, "HST focus", TO_PLANE = False)
(wfo, sampling) = proper.prop_end(wfo)
return (wfo, sampling)

To execute this prescription at λ=0.5 μm using a 512 x 512 grid, the call to PROP_RUN looks like this:
(psf, sampling) = proper.prop_run( ’hubble_simple’, 0.5, 512 )

Matlab:
function [wfa, dx] = hubble_simple(lambda, gridsize, delta_sec)
if nargin < 3
delta_sec = 0.0;
end
diam = 2.4;
fl_pri = 5.52085;
d_pri_sec = 4.907028205;
fl_sec = -0.6790325;
d_sec_focus = 6.3919974;
beam_ratio = 0.5;

% delta primary to secondary spacing (m)
%
%
%
%
%

diameter of telescope
focal length mirror 1
mirror 1 to mirror 2
focal length mirror 2
mirror 2 to focus

(m)
(m)
(m)
(m)
(m)

wavefront = prop_begin(diam, lambda, gridsize, beam_ratio);
wavefront
wavefront
wavefront
wavefront
wavefront
wavefront
wavefront

=
=
=
=
=
=
=

prop_circular_aperture(wavefront, diam / 2.0);
% primary edge
prop_circular_obscuration(wavefront, 0.396);
% secondary obscuration
prop_rectangular_obscuration(wavefront, 0.0264, 2.5); % vertical vanes
prop_rectangular_obscuration(wavefront, 2.5, 0.0264); % horizontal vanes
prop_circular_obscuration(wavefront, 0.078, 'xc', -0.9066, 'yc', -0.5538);
prop_circular_obscuration(wavefront, 0.078, 'xc', 0.0, 'yc', 1.0705);
prop_circular_obscuration(wavefront, 0.078, 'xc', 0.9127, 'yc', -0.5477);

wavefront = prop_define_entrance(wavefront);
wavefront = prop_lens(wavefront, fl_pri, 'primary');
wavefront = prop_propagate(wavefront, d_pri_sec + delta_sec, 'surface_name', 'secondary');
wavefront = prop_lens(wavefront, fl_sec, 'secondary');
wavefront = prop_propagate(wavefront, d_sec_focus + delta_sec, 'surface_name', 'HST focus');
[wfa, dx] = prop_end(wavefront);
end

70

To run this example, type the following on the Matlab command line:
[psf, sampling] = prop_run( ’hubble_simple’, 0.5, 512 )

The PASSVALUE keyword is not specified in this call, so the normal separation between the primary and secondary
mirrors is used, producing an in-focus PSF at the focal plane. Note that the initial beam width/grid width ratio is
explicitly set to 0.5 (which is the default assumed by PROP_BEGIN if the ratio is not specified). This sizing results
in a Nyquist-sampled PSF at the focal plane (Figure 16). Finer sampling of the PSF can be obtained by reducing the
initial beam diameter/grid width (beam_ratio), at the expense of reducing the extent of the PSF and the initial sampling
of the beam.

Changing the Focus (and a detour discussion on errors and sampling)
The focus of this simplified Hubble can be changed by providing the secondary mirror offset from its nominal position
by means of the PASSVALUE keyword. Let us now create PSFs with the secondary moved 40 μm closer and 40 μm
further from the primary:
IDL:
prop_run, ’hubble_simple’, psfa, 0.5, 512, samplinga, PASSVALUE=-40e-6
prop_run, ’hubble_simple’, psfb, 0.5, 512, samplingb, PASSVALUE=40e-6

Python:
(psfa, samplinga) = proper.prop_run( ’hubble_simple’, 0.5, 512,
PASSVALUE={'delta_sec':-40e-6} )
(psfb, samplingb) = proper.prop_run( ’hubble_simple’, 0.5, 512,
PASSVALUE={'delta_sec':40e-6} )

Matlab:
[psfa, samplinga] = prop_run( ’hubble_simple’, 0.5, 512, 'PASSVALUE', -40e-6 );
[psfb, samplingb] = prop_run( ’hubble_simple’, 0.5, 512, 'PASSVALUE', 40e-6 );

The PSFs for the defocused system are shown in Figure 17. There are some things to notice here. First, the samplings
of both are significantly different than for the in-focus PSF (Δin-focus=6.0 μm, Δ-40=6.6 μm, Δ+40=7.7 μm). The PSFs
are sufficiently defocused so that they are in the far-field region, thus their sampling is proportional to their respective
distances from the beam waist (focus). This ensures that the beam, as it expands from or contracts towards focus,
occupies approximately the same region of the wavefront array.
Rectangular grid patterns can also be seen in the PSFs. These are computational artifacts caused by wrap-around
errors due to the Fourier transforms. As can be seen in the log-stretched images, the wings of the PSFs have sufficient
signal that they wrap-around the sides of the array, creating interference patterns. Working to suppress such artifacts
is one of the primary headaches of using near-field/far-field propagators.
Let us first try a larger grid size, 1024 x 1024 instead of 512 x 512. As shown in Figure 18, increasing the grid size
while maintaining the same beam/grid width ratio does not really help to suppress the artifacts.

71

-40 μm

+40 μm

Figure 17. Simulated PSFs from the
simplified Hubble prescription with the
secondary mirror moved 40 μm toward
(-) and away (+) from the primary mirror
relative to its nominal position. The top
panels have linear intensity stretches and
the bottom are logarithmic. Wraparound errors from the FFTs cause the
grid-like patterns seen in the images.
The entire 512 x 512 grids are shown.

Figure 18. Simulated PSF for the simple Hubble with the
secondary moved +40 μm from the primary. The grid size was
1024 x 1024 (shown rebinned to 512 x 512) and the initial
beam/grid size ratio was 0.5. Increasing the grid size relative to
that used in Figure 16 does not reduce the grid-pattern artifacts.

Next, we can try the combination of increasing the grid size and reducing the initial beam/grid width ratio. Choosing
1024 x 1024 with a beam ratio of 0.25 will create an initial beam with 256 samples across it, just the same as in the
512 x 512 case with a beam ratio of 0.5. By doing this, additional zero padding is created that allows the wings to
extend further before wrapping. As shown in Figure 19, this greatly reduces the errors, though some are still visible.
Increasing the grid size further while proportionally decreasing the beam ratio to maintain constant beam sampling
will suppress the errors even more.
A side effect of this technique is that while the sampling remains constant in the far field, in the near field it will vary
with the beam ratio. A smaller beam ratio results in finer sampling in the near field (the PSF looks bigger). However,
and conversely to the far-field case, this also reduces the area into which the wings can expand, increasing the chances
of wrap-around. This is just one of those things that has to be dealt with when using near field/far field propagators.

72

Figure 19. Like Figure 18 except that the initial beam/grid width
ratio was 0.25, creating more room for the PSF wings to extend
before wrapping. The central 512 x 512 region of the 1024 x 1024
array is shown.

Figure 20. (Left) Simulated PSF generated with a 512 x 512 grid and beam ratio of 0.4 with forced
propagation using TO_PLANE. The central 100 x 100 pixels are shown. (Right) PSF on left
interpolated to 7x finer sampling using PROP_MAGNIFY. The entire 512 x 512 interpolated region
is shown.

Now let’s try another trick. The amount of defocus we have applied is sufficiently small that we can try forcing the
PROPER routines to use the near-field propagator further away from focus than they normally would. This can be
done using the TO_PLANE switch in the call to PROP_PROPAGATE that propagates the beam the final step. Recall
when traveling from one surface to another, the PROPER routines propagate to the focus of the current beam and then
to the next surface. By specifying TO_PLANE, the angular spectrum propagator is used to go from focus to the desired
position, and the result will have the same sampling as it would at focus. For the simplified Hubble, we can try this
by modifying the last PROP_PROPAGATE line:

73

IDL:

prop_propagate, wavefront, d_sec_to_focus+delta_sec, /TO_PLANE

Python: proper.prop_propagate( wavefront, d_sec_to_focus+delta_sec, TO_PLANE=True )
Matlab: prop_propagate( wavefront, d_sec_to_focus+delta_sec, 'TO_PLANE' );

Using a 512 x 512 grid with a beam ratio of 0.4 and TO_PLANE produces a PSF (Figure 20) that is more coarsely
sampled than those generated above. Using PROP_MAGNIFY to interpolate this to finer sampling produces a result
equivalent to that created using a 4096 x 4096 grid with 0.0625 beam ratio and default propagation, but in only 2% of
the time.

The Talbot Effect
The Talbot effect is the name for the curious phenomenon that occurs when a wavefront has a spatially-periodic
amplitude pattern of period P. As it propagates through space, the amplitude pattern evolves into a phase pattern with
a period of P and a reduced amplitude pattern with a period of P/2. It then turns into an amplitude pattern of period
P but with reversed contrast, and then back into a phase pattern. Finally, it ends up as the same amplitude pattern it
began as. The Talbot length, LT, the distance over which the amplitude pattern reconstitutes itself, is LT=2P2/λ, where
λ is the wavelength. A discussion of the Talbot effect is provided by Goodman in Introduction to Fourier Optics.
The PROPER routines can be used to simulate the Talbot effect. The prescription below, talbot, creates a 128 x
128 wavefront array with a cosine amplitude pattern (i.e. an amplitude grating) varying along the X axis. The caller
passes to the prescription the spatial period of the pattern in meters and the distance over which to propagate. The
TO_PLANE switch on PROP_PROPAGATE forces it to propagate to a plane to make interpreting the phase values
easier (it would do this anyway for the examples that follow, but this just emphasizes the point). The NOABS switch
in the call to PROP_END tells it to return the complex-amplitude wavefront array rather than the modulus-square of
the wavefront, so that the amplitude and phase can be examined.
IDL:
pro talbot, wavefront, wavelength, gridsize, sampling, PASSVALUE=optval
talbot_length = 2.0d * optval.period^2 / wavelength
beam_ratio = 0.5
prop_begin, wavefront, optval.diam, wavelength, gridsize, beam_ratio
;-- create 1-D grating pattern
m = 0.2 ;-- pattern amplitude
x = (findgen(gridsize) - gridsize/2) * prop_get_sampling(wavefront)
grating = 0.5 * (1 + m * cos(2 * !pi * x / optval.period))
;-- create 2-D amplitude grating pattern
grating = grating # replicate(1.0,gridsize)
prop_multiply, wavefront, grating
prop_define_entrance, wavefront
prop_propagate, wavefront, optval.dist, /TO_PLANE
prop_end, wavefront, sampling, /NOABS
return
end

74

Python:
import proper
import numpy as np
def talbot(wavelength, gridsize, PASSVALUE = {'period': 0., 'diam': 0., 'dist': 0.}):
talbot_length = 2. * PASSVALUE['period']**2 / wavelength
beam_ratio = 0.5
wfo = proper.prop_begin(PASSVALUE['diam'], wavelength, gridsize, beam_ratio)
# create 1-D grating pattern
m = 0.2
x = (np.arange(gridsize, dtype = np.float64) - gridsize/2) \
* proper.prop_get_sampling(wfo)
grating = 0.5 * (1 + m * np.cos(2*np.pi*x/PASSVALUE['period']))
# create 2-D amplitude grating pattern
grating = np.dot(grating.reshape(gridsize,1), np.ones([1,gridsize], dtype = np.float64))
proper.prop_multiply(wfo, grating)
proper.prop_define_entrance(wfo)
proper.prop_propagate(wfo, PASSVALUE['dist'], TO_PLANE = True)
(wfo, sampling) = proper.prop_end(wfo, NOABS = True)
return (wfo, sampling)

Matlab:
function [wavefront, sampling] = talbot(wavelength, gridsize, optval)
talbot_length = 2.0 * optval.period^2 / wavelength;
wavefront = prop_begin(optval.diam, wavelength, gridsize);
% Create 1-D grating pattern
m = 0.2;
% pattern amplitude
x = ((1:gridsize)-fix(gridsize/2)-1)*prop_get_sampling(wavefront);
% Create 2-D amplitude grating pattern
[gx, gy] = meshgrid(x, x);
grating = 0.5 * (1.0 + m * cos(2.0*pi*gx/optval.period));
wavefront = prop_multiply(wavefront, grating);
wavefront = prop_define_entrance(wavefront);
wavefront = prop_propagate(wavefront, optval.dist, 'to_plane');
[wavefront, sampling] = prop_end(wavefront, 'noabs');
end

The demo program listed below, talbot_demo, will propagate the wavefront every 1/8th of the Talbot length for
one length and plot the amplitude and phase at each interval.

75

IDL:
pro talbot_demo
diam = 0.1
;-- beam diameter in meters
period = 0.04
;-- period of cosine pattern (meters)
wavelength_microns = 0.5d
wavelength_m = wavelength_microns * 1.0e-6
n = 128
nseg = 9
talbot_length = 2 * period^2 / wavelength_m
delta_length = talbot_length / (nseg – 1.0)
window, xsize=500, ysize=900
!p.multi = [0, 2, nseg]
;-- setup a 2 by ”nseg” panel of plots
z = 0.0d
for i = 1, nseg do begin
prop_run, 'talbot', wavefront, wavelength_microns, n, $
PASSVALUE={diam:diam, period:period, dist:z}
;-- extract a horizontal cross-section of array
wavefront = wavefront(*,n/2)
amp =
amp =
phase
phase

abs(wavefront)
amp - mean(amp)
= atan(wavefront, /phase)
= phase - mean(phase)

plot, amp, yran=[-0.0013,0.0013], xstyle=1, ystyle=1
plot, phase, yran=[-0.25,0.25], xstyle=1, ystyle=1
z = z + delta_length
endfor
!p.multi = 0
end

Python:
import matplotlib.pyplot as plt
def talbot_demo():
diam = 0.1
# beam diameter in meters
period = 0.04
# period of cosine pattern (meters)
wavelength_microns = 0.5
wavelength_m = wavelength_microns * 1.e-6
n = 128
nseg = 9
talbot_length = 2 * period**2 / wavelength_m
delta_length = talbot_length / (nseg - 1.)
z = 0.
plt.close('all')
f = plt.figure(figsize = (8, 18))
for i in range(nseg):
(wavefront, sampling) = proper.prop_run('talbot',
wavelength_microns, n,
PASSVALUE = {'diam': diam, 'period': period, 'dist': z})
# Extract central cross-section of array
wavefront = wavefront[:,n/2]

76

amp = np.abs(wavefront)
amp -= np.mean(amp)
phase = np.arctan2(wavefront.imag, wavefront.real)
phase -= np.mean(phase)
ax1 = f.add_subplot(nseg,2,2*i+1)
ax1.set_ylim(-0.0015, 0.0015)
ax1.plot(amp)
ax2 = f.add_subplot(nseg,2,2*i+2)
ax2.set_ylim(-0.25, 0.25)
ax2.plot(phase)
z += delta_length
f.tight_layout()
plt.show()
return
if __name__ == '__main__':
talbot_demo()

Matlab:
diam = 0.1;
% beam diameter (m)
n = 128;
% number of pixels
nseg = 9;
% number of segments
period = 0.04;
% period of cosine pattern (m)
z = 0.0;
% propagation distance (m)
wavelength_microns = 0.5;
wavelength_m = wavelength_microns * 1.0d-6;
talbot_length = 2.0 * period^2 / wavelength_m;
delta_length = talbot_length / (nseg - 1);
figarray(nseg, 2);
ifig = 0;
for i = 1 : nseg
ov = struct('diam', diam, 'dist', z, 'period', period);
wavefront = prop_run('talbot', wavelength_microns, n, 'PASSVALUE', ov);
% Extract a horizontal cross-section of array
wavefront = wavefront(fix(n / 2) + 1, :);
amp
amp
pha
pha

=
=
=
=

abs(wavefront);
amp - mean(amp);
phase(wavefront);
pha - mean(pha);

ifig = ifig + 1;
figplace(ifig);
clf;
axes('FontSize', 16);
plot(amp);
axis([1, n, -0.0013, 0.0013]);
set(get(gcf, 'CurrentAxes'), 'FontSize', 16);
ifig = ifig + 1;
figplace(ifig);
clf;
axes('FontSize', 16);
plot(pha);
axis([1, n, -0.25, 0.25]);
set(get(gcf, 'CurrentAxes'), 'FontSize', 16);
z
end

= z + delta_length;

77

The results are plotted to the screen. A touched-up versions of the plots are shown in the left panel of Figure 21. As
the plots show, the amplitude pattern becomes a phase pattern at ¼LT with a reduced amplitude pattern with half the
spatial period. At 1LT (and multiples thereof) it returns to its initial state and then repeats this process over again.

Amplitude

Phase

5/8 LT
7/8 LT
1 LT

3/4 LT

7/8 LT
1 LT

5/8 LT

1/2 LT

3/8 LT

1/4 LT

1/8 LT

0 LT

Phase

3/4 LT

1/2 LT

3/8 LT

1/4 LT

1/8 LT

0 LT

Amplitude

Figure 21. Cross-sectional plots of the amplitude and phase of a two-dimensional (x,y) wavefront
that initially has a amplitude pattern of spatial period P along the x direction. The wavefront is
propagated over various distances z, which are shown as fractions of the Talbot length (LT=2P2/λ)
from the initial position. (Left) The evolution of the wavefront over the specified propagation
distance. (Right) The same as the left panel, except that the phase is negated at ¼LT (shown before
negation at that position).
That a spatially-periodic amplitude pattern turns into a phase pattern at ¼LT introduces the possibility of correcting
amplitude wavefront errors with phase-modifying devices, such as deformable mirrors. A modification to talbot
can demonstrate this (creating talbot_correct). The call to PROP_PROPAGATE is replaced with the
following (in IDL, with something similar in Python or Matlab):
if ( optval.dist le 0.25*talbot_length ) then begin
prop_propagate, wavefront, optval.dist, /TO_PLANE
endif else begin
prop_propagate, wavefront, 0.25*talbot_length, /TO_PLANE
phase = prop_get_phase( wavefront )
;-- in radians
phase = phase * wavelength / (2 * !pi) ;-- in meters
prop_add_phase, wavefront, -phase
prop_propagate, wavefront, optval.dist-0.25*talbot_length, /TO_PLANE
endelse

78

talbot_demo is modified to call talbot_correct, creating talbot_correct_demo.
If the wavefront is being propagated past ¼LT, it is first propagated to there. The phase is extracted using
PROP_GET_PHASE(), converted from radians to meters, and the opposite phase added to the wavefront with
PROP_ADD_PHASE, effectively zeroing-out the phase pattern created by the evolution of the initial amplitude
pattern. The wavefront is then propagated the remaining distance. As shown in the right panel of Figure 21,
compensating for the phase at ¼LT negates the bulk of the amplitude pattern, leaving only the P/2 component, which
is at a much lower level than the initial pattern. Note that LT in this case is on the order of 6 x 1015 meters!

A Simple Microscope (and Objects at Finite Distances)
The example telescopes described previously assumed that the emitting source was at infinity or practically so (e.g. a
star), so that the light entering the optical systems was collimated. However, when an emitting point source is at a
finite distance from the system, it will create a spherically expanding wavefront, and the incoming light is no longer
collimated. The PROPER routines cannot explicitly simulate propagation beginning from a point source at a finite
distance because the first surface must be the entrance aperture of the system. There is a trick that will accomplish
the same thing. An expanding spherical wavefront emitted from a source located at some distance d prior to the
entrance aperture can be simulated by inserting a negative-power (concave) lens with a focal length of -d at the
aperture, creating the same phase variation.
An optical system in which the object is almost always at some close distance is a microscope. The simplest compound
(i.e. two or more lenses) microscope (Figure 22) has an objective lens that creates an intermediate, magnified image
of the object and an eyepiece (ocular) lens that further magnifies that image. The object is placed at distance dsource
prior to the objective lens of focal length fobj. The objective forms the intermediate image at a distance of fobj+L, where
L (known as the tube length) has been standardized to 16 cm by microscope makers. This image is located at the focus
of the ocular lens having a focal length of foc that ideally forms a nearly-collimated beam, with the exit pupil located
foc past the ocular. This is where the lens of the user’s eye (feye≈22 mm) is positioned, with the eye focused at infinity
to form the final image on the retina. In this simple system, the image is inverted.
A prescription for a simple compound microscope is given below. The objective (which is also the entrance aperture)
has a diameter of 5 mm and focal length of 10 mm. An eyepiece lens of focal length 20 mm is used. The distance the
object must be from the objective to form an in-focus image is computed using the Gaussian lens equation. The focus
may be changed by specifying an offset from this distance using the PASSVALUE keyword to PROP_RUN. The exit
pupil distance is determined from the lens equation as the location where the ocular forms an image of the objective.
This program produces the PSF of the point source object as seen with the eye (it ignores the effects of changes in the
indices of refraction).

79

feye

Eye

foc
Ocular
foc
Intermediate
image

Figure 22. Schematic layout of a simple
compound microscope.

L

fobj

Objective

dsource
Object

IDL:
pro microscope, wavefront, wavelength, gridsize, sampling, PASSVALUE=focus_offset
d_objective = 0.005d
fl_objective = 0.010d
fl_eyepiece = 0.020d
fl_eye = 0.022d

;-;-;-;--

objective diameter in meters
objective focal length in meters
eyepiece focal length
human eye focal length

beam_ratio = 0.4
prop_begin, wavefront, d_objective, wavelength, gridsize, beam_ratio
d1 = 0.160d
;-- standard tube length
d_intermediate_image = fl_objective + d1
;-- compute in-focus distance of object from objective
d_object = 1 / (1/fl_objective - 1/d_intermediate_image)
prop_circular_aperture, wavefront, d_objective/2
prop_define_entrance, wavefront
;-- simulate the diverging wavefront emitted from a point source placed
;-- "d_object" in front of the objective by using a negative lens (focal
;-- length = -d_object) placed at the location of the objective
if ( n_elements(focus_offset) eq 0 ) then focus_offset = 0.0
prop_lens, wavefront, -(d_object + focus_offset)
prop_lens, wavefront, fl_objective, 'objective'
prop_propagate, wavefront, d_intermediate_image, 'intermediate image'
prop_propagate, wavefront, fl_eyepiece, 'eyepiece'
prop_lens, wavefront, fl_eyepiece, 'eyepiece'
exit_pupil_distance = fl_eyepiece / (1 - fl_eyepiece/(d_intermediate_image+fl_eyepiece))

80

prop_propagate, wavefront, exit_pupil_distance, 'exit pupil/eye'
prop_lens, wavefront, fl_eye, 'eye'
prop_propagate, wavefront, fl_eye, 'retina'
prop_end, wavefront, sampling
return
end

Python:
import proper
def microscope(wavelength, gridsize, PASSVALUE = {'focus_offset': 0.}):
# Define entrance aperture diameter and other quantities
d_objective = 0.005
# objective diameter in meters
fl_objective = 0.010
# objective focal length in meters
fl_eyepiece = 0.020
# eyepiece focal length
fl_eye = 0.022
# human eye focal length
beam_ratio = 0.4
# Define the wavefront
wfo = proper.prop_begin(d_objective, wavelength, gridsize, beam_ratio)
d1 = 0.160
# standard tube length
d_intermediate_image = fl_objective + d1
# Compute in-focus distance of object from objective
d_object = 1 /(1/fl_objective - 1/d_intermediate_image)
# Define a circular aperture
proper.prop_circular_aperture(wfo, d_objective/2.)
# Define entrance
proper.prop_define_entrance(wfo)
# simulate the diverging wavefront emitted from a point source placed
# "d_object" in front of the objective by using a negative lens (focal
# length = -d_object) placed at the location of the objective
focus_offset = PASSVALUE['focus_offset']
# Define a lens
proper.prop_lens(wfo, -(d_object + focus_offset))
proper.prop_lens(wfo, fl_objective, "objective")
# Propagate the wavefront
proper.prop_propagate(wfo, d_intermediate_image, "intermediate image")
proper.prop_propagate(wfo, fl_eyepiece, "eyepiece")
proper.prop_lens(wfo, fl_eyepiece, "eyepiece")
exit_pupil_distance = fl_eyepiece / (1 - fl_eyepiece/(d_intermediate_image+fl_eyepiece))
proper.prop_propagate(wfo, exit_pupil_distance, "exit pupil/eye")
proper.prop_lens(wfo, fl_eye, "eye")
proper.prop_propagate(wfo, fl_eye, "retina")
# End
(wfo, sampling) = proper.prop_end(wfo)
return (wfo, sampling)

81

Matlab:
function [wavefront, sampling] = microscope(wavelength, gridsize, focus_offset)
d_objective = 0.005;
% objective diameter (m)
fl_objective = 0.01;
% focal length objective (m)
fl_eyepiece = 0.02;
% focal length eyepiece (m)
fl_eye = 0.022;
% focal length human eye (m)
beam_ratio = 0.4;
% initial beam width/grid width
d1 = 0.16;
% standard tube length (m)
d_intermediate_image = fl_objective + d1;
% intermediate image distance (m)
% Compute in-focus distance of object from objective
d_object = 1.0 / (1.0 / fl_objective - 1.0 / d_intermediate_image);
wavefront = prop_begin(d_objective, wavelength, gridsize, beam_ratio);
wavefront = prop_circular_aperture(wavefront, d_objective / 2.0);
wavefront = prop_define_entrance(wavefront);
% Simulate the diverging wavefront emitted from a point source placed
% "d_object" in front of the objective by using a negative lens (focal
% length = -d_object) placed at the location of the objective.
if (nargin < 3)
focus_offset = 0.0;
end

% focus offset (m)

wavefront = prop_lens(wavefront, -(d_object + focus_offset));
wavefront = prop_lens(wavefront, fl_objective, 'objective');
wavefront = prop_propagate(wavefront, d_intermediate_image, …
'surface_name', 'intermediate image');
wavefront = prop_propagate(wavefront, fl_eyepiece, ...
'surface_name', 'eyepiece');
wavefront = prop_lens(wavefront, fl_eyepiece, 'eyepiece');
exit_pupil_distance = fl_eyepiece / ...
(1.0 - fl_eyepiece / (d_intermediate_image + fl_eyepiece));
wavefront = prop_propagate(wavefront, exit_pupil_distance, ...
'surface_name', 'exit pupil/eye');
wavefront = prop_lens(wavefront, fl_eye, 'eye');
wavefront = prop_propagate(wavefront, fl_eye, 'surface_name', 'retina', 'to_plane');
[wavefront, sampling] = prop_end(wavefront);
end

82

A Stellar Coronagraph
When a telescope looks at a star, the star’s light is diffracted by the edges of the telescope aperture and any obscurations
that may be in the system (e.g. a secondary mirror). This creates the point spread function (PSF) that, if the optics are
good, has a sharp, narrow core and faint wings. If an astronomer is using the telescope to look for something faint
near that star, such as a planet, the light in the wings of the stellar PSF will overwhelm that from the planet. For
example, if one were to travel to the nearest star (4.3 light years away), and look back at our own solar system with a
telescope, Jupiter would be about 109 times fainter than the Sun and appear no further than 3.7 arcseconds from it.
Earth would be 1010 times fainter than the Sun and would be no further than 0.7 arcsec away. With a 2.4 meter
diameter telescope (circular, no obscurations, perfect optics) and looking at visible wavelengths (λ = 0.5 μm), the
signal from Jupiter would be about 150 times fainter than the wings of the PSF at the same location. With real optics
that have figuring and polishing errors that scatter light, Jupiter would be about 2000 times fainter than the PSF wings.
A method is needed to get rid of the light from the star, allowing the planet to be seen.
Simply blocking the light from the star with some sort of occulter will not solve the problem – the PSF wings will
remain unsuppressed (it does help reduce scatter from optical surfaces after the occulter, though). In 1939, Bernard
Lyot devised a solution that makes use of the wave-like nature of light to suppress the wings (his goal was to observe
the corona of the Sun rather than stars outside of the Solar System). The image of the star is first focused onto an
occulter of some sort (e.g. a small disk), sized so that it will not block the image of the surrounding object of interest
(e.g. a planet or circumstellar dust disk). The light from the PSF wings then passes to a lens or mirror that creates a
collimated beam, forming an image of the entrance pupil in the Lyot plane. Because of the wave-like nature of light,
the occulter acts as a spatial filter and concentrates the residual flux into the regions of the pupil corresponding to the
diffracting edges in the entrance pupil. These portions of the pupil are blocked with a mask (the Lyot stop), removing
most of the remaining light from the star. Sources beyond the radius of the occulter are not filtered and pass through
the system essentially unaffected (except for attenuation by the Lyot stop). Another lens or mirror recoverges the
light, creating an image in the final focal plane. A simple schematic of such a coronagraph is shown in Figure 23.
The effect of the coronagraph can be understood if one views the pupil and focal planes as Fourier transforms of each
other, so that the amplitude distribution in the focal plane (the PSF) represents the frequency spectrum of the pupil.
Blocking the central portion of the PSF with an occulter is thus equivalent to removing the low-spatial-frequency
components of the pupil, leaving only the high ones (the edges). This explains the image at the Lyot stop. Note that
as the size of the occulter increases, higher spatial frequencies in the pupil are suppressed, causing the regions around
the edges in the Lyot plane to become narrower. This allows a more “open” or “less aggressive” Lyot stop to be used,
increasing throughput for field sources (e.g. planets).
The narrowness of the residual light regions in the Lyot plane is also dependent on the shape of the occulter. A hardedge spot will not concentrate the light around the edges very well (it essentially diffracts light in the image plane into
the pupil). Occulters with graded transmissions, such as a Gaussian spot, work much better. Recent studies (Kuchner
and Traub, Astrophysical Journal, 570, 900 (2002)) have invented “band-limited” occulters that theoretically
concentrate all of the remaining light around the edges, providing full suppression of the stellar light (one of these
occulters is implemented with PROP_8TH_ORDER_MASK).
A coronagraph only suppresses the diffraction pattern created by the edges in a system – it does nothing to the scattered
light created by surface errors, which must be corrected using wavefront control techniques.

83

Figure 23. Schematic layout of a Lyot stellar coronagraph. For simplicity, all lenses have the
same focal length, f. Collimated light (e.g. from a star) enters from the left. The image at the
first focus is shown (square-root intensity stretch) multiplied by a circular, 8 th-order occulter
(shown here with 50% intensity transmission at r=8λ/D). The corresponding image at the Lyot
plane, prior to masking by the Lyot stop, is also shown (I0.2 intensity stretch). The Lyot stop
would only pass the central dark region. The distance from the pupil reimaging lens (lens 2)
to the Lyot stop is sometimes set to f rather than 2f. The difference is not usually significant,
though 2f produces a true image of the entrance pupil.

A Simple Coronagraph with Selectable Occulters
Coronagraphs can be easily modeled using PROPER routines (in fact that is why the PROPER library was created).
As an example, we can start off with the simple coronagraph shown in Figure 23. Because the coronagraph will be a
part of a more complete optical system in a later example, it is defined as a separate function that will be called from
a prescription routine. In coronagraph.pro listed below, different occulters can be used: a solid spot, an apodized
Gaussian spot, and an 8th-order band-limited apodized occulter. Each has a matching Lyot stop that is appropriately
sized and shaped. The occulter type is selected by assigning the parameter occulter_type to a string (‘SOLID’,
‘GAUSSIAN’, or ‘8TH_ORDER’). The occulter 50% intensity transmission radius (or solid spot radius) is set here
to be 4λ/D radians (D = entrance aperture diameter). The routine will display the wavefront amplitude immediately
after the occulter and then just before the Lyot stop.
IDL:
pro coronagraph, wavefront, fl_lens, occulter_type, diam
prop_lens, wavefront, fl_lens, 'coronagraph imaging lens'
prop_propagate, wavefront, fl_lens, 'occulter'
;-- occulter sizes are specified here in units of lambda/diameter;
;-- convert lambda/diam to radians then to meters
lambda = prop_get_wavelength( wavefront )
occrad = 4.0
;-- occulter radius in lam/D
occrad_rad = occrad * lambda / diam
;-- occulter radius in radians
dx_m = prop_get_sampling(wavefront)
dx_rad = prop_get_sampling_radians(wavefront)
occrad_m = occrad_rad * dx_m / dx_rad
;-- occulter radius in meters

84

case occulter_type of
'8TH_ORDER' : prop_8th_order_mask, wavefront, occrad, /CIRCULAR
'GAUSSIAN' : begin
r = prop_radius( wavefront )
h = sqrt(-0.5 * occrad_m^2 / alog(1 - sqrt(0.5)))
gauss_spot = 1 - exp(-0.5*(r/h)^2)
prop_multiply, wavefront, gauss_spot
end
'SOLID' : prop_circular_obscuration, wavefront, occrad_m
endcase
tvscl, sqrt(prop_get_amplitude(wavefront))
xyouts, 256, 10, 'After Occulter', CHARSIZE=2, ALIGN=0.5, /DEVICE
prop_propagate, wavefront, fl_lens, 'pupil reimaging lens'
prop_lens, wavefront, fl_lens, 'pupil reimaging lens'
prop_propagate, wavefront, 2*fl_lens, 'lyot stop'
tvscl, prop_get_amplitude(wavefront)^0.2, 512, 0
xyouts, 768, 10, 'Before Lyot Stop', CHARSIZE=2, ALIGN=0.5, /DEVICE
case occulter_type of
'8TH_ORDER' : prop_circular_aperture, wavefront, 0.50, /NORM
'GAUSSIAN' : prop_circular_aperture, wavefront, 0.25, /NORM
'SOLID' : prop_circular_aperture, wavefront, 0.84, /NORM
endcase
prop_propagate, wavefront, fl_lens, 'reimaging lens'
prop_lens, wavefront, fl_lens, 'reimaging lens'
prop_propagate, wavefront, fl_lens, 'final focus'
return
end

The coronagraph procedure is called from the prescription run_occulter:
pro run_occulter, wavefront, wavelength, grid_size, sampling, PASSVALUE=optval
diam = 0.1d
;-- telescope diameter in meters
fl_lens = 24 * diam
beam_ratio = 0.3
prop_begin, wavefront, diam, wavelength, grid_size, beam_ratio
prop_circular_aperture, wavefront, diam/2
prop_define_entrance, wavefront
coronagraph, wavefront, fl_lens, optval.occulter_type, diam
prop_end, wavefront, sampling
return
end

Python:
import proper
import numpy as np
import matplotlib.pylab as plt
def coronagraph(wfo, f_lens, occulter_type, diam):
proper.prop_lens(wfo, f_lens, "coronagraph imaging lens")
proper.prop_propagate(wfo, f_lens, "occulter")

85

# occulter sizes are specified here in units of lambda/diameter;
# convert lambda/diam to radians then to meters
lamda = proper.prop_get_wavelength(wfo)
occrad = 4.
# occulter radius in lam/D
occrad_rad = occrad * lamda / diam
# occulter radius in radians
dx_m = proper.prop_get_sampling(wfo)
dx_rad = proper.prop_get_sampling_radians(wfo)
occrad_m = occrad_rad * dx_m / dx_rad # occulter radius in meters
plt.figure(figsize=(12,8))
if occulter_type == "GAUSSIAN":
r = proper.prop_radius(wfo)
h = np.sqrt(-0.5 * occrad_m**2 / np.log(1 - np.sqrt(0.5)))
gauss_spot = 1 - np.exp(-0.5 * (r/h)**2)
proper.prop_multiply(wfo, gauss_spot)
plt.suptitle("Gaussian spot", fontsize = 18)
elif occulter_type == "SOLID":
proper.prop_circular_obscuration(wfo, occrad_m)
plt.suptitle("Solid spot", fontsize = 18)
elif occulter_type == "8TH_ORDER":
proper.prop_8th_order_mask(wfo, occrad, CIRCULAR = True)
plt.suptitle("8th order band limited spot", fontsize = 18)
# After occulter
plt.subplot(1,2,1)
plt.imshow(np.sqrt(proper.prop_get_amplitude(wfo)), origin = "lower", cmap = plt.cm.gray)
plt.text(200, 10, "After Occulter", color = "w")
proper.prop_propagate(wfo, f_lens, "pupil reimaging lens")
proper.prop_lens(wfo, f_lens, "pupil reimaging lens")
proper.prop_propagate(wfo, 2*f_lens, "lyot stop")
plt.subplot(1,2,2)
plt.imshow(proper.prop_get_amplitude(wfo)**0.2, origin = "lower", cmap = plt.cm.gray)
plt.text(200, 10, "Before Lyot Stop", color = "w")
plt.show()
if occulter_type == "GAUSSIAN":
proper.prop_circular_aperture(wfo, 0.25, NORM = True)
elif occulter_type == "SOLID":
proper.prop_circular_aperture(wfo, 0.84, NORM = True)
elif occulter_type == "8TH_ORDER":
proper.prop_circular_aperture(wfo, 0.50, NORM = True)
proper.prop_propagate(wfo, f_lens, "reimaging lens")
proper.prop_lens(wfo, f_lens, "reimaging lens")
proper.prop_propagate(wfo, f_lens, "final focus")
return

The coronagraph procedure is called from the prescription run_occulter:
import proper
from coronagraph import coronagraph
def run_occulter(wavelength, grid_size, PASSVALUE = {'occulter_type': 'GAUSSIAN'}):
diam = 0.1
f_lens = 24 * diam
beam_ratio = 0.3

# telescope diameter in meters

wfo = proper.prop_begin(diam, wavelength, grid_size, beam_ratio)
proper.prop_circular_aperture(wfo, diam/2)
proper.prop_define_entrance(wfo)

86

coronagraph(wfo, f_lens, PASSVALUE["occulter_type"], diam)
(wfo, sampling) = proper.prop_end(wfo)
return (wfo, sampling)

Matlab:
function wavefront = coronagraph(wavefront, f_lens, occulter_type, diam)
global ifig;
% index of figure
wavefront
wavefront

= prop_lens(wavefront, f_lens, 'coronagraph imaging lens');
= prop_propagate(wavefront, f_lens, 'snm', 'occulter');

% Occulter sizes are specified here in units of lambda / diameter.
% Convert lambda / diameter to radians, then to meters.
lambda = prop_get_wavelength(wavefront);
occrad = 4.0;
occrad_rad = occrad * lambda / diam;
dx_m = prop_get_sampling(wavefront);
dx_rad = prop_get_sampling_radians(wavefront);
occrad_m = occrad_rad * dx_m / dx_rad;

%
%
%
%
%
%

wavelength (m)
occulter radius in lambda / diam
occulter radius (radians)
pixel spacing (m)
pixel spacing (radians)
occulter radius (m)

switch occulter_type
case '8TH_ORDER'
wavefront = prop_8th_order_mask(wavefront, occrad, ...
'tmin', 0.0d0, 'tmax', 1.0d0, 'circ');
case 'GAUSSIAN'
r
= prop_radius(wavefront);
h
= sqrt(-0.5d0 * occrad_m^2 / log(1.0d0 - sqrt(0.5d0)));
gauss_spot = 1.0d0 - exp(-0.5d0 * (r / h).^2);
wavefront = prop_multiply(wavefront, gauss_spot);
case 'SOLID'
wavefront = prop_circular_obscuration(wavefront, occrad_m);
end
ifig = ifig + 1;
figure(ifig);
clf;
axes('FontSize', 16);
imagesc((prop_get_amplitude(wavefront)).^0.5);
axis equal;
% x-axis units = y-axis units
axis tight;
% set axis limits to range of data
axis xy;
% set y-axis to increase from bottom
hc = colorbar('vert');
set(hc, 'FontSize', 16);
colormap(gray);
set(get(gcf, 'CurrentAxes'), 'FontSize', 16);
tit1 = sprintf('After Occulter');
title(tit1, 'FontSize', 16);
wavefront
wavefront

= prop_propagate(wavefront, f_lens, 'snm', 'pupil reimaging lens');
= prop_lens(wavefront, f_lens, 'pupil reimaging lens');

wavefront

= prop_propagate(wavefront, f_lens * 2.0, 'snm', 'lyot stop');

ifig = ifig + 1;
figure(ifig);
clf;
axes('FontSize', 16);
imagesc((prop_get_amplitude(wavefront)).^0.2);
axis equal;
% x-axis units = y-axis units
axis tight;
% set axis limits to range of data
axis xy;
% set y-axis to increase from bottom
hc = colorbar('vert');
set(hc, 'FontSize', 16);
colormap(gray);
set(get(gcf, 'CurrentAxes'), 'FontSize', 16);

87

tit2 = sprintf('Before Lyot Stop');
title(tit2, 'FontSize', 16);
switch occulter_type
case '8TH_ORDER'
wavefront = prop_circular_aperture(wavefront, 0.5, 'norm');
case 'GAUSSIAN'
wavefront = prop_circular_aperture(wavefront, 0.25, 'norm');
case 'SOLID'
wavefront = prop_circular_aperture(wavefront, 0.84, 'norm');
end
wavefront
wavefront

= prop_propagate(wavefront, f_lens, 'snm', 'reimaging lens');
= prop_lens(wavefront, f_lens, 'reimaging lens');

wavefront

= prop_propagate(wavefront, f_lens, 'snm', 'final focus');

end

The coronagraph procedure is called from the prescription run_occulter:
function [wavefront, sampling] = run_occulter(wavelength, grid_size, optval)
beam_ratio = 0.3;
% beam diameter fraction
diam = 0.1;
% telescope diameter (m)
f_lens = 24.0 * diam;
% focal length (m)
wavefront

= prop_begin(diam, wavelength, grid_size, beam_ratio);

wavefront
wavefront

= prop_circular_aperture(wavefront, diam / 2.0d);
= prop_define_entrance(wavefront);

wavefront

= coronagraph(wavefront, f_lens, optval.occulter_type, diam);

[wavefront, sampling] = prop_end(wavefront);
end

In this contrived example, the entrance aperture is 10 cm wide and all of the lenses have a focal ratio of f/24. The
beam ratio is set to 0.3, which creates an oversampled image at the focus. This allows better sampling of the occulter.
The occulter type is defined by setting the occulter_type member of a structure that is passed to PROP_RUN using
the PASSVALUE keyword. The type is a string (‘SOLID’, ‘GAUSSIAN’, or ‘8TH_ORDER’). A structure is used
because additional parameters will be added in later examples. An example run would be:
IDL:

prop_run, ’run_occulter’, psf, 0.6, 512, PASSVALUE={occulter_type:’GAUSSIAN’}

Python:

(psf, sampling) = proper.prop_run( ’run_occulter’, 0.6, 512,
PASSVALUE={"occulter_type":"GAUSSIAN"} )

Matlab:

param.type = 'GAUSSIAN';
[psf, sampling] = prop_run( ’run_occulter’, 0.6, 512, param );

which uses a Gaussian occulter and generates an image for λ=0.6 μm with a grid size of 512 x 512.
We can now use these two routines to show how different occulters modify the wavefront as seen in the Lyot plane.
The routine below, occulter_demo, calls the coronagraph routine using each occulter type. The occulted image
and the amplitude in the Lyot plane are displayed for each. This routine is executed by typing “occulter_demo”
on the command line. The results are shown in Figure 24.

88

IDL:
pro occulter_demo
n = 512
lambda = 0.55

;-- grid size
;-- wavelength (microns)

window, 0, XSIZE=n*2, YSIZE=n, TITLE='Solid spot'
prop_run, 'run_occulter', solid, lambda, n, $
PASSVALUE={occulter_type:'SOLID'}
window, 1, XSIZE=n*2, YSIZE=n, TITLE='Gaussian spot'
prop_run, 'run_occulter', gaussian, lambda, n, $
PASSVALUE={occulter_type:'GAUSSIAN'}
window, 2, XSIZE=n*2, YSIZE=n, TITLE='8th order band limited spot'
prop_run, 'run_occulter', eighth_order, lambda, n, $
PASSVALUE={occulter_type:'8TH_ORDER'}
end

Python:
import proper
def occulter_demo():
n = 512
lamda = 0.55

# grid size
# wavelength (microns)

(solid, sampl_solid) = proper.prop_run('run_occulter', lamda, n,
PASSVALUE = {"occulter_type": "SOLID"})
(gaussian, sampl_gauss) = proper.prop_run('run_occulter', lamda, n,
PASSVALUE = {"occulter_type": "GAUSSIAN"})
(eighth_order, sampl_eighth_order) = proper.prop_run('run_occulter', lamda, n,
PASSVALUE = {"occulter_type": "8TH_ORDER"})
return
if __name__ == '__main__':
occulter_demo()

Matlab:
global ifig;
ifig = 0;
n = 512;
lambda = 0.550;

% index of figure
% number of pixels
% wavelength (um)

optval.occulter_type = 'SOLID';
% solid occulter
solid = prop_run('run_occulter', lambda, n, 'passvalue', optval);
optval.occulter_type = 'GAUSSIAN';
% gaussian occulter
gaussian = prop_run('run_occulter', lambda, n, 'passvalue', optval);
optval.occulter_type = '8TH_ORDER';
% prop_8th_order_mask
eighth_order = prop_run('run_occulter', lambda, n, 'passvalue', optval);

89

Figure 24. Output from running occulter_demo. The solid occulter does a relatively poor job of
concentrating light around the edge of the pupil in the Lyot plane, where it could be masked by the Lyot
stop. The Gaussian and 8th-order occulters do better. The inner edge of the pupil “doughnut” is quite
sharp for the 8th-order, and it allows for a more open (higher throughput) Lyot stop than the Gaussian
(which declines less rapidly toward the center). Both the Gaussian and 8th-order occulters provide for
greater light suppression than the solid spot. The horizontal and vertical patterns are caused by the
limitations of using a finite rectangular grid with Fourier-based propagators (they are at a relatively low
level, and the images have been strongly stretched to show such details).

90

A Simple Coronagraph with a Telescope Having Optical Surface Errors
A real coronagraph would be attached to a telescope that has optical surface errors that are unavoidable during their
manufacture. Usually, the bulk of these errors are in the objective lens (or primary mirror), especially mid-spatialfrequency errors that scatter light to large angles from the source. A coronagraph will not suppress this scattered light.
To investigate the effect of these errors, we expand on our example coronagraph, adding a telescope that will function
as the front end of the system. Like the coronagraph, the telescope will be a separate procedure (telescope) called
from the prescription. The telescope objective will be given some mid-spatial-frequency aberrations using
PROP_PSD_ERRORMAP if the telescope routine’s parameter use_errors is non-zero. The routine propagates the
wavefront from the objective, through focus, and then to a pupil imaging lens, which collimates the beam. The
wavefront is then propagated to where a deformable mirror will be inserted later, and then to the entrance of the
coronagraph.
IDL:
pro telescope, wavefront, fl_lens, use_errors
if ( use_errors ) then begin
rms_error = 10.0e-9
;-- RMS wavefront error in meters
c_freq = 15.0
;-- correlation frequency (cycles/meter)
high_power = 3.0
;-- high frequency falloff (r^-high_power)
prop_psd_errormap, wavefront, 10.0e-9, c_freq, high_power, $
/RMS, MAP=obj_map, FILE='telescope_obj.fits'
endif
prop_lens, wavefront, f_lens, 'objective'
;-- propagate through focus to the pupil
prop_propagate, wavefront, fl_lens*2, 'telescope pupil imaging lens'
prop_lens, wavefront, fl_lens, 'telescope pupil imaging lens'
;-- propagate to a deformable mirror (to be inserted later)
prop_propagate, wavefront, fl_lens, 'DM'
prop_propagate, wavefront, fl_lens, 'coronagraph lens'
return
end

Python:
import proper
def telescope(wfo, f_lens, use_errors, use_dm = False):
if use_errors:
rms_error = 10.e-9
c_freq = 15.
high_power = 3.

# RMS wavefront error in meters
# correlation frequency (cycles/meter)
# high frewquency falloff (r^-high_power)

proper.prop_psd_errormap(wfo, rms_error, c_freq, high_power, RMS = True,
MAP = "obj_map", FILE = "telescope_obj.fits")
proper.prop_lens(wfo, f_lens, "objective")
# propagate through focus to pupil
proper.prop_propagate(wfo, f_lens*2, "telescope pupil imaging lens")
proper.prop_lens(wfo, f_lens, "telescope pupil imaging lens")
# propagate to a deformable mirror (to be inserted later)
proper.prop_propagate(wfo, f_lens, "DM")
proper.prop_propagate(wfo, f_lens, "coronagraph lens")
return

91

Matlab:
function wavefront = telescope(wavefront, fl_lens, use_errors)
if use_errors == 1
rms_error = 10.0d-09;
% RMS wavefront error
c_freq = 15.0;
% correlation frequency (cycles / m)
high_power = 3.0; % high frequency falloff
flnm = 'telescope_obj.fits';
[wavefront, obj_map] = prop_psd_errormap(wavefront, rms_error, ...
c_freq, high_power, 'file', flnm, 'rms');
end
wavefront = prop_lens(wavefront, fl_lens, 'objective');
% Propagate through focus to the pupil
wavefront = prop_propagate(wavefront, fl_lens * 2.0, ...
'snm', 'telescope pupil imaging lens');
wavefront = prop_lens(wavefront, fl_lens, 'telescope pupil imaging lens');
% Propagate to a deformable mirror (no actual DM here)
wavefront = prop_propagate(wavefront, fl_lens, 'snm', 'DM');
wavefront = prop_propagate(wavefront, fl_lens, 'snm', 'coronagraph lens');
end

We can integrate the telescope and the coronagraph by adding the following line to run_occulter just before the
call to the routine coronagraph, naming the new routine run_coronagraph:
IDL:

telescope, wavefront, fl_lens, optval.use_errors

Python:

telescope(wfo, fl_lens, PASSVALUE['use_errors'])

Matlab:

wavefront = telescope(wavefront, fl_lens, optval.use_errors);

Now, when PROP_RUN is called, the use_errors member of the structure assigned to the PASSVALUE keyword
must also be set, like so:
IDL:

prop_run, ’run_coronagraph’, psf, 0.55, 512,
PASSVALUE={occulter_type:’GAUSSIAN’,use_errors:1}

Python:

(psf, dx) = prop_run( ’run_coronagraph’, 0.55, 512,
PASSVALUE={"occulter_type":’GAUSSIAN’,"use_errors":1} )

Matlab:

optval.type = 'GAUSSIAN';
optval.use_errors = 1;
[psf, dx] = prop_run( ’run_coronagraph’, 0.55, 512, 'PASSVALUE', optval );

Results of this run are shown in Figure 25 and Figure 26. The mid-frequency errors create speckles of scattered light.
Over a broad passband, the PSF expands with wavelength and the speckles will smear into streaks.

92

Figure 25. Results displayed by run_coronagraph when surface errors in the
telescope objective are included. The errors scatter light that cannot be
suppressed by the coronagraph. This scattered light can be seen within the center
of the Lyot plane image.

Figure 26. Occulted star image for a system with wavefront
errors, corresponding to the case presented in Figure 25. At
this stretch (I1/4), nothing would be seen if the system had no
errors.

A Simple Coronagraph: Wavefront Correction with a Deformable Mirror
We can see from the previous example that the scattered light background created by wavefront errors reduces the
effectiveness of the coronagraph. In that example the wavefront had only phase errors introduced at the objective, but
it would also be possible to have amplitude errors caused by non-uniform coatings or glass impurities that would also
scatter light. Wavefront phase errors can be reduced by placing a deformable mirror (DM) at a pupil prior to the
occulter. Just such a place was defined in the telescope routine. Amplitude errors can also be reduced by a DM
to some degree, but we will not discuss that here. A DM can only correct errors over a limited range of spatial
frequencies, up to Nact/2 cycles over the beam diameter where Nact is the number of actuators across the beam.
In the real world, the greatest difficulty with the use of a DM to correct aberrations is measuring those errors. There
are a wide variety of techniques to do so, each with its pros and cons. In the following example, however, to avoid
the complexity introduced by wavefront sensing, we shall simply cheat and make use of the error map created by
PROP_PSD_ERRORMAP. That map is in meters of wavefront error. The DM surface is thus set to be half that

93

(because reflection doubles the path length) and with the opposite sign. The map must also be rotated 180º because
the beam went through focus prior to the DM (after rotation using IDL’s ROTATE() function, the map must be shifted
1 pixel in each direction to return its center in the same place). A 49 by 49 element DM with 47 elements spanning
the beam diameter is used. The map must be projected onto this reduced number of samples, and that is done with
PROP_MAGNIFY. The resized map is then passed to PROP_DM with the /FIT switch set. This tells PROP_DM
that the map is the requested surface height at each actuator rather than the height of the actuator. The map will be fit
to include the effects of the actuator influence function in order to determine the actuator heights necessary to obtain
the required surface.
The example code below is inserted in telescope just after the propagation to the DM. An additional parameter,
use_dm, must be added to the end of the parameter definition of telescope. The modified routine is now called
telescope_dm, and a new version of run_coronagraph, called run_coronagraph_dm, is created that calls
it instead of telescope. When use_dm is not zero, the wavefront will be compensated by the deformable mirror.
IDL:
if ( use_dm and use_errors ) then begin
nact = 49
;-- number of DM actuators along one axis
nact_across_pupil = 47 ;-- number of DM actuators across pupil
dm_xc = nact / 2
;-- actuator at wavefront center
dm_yc = nact / 2
d_beam = 2 * prop_get_beamradius( wavefront )
;-- beam diameter
act_spacing = d_beam / nact_across_pupil
;-- actuator spacing
map_spacing = prop_get_sampling( wavefront ) ;-- map sampling
;-- have passed through focus, so pupil has rotated 180 deg;
;-- need to rotate error map (also need to shift due to the way
;-- the rotate() function operates to recenter map)
obj_map = shift(rotate(obj_map,2),1,1)
;-- interpolate map to match number of DM actuators
dm_map = prop_magnify( obj_map, map_spacing/act_spacing, nact )
;-- Need to put on opposite pattern;
;-- convert wavefront error to surface height
prop_dm, wavefront, -dm_map/2, dm_xc, dm_yc, act_spacing, /FIT
endif

Python:
if use_dm:
nact = 49
# number of DM actuators along one axis
nact_across_pupil = 47
# number of DM actuators across pupil
dm_xc = nact / 2
dm_yc = nact / 2
d_beam = 2 * proper.prop_get_beamradius(wfo)
# beam diameter
act_spacing = d_beam / nact_across_pupil
# actuator spacing
map_spacing = proper.prop_get_sampling(wfo)
# map sampling
# have passed through focus, so pupil has rotated 180 deg;
# need to rotate error map (also need to shift due to the way
# the rotate() function operates to recenter map)
obj_map = np.roll(np.roll(np.rot90(obj_map, 2), 1, 0), 1, 1)
# interpolate map to match number of DM actuators
dm_map = proper.prop_magnify(obj_map, map_spacing/act_spacing, nact, QUICK = True)
# Need to put on opposite pattern; convert wavefront error to surface height
proper.prop_dm(wfo, -dm_map/2, dm_xc, dm_yc, act_spacing, FIT = True)

94

Matlab:
if use_dm == 1
nact = 49;
% number of DM actuators along one axis
nact_across_pupil = 47;
% number of DM actuators across pupil
dm_xc = fix(nact / 2);
% actuator X index at wavefront center
dm_yc = fix(nact / 2);
% actuator Y index at wavefront center
d_beam = 2.0 * prop_get_beamradius(wavefront);
% beam diameter
act_spacing = d_beam / nact_across_pupil;
% actuator spacing
map_spacing = prop_get_sampling(wavefront);
% map sampling
% Have passed through focus, so pupil has rotated 180 degrees;
% need to rotate error map (also need to shift due to the way
% the rotate function operates to recenter map)
obj_map = circshift(rot90(obj_map, 2), [1, 1]);
% Interpolate map to match number of DM actuators
dm_map = prop_magnify(obj_map, map_spacing / act_spacing, 'size_out', nact);
% Need to put on opposite pattern and convert wavefront error to surface height
wavefront = prop_dm(wavefront, -dm_map / 2.0, dm_xc, dm_yc, act_spacing, 'fit');
end

The procedure coronagraph_demo, listed below, demonstrates this new functionality to our example coronagraph.
IDL:
To run it, simply type “coronagraph_demo” on the command line.
pro coronagraph_demo
n = 512
lambda = 0.55

;-- grid size
;-- wavelength (microns)

window, 0, xsize=n*2, ysize=n, title='no errors'
prop_run, 'run_coronagraph_dm', no_errors, lambda, n, $
PASSVALUE={use_errors:0,use_dm:0,occulter_type:'8TH_ORDER'}
window, 1, xsize=n*2, ysize=n, title='with errors, no DM'
prop_run, 'run_coronagraph_dm', with_errors, lambda, n, $
PASSVALUE={use_errors:1,use_dm:0,occulter_type:'8TH_ORDER'}
window, 2, xsize=n*2, ysize=n, title='with errors, DM correction'
prop_run, 'run_coronagraph_dm', with_dm, lambda, n, $
PASSVALUE={use_errors:1,use_dm:1,occulter_type:'8TH_ORDER'}
np = 256
psfs = fltarr(np*3,np)
psfs(0,0) = no_errors(n/2-np/2:n/2-1,n/2-np/2:n/2-1)
psfs(np,0) = with_errors(n/2-np/2:n/2-1,n/2-np/2:n/2-1)
psfs(np*2,0) = with_dm(n/2-np/2:n/2-1,n/2-np/2:n/2-1)
window, 3, xsize=np*3, ysize=np, title='PSFs'
tvscl, psfs^0.25
xyouts, 0.5*np, 10, 'No errors', /dev, align=0.5, size=2
xyouts, 1.5*np, 10, 'With errors', /dev, align=0.5, size=2
xyouts, 2.5*np, 10, 'DM corrected', /dev, align=0.5, size=2
print,
print,
print,
print,

'Maximum speckle flux / stellar flux :'
'
No wavefront errors
= ', max(no_errors)
'
With wavefront errors = ', max(with_errors)
'
With DM correction
= ', max(with_dm)

end

95

Python:
To run it, simply type “coronagraph_demo” on the command line.
import proper
import numpy as np
import matplotlib.pylab as plt
def coronagraph_demo():
n = 256
lamda = 0.55

# grid size
# wavelength (microns)

no_errors, no_errors_sampl = proper.prop_run("run_coronagraph_dm", lamda, n,
PASSVALUE = {'use_errors': False, 'use_dm': False, 'occulter_type': '8TH_ORDER'},
VERBOSE = False)
with_errors, with_errors_sampl = proper.prop_run("run_coronagraph_dm", lamda, n,
PASSVALUE = {'use_errors': True, 'use_dm': False, 'occulter_type': '8TH_ORDER'},
VERBOSE = False)
with_dm, with_dm_sampl = proper.prop_run("run_coronagraph_dm", lamda, n,
PASSVALUE = {'use_errors': True, 'use_dm': True, 'occulter_type': '8TH_ORDER'},
VERBOSE = False)
nd = 256
psfs = np.zeros([3,nd,nd], dtype = np.float64)
psfs[0,:,:] = no_errors[n/2-nd/2:n/2+nd/2,n/2-nd/2:n/2+nd/2]
psfs[1,:,:] = with_errors[n/2-nd/2:n/2+nd/2,n/2-nd/2:n/2+nd/2]
psfs[2,:,:] = with_dm[n/2-nd/2:n/2+nd/2,n/2-nd/2:n/2+nd/2]
plt.figure(figsize = (14,7))
plt.suptitle("PSFs", fontsize = 18, fontweight = 'bold')
plt.subplot(1,3,1)
plt.imshow(psfs[0,:,:]**0.25, origin = "lower", cmap = plt.cm.gray)
plt.subplot(1,3,2)
plt.imshow(psfs[1,:,:]**0.25, origin = "lower", cmap = plt.cm.gray)
plt.subplot(1,3,3)
plt.imshow(psfs[2,:,:]**0.25, origin = "lower", cmap = plt.cm.gray)
plt.show()
print
print
print
print

"Maximum speckle flux / stellar flux :"
" No wavefront errors = ", np.max(no_errors), np.min(no_errors)
" With wavefront errors = ", np.max(with_errors)
" With DM correction = ", np.max(with_dm)

if __name__ == '__main__':
coronagraph_demo()

Matlab:
Here is coronagraph_demo:
global ifig;
ifig = 0;

% figure number
% figure number

n = 512;
nps = 256;
nps2 = fix(nps / 2.0);
icx = fix(n
/ 2.0) + 1;
icy = fix(n
/ 2.0) + 1;
ix1 = icx - nps2;
ix2 = ix1 + nps - 1;
iy1 = icy - nps2;
iy2 = iy1 + nps - 1;

% grid size
% number of pixels in psf sample

96

%
%
%
%

psf
psf
psf
psf

% index of center of array X
% index of center of array Y
sample min index X
sample max index X
sample min index Y
sample max index Y

lambda = 0.55;

% wavelength (um)

figarray(4, 4);

% set up positions of figures in array

optval.use_dm = 0;
optval.use_errors = 0;
optval.occulter_type = '8TH_ORDER';
psf1 = prop_run('run_coronagraph_dm',
psfs = psf1(iy1:iy2, ix1:ix2);

% deformable mirror: no
% prop_psd_errormap: no
% prop_8th_order_mask: yes
lambda, n, 'prm', optval);

% Plot Point Spread Function intensity for case with no errors
ifig = ifig + 1;
figplace(ifig);
clf;
axes('FontSize', 16);
imagesc(psfs.^0.25);
axis equal;
% x-axis units = y-axis units
axis tight;
% set axis limits to range of data
axis xy;
% set y-axis to increase from bottom
hc = colorbar('vert');
set(hc, 'FontSize', 16);
caxis([0.0, 0.03]);
colormap(gray);
set(get(gcf, 'CurrentAxes'), 'FontSize', 16);
tit1 = sprintf('PSF: no errors');
title(tit1, 'FontSize', 16);
optval.use_dm = 0;
optval.use_errors = 1;
optval.occulter_type = '8TH_ORDER';
psf2 = prop_run('run_coronagraph_dm',
psfs = psf2(iy1:iy2, ix1:ix2);

% deformable mirror: no
% prop_psd_errormap: yes
% prop_8th_order_mask: yes
lambda, n, 'prm', optval);

% Plot Point Spread Function intensity for case with errors, no DM
ifig = ifig + 1;
figplace(ifig);
clf;
axes('FontSize', 16);
imagesc(psfs.^0.25);
axis equal;
% x-axis units = y-axis units
axis tight;
% set axis limits to range of data
axis xy;
% set y-axis to increase from bottom
hc = colorbar('vert');
set(hc, 'FontSize', 16);
caxis([0.0, 0.03]);
colormap(gray);
set(get(gcf, 'CurrentAxes'), 'FontSize', 16);
tit2 = sprintf('PSF: with errors');
title(tit2, 'FontSize', 16);
optval.use_dm = 1;
optval.use_errors = 1;
optval.occulter_type = '8TH_ORDER';
psf3 = prop_run('run_coronagraph_dm',
psfs = psf3(iy1:iy2, ix1:ix2);

% deformable mirror: yes
% prop_psd_errormap: yes
% prop_8th_order_mask: yes
lambda, n, 'prm', optval);

% Plot Point Spread Function intensity for case with errors, DM correction
ifig = ifig + 1;
figplace(ifig);
clf;
axes('FontSize', 16);
imagesc(psfs.^0.25);
axis equal;
% x-axis units = y-axis units
axis tight;
% set axis limits to range of data
axis xy;
% set y-axis to increase from bottom
hc = colorbar('vert');
set(hc, 'FontSize', 16);
caxis([0.0, 0.03]);
colormap(gray);
set(get(gcf, 'CurrentAxes'), 'FontSize', 16);
tit3 = sprintf('PSF: DM corrected');

97

title(tit3, 'FontSize', 16);
fprintf(1,
fprintf(1,
fprintf(1,
fprintf(1,

'Maximum speckle flux / stellar flux :\n');
'
No wavefront errors
= %16.7e\n', max(max(psf1)));
'
With wavefront errors = %16.7e\n', max(max(psf2)));
'
With DM correction
= %16.7e\n', max(max(psf3)));

The first call to run_coronagraph_dm simulates an unaberrated system, producing results like those shown in
the bottom panel of Figure 24. The second includes wavefront aberrations, and the third uses a DM to correct them.
The results of the last two are shown in Figure 27 and Figure 28. The DM reduces the brightest speckles by about a
factor of 300.

Figure 27.
Displayed results from
coronagraph_demo. These images show
the intensity at the final focal plane of the
coronagraph (identically stretched in
intensity to show low-level detail). On the
left is the occulted source in a system
without aberrations, the middle has
aberrations, and on the right is after
wavefront error correction by a
deformable mirror.

Figure 28. Results of running coronagraph_demo. In each panel, the left half shows the
wavefront amplitude at the first image plane (after occulter) and on the right in the Lyot
plane (before Lyot stop), stretched to show low-intensity details. The top panel shows the
effects of mid-spatial-frequency aberrations, and the bottom shows the same case after
correction by a deformable mirror. The residuals have higher spatial frequencies than can
be corrected by the DM. An 8th-order band-limited occulter with 50% intensity
transmission at 4λ/D was used.

98

PROPER Routine Reference Manual

99

PROP_8TH_ORDER_MASK
Multiply the current wavefront by an 8th-order transmission profile representing the occulter in a coronagraph.
These masks are described by Kuchner et al. in The Astrophysical Journal, 628, 466 (2005). The transmission
(square of the amplitude) is defined to be

 l  m sin(x / l ) l m sin(x / m) m 
T ( x)  a  



l (x / m) m 
(x / l ) l
 l

2

where x is the radius from the center of the occulter,  =1.788/w (w is half-width where the transmission is
50%), and l and m describe the form of the transmission function (l=3, m=1). The constant a is set to define
the required transmission range. By default, a linear mask is created (mask transmission varies along the X
axis with constant values along the Y axis, or the opposite if the /Y_AXIS switch is set). Circular and elliptical
masks can also be created.

Intensity transmission profile of an 8th-order mask.

Syntax
IDL:

prop_8th_order_mask, wavestruct, hwhm [, /CIRCULAR] [, ELLIPTICAL=ratio]
[, MASK=maskarray] [, MAX_TRANSMISSION=maxvalue]
[, /METERS] [, MIN_TRANSMISSION=minvalue] [, /Y_AXIS]

Python: [mask = ] proper.prop_8th_order_mask( wavestruct, hwhm [, CIRCULAR=True/False]
[, ELLIPTICAL=ratio] [, MAX_TRANSMISSION=maxvalue] [, METERS=True/False]
[, MIN_TRANSMISSION=minvalue] [, Y_AXIS=True/False] )
Matlab: wavestruct_out =
- OR [ wavestruct_out, mask ] =
prop_8th_order_mask( wavestruct_in, hwhm [, 'CIRCULAR' ]
[, 'ELLIPTICAL', ratio ] [, 'MAX_TRANSMISSION', maxvalue ] [, 'METERS' ]
[, 'MIN_TRANSMISSION', minvalue ] [, 'Y_AXIS' ] );

Returns
wavestruct_out (Matlab)
(Required) The modified wavefront structure.

mask (Python)
mask (Matlab)
(Optional) A variable that will contain the mask amplitude pattern created by this routine.

100

Arguments
wavestruct (IDL)
wavestruct (Python)
wavestruct_in (Matlab)
(Required) The current wavefront structure.

hwhm
(Required) The radius from the mask center at which the intensity transmission is 50% of its maximum
value. By default, this is assumed to be in units of λ/D radians (D = entrance pupil diameter), but can
be in meters if the METERS switch is given.

Keywords and Switches
/CIRCULAR (IDL)
CIRCULAR=True or False (Python)
'CIRCULAR' (Matlab)
(Optional) Switch that indicates that a circularly symmetric mask should be generated (the default is a
linear mask).

ELLIPTICAL=ratio (IDL)
ELLIPTICAL=ratio (Python)
'ELLIPTICAL', ratio (Matlab)
(Optional) Indicates that an elliptical mask should be created with an aspect ratio of ratio = x_width /
y_width. The ellipse axes are aligned along the wavefront X and Y axes.

MASK=maskarray (IDL)
(Optional) In IDL, a variable that will contain the mask amplitude pattern created by this routine.

MAX_TRANSMISSION=maxvalue (IDL)
MAX_TRANSMISSION=maxvalue (Python)
'MAX_TRANSMISSION', maxvalue (Matlab)
(Optional) Specifies the maximum value of the mask transmission. The default is 1.0.

/METERS (IDL)
METERS=True or False (Python)
'METERS' (Matlab)
(Optional) Switch that indicates that the value of hwhm is in meters rather than units of λ/D.

MIN_TRANSMISSION=minvalue (IDL)
MIN_TRANSMISSION=minvalue (Python)
'MIN_TRANSMISSION', minvalue (Matlab)
(Optional) Specifies the minimum value of the mask transmission. The default is 0.0.

101

/Y_AXIS (IDL)
Y_AXIS=True or False (Python)
'Y_AXIS' (Matlab)
(Optional) Switch that specifies that the transmission of a linear occulter should vary along the Y axis.

Examples
Multiply the current wavefront by a mask with a 4 λ/D HWHM (D = diameter of entrance aperture):
IDL:
prop_8th_order_mask, wavefront, 4.0

Python:
proper.prop_8th_order_mask( wavefront, 4.0 )

Matlab:
wavefront = prop_8th_order_mask( wavefront, 4.0 );

Multiply the current wavefront by a circular mask with a 3λ/D HWHM and return the mask amplitude in
the variable maskarray:
IDL:
prop_8th_order_mask, wavefront, 3.0, MASK=maskarray, /CIRCULAR

Python:
maskarray = proper.prop_8th_order_mask( wavefront, 3.0, CIRCULAR=True )

Matlab:
[wavefront, maskarray] = prop_8th_order_mask( wavefront, 3.0, 'CIRCULAR' );

102

PROP_ADD_PHASE
Add an error map or value to the current wavefront phase component. If an array, it is assumed to be at the
same sampling as the wavefront. Note that this is wavefront, not surface, error.

Syntax
IDL:

prop_add_phase, wavestruct, phase_error

Python:

proper.prop_add_phase( wavestruct, phase_error )

Matlab:

wavestruct_out = prop_add_phase( wavestruct_in, phase_error );

Returns
wavestruct_out (Matlab)
(Required) The modified wavefront structure.

Arguments
wavestruct (IDL)
wavestruct (Python)
wavestruct_in (Matlab)
(Required) The current wavefront structure.

phase_error
(Required) Two dimensional array containing the wavefront error in meters. The spacing must be the
same as that in the current wavefront, which can be obtained using PROP_GET_SAMPLING.

Examples
Add 0.5 μm RMS of defocus to the current wavefront, given that the Zernike polynomial for defocus for an
unobscured circular aperture is
PROP_ZERNIKES):
IDL:

3 ( 2 r 2  1) where r = 1.0 at the pupil radius (equivalent to using

rho = prop_radius( wavefront )
focus = 0.5e-6 * sqrt(3) * (2*rho^2 – 1)
prop_add_phase, wavefront, focus

Python:

rho = proper.prop_radius( wavefront )
focus = 0.5e-6 * np.sqrt(3) * (2*rho**2 – 1)
proper.prop_add_phase( wavefront, focus )

Matlab:

rho = prop_radius( wavefront );
focus = 0.5e-6 .* sqrt(3) .* (2 .* rho.^2 – 1);
wavefront = prop_add_phase( wavefront, focus );

See Also
PROP_ERRORMAP, PROP_PSD_ERRORMAP, PROP_ZERNIKES
103

PROP_BEGIN
Initialize a PROPER prescription. This routine must be called before calling other PROPER routines.

Syntax
IDL:

prop_begin, wavestruct, beam_diam, wavelength, grid_n, beam_diam_fraction

Python: wavestruct = proper.prop_begin( beam_diam, wavelength, grid_n, beam_diam_fraction )
Matlab: wavestruct = prop_begin( beam_diam, wavelength, grid_n, beam_diam_fraction );

Returns
wavestruct (Python)
wavestruct (Matlab)
(Required) The new, initialized wavefront structure .

Arguments
wavestruct (IDL)
(Required) A variable to contain the new, initialized wavefront structure.

beam_diam
(Required) The initial diameter of the beam in meters.

wavelength
(Required) The wavelength of the simulation in meters.

grid_n
(Required) The dimensions of the simulation grid (grid_n by grid_n pixels). For maximum efficiency,
this must be a power of two. Using too small of a grid may lead to significant Fourier transform artifacts
(wrap-around, etc.) caused by undersampling and aliasing.

beam_diam_fraction
(Optional) Specifies the ratio of the beam diameter to the grid diameter (default is 0.5). This affects the
sampling of the propagation. For instance, in a telescope prescription, a ratio of 0.5 (the beam occupies
half of the grid width) at the entrance aperture results in a Nyquist-sampled image at the focus. A smaller
ratio results in finer sampling but with the possibility of undersampling features in the pupil and FFT
wrap-around errors.

Examples
Initialize a simulation for a 1.0 meter diameter beam, a wavelength of 0.5 microns, a computation grid size
of 1024 by 1024 elements, and a beam diameter ratio of 0.4 (beam diameter = 0.4 × 1024 = 409.6 pixels):

104

IDL:

prop_begin, wavefront, 1.0, 0.5e-6, 1024, 0.4

Python:

wavefront = proper.prop_begin( 1.0, 0.5e-6, 1024, 0.4 )

Matlab:

wavefront = prop_begin( 1.0, 0.5e-6, 1024, 0.4 );

See Also
PROP_END

105

PROP_CIRCULAR_APERTURE
Multiply the current wavefront by a circular aperture (clear inside, dark outside). The edge of the circle is
antialiased (the value of an edge pixel varies between 0.0 and 1.0 in proportion to the amount of a pixel
covered by the circle). To create an array of circular apertures (a Shack-Hartmann mask, for instance), the
user should create a single mask image using multiple calls to PROP_ELLIPSE and then use
PROP_MULTIPLY to multiply the wavefront by that mask.

Syntax
IDL:

prop_circular_aperture, wavestruct, radius [, xoff, yoff] [, /NORM]

Python:

proper.prop_circular_aperture( wavestruct, radius [, xoff, yoff] [, NORM=True/False] )

Matlab:

wavestruct_out = prop_circular_aperture( wavestruct_in, radius [, 'XC', xoff ] [, 'YC', yoff ]
[, 'NORM' ] );

Returns
wavestruct_out (Matlab)
(Required) The modified wavefront structure.

Arguments
wavestruct (IDL)
wavestruct (Python)
wavestruct_in (Matlab)
(Required) The current wavefront structure.

radius
(Required) Radius of aperture in meters, or if the NORM switch is set, the radius in terms of the fraction
of the beam radius at the current surface.

xoff, yoff (IDL)
xoff, yoff (Python)
(Optional) X and Y axis offsets of the circle center from the center of the wavefront grid. These are
specified in meters unless the NORM switch is set, in which case they are in fractions of the current beam
radius. By default, the circle is centered at the center of the wavefront grid.

Keywords and Switches
/NORM (IDL)
NORM=True or False (Python)
'NORM' (Matlab)
(Optional) Switch that indicates that radius and, if provided, xoff and yoff, are in fractions of the current
beam radius (for an unaberrated beam) rather than in meters.

106

'XC', xoff
'YC', yoff (Matlab)
(Optional) X and Y axis offsets of the circle center from the center of the wavefront grid. These are
specified in meters unless the NORM switch is set, in which case they are in fractions of the current beam
radius. By default, the circle is centered at the center of the wavefront grid.

Examples
Multiply the wavefront by a circular entrance aperture with a central obscuration that is 33% of the
diameter of the beam radius:
IDL:

prop_circular_aperture, wavefront, 1.0, /NORM
prop_circular_obscuration, wavefront, 0.33, /NORM

Python:

proper.prop_circular_aperture( wavefront, 1.0, NORM=True )
proper.prop_circular_obscuration( wavefront, 0.33, NORM=True )

Matlab:

wavefront = prop_circular_aperture( wavefront, 1.0, 'NORM' );
wavefront = prop_circular_obscuration( wavefront, 0.33, 'NORM' );

Multiply the wavefront by a filled circle of 10.0 mm radius and centered +1 mm from the wavefront center
along the X axis:
IDL:

prop_circular_aperture, wavefront, 0.010, 0.001, 0.0

Python:

proper.prop_circular_aperture( wavefront, 0.010, 0.001, 0.0 )

Matlab:

wavefront = prop_circular_aperture( wavefront, 0.010, 'XC', 0.001, ...
'YC', 0.0 );

See Also
PROP_CIRCULAR_OBSCURATION, PROP_ELLIPSE,
PROP_ELLIPTICAL_APERTURE, PROP_ELLIPTICAL_OBSCURATION,
PROP_RECTANGLE, PROP_RECTANGULAR_APERTURE,
PROP_RECTANGULAR_OBSCURATION

107

PROP_CIRCULAR_OBSCURATION
Multiply the current wavefront by a circular obscuration (dark inside, clear outside). The edge of the circle
is antialiased (the value of an edge pixel varies between 0.0 and 1.0 in proportion to the amount of a pixel
covered by the circle).

Syntax
IDL:

prop_circular_obscuration, wavestruct, radius [, xoff, yoff] [, /NORM]

Python:

proper.prop_circular_obscuration( wavestruct, radius [, xoff, yoff] [, NORM=True/False] )

Matlab:

wavestruct_out = prop_circular_obscuration( wavestruct_in, radius [, 'XC', xoff ] [, 'YC', yoff ]
[, 'NORM' ] );

Returns
wavestruct_out (Matlab)
(Required) The modified wavefront structure.

Arguments
wavestruct (IDL)
wavestruct (Python)
wavestruct_in (Matlab)
(Required) The current wavefront structure.

radius
(Required) Radius of aperture in meters, or if the NORM switch is set, the radius in terms of the fraction
of the beam radius at the current surface.

xoff, yoff (IDL)
xoff, yoff (Python)
(Optional) X and Y axis offsets of the circle center from the center of the wavefront grid. These are
specified in meters unless the NORM switch is set, in which case they are in fractions of the current beam
radius. By default, the circle is centered at the center of the wavefront grid.

Keywords and Switches
/NORM (IDL)
NORM=True or False (Python)
'NORM' (Matlab)
(Optional) Switch that indicates that radius and, if provided, xoff and yoff, are in fractions of the current
beam radius (for an unaberrated beam) rather than in meters.

108

'XC', xoff
'YC', yoff (Matlab)
(Optional) X and Y axis offsets of the circle center from the center of the wavefront grid. These are
specified in meters unless the NORM switch is set, in which case they are in fractions of the current beam
radius. By default, the circle is centered at the center of the wavefront grid.

Examples
Multiply the wavefront by an circular entrance aperture with a 33% central circular obscuration:
IDL:

prop_circular_aperture, wavefront, 1.0, /NORM
prop_circular_obscuration, wavefront, 0.33, /NORM

Python:

proper.prop_circular_aperture( wavefront, 1.0, NORM=True )
proper.prop_circular_obscuration( wavefront, 0.33, NORM=True )

Matlab:

wavefront = prop_circular_aperture( wavefront, 1.0, 'NORM' );
wavefront = prop_circular_obscuration( wavefront, 0.33, 'NORM' );

Multiply the wavefront by a filled dark circle of 1.0 mm radius and centered +10 mm from the wavefront
center along the X axis:
IDL:

prop_circular_obscuration, wavefront, 0.001, 0.01, 0.0

Python:

proper.prop_circular_obscuration( wavefront, 0.001, 0.01, 0.0 )

Matlab:

wavefront = prop_circular_obscuration( wavefront, 0.001, 'XC',0.01, ...
'YC', 0.0 );

See Also
PROP_CIRCULAR_APERTURE, PROP_ELLIPTICAL_APERTURE,
PROP_ELLIPTICAL_OBSCURATION, PROP_RECTANGLE,
PROP_RECTANGULAR_APERTURE,
PROP_RECTANGULAR_OBSCURATION

109

PROP_COMPILE_FFTI (IDL, Python)
Compile the C routines that provide the interface to the Intel Math Kernel Library FFT routine.

Syntax
prop_compile_ffti

Arguments
This routine has no arguments

Notes
After the interface has been successfully compiled, PROPER needs to be set to use them with the
PROP_USE_FFTI routine.

110

PROP_COMPILE_FFTW (IDL, Python)
Compile the C routines that provide the interface to the FFTW library for improved FFT speed.

Syntax
prop_compile_fftw

Arguments
This routine has no arguments

Notes
After the interface has been successfully compiled, PROPER needs to be set to use them with the
PROP_USE_FFTW routine.

111

PROP_DEFINE_ENTRANCE
This routine normalizes the current wavefront array to have a total intensity of 1.0. This routine should be
called after the entrance aperture is drawn.

Syntax
IDL:

prop_define_entrance, wavestruct

Python:

proper.prop_define_entrance( wavestruct )

Matlab:

wavestruct_out = prop_define_entrance( wavestruct_in );

Returns
wavestruct_out (Matlab)
(Required) The modified wavefront structure.

Arguments
wavestruct (IDL)
wavestruct (Python)
wavestruct_in (Matlab)
(Required) The current wavefront structure.

112

PROP_DIVIDE
Divide the current wavefront by a user-specified value or 2D array

Syntax
IDL:

prop_divide, wavestruct, value

Python:

proper.prop_divide( wavestruct, value )

Matlab:

wavestruct_out = prop_divide( wavestruct_in, value );

Returns
wavestruct_out (Matlab)
(Required) The modified wavefront structure.

Arguments
wavestruct (IDL)
wavestruct (Python)
wavestruct_in (Matlab)
(Required) The current wavefront structure.

value
(Required) The scalar value or two-dimensional array containing the values by which the current
wavefront will be divided. The map in the array is assumed to be centered within the array, and it must
have the same dimensions as the current wavefront gridsize (see PROP_GET_GRIDSIZE).

Examples
Reduce the wavefront amplitude by a factor of 2:
IDL:
prop_divide, wavefront, 2
Python: proper.prop_divide( wavefront, 2 )
Matlab: wavefront = prop_divide( wavefront, 2 );

Replace the current wavefront amplitude with a different one:
IDL:

prop_divide, wavefront, prop_get_amp(wavefront)
prop_multiply, wavefront, new_amp

Python: proper.prop_divide( wavefront, proper.prop_get_amp(wavefront) )
proper.prop_multiply( wavefront, new_amp )
Matlab: wavefront = prop_divide( wavefront, prop_get_amp(wavefront) );
wavefront = prop_multiply( wavefront, new_amp );

See Also
PROP_ADD_PHASE, PROP_MULTIPLY
113

PROP_DM
Modify the phase of the current wavefront using a deformable mirror (DM). The DM considered here is an
array of actuators uniformly distributed on a rectangular grid and covered with a thin-plate mirror. The user
specifies either the height of each actuator or the height of the surface at each actuator (in which case the
required actuator height is solved for accounting for the influence function). The deflection of the mirror
face sheet on sub-actuator scales includes an influence function derived from a Xinetics high-density (1.0
mm spacing) DM.

Syntax
IDL:

prop_dm, wavestruct, dm_height, dm_xc, dm_yc, spacing
[, /FIT] [, MAP=map] [, /NO_APPLY] [, N_ACT_ACROSS_PUPIL=nact],
[, XTILT=xtilt] [, YTILT=ytilt] [, ZTILT=ztilt][, /XYZ] [, /ZYX]

Python: [map = ] proper.prop_dm( wavestruct, dm_height, dm_xc, dm_yc, spacing
[, FIT=True/False] [, NO_APPLY=True/False] [, N_ACT_ACROSS_PUPIL=nact]
[, XTILT=xtilt] [, YTILT=ytilt] [, ZTILT=ztilt][, XYZ=True/False] [, ZYX=True/False] )
Matlab: wavestruct_out =
- OR [ wavestruct_out, map ] =
prop_dm( wavestruct_in, dm_height, dm_xc, dm_yc, spacing
[, 'FIT' ] [, 'NO_APPLY' ] [, 'N_ACT_ACROSS_PUPIL', nact ]
[, 'XTILT', xtilt ] [, 'YTILT', ytilt ] [, 'ZTILT', ztilt ] [, 'XYZ'] [,' ZYX' ] );

Returns
wavestruct_out (Matlab)
(Required) The modified wavefront structure.

map (Python)
map (Matlab)
(Optional) The surface height (not wavefront) map corresponding to the input DM strokes. The map is
in meters and is at the sampling of the current wavefront.

Arguments
wavestruct (IDL)
wavestruct (Python)
wavestruct_in (Matlab)
(Required) The current wavefront structure.

dm_height
(Required) Either of the following:
1) A two-dimensional array (nx,ny) in which each element represents the height in meters of the
corresponding actuator or required surface height at that actuator.
2) The filename of a FITS two-dimensional image file containing the heights.

114

The first element in the array is assumed to be in the lower left corner in the wavefront coordinate system.
If the FIT switch is set, then the heights in the array represent the desired height of the DM surface at
that position including effects of the actuator influence functions.

dm_xc, dm_yc
(Required) X, Y actuator coordinates of the optical axis. The center of the first actuator is (0.0,0.0) and
of the last is (nx-1,ny-1).

spacing
(Required if N_ACT_ACROSS_PUPIL not defined) The distance in meters between DM actuators. This
parameter is (IDL: omitted, Python: omitted, Matlab: ignored) if the N_ACT_ACROSS_PUPIL
parameter is defined.

Keywords and Switches
/FIT (IDL)
FIT=True or False (Python)
'FIT' (Matlab)
(Optional) Switch indicating that the values in dm_height are the desired surface heights, rather than
commanded heights. The heights will be fit by an iterative algorithm that solves for actuator heights
required to produce the desired surface heights, including the effects of the actuator influence function.

MAP=maparray (IDL)
(Optional) A variable to containt the surface height (not wavefront) map corresponding to the input DM
strokes. The map is in meters and is at the sampling of the current wavefront.

/NO_APPLY (IDL)
NO_APPLY=True or False (Python)
'NO_APPLY' (Matlab)
(Optional) If set, a DM surface map will be generated and returned in map/MAP but not applied to the
wavefront.

N_ACT_ACROSS_PUPIL = nact (IDL)
N_ACT_ACROSS_PUPIL = nact (Python)
'N_ACT_ACROSS_PUPIL', nact (Matlab)
(Optional) The number of actuators that span the beam diameter (which is measured along the X axis).
This will determine the actuator spacing, so any value specified by the spacing parameter will be ignored.

XTILT = xtilt, YTILT = ytilt, ZTILT = ztilt (IDL)
XTILT = xtilt, YTILT = ytilt, ZTILT = ztilt (Python)
'XTILT', xtilt
'YTILT', ytilt
'ZTILT', ztilt (Matlab)
(Optional) Define the rotation of the DM about the X, Y, and Z axes. By default the DM is coplanar
with the wavefront array and perpendicular to the optical axis. If one or more of these is set, the DM
surface pattern is rotated in three-dimensional space and orthographically projected onto the wavefront.
When viewed as an image, the wavefront coordinate system assumes the wavefront and initial DM
surface are in the X-Y plane with the first pixel in the lower left. The system is left-handed with the +Z

115

axis towards the observer. By default the rotations occur in X, Y, then Z order unless the ZYX switch is
set.

/XYZ -or- /ZYX (IDL)
XYZ=True or False -or- ZYX=True or False (Python)
'XYZ' -or- 'ZYX' (Matlab)
(Optional) Switches that specify whether the rotation of the DM surface occurs in X, Y, then Z order
(the default) or Z, Y, then X. The default is X, Y, then Z (XYZ).

Examples
Provide a 32 x 32 map of DM actuator (not surface) heights and set a DM to them. The actuators are
spaced by 5 mm, and the center of the beam should fall on the center of the middle actuator:
IDL:
…
dm_xc = 50.0d
dm_yc = 50.0d
dm_spacing = 0.005d
prop_dm, wavefront, dm_heights, dm_xc, dm_yc, dm_spacing
Python:
…
dm_xc = 50.0
dm_yc = 50.0
dm_spacing = 0.005
proper.prop_dm( wavefront, dm_heights, dm_xc, dm_yc, dm_spacing )
Matlab:
…
dm_xc = 50.0;
dm_yc = 50.0;
dm_spacing = 0.005;
wavefront = prop_dm( wavefront, dm_heights, dm_xc, dm_yc, dm_spacing );

116

PROP_ELLIPSE
Return a 2D image containing a filled, antialiased ellipse with the major axis aligned along either the X or
Y axis. Note that this routine does not modify the wavefront structure. PROP_ELLIPSE may be
required when creating multi-aperture masks, otherwise users should use
PROP_ELLIPTICAL_APERTURE and PROP_ELLIPTICAL_OBSCURATION when possible.

Syntax
IDL:

image = prop_ellipse( wavestruct, x_radius, y_radius [, xoff, yoff] [, /DARK] [, /NORM] )

Python: image = proper.prop_ellipse( wavestruct, x_radius, y_radius [, xoff, yoff]
[, DARK=True/False] [, NORM=True/False] )
Matlab: image = prop_ellipse( wavestruct, x_radius, y_radius [, 'XC', xoff ] [, 'YC', yoff ]
[, 'DARK' ] [, 'NORM' ] );

Returns
image
(Required) A two-dimensional image containing a filled, antialiased ellipse scaled between 0.0 to 1.0.

Arguments
wavestruct
(Required) The current wavefront structure (used to obtain sampling information).

x_radius, y_radius
(Required) Radii of the ellipse along the X and Y image axes (in meters, unless NORM switch is set).

xoff, yoff (IDL)
xoff, yoff (Python)
(Optional) X and Y axis offsets of the ellipse center relative to center of wavefront grid (in meters, unless
NORM switch is set). If not specified, the ellipse is centered at the center of the wavefront grid.

Keywords and Switches
/DARK (IDL)
DARK=True or False (Python)
'DARK' (Matlab)
(Optional) Switch indicating that the interior of the ellipse should be set to 0.0 and the exterior to 1.0.
By default, it is 1.0 interior and 0.0 exterior.

/NORM (IDL)
NORM=True or False (Python)
'NORM' (Matlab)
117

(Optional) Switch that indicates that x_radius and y_radius and, if provided, xoff and yoff, are in fractions
of the current beam radius (for an unaberrated beam) rather than in meters.

'XC', xoff
'YC', yoff (Matlab)
(Optional) X and Y axis offsets of the ellipse center relative to center of wavefront grid (in meters, unless
NORM switch is set). If not specified, the ellipse is centered at the center of the wavefront grid.

Examples
Create an image with a 1.2 m semi-major axis and 0.7 semi-minor axis ellipse in it:
IDL:

ellipse = prop_ellipse( wavefront, 1.2, 0.7 )

Python:

ellipse = proper.prop_ellipse( wavefront, 1.2, 0.7 )

Matlab:

ellipse = prop_ellipse( wavefront, 1.2, 0.7 );

Create a mask with two 12 mm -radius circular holes separated by 10 mm:
IDL:

mask = prop_ellipse( wavefront, 0.012, 0.012, -0.005, 0.0 ) + $
prop_ellipse( wavefront, 0.012, 0.012, 0.005, 0.0 )

Python:

mask = proper.prop_ellipse( wavefront, 0.012, 0.012, -0.005, 0.0 ) +
proper.prop_ellipse( wavefront, 0.012, 0.012, 0.005, 0.0 )

Matlab:

mask = prop_ellipse( wavefront, 0.012, 0.012, 'XC', -0.005, 'YC', 0.0 ) + ...
prop_ellipse( wavefront, 0.012, 0.012, 'XC', 0.005, 'YC', 0.0 );

See Also
PROP_CIRCULAR_APERTURE, PROP_CIRCULAR_OBSCURATION,
PROP_ELLIPTICAL_APERTURE, PROP_ELLIPTICAL_OBSCURATION,
PROP_RECTANGLE, PROP_RECTANGULAR_APERTURE,
PROP_RECTANGULAR_OBSCURATION, PROP_ROTATE

118

PROP_ELLIPTICAL_APERTURE
Multiply the current wavefront by an elliptical aperture (clear inside, dark outside). The ellipse is
antialiased (the value of an edge pixel varies between 0.0 and 1.0 in proportion to the amount of a pixel
covered by the ellipse). To create a multi-aperture mask, see PROP_ELLIPSE.

Syntax
IDL:

prop_elliptical_aperture, wavestruct, x_ radius, y_radius [, xoff, yoff] [, /NORM]

Python:

proper.prop_elliptical_aperture( wavestruct, x_ radius, y_radius [, xoff, yoff]
[, NORM=True/False] )

Matlab:

wavestruct_out = prop_elliptical_aperture( wavestruct_in, x_ radius, y_radius
[, 'XC', xoff ] [, 'YC', yoff ] [, 'NORM' ] );

Returns
wavestruct_out (Matlab)
(Required) The modified wavefront structure.

Arguments
wavestruct (IDL)
wavestruct (Python)
wavestruct_in (Matlab)
(Required) The current wavefront structure.

x_radius, y_radius
(Required) Radii of the aperture along X and Y image axes in meters, or if the NORM switch is set, the
radii in terms of the fraction of the beam radius at the current surface.

xoff, yoff (IDL)
xoff, yoff (Python)
(Optional) X and Y axis offsets of the ellipse center from the center of the wavefront grid. These are
specified in meters unless the NORM switch is set, in which case they are in fractions of the current beam
radius. By default, the ellipse is centered at the center of the wavefront grid.

Keywords and Switches
/NORM (IDL)
NORM=True or False (Python)
'NORM' (Matlab)
(Optional) Switch that indicates that x_radius and y_radius and, if provided, xoff and yoff, are in fractions
of the current beam radius (for an unaberrated beam) rather than in meters.

119

'XC', xoff
'YC', yoff (Matlab)
(Optional) X and Y axis offsets of the ellipse center relative to center of wavefront grid (in meters, unless
NORM switch is set). If not specified, the ellipse is centered at the center of the wavefront grid.

Examples
Multiply the wavefront by an elliptical entrance aperture with axis radii of 0.6 and 0.3 of the beam radius
(the beam radius is measured along the X axis):
IDL:

prop_elliptical_aperture, wavefront, 0.6, 0.35, /NORM

Python:

proper.prop_elliptical_aperture( wavefront, 0.6, 0.35, NORM=True )

Matlab:

wavefront = prop_elliptical_aperture( wavefront, 0.6, 0.35, 'NORM' );

Multiply the wavefront by an ellipse of 10.0 mm by 5 mm radii and centered +1 mm from the wavefront
center along the X axis:
IDL:

prop_circular_aperture, wavefront, 0.010, 0.005, 0.001, 0.0

Python:

proper.prop_circular_aperture( wavefront, 0.010, 0.005, 0.001, 0.0 )

Matlab:

wavefront = prop_circular_aperture( wavefront, 0.01, 0.005, ...
'XC', 0.001, 'YC', 0.0 );

See Also
PROP_CIRCULAR_APERTURE, PROP_CIRCULAR_OBSCURATION,
PROP_ELLIPTICAL_OBSCURATION, PROP_RECTANGLE,
PROP_RECTANGULAR_APERTURE,
PROP_RECTANGULAR_OBSCURATION

120

PROP_ELLIPTICAL_OBSCURATION
Multiply the current wavefront by an elliptical obscuration (dark inside, clear outside). The ellipse is
antialiased (the value of an edge pixel varies between 0.0 and 1.0 in proportion to the amount of a pixel
covered by the ellipse).

Syntax
IDL:

prop_elliptical_obscuration, wavestruct, x_ radius, y_radius [, xoff, yoff] [, /NORM]

Python: proper.prop_elliptical_obscuration( wavestruct, x_ radius, y_radius [, xoff, yoff]
[, NORM=True/False] )
Matlab: wavestruct_out = prop_elliptical_obscuration( wavestruct_in, x_ radius, y_radius
[, 'XC', xoff ] [, 'YC', yoff ] [, 'NORM' ] );

Returns
wavestruct_out (Matlab)
(Required) The modified wavefront structure.

Arguments
wavestruct (IDL, Python)
wavestruct_in (Matlab)
(Required) The current wavefront structure.

x_radius, y_radius
(Required) Radii of aperture along X and Y image axes in meters, or if the NORM switch is set, the radii
in terms of the fraction of the beam radius at the current surface.

xoff, yoff (IDL)
xoff, yoff (Python)
(Optional) X and Y axis offsets of the ellipse center from the center of the wavefront grid. These are
specified in meters unless the NORM switch is set, in which case they are in fractions of the current beam
radius. By default, the ellipse is centered at the center of the wavefront grid.

Keywords and Switches
/NORM (IDL)
NORM=True or False (Python)
'NORM' (Matlab)
(Optional) Switch that indicates that x_radius and y_radius and, if provided, xoff and yoff, are in fractions
of the current beam radius (for an unaberrated beam) rather than in meters.

121

'XC', xoff
'YC', yoff (Matlab)
(Optional) X and Y axis offsets of the ellipse center relative to center of wavefront grid (in meters, unless
NORM switch is set). If not specified, the ellipse is centered at the center of the wavefront grid.

Examples
Multiply the wavefront by an elliptical obscuration aperture 10 mm by 5 mm in size:
IDL:

prop_elliptical_obscuration, wavefront, 0.005, 0.0025, /NORM

Python:

proper.prop_elliptical_obscuration( wavefront, 0.005, 0.0025, NORM=True )

Matlab:

wavefront = prop_elliptical_obscuration( wavefront, 0.005, 0.0025, 'NORM' );

See Also
PROP_CIRCULAR_APERTURE, PROP_CIRCULAR_OBSCURATION,
PROP_ELLIPSE, PROP_ELLIPTICAL_APERTURE, PROP_RECTANGLE,
PROP_RECTANGULAR_APERTURE,
PROP_RECTANGULAR_OBSCURATION

122

PROP_END
Conclude the propagation through an optical system. PROP_END must be called as the last PROP routine
in a prescription. By default, PROP_END will convert the wavestruct structure to a real-valued array
containing the modulus squared of wavefront, resulting in the intensity pattern. Setting the NOABS switch
will bypasses the calculation, and the structure will simply be converted to the wavefront array. PROP_END
can also return only the central specified portion of the wavefront array when the EXTRACT keyword is
specified.

Syntax
IDL:

prop_end, wavestruct [, sampling] [, EXTRACT=n] [, /NOABS]

Python:

(wavefront, sampling) = proper.prop_end( wavestruct [, EXTRACT=n] [, NOABS=True/False] )

Matlab:

wavefront =
- OR [ wavefront, sampling ] =
prop_end( wavestruct [, 'EXTRACT', n ] [, 'NOABS' ] );

Returns
wavefront (Python)
wavefront (Matlab)
(Required) The two dimensional wavefront intensity (the modulus-squared of the wavefront, unless the
NOABS switch is set).

sampling (Python)
sampling (Matlab)
(Optional) Variable in which the sampling of the wavefront in meters is returned.

Arguments
wavestruct
(Required) Upon call, the current wavefront structure. In IDL, PROP_END will replace the structure
with the wavefront. By default the intensity (modulus squared) of the wavefront will be returned, unless
the NOABS switch is set.

sampling (IDL)
(Optional) Variable in which the sampling of the wavefront in meters is returned.

Keywords and Switches
EXTRACT=n (IDL)
EXTRACT=n (Python)
'EXTRACT', n (Matlab)
(Optional) Specifies the size of a subarray (n by n pixels) centered on the wavefront to extract from the
wavefront array.

123

/NOABS (IDL)
NOABS=True or False (Python)
'NOABS' (Matlab)
(Optional) Switch that causes the wavefront array to be returned without taking the modulus squared,
the default. The wavefront array will have complex values.

See Also
PROP_BEGIN

124

PROP_END_SAVESTATE
Terminate the current state saving that was initiated with PROP_INIT_SAVESTATE. This deletes the
temporary files created by PROP_STATE.

Syntax
IDL:

prop_end_savestate

Python: proper.prop_end_savestate
Matlab: prop_end_savestate

Examples
A savestate sequence goes something like this in IDL:
prop_init_savestate
…
if ( prop_is_statesaved(wavefront) ) then goto, skipstuff
…
prop_state, wavefront
skipstuff:
prop_state, wavefront
…
prop_end_savestate

See Also
PROP_INIT_SAVESTATE, PROP_IS_STATESAVED, PROP_STATE

125

PROP_ERRORMAP
Read in a wavefront, mirror surface, or amplitude error map from a FITS image file and add it to the current
wavefront (wavefront and surface errors are added to the phase, while the wavefront is multiplied by the
amplitude error). One (and only one) of MIRROR_SURFACE, WAVEFRONT, or AMPLITUDE switch must
be set to properly apply the error map to the wavefront. For surface or wavefront maps, the error is assumed
to be in meters, unless either of the NM or MICRON switches is set to specify the units. The amplitude map
is assumed to range from 0.0 to 1.0. The map can be multiplied by a constant specified using the optional
MULTIPLY parameter. The sampling and wavefront center coordinates of the map can be specified with the
SAMPLING, XC_MAP, and YC_MAP keywords. The map will be interpolated to match the current
wavefront sampling. The map may be rotated about the wavefront center by specifying the ROTATEMAP
keyword.

Syntax
IDL:

prop_errormap, wavestruct, filename [, xshift, yshift] [, /AMPLITUDE]
[, MAGNIFY=constant] [, MAP=array] [, /MICRONS] [, /MIRROR_SURFACE]
[, MULTIPLY=constant] [, /NM] [, ROTATEMAP=angle] [, SAMPLING=sampling]
[, /WAVEFRONT] [, XC_MAP=xc, YC_MAP=yc]

Python: [map = ] proper.prop_errormap( wavestruct, filename [, xshift, yshift]
[, AMPLITUDE=True/False] [, MAGNIFY=constant] [, MICRONS=True/False]
[, MIRROR_SURFACE=True/False] [, MULTIPLY=constant] [, NM=True/False]
[, ROTATEMAP=angle] [, SAMPLING=sampling] [, WAVEFRONT=True/False]
[, XC_MAP=xc, YC_MAP=yc] )
Matlab:

wavestruct_out =
- OR [ wavestruct_out, map ] =
prop_errormap( wavestruct_in, filename [, 'XSHIFT', xshift ] [, 'YSHIFT', yshift ]
[, 'AMPLITUDE' ] [, 'MAGNIFY', constant ] [, 'MICRONS' ]
[, 'MIRROR_SURFACE' ] [, 'MULTIPLY', constant ] [, 'NM' ]
[, 'ROTATEMAP', angle ] [, 'SAMPLING', sampling] [, 'WAVEFRONT' ]
[, 'XC_MAP', xc ] [, 'YC_MAP', yc ] );

Returns
wavestruct_out (Matlab)
(Required) The modified wavefront structure.

map (Python)
map (Matlab)
(Optional) Variable in which the error map will be returned for use by the user. The map will be in meters
of wavefront or mirror surface error, unless it is an amplitude error map. All rotations and shifts will
have been applied, and it will have the current wavefront sampling.

126

Arguments
wavestruct (IDL)
wavestruct (Python)
wavestruct_in (Matlab)
(Required) The current wavefront structure.

filename
(Required) The name of the FITS image file containing the map.

xshift, yshift (IDL)
xshift, yshift (Python)
(Optional) Parameters specifying the amount in meters to shift the map in the X and Y wavefront
directions. Shifts occur before rotation, if any.

Keywords and Switches
/AMPLITUDE -or- /MIRROR_SURFACE -or- /WAVEFRONT (IDL)
AMPLITUDE=True or False -or- MIRROR_SURFACE=True or False
-or- WAVEFRONT=True or False (Python)
'AMPLITUDE' -or- 'MIRROR_SURFACE' -or- 'WAVEFRONT' (Matlab)
(Optional) Indicates the type of the error map in the file (amplitude, wavefront, or mirror surface). The
default type is wavefront. If the map is a mirror surface, it is multiplied by -2 to account for reflection to
convert to wavefront error (e.g. a pit in the surface will induce an additional amount of phase lag).

MAP=array (IDL)
(Optional) Variable in which the error map will be returned for use by the user. The map will be in meters
of wavefront or mirror surface error, unless it is an amplitude error map. All rotations and shifts will
have been applied, and it will have the current wavefront sampling.

/MICRONS -or- /NM (IDL)
MICRON=True or False -or- NM=True or False (Python)
'MICRONS' -or- 'NM' (Matlab)
(Optional) Specifies that the wavefront or mirror surface errors in the map file are in microns or
nanometers (meters is the default).

MULTIPLY=constant (IDL)
MULTIPLY=constant (Python)
'MULTIPLY', constant (Matlab)
(Optional) Specifies a constant by which the error map should be multiplied.

ROTATEMAP=angle (IDL)
ROTATEMAP=angle (Python)
'ROTATEMAP', angle (Matlab)
(Optional) Specifies the angle in degrees counterclockwise that the map will be rotated around the center
of the wavefront after the shifts (if any) have been applied.

127

SAMPLING=sampling (IDL)
SAMPLING=sampling (Python)
'SAMPLING', sampling (Matlab)
(Optional) Specifies the spacing in meters between samples in the image map file. If this keyword is not
specified, then the image file header must contain either the PIXSIZE keyword (sampling per pixel in
meters) or the RADPIX keyword (the beam diameter in pixels in the map). Note that RADPIX header
keyword value will override either the SAMPLING keyword or the PIXSIZE header keyword. Using
one of these values, the image file map will be interpolated to match the current sampling of the
wavefront.

XC_MAP=xcenter
YC_MAP=ycenter (IDL)
XC_MAP=xcenter
YC_MAP=ycenter (Python)
'XC_MAP', xcenter
'YC_MAP', ycenter (Matlab)
(Optional) Specifies the pixel coordinates of the center of the wavefront in the image file map. The
center of the first pixel in the map is (0.0, 0.0). By default, the center is assumed to be (nx/2, ny/2) in
IDL or Python and (nx/2+1,ny/2+1) in Matlab, where nx and ny are the pixel dimensions of the map.

'XSHIFT', xshift
'YSHIFT', yshift (Matlab)
(Optional) Parameters specifying the amount in meters to shift the map in the X and Y wavefront
directions. Shifts occur before rotation, if any.

Examples
Read in the wavefront error map from the file map.fits, which is in nanometers and sampled by 1.0 mm, and
add it to the current wavefront array phase.
IDL:
prop_errormap, wavefront, ’map.fits’, /NM, /WAVEFRONT, SAMPLING=0.001
Python:
proper.prop_errormap( wavefront, ’map.fits’, NM=True, WAVEFRONT=True,
SAMPLING=0.001 )
Matlab:
wavefront = prop_errormap( wavefront, ’map.fits’, 'WAVEFRONT', 'NM', ...
'SAMPLING', 0.001 );

See Also
PROP_PSD_ERRORMAP, PROP_READMAP, PROP_ZERNIKES

128

PROP_FFTW_WISDOM (IDL, Python)
Generate an FFTW wisdom file containing the setup information necessary to compute an optimized Fast
Fourier Transform for a given array size. If this is not done, the default (and likely less optimal) FFTW
configuration will be done. This routine produces a wisdom file with a rootname specified by the
PROPER_FFTW_WISDOM_FILE environment variable and a suffix specifying the grid size. The FFTW
library must be installed and the PROPER FFTW interface compiled using PROP_COMPILE_FFTW
before using this routine. The wisdom file is appropriate for a given system and array size; a change in system
or installation of a new FFTW library (or perhaps even other system libraries) may require re-running this
again to generate an appropriate wisdom file. The wisdom file is used only by PROPER routines. NOTE: it
may take many minutes or even hours to compute wisdom for large (2048 or higher) grid sizes.

Syntax
IDL:

prop_fftw_wisdom, gridsize

Python:

proper.prop_fftw_wisdom( gridsize )

Arguments
gridsize
The dimension of one side of the two-dimensional, square array for which wisdom will be attained.

See Also
PROP_COMPILE_FFTW, PROP_USE_FFTW

129

PROP_FIT_ZERNIKES
Fit circular Zernike polynomials to a two-dimensional map, returning the RMS amount of each aberration.
The region of the map to be fit is defined by a two-dimensional mask. The Zernike polynomials are circular
and orthonormal for clear apertures (up to an arbitrary number of polynomials) and centrally-obscured
apertures (up to the first 22 polynomials).
The Noll ordering scheme is used (use
PROP_NOLL_ZERNIKES to generate a table of polynomials). Note that this routine does not perform
phase unwrapping prior to fitting the map.

Syntax
IDL:

prop_fit_zernikes, input_map, mask, radius, num_z, z_coeff [, fitted_map]
[, OBSCURATION_RATIO=value] [, XC=xcenter, YC=ycenter]

Python: z_coeff =
- OR ( z_coeff, fitted_map ) =
proper.prop_fit_zernikes( input_map, mask, radius, num_z [, FIT=True]
[, OBSCURATION_RATIO=value] [, XC=xcenter, YC=ycenter] )
Matlab: z_coeff =
- OR [ z_coeff, fitted_map ] =
prop_fit_zernikes( input_map, mask, radius, num_z
[, 'OBSCURATION_RATIO', value ] [, 'XC', xcenter ] [, 'YC', ycenter ] );

Returns
z_coeff (Python)
z_coeff (Matlab)
(Required) A variable to contain the fitted Zernike polynomial coefficients (RMS error) fitted by this
routine, assuming the Noll ordering scheme. The first element, z_coeff[0], corresponds to Z1 (piston).
These coefficients will have the same units as the map being fitted. If the FIT switch is set, then this is
part of the (z_coeff, fitted_map) (Python) or [z_coeff, fitted_map] (Matlab) tuple.

fitted_map (Python)
fitted_map (Matlab)
(Optional) Used only if the FIT switch is set, this is a variable in which the fitted Zernike polynomial
map is returned as part of the (z_coeff, fitted_map) (Python) or [z_coeff, fitted_map] (Matlab) tuple. This
map will have the same units as the map that was fit.

Arguments
input_map
(Required) The two-dimensional map to fit with circular Zernike polynomials. The returned Zernike
coefficients will be in the same units used for this map.

mask
(Required) A two-dimensional mask with the same dimensions as input_map specifying whether the
corresponding pixel in the map should be included in the fit (one or zero). This is usually defined by the
pupil, aperture, or beam area on the surface.

130

radius
(Required) The radius in pixels of the circular region over which the Zernike polynomials are defined.

num_z
(Required) The number of Zernike polynomials to fit to the input map. The first num_z polynomials
will be used, assuming the Noll ordering scheme. For obscured Zernikes, the maximum number is 22.

z_coeff (IDL)
A variable to contain the fitted Zernike polynomial coefficients (RMS error) fitted by this routine,
assuming the Noll ordering scheme. The first element, z_coeff[0], corresponds to Z1 (piston). These
coefficients will have the same units as the map being fitted.

FIT=True (Python)
(Optional) Specifies that the fitted map should be returned as part of the (z_coeff, fitted_map) tuple.

fitted_map (IDL)
(Optional) A variable to contain the fitted Zernike polynomial map. This map will have the same units
as the map that was fit.

Keywords and Switches
OBSCURATION_RATIO=value (IDL)
OBSCURATION_RATIO=value (Python)
'OBSCURATION_RATIO', value (Matlab)
(Optional) Sets the ratio of the diameter of a circular central obscuration to the diameter of a circular
aperture, ranging from 0.0 to 1.0. If defined, the returned Zernike coefficients are properly normalized
for an obscured system (i.e. they represent the RMS aberrations over the unobscured area). Only the
first 22 Zernike polynomials can be fit if this is set.

XC=xcenter
YC=ycenter (IDL)
XC=xcenter
YC=ycenter (Python)
'XC', xcenter
'YC', ycenter (Matlab)
(Optional) Specifies that the fitted map should be returned as part of the (z_coeff, fitted_map) (Python)
or [z_coeff, fitted_map] (Matlab) tuple.

See Also
PROP_NOLL_ZERNIKES, PROP_ZERNIKES

131

PROP_GET_AMPLITUDE
Return the amplitude distribution of the current wavefront, centered in the array.
NOTE: The wavefront array is renormalized by PROP_DEFINE_ENTRANCE to have a total intensity of
one, which alters the amplitude.
If the amplitude varies by zero to one prior to calling
PROPER_DEFINE_ENTRANCE, afterwards it will not.

Syntax
IDL:

amp = prop_get_amplitude( wavestruct )

Python: amp = proper.prop_get_amplitude( wavestruct )
Matlab: amp = prop_get_amplitude( wavestruct );

Returns
amp
(Required) The two-dimensional wavefront amplitude.

Arguments
wavestruct
(Required) The current wavefront structure.

See Also
PROP_GET_PHASE, PROP_GET_WAVEFRONT

132

PROP_GET_BEAMRADIUS
Return the current radius in meters of the pilot beam. For a unaberrated system, this provides a fairly
accurate estimate of the beam radius for the current wavefront.

Syntax
IDL:

rad = prop_get_beamradius( wavestruct )

Python: rad = proper.prop_get_beamradius( wavestruct )
Matlab: rad = prop_get_beamradius( wavestruct );

Returns
rad
(Required) The radius of the pilot beam in meters at the current location.

Arguments
wavestruct
(Required) The current wavefront structure.

See Also
PROP_FRATIO(), PROP_GET_GRIDSIZE(),
PROP_GET_NYQUISTSAMPLING(), PROP_GET_SAMPLING_ARCSEC(),
PROP_GET_SAMPLING()

133

PROP_GET_DISTANCETOFOCUS
Returns the distance in meters from the current position to the focus of the current wavefront. This is
actually the distance from the current position to the beam waist of an unaberrated beam.

Syntax
IDL:

distance = prop_get_distancetofocus( wavestruct )

Python: distance = proper.prop_get_distancetofocus( wavestruct )
Matlab: distance = prop_get_distancetofocus( wavestruct );

Returns
distance
(Required) The distance in meters of the current location from the nearest focus based on the pilot
beam.

Arguments
wavestruct
(Required) The current wavefront structure.

134

PROP_GET_FRATIO
Return the focal ratio of the current pilot beam, which is a reasonably accurate estimate of the wavefront
beam focal ratio in an unaberrated system. The focal ratio is computed by dividing the current distance to
the beam waist by the diameter of the beam.

Syntax
IDL:

focal_ratio = prop_get_fratio( wavestruct )

Python: focal_ratio = proper.prop_get_fratio( wavestruct )
Matlab: focal_ratio = prop_get_fratio( wavestruct );

Returns
focal_ratio
(Required) The current focal ratio based on the pilot beam.

Arguments
wavestruct
(Required) The current wavefront structure.

See Also
PROP_GET_WAVELENGTH

135

PROP_GET_GRIDSIZE
Return the dimension in pixels of one side of the wavefront array (which is square).

Syntax
IDL:

gridsize = prop_get_gridsize()

Python: gridsize = proper.prop_get_gridsize( wavestruct )
Matlab: gridsize = prop_get_gridsize();

Returns
gridsize
(Required) The current wavefront grid diameter in pixels (integer).

Arguments
wavestruct (Python only)
(Required) The current wavefront structure.

See Also
PROP_GET_FRATIO, PROP_GET_SAMPLING, PROP_GET_WAVELENGTH

136

PROP_GET_NYQUISTSAMPLING
Return the sampling in meters that meets the Nyquist sampling criterion for the current wavefront, which is
sampling = Fλ/2
where F is the current focal ratio of the pilot beam and λ is the current wavelength. The value returned really
only makes sense when the current wavefront is at the focus of an unaberrated beam.

Syntax
IDL:

sampling = prop_get_nyquistsampling( wavestruct [,wavelength] )

Python: sampling = proper.prop_get_nyquistsampling( wavestruct [,wavelength] )
Matlab: sampling = prop_get_nyquistsampling( wavestruct [,wavelength] );

Returns
sampling
(Required) The Nyquist sampling of the current wavefront in meters.

Arguments
wavestruct
(Required) The current wavefront structure.

wavelength
(Optional) The wavelength in meters that, if specified, is used instead of the current wavelength.

See Also
PROP_GET_FRATIO, PROP_GET_GRIDSIZE, PROP_GET_SAMPLING

137

PROP_GET_PHASE
Return the phase in radians of the current wavefront as an array. Note that this array may be phase
wrapped (values are modulo 2π).

Syntax
IDL:

phase = prop_get_phase( wavestruct )

Python: phase = proper.prop_get_phase( wavestruct )
Matlab: phase = prop_get_phase( wavestruct );

Returns
phase
(Required) The phase of the current wavefront in radians.

Arguments
wavestruct
(Required) The current wavefront structure.

See Also
PROP_GET_AMPLITUDE, PROP_GET_WAVEFRONT

138

PROP_GET_REFRADIUS
Return the radius (meters) of the reference surface to which the phase of the current wavefront is measured.
If the surface is planar, the radius is zero. Assuming that forward propagation occurs from left to right, a
negative radius indicates that the center of the reference surface is to the right of the current position (e.g. the
beam is converging).

Syntax
IDL:

radius = prop_get_refradius( wavestruct )

Python: radius = proper.prop_get_refradius( wavestruct )
Matlab: radius = prop_get_refradius( wavestruct );

Returns
radius
(Required) The reference radius of the pilot beam.

Arguments
wavestruct
(Required) The current wavefront structure.

139

PROP_GET_SAMPLING
Return the sampling in meters of the current wavefront.

Syntax
IDL:

sampling = prop_get_sampling( wavestruct )

Python: sampling = proper.prop_get_sampling( wavestruct )
Matlab: sampling = prop_get_sampling( wavestruct );

Returns
sampling
(Required) The current wavefront sampling in meters.

Arguments
wavestruct
(Required) The current wavefront structure.

See Also
PROP_GET_GRIDSIZE, PROP_GET_NYQUISTSAMPLING,
PROP_GET_SAMPLING_ARCSEC, PROP_GET_SAMPLING_RADIANS

140

PROP_GET_SAMPLING_ARCSEC
Return the sampling in arcseconds of the current wavefront. The result is only valid at focus for beams
with low aberrations.

Syntax
IDL:

sampling = prop_get_sampling_arcsec( wavestruct )

Python: sampling = proper.prop_get_sampling_arcsec( wavestruct )
Matlab: sampling = prop_get_sampling_arcsec( wavestruct );

Returns
sampling
(Required) The sampling of the current wavefront in arcseconds.

Arguments
wavestruct
(Required) The current wavefront structure.

See Also
PROP_GET_GRIDSIZE, PROP_GET_NYQUISTSAMPLING,
PROP_GET_SAMPLING, PROP_GET_SAMPLING_RADIANS

141

PROP_GET_SAMPLING_RADIANS
Return the sampling in radians of the current wavefront. The result is only valid at focus for beams with
low aberrations.

Syntax
IDL:

sampling = prop_get_sampling_radians( wavestruct )

Python: sampling = proper.prop_get_sampling_radians( wavestruct )
Matlab: sampling = prop_get_sampling_radians( wavestruct );

Returns
sampling
(Required) The sampling of the current wavefront in radians.

Arguments
wavestruct
(Required) The current wavefront structure.

See Also
PROP_GET_GRIDSIZE, PROP_GET_NYQUISTSAMPLING,
PROP_GET_SAMPLING, PROP_GET_SAMPLING_ARCSEC

142

PROP_GET_WAVEFRONT
Return the current complex-valued wavefront array with the wavefront centered in the array.

Syntax
IDL:

wavefront = prop_get_wavefront( wavestruct )

Python: wavefront = proper.prop_get_wavefront( wavestruct )
Matlab: wavefront = prop_get_wavefront( wavestruct );

Returns
wavefront
(Required) The current two-dimensional, complex-valued wavefront array.

Arguments
wavestruct
(Required) The current wavefront structure.

See Also
PROP_GET_AMPLITUDE, PROP_GET_PHASE

143

PROP_GET_WAVELENGTH
Return the wavelength in meters of the current wavefront.

Syntax
IDL:

wavelength = prop_get_wavelength( wavestruct )

Python: wavelength = proper.prop_get_wavelength( wavestruct )
Matlab: wavelength = prop_get_wavelength( wavestruct );

Returns
wavelength
(Required) The wavelength in meters of the current wavefront.

Arguments
wavestruct
(Required) The current wavefront structure.

See Also
PROP_GET_FRATIO, PROP_GET_GRIDSIZE, PROP_GET_SAMPLING

144

PROP_HEX_WAVEFRONT
Compute the transmission and wavefront phase errors for an hexagonally-arranged array of segmented
hexagonal apertures (e.g. a segmented primary mirror telescope like Keck or JWST). The current wavefront
is multiplied by a mask comprising the segments (drawn with antialiasing). Each segment may also have
phase errors described by hexagonal Zernike polynomial coefficients (up to Z22). The hexagonal segments
are oriented with top and bottom sides parallel to the X axis (unless the ROTATION keyword is set). They
are in the same order as the Noll-ordered circular Zernikes (see PROP_PRINT_ZERNIKES), but they are
normalized for a hexagonal aperture (see Mahajan & Dai, JOSAA, 24, 2994 (2007)).

Syntax
IDL:

prop_hex_wavefront, wavestruct, nrings, hexrad, hexsep [, zernike_val]
[, APERTURE=var] [, /DARKCENTER] [, /NO_APPLY]
[, PHASE=var] [, ROTATION=angle] [, XCENTER=xc, YCENTER=yc]

Python: [ aperture = ]
- OR [ ( aperture, phase ) = ]
proper.prop_hex_wavefront( wavestruct, nrings, hexrad, hexsep
[, zernike_val] [, DARKCENTER=True/False] [, NO_APPLY=True/False]
[, ROTATION=angle] [, XCENTER=xc, YCENTER=yc] )
Matlab: wavestruct_out =
- OR [ wavestruct_out, aperture ] =
- OR [ wavestruct_out, aperture, phase ] =
prop_hex_wavefront( wavestruct_in, nrings, hexrad, hexsep
[, 'DARKCENTER' ] [, 'NO_APPLY' ] [, 'ROTATION', angle ]
[, 'XCENTER', xc ] [, 'YCENTER', yc ] [ 'ZERNIKE_VAL', zernike_val] );

Returns
wavestruct_out (Matlab)
(Required) The modified wavefront structure.

Arguments
wavestruct (IDL)
wavestruct (Python)
wavestruct_in (Matlab)
(Required) The current wavefront structure.

Returns
aperture (Python)
aperture (Matlab)
(Optional) A variable in which the aperture amplitude map is returned.

145

phase (Python)
phase (Matlab)
(Optional) A variable in which the phase map in meters is returned (returned only if ZERNIKE_VAL is
defined).

Arguments
nrings
(Required) The number of rings of hexagons around the central hexagon (e.g., setting to 1 will result in
a central hexagon surrounded by a ring of adjacent hexagons).

hexrad
(Required) The distance in meters from the center of a hexagonal segment to a vertex.

hexsep
(Required) The distance in meters between the centers of two adjacent hexagonal segments.

zernike_val (IDL)
zernike_val (Python)
'ZERNIKE_VAL', zernike_val (Matlab)
(Optional) Array of dimensions (22, num_hex) in IDL, (22, num_hex) in Python, and (num_hex, 22) in
Matlab, where num_hex = nrings × (nrings + 1) × 3 + 1, the number of segments (including the central
one, whether darkened or not). Each row in this array specifies the hexagonal Zernike polynomial
coefficients (Z1 to Z22) for a segment. The Zernikes are Noll-ordered (see prop_zernikes for a list of
them). The values are in meters of RMS wavefront phase error. Even if the central segment is made
dark using the DARKCENTER switch, there must be an entry for it.

NRINGS = 1

NRINGS = 3

Segment indexing scheme used for specification of Zernikes. Note that in Matlab the user should add
1 to the indices shown. The central segment is always numbered whether it is set to dark or not.

Keywords and Switches
APERTURE=var (IDL)
(Optional) Keyword set to a variable in which the aperture amplitude map is returned.

146

/DARKCENTER (IDL)
DARKCENTER=True or False (Python)
'DARKCENTER' (Matlab)
(Optional) If set, the central hexagonal segment will be dark.

/NO_APPLY (IDL)
NO_APPLY=True or False (Python)
'NO_APPLY' (Matlab)
(Optional) If set, the current wavefront is not modified. This is useful if the user wants to compute the
aperture and/or phase arrays without applying them to the wavefront.

PHASE=var (IDL)
(Optional) Keyword set to a variable in which the phase error map in meters is returned (use only if
zernike_vals is defined).

ROTATION=angle (IDL)
ROTATION=angle (Python)
'ROTATION', angle (Matlab)
(Optional) The degrees counter-clockwise to rotate the aperture about its center.

XCENTER = xc
YCENTER = yc (IDL)
XCENTER = xc
YCENTER = yc (Python)
'XCENTER', xc
'YCENTER', yc (Matlab)
(Optional) Specifies the offset in meters of the aperture center from the center of the wavefront. By
default, the aperture is centered on the wavefront.

Examples
Multiply the wavefront by a hexagonally segmented aperture and add aberrated segment phase errors. Each
segment is given 50 nm RMS of random phase error (including piston).
IDL:
nrings = 2
hexrad = 0.75055
hexsep = 1.315
rms_segment = 50.0e-9
nhex = nrings * (nrings + 1) * 3 + 1
zval = fltarr(22, nhex)
;-- each segment has randomly different phase errors but the same RMS error
for i = 0, nhex-1 do begin
val = randomu(seed,22) – 0.5
rss_val = sqrt(total(val^2))
zval(0,i) = val / rss_val * rms_segment
endfor
prop_hex_wavefront, wavefront, nrings, hexrad, hexsep, zval, /DARKCENTER

147

Python:
nrings = 2
hexrad = 0.75055
hexsep = 1.315
rms_segment = 50.0e-9
nhex = nrings * (nrings + 1) * 3 + 1
zval = np.zeros((nhex,22))
# each segment has randomly different phase errors but with the same RMS error
for i in range(0, nhex):
val = np.random.uniform(-0.5, 0.5, 22)
rss_val = np.sqrt(np.sum(val**2))
zval[i,0] = val / rss_val * rms_segment
proper.prop_hex_wavefront( wavefront, nrings, hexrad, hexsep, zval,
DARKCENTER=True )

Matlab:
nrings = 2;
hexrad = 0.75055;
hexsep = 1.315;
rms_segment = 50.0e-9;
nhex = nrings * (nrings + 1) * 3 + 1;
zval = zeros(nhex,22);
% each segment has randomly different phase errors but the same RMS error
rng('default');
for i = 1 : nhex
val = zeros(1, 22);
val(1:22) = rand(1, 22) – 0.5;
rss_val = sqrt(sum(val.^2));
zval(i, :) = val / rss_val * rms_segment;
end
wavefront = prop_hex_wavefront( wavefront, nrings, hexrad, hexsep, ...
'ZERNIKE_VAL', zval, 'DARKCENTER' );

The above codes create and apply an aperture mask and phase map that look something like these:

Aperture

148

Phase

PROP_INIT_SAVESTATE
Initialize the state saving system. This routine should be called before calling PROP_RUN. See the section
“Save States” for more details on this system. PROP_END_SAVESTATE must be called after all runs have
been completed in order to clean up temporary storage files.

Syntax
IDL:

prop_end_savestate

Python: proper.prop_end_savestate
Matlab: prop_end_savestate

Examples
A save state sequence goes something like this in IDL:
prop_init_savestate
for i = 0, 10 do begin
prop_run, ’testprop’, psf, 0.55, 1024
endfor
prop_end_savestate

See Also
PROP_END_SAVESTATE, PROP_IS_STATESAVED, PROP_STATE

149

PROP_IRREGULAR_POLYGON
Return an image containing a filled (interior value = 1) convex polygon with antialiased edges. If the DARK
switch is set, then the interior = 1 and the exterior = 0. NOTE: This routine only works for convex polygons
and will produce incorrect results for anything else.

Syntax
IDL:

image = prop_irregular_polygon( wavestruct, xvert, yvert [, /DARK] [, /NORM] )

Python: image = proper.prop_irregular_polygon( wavestruct, xvert, yvert
[, DARK=True/False] [, NORM=True/False] )
Matlab: image = prop_irregular_polygon( wavestruct, xvert, yvert [, 'DARK' ] [, 'NORM' ] );

Returns
image
(Required) An image containing an antialiased filled polygon.

Arguments
wavestruct
(Required) The current wavefront structure.

xvert, yvert
(Required) One-dimensional arrays containing the X and Y vertex coordinates of the polygon. These
values are offsets from the wavefront center in meters unless the NORM switch is set. The vertices must
be in sequential order, either clockwise or counter-clockwise. If the last vertex does not have the same
coordinates as the first, the polygon is assumed to begin and end at the first vertex.

Keywords and Switches
/DARK (IDL)
DARK=True or False (Python)
'DARK' (Matlab)
(Optional) Specifies the interior pixels of the polygon to be set to 1.0 and the exterior to 0.0. The reverse
is the default.

/NORM (IDL)
NORM=True or False (Python)
'NORM' (Matlab)
(Optional) Specifies that the coordinates are normalized to the beam radius.

See Also
PROP_POLYGON

150

PROP_IS_STATESAVED
This function is used to determine if the state saving system has been enabled, a previous propagation run
has saved a state for the current wavelength, and if it is possible to jump to a later position in the current
prescription to avoid redundant computation through early parts of the prescription. It returns 1 if the state
saving system is enabled, otherwise 0. For more details, see the section “Save States”. Save states must not
be used with PROP_RUN_MULTI.

Syntax
IDL:

status = prop_is_statesaved( wavestruct )

Python:

status = proper.prop_is_statesaved( wavestruct )

Matlab: status = prop_is_statesaved( wavestruct );

Arguments
wavestruct
(Required) The current wavefront structure.

Examples
PROP_IS_STATESAVED is used as shown in the follow snippet:
…
if ( prop_is_statesaved(wavefront) ) then goto, skipstuff
…
skipstuff:
prop_state
…

See Also
PROP_END_SAVESTATE, PROP_INIT_SAVESTATE, PROP_STATE

151

PROP_LENS
Alter the phase of the current wavefront as if it passed through a perfect lens or reflected from a perfect
mirror.

Syntax
IDL:

prop_lens, wavestruct, lens_fl [, surface_name]

Python: proper.prop_lens( wavestruct, lens_fl [, surface_name] )
Matlab: wavestruct_out = prop_lens( wavestruct_in, lens_fl [, surface_name] );

Returns
wavestruct_out (Matlab)
(Required) The modified wavefront structure.

Arguments
wavestruct (IDL)
wavestruct (Python)
wavestruct_in (Matlab)
(Required) The current wavefront structure.

lens_fl
(Required) The focal length of the lens in meters. It is positive for convex lenses (concave mirrors)
and negative for concave lenses (convex mirrors).

surface_name
(Optional) String containing the name of the lens. The name will be printed when the lens is applied.

Examples
Apply a convex lens of 3 meter focal length:

152

IDL:

prop_lens, wavefront, 3.0

Python:

proper.prop_lens( wavefront, 3.0 )

Matlab:

wavefront = prop_lens( wavefront, 3.0 );

PROP_MAGNIFY
Resample an image using damped sinc interpolation. This function can be used only on real and complexvalued data. By default a Lanczos interpolation kernel is used. PROP_MAGNIFY calls an external C
program, prop_szoom_c.c, to do the interpolation. If that executable does not exist, it calls the slower version
written in the execution language. If the QUICK switch is specified, IDL’s interpolate function is used with
CUBIC=-0.5 to do the interpolation, or Python's map_coordinates function with order=3, or Matlab's interp2
function with cubic interpolation. These are much faster than the sinc interpolator and typically produces
similar results. Note that it is possible to have some erroneous negative values, especially near the cores of
point spread functions, so the user should examine the results closely. It is often better to interpolate the
complex field than the amplitude or intensity because it is often smoother.

Syntax
IDL:

new_image = prop_magnify( old_image, magnification [, n_new]
[, /AMP_CONSERVE] [, /CONSERVE] [, /QUICK] )

Python: new_image = proper.prop_magnify( old_image, magnification [, n_new]
[, AMP_CONSERVE=True/False] [, CONSERVE=True/False],
[, QUICK=True/False] )
Matlab: new_image = prop_magnify( old_image, magnification [, 'AMP_CONSERVE' ]
[, 'CONSERVE' ] [, 'QUICK' ] [, 'SIZE_OUT', size_out ] ) ;

Returns
new_image
(Required) A 2D array containing the magnified image.

Arguments
old_image
(Required) A 2D array (real or complex) containing the image to magnify/demagnify.

magnification
(Required) The amount to magnify/demagnify. A value greater than 1.0 results in magnification.

n_new (IDL)
n_new (Python)
(Optional) The dimension of the output image (n_new by n_new) in pixels. By default, the new image
size is the integral value of the old one times the magnication.

Keywords and Switches
/AMP_CONSERVE (IDL)
AMP_CONSERVE=True or False (Python)
'AMP_CONSERVE' (Matlab)
(Optional) If set, the input image is assumed to be the amplitude of a wavefront, not intensity, and the
interpolated image will be divided by the magnification to conserve intensity (square of amplitude).

153

/CONSERVE (IDL)
CONSERVE=True or False (Python)
'CONSERVE' (Matlab)
(Optional) If set, the field intensity will remain constant. By default, the interpolated values are returned
without scaling, leading to a change in the total value. If the input image is complex, then it is assumed
that the input is an electric field, so the interpolated result will be divided by the magnification. If the
image is not complex, then it is assumed that the input image is in intensity, so the interpolated image
will be divided by the square of the magnification (use AMP_CONSERVE if the image is amplitude).

/QUICK (IDL)
QUICK=True or False (Python)
'QUICK' (Matlab)
(Optional) If set, IDL’s “interpolate” function (with the CUBIC interpolation keyword set to -0.5) or
Python's map_coordinates function (with order=3) or Matlab's interp2 function with cubic interpolation
is used instead of the more exact, but much slower, sinc interpolator.

'SIZE_OUT', size_out (Matlab)
(Optional) The dimension of the output image (size_out by size_out) in pixels. By default, the new
image size is the same as the old one.

Examples
Magnify old_image by a factor of 3.0 and store the central 256 x 256 pixels in new_image:
IDL:

new_image = prop_magnify( old_image, 3.0, 256 )

Python:

new_image = proper.prop_magnify( old_image, 3.0, 256 )

Matlab:

new_image = prop_magnify( old_image, 3.0, 'SIZE_OUT', 256 );

Using the QUICK method:

154

IDL:

new_image = prop_magnify( old_image, 3.0, 256, /QUICK )

Python:

new_image = proper.prop_magnify( old_image, 3.0, 256, QUICK=True )

Matlab:

new_image = prop_magnify( old_image, 3.0, 'SIZE_OUT', 256, 'QUICK' );

PROP_MULTIPLY
Multiply the current wavefront by a user-specified value or 2D array

Syntax
IDL:

prop_multiply, wavestruct, value

Python:

proper.prop_multiply( wavestruct, value )

Matlab: wavestruct_out = prop_multiply( wavestruct_in, value );

Returns
wavestruct_out (Matlab)
(Required) The modified wavefront structure.

Arguments
wavestruct (IDL)
wavestruct (Python)
wavestruct_in (Matlab)
(Required) The current wavefront structure.

value
(Required) Scalar value or two-dimensional array containing the values by which the current wavefront
will be multiplied. The map in the array is assumed to be centered within the array, and it must have
the same dimensions as the current wavefront gridsize (see PROP_GET_GRIDSIZE).

Examples
Multiply the wavefront amplitude by a 2D Gaussian with  = 0.01 meters:
IDL:

r = prop_radius( wavefront )
gauss_amp = exp(-0.5*(r/0.01)^2)
prop_multiply, wavefront, gauss_amp

Python:

r = proper.prop_radius( wavefront )
gauss_amp = np.exp(-0.5*(r/0.01)**2)
proper.prop_multiply( wavefront, gauss_amp )

Matlab:

r = prop_radius( wavefront );
gauss_amp = exp(-0.5*(r./0.01).^2);
wavefront = prop_multiply( wavefront, gauss_amp );

See Also
PROP_ADD_PHASE, PROP_DIVIDE

155

PROP_NOLL_ZERNIKES
Return a string array in which each element contains the Zernike polynomial equation corresponding to the
index of that element. The polynomials are orthonormal for an unobscured circular aperture. They follow
the ordering convention of Noll (J. Opt. Soc. America, 66, 207 (1976)). Element (0) is always blank. The
equations contain the variables r (normalized radius) and t (azimuth in radians). The aberrations defined by
the polynomials have an RMS of 1.0 about a mean of 0.0.

Syntax
IDL:

list = prop_noll_zernikes( max_z )

Python:

list = proper.prop_noll_zernikes( max_z )

Matlab: list = prop_noll_zernikes( max_z );

Returns
list
(Required) An array of strings, each element containing the corresponding Zernike polynomial equation.
In IDL and Python the first element (e.g., list[0]) is blank, and the first valid equation is stored in the
next element (Z1 = piston; e.g., list[1]). In Matlab, the first element corresponds to Z1 (e.g., list[1]).

Arguments
max_z
(Required) The maximum number of polynomials to return. In IDL and Python, the returned array is
dimensioned max_z+1, where the 1st element is to be ignored. In Matlab max_z elements are returned.

Examples
Display the first few Zernike terms:
IDL:
list = prop_noll_zernikes( 5 )
for i = 1, 5 do print, i, ’ = ’, list(i)
This will display the following:
1
2
3
4
5

=
=
=
=
=

1
2 * (r)
2 * (r)
sqrt(3)
sqrt(6)

*
*
*
*

cos(t)
sin(t)
(2.0*r^2 – 1.0)
(r^2) * sin(2*t)

Note that this does the same thing as calling PROP_PRINT_ZERNIKES.

See Also
PROP_FIT_ZERNIKES, PROP_PRINT_ZERNIKES, PROP_ZERNIKES

156

PROP_PIXELLATE
Integrate an image onto detector pixels of a specified size. This is done by convolving the Fourier transform
of the input image with a sinc function representing the transfer function of an idealized square pixel. This
is then Fourier transformed back, producing an array containing the image values integrated onto detectorsized pixels but at the spacing of the input image. This array is then interpolated to produce the image
integrated onto detector-sized pixels with detector-pixel sampling. It is assumed that the spacing and size of
the detector pixels are the same.

Syntax
IDL:

result = prop_pixellate( input_image, input_sampling, output_sampling [, output_dim] )

Python:

result = proper.prop_pixellate( input_image, input_sampling, output_sampling [, output_dim] )

Matlab:

result = prop_pixellate( input_image, input_sampling, output_sampling [, output_dim] );

Returns
result
(Required) The pixellated image sampled by output_sampling with dimensions output_dim ×
output_dim.

Arguments
input_image
(Required) The real-valued image (square) to be pixellated.

input_sampling
(Required) The sampling of the input image, in the same units as output_sampling.

output_sampling
(Required) The sampling of the output image (=size of the square detector pixel), in the same units as
output_sampling.

output_dim
(Optional) Specified the dimension (output_dim by output_dim) of the pixellated image. By default, the
size is set as necessary to include all values in the input image.

Examples
Map an image sampled by 15 μm onto a detector with 30 μm pixels, keeping the image dimension in pixels
the same:
IDL:

output = prop_pixellate( input_image, 15.0, 30.0 )

Python: output = proper.prop_pixellate( input_image, 15.0, 30.0 )
Matlab: output = prop_pixellate( input_image, 15.0, 30.0 );

157

PROP_POLYGON
Return a 2D image containing a filled, antialiased polygon. The polygon is symmetrical (the vertices are all
the same distance from the center at are at equal angles from each other). This routine does not modify the
wavefront structure. The polygon will have one vertex along the +X axis from its center, unless the
ROTATION keyword is specified.

Syntax
IDL:

image = prop_polygon( wavestruct, nvert, radius [, xc, yc] [, /DARK] [, /NORM]
[, ROTATION=angle] )

Python: image = proper.prop_polygon( wavestruct, nvert, radius [, xc, yc] [, DARK=True/False]
[, NORM=True/False] [, ROTATION=angle] )
Matlab: image = prop_polygon( wavestruct, nvert, radius [, 'XC', xc ] [, 'YC', yc ] [, 'DARK' ]
[, 'NORM' ] [, 'ROTATION', angle ] );

Returns
image
(Required) An image containing an antialiased, filled polygon.

Arguments
wavestruct
(Required) The current wavefront structure (used to obtain sampling information).

radius
(Required) The distance from any vertex to the center of the polygon (in meters, unless NORM switch
is set).

xc, yc (IDL)
xc, yc (Python)
(Optional) Center of the polygon relative to center of wavefront grid (in meters, unless NORM switch is
set). If not specified, it is centered within the grid.

Keywords and Switches
/DARK (IDL)
DARK=True or False (Python)
'DARK' (Matlab)
(Optional) Switch indicating that the interior of the polygon should be set to 0.0 and the exterior to 1.0.
By default, it is 1.0 interior and 0.0 exterior.

158

/NORM (IDL)
NORM=True or False (Python)
'NORM' (Matlab)
(Optional) Switch specifying that radius is specified as a fraction of the beam radius at the current
surface.

ROTATION=angle (IDL)
ROTATION=angle (Python)
'ROTATION', angle (Matlab)
(Optional) Specifies the degrees counter-clockwise to rotate the polygon around its center.

'XC', xc
'YC', yc (Matlab)
(Optional) Center of the polygon relative to center of wavefront grid (in meters, unless NORM switch is
set). If not specified, it is centered within the grid.

Examples
Create an image with a hexagon with vertices 0.5 meters from its center:
IDL:

hex = prop_polygon( wave, 6, 0.5 )

Python:

hex = proper.prop_polygon( wave, 6, 0.5 )

Matlab:

hex = prop_polygon( wave, 6, 0.5 );

See Also
PROP_ELLIPSE, PROP_IRREGULAR_POLYGON, PROP_RECTANGLE

159

PROP_PRINT_ZERNIKES
Print to the display the equations for the first N Noll-ordered Zernike polynomials for an unobscured, circular
aperture.

Syntax
IDL:

prop_print_zernikes, n

Python: proper.prop_print_zernikes( n )
Matlab: prop_print_zernikes( n );

Arguments
n
(Required) The number of Zernike polynomials to print (1 to n).

Examples
Display the first five Zernike polynomials:
IDL:

prop_print_zernikes, 5

This produces the following output on the terminal:
1
2
3
4
5

=
=
=
=
=

1
2 * (r)
2 * (r)
sqrt(3)
sqrt(6)

*
*
*
*

cos(t)
sin(t)
(2.0*r^2 – 1.0)
(r^2) * sin(2*t)

See Also
PROP_FIT_ZERNIKES, PROP_NOLL_ZERNIKES, PROP_ZERNIKES

160

PROP_PROPAGATE
Propagate the current wavefront a specified distance (either forward or backward from the current position).
The propagator will select the appropriate method (Fresnel or angular spectrum).

Syntax
IDL:

prop_propagate, wavestruct, dz [, surface_name] [, /TO_PLANE]

Python: proper.prop_propagate( wavestruct, dz [, surface_name] [, TO_PLANE=True/False] )
Matlab: wavestruct_out = prop_propagate( wavestruct_in, dz [,'SURFACE_NAME', surface_name ]
[, 'TO_PLANE' ] );

Returns
wavestruct_out (Matlab)
(Required) The modified wavefront structure.

Arguments
wavestruct (IDL)
wavestruct (Python)
wavestruct_in (Matlab)
(Required) The current wavefront structure.

dz
(Required) The distance in meters over which to propagate the current wavefront. Forward propagation
is a positive distance.

surface_name (IDL)
surface_name (Python)
(Optional) String containing the name of the surface to which the wavefront is being propagated. The
name will be printed out on the display during propagation.

Keywords and Switches
'SURFACE_NAME', surface_name (Matlab)
(Optional) String containing the name of the surface to which the wavefront is being propagated. The
name will be printed out on the display during propagation.

/TO_PLANE (IDL)
TO_PLANE=True or False (Python)
'TO_PLANE' (Matlab)
(Optional) Setting this switch forces the wavefront to be propagated to a plane. If the current wavefront
is in the far field, it is propagated to the beam waist and then propagated to the final plane using an
angular spectrum propagator.

161

Examples
Propagate the current wavefront forward by 3.0 meters to ‘mirror 1’:

162

IDL:

prop_propagate, wavefront, 3.0, ’mirror 1’

Python:

proper.prop_propagate( wavefront, 3.0, ’mirror 1’ )

Matlab:

wavefront = prop_propagate( wavefront, 3.0, ...
'SURFACE_NAME', ’mirror 1’ );

PROP_PSD_ERRORMAP
Create a realization of a two-dimensional surface, wavefront, or amplitude error map from a specified twodimensional power spectral density (PSD) profile. This is often called a “phase screen” by ground-based
adaptive optics modelers. This map is applied to the current wavefront (added if wavefront or surface,
multiplied if amplitude). Because the map is generated using a Fourier transform without any additional
processing, the lowest spatial frequency it includes (in cycles/diameter) is equal to the pupil-to-grid size ratio.

Syntax
IDL:

prop_psd_errormap, wavestruct, amp, b, c
[, AMPLITUDE=value] [, FILE=string] [, INCLINATION=angle]
[, MAP=variable] [, MAX_FREQUENCY=value] [, /MIRROR] [, /NO_APPLY]
[, /RMS] [, ROTATION=angle] [, /TPF]

Python: [map = ] proper.prop_psd_errormap( wavestruct, amp, b, c
[, AMPLITUDE=value] [, FILE=string] [, INCLINATION=angle]
[, MAX_FREQUENCY=value] [, MIRROR=True/False] [, NO_APPLY=True/False]
[, RMS=True/False] [, ROTATION=angle] [, TPF=True/False] )
Matlab: wavestruct_out =
- OR [ wavestruct_out, map ] =
prop_psd_errormap( wavestruct_in, amp, b, c
[, 'AMPLITUDE', value ] [, 'FILE', string ] [, 'INCLINATION', angle ]
[, 'MAX_FREQUENCY', value ] [, 'MIRROR' ] [, 'NO_APPLY' ]
[, 'RMS' ] [, 'ROTATION', angle ] [, 'TPF' ] );

Returns
wavestruct_out (Matlab)
(Required) The modified wavefront structure.

map (Python)
map (Matlab)
(Optional) The two-dimensional wavefront, surface, or amplitude error map created by this routine.

Arguments
wavestruct (IDL)
wavestruct (Python)
wavestruct_in (Matlab)
(Required) The current wavefront structure.

amp
(Required) The low-spatial-frequency error. By default this is the wavefront error power per areal spatial
frequency (units are meters4). If the MIRROR switch is set, this is assumed to be surface error (wavefront
error will be twice this due to reflection). If the AMPLITUDE keyword is specified, then this is assumed
to be the total RMS error of the entire map. If the RMS switch is set, then the entire error map will be
renormalized to have an RMS value set to this parameter.

163

b
(Required) The correlation length parameter (cycles/meter). This indicates the spatial frequency where
the PSD curve transitions from a flat profile at low frequencies to a sloping one at higher frequencies.

c
(Required) The high-spatial-frequency PSD profile power law exponent.

Keywords and Switches
AMPLITUDE=value (IDL)
AMPLITUDE=value (Python)
'AMPLITUDE', value (Matlab)
(Optional) This keyword, if set to a value, indicates that an amplitude, rather than surface or wavefront,
error map with a maximum set by the keyword value is to be generated and applied to the current
wavefront. The RMS switch is ignored if set, and the PSD parameters in this case are as described above.
Note that intensity is the square of amplitude, so AMPLITUDE=0.9 will result in a maximum intensity
transmission of 0.81.

FILE=string (IDL)
FILE=string (Python)
'FILE', string (Matlab)
(Optional) This keyword, if set to a value, specifies that the error map generated by this
routine will be saved to a FITS file with the provided name (exclude the filename extension).
Subsequent calls to this routine with the same filename will cause the error map in the file to
be read in and applied to the wavefront, rather than a new map being generated. In this
instance the other parameters will be ignored, except when the AMPLITUDE keyword is set
to a value, in which case the map is assumed to be an amplitude map and will have a maximum
specified by the AMPLITUDE keyword value but the same RMS value about the mean as
before.
Note that there are no checks made to ensure that the parameter values used to create the
map in a file are the same ones given in subsequent calls.

INCLINATION=angle (IDL)
INCLINATION=angle (Python)
'INCLINATION', angle (Matlab)
(Optional) This keyword specifies the inclination in degrees from the Y axis in the Y-Z plane that the
surface described by the PSD makes relative to the direction of the incident beam. An inclination of zero
(the default) indicates that the surface is perpendicular to the propagation direction. The sign of the
inclination is not important. Specifying the inclination alters the projection of the surface onto the
wavefront. When the ROTATION angle is also specified, the surface is first inclined and then rotated.

MAP=variable (IDL)
(Optional) The wavefront, surface, or amplitude error map created by this routine will be returned in the
variable assigned to this optional keyword.

164

MAX_FREQUENCY=variable (IDL)
MAX_FREQUENCY=variable (Python)
'MAX_FREQUENCY', variable (Matlab)
(Optional) Maximum spatial frequency (cycles/meter) in the generated map. This can be used to prevent
high spatial frequency components from generating aliasing errors when a map is resampled.

/MIRROR (IDL)
MIRROR=True or False (Python)
'MIRROR' (Matlab)
(Optional) This switch, if set, indicates that the PSD specifies the surface, not wavefront, error of a
mirror. The wavefront error is twice the surface error due to reflection.

/NO_APPLY (IDL)
NO_APPLY=True or False (Python)
'NO_APPLY' (Matlab)
(Optional) This switch, if set, will cause a map to be generated but not applied to the wavefront. This is
useful if you wish to obtain a map that you will modify yourself.

/RMS (IDL)
RMS=True or False (Python)
'RMS' (Matlab)
(Optional) This switch, if set, indicates that the generated error map is to be renormalized to have an
RMS value specified by the amp parameter.

ROTATION=angle (IDL)
ROTATION=angle (Python)
'ROTATION', angle (Matlab)
(Optional) This keyword specifies the counter-clockwise rotation in degrees of the surface in the X-Y
plane. This only has an effect if the INCLINATION keyword is also defined.

/TPF (IDL)
TPF=True or False (Python)
'TPF' (Matlab)
(Optional) This switch, if set, indicates that the Terrestrial Planet Finder (TPF) PSD profile specification
is to be used.

Examples
Apply a PSD-defined wavefront error map to the current wavefront assuming the TPF PSD form
specification and return the generated map in the variable errormap:
IDL:

amp = 9.6e-19
b = 4.0
c = 3.0
prop_psd_errormap, wavefront, amp, b, c, /TPF, MAP=errormap

Python:

amp = 9.6e-19
b = 4.0
c = 3.0
errormap = proper.prop_psd_errormap( wavefront, amp, b, c, TPF=True )

165

Matlab:

amp = 9.6e-19;
b = 4.0;
c = 3.0;
[wavefront, errormap] = prop_psd_errormap( wavefront, amp, b, c, 'TPF' );

Multiply the current wavefront by a PSD-defined amplitude error map with a maximum value of 0.9 and an
RMS of 0.001 about the mean to the current wavefront:
IDL:

amp = 0.001
b = 1.0
c = 4.0
prop_psd_errormap, wavefront, amp, b, c, AMPLITUDE=0.9

Python:

amp = 0.001
b = 1.0
c = 4.0
proper.prop_psd_errormap( wavefront, amp, b, c, AMPLITUDE=0.9 )

Matlab:

amp = 0.001;
b = 1.0;
c = 4.0;
wavefront = prop_psd_errormap( wavefront, amp, b, c, 'AMPLITUDE', 0.9 );

Apply a PSD-defined mirror surface error map, saving the result to the file ‘primary’ (the error map will be
read from the file on subsequent calls if the same filename is specified):
IDL:

prop_psd_errormap, wavefront, amp, b, c, FILE=’primary’

Python:

proper.prop_psd_errormap( wavefront, amp, b, c, FILE=’primary’ )

Matlab:

wavefront = prop_psd_errormap( wavefront, amp, b, c, 'FILE', ’primary’ );

See Also
PROP_ERRORMAP, PROP_ZERNIKES

166

PROP_RADIUS
Return a 2D array in which the value of each element corresponds to the distance of that element from the
center of the current wavefront. By default, the distance is in meters unless the NORM switch is set, in which
case it is normalized to the current radius of the beam as determined by the Gaussian tracer beam. The center
of the wavefront array is defined to be at the center pixel of the array.

Syntax
IDL:

radius = prop_radius( wavestruct [, /NORM] )

Python:

radius = proper.prop_radius( wavestruct [, NORM=True/False] )

Matlab: radius = proper.prop_radius( wavestruct [, 'NORM' ] );

Returns
radius
(Required) A 2D array in which the value of each element corresponds to the distance of that element
from the center of the current wavefront.

Arguments
wavestruct
(Required) The current wavefront structure.

Keywords and Switches
/NORM (IDL)
NORM=True or False (Python)
'NORM' (Matlab)
(Optional) Indicates that the returned array is to contain distances normalized to the beam radius. This
assumes that the radius of the pilot tracer beam accurately reflects the size of the actual beam in the
wavefront array, which will not be true in the case of significant aberrations.

Examples
Multiply the wavefront amplitude by a 2D Gaussian with  = 0.01 meters:
IDL:
r = prop_radius( wavefront )
gauss_amp = exp(-0.5*(r/0.01)^2)
prop_multiply, wavefront, gauss_amp

Python:
r = proper.prop_radius( wavefront )
gauss_amp = np.exp(-0.5*(r/0.01)**2)
proper.prop_multiply( wavefront, gauss_amp )

167

Matlab:
r = prop_radius( wavefront );
gauss_amp = exp(-0.5 .* (r./0.01).^2);
wavefront = prop_multiply( wavefront, gauss_amp );

See Also
PROP_GET_BEAMRADIUS

168

PROP_READMAP
Read an error map from a FITS image file. Note that the map is not applied to the current wavefront (use
PROP_ERRORMAP to read in a map and do that). The map will be interpolated as necessary to match the
sampling of the current wavefront.

Syntax
IDL:

prop_readmap, wavestruct, filename, map [, xshift, yshift]
[,SAMPLING=value] [, XC_MAP=value, YC_MAP=value]

Python: map = proper.prop_readmap( wavestruct, filename[, xshift, yshift]
[,SAMPLING=value] [, XC_MAP=value, YC_MAP=value] )
Matlab: map = prop_readmap( wavestruct, filename[, 'XSHIFT', xshift ] [ 'YSHIFT', yshift ]
[, 'SAMPLING', value ] [, 'XC_MAP', value ] [, 'YC_MAP', value] );

Returns
map (Python)
map (Matlab)
(Required) The error map.

Arguments
wavestruct
(Required) The current wavefront structure.

filename
(Required) The name of a FITS image file containing the error map.

map (IDL)
(Required) A variable in which the error map is returned.

xshift, yshift (IDL)
xshift, yshift (Python)
(Optional) amounts to shift the map in meters in the wavefront coordinate system.

Keywords and Switches
SAMPLING=value (IDL)
SAMPLING=value (Python)
'SAMPLING', value (Matlab)
(Optional) Keyword that specifies the sampling of the map in meters. This will override any sampling
specified in the data file header. This must be specified if no sampling is specified in the header using
the PIXSIZE keyword. Note that the sampling is not returned by this keyword. If the header value

169

RADPIX is defined (the radius of the beam in the map in pixels), then that value will override any other
sampling specifiers, including the SAMPLING keyword.

XC_MAP=value (IDL)
YC_MAP=value
XC_MAP=value (Python)
YC_MAP=value
'XC_MAP', value (Matlab)
'YC_MAP', value
(Optional) Keyword values that specify the center of the wavefront in the map (prior to the map being
shifted if xshift and yshift are given) in pixels. The center of the first pixel is (0.0, 0.0). By default, the
map is assumed to be centered at (n/2, n/2).

Examples
Read an error map from the file ‘surface1.fits’, which has a sampling of 1 mm:
IDL:
prop_readmap, wavefront, ’surface1.fits’, map, SAMPLING=0.001

Python:
map = proper.prop_readmap( wavefront, ’surface1.fits’, SAMPLING=0.001 )

Matlab:
map = prop_readmap( wavefront, ’surface1.fits’, 'SAMPLING', 0.001 );

See Also
PROP_ERRORMAP, PROP_PSD_ERRORMAP

170

PROP_RECTANGLE
Return a 2D image containing a filled, antialiased rectangle. This routine does not modify the wavefront
structure.
This function is intended for use by other PROPER routines.
Users should use
PROP_RECTANGULAR_APERTURE and PROP_RECTANGULAR _OBSCURATION if possible.

NOTE: Prior to PROPER version 3.0, the manual incorrectly stated that when
using the NORM option, the X and Y widths of the rectangle were relative to the
beam diameter; they were (and are now) actually relative to the beam radius.

Syntax
IDL:

image = prop_rectangle( wavestruct, xwidth, ywidth [, xoff, yoff] [, /DARK]
[, /NORM] [, ROTATION=angle] )

Python: image = proper.prop_rectangle( wavestruct, xwidth, ywidth [, xoff, yoff] [, DARK=True/False]
[, NORM=True/False] [, ROTATION=angle] )
Matlab: image = prop_rectangle( wavestruct, xwidth, ywidth [, 'XC', xoff ] ['YC', yoff ]
[, 'DARK' ] [, 'NORM' ] [, 'ROTATION', angle );

Returns
image
(Required) An image the same size as the current wavefront containing an antialised, filled rectangle.

Arguments
wavestruct
(Required) The current wavefront structure (used to obtain sampling information).

xwidth, ywidth
(Required) Width of obscuration along X and Y image axes in meters, or if the NORM switch is set, the
widths in terms of the fraction of the beam radius at the current surface.

xoff, yoff (IDL)
xoff, yoff (Python)
(Optional) X and Y axis offsets of the rectangle center from the center of the wavefront grid. These are
specified in meters unless the NORM switch is set, in which case they are in fractions of the current beam
radius. By default, the rectangle is centered at the center of the wavefront grid.

Keywords and Switches
/DARK (IDL)
DARK=True or False (Python)
'DARK' (Matlab)

171

(Optional) Switch indicating that the interior of the rectangle should be set to 0.0 and the exterior to 1.0.
By default, it is 1.0 interior and 0.0 exterior.

/NORM (IDL)
NORM=True or False (Python)
'NORM' (Matlab)
(Optional) Switch specifying that the X and Y widths and X and Y centers are specified as fractions of
the beam radius. By default, they are in meters.

ROTATION=angle (IDL)
ROTATION=angle (Python)
'ROTATION', angle (Matlab)
(Optional) Specifies the degrees counter-clockwise to rotate the rectangle around its center.

'XC', xoff
'YC', yoff (Matlab)
(Optional) X and Y axis offsets of the rectangle center from the center of the wavefront grid. These are
specified in meters unless the NORM switch is set, in which case they are in fractions of the current beam
radius. By default, the rectangle is centered at the center of the wavefront grid.

Examples
Create an image with a 1.2 m by 0.5 m rectangle in it offset from the center by 0.2, 0.3 in X, Y:
IDL:

rect = prop_rectangle( wavefront, 1.2, 0.5, 0.2, 0.3 )

Python:

rect = proper.prop_rectangle( wavefront, 1.2, 0.5, 0.2, 0.3 )

Matlab:

rect = prop_rectangle( wavefront, 1.2, 0.5, 'XC', 0.2, 'YC', 0.3 );

See Also
PROP_CIRCULAR_APERTURE, PROP_CIRCULAR_OBSCURATION,
PROP_ELLIPSE, PROP_ELLIPTICAL_APERTURE,
PROP_ELLIPTICAL_OBSCURATION, PROP_RECTANGULAR_APERTURE,
PROP_RECTANGULAR_OBSCURATION

172

PROP_RECTANGULAR_APERTURE
Multiply the current wavefront by a rectangular aperture (clear inside, dark outside). The rectangle is
antialiased (the value of an edge pixel varies between 0.0 and 1.0 in proportion to the amount of a pixel
covered by the rectangle).

NOTE: Prior to PROPER version 3.0, the manual incorrectly stated that when
using the NORM option, the X and Y widths of the rectangle were relative to the
beam diameter; they were (and are now) actually relative to the beam radius.

Syntax
IDL:

prop_rectangular_aperture, wavestruct, xwidth, ywidth [, xoff, yoff]
[, /NORM] [, ROTATION=angle]

Python:

proper.prop_rectangular_aperture( wavestruct, xwidth, ywidth [, xoff, yoff]
[, NORM=True/False] [, ROTATION=angle] )

Matlab:

wavestruct_out = prop_rectangular_aperture( wavestruct_in, xwidth, ywidth
[, 'XC', xoff ] ['YC', yoff ] [, 'NORM' ] [, 'ROTATION', angle );

Returns
wavestruct_out (Matlab)
(Required) The modified wavefront structure.

Arguments
wavestruct (IDL)
wavestruct (Python)
wavestruct_in (Matlab)
(Required) The current wavefront structure.

xwidth, ywidth
(Required) Width of obscuration along X and Y image axes in meters, or if the NORM switch is set, the
widths in terms of the fraction of the beam radius at the current surface.

xoff, yoff (IDL)
xoff, yoff (Python)
(Optional) X and Y axis offsets of the rectangle center from the center of the wavefront grid. These are
specified in meters unless the NORM switch is set, in which case they are in fractions of the current beam
radius. By default, the rectangle is centered at the center of the wavefront grid.

173

Keywords and Switches
/NORM (IDL)
NORM=True or False (Python)
'NORM' (Matlab)
(Optional) Switch specifying that the X and Y widths and X and Y centers are specified as fractions of
the beam radius. By default, they are in meters.

ROTATION=angle (IDL)
ROTATION=angle (Python)
'ROTATION', angle (Matlab)
(Optional) Specifies the degrees counter-clockwise to rotate the rectangle around its center.

'XC', xoff
'YC', yoff (Matlab)
(Optional) X and Y axis offsets of the rectangle center from the center of the wavefront grid. These are
specified in meters unless the NORM switch is set, in which case they are in fractions of the current beam
radius. By default, the rectangle is centered at the center of the wavefront grid.

Examples
Multiply the wavefront by a rectangle of 10.0 mm by 5 mm and centered +1 mm from the wavefront center
along the X axis:
IDL:

prop_rectangular_aperture, wave, 0.010, 0.005, 0.001, 0.0

Python: proper.prop_rectangular_aperture( wave, 0.010, 0.005, 0.001, 0.0 )
Matlab: wave = prop_rectangular_aperture( wave, 0.010, 0.005, 'XC', 0.001, 'YC', 0.0 );

See Also
PROP_CIRCULAR_APERTURE, PROP_CIRCULAR_OBSCURATION,
PROP_ELLIPSE, PROP_ELLIPTICAL_OBSCURATION,
PROP_RECTANGULAR_APERTURE,
PROP_RECTANGULAR_OBSCURATION

174

PROP_RECTANGULAR_OBSCURATION
Multiply the current wavefront by a rectangular obscuration (clear outside, dark inside). The rectangle is
antialiased (the value of an edge pixel varies between 0.0 and 1.0 in proportion to the amount of a pixel
covered by the rectangle).

NOTE: Prior to PROPER version 3.0, the manual incorrectly stated that when
using the NORM option, the X and Y widths of the rectangle were relative to the
beam diameter; they were (and are now) actually relative to the beam radius.

Syntax
IDL:

prop_rectangular_obscuration, wavestruct, xwidth, ywidth [, xoff, yoff]
[, /NORM] [, ROTATION=angle]

Python:

proper.prop_rectangular_obscuration( wavestruct, xwidth, ywidth [, xoff, yoff]
[, NORM=True/False] [, ROTATION=angle] )

Matlab:

wavestruct_out = prop_rectangular_obscuration( wavestruct_in, xwidth, ywidth
[, 'XC', xoff ] ['YC', yoff ] [, 'NORM' ] [, 'ROTATION', angle );

Returns
wavestruct_out (Matlab)
(Required) The modified wavefront structure.

Arguments
wavestruct (IDL)
wavestruct (Python)
wavestruct_in (Matlab)
(Required) The current wavefront structure.

xwidth, ywidth
(Required) Width of obscuration along X and Y image axes in meters, or if the NORM switch is set, the
widths in terms of the fraction of the beam radius at the current surface.

xoff, yoff (IDL)
xoff, yoff (Python)
(Optional) X and Y axis offsets of the rectangle center from the center of the wavefront grid. These are
specified in meters unless the NORM switch is set, in which case they are in fractions of the current beam
radius. By default, the rectangle is centered at the center of the wavefront grid.

175

Keywords and Switches
/NORM (IDL)
NORM=True or False (Python)
'NORM' (Matlab)
(Optional) Switch specifying that the X and Y widths and X and Y centers are specified as fractions of
the beam radius. By default, they are in meters.

ROTATION=angle (IDL)
ROTATION=angle (Python)
'ROTATION', angle (Matlab)
(Optional) Specifies the degrees counter-clockwise to rotate the rectangle around its center.

'XC', xoff
'YC', yoff (Matlab)
(Optional) X and Y axis offsets of the rectangle center from the center of the wavefront grid. These are
specified in meters unless the NORM switch is set, in which case they are in fractions of the current beam
radius. By default, the rectangle is centered at the center of the wavefront grid.

Examples
Multiply the wavefront by a rectangular obscuration with a width of 5 mm and height of 3 mm:
IDL:

prop_rectangular_obscuration, wavefront, 0.005, 0.003

Python:

proper.prop_rectangular_obscuration( wavefront, 0.005, 0.003 )

Matlab:

wavefront = prop_rectangular_obscuration( wavefront, 0.005, 0.003 );

See Also
PROP_CIRCULAR_APERTURE, PROP_CIRCULAR_OBSCURATION,
PROP_ELLIPSE, PROP_ELLIPTICAL_OBSCURATION,
PROP_RECTANGULAR_APERTURE,
PROP_RECTANGULAR_OBSCURATION

176

PROP_RESAMPLEMAP
Resample a map using cubic convolution interpolation onto a grid with the same size and sampling as the
current wavefront. Optionally, shift the map. In IDL, the resampled map replaces the input map. Note that
the map is not applied to the wavefront. The input map must be defined over a large enough region that when
resampled and shifted there will be no extrapolated values inside the beam.

Syntax
IDL:

prop_resamplemap, wavestruct, map, sampling [, xc, yc [, xshift, yshift] ]

Python: new_map = proper.prop_resamplemap( wavestruct, old_map, sampling
[, xc, yc [, xshift, yshift] ] )
Matlab: new_map = prop_resamplemap( wavestruct, old_map, sampling
[, xc, yc [, xshift, yshift] ] );

Returns
new_map (Python)
new_map (Matlab)
(Optional) The resampled map.

Arguments
wavestruct
(Required) The current wavefront structure. Used to obtain the current wavefront sampling.

map (IDL)
old_map (Python)
old_map (Matlab)
(Required) The variable containing the map to be resampled. In IDL this will be replaced by the
resampled map. The input map must be defined over a large enough region that when resampled and
shifted there will be no extrapolated values inside the beam.

sampling
(Required) The spacing between points in the input map in meters

xc, yc

(Optional) The pixel coordinates in the input map where the wavefront is centered (the 1 st pixel is
centered at (0.0,0.0)). The default assumes it is centered on the central pixel.

xshift, yshift
(Optional) The amounts to shift the map in meters in the wavefront coordinate system

See Also
PROP_ERRORMAP, PROP_READMAP

177

PROP_ROTATE
Rotate and shift a real-valued or complex-valued image via interpolation.

Syntax
IDL:

new_image = prop_rotate( old_image, angle
[, CUBIC=value] [, MISSING=value]
[, XC=value, YC=value] [, XSHIFT=value, YSHIFT=value] )

Python: new_image = proper.prop_rotate( old_image, angle
[, CUBIC=True/False] [, MISSING=value]
[, XC=value, YC=value] [, XSHIFT=value, YSHIFT=value] )
Matlab: new_image = prop_rotate( old_image, angle
[, 'CUBIC'] ['METH', string] [, 'MISSING', value]
[, 'XC', value] [, 'YC', value] [, 'XSHIFT', value] [, 'YSHIFT', value] );

Returns
new_image
(Required) Rotated, and possibly shifted, two-dimensional image.

Arguments
old_image
(Required) Two-dimensional, real-valued image to be rotated and/or shifted.

angle
(Required) The angle to rotate the image, measured counter-clockwise in degrees.

Keywords and Switches
CUBIC=value (IDL)
(Optional) Specifies that cubic convolution interpolation is to be used (bilinear is the default). The value
of this keyword affects the shape of the convolution kernel. It is usually recommended to be set to -0.5
(see the INTERPOLATE function description in the IDL Reference Manual).

CUBIC=True or False (Python)
(Optional) Specifies that Python's map_coordinates function with order=3 be used for interpolation
(bilinear is the default).

'CUBIC' (Matlab)
(Optional) Specifies that Matlab's interp2 function with the 'CUBIC' (cubic convolution) method be used
for interpolation (bilinear is the default). This is the same as using 'METH','cubic'.

178

'METH', value (Matlab)
(Optional) String specifying the type of interpolation that Matlab's interp2 function will use (options are
'cubic', 'nearest', 'linear', 'spline'; the default is 'linear' for bilinear interpolation).

MISSING=value (IDL)
MISSING=value (Python)
'MISSING', value (Matlab)
(Optional) Specifies the value of extrapolated data points.

XC=value, (IDL)
YC=value
XC=value, (Python)
YC=value
'XC', value, (Matlab)
'YC', value
(Optional) Center of rotation in image pixels; (0,0) is the center of the 1st pixel. If not specified, the
center of rotation is assumed to be (n/2,n/2).

XSHIFT=value, (IDL)
YSHIFT=value
XSHIFT=value, (Python)
YSHIFT=value
XSHIFT=value, (Matlab)
YSHIFT=value
(Optional) Amount in pixels to shift the image after rotation.

See Also
PROP_RESAMPLEMAP

179

PROP_ROUNDED_RECTANGLE
Return a two-dimensional image, with the same dimensions as the current wavefront array, containing a
rectangular mask (0 outside, 1 inside) with rounded corners. This routine was created for modeling the TPFC primary mirror. Note that the mask is not applied to the current wavefront.

Syntax
IDL:

array = prop_rounded_rectangle( wavestruct, corner_radius, width, height [, xc, yc] )

Python: array = proper.prop_rounded_rectangle( wavestruct, corner_radius, width, height [, xc, yc] )
Matlab: array = prop_rounded_rectangle( wavestruct, corner_radius, width, height
[, 'XC', xc] ['YC', yc] );

Returns
array
(Required) The 2D array containing the rounded rectangle.

Arguments
wavestruct
(Required) The current wavefront structure, used to obtain sampling and grid size information.

corner_radius
(Required) The radius in meters of a rounded corner (90º section of a circle).

width, height
(Required) The width and height in meters of the rectangular mask.

xc, yc (IDL)
xc, yc (Python)
(Optional) The offset in meters of the center of the rectangle from the center of the optical axis.

Keywords and Switches
'XC', xc (Matlab)
'YC', yc
(Optional) The offset in meters of the center of the rectangle from the center of the optical axis.

See Also
PROP_RECTANGLE

180

PROP_RUN
Execute a prescription containing PROPER procedure calls. For running a prescription in parallel, see
PROP_RUN_MULTI.

Syntax
IDL:

prop_run, prescription, result, wavelength, gridsize [, sampling_m]
[, PASSVALUE=value] [, /PHASE_OFFSET] [, /PRINT_INTENSITY]
[, /QUIET] [, /TABLE] [, /VERBOSE]

Python: ( result, sampling_m ) = proper.prop_run( prescription, wavelength, gridsize
[, PASSVALUE=value] [, PHASE_OFFSET=True/False]
[, PRINT_INTENSITY=True/False] [, QUIET=True/False]
[, TABLE=True/False] [, VERBOSE=True/False] )
Matlab: result = - OR [ result, sampling_m ] =
prop_run( prescription, wavelength, gridsize [, 'PASSVALUE', value]
[, 'PHASE_OFFSET'] [, 'PRINT_INTENSITY'] [, 'QUIET']
[, 'TABLE'] [, 'VERBOSE'] );

Returns
result (Python)
result (Matlab)
(Required) A variable to hold the result of the propagation.

sampling_m (Python)
sampling_m (Matlab)
(Required) A variable in which the sampling of the result in meters is returned (assuming the user has
set the corresponding parameter in the prescription).

Arguments
prescription
(Required) The name of the procedure containing the calls to PROPER routines. The file containing this
procedure must have the same rootname and a .pro suffix for IDL, .py suffix for Python, or .m suffix
for Matlab.

result (IDL)
(Required) A variable to contain the result of the propagation.

wavelength
(Required) Either the wavelength in microns at which to propagate, or the name of a text file containing
a list of wavelength (microns) and weight pairs, one pair per line. In the latter case, the prescription is
run once for each wavelength in the list and the result is the weighted sum of the individual results.

181

gridsize
(Required) The size of the two-dimensional grid that describes the wavefront (gridsize by gridsize
pixels). This must be a factor of 2 (e.g. 1024, 2048, 4096).

sampling_m (IDL)
(Optional) Variable in which the sampling of the result in meters is returned (assuming the user has set
the corresponding parameter in the prescription).

Keywords and Switches
PASSVALUE=value (IDL)
PASSVALUE=value (Python)
'PASSVALUE', value (Matlab)
(Optional) This keyword is set to variable (which could be a structure containing many variables; in
Matlab it needs to be a structure) that is passed to the prescription for use within the prescription. This
is useful for passing parameters in addition to the required ones. In IDL the prescription can change and
return the value in the same variable. In Python the value must be a dictionary.

/PHASE_OFFSET (IDL)
PHASE_OFFSET=True or False (Python)
'PHASE_OFFSET' (Matlab)
(Optional) If set, the wavefront phase at all points will be incremented by the propagation distance. By
default, there is no offset.

/PRINT_INTENSITY (IDL)
PRINT_INTENSITY=True or False (Python)
'PRINT_INTENSITY' (Matlab)
(Optional) If set, the total intensity of the wavefront will be printed out after each propagation.

/QUIET (IDL)
QUIET=True or False (Python)
'QUIET' (Matlab)
(Optional) If set, intermediate messages will be suppressed.

/TABLE (IDL)
TABLE=True or False (Python)
'TABLE' (Matlab)
(Optional) If set, a table of the beam dimensions and sampling at each surface will be displayed on the
terminal.

/VERBOSE (IDL)
VERBOSE=True or False (Python)
'VERBOSE' (Matlab)
(Optional) If set, status messages from the PROP propagation routines will be printed.

Examples

182

Run the prescription in ‘telescope.pro’ at a wavelength of 0.5 μm using a wavefront gridsize of 1024 by
1024:
IDL:

prop_run, ’telescope’, psf, 0.5, 1024, sampling

Python:

(psf, sampling) = proper.prop_run( ’telescope’, 0.5, 1024 )

Matlab:

[psf, sampling] = prop_run( ’telescope’, 0.5, 1024 );

Run the prescription in ‘telescope.pro’ at a wavelength of 0.5 μm using a wavefront gridsize of 1024 by
1024, returning the final sampling in the variable scale and passing the focal length and diameter via the
PASSVALUE keyword:
IDL:
prop_run, ’telescope’, psf, 0.5, 1024, scale, PASSVALUE={fl:10.0, diam:2.0}

Python:
(psf, scale) = proper.prop_run( ’telescope’, 0.5, 1024,
PASSVALUE={'fl':10.0, 'diam':2.0} )

Matlab:
pars.fl = 10.0;
pars.diam = 2.0;
[psf, scale] = prop_run( ’telescope’, 0.5, 1024, 'PASSVALUE', pars );

183

PROP_RUN_MULTI
Execute multiple instances in parallel of a prescription containing PROPER procedure calls.

Syntax
IDL:

prop_run_multi, prescription, result, wavelength, gridsize [, sampling_m]
[, /NO_SHARED_MEMORY] [, PASSVALUE=value] [, /PHASE_OFFSET] [, /QUIET]

Python: ( result, sampling_m ) = proper.prop_run_multi( prescription, wavelength, gridsize
[, NCPUS=value] [, PASSVALUE=value] [, PHASE_OFFSET=True/False]
[, QUIET=True/False] )
Matlab: result = - OR [ result, sampling_m ] =
prop_run_multi( prescription, wavelength, gridsize [, 'PASSVALUE', value]
[, 'PHASE_OFFSET'] [, 'QUIET'] );

Returns
result (Python)
result (Matlab)
(Required) A variable to hold the result of the propagation. This will be a three-dimensional array where
the 3rd dimension is equal to the number of wavelengths and/or optional parameter (PASSVALUE)
entries. This is part of the ( result, sampling_m ) tuple.

sampling_m (Python)
sampling_m (Matlab)
(Required) A variable in which the sampling of the result in meters is returned (assuming the user has
set the corresponding parameter in the prescription). This is an array of size equal to the number of
wavelengths and/or optional parameter (PASSVALUE) entries. This is part of the ( result, sampling_m )
tuple.

Arguments
prescription
(Required) The name of the procedure containing the calls to PROPER routines. The file containing this
procedure must have the same rootname and a .pro suffix for IDL, .py suffix for Python, or .m suffix
for Matlab.

result (IDL)
(Required) A variable to hold the result of the propagation. This will be a three-dimensional array where
the 3rd dimension is equal to the number of wavelengths and/or optional parameter (PASSVALUE)
entries.

wavelength
(Required) The wavelength in microns at which to propagate. This may be an array of values, in which
case the prescription will be run in parallel for each wavelength. If it is a single value, then the
PASSVALUE parameter should contain multiple entries.

184

gridsize
(Required) The size of the two-dimensional grid that describes the wavefront (gridsize by gridsize
pixels). This must be a factor of 2 (e.g. 1024, 2048, 4096, etc.).

sampling_m (IDL)
(Optional) A variable in which the sampling of the result in meters is returned (assuming the user has
set the corresponding parameter in the prescription). This is an array of size equal to the number of
wavelengths and/or optional parameter (PASSVALUE) entries.

Keywords and Switches
/NO_SHARED_MEMORY (IDL)
As of PROPER Version 3.0, the default method for transferring the wavefront array from the child
process is via shared memory. Previously it was done via a variable, which took longer (especially for
large arrays) due to the need to make an additional copy. There have been instances where in other
programs the author has had the machine hang when the shared memory was being freed (this was on
v8.3 IDL in Linux). Setting this switch uses the old slow-but-safe method.

NCPUS=value (Python)
(Optional) Integer that sets the maximum number of CPUs that can be used. This should be equal to or
greater than the number of entries in wavelength or PASSVALUE.

PASSVALUE=value
(Optional) This keyword is set to variable (which could be a structure containing many variables) that is
passed to the prescription for use within the prescription. This is useful for passing parameters in
addition to the required ones. The prescription CANNOT return the value in the same variable (this is
allowed with IDL in PROP_RUN). If this is an array, the prescription will be run in parallel for each
entry. If both this and wavelength are arrays, they must have the same number of entries and each
PASSVALUE entry will be used for the corresponding wavelength entry. In Python the value must be a
dictionary or list of dictionaries (one for each process running in parallel).

/PHASE_OFFSET (IDL)
PHASE_OFFSET=True or False (Python)
'PHASE_OFFSET' (Matlab)
If set, the wavefront phase at all points will be incremented by the propagation distance. By default,
there is no offset.

/QUIET (IDL)
QUIET=True or False (Python)
'QUIET' (Matlab)
If set, intermediate messages will be suppressed.

185

PROP_SHIFT_CENTER
Shift an n by n array by (n/2, n/2) in X and Y (n must be even). This effectively shifts the center of an image
to the lower-left corner of the array.

Syntax
IDL:

result = prop_shiftcenter( image )

Python: result = proper.prop_shiftcenter( image )
Matlab: result = prop_shiftcenter( image );

Returns
result
The two-dimensional shifted array.

Arguments
image
The two-dimensional array to be shifted.

186

PROP_STATE
Save the current propagation state to a file for the current wavelength, if one does not already exist. If one
does exist, read it in and use it to define the current wavefront. See the section “Save States” for more
information. This cannot be used with PROP_RUN_MULTI.

Syntax
IDL:

prop_state, wavestruct

Python: proper.prop_state( wavestruct )
Matlab: wavestruct_out = prop_state( wavestruct_in );

Returns
wavestruct_out (Matlab)
The new wavefront structure (usually the same variable as the input wavefront structure).

Arguments
wavestruct (IDL)
wavestruct (Python)
wavestruct_in (Matlab)
The current wavefront structure.

See Also
PROP_END_SAVESTATE, PROP_INIT_SAVESTATE,
PROP_IS_STATESAVED

187

PROP_USE_FFTI (IDL, Python)
In IDL, this enables or disables the use of the Intel Math Kernel Library FFT routine instead of the built-in
one for propagation. The changes remain in effect over multiple IDL sessions until otherwise altered by
calling this routine.
In Python, this checks that the Intel Math Kernel Library has been installed and is available for use, then
enables it.

Syntax
IDL:

prop_use_ffti [, /DISABLE]

Python:

proper.prop_use_ffti( [DISABLE=True] [, MKL_DIR=directory] )

Keywords and Switches
/DISABLE (IDL)
DISABLE=True (Python)
Disables the Intel MKL routines so that the built-in FFT (Numpy for Python) is used instead.

MKL_DIR=directory (Python)
Specifies the directory containing the MKL libraries. If not given, PROPER will look in the
following directories:

Linux, Unix, MacOS: /opt/intel/mkl/lib/intel64
Windows:
C:/Program Files(x86)/IntelSWTools/compilers_and_libraries/
windows/mkl/lib/intel64

See Also
PROP_COMPILE_FFTI

188

PROP_USE_FFTW
In IDL, this enables or disables the use of the FFTW library’s FFT routine instead of the built-in IDL one for
propagation. The changes remain in effect over multiple IDL sessions until otherwise altered by calling this
routine. If the Intel Math Kernel Library FFT is also enabled, then it will be used instead of FFTW.
In Python, this checks that the FFTW library has been installed and is available for use, then enables its use.
The pyFFTW package is used.

Syntax
IDL:

prop_use_fftw [, /DISABLE]

Python:

proper.prop_use_fftw( [DISABLE=True] )

Keywords and Switches
/DISABLE (IDL)
DISABLE=True (Python)
Disables the FFTW routines so that the built-in FFT (Numpy for Python) is used instead.

See Also
PROP_COMPILE_FFTW, PROP_FFTW_WISDOM

189

PROP_WRITEMAP
Write an error map (surface, wavefront, or amplitude) to a FITS image file. Such a map can be read later by
PROP_ERRORMAP.

Syntax
IDL:

prop_writemap, map, filename [, /AMPLITUDE] [, /MIRROR] [, RADIUS_PIX=value]
[, SAMPLING=value] [, /WAVEFRONT]

Python: proper.prop_writemap( map, filename [, AMPLITUDE=True/False] [, MIRROR=True/False]
[, RADIUS_PIX=value] [, SAMPLING=value] [, WAVEFRONT=True/False] )
Matlab: prop_writemap( map, filename [, 'AMPLITUDE'] [, 'MIRROR']
[, 'RADIUS_PIX', value] [, 'SAMPLING', value] [, 'WAVEFRONT' ] );

Arguments
map
(Required) The error map to write out. Must be a two-dimensional real-valued array (not complex
valued).

filename
(Required) String name of the FITS file to write.

Keywords and Switches
/AMPLITUDE (IDL)
AMPLITUDE=True or False (Python)
'AMPLITUDE' (Matlab)
(Optional) Indicates map is an amplitude map. By default it is assumed to be a wavefront error map.
Only one of AMPLITUDE, MIRROR, or WAVEFRONT should be set.

/MIRROR (IDL)
MIRROR=True or False (Python)
'MIRROR' (Matlab)
(Optional) Indicates map is a surface error map for a mirror (1/2 the wavefront error). By default it is
assumed to be a wavefront error map. Only one of AMPLITUDE, MIRROR, or WAVEFRONT should be
set.

RADIUS_PIX=value (IDL)
RADIUS_PIX=value (Python)
'RADIUS_PIX', value (Matlab)
Specifies the beam radius in units of map pixels. If given, the value of SAMPLING is ignored. When
the file is read by PROP_ERRORMAP, the map will be resampled to match the current beam size.

SAMPLING=value (IDL)
SAMPLING=value (Python)
'SAMPLING', value (Matlab)
190

The sampling of the map in meters; ignored if RADIUS_PIX is defined. The file header keyword
PIXSIZE will be set to this value.

/WAVEFRONT (IDL)
WAVEFRONT=True or False (Python)
'WAVEFRONT' (Matlab)
(Optional) Indicates map is a wavefront error map. By default it is assumed to be a wavefront error map,
so this option is meant to allow the user to be clear about the map type. Only one of AMPLITUDE,
MIRROR, or WAVEFRONT should be set.

See Also
PROP_ERRORMAP, PROP_READMAP

191

PROP_ZERNIKES
Apply circular Zernike polynomial aberrations to the current wavefront phase or amplitude component. The
polynomial ordering established by Noll (J. Opt. Soc. Am., 66, 207 (1976)) is assumed. An arbitrary number
of polynomials normalized for an unobscured aperture can be used, or just the first 22 for a centrally obscured
aperture. Obscured Zernikes are used if the user specifies the eps parameter. The polynomial equations for
the unobscured Zernikes can be printed using PROP_PRINT_ZERNIKES.
The first 22 Zernike aberrations are:
Number
1
2
3
4
5
6
7
8
9
10
11

Name
Piston
X tilt
Y tilt
Focus
45º astigmatism
0º astigmatism
Y coma
X coma
Y clover (trefoil)
X clover (trefoil)
3rd order spherical

Number
12
13
14
15
16
17
18
19
20
21
22

Name
5th order 0º astigmatism
5th order 45º astigmatism
X quadrafoil
Y quadrafoil
5th order X coma
5th order Y coma
5th order X clover
5th order Y clover
X pentafoil
Y pentafoil
5th order spherical

The coordinate system used to define the aberration patterns is aligned with that in the wavefront array (X
and Y image axes). The azimuth of the aberration is measured counterclockwise from the +X axis. The
Zernikes are normalized over a circular aperture with a radius equal to that of the beam at the current surface
(the beam size returned by PROP_GET_BEAMRADIUS, which is not necessarily the illuminated beam
radius). The user can specify an alternative normalization radius with the RADIUS keyword.

Syntax
IDL:

prop_zernikes, wavestruct, zernike_num, zernike_val [, obscuration_ratio]
[, /AMPLITUDE] [, MAP=wfe] [ , NAME=string] [, /NO_APPLY] [, RADIUS=value]

Python:

[wfe =] proper.prop_zernikes( wavestruct, zernike_num, zernike_val [, obscuration_ratio]
[, AMPLITUDE=True/False] [ , NAME=string] [, NO_APPLY=True/False]
[, RADIUS=value] )

Matlab: wavestruct_out = - OR [ wavestruct_out, wfe ] = prop_zernikes( wavestruct_in, zernike_num, zernike_val
[, 'AMPLITUDE'] [, 'EPS', obscuration_ratio]
[ ,' NAME', string] [, 'NO_APPLY'] [, 'RADIUS', value] );

Returns
wavestruct_out (Matlab)
(Required) The modified wavefront structure.

wfe (Python)
wfe (Matlab)
(Optional) A variable that will contain the two-dimensional wavefront error map created by this
routine (values in meters RMS of wavefront error).

192

Arguments
wavestruct (IDL)
wavestruct (Python)
wavestruct_in (Matlab)
(Required) The current wavefront structure.

zernike_num
(Required) Scalar or 1D array of Zernike polynomial indices. The first 22 are listed above.

zernike_val
(Required) Scalar or 1D array of Zernike polynomial coefficients corresponding to the Zernike
polynomials indexed by zernike_num. The values must be in meters of RMS phase error or
dimensionless RMS amplitude error.

obscuration_ratio (IDL)
obscuration_ratio (Python)
(Optional) The central obscuration ratio (Dobscuration/Dpupil, valued 0.0 to 1.0). The default is 0.0 (clear
aperture). If specified, only the first 22 Zernike polynomials can be used. The Zernike polynomials will
be properly normalized for a centrally obscured aperture.

Keywords and Switches
/AMPLITUDE (IDL)
AMPLITUDE=True or False (Python)
'AMPLITUDE' (Matlab)
(Optional) Keyword that specifies that the Zernike values in zernike_val represent the wavefront RMS
amplitude (rather than phase) variation. The current wavefront will be multiplied by the generated map.

MAP=wfe (IDL)
(Optional) Keyword set to a variable that will contain the two-dimensional wavefront error map created
by this routine (values in meters RMS of wavefront error).

NAME=string (IDL)
NAME=string (Python)
'NAME', string (Matlab)
(Optional) Keyword set to a string containing the name of the current surface. If given, this routine will
print ‘Applying aberrations to name’ when it is called.

/NO_APPLY (IDL)
NO_APPLY=True or False (Python)
'NO_APPLY' (Matlab)
(Optional) If this switch is set, then an aberration map will be generated but not applied to the wavefront.
This is useful if you wish to create a map that will be applied later after some modification.

RADIUS=value (IDL)
RADIUS=value (Python)
'RADIUS', value (Matlab)

193

(Optional) Keyword specifying the radius in meters to which the Zernike polynomials are normalized.
If this is not specified, the pilot beam radius, returned by PROP_GET_BEAMRADIUS, is used.

Examples
Add 0.5 μm RMS of defocus (Z4) and 0.2 μm of X coma (Z8) to the current wavefront:
IDL:
Python:
Matlab:

prop_zernikes, wavefront, [4,8], [0.5,0.2]*1.0e-6
proper.prop_zernikes( wavefront, [4,8], [0.5,0.2]*1.0e-6 )
wavefront = prop_zernikes( wavefront, [4,8], [0.5,0.2]*1.0e-6 );

Add 0.5 μm RMS of defocus (Z4) to the current wavefront, and store the added wavefront error map into the
variable mp:
IDL:
Python:
Matlab:

prop_zernikes, wavefront, 4, 0.5e-6, MAP=mp
mp = proper.prop_zernikes( wavefront, 4, 0.5e-6 )
[wavefront, mp] = prop_zernikes( wavefront, 4, 0.5e-6 );

See Also
PROP_ADD_PHASE, PROP_ERRORMAP, PROP_NOLL_ZERNIKES,
PROP_PRINT_ZERNIKES, PROP_PSD_ERRORMAP, PROP_READMAP

194



Source Exif Data:
File Type                       : PDF
File Type Extension             : pdf
MIME Type                       : application/pdf
PDF Version                     : 1.7
Linearized                      : No
Warning                         : Info object (734 0 obj) not found at 4590149
EXIF Metadata provided by EXIF.tools

Navigation menu