mjhoptics / ray-optics

geometric ray tracing for optical systems
BSD 3-Clause "New" or "Revised" License
263 stars 54 forks source link

Added surfaces not shown in plot #127

Open dominikonysz opened 1 year ago

dominikonysz commented 1 year ago

Hello Michael,

I'm currently having a problem with plotting an optical model after adding surfaces later on using seq_model.add_surface(...). Here is an example using the Triplet example:

from rayoptics.environment import *

from matplotlib import pyplot as plt

opm = OpticalModel()
sm = opm.seq_model

osp = opm.optical_spec
osp['pupil'] = PupilSpec(osp, key=['object', 'pupil'], value=12.5)
osp['wvls'] = WvlSpec([('F', 0.5), (587.5618, 1.0), ('C', 0.5)], ref_wl=1)
max_field = 20
FLD = [1e-10, 0.5 * max_field, 0.75 * max_field, max_field]
osp["fov"] = FieldSpec(osp, key=["object", "angle"], value=max(FLD), flds=FLD)

opm.radius_mode = True
sm.gaps[0].thi=1e10

sm.add_surface([23, 4.831, 'N-LAK9', 'Schott'])
sm.add_surface([7331.288, 5.86])
sm.add_surface([-24.456, .975, 'N-SF5,Schott'])
sm.set_stop()
sm.add_surface([21.896, 4.822])
sm.add_surface([86.759, 3.127, 'N-LAK9', 'Schott'])
sm.add_surface([-20.4942, 41.2365])

opm.update_model()

# Here everything is fine
plt.figure(
    100,
    FigureClass=InteractiveLayout,
    opt_model=opm,
    is_dark=True,
).plot()
plt.show()

opm.radius_mode = True
sm.set_cur_surface(3)
sm.add_surface([100, 5, 'N-LAK9', 'Schott'])
sm.add_surface([1000, 1])

opm.update_model()

# Here the new lens is missing but the rays are interacting with the hidden lens
plt.figure(
    100,
    FigureClass=InteractiveLayout,
    opt_model=opm,
    is_dark=True,
).plot()
plt.show()

So plotting the example optical model obviously works as expected:

but in the second plot the new lens (between the middle and last lens) is not shown although the rays are being traced correctly:

As far as I could see the new elements are not being added to the part_tree although the ids of the other elements are updated. I have also tried running opm.update_model(build="rebuild") but that didn't help unfortunately.

Am I missing some step additional to the opm.update_model() or is there some other way to force the reload of the part_tree to correctly display the plot?

mjhoptics commented 1 year ago

Hello @dominikonysz, Sorry for the delay in responding. I've looked at your well documented issue and see a modeling problem and have a workaround to the lens not drawing problem.

The first is that add_surface inserts the surface after cur_surface. This is the convention, at least with CODE V. This is different than inserting an item into into a python list. So if you want to insert an element following lens 2, you would set_cur_surface to 4, the rear surface of lens 2.

The workaround for the lens not drawing problem is to use a method in the opm, rebuild_from_seq(). This is my "force rebuild on the element model" hammer that rebuilds everything from the current sequential model.

sm.set_cur_surface(4)
sm.add_surface([100, 5, 'N-LAK9', 'Schott'])
sm.add_surface([1000, 1])

opm.update_model()
opm.rebuild_from_seq()

Clearly, though, this is a bug and should be fixed. I haven't had a chance to test it on my dev version; if it works there, I'll report back.

Let me know if you have any questions on this. Thanks for using ray-optics. Mike Hayford

dominikonysz commented 1 year ago

Hey @mjhoptics,

you're right about the modelling problem. That was a little oopsie in my example because I'm usually using a wrapper around the opm that uses surface+1 to skip the object surface. Thank you for pointing that out anyway!

And I have tried out the rebuild_from_seq() and that was exactly what I was looking for! I don't know how I missed that in my search with such a telling name.

Only thing I have noticed is that I need to call opm.rebuild_from_seq() before opm.update_model() if I remove surfaces because opm.update_model() breaks otherwise:

File [/opt/homebrew/Caskroom/miniconda/base/envs/thesis/lib/python3.10/site-packages/rayoptics/optical/opticalmodel.py:338](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniconda/base/envs/thesis/lib/python3.10/site-packages/rayoptics/optical/opticalmodel.py:338), in OpticalModel.update_model(self, **kwargs)
    334 if len(self['part_tree'].nodes_with_tag(tag='#element')) == 0:
    335     elements_from_sequence(self['ele_model'], self['seq_model'], 
    336                            self['part_tree'])
--> 338 self['ele_model'].update_model(**kwargs)
    339 self['part_tree'].update_model(**kwargs)
    340 if self.specsheet is None:

File [/opt/homebrew/Caskroom/miniconda/base/envs/thesis/lib/python3.10/site-packages/rayoptics/elem/elements.py:1997](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/Caskroom/miniconda/base/envs/thesis/lib/python3.10/site-packages/rayoptics/elem/elements.py:1997), in ElementModel.update_model(self, **kwargs)
   1994     e.parent = None
   1996 self.elements = elements
-> 1997 self.sequence_elements()
   1999 src_model = kwargs.get('src_model', None)
   2000 if src_model is not self:
...
   2024 for e in self.elements:
   2025     if hasattr(e, 'z_dir'):
-> 2026         e.z_dir = seq_model.z_dir[e.reference_idx()]

IndexError: list index out of range

I guess the cause is the same as for the other things so simply flipping the calls fixes that.

Thank you for your help!

mjhoptics commented 1 year ago

Hello @dominikonysz, Sorry for the delay in responding. I've looked at your well documented issue and see a modeling problem and have a workaround to the lens not drawing problem.

The first is that add_surface inserts the surface after cur_surface. This is the convention, at least with CODE V. This is different than inserting an item into into a python list. So if you want to insert an element following lens 2, you would set_cur_surface to 4, the rear surface of lens 2.

The workaround for the lens not drawing problem is to use a method in the opm, rebuild_from_seq(). This is my "force rebuild on the element model" hammer that rebuilds everything from the current sequential model.

sm.set_cur_surface(4)
sm.add_surface([100, 5, 'N-LAK9', 'Schott'])
sm.add_surface([1000, 1])

opm.update_model()
opm.rebuild_from_seq()

Clearly, though, this is a bug and should be fixed. I haven't had a chance to test it on my dev version; if it works there, I'll report back.

Let me know if you have any questions on this. Thanks for using ray-optics. Mike Hayford

mjhoptics commented 1 year ago

Hello @dominikonysz , I updated 2 fixes for adding and removing elements when add_surface() and remove() are used. It was mostly there, just needed to wire it together correctly. Thanks again for your reports, let me know if you still see issues with it. Thanks Mike Hayford

dominikonysz commented 12 months ago

Hey @mjhoptics,

as far as I can tell your changes help with adding another lens into an "air gap" between two lenses. However, I'm still having a problem with introducing a new lens by splitting a lens in half and therefore first adding an air gap and then the first surface of the new lens. Here's another example to reproduce:

from rayoptics.environment import *

from matplotlib import pyplot as plt

opm = OpticalModel()
sm = opm.seq_model

osp = opm.optical_spec
osp['pupil'] = PupilSpec(osp, key=['object', 'pupil'], value=12.5)
osp['wvls'] = WvlSpec([('F', 0.5), (587.5618, 1.0), ('C', 0.5)], ref_wl=1)
max_field = 20
FLD = [1e-10, 0.5 * max_field, 0.75 * max_field, max_field]
osp["fov"] = FieldSpec(osp, key=["object", "angle"], value=max(FLD), flds=FLD)

sm.gaps[0].thi=1e10

sm.add_surface([0, 10, 'N-BK7', 'Schott'])
sm.add_surface([0, 10])
sm.add_surface([0, 10, 'N-BK7', 'Schott'])
sm.set_stop()
sm.add_surface([0, 10])
sm.add_surface([0, 10, 'N-BK7', 'Schott'])
sm.add_surface([0, 10])

opm.update_model()

plt.figure(
    100,
    FigureClass=InteractiveLayout,
    opt_model=opm,
    is_dark=True,
).plot()
plt.show()

sm.set_cur_surface(3)
sm.gaps[3].thi = 5
sm.add_surface([0, 1, 'air'])
sm.add_surface([0, 5, 'N-BK7', 'Schott'])

opm.update_model()

plt.figure(
    100,
    FigureClass=InteractiveLayout,
    opt_model=opm,
    is_dark=True,
).plot()
plt.show()

It's results in

compared to the result with the opm.rebuild_from_seq() workaround:

mjhoptics commented 12 months ago

Hi @dominikonysz, Yes, this is an (interesting) problem. This feels like less of a quick fix than a rethinking of elements_from_sequence. In the interim, I think whenever surface insertions or deletions cause modifications of existing elements, then using the rebuild_from_seq function is needed. Thanks for the report, hopefully the workaround is acceptable for now. Regards Mike Hayford

dominikonysz commented 11 months ago

Hey @mjhoptics, Sorry, I completely forgot to answer. Yes, the workaround is perfectly sufficient for my needs right now. Thank you for looking into it