Skip to content

Paraxial surfaces: fixes for tracing and rendering#514

Open
rjmoerland wants to merge 9 commits intomasterfrom
robert/f2-mirror
Open

Paraxial surfaces: fixes for tracing and rendering#514
rjmoerland wants to merge 9 commits intomasterfrom
robert/f2-mirror

Conversation

@rjmoerland
Copy link
Copy Markdown
Collaborator

@rjmoerland rjmoerland commented Mar 12, 2026

Background
While working on #382 I ran into a few issues regarding the handling of paraxial mirrors, and the backward tracing of real rays through paraxial surfaces. Those issues lead to erroneous results for the locations and magnitudes of the front and back focal plane when paraxial mirrors are involved. In addition, the RealRay interaction with paraxial surfaces was not handling backward tracing well, leading to confusing ray diagrams that did not reflect the expected outcome.

This PR hopes to address these shortcomings, and consists of two parts:

  1. Fixing the tracing of paraxial rays through the system that includes rays bouncing back from paraxial mirrors.
  2. Fixing the interaction of real rays with paraxial surfaces, in order to fix the display of the ray diagram.

Approach
The tracing of rays through real surfaces seems to be accurate regardless the direction, and also the paraxial calculations (f1, f2, etc.) seem accurate. Therefore, I've used the outcome of "real" models as the "gold standard" to compare the results with when using paraxial surfaces.

Sign convention:
For mirrors and a collimated beam coming from the left, the rays will be focused to the left of the surface when the focal length is negative. For a positive focal length, the rays will seem like coming from the right of the surface. The sign is flipped for rays coming from the right, i.e., after having reflected off an mirror earlier in the path. This sign convention seems to be in step with real mirrors: for a collimated beam coming from the left, a negative radius yields a real focus on the left, and a positive radius gives a virtual focus on the right. For rays from the right, it's vice versa.

Examples
Below I show a few examples of problematic lens systems. I tabulated the outcome of the paraxial calculations as obtained from master (at the time of writing based on this commit ), together with the outcomes obtained with the code in this PR (labeled as "This PR") and the outcome of paraxial calculations for an equivalent "real" lens system. Sometimes the results from master and this PR are identical, but then the ray diagram is off, sometimes both are off.

Below each table I plot the ray diagrams as obtained with the master branch, this branch, and the equivalent real systems. Also here, the paraxial ray diagrams obtained with this branch and the equivalent real systems are identical.

Example 1

A single paraxial mirror, with focal length -50 (real focus to the left):

f1 F1 P1 f2 F2 P2
Master 50.000 50.000 0.000 -50.000 -50.000 0.000
This PR -50.000 -50.000 0.000 -50.000 -50.000 0.000
Real mirror* -50.000 -50.000 0.000 -50.000 -50.000 0.000

*R = -100

master
image

This PR
image

Reference equivalent system
image

Example 2

A paraxial lens with f=50, followed by a flat mirror at 10 units distance, reflecting the light back through the lens:

f1 F1 P1 f2 F2 P2
Master -31.250 -18.750 12.500 -31.250 -18.750 12.500
This PR -31.250 -18.750 12.500 -31.250 -18.750 12.500
Real lens* -31.250 -18.750 12.500 -31.250 -18.750 12.500

*Lens consisting of two surfaces with R = 100 and R = -100, respectively, with n = 2 and thickness 0.

master
image

This PR
image

Reference equivalent system
image

Example 3

Two paraxial mirrors, with f = -50 and f = 50 respectively, and a gap of 20 in between:

f1 F1 P1 f2 F2 P2
Master 20.833 29.167 8.333 31.250 -1.250 -32.500
This PR -31.250 -18.750 12.500 31.250 -1.250 -32.500
Real mirrors* -31.250 -18.750 12.500 31.250 -1.250 -32.500

*Real mirrors with radius -100 and +100, respectively.

master:
image

This PR:
image

Reference equivalent system
image

Final remark
As far as my understanding goes, the paraxial ray tracer seems like it was written largely with linear optical lens systems in mind, and perhaps at a time when computing power was more limited. Optiland's real ray tracing has no issues with tracing rays through 3D space, and though I don't know what the best approach would be, I would like to see a fully vectorial approach for paraxial rays as well. Ideally, surfaces would be placed in the optical system with well-defined surface normals, and those normals would be used for paraxial ray tracing. In that case, there could be no confusion about the direction from which light is hitting the element. But, perhaps this is already considered in the non-sequential mode?

As a thought, perhaps attaching a ThinLensInteractionModel to every real surface, to be used during paraxial ray tracing only, could be a way to leverage the current RealRay tracing engine to obtain paraxial results relatively easily?

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 12, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #514      +/-   ##
==========================================
+ Coverage   93.48%   93.49%   +0.01%     
==========================================
  Files         375      375              
  Lines       23682    23693      +11     
==========================================
+ Hits        22138    22151      +13     
+ Misses       1544     1542       -2     
Files with missing lines Coverage Δ
...tiland/interactions/thin_lens_interaction_model.py 94.64% <100.00%> (+1.30%) ⬆️
optiland/paraxial.py 93.37% <100.00%> (ø)
optiland/raytrace/paraxial_ray_tracer.py 98.36% <100.00%> (+3.27%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@rjmoerland rjmoerland force-pushed the robert/f2-mirror branch 4 times, most recently from 50f15df to 7474f99 Compare March 15, 2026 12:13
@rjmoerland rjmoerland marked this pull request as ready for review March 15, 2026 12:15
@rjmoerland
Copy link
Copy Markdown
Collaborator Author

rjmoerland commented Mar 15, 2026

@HarrisonKramer, tangentially related to this PR: I've struggled with this much longer than one might think, based on the number of lines that are changed in the core framework. This was mostly because of the bugs with the signs described above, as now sign convention seems to be pretty consistent. I was thinking, would there be room for a tutorial on the sign conventions (if we agree on this PR)? I am thinking of putting it somewhere between the existing 1a 1b tutorials, and could deal with real (spherical) surfaces and paraxial surfaces.
Edit: and I'm volunteering to create it

@HarrisonKramer
Copy link
Copy Markdown
Owner

Hi @rjmoerland,

Thanks for this deepdive and the fix! It's clear there are flaws in the master branch implementation. Your fix looks solid, and the new sign handling makes sense. I think we can already merge this, but let me answer your questions here first.

First, you're right that paraxial raytracing logic was implemented with only simple linear systems in mind. I have come across a few cases, in which the assumptions here have failed. It very likely makes the most sense just to extend the paraxial ray tracing capability to 3D. I need to explore this in more detail, but I agree that it will effectively just be an extension of the 3D real ray tracing engine, but while using the paraxial assumptions.

Adding a ThinLensInteractionModel could also work, though my first instinct is that this might be more complex than needed. Happy to be proven wrong here though. I would like to investigate the possibilities here, then come back to it, if that's okay for you. If I create an issue (or later a PR), I will plan to reference you there too, so you can review any proposals.

And yes, a new tutorial would be great! If it's not clear for you, then it's unlikely it will be clear for the general user. Improving clarity here makes sense. Perhaps we can also update the docs if needed. For example, there's already a section on sign conventions in the cheat sheet.

Thanks again, and let me know if this PR is ready and I will merge.

Kramer

@rjmoerland
Copy link
Copy Markdown
Collaborator Author

(for the record, I'm gonna answer you, but I'm in the field for work right now so a bit squeezed for time. Same for the other PR! 🙏 )

@HarrisonKramer
Copy link
Copy Markdown
Owner

All good, no rush!

@rjmoerland
Copy link
Copy Markdown
Collaborator Author

Hi @HarrisonKramer ,

Thanks for this deepdive and the fix! It's clear there are flaws in the master branch implementation. Your fix looks solid, and the new sign handling makes sense. I think we can already merge this, but let me answer your questions here first.

Thanks! I've updated the PR to the new API and resolved the few conflicts. For me this is then good to have merged as well.

Adding a ThinLensInteractionModel could also work, though my first instinct is that this might be more complex than needed. Happy to be proven wrong here though. I would like to investigate the possibilities here, then come back to it, if that's okay for you. If I create an issue (or later a PR), I will plan to reference you there too, so you can review any proposals.

Yes, you're right, that was jumping the gun a bit. What I meant to say is that there seem to be 3 ray tracers: a paraxial ray tracer, a real ray tracer that can trace ParaxialRays, and one that can trace RealRays. It's extra overhead to maintain 3 ray tracers when something changes in the design of the framework, and I also found it somewhat confusing when trying to fix the issues in this PR: the paraxial values come from the paraxial ray tracer, but the graph is generated with RealRays through a surface interaction that may or may not be paraxial. On top of that, the paraxial ray tracers seem to be designed for the in-line configuration, whereas the 3D ray tracer seems to be very capable of tracing through optics at arbitrary locations.

What I like about the various InteractionModels is that they define a real and a paraxial interaction, an approach which I think has a high potential to simplify to a single ray tracer. In the current implementation, as far as I can tell, the interact_real_rays is fully vectorial and takes RealRays, but interact_paraxial_rays is mostly scalar, taking ParaxialRays. I personally would drop the paraxial ray tracers, and always use the vector-based approach, with RealRays (that can then be just called Rays).

In order to get the result of interacting with the real surface, you would trace via each surface and call a method named (for example) interact_real_surface. For paraxial results, you could call something like interact_paraxial_surface. Both would take the RealRays argument, but process them differently. Since this PR implements the paraxial interaction (thin lens model) vectorially for RealRays, it was the source of my enthusiasm to just slap one of those to every surface, but I agree there has to be a more elegant way. Maybe, the implementation for the thin lens model can serve as a start. I would love to see what ideas you yourself come up with, so please do tag me when you're ready.

And yes, a new tutorial would be great! If it's not clear for you, then it's unlikely it will be clear for the general user. Improving clarity here makes sense. Perhaps we can also update the docs if needed. For example, there's already a section on sign conventions in the cheat sheet.

Great! The sign convention you mentioned is pretty clear, so I was thinking of focusing on how the signs of real and the paraxial surfaces are tied together, as it is what I found difficult after I used paraxial mirrors. WDUT?

Thanks again, and let me know if this PR is ready and I will merge.

Ready! 🙏

Cheers,
Robert

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants