RayCollection and Ray Objects

The most important object in Raypier is the RayCollection. This is (as you might guess from the name) a 1D array of Ray objects.

The Ray object (raypier.core.ctracer.Ray) represents a single ray, and wraps an underlying C-structure. The single Ray object exists as a convenience for python scripting. The actual ray-tracing operation operates only RayCollection objects.

Ray objects have the following attributes:

  • origin - A 3-vector (tuple) of floats giving the start-point for the ray

  • direction - A 3-vector giving the direction of the ray. This is always normalised to unit length

  • E_vector - A 3-vector defining the polarisation-axes. This vector is unit-length and orthogonal to the direction vector

  • normal - A 3-vector giving the unit-length surface normal of the face from which the ray originated. May be undefined for rays which have not originated from a face intersection

  • refractive_index - a complex value giving the refractive index of the material through which the ray has propagated

  • E1_amp - the complex electric field amplitude for polarisations parallel to the E_vector axis

  • E2_amp - the complex electric field amplitude for the polarisation orthogonal to the E_vector axis

  • length - the geometric length of the ray from it’s start-point to its termination at an intersecting face (or may be set to the max length of the ray, it no intersection has occured

  • phase - An additional phase-factor that may be introduced by face interactions. Currently, only used to hold the “grating phase” arising from diffraction-grating surfaces.

  • accumulated_path - the total optical path length accumulated from the parent ray plus (parent length * real part of refractive index)

  • wavelength_idx - a index into the wavelength list (held by both the RayCollection and/or source object)

  • parent_idx - the index of the parent-ray in the parent RayCollection object

  • end_face_idx - the index into the Global Face List of the face at which the ray terminates (i.e. intersects). The Global Face List can be accessed on the all_faces atrtibure of the RayTraceModel object.

  • ray_type_idx - a bitfield indicating is the ray is a reflected or transmitted ray. Other ray-types may be defined in future

Rays have some additional read-only properties defined:

  • power - the sum of squares of the E1_amp and E2_amp components

  • amplitude - the square-root of the power

  • jones_vector - Returns a 2-tuple (alpha, beta) representing the Jones Vector describing the polarisation state of the ray. See _https://en.wikipedia.org/wiki/Jones_calculus#Jones_vector

  • E_left - Returns the complex electric field amplitude for the left circular polarisation state

  • E_right - Returns the complex electric field amplitude for the right circular polarisation state

  • ellipticity - Returns the ratio of the power in the right-hand polarisation state to the left-hand polarisation state I.e. A value of +1 indicates 100% right-circular polarisation, 0 indicates linear polarisation, -1 indicates 100% left polarisation.

  • major_minor_axes - Returns a 2-tuple of unit-length vectors describing the major and minor polarisation axes

RayCollection objects have substantially the same attributes/properties as the Ray object, except that each property returns a numpy array containing the values for all rays in the collection.

RayCollection objects are iterable (yielding single Rays) and subscriptable.

Creating RayCollections

If you need to create a RayCollection with a large number of rays (say, if you are writing your own Source class), the easiest method is to create a numpy array with the equivalent numpy dtype:

>>> from raypier.api import ray_dtype, RayCollection
>>> print(ray_dtype)
[('origin', '<f8', (3,)),
 ('direction', '<f8', (3,)),
 ('normal', '<f8', (3,)),
 ('E_vector', '<f8', (3,)),
 ('refractive_index', '<c16'),
 ('E1_amp', '<c16'),
 ('E2_amp', '<c16'),
 ('length', '<f8'),
 ('phase', '<f8'),
 ('accumulated_path', '<f8'),
 ('wavelength_idx', '<u4'),
 ('parent_idx', '<u4'),
 ('end_face_idx', '<u4'),
 ('ray_type_id', '<u4')]

Once you’ve created a numpy array with this dtype, you populate its fields as required. You can then create a RayCollection instance from this array using :py:meth:`RayCollection.from_array’ classmethod. E.g.:

>>> import numpy
>>> my_rays = numpy.zeros(500, ray_dtype)
>>> my_rays['direction'] = numpy.array([[0,1,1]],'d')
<... assign other members as necessary ...>
>>> rc = RayCollection.from_array(my_rays)

Note, data is always copied to and from RayCollections. The reason why we don’t use memory views is that RayCollections have a dynamic size and can grow in size by re-allocation of their memory. Numpy array, by contrast are static in size.

Likewise, we can convert a RayCollection to a numpy array using its RayCollection.copy_as_array() method.:

>>> arr = rc.copy_as_array()