Quickstart tutorial

A lens can easily be defined, plotted, and evaluated in ten lines of code as shown in this tutorial. The first few lines are for definition of system geometry, then bundle definition defines the rays for analysis, and finally the evaluation of the optical stack on the bundle yields the trace and plots used for analysis.

System geometry

The system geometry is defined by

using GeometricalOptics

funcsList = [spherical, spherical, zplane]
coeffsList = [[1., 6.], [2., -6.], [8.]]
nList = [1., 1.5, 1.]

optSta = opticalstack(coeffsList, funcsList, nList)

To understand how this works, note that each surface in funcsList has an associated Vector in coeffsList and value in nList. The positions in all three Vectors line up. The coefficient list entry is used for the corresponding surface, and the index of refraction entry is for the space preceding the corresponding surface. The "precedence" in the previous sentence is set by the sequence of the surfaces in surfList and therefore the order of traversal of the surfaces by the ray bundle. By contrast, surface precedence is not explicitly implied by the definition of the surface geometry. This is useful for defining different types of systems.

The spherical surface has its own coefficient Vector definition. It is spherical(x,y,coeffs) where coeffs=[zpos,signed_radius] with entries z-position and signed radius, accordingly. This functionality extends to allow simple user defined functions: the user can define a new function my_ytilt(x,y,coeffs)=coeffs[1]+coeffs[2]*y, which creates a tilted plane with z position set by the first entry in coeffs and y tilt set by the second entry. The user would then pass in my_ytilt with no arguments to the function list vector (funcsList above). This is explored with a more complicated and fun surface in Sideloading a new surface function: caustics in the swimming pool.

Here there are three surfaces. System units are arbitrary and can be prescribed based on the needs of demonstration. The light will propagate through air (index of refraction n=1.0) to a spherical surface with apex at z=1 and radius +6.0, and then refract into a medium of n=1.5. Then the light will propagate through a glass (n=1.5) to a second surface with apex at z=2 and radius -6.0. Finally the light will refract back out to air and propagate to a z-plane (zplane) at z=8.0.

Ray geometry

The least verbose method to trace a bundle of light is by using the square bundle definition bundle(x,y,angx,angy,zpos). Other bundles are possible; for example, bundle_as_array can make a circular bundle as shown in Vectorizing over index, 3D analysis. In this construction, x,y,zpos are physical positions, and angx,angy are tilts in radians away from the z-axis in the x and y directions respectively (arguments of direction tangents). By convention, the rays all originate from the same z position given by scalar zpos. Arguments x,y can be Vectors and the rest scalars to make the bundle of rays all parallel or collimated. These Vectors need not be the same length; this sets the count of the rays in either direction. If x::Vector{T} where T<:Real = [0.] as below, the rays will all begin with coordinate x=0, and have y values given by the extent of the vector in y.

test_bundle = bundle([0.], (-1:0.2:1), 0., 0., 0.)

Analogously, another bundle can be traced but have angx,angy as Vectors and all other arguments as scalars. This would correspond with all rays originating from a point in space with a particular array of angles. In any event, the bundle represents an array of input rays.

Evaluation and plotting

The output of the method on the instance of opticalstack is variable depending on keyword.

With a bundle passed in and no render optional argument rend used the default output is a trace.

Passed into rac, the ray lateral coordinate at one position in trajectory can be plotted as a function of ray lateral coordinate at another position. In this case, the position is related to the ray bundle information. Index 1 corresponds to the set of ray information before striking any surfaces, and the last index corresponds to the ray information at the last requested surface. Therefore when traveling through k surfaces, the number of positions along ray trajectory will be (k+1). The three-surface system is evaluated at ray position four with respect to ray position one.

trace1 = optSta(test_bundle)
p_rac = rac(trace1, 1, 4)

The bundle strikes the final surface on a z-plane. Root-mean-square ray deviation rms_spot with no optional keyword pos is evaluated at the last surface.

rms = rms_spot(trace1)
0.03846450937451718

The trace can be evaluated by other means using the evaluation functions explained in Bundle, surface, and trace options. The bigtrace type stores more information than the trace type and can be extracted by setting optional argument isbigtrace=true into the opticalstack call.

If the render mode keyword rend is used, the output is a plot, here set as a 2D plot of the lens in the YZ plane:

p_lens = optSta(test_bundle; rend = "YZ")

Argument options for plotting are rend="YZ", rend="XZ", rend="3Dcirc", rend="3Dsq". The global plot domain of surfaces can be adjusted by xdom and ydom.

The various plots can be plotted statically as shown in the docs or as interactive plots by using the plotly backend of Plots with this package. Just add using Plots; Plots.plotly() and the plots will be interactive.

p_lens = optSta(test_bundle; rend = "YZ", ydom = -2:0.1:2)

Yes, this is the eleventh line of code for those of you keeping count, and I said ten lines. But it's not necessary to repeat the plotting operation. You get the point :)

Now, what next?

There are other methods and types in GeometricalOptics.jl which can be used, and this package can be interfaced with other packages in Julia. Other more complicated demonstrations can be constructed as in Examples (2D and 3D). But the core functionality still remains the same as in this tutorial. The opticalstack connects a bundle of rays and with a sequence of surfaces. Simply substitute different functions for surfaces or vectorize over lenses to look at different configurations depending on the behavior you wish to demonstrate. Other bundle prescriptions are also possible. The abstract vectorization of Julia allows a surprisingly intuitive extension of this simpler example to other practical demonstrations.