← Projects

Project

TeneaX

TeneaX is a differentiable optical design engine. Classical lens design is a forward, trial-and-error loop — you shape a surface, trace the light, look at the result, and adjust. TeneaX inverts that: you specify the illuminance pattern you want on a target plane, and the engine solves for the lens that produces it, automatically, by gradient descent through a fully differentiable ray tracer. Formally it minimizes the mismatch between the achieved and the target distribution, minc L(c)=I(c)Itarget2,\min_{c}\ \mathcal{L}(c) = \lVert I(c) - I_{\text{target}} \rVert^2, where cc are the control points of a freeform lens and I(c)I(c) is the illuminance from tracing thousands of rays through it. Because every step of that trace — refraction, intersection, the detector splat — is differentiable, gradients flow from the target all the way back to the surface.

Adjoint Methods

To optimize a lens with thousands of degrees of freedom, we need the gradient of the merit with respect to every control point. Doing that by finite differences would cost one full ray trace per parameter — hopeless. The adjoint method, which in modern terms is exactly reverse-mode automatic differentiation, computes the entire gradient in a single backward pass, at a cost independent of the number of parameters. The physics itself is standard: each ray refracts by the vectorial Snell's law t=ηd+(ηcosθicosθt)n\mathbf{t} = \eta\,\mathbf{d} + (\eta\cos\theta_i - \cos\theta_t)\,\mathbf{n} with η=n1/n2\eta = n_1/n_2 and cosθt=1η2(1cos2θi)\cos\theta_t = \sqrt{1 - \eta^2(1-\cos^2\theta_i)}. What makes TeneaX different is that this whole chain is written in JAX, so the adjoint pass yields cL=(I/c)(L/I)\nabla_c \mathcal{L} = (\partial I/\partial c)^{\top}(\partial \mathcal{L}/\partial I) essentially for free — the same one-backward-pass trick that powers neural-network training and physically based renderers like Mitsuba. That is what makes inverse design at this scale actually tractable.

Adjoint Methods

NURBS Surfaces

The lens surface has to be smooth, freeform, and controllable with a manageable number of parameters — which is exactly what a NURBS surface gives you. It is a weighted combination of B-spline basis functions over a grid of control points, S(u,v)=ijNi,p(u)Nj,q(v)wijPijijNi,p(u)Nj,q(v)wij,S(u,v) = \frac{\sum_i \sum_j N_{i,p}(u)\,N_{j,q}(v)\,w_{ij}\,\mathbf{P}_{ij}}{\sum_i \sum_j N_{i,p}(u)\,N_{j,q}(v)\,w_{ij}}, where the control points Pij\mathbf{P}_{ij} are the design variables the optimizer moves and everything else stays fixed. A modest 15×1515\times15 grid already spans a rich space of freeform shapes while keeping the problem well-conditioned. Tracing a ray against such a surface has no closed form, so every ray–surface hit is found by Newton–Raphson iteration, solving S(u,v)=o+tdS(u,v) = \mathbf{o} + t\,\mathbf{d} for (u,v,t)(u,v,t) — and because Newton's iteration is itself differentiable, the gradient still flows cleanly through the intersection.

Watch on YouTube
NURBS Surfaces

Resulting Surface

With a differentiable forward model and a NURBS parametrization in place, the inverse problem becomes a least-squares fit, and the classic optical-design optimizer applies: Levenberg–Marquardt. Writing the merit as residuals r(c)=I(c)Itargetr(c) = I(c) - I_{\text{target}}, each step solves (JJ+λI)δ=Jr,J=rc,(J^{\top}J + \lambda I)\,\delta = -\,J^{\top} r, \qquad J = \frac{\partial r}{\partial c}, and moves the control points by δ\delta. The damping λ\lambda interpolates between Gauss–Newton (fast, λ0\lambda \to 0) and gradient descent (safe, λ\lambda \to \infty): a step is accepted only if it lowers the cost, otherwise λ\lambda is raised and the step retried. That single rule also makes the solver robust to the numerical pathologies of ray tracing — total internal reflection, grazing hits — that would otherwise poison the optimization. The result is a freeform lens, expressed in real photometric units (Lux), that reshapes the source into the specified target pattern.

Resulting Surface