    

 ## On this page

  

 

 # PythonStandalone\_23\_ray\_fan\_native\_manual\_comparison

 Last update: 17.07.2025 

 1import clr, os, winreg

 2from itertools import islice

 3 

 4import time

 5import matplotlib.pyplot as plt

 6import numpy as np

 7 

 8from System import Enum, Int32, Double

 9 

 10class PythonStandaloneApplication(object):

 11 class LicenseException(Exception):

 12 pass

 13 class ConnectionException(Exception):

 14 pass

 15 class InitializationException(Exception):

 16 pass

 17 class SystemNotPresentException(Exception):

 18 pass

 19 

 20 def __init__(self, path=None):

 21 \# determine location of ZOSAPI\_NetHelper.dll &amp; add as reference

 22 aKey = winreg.OpenKey(winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER), r"Software\\Zemax", 0, winreg.KEY_READ)

 23 zemaxData = winreg.QueryValueEx(aKey, 'ZemaxRoot')

 24 NetHelper = os.path.join(os.sep, zemaxData[0], r'ZOS-API\\Libraries\\ZOSAPI\_NetHelper.dll')

 25 winreg.CloseKey(aKey)

 26 clr.AddReference(NetHelper)

 27 import ZOSAPI_NetHelper

 28 

 29 \# Find the installed version of OpticStudio

 30 if path is None:

 31 isInitialized = ZOSAPI_NetHelper.ZOSAPI_Initializer.Initialize()

 32 else:

 33 \# Note -- uncomment the following line to use a custom initialization path

 34 isInitialized = ZOSAPI_NetHelper.ZOSAPI_Initializer.Initialize(path)

 35 

 36 \# determine the ZOS root directory

 37 if isInitialized:

 38 dir = ZOSAPI_NetHelper.ZOSAPI_Initializer.GetZemaxDirectory()

 39 else:

 40 raise PythonStandaloneApplication.InitializationException("Unable to locate Zemax OpticStudio. Try using a hard-coded path.")

 41 

 42 \# add ZOS-API referencecs

 43 clr.AddReference(os.path.join(os.sep, dir, "ZOSAPI.dll"))

 44 clr.AddReference(os.path.join(os.sep, dir, "ZOSAPI\_Interfaces.dll"))

 45 import ZOSAPI

 46 

 47 \# create a reference to the API namespace

 48 self.ZOSAPI = ZOSAPI

 49 

 50 \# create a reference to the API namespace

 51 self.ZOSAPI = ZOSAPI

 52 

 53 \# Create the initial connection class

 54 self.TheConnection = [ZOSAPI.ZOSAPI\_Connection](class_z_o_s_a_p_i_1_1_z_o_s_a_p_i___connection.xhtml)()

 55 

 56 if self.TheConnection is None:

 57 raise PythonStandaloneApplication.ConnectionException("Unable to initialize .NET connection to ZOSAPI")

 58 

 59 self.TheApplication = self.TheConnection.CreateNewApplication()

 60 if self.TheApplication is None:

 61 raise PythonStandaloneApplication.InitializationException("Unable to acquire ZOSAPI application")

 62 

 63 if self.TheApplication.IsValidLicenseForAPI == False:

 64 raise PythonStandaloneApplication.LicenseException("License is not valid for ZOSAPI use")

 65 

 66 self.TheSystem = self.TheApplication.PrimarySystem

 67 if self.TheSystem is None:

 68 raise PythonStandaloneApplication.SystemNotPresentException("Unable to acquire Primary system")

 69 

 70 def __del__(self):

 71 if self.TheApplication is not None:

 72 self.TheApplication.CloseApplication()

 73 self.TheApplication = None

 74 

 75 self.TheConnection = None

 76 

 77 def OpenFile(self, filepath, saveIfNeeded):

 78 if self.TheSystem is None:

 79 raise PythonStandaloneApplication.SystemNotPresentException("Unable to acquire Primary system")

 80 self.TheSystem.LoadFile(filepath, saveIfNeeded)

 81 

 82 def CloseFile(self, save):

 83 if self.TheSystem is None:

 84 raise PythonStandaloneApplication.SystemNotPresentException("Unable to acquire Primary system")

 85 self.TheSystem.Close(save)

 86 

 87 def SamplesDir(self):

 88 if self.TheApplication is None:

 89 raise PythonStandaloneApplication.InitializationException("Unable to acquire ZOSAPI application")

 90 

 91 return self.TheApplication.SamplesDir

 92 

 93 def ExampleConstants(self):

 94 if self.TheApplication.LicenseStatus == self.ZOSAPI.LicenseStatusType.PremiumEdition:

 95 return "Premium"

 96 elif self.TheApplication.LicenseStatus == self.ZOSAPI.LicenseStatusTypeProfessionalEdition:

 97 return "Professional"

 98 elif self.TheApplication.LicenseStatus == self.ZOSAPI.LicenseStatusTypeStandardEdition:

 99 return "Standard"

 100 else:

 101 return "Invalid"

 102 

 103 def reshape(self, data, x, y, transpose = False):

 104 """Converts a System.Double\[,\] to a 2D list for plotting or post processing

 105 

 106 Parameters

 107 ----------

 108 data : System.Double\[,\] data directly from ZOS-API 

 109 x : x width of new 2D list [use var.GetLength(0) for dimension]

 110 y : y width of new 2D list [use var.GetLength(1) for dimension]

 111 transpose : transposes data; needed for some multi-dimensional line series data

 112 

 113 Returns

 114 -------

 115 res : 2D list; can be directly used with Matplotlib or converted to

 116 a numpy array using numpy.asarray(res)

 117 """

 118 if type(data) is not list:

 119 data = list(data)

 120 var_lst = [y] * x;

 121 it = iter(data)

 122 res = [list(islice(it, i)) for i in var_lst]

 123 if transpose:

 124 return self.transpose(res);

 125 return res

 126 

 127 def transpose(self, data):

 128 """Transposes a 2D list (Python3.x or greater). 

 129 

 130 Useful for converting mutli-dimensional line series (i.e. FFT PSF)

 131 

 132 Parameters

 133 ----------

 134 data : Python native list (if using System.Data[,] object reshape first) 

 135 

 136 Returns

 137 -------

 138 res : transposed 2D list

 139 """

 140 if type(data) is not list:

 141 data = list(data)

 142 return list(map(list, zip(*data)))

 143 

 144if __name__ == '\_\_main\_\_':

 145 zos = PythonStandaloneApplication()

 146 

 147 \# load local variables

 148 ZOSAPI = zos.ZOSAPI

 149 TheApplication = zos.TheApplication

 150 TheSystem = zos.TheSystem

 151 

 152 \# Set up primary optical system

 153 sampleDir = TheApplication.SamplesDir

 154 testFile = sampleDir + "\\\\Sequential\\\\Objectives\\\\Double Gauss 28 degree field.zos"

 155 TheSystem.LoadFile(testFile, False)

 156 

 157 max_rays = 150

 158 \#! \[e23s01\_py\]

 159 max_field = 0

 160 for i in range(1,TheSystem.SystemData.Fields.NumberOfFields + 1):

 161 if TheSystem.SystemData.Fields.GetField(i).Y &gt; max_field:

 162 max_field = TheSystem.SystemData.Fields.GetField(i).Y

 163 \#! \[e23s01\_py\]

 164 

 165 max_num_field = TheSystem.SystemData.Fields.NumberOfFields

 166 

 167 \# sets up plot

 168 plt.close('all')

 169 plt.rcParams["figure.figsize"] = (15, 8)

 170 colors = ('b', 'g', 'r', 'c', 'm', 'y', 'k')

 171 

 172 tic = time.time()

 173 

 174 \#! \[e23s02\_py\]

 175 \# Set up Batch Ray Trace

 176 raytrace = TheSystem.Tools.OpenBatchRayTrace()

 177 nsur = TheSystem.LDE.NumberOfSurfaces

 178 normUnPolData = raytrace.CreateNormUnpol(max_rays + 1, ZOSAPI.Tools.RayTrace.RaysType.Real, nsur)

 179 \#! \[e23s02\_py\]

 180 

 181 \#! \[e23s03\_py\]

 182 \# define batch ray trace constants

 183 hx = 0

 184 \# since python doesn't include STOP number in range, need to use STOP value slightly more than 1

 185 py_ary = np.arange(0, 1.0001, 1 / max_rays) * 2 - 1

 186 px = 0

 187 max_wave = TheSystem.SystemData.Wavelengths.NumberOfWavelengths

 188 \#! \[e23s03\_py\]

 189 

 190 \# initilize x/y image plane arrays

 191 y_ary = np.zeros((max_num_field + 1, max_wave + 1, max_rays + 1))

 192 

 193 \#! \[e23s04\_py\]

 194 \# image surface number and primary wavelength

 195 nsur = TheSystem.LDE.NumberOfSurfaces

 196 pwav = 0

 197 for a in range(1, TheSystem.SystemData.Wavelengths.NumberOfWavelengths + 1):

 198 if TheSystem.SystemData.Wavelengths.GetWavelength(a).IsPrimary == 1:

 199 pwav = a

 200 

 201 \# creates array of Y coordinate chief ray values

 202 chief_ary = np.zeros(max_num_field)

 203 for field in range(1, max_num_field + 1):

 204 \#hy = TheSystem.SystemData.Fields.GetField(field).Y / max\_field

 205 hy = 1 if max_field == 0 else TheSystem.SystemData.Fields.GetField(field).Y / max_field

 206 \# gets single value without using MFE (see ZPL OPEV)

 207 chief_ary[field - 1] = TheSystem.MFE.GetOperandValue(ZOSAPI.Editors.MFE.MeritOperandType.REAY, nsur, pwav, 0, hy , 0, 0, 0, 0)

 208 \#! \[e23s04\_py\]

 209 

 210 \# setup plot

 211 for field in range(1, max_num_field + 1):

 212 if (field == 1):

 213 ax1 = plt.subplot(2, max_num_field, field + max_num_field)

 214 else:

 215 plt.subplot(2, max_num_field, field + max_num_field)

 216 

 217 hy = 1 if max_field == 0 else TheSystem.SystemData.Fields.GetField(field).Y / max_field

 218 

 219 for wave in range(1, max_wave + 1):

 220 \#! \[e23s05\_py\]

 221 \# Adding Rays to Batch, varying normalized object height hy

 222 normUnPolData.ClearData()

 223 for i in range(0, max_rays + 1):

 224 py = py_ary[i]

 225 normUnPolData.AddRay(Int32(wave), Double(hx), Double(hy), Double(px), Double(py), Enum.Parse(ZOSAPI.Tools.RayTrace.OPDMode, "None"))

 226 \#! \[e23s05\_py\]

 227 

 228 \#! \[e23s06\_py\]

 229 \# Run Batch Ray Trace

 230 raytrace.RunAndWaitForCompletion()

 231 \#! \[e23s06\_py\]

 232 

 233 \#! \[e23s07\_py\]

 234 \# Read and display results

 235 normUnPolData.StartReadingResults()

 236 

 237 \# Python NET requires all arguments to be passed in as reference, so need to have placeholders

 238 sysInt = Int32(1)

 239 sysDbl = Double(1.0)

 240 

 241 output = normUnPolData.ReadNextResult(sysInt, sysInt, sysInt,

 242 sysDbl, sysDbl, sysDbl, sysDbl, sysDbl, sysDbl, sysDbl, sysDbl, sysDbl, sysDbl, sysDbl);

 243 

 244 while output[0]:

 245 if (output[2] == 0 and output[3] == 0):

 246 y_ary[field, wave, output[1] - 1] = output[5]

 247 

 248 \# Python NET requires all arguments to be passed in as reference, so need to have placeholders

 249 output = normUnPolData.ReadNextResult(sysInt, sysInt, sysInt,

 250 sysDbl, sysDbl, sysDbl, sysDbl, sysDbl, sysDbl, sysDbl, sysDbl, sysDbl, sysDbl, sysDbl);

 251 plt.plot(py_ary[:], np.squeeze((y_ary[field, wave,:] - chief_ary[field - 1]) * 1000), '-', ms = 4)

 252 \#! \[e23s07\_py\]

 253 raytrace.Close();

 254 manual = round(time.time() - tic, 3)

 255 

 256 tic = time.time()

 257 

 258 \#! \[e23s08\_py\]

 259 ray = TheSystem.Analyses.New_Analysis(ZOSAPI.Analysis.AnalysisIDM.RayFan)

 260 ray_settings = ray.GetSettings()

 261 ray_settings.NumberOfRays = max_rays / 2

 262 ray_settings.Field.UseAllFields()

 263 ray_settings.Wavelength.UseAllWavelengths()

 264 

 265 ray.ApplyAndWaitForCompletion()

 266 ray_results = ray.GetResults()

 267 \#! \[e23s08\_py\]

 268 

 269 for field in range(1, max_num_field + 1):

 270 \#! \[e23s09\_py\]

 271 \# Read and display results

 272 if field == 1:

 273 ax2 = plt.subplot(2, max_num_field, field)

 274 else:

 275 plt.subplot(2, max_num_field, field)

 276 

 277 ds = ray_results.GetDataSeries(field *2 - 2)

 278 \# get raw .NET data into numpy array

 279 xRaw = np.asarray(tuple(ds.XData.Data))

 280 yRaw = np.asarray(tuple(ds.YData.Data))

 281 

 282 x = xRaw

 283 y = yRaw.reshape(ds.YData.Data.GetLength(0), ds.YData.Data.GetLength(1))

 284 

 285 plt.plot(x, y)

 286 hy = 1 if max_field == 0 else TheSystem.SystemData.Fields.GetField(field).Y / max_field

 287 plt.title('Field: %4.3f' % (hy))

 288 \#! \[e23s09\_py\]

 289 

 290 native = round(time.time() - tic, 3)

 291 

 292 print('Tracing ' + str(max_rays + 1) + ' number of rays')

 293 print('Elapsed time is ' + str(native) + ' seconds with native code')

 294 print('Elapsed time is ' + str(manual) + ' seconds with manual code')

 295 print('The native code is ' + str(round((manual - native) / native * 100, 2)) + '% faster')

 296 

 297 ax1.set_ylabel('Manual Code (%.3fs)' % (manual))

 298 ax2.set_ylabel('Native Code (%.3fs)' % (native))

 299 

 300 plt.subplots_adjust(wspace=0.5)

 301 

 302 \# This will clean up the connection to OpticStudio.

 303 \# Note that it closes down the server instance of OpticStudio, so you for maximum performance do not do

 304 \# this until you need to.

 305 del zos

 306 zos = None

 307 

 308 \# place plt.show() after clean up to release OpticStudio from memory

 309 plt.show()

[ZOSAPI.ZOSAPI\_Connection](class_z_o_s_a_p_i_1_1_z_o_s_a_p_i___connection.xhtml)

**Definition:** ZemaxService.cs:198