How to determine perturbation values in a Monte Carlo tolerance analysis

This article describes how to use a ZPL macro to determine the perturbation values assigned by OpticStudio during a Monte Carlo tolerance analysis. 

Authored By Sanjay Gangadhara


Article Attachments


Tolerance analysis is an important part of any optical design as it allows the designer to determine the sensitivity of the system to errors introduced during manufacturing. In OpticStudio, these errors may be introduced and analyzed either individually (Sensitivity) or simultaneously (Monte Carlo).

The goal of a Monte Carlo analysis is to determine the variation in system performance that arises from the simultaneous application of tolerance errors in a statistical fashion. However, there may be times when it is useful to know the values for the individual perturbations; for example, when some set of perturbations could be used to enhance system performance. We can analyze our system in such a way by saving the Monte Carlo files and extracting the data with a ZPL macro.

Step 1: Performing the Monte Carlo tolerance analysis

To analyze a large number of files - like what is output following a Monte Carlo analysis - it is best to use a ZPL macro. In the Knowledgebase article entitled "How to open consecutively-names lens files using a ZPL macro", information is given on how to extract information from multiple Monte Carlo files and can be expanded upon to obtain tolerance perturbations.

Tolerance perturbations may be significantly different from one system to the next. Thus, it is difficult to develop a macro that can be used to extract Monte Carlo data from any general file. In this article, a macro has been developed which is suited to a particular system that is being analyzed. However, the general structure of the macro can be replicated and subsequently customized for most files of interest.

The first step in creating the macro is to determine what tolerance perturbations will be evaluated in the system. The example used for this article is a Cooke triplet (as given in the sample file {Zemax}\Samples\Sequential\Objectives\Cooke 40 degree field.zmx. The default set of tolerances will be used in the analysis:

default tolerances

Some minor changes to this default set are made in the Tolerance Data Editor. Namely:

  1. Intermediate thickness compensation is turned off (Adjust = Surf for all of the TTHI operands)
  2. Surface decenters are referenced to the back surface for each lens (all TSDX and TSDY operands are deleted for surfaces 2, 4, and 6) 

The resulting system may be found in the archive (.ZAR) file attached to this article.

There are 41 perturbations to be analyzed in this system:

  • 6 radii of curvature
  • 5 thicknesses (back focal length is used as a compensator)
  • 3 element decenters in X
  • 3 element decenters in Y
  • 3 element tilts about X
  • 3 element tilts about Y
  • 3 surface decenters in X
  • 3 surface decenters in Y
  • 6 surface irregularities
  • 3 refractive indices
  • 3 Abbe numbers

corresponding to the 41 tolerance operands found in the Tolerance Data Editor. The macro we will write must therefore store data for these 41 perturbations for all of the Monte Carlo runs.

The Monte Carlo analysis is then performed. For this example, RMS spot radius is chosen as the tolerance criterion:


and 100 Monte Carlo runs are performed:

monte carlo runs

Remember that we must save each of the runs in order to extract the desired data. The name of the files will be “MC_Txxxx”, where "xxxx" corresponds to the run number.

Step 2: Creating the ZPL macro

The macro used to extract data from the Monte Carlo runs for this specific example is provided as an article attachment. The first step is to specify the number of runs that were performed:

! Define number of MC runs

n_files = 100

Data arrays are then defined to store the perturbation values for each run. Arrays are created to hold data for the 6 radii of curvature (trad1_per, …, trad6_per), the 5 thicknesses (tthi1_per, …, tthi5_per), etc. For surface irregularities – which in this example have been modeled using the TIRR operand – arrays are defined to store the magnitude of the irregularity (tirrm1_per, …, tirrm6_per) as well as the angle of the astigmatism error (tirra1_per, …, tirra6_per). An array is also defined to hold the criterion value for each run (tcrit_per).

The nominal values for each of the perturbations (radii, thicknesses, etc.) are then obtained, using the TOLV function:

! Get nominal values for radii, thicknesses, indices, and Abbe numbers
! (all other perturbations have a nominal value of zero)

trad1_nom = TOLV(3,90)
trad2_nom = TOLV(4,90)
trad3_nom = TOLV(5,90)
trad4_nom = TOLV(6,90)
trad5_nom = TOLV(7,90)
trad6_nom = TOLV(8,90)

tthi1_nom = TOLV(9,90)
tthi2_nom = TOLV(10,90)
tthi3_nom = TOLV(11,90)
tthi4_nom = TOLV(12,90)
tthi5_nom = TOLV(13,90)

tind1_nom = TOLV(38,90)
tind3_nom = TOLV(39,90)
tind5_nom = TOLV(40,90)

tabb1_nom = TOLV(41,90)
tabb3_nom = TOLV(42,90)
tabb5_nom = TOLV(43,90)

As indicated in the macro comment, in this case the nominal values are all zero for the perturbations not listed (e.g. element tilts).

We are now ready to loop through all of the Monte Carlo files and extract the desired perturbation data. We first get the full file name for our nominal file, so that we may re-load this file at the end of our loop:


We then define a string variable which will be the basis for the Monte Carlo file name (with full path included – we have assumed that all of the Monte Carlo files are in the same directory as the nominal file):


B$ = A$+"\MC_T"

A FORMAT keyword is then used to specify the format in which the file number will be added to the file name, to be consistent with the OpticStudio file numbering system:

FORMAT "%#04i" LIT

This keyword will only work is the number of Monte Carlo files is < 10,000, which seems like a reasonable limit for the type of analysis we are performing. Finally, the FOR loop is started:

FOR i, 1, n_files, 1

Inside of the loop, we generate the string which will contain the full file name of the Monte Carlo file, and then load the file using the LOADLENS keyword:

F$ = B$+$STR(i)+".zmx"

Then the desired data are extracted from the file, using the MFCN function (for the tolerance criterion value), the RADI function (for the radii of curvature), the THIC function (for the thicknesses), the PARM function (for the surface and element decenters, element tilts, and surface irregularities), and the SOLV function (for the refractive indices and Abbe numbers). In the appropriate cases, the nominal values were subtracted from the obtained value, in order to calculate the perturbation value.

We see that the surface number inputs to the macro functions have been hard-wired into the code, and that in all cases the surface numbers differ from the numbers in the nominal file. This is because the Monte Carlo file will have additional surfaces relative to the nominal file, in order to model element tilts and decenters (via Coordinate Break surfaces). To determine the appropriate surface numbers in each case, one of the Monte Carlo files was manually opened and investigated.

Once the FOR loop is complete, we reload the nominal file. We then print the results to the screen, using another FOR loop, with FORMAT keywords also placed to control the numerical precision of the printed data:

! Print the results

PRINT "Run#   Criterion       ",
PRINT "RAD1       RAD2       RAD3       RAD4       RAD5       RAD6       ",
PRINT "THI1       THI2       THI3       THI4       THI5       ",
PRINT "TEDX12     TEDY12     TETX12     TETY12     ",
PRINT "TEDX34     TEDY34     TETX34     TETY34     ",
PRINT "TEDX56     TEDY56     TETX56     TETY56     ",
PRINT "TSDX1      TSDX3      TSDX5      TSDY1      TSDY3      TSDY5       ",
PRINT "TIRRM1          TIRRM2          TIRRM3          ",
PRINT "TIRRM4          TIRRM5          TIRRM6          ",

PRINT "TIRRA1     TIRRA2     TIRRA3     TIRRA4     TIRRA5     TIRRA6       ",
PRINT "TIND1      TIND3      TIND5      ",
PRINT "TABB1      TABB3      TABB5"

FOR i, 1, n_files, 1

FORMAT "%#04i" LIT
PRINT i, "   ",
PRINT tcrit_per(i), " ",
PRINT trad1_per(i), " ", trad2_per(i), " ", trad3_per(i), " ",
PRINT trad4_per(i), " ", trad5_per(i), " ", trad6_per(i), " ",
PRINT tthi1_per(i), " ", tthi2_per(i), " ", tthi3_per(i), " ",
PRINT tthi4_per(i), " ", tthi5_per(i), " ",
PRINT tedx12_per(i), " ", tedy12_per(i), " ",
PRINT tetx12_per(i), " ", tety12_per(i), " ",
PRINT tedx34_per(i), " ", tedy34_per(i), " ",
PRINT tetx34_per(i), " ", tety34_per(i), " ",
PRINT tedx56_per(i), " ", tedy56_per(i), " ",
PRINT tetx56_per(i), " ", tety56_per(i), " ",
PRINT tsdx1_per(i), " ", tsdx3_per(i), " ", tsdx5_per(i), " ",
PRINT tsdy1_per(i), " ", tsdy3_per(i), " ", tsdy5_per(i), "    ",
FORMAT "%+10.5e" LIT
PRINT tirrm1_per(i), "   ", tirrm2_per(i), "   ", tirrm3_per(i), "   ",
PRINT tirrm4_per(i), "   ", tirrm5_per(i), "   ", tirrm6_per(i), "   ",
PRINT tirra1_per(i), " ", tirra2_per(i), " ", tirra3_per(i), " ",
PRINT tirra4_per(i), " ", tirra5_per(i), " ", tirra6_per(i), " ",
PRINT tind1_per(i), " ", tind3_per(i), " ", tind5_per(i), " ",
PRINT tabb1_per(i), " ", tabb3_per(i), " ", tabb5_per(i), " "


Finally, the memory associated with each array is freed using the RELEASE keyword.

This macro is customized for the file provided in this example. However, the basic structure of the macro may remain the same even as the file and/or the tolerance perturbations differ.

Step 3: Running the macro

Once the macro is complete, it can be run to generate the desired data:

text viewer

This output may then be sent to a text file for future analysis.

text viewer 2nd picture



Was this article helpful?
0 out of 0 found this helpful



Please sign in to leave a comment.