pyoptics.fresnel

Fresnel lenses: design, ray tracing with real facets, and the way back.

A Fresnel lens keeps the surface slopes of a thick refracting lens (its “parent”, a sphere, conic or even asphere) and drops the material that only holds the surface at its height: the sag is cut into annular zones and every zone is lowered onto a common base plane. What remains is a plate with a sawtooth relief of facets (the active, refracting parts) and risers (the steps between the zones, also called drafts or groove walls). A. Fresnel proposed it in 1822 for lighthouse lenses; today Fresnel lenses are moulded in PMMA or polycarbonate for solar concentrators, overhead projectors, spotlights, field lenses of cameras and VR headsets and LED optics.

Geometry

In the local frame of a FresnelSurface the base plane is z = 0 and the parent sag is z_p(r) (raytrace.Surface.sag). The zone radii are 0 = r_0 < r_1 < … < r_K, for a constant groove pitch (moulded lenses), a constant groove depth (diamond turned lenses, kinoforms) or given explicitly (zone_radii()). The facet of zone k is

curved facets:  f_k(r) = z_p(r) - z_p(r_k),
flat facets:    f_k(r) = s_k (r - r_k),  s_k = (z_p(r_k+1) - z_p(r_k))/(r_k+1 - r_k),

so every facet starts on the base plane at the inner edge of its zone and has the step height h_k = f_k(r_k+1) at the outer edge, where the riser returns to the base plane. Flat facets are cones (a common way of cutting moulds); the central zone always keeps the curved parent. A riser is vertical (parallel to the axis), or, with a draft angle delta > 0 that lets the mould release, a cone that leans by delta from the axis and covers the start of the next facet over about |h_k| tan(delta). The last zone continues beyond r_K without a step.

Ray tracing

Two models of a FresnelSurface:

  • model="ideal": the usual idealization of lens design programs (e.g. the “Fresnel” surface of Zemax OpticStudio): the rays meet the flat base plane and are refracted with the normal of the parent at the same radius. The ray directions are those of an infinitely fine Fresnel lens; the optical path is that of a plane plate, so the OPD of such a lens is not the wavefront of any real lens (the rays are not normal to it).

  • model="facets": the real sawtooth. Each ray is intersected with the facets and risers of the zones it can reach and refracted with the normal of the piece it hits. The optical path includes the step heights, so the pupil phase jumps by about (n - n’) h_k at the zone edges. Rays are lost (vignetted, NaN) where a Fresnel lens loses light:

    • riser hits: the ray meets a riser first (draft loss, for oblique incidence also the shadow of the vertical riser);

    • shadowing: after refraction at a facet, the ray hits the relief again (the next riser) before it leaves the grooves, which is the main loss on the exit side of fast lenses;

    • total internal reflection at the facet.

    stray="refract" instead refracts riser hits with the riser normal and traces them on: the first order stray light, e.g. the halo around the focus of a solar concentrator. Rays that re-hit the relief are lost in either case (they would need a non sequential trace).

FresnelSurface.classify() and facet_losses() tell which of these happens to each ray, raytrace.System.trace_polarization() adds the Fresnel reflection losses at the facets.

The paraxial data (power, focal lengths, pupils) are those of the parent; the Seidel sums of a Fresnel surface are not computed (NaN, as for an ideal lens), since a plane carrying the parent’s slopes does not have the aberrations of the parent. A Fresnel surface is traced with NumPy (not by the compiled kernel).

Design and the way back

FresnelSurface.from_surface() collapses a thick lens surface into zones and FresnelSurface.parent_surface() returns the thick lens surface again; the zone radii and facet slopes are the whole description of the lens. focusing_slopes() gives the exact facet slopes for a perfect focus of the ideal model by the vector law of refraction (the facet normal is parallel to n d - n’ d’); surface_from_slopes() fits an even asphere (a parent) to slopes, e.g. to the facet angle table of a moulded lens or to a design from focusing_slopes(). The parent of an aberration free Fresnel lens is not the aberration free thick lens: a plano-convex lens that focuses a collimated beam through its curved side without spherical aberration has the hyperbola K = -n**2, the corresponding Fresnel lens a different conic and aspheric terms, since its facets sit on the plane instead of on the curved surface.

Waves: Fresnel lens vs. kinoform

The steps of a Fresnel lens are many wavelengths deep (a 0.3 mm deep groove in PMMA is several hundred waves of path); the phase jumps between the zones are arbitrary and the zones add up incoherently: the focus is that of the rays, blurred by diffraction at the zone width. A kinoform is a Fresnel lens with the groove depth lambda_0/(n - 1) (constant depth zones): every step is one wave, the zones add up in phase and the lens is diffractive. At other wavelengths the step is alpha = lambda_0 (n(lambda) - 1)/(lambda (n_0 - 1)) waves; the scalar efficiency of the diffraction order m is sinc**2(alpha - m) (kinoform_efficiency()), its focal length f m lambda_0/lambda and its Abbe number lambda_d/(lambda_F - lambda_C) = -3.45 (diffractive_abbe_number()), strong and of opposite sign to glass.

References

A. Fresnel, “Mémoire sur un nouveau système d’éclairage des phares”, Paris (1822).

R. Leutz and A. Suzuki, “Nonimaging Fresnel Lenses: Design and Performance of Solar Concentrators”, Springer (2001).

A. Davis and F. Kühnlenz, “Optical design using Fresnel lenses”, Optik & Photonik 2(4), 52-55 (2007).

W. T. Welford, “Aberrations of Optical Systems”, Adam Hilger (1986), ch. 13 (Fresnel lenses and diffractive surfaces).

D. A. Buralli, G. M. Morris and J. R. Rogers, “Optical performance of holographic kinoforms”, Appl. Opt. 28, 976-983 (1989).

D. C. O’Shea, T. J. Suleski, A. D. Kathman and D. W. Prather, “Diffractive Optics: Design, Fabrication, and Test”, SPIE Press (2004), ch. 4.

N. Sultanova, S. Kasarova and I. Nikolov, “Dispersion properties of optical polymers”, Acta Phys. Pol. A 116, 585-587 (2009) (PMMA).

Zemax OpticStudio User Manual, “Fresnel” surface type.

Classes

FacetLosses

Fate of the rays at a Fresnel surface (facet_losses()).

FresnelSurface

Fresnel surface: a parent surface collapsed into zones on a base plane.

Functions

diffractive_abbe_number

Abbe number lambda_d/(lambda_F - lambda_C) of a diffractive lens (-3.45).

facet_losses

Classify the rays of a pupil grid at a Fresnel surface of a system.

focusing_slopes

Exact facet slopes dz/dr that send rays through a point on the axis.

kinoform_depth

Groove depth of a kinoform: order*lambda_0/(n - n_out).

kinoform_efficiency

Scalar diffraction efficiency sinc**2(alpha - m) of a kinoform.

pmma

Refractive index of PMMA (Sultanova et al. 2009, 437 to 1052 nm, n_d = 1.4906).

surface_from_slopes

Fit a parent surface (sphere or conic plus even aspheric terms) to slopes.

vogel_spiral

n points spread uniformly over the unit disk (Vogel's sunflower spiral).

zone_radii

Zone radii of a Fresnel lens collapsed from a surface.

class pyoptics.fresnel.FresnelSurface(R=inf, thickness=0.0, zones=None, pitch=None, depth=None, n_zones=None, radius=None, model='facets', facets='curved', draft=0.0, stray='lose', **kwargs)[source]

Bases: Surface

Fresnel surface: a parent surface collapsed into zones on a base plane.

See the module docstring for the geometry and the two ray trace models.

Parameters:
R, thicknessnumbers, optional

Vertex radius of the parent and distance to the next vertex (from the base plane).

zonessequence, optional

Zone radii 0 = r_0 < … < r_K (see zone_radii()).

pitch, depth, n_zonesoptional

Instead of zones: build them with zone_radii() up to radius.

radiusnumber, optional

Outer radius for pitch, depth or n_zones (default: semi_diameter).

model{“facets”, “ideal”}, optional

Real sawtooth or the flat idealization.

facets{“curved”, “flat”}, optional

Facets that follow the parent, or cones through the zone edges of the parent (central zone curved).

draftnumber, optional

Draft angle of the risers (radians) from the axis.

stray{“lose”, “refract”}, optional

Rays that hit a riser first are lost, or refracted at the riser.

n_after, material, conic, aspheric, semi_diameter, mirror, decenter, tilt,
coating, label, index_offsetoptional

As for raytrace.Surface (conic and aspheric describe the parent).

classmethod from_surface(surface, pitch=None, depth=None, n_zones=None, zones=None, radius=None, **kwargs)[source]

Fresnel version of a (thick lens) surface.

Parameters:
surfaceraytrace.Surface

The parent: radius, conic, aspheric terms, thickness, medium, semi diameter, decentre, tilt, coating and label are copied.

pitch, depth, n_zones, zones, radiusoptional

Zones, see zone_radii().

**kwargs

model, facets, draft, stray, or overrides of the copied values (e.g. thickness).

Returns:
FresnelSurface
parent_surface(thickness=None)[source]

The thick lens surface (the parent) as a raytrace.Surface.

Parameters:
thicknessnumber, optional

Distance to the next vertex (default: that of the Fresnel surface; a parent in place of a Fresnel surface on the exit side of a plate needs the thickness changed by its sag at the rim).

Returns:
raytrace.Surface
parent_sag(r)[source]

Sag z_p of the parent at the radii r.

facet_sag(k, r)[source]

Height f_k(r) of the facet of zone k at the radii r (k may be an array).

profile(r)[source]

Height z(r) of the relief (facets and risers) at the radii r.

sag(r2)[source]

Sag at the squared radius r2: the relief (facets), 0 for the ideal model.

section(h, n_per_zone=12)[source]

Points (y, z) of the meridional section from -h to h, with the corners of the risers, for drawing.

Parameters:
hnumber

Half height of the section.

n_per_zoneint, optional

Points per facet.

Returns:
y, zarrays
intersect(P, d)[source]

Distance along the rays to the base plane (ideal model) or to the first facet or riser they hit (facets model).

Parameters:
P(N, 3) array

Start points relative to the vertex, in the local frame of the surface.

d(N, 3) array

Unit directions.

Returns:
t(N,) array

NaN for rays that miss.

normal(Q)[source]

Unit normals at the points Q: of the parent (ideal model) or of the facet or riser through Q.

Parameters:
Q(N, 3) array

Points on the surface, relative to the vertex.

Returns:
N(N, 3) array

Normals with N_z >= 0 (radial for vertical risers).

deflect(Q, d, n1, n2)[source]

Refract (or reflect) the rays at the base plane (ideal) or at the pieces they hit (facets: lost rays NaN, see the module docstring).

Parameters:
Q(N, 3) array

Intersection points in the local frame.

d(N, 3) array

Unit directions of the incident rays.

n1, n2numbers

Refractive indices in front of and behind the surface.

Returns:
d2array

New unit directions.

doplarray

Extra optical path (zero).

classify(P, d, n1, n2)[source]

What happens to rays at the surface.

Parameters:
P(N, 3) array

Points on the rays in the local frame (in front of the surface).

d(N, 3) array

Unit directions.

n1, n2numbers

Refractive indices in front of and behind the surface.

Returns:
code(N,) int array

FACET (refracted by a facet and out), RISER (hits a riser first), SHADOWED (refracted by a facet, then hits the relief again), TIR (total internal reflection at the first piece) or MISS.

Q(N, 3) array

First hits (NaN for misses).

pyoptics.fresnel.zone_radii(surface, radius, pitch=None, depth=None, n_zones=None)[source]

Zone radii of a Fresnel lens collapsed from a surface.

Parameters:
surfaceraytrace.Surface or callable

The parent (its sag(r2)) or a function z(r2).

radiusnumber

Outer radius of the lens.

pitchnumber, optional

Constant zone width (groove pitch).

depthnumber, optional

Constant groove depth: zone k ends where |z_p(r) - z_p(0)| reaches (k + 1) depth (needs a monotonic sag; the last zone may be shallower).

n_zonesint, optional

Number of zones of equal width (alternative to pitch).

Returns:
rarray

0 = r_0 < r_1 < … < r_K = radius.

pyoptics.fresnel.focusing_slopes(r, focus, n1, n2, d_in=(0.0, 0.0, 1.0), z=0.0, d_out=None)[source]

Exact facet slopes dz/dr that send rays through a point on the axis.

The facet normal is parallel to n1 d - n2 d’ (vector law of refraction) for the incident direction d and the direction d’ from the facet point to the focus. With z = 0, these are the slopes of an aberration free ideal Fresnel lens (flat base plane).

Parameters:
rarray

Radii of the facet points (meridional, y = r).

focusnumber

z of the focus on the axis (local frame), or np.inf/-np.inf for a collimated output along +z/-z.

n1, n2numbers

Indices in front of and behind the surface.

d_in(3,) or (N, 3) array, optional

Incident directions (default: along the axis). A point source on the axis at z_s: d_in = (r, z - z_s)/norm, see the notebook.

znumber or array, optional

Heights of the facet points.

d_out(N, 3) array, optional

Wanted output directions instead of those to the focus (e.g. into a plate whose far side refracts them to the focus); focus is then ignored.

Returns:
slopearray

dz/dr at r (TIR or no solution: NaN).

pyoptics.fresnel.surface_from_slopes(r, slope, n_aspheric=4, conic=0.0, weights=None, **kwargs)[source]

Fit a parent surface (sphere or conic plus even aspheric terms) to slopes.

Least squares fit of dz/dr of raytrace.Surface (vertex curvature c and the coefficients A4, A6, … for a fixed conic) to the slopes, e.g. the facet angle table of a Fresnel lens or the result of focusing_slopes().

Parameters:
r, slopearrays

Radii and slopes dz/dr.

n_asphericint, optional

Number of aspheric coefficients (A4, …, A_{2 n_aspheric + 2}).

conicnumber, optional

Fixed conic constant of the fit.

weightsarray, optional

Weights of the slopes.

**kwargs

Passed on to raytrace.Surface (thickness, n_after, material, …).

Returns:
surfaceraytrace.Surface
rmsnumber

RMS slope residual.

pyoptics.fresnel.facet_losses(system, index, field=None, n_rays=20000, px=None, py=None)[source]

Classify the rays of a pupil grid at a Fresnel surface of a system.

Parameters:
systemraytrace.System
indexint

Index of the FresnelSurface in system.surfaces.

fieldoptional

Field (default: on axis).

n_raysint, optional

Number of rays, on a Vogel (sunflower) spiral over the pupil: uniform in area and without the aliasing of a square grid against the narrow rings of the risers.

px, pyarrays, optional

Pupil coordinates instead of the spiral.

Returns:
FacetLosses
class pyoptics.fresnel.FacetLosses(px, py, code, r, fractions)

Bases: tuple

Fate of the rays at a Fresnel surface (facet_losses()).

px, pyarrays

Pupil coordinates of the rays.

codeint array

FACET, RISER, SHADOWED, TIR or MISS for each ray (NaN rays in front of the surface: MISS).

rarray

Radius of the first hit on the surface.

fractionsdict

Fraction of the rays in each class, by name (“facet”, “riser”, “shadowed”, “TIR”, “miss”).

code

Alias for field number 2

fractions

Alias for field number 4

px

Alias for field number 0

py

Alias for field number 1

r

Alias for field number 3

pyoptics.fresnel.vogel_spiral(n)[source]

n points spread uniformly over the unit disk (Vogel’s sunflower spiral).

Parameters:
nint
Returns:
x, yarrays
pyoptics.fresnel.kinoform_efficiency(order, wavelength, design_wavelength, n, n_design, n_out=1.0, design_order=1)[source]

Scalar diffraction efficiency sinc**2(alpha - m) of a kinoform.

Parameters:
orderint or array

Diffraction order m.

wavelength, design_wavelengthnumbers or arrays
n, n_designnumbers or arrays

Index of the relief at wavelength and at the design wavelength.

n_outnumber, optional

Index in the grooves.

design_orderint, optional

Design order (step of design_order waves at the design wavelength).

Returns:
etaarray

Efficiency with alpha = design_order lambda_0 (n - n_out)/((n_0 - n_out) lambda).

pyoptics.fresnel.kinoform_depth(design_wavelength, n, n_out=1.0, order=1)[source]

Groove depth of a kinoform: order*lambda_0/(n - n_out).

Parameters:
design_wavelengthnumber
n, n_outnumbers

Indices of the relief and of the medium in the grooves at lambda_0.

orderint, optional

Design order (multi-order diffractive lenses: order > 1).

Returns:
depthnumber
pyoptics.fresnel.diffractive_abbe_number(lambda_d=587.56, lambda_F=486.13, lambda_C=656.27)[source]

Abbe number lambda_d/(lambda_F - lambda_C) of a diffractive lens (-3.45).

Parameters:
lambda_d, lambda_F, lambda_Cnumbers, optional

Wavelengths (any common unit).

Returns:
Vnumber
pyoptics.fresnel.pmma(wavelength_nm)[source]

Refractive index of PMMA (Sultanova et al. 2009, 437 to 1052 nm, n_d = 1.4906).

Parameters:
wavelength_nmnumber or array

Vacuum wavelength in nm.

Returns:
nnumber or array