Optimization of Exit Pupil Expander with 2D out-coupler


In this article, a simulation method with an example of optimization and tolerance analysis for an exit pupil expander (EPE) system using 2D grating as out-coupler has been proposed and demonstrated.
In this workflow, we will use 3 software for different part of works to achieve the large goal of optimizing the system. First, we use Lumerical to construct the grating model and simulate with RCWA. Second, we build the full EPE system in OpticStudio and dynamically link to Lumerical for integrating accurate grating model. Finally, optiSLang is used to overall control to both software to do the system level optimization by modifying the grating model to achieve the required optical performance in the whole EPE system.

Author: Michael Cheng


Article attachments


We will first construct the simulation system in Lumerical and OpticStudio, which are dynamically linked (See Dynamic workflow between Lumerical RCWA and Zemax OpticStudio). Then the OpticStudio is linked to optiSLang, via Python node, to be optimized, as shown in Figure 1.


Figure 1 Lumerical is linked to OpticStudio via dynamic link. OpticStudio is linked to optiSLang via Python node. The optimization is controlled by optiSLang.

As shown in Figure 2, the EPE system includes two gratings for in-coupler and out-coupler. The out-coupler grating is separated into several tiles, as shown at left side. Each of the tile will be optimized to have different grating shapes. The right shows how the light changes its propagation in k-space. See the following article for more information about the k-space: How to simulate exit pupil expander (EPE) with diffractive optics for augmented reality (AR) system in OpticStudio: part 1


Figure 2 Grating layout on the waveguide and in k-space.

Step 1: System set-up (Lumerical)

In attached files, two .fsp files are provided. The first grating is as shown in Figure 3, which is a binary grating used in in-coupler grating. The structure of this grating is fixed and won’t change during optimization.


Figure 3 The structure in in-coupler grating is a binary grating.

The second .fsp file is as shown in Figure 4, which is a parallelogram pillar with 7 variables. Each of the tile in the out-coupler will be optimized with different set of variable combinations during optimization. More information about optimization settings will be explained in Optimization Set-up section.


Figure 4 The structure in out-coupler grating is a parallelogram pillar.

The two .fsp files will be used by OpticStudio for simulating the whole EPE system via dynamic link. For more information about dynamic link, see Dynamic workflow between Lumerical RCWA and Zemax OpticStudio.

Step 2: System set-up (OpticStudio)

As shown in Figure 5, in this system, a collimated beam is incident on the in-coupler grating, propagating through the waveguide, and be coupled out with the second grating. The eye box is at the farther part of the second grating. The goal of optimization is to optimize the uniformity and total power received by the eyebox .


Figure 5 Initial EPE system and the irradiance at eye pupil.

In attached file, one .zar file for the whole EPE system in OpticStudio is provided. As shown in Figure 6, only the half of the tiles in second grating is built. This is because the system has symmetricity. It can be seen in Figure 7 that the parameter Mirroring for the detector is set to 1, which means the detector will always mirror the result between -x and +x half parts during the raytracing. By doing this we can get same simulation with only half of the rays.


Figure 6 EPE system setting in OpticStudio.


Figure 7 The parameter Mirroring of the detector is set to 1, which means mirroring in x direction is assumed for this detector.

It can be seen that all grating objects in the system have been set with the dynamic link DLL, as shown in Figure 8.


Figure 8 Dynamic link DLL is loaded for gratings in the EPE system.

Step 3: Optimization set-up (optiSLang )

3-1. Python to evaluate the system

In the attached file, a python file EPE_2D_for_optiSLang.py is prepared for linking optiSLang to OpticStudio. It’s very useful to use a python code to link the optimizer, coming with Ansys optiSLang, and the solver, Ansys Zemax OpticStudio + Ansys Lumerical . The benefit is high flexibility to do pre- and post-data-processing during each optimization cycle. In this section, the code structure will be explained.
The basic structure of the code is first generated from the button in OpticStudio, as shown in Figure 9. More information about writing API to access OpticStudio can be found in knowledge base articles in: Knowledgebase > Programming > API


Figure 9 Generate boilerplate for Python Interactive Extension code.

A few more modules are imported into the boilerplate. The modules numpy, scipy are used to do post-data-processing of the irradiance data from eyebox. The module matplotlib is used to draw and export irradiance on the eyebox for review later. The modules time and random are imported for timer to track the calculation time.


By trying to read the variable OSL_WORKING_DIR, we can know whether this Python code is called by optiSLang or performed manually. When the Python code is called by optiSLang, a few variables, which is called Environment Variable, will be created for passing some optiSLang information. Even though these variables are not defined in the Python file, they will be available when the code is called by optiSLang. For more information about the Environment Variable in optiSLang, see optiSLang User’s Guide.


In this Python code, there are 32 variables, such like clen1, h2, rot4, w1, and power, that are for optimization and supposed to be defined by optiSLang. As we will set these variables as parameter in optiSLang, during the sensitivity analysis or optimization process, optiSLang will automatically vary the value of them. On the other hand, if we directly run this Python code not from optiSLang, then the value of these variables will be constant as shown in the code below.


As shown in the Figure 10, the grating parameters of each tile are determined by first setting up the 4 corners and interpolating for other tiles. where ν is dC, dR, dL, θC, θR, θL, h, and n is 1,2,3,4, which corresponds to the tiles at the 4 corners. With this equation, the 7 grating parameters at each tile can be controlled by the parameters at the 4 corners with some weighting (wn) and non-linear power (p).



Figure 10 The parameters in the central tiles are interpolated from the 4 corners.

optiSLang varies these parameters using the predefined optimization algorithm. The varied parameter values are set in the python code, which will further set up the parameters of each grating tile in OpticStudio . In this process, the Python code plays a role to do the work converting these variables into the exact parameters in the OpticStudio. This pre-data-processing is only possible when we optimize the system with optiSLang. It could not be done using the built-in optimizer in OpticStudio. In this way, optiSLang can optimize the system based on some virtual or high-level variables that are not directly exposed in the OpticStudio UI.

After setting up the parameters, we then trace rays with the following piece of code.


Another benefit of using optiSLang to optimize the system is post-data-processing. In this optimization process, we don’t directly optimize the irradiance distribution on the eye box. Instead, we first do a convolution on the irradiance distribution with a pupil function, as shown in Figure 11, and then set the optimization target to the uniformity of this convolved result. The x and y axis of this result can be explained as the human eye shift in the eye box. And the z axis (color bar) is the average irradiance seen by the human eye.



Figure 11 A convolution is performed on the irradiance distribution with the pupil function.
Based on the convolved result, we can then calculate the contrast, total power, and uniformity as shown below.


The code for these criteria is defined as below. In this case, we mainly want to optimize for Contrast and Total Power. The function of Uniformity is similar to Contrast, where both want the irradiance on the eye box to be uniform. Although they are for the same goal, they use different definitions, so we consider both here.


The final part of the Python code, as shown below, draws the result of the irradiance at eye box and its convolved result. And then export pictures. This is useful for users to check how the irradiance looks like for each of the optimized systems directly within optiSLang postprocessing.


3-2. Parametric system

After the Python code is prepared, we can start to plan for the optimization in optiSLang. The first step is open an empty file in optiSLang, drag the Solver wizard, drop in scenery, then select Python integration.


As shown below, a window of the wizard will pop up showing the Python code. We will first set up the parameters by right clicking on variables, such like clen1, and select Use as parameter. We will do this for all variables from clen1 to power. As shown below, the selected variables will be shown as the left column “Parameter”. You can also use the shortcut "Ctrl+P".


After the parameter has been set up, we should test whether the Python code can be successfully run. To do this, we should open OpticStudio and turn on the Interactive Extension mode as below. Then in the Solver Wizard, we can click the down arrow and select “Test run with inputs” as shown below. If it works well, you should see, in OpticStudio window, the dialog of Interactive Extension will show as “Connected”.


If the test run fails, one of the possible reasons is that the Python environment is not adequate. Users can change the setting as below to see if the problem can be fixed.


After the calculation is done, which takes 13 minutes in our test, we should see message in the log saying, “Manual test run successfully processed.”, as shown below. Now if we go to the ***.opd folder, which is easily accessible via rightclick on the systems head and select “show working directory”, we can find the irradiance is exported in the folder “\Parametric_solver_system\design_data\”, which is the path specified in the Python code.


Now similar to the setting of parameter, we can do the same for the response. Here we will right click on the variables “Uniformity”, “Contrast”, and “TotalPower” in the Python code and select “Use as response”. Then the 3 variables will be shown in the right-side column of Responses.


The next page in the wizard will ask users to define the reference value and the range of each parameter. The Reference value will just follow the settings when we set the parameters in previous step. The range is up to the designer’s decision and there is no standard answer. Users can check the attached optiSLang file in the download link to check how the range is determined in this optimization. Note this range is absolute. During optimization, the parameters will not break the boundary. This is different to what we usually expect in the Zemax OpticStudio’s optimizer.


In the next page of the wizard, we need to setup the criteria based on the given response. As shown below, we can just drag the response to the bottom side to set a constrain or objective. In this case, we have set up 3 objectives to minimize contrast, uniformity, and maximize total power. We can also set up 2 constrains for contrast and total power to tell optiSLang to avoid some extreme cases where the result is uniform while the total power is extreme low, or the opposite case.


There is nothing to do on the final page. After we click the Finish button, a parameter system will show in the workplace.


3-3. (Optional) Set-up for parallel computing

The actions in this section are not absolutely necessary. Here we show how to set up for parallel computing at optiSLang side for speed up the optimization. Users can consider doing this if you have more than one Lumerical FDTD solver license. To set up this, the first step is to right click on the parametric system block, select “Edit”, and then set the Limit maximum in parallel, as shown below, to 6 or any number that is not larger than 8 or the number of your total Lumerical FDTD solver license.


Note we need to do the same to right click on the Python node and select “Edit”. To set up the details, we need to first click on the upper right corner’s hamburger mark, check Properties & Placeholders, and then click the OK button. Then we can set up the MaxParallel to 6 as shown below. Note we also need to set up Maximum in parallel at lower part in the window to 6. If you set up this parameter first, the MaxParallel in above will also be automatically changed, but it’s safer to double check it’s set as expected.
Finally, it’s suggested to check Retry execution, set Number of retries to 20, and set Delay between attempts to 1000 ms. These settings avoid the race condition that optiSLang tries to access same OpticStudio instance with more than 1 thread.


Note that if the parallel settings is made more than one, when running optiSLang, we need to also open the same number of instances of OpticStudio, then optiSLang can create a thread for each of them.


3-4. Sensitivity & Optimization Settings

The next step is to set up the Sensitivity analysis. Sensitivity Analysis in general is a method to find out most important parameters, which have most influence on the response and generate best possible metamodels showing the relation between response and parameter variation to better understand the systems behavior.

Sensitivity system can be set up by dragging the wizard to the parametric system block as shown below. The parameters and criteria will be just copied, and we don’t need to set again. By default, it will suggest the AMOP model, and we can keep this setting. The AMOP is an iterative sampling method, which samples designs into the design space until a target criterion has been reached – either maximum of designs or model quality. Due to high non-linearities in this case no sufficient model quality has been reached, so that in next stage real run optimization will be conducted.


Similarly, we will drag Optimization wizard to the AMOP block for conducting an optimization. Note when it asks the optimization method, we should select Real Run because this system will never have high-quality Metamodel of Optimal Prognosis (MOP). MOP is proposed in (Most and Will 2008) which is based on the search for the optimal input variable set and the most appropriate approximation model (polynomial or MLS with linear or quadratic basis). For the optimization algorithm, it’s suggested to use Evolutionary Algorithm, which is suitable for very non-uniform and discontinuous solution space.


3-5. Start optimization

To start the optimization, users just need to open an OpticStudio and make it ready in the Interactive Extension mode. The Auto Close on Disconnect must be unchecked as shown below. Note if the parallel computation has been set, for example to 6, as explained in above, users will need to open the same number (ex. 6) of instances here and optiSLang will access all of them at the same time.


When all are ready, we can then click the button to optimize.


In this example system, we took about 2~3 days to run this optimization on a normal desktop PC.

3-6. Optimization result

The optimization result can be found by double clicking the Postprocessing extended from the Evolutionary Algorithm block, as shown below.


The red marked designs in the following plot is called Pareto front. A Pareto front in general shows up the tradeoff between multiple objectives, where no design dominates the other one regarding performance. That means all of these designs show up different balances of multiple criteria. We have picked up 3 results and shows them as below. No. 986 has higher contrast than No. 946, while it looks more uniform. This means criteria used here could be improved to match better to human vision.


Next steps

There are some considerations that are not covered in this demonstration, but users could pay more attention when they try to follow this process for their systems.

  • In this demonstration, we only consider the central field, which is a collimated beam normally incident on the waveguide. For a more comprehensive optimization, more fields can be added to cover the uniformity in full field of view.
  • Similarly, the system is only designed for single wavelength. Depending on the system design, the optimization can include multiple wavelengths.
  • Some irradiance distributions look more uniform but result in higher contrast. The criteria could be improved by modifying the Python code.


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



Please sign in to leave a comment.