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()