Solved

Run python code from macro and get output variables to OS macro


Hi, 


I would like to run a python script from a macro (could be a python executable), then get some of the outputed values in the macro. Then I would like to write this values in a merit function from the macro.


Is there a simple way to do it or some starting code?


Thanks,


Jose

icon

Best answer by David.Nguyen 17 June 2021, 18:49

View original

18 replies

Userlevel 7
Badge +2

Hi Jose,


Is there a particular reason you want the Python code to execute from the macro? Cause, in my mind, the most straight-forward way is perhaps to create a standalone ZOS-API application, such that OpticStudio runs within your Python code.


Otherwise, you might want to have a read at that article from Michael Humphreys.


Take care,


David

Hi David, 


Thank you for your swift reply. I am new to the Python-Zemax ZOS-API so it might be better to do a standalone as you suggest, also in the long run. I have already written some code in the Zemax macro, so I thought I would need to learn fewer new commands using the inherent mode .


I also thought that in the specific case of what I want to do, it would be more compact: 



  1. The macro runs the POP for different configurations and saves the data

  2. It calls Python to perform calculations with the saved data and imports some output variables from Python

  3. It uploads the values to a merit function

  4. This macro will be used also for tolerancing with merit function . The merit function will call the script. 


I would appreciate also recommendations of articles or starting code with the standalone application. 


Cheers,


Jose


 

Userlevel 7
Badge +2

Hi Jose,


In my personal opinion, ZOS-API is the way forward. The learning curve is steeper, but I think its worth the time thanks to the possibilities offered by the Python environment, or any other ZOS-API langage.


You can call a macro from ZOS-API, but its a bit glitchy. To the best of my knowledge, you have to make the macro as a solve, and return a dummy value. Otherwise, if time permits, you can translate your macro to ZOS-API entirely.


I will try to prepare an example following your requirements.


Take care,


David

Userlevel 7
Badge +2

Hi Jose,


Have a look at this Standalone Application.


The main things I changed from the template are the imports in line 3-4 (Numpy, and MatPlotLib) and line 151 until the end of the file.


The application needs the Double Gauss file, which is installed by default (unless you moved it elsewhere), and a dummy macro I created to emulate your own macro, which you can find in the repository.


I tried to answer your points with comments in my code. I didn't quite understood what you mean with 4., but maybe you can clarify later on.


Take your time to review the code, its not your average ZOS-API snipet, but I hope it can serve other people on the forums as well, and we can discuss it later on.


The main takeaway is that, in my opinion, you can move away from ZPL, and implement everything with the ZOS-API.


As for resources, I recommend starting with the 5-hour learning path Getting Started with the ZOS-API. Then, I find the search box in My.Zemax to be quite good to find relevant knowledgebase articles, and other forum posts. Finally, try to get use to the way the ZOS-API Syntax Help is structured. Ultimately, this is your best resource for the ZOS-API.


I hope this helps. Take care,


David

Hi David, 


Thank you very much for the great help. I will carefully review the material you've sent and see if I can get a simple example to work.


I will then let you know if I have some difficulties. 


Cheers,


Jose

Hi David, 


I have now succesfully implemented the Python ZOS-API based on the code you sent, thanks. I would like to add that this link was also helfpul to get started quickly with POP


https://my.zemax.com/en-US/forum/threads/ffbc91cf-2aff-ea11-a815-000d3a36886f


My Python script does the following now:



  • Runs POP analysis for different configurations and saves the Irradiance and Phase matrices into variables

  • Calls a subfunction that takes the matrices, performs a calculation and outputs a relevant number


I am now at the Merit function-tolerancing part, I will try to describe the problem a bit better: 


The number calculated has to be the merit function for the tolerancing. For each modified lens editor values after a tolerance iteration the Python script has to run in order to get the Merit function value for that iteration. 


 Any suggestions for this problem?


Cheers,


Jose

Userlevel 7
Badge +2

Hi Jose,


This is a really good resource actually, thanks for sharing that.


I would suggest the following snippet, but it is not currently working.


The idea is to run a Tolerance Data Analysis with a single MC run multiple times, save the Tolerance Data in a text file (*.ZTD) at each step and read the MF value from the text file.


I had mainly two problems.


1. The *.ZTD file saves in a weird encoding from OpticStudio (I tried changing Setup..Project Preferences..General..TXT File Encoding without success) and I have troubles reading them with Python. Maybe you can have a go and tell me what you think.


2. To verify that having a single MC run was working, I saved the Best/Worst MC runs, and opened it. Unfortunately, for me, the file did not contain the coordinate breaks. So, it might not be possible to run a single MC.


Alternatively, one could save the Best MC run, open it, and evaluate its MF, but reading it from the *.ZTD file sounds like a better idea.


I hope this can help a bit, and maybe someone from Zemax can jump in :)


Take care,


David

Hi David, 


Thanks again for your answer. I will check if I can do something with your code. 


I was thinking more of using the UDOC operand. Again, I have never used, but after some reading, my idea is the following:


The merit function contains the UDOC operand. The UDOC operand calls an executable (hopefully I can make an executable of the Python code I already wrote) that returns the value. Then I just use tolerancing with that merit function. 


What do you think?


Cheers,


Jose

Userlevel 7
Badge +2

Hi Jose,


Maybe I misunderstood what you intended to do in the first place.


UDOC is a good idea, what I would suggest is translate your Python code to C++/C# and embed it into the UDOC. It'll be so much faster as opposed to the UDOC calling an executable built on Python.


This is a good article to get started, and here you have another example of a UDOC I made to help someone else on the forum.


Let me know if this helps, and how you are getting along.


Take care,


David

Hi David, 


Thanks I will start embedding the C# version of my program in the boilerplate code and see if I manage :) 


Is it faster because the UDOCXX.EXE would have to be built with C# anyway and from there call the Python executable?


I more or less understood from your answer that it is not possible to build the UDOCXX.EXE directly from Python


If there was a user operand boilerplate code made for Python (I don't know if there is intention of making it) , would it be equally fast than C#?


Cheers,


Jose

I was just thinking that it could be difficult to re-write the python code with all the calculations - I don't have experience with C#


How about running the Python script from the csharp boiler plate code using IronPython. Still slow?


 

Userlevel 7
Badge +2

Hi Jose,


I'm not a Python expert so take my answers cautiously.


I don't know what a Python executable is. Is it translated to a compiled programing language before its made into an executable?


I've never heard of someone making a UDOC based on Python, and I don't know IronPython.


What I can tell is that a compiled programing language is often much faster than an interpreted one (like Python or MATLAB).


I didn't have much experience with C# myself, but I don't think you need to learn a lot to get started. If you start with the boilerplate template, and use VisualStudio as shown in the KBAs, and you don't do anything too fancy, it isn't much different to Python.


I hope this can help a little bit. Otherwise, Michael Humphreys from Zemax would be the person I recommend to answer those questions :p


Take care,


David

Hi David, 

Thanks for your answer and previous help. 

I finally managed to make a UDOC in Csharp that runs a Python program to calculate the operand result (see attached code).

  • The Csharp code changes settings of a POP analysis and saves a few .txt files that the Python code uses for calculations.
  • Csharp can read the values printed by Python and updates the operand result

The problem is that it only works to update the Merit Function. Once you ask OpticStudio to run the Montecarlo, the process somehow cannot keep up and one gets results like the one in the pic below. Some values are calculated but then they repeat, and sometimes there is some problem in the program (0 values).

I could test that this “synchronisation” problem is the due to the saving text file part from the command   

POPDef.ToFile("xxx.txt");

If one comments the line, the problem is not there (except for that I always get the same result since I read the same files...)

I have tried adding a pause in different places of the code without success

Any ideas on how to solve this?

Cheers,

Jose

Userlevel 7
Badge +2

Hi Jose,

 

This is interesting. I think the process of writting to a text file is asynchronous. Therefore, having pauses or breaks wouldn’t help.

My question to you would be, what are you computing in your Python script, and can it be computed in C# instead? If you can avoid writting to a text file at all, it would alleviate the issue.

Let me know if this makes sense.

Take care,

 

David

Hi David, 

I know your approach would be more efficient, but I would like to have this tool that can use the Python code for calculations also in the future, since we use Python routinely. Also the calculations are long to translate to Csharp.

I feel that this problem should ideally have a simple solution, but it is being quite hard to find the way to synchronize the process. Also I am so close to the end now with this approach.

 

Cheers,

Jose

 

 

Userlevel 7
Badge +2

Hi Jose,

 

Got you.

I think its at the edge of what I know about the ZOS-API. I did look a little bit and a Google searched revealed this thread.

Where someone explains how to check if a file is being locked, meaning it is currently being written. I imagine you could try something similar after you call the write method, and poll until the file is not locked, which would mean it has been effectively written.

Let me know if you try it and it works.

Take care,

 

David

Hi David, 

I had a call with Zemax support and they figured out what the problem was :) 

One has to select only 1 core to do the tolerancing-then everything magically works. I had selected 12 cores by default, but  the task cannot be solved in parallel (at least with my code). 

I tried previously the polling you suggested, but that was not the problem. I also checked the polling is not needed when one selects 1 core. I did add some code to delete the files after I call the python code.  

Thanks for your advice and hope you also learned something ;)

Jose

Userlevel 7
Badge +2

Hi Jose,

Thanks for the feedback, I did learn something indeed!

I’m guessing that the multiple cores were trying to access the file at the same time, and causing the issue.

Take care,

David

Reply