Thin Film Optics Module
Description
This issue proposes the addition of a new module for modeling thin film optics. The objective is to support computation and visualization of the optical response (reflection, transmission) of multilayer systems using the transfer matrix method (or similar formalism). The implementation will build upon existing features in the library, including the material module for optical constants and integration with coating.
📌 Roadmap
1. Leverage existing materials module
- [x] Retrieve optical constants (n, k) using the existing
materialmodule. - [x] Allow user-defined material definitions and dispersion models.
2. Core computational engine
- [x] Implement multilayer reflection and transmission calculations.
- [x] Support variable wavelength and angle of incidence (including TE and TM polarizations).
- [x] Support $\lambda$ in nm and µm, wavenumber $k$, relative wavenumber $k_r$, eV, frequency $\nu$, degree and radiant.
- [x] Support QWOT layer.
3. Integration with existing modules
- [ ] Ensure interoperability with the
coatingmodule. - [ ] Align with current data structures and plotting systems.
4. Visualization tools
- [x] Plot reflectance and transmittance vs. wavelength and angle.
- [ ] Plot complex amplitude (e.g., electric field or reflectivity) throughout the layer stack.
- [x] Add layer diagram or stack visualizations.
- [ ] Color rendering
5. Examples and use cases
- [ ] Anti-reflection (AR) coating on glass.
- [ ] Bragg mirror (dielectric mirror).
- [ ] Metallic mirror with protective overcoat.
- [ ] Narrow-bandpass optical filter.
6. Advanced features (Phase 2)
- [x] Optimization of layer parameters (e.g., for target reflectivity).
- [ ] Sensitivity analysis to layer thickness or material changes.
- [ ] Multi-objective optimization (e.g., broadband AR + minimal absorption).
- [ ] Implement incoherent Thin Film Stack
I’m ready to start working on this and would be happy to iterate based on feedback. Let me know if anything should be prioritized differently or if there are design constraints I should be aware of.
Hi @lordpositron ,
Looks like a solid plan. And also this would be a great contribution. I have had several occasions where a special kind of coating was needed for a certain component (lens, or BS, etc.) and a lot of effort was put into its optimization. Having a module like the one you proposed directly within Optiland would be great!
As you may notice from the GUI current state, we left a "Materials" page in the sidebar. I envision that beyond having an interactive Abbe diagram with the different glass maps available, etc., we can also have a coatings analysis toolset (including optimization routines, etc.).
I'll be happy to keep an eye on this issue and track the progress, and if you need some feedback just lmk :)
Looking great :)
Best, ~Manuel
Hi @lordpositron,
Thanks for opening this and for offering to work on it! I'm glad we're getting to this feature.
I like the approach you proposed. I have some remarks/thoughts:
- TMMax looks like it uses jax. Would that be compatible here? Ideally, we'd set up the code using our backend, so we can swap between numpy and torch, if possible.
- If we need to write something ourselves, I personally like Polarized light and optical systems by Chipman, chapter 13. Good reference IMO, but I know there are many.
- On integration with existing modules - we need to think about how we want to implement this. If polarization is enabled, then it will likely need to follow the strategy used in
FresnelCoating, which requires a means to calculate the Jones matrix. If polarization is disabled, then I suppose we could use the average of TE & TM transmission (or reflection) for each surface. That could be built into a slightly simpler coating class type. - It would be great to be able to build a "thin film stack" object that we could e.g., pass as a coating type to a surface. I think this was your intention already.
- I really like the idea of optimization of thin film stacks. It would be great if we could set it up in a similar way to the standard optimization scheme we have. We don't need to focus on this now though, but I am happy to discuss more if interesting.
Let me know if you need help with anything, or want any feedback.
Thanks, Kramer
Hi @HarrisonKramer, Thanks for your detailed feedback, it's very helpful!
-
I plan to convert the core logic to NumPy/Numba, and possibly look into Torch compatibility later on. I'm not too familiar with Torch for now, so that will likely come in a second phase.
-
TMMax will not be used as a dependency or directly cloned. I’ll rewrite parts of it to fit the project’s structure. That said, it’s still a great reference to validate the implementation and get inspiration from the fast vectorized approach.
-
Thanks for the book reference, I haven’t read that particular chapter yet, but I’ll definitely check it out! 😄
-
Regarding the implementation: we can compute the TE and TM transmission/reflection, and from there build the corresponding Jones matrices, to integrate with the existing framework. The only remaining question is how well spatial dependence (and the variation of AOI) will propagate, but I’ll explore that through testing. Ultimately, this should align with the approach used in
BaseCoatingPolarized. -
I also indeed had in mind to develop a "thin film stack" object that computes transmission/reflection independently, but can also be plugged into more complex optical systems.
From your message, it sounds like we’re on the same page regarding the overall direction. I'll try to put together a proposal in the coming weeks, I’m a bit busy this week but looking forward to pushing this forward soon!
And thanks @manuelFragata for the enthusiasm 😃 Best, Corentin
Hi @lordpositron,
Looks good! Yes, I think we're aligned on the general direction.
Regarding using NumPy/Numba & PyTorch, you can also try to already build the code in Optiland using the existing backend abstraction. So, e.g., instead of writing np.array or np.sum, you'd use be.array and be.sum, where we import be as import optiland.backend as be. Of course, feel free to start with NumPy for now if you prefer.
One note I forgot - the Jones matrix calculation can be costly. It might be useful to cache and/or precalculate + interpolate. That could even be a configurable option. We can ignore this in the beginning, but it's something to consider.
Regards, Kramer
(This message is meant to illustrate the current progress of the project; it will be updated as things evolve) Hello, I’ve started implementing the matrix method for thin-film calculations, based on the following references:
- Chap. 13 Polarized Light and Optical Systems, Russell A. Chipman, Wai-Sze Tiffany Lam, and Garam Young
- Excellent entry point, complete.
- F. Abelès, Researches on the propagation of sinusoidal electromagnetic waves in stratified media. Applications to thin films, Ann. Phys. Paris, 12th Series 5 (1950): 596–640
- Initial publication
- Chap. 2 Thin-Film Optical Filters, 5th edition, Hugh Angus Macleod, CRC Press
- More detailed on thin film and concrete implementation.
The implementation is not entirely straightforward as I originally thought, in the case of absorbing materials ($k \neq 0$), since the calculations involve complex numbers. The matrix method is fully vectorized over AOI and $\lambda$ coordinates, and relies on optiland.backend.
Below, I present comparisons between my implementation, TMM, and TFCalc.
Comparison TMM/Optiland
TMM is recognized as a reference for thin-film optical calculations; in fact, it is the module used in Sarangan, A. (2020). Optical Thin Film Design (1st ed.). CRC Press. DOI.
Here, I chose to perform 2D tests (AOI, $\lambda$) for both $T$ and $R$, in $s$ and $p$ polarizations. The calculations were carried out on the same structure, with refractive indices taken from optiland.material in both cases. The differences between the TMM calculation and Optiland are within $10^{-13}$, demonstrating perfect agreement between the two methods.
From these tests, I observed a striking speed difference: the Optiland implementation is about 100–300× faster than TMM (which is not vectorized), even when using the numpy backend (non-Torch).
| Structure | Comparison TMM/Optiland |
|---|---|
| [TiO2/SiO2 $]_{10}$ thin-film stack at 45°. Initial test, for legacy | |
| From Rizea, Adrian & Popescu, I.M.. (2012). Design techniques for all-dielectric polarizing beam splitter cubes, under constrained situations. Romanian Reports in Physics. 64. 482-491. |
Comparison TFCalc/Optiland
Since I’m using the demo version of TFCalc, I can only use a limited set of material : TiO₂-A ($n(\lambda = 600 \text{ nm}) = 2.35 + i \times 3\times 10^{-4}$), SiO₂ ($n(\lambda = 600 \text{ nm}) = 1.45$, MgF₂ ($n(\lambda = 550 \text{ nm}) = 1.38$) and Air $n = 1$. In TFCalc, some materials (SiO₂ and TiO₂) have their refractive index for a range wavelength (300, 400, 500nm...), but as long as the interpolation method is not known, we will use IdealMaterial with fixed values of $n$ and $k$. To avoid issues unrelated to the computation method of reflectivities (mainly due to refractive index interpolation), we will compare the results of our code with two types of simulations:
- $R$ and $T$ (for $s$ and $p$ polarizations) versus AOI at a given $\lambda$, using a material with a known refractive index (at $\lambda$). Note that the refractive index of TiO₂-A is complex, thus we test the absortance part.
- $R$ and $T$ (for $s$ and $p$ polarizations) versus $\lambda$ for materials with a fixed refractive index (e.g., MgF₂ and H in TFCalc).
In the following table, I present three different structures (left column). We observe that the error remains below 10⁻³ (bottom axes of right column), which is roughly the export precision of TFCalc. Therefore, the results are very close, and we can consider our calculation method validated against the TFCalc approach.
| Structure | Comparison TFCalc/Optiland |
|---|---|
Code example
from optiland.materials import Material, IdealMaterial, plot_nk
from optiland.thin_film import ThinFilmStack, Layer
import optiland.backend as be
import matplotlib.pyplot as plt
MGF2 = Material("MgF2", reference="Rodriguez-de_Marcos")
TiO2 = Material("TiO2", reference="Zhukovsky")
air = IdealMaterial(1.0)
working_wl = 1.1 # um
d_TiO2 = working_wl / (4 * TiO2.n(working_wl)) # in um
d_MGF2 = working_wl / (4 * MGF2.n(working_wl)) # in um
# (LH)^6 L
layers = [Layer(MGF2, d_MGF2, "MGF2"), Layer(TiO2, d_TiO2, "TiO2")] * 6 + [
Layer(MGF2, d_MGF2, "MGF2"),
]
stack = ThinFilmStack(incident=air, substrate=air, layers=layers)
wl = be.linspace(200, 2000, 1001)
AOI = 0
R = stack.reflectance_nm_deg(wl, AOI, polarization="p")
# plot the structure
stack.plot_structure()
# no direct way to plot, but it's in the pipeline
fig, ax = plt.subplots()
ax.plot(wl, R)
ax.set_xlabel("$\lambda$ (nm)")
ax.set_ylabel("Reflectance")
ax.grid(True, alpha=0.3)
On the other hand, for the other modules I listed in the issue description, I haven’t obtained conclusive results yet — either because the APIs are limited : POCAL, TMM-FAST (especially for importing materials), or because the calculations seem inconsistent : TMMax (possibly due to mistakes on my side).
Corentin
Hi @lordpositron,
This is great! Really encouraging results. I'm glad it is working as expected, and great to hear you're using the optiland.backend. This will of course making it simpler to connect it into the Optiland workflows.
On the comparison to other tools - if the results are consistent both with TMM and TFCalc for a few different thin film coating configurations, then I'd consider it sufficiently verified to continue. We can test further once things are fully implemented, e.g., against other commercial lens design software.
Thanks for sharing the progress. Let me know if you need other feedback or help as you continue to the next step. It looks like you're a lot of the way there already!
Regards, Kramer
Hi @HarrisonKramer I was able to run tests comparing with TMM and TFCalc, and the results are shown in the previous comment (https://github.com/HarrisonKramer/optiland/issues/246#issuecomment-3218302573). They demonstrate very good agreement between the different implementations across various structures. I think we’re in good shape to continue the implementation — what do you think? If you have any questions or if something is unclear, don’t hesitate 🙂 Corentin
Hi @lordpositron,
This is really impressive work! Thanks for systematically benchmarking against both TMM and TFCalc, the results look excellent and it’s clear you’ve been very thorough in validating the implementation. I agree that we’re in good shape to move forward with the integration.
Thanks a lot for all the effort you’re putting in here. It’ll be a great addition to Optiland. Looking forward to the next steps!
Best, Kramer
Hello,
Just a quick message to say that I’m still working on the feature. I’ve been a bit short on time lately because of my PhD, but I think I’m getting close to being able to make a pull request, maybe not with all the requirements I initially proposed, but it should already be a good starting point. Then doing the rest on other PR. I’d also appreciate your feedback on the code structure, especially regarding the computation and optimization parts of the thin-film module. These aspects are somewhat decoupled from the ray-tracing logic. I didn’t manage to fully reuse the existing optimization framework, but in the end, it could actually be a really good thing. It would allow us to add operands for thin-film optimization, similar to what’s possible in Zemax.
Thanks again for all the work ! Best, Corentin
optiland/thin_film/
├── __init__.py # Public API exports
├── stack.py # ThinFilmStack - Main class
├── layer.py # Layer - Individual layer representation
├── analysis.py # SpectralAnalyzer - Spectral analysis tools
├── core.py # TMM Engine - Core calculations
└── optimization/ # Optimization subsystem
├── __init__.py # Optimization exports
├── optimizer.py # ThinFilmOptimizer - Main optimizer
├── report.py # ThinFilmReport - Results & visualization
├── variable/ # Optimization variables
│ └── layer_thickness.py # LayerThicknessVariable
└── operand/ # Optimization operands
└── thin_film.py # ThinFilmOperand - R/T/A calculations
Core Components
ThinFilmStack (stack.py)
- Purpose: Central class for multilayer optical calculations
- Key Features:
- TMM coherent calculations for R/T/A
- Multi-wavelength and multi-angle support
- Support for s, p, and unpolarized light
- Fluent API for stack construction
- Integration: Uses
optiland.materialsandoptiland.backend
Layer (layer.py)
- Purpose: Represents individual optical layer
- Properties:
- Material reference (BaseMaterial)
- Thickness in microns
- Complex refractive index n~ = n + ik
- Usage: Building blocks for ThinFilmStack
SpectralAnalyzer (analysis.py)
- Purpose: Advanced analysis and visualization tools
- Capabilities:
- Spectral sweeps with unit conversion
- Angular analysis
- Multi-polarization support
- Integrated matplotlib plotting
TMM Engine (core.py)
- Purpose: Low-level Transfer Matrix Method calculations
- Implementation:
_tmm_coh()function for coherent calculations- Vectorized operations for performance
- s/p polarization handling
Optimization Subsystem
ThinFilmOptimizer (optimization/optimizer.py)
- Purpose: High-level optimization interface
- Features:
- Fluent API design
- Multi-objective optimization
- SciPy integration (L-BFGS-B, TNC, SLSQP)
- State management and reset capabilities
Example Usage:
optimizer = ThinFilmOptimizer(stack)
optimizer.add_thickness_variable(0, min_nm=50, max_nm=200)
optimizer.add_target("R", 550.0, "below", 0.05)
result = optimizer.optimize()
LayerThicknessVariable (optimization/variable/layer_thickness.py)
- Purpose: Optimization variable for layer thickness
- Features:
- Inherits from VariableBehavior (Optiland pattern)
- Automatic scaling for optimization
- Bounds management (min/max)
- Real-time stack updates
ThinFilmOperand (optimization/operand/thin_film.py)
- Purpose: Merit function operands for R/T/A calculations
- Methods:
reflectance(),transmittance(),absorptance()- Weighted multi-wavelength calculations
- Polarization and angle support
Hi @lordpositron,
Thanks for the update! There's no huge rush here necessarily, and your PhD takes precedence of course :)
It is perfectly fine to start with a subset of the original proposed tasks for an initial PR. We can always add more features and it's best (and easier) to work in smaller PRs anyway.
Your proposal looks great and I would accept the PR if it were implemented this way. I very much like the ability to directly optimize the stacks, and your proposed interface is clean. I had originally thought to configure the thin film optimization to have a very similar API to Optiland's current optimization API, and possibly even configure it so they could interact. For example, it would be neat to be able to optimize image resolution (like MTF) of a system by varying the coating properties. What you have is pretty much spot on here, which looks great and will work well. I'm happy to proceed with your proposal.
Let me know if you need any other help or feedback.
Thanks again! Kramer