optiland icon indicating copy to clipboard operation
optiland copied to clipboard

Multi-sequence tracing

Open Jonas231 opened this issue 9 months ago • 5 comments

Is your feature request related to a problem? Please describe.

In Zemax e.g it is not possible to optimize for multiple sequences of surfaces or only via multi-configurations. Say you want to optimize the System for imaging Performance but at the same time optimize a special Ghost sequence or the System in reverse without having to define configs. Say you just define the sequence in which the rays shall strike the surfaces. Of cause only certain sequences will make sense... And of course respective object and image pos. will have to be defined for each sequence.

Describe the solution you'd like

There is the main sequence used to define the System and then you allow for sub-sequences with their own independent merit functions. This would be great. Describe alternatives you've considered

All possible realistic sub-sequences could be proposed by an Initial non-sequential trace with few rays. But this is only an idea to add this later... Additional context

Jonas231 avatar Mar 18 '25 12:03 Jonas231

Hi @Jonas231,

Thanks a lot for opening this issue. This is a very interesting proposal and one that I think could be implemented without considerable changes to the current codebase. Currently, Optiland traces rays through surfaces sequentially. The surfaces are effectively defined in a list. Reordering this list is a simple Python operation and would result in the rays passing through the system in a different order, which I think is what you want.

Can I ask what specific use cases you have in mind? Perhaps you can elaborate on these. I recognize the ghosting analysis - this is a good example. I wonder if there are others. That might help me better understand how best to implement something like this. I can imagine that changing the raytracing functionality slightly to also require a sub-sequence, as you call it, might work. By default, rays would pass through a system in sequential order, but the user can choose to modify this, e.g., reverse it. There could be other downstream effects of changing this, which would need to be investigated.

Interested to hear your thoughts. Perhaps you also have an idea of how this could be built into the current codebase efficiently.

Regards, Kramer

HarrisonKramer avatar Mar 18 '25 19:03 HarrisonKramer

Hi Kramer,

the feature is also advertized by Quadoa. They list a few possibilities of this feature. Perhaps the most relevant ones for me: Optimization of ghost paths and being able to optimize subcomponents of an optical system but monitoring the whole system performance at the same time. And perhaps telecentric / afocal imaging and Illumination through a telescope as example for a system where reverse performance could also be relevant. As for the programming - I am not yet deep enough into the code to give an answer how this can be done best.

Regards, Jonas

Jonas231 avatar Mar 19 '25 09:03 Jonas231

Hi Jonas,

Thanks for sharing more info and use cases. I also looked through the Quadoa features on their site. I definitely think this is something that could be implemented in Optiland and it would be a great enhancement. I will add this to the roadmap and will investigate how best to implement this. I will add some more details here on my current thoughts on the implementation and will use this issue to track the progress.

Proposed implementation strategy

  • Add a new SequencedOptic class that accepts an Optic instance and defines alternative traversal orders / sub-sequences.
  • A SequencedOptic instance wraps the functionality of the core Optic, and only modifies the tracing order and (optionally) whether a particular surface is refractive or reflective.
  • A SequencedOptic can be used anywhere where a standard Optic is used.
  • When a SequencedOptic is created with an instance of Optic, they should refer to the same surfaces, such that if the surface changes in the SequencedOptic instance that it is also changed in the Optic instance.

Possible sample code

optic = Optic(...)
# define rest of optic here...

# define a SequencedOptic with a new raytracing order
sequenced_optic = SequencedOptic(optic=optic, order=[0, 1, 2, 1, 2, 3])

# Run a spot diagram analysis
spot = SpotDiagram(sequenced_optic)
spot.view()

# Use in optimization - define metrics with both the original optic and the new sequenced_optic
# ... omitting some code here... see the examples in the docs for complete code
# Operand 1: RMS spot size for the new sequence
input_data = {'optic': sequenced_optic , 'surface_number': -1, 'Hx': 0, 'Hy': 0,
              'num_rays': 5, 'wavelength': 0.55, 'distribution': 'hexapolar'}
problem.add_operand(operand_type='rms_spot_size', target=0, weight=1, input_data=input_data)

# Operand 2: effective focal length for the base optic
input_data = {'optic': optic}
problem.add_operand(operand_type='f2', target=50, weight=1, input_data=input_data)

In general, I think this approach could work with relatively little change to the current codebase. However, there are a few potential difficulties I see here:

  • Not all operands/commands make sense for all possible sub-sequences. How should this be controlled/configured? Should the user be fully responsible for this, or should there be built-in constraints?
  • How should rays be built for a particular sub-sequence? Rays are defined based on field/aperture properties, in general. If the surface order changes, it will likely change the first order properties, which changes how rays will be built. Should rays be built using the baseline optic or the optic with the new surface sequence? Or, should this be configurable?
  • For visualization, if it is desirable to plot both the original and sequenced optic together, then this will require some reworking/refactoring.

Let me know if you have any other thoughts here, or have a preference on how some of the difficulties I point out should be handled.

Regards, Kramer

HarrisonKramer avatar Mar 22 '25 09:03 HarrisonKramer

Hi Kramer,

Great. About the potential difficulties:

I think both operands and rays should be possible to be defined for each sequence. The main sequence is used by default and to create the system. Unless you define a new object, source def. like ray grid type, stop and image plane those are inherited from the main sequence. This may make no physical sense. Then the user should have the possibility to define new values for the sub-sequence(s). Maybe optical elements of subsequent surfaces like lenses should be recognized automatically. In contrast to a new configuration no surfaces or elements are changed (pos. , orientation), except for those three surfs needed for ray def. Maybe ray pos. and angles should be able to be picked up from any surface of the main sequence. First order properties are different for each sequence and should be evaluated separatly. Only General constraints like min. Center thickness glass e.g. should be shared among the sequences. Default merit functions like spot size should be separate for each sequence. And yes, like multiple configs, multiple sequences should ideally be possible to plot together. Maybe a new config also has to be a subsequence and inherit from it or each new config can have its dedicated sequences (I am not sure about the distinction of both concepts. In SYNOPSYS configs can be almost totally independent optical systems...) These are my current preferences based on experience with commercial software.

Best regards, Jonas

Jonas231 avatar Mar 23 '25 16:03 Jonas231