Tolerancing the input beam when using POP

  • 9 February 2023
  • 8 replies


I’m working on a system in which I evaluate physical optics propagation (POP) for beams of several different sizes in the merit function. The way I change the beam size within POP is using a little macro I wrote (see snippet below). 

This code works well when I’m optimizing or playing with values in the LDE, but it does not yield consistent results when I run a tolerance analysis. I suspect this is because of how .cfg files are handled during tolerancing.

Is there a better way to modify the POP parameters so that they are modified appropriately during tolerancing? 


path$ = $PATHNAME() + "\"
lensFilename$ = $FILENAME()
lengthFilename = SLEN(lensFilename$)
shortenFilename$ = $LEFTSTRING(lensFilename$, lengthFilename-3)
cfgFilename$ = path$ + shortenFilename$ + "CFG"

w0_in = PVHX() # read in the HX parameter. "PV" is a mnenomic for "pass value"

! change the beam waist of the light coming from the fiber
MODIFYSETTINGS cfgFilename$, POP_PARAM1, w0_in # Waist X of beam definition tab
MODIFYSETTINGS cfgFilename$, POP_PARAM2, w0_in # Waist Y of beam definition tab



Best answer by Sandrine Auriol 16 February 2023, 11:32

View original

8 replies

Userlevel 6
Badge +2

Hi Liz,

When tolerancing, are you using more than 1 core:

Tolerancing by default, both during SA and MC, will make a copy of your optical system in memory and attempt to evaluate this optical system independent of the other optical systems. 

A ZPL script is always single-threaded.  The MODIFYSETTINGS keyword puts a write-lock on the file (if only for a few milliseconds) when trying to modify the settings and once it closes the file, it removes the lock.  When using either a script or a ZPLM, the single-threaded nature of the ZPL ensures that the MODIFYSETTINGS is correctly written and subsequent calls will always use the latest values written by the MODIFYSETTINGS keyword.

However, since the tolerancing makes multiple copies of the optical system, when both System A and System B call MODIFYSETTINGS from the ZPLM, there is no conflict resolution to make sure the POPD operand called from System A uses System A’s MODIFYSETTINGS; rather, the POPD will simply use the last values written to the POP.CFG.

So, if you want to keep your current workflow, I suggest dropping the # of Cores to 1; this is significantly increase your tolerancing time but you won’t have to change anything else about your workflow.  

If you need the speed of multi-threading for tolerancing, I would suggest using a stand-in for POPD.  Paul Colbourne has a really good method for using pure geometric rays to describe the beam envelope of a TEM00 beam which could be used as a stand-in for POP:

Using skew rays to model Gaussian beams - webinar – Knowledgebase (

Userlevel 7
Badge +3


It looks like you are using the Hx cell in a ZPLM merit function operand to pass a starting beam waist value to POP.  Just curious, how do you go about setting the Hx cell as a variable for optimization and/or tolerancing?  I can think of one possible approach, but I’m curious to know how you are doing it.



Hi @Jeff.Wilde , 

That’s right -- but I actually haven’t set the Hx cell as a variable. That would make the macro a lot more useful. Can you share how were you imagining going about that? 



Userlevel 7
Badge +3

Hi Liz,

Well, the approach I have in mind is a little bit convoluted, and I would prefer to test it before proposing it as a solution.  Let me see if I can do that in the next day or so.

Going back to your current situation, I assume then you are using the ZPLM operand to manually enter the starting beam waist size in the Hx cell, and subsequently conducting optimization and tolerancing with various POPD operands -- is that correct?  If so, why do you need the ZPLM operand in the first place when you could simply change/save the POP parameters via the GUI before optimization and/or tolerancing?  I don’t understand the need for the ZPLM operand.  Can you explain?




Userlevel 6
Badge +2

Hi Liz,

One way I could think of setting an input as a variable (which would be convoluted like Jeff said) would be to simply bypass the PVHX() function and rather to use the multi-config editor to set a dummy value as a variable.   It would be a 3 step process:

  1. Insert a Dummy Surface in the LDE with a value that doesn’t matter, like the Radius (the thickness could disrupt layout for instance).  T
  2. In the MCE, use the corresponding operand (CRVT) and set this as a variable
  3. In the ZPLM, use MCON() to get the value which can be used as an input to the ZPL as well as a variable which changes during the optimization.

If you then need to use this variable as an input to an operand, you can use OPEV().  If you have a long running operand which can benefit from multiple adjacent operands in the MFE, then you can use the SETOPERAND keyword for the Col 1-8 input values.  The row value in the SETOPERAND keyword would have to be hard-coded and the operand you modify must be below the ZPLM, otherwise the operand will update on the next (n+1) cycle and not on the current (n) cycle.

@Jeff.Wilde if you have a different approach, I would love to hear it because not having pickups or the ability to use the output from a previous operand as the input for a latter operand has been a long time frustration of mine.

Also, an approach I could see of using the waist as an input to the MODIFYSETTINGS via a PVHX would be to save multiple merit functions with different Hx (waist) values before running a tolerance and to use a TSC with LOADMERIT to evaluate the perturbed system for different size Gaussian beams (just my best guess as to a use case for something like this).

Userlevel 7
Badge +3

Hi Michael & Liz,

My idea is similar, although it uses two ZPLM operands (both with zero weights) and continues to use the PVHX function:

  1. Insert a dummy surface in the LDE.
  2. In the Multi-Configuration Editor (MCE) use a CRVT operand for this dummy surface as a placeholder for the starting beam waist value.  This will set one-over-the-radius-of-curvature for the dummy surface to be equal to the beam waist -- but doing so should have no impact on the optical path.
  3. Use one ZPLM operand to call a script that uses MCON to read the CRVT value from the MCE, then use a SETOPERAND keyword to transfer this value to the Hx cell in a subsequent ZPLM operand (like the one Liz is currently using, which reads the value with PVHX and transfers it to a configuration file).  Note: multiple POPD operands can then follow these two ZPLM operands, but yes, they should be contiguous so that multiple results can be extracted from a single POP run (per iteration), assuming of course multiple results are needed.  Otherwise, a single POPD operand may suffice.

Now the CRVT operand in the MCE (i.e., the starting POP beam waist) can be set as a variable for optimization, or it can be set as a parameter for tolerancing by using a TMCO operand in the Tolerance Data Editor.

It seems like this should work, but it is a bit convoluted with a lot of  “moving parts,” so it should be carefully tested to make sure it works properly before spending a lot of time generating results.  Also, since both optimization and tolerancing can be multi-threaded, I assume that 1 core should be selected in either case to avoid the problem Michael describes above.




Userlevel 6
Badge +2


I hope you don’t mind but I have modified the title of the original post so that it can be more easily found by other users. I think it is indeed an interesting topic. 
I had a similar question on support and worked on the same idea of using a dummy surface (I used a dummy Biconic surface and used the conic and X conic parameters of the biconic surface as the input values for the X and Y waist size) to optimize for the input beam. The difference was that we were using the skew gaussian beam operands GBS*.

Here is the macro for reference.


I haven’t tried it but for POP, it may work with a user operand in C# that modifies POP settings and then runs the analysis.

I’ve found this to be quite useful--but slow. I would very much appreciate if Zemax can implement a multi-core way to do this! If you do, please let me know! Thanks!