differentiable-tmm 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- differentiable_tmm-0.1.0/Differentiable_TMM/__init__.py +18 -0
- differentiable_tmm-0.1.0/MANIFEST.in +5 -0
- differentiable_tmm-0.1.0/PKG-INFO +627 -0
- differentiable_tmm-0.1.0/README.md +604 -0
- differentiable_tmm-0.1.0/assets/Si3N4.txt +101 -0
- differentiable_tmm-0.1.0/assets/SiO2.txt +395 -0
- differentiable_tmm-0.1.0/core/TMM.py +322 -0
- differentiable_tmm-0.1.0/core/__init__.py +3 -0
- differentiable_tmm-0.1.0/core/_init_.py +1 -0
- differentiable_tmm-0.1.0/core/color_transformation.py +193 -0
- differentiable_tmm-0.1.0/differentiable_tmm.egg-info/PKG-INFO +627 -0
- differentiable_tmm-0.1.0/differentiable_tmm.egg-info/SOURCES.txt +29 -0
- differentiable_tmm-0.1.0/differentiable_tmm.egg-info/dependency_links.txt +1 -0
- differentiable_tmm-0.1.0/differentiable_tmm.egg-info/requires.txt +5 -0
- differentiable_tmm-0.1.0/differentiable_tmm.egg-info/top_level.txt +5 -0
- differentiable_tmm-0.1.0/example/__init__.py +1 -0
- differentiable_tmm-0.1.0/example/_paths.py +8 -0
- differentiable_tmm-0.1.0/example/example1.py +164 -0
- differentiable_tmm-0.1.0/example/example2.py +158 -0
- differentiable_tmm-0.1.0/example/example3.py +221 -0
- differentiable_tmm-0.1.0/example/example4.py +237 -0
- differentiable_tmm-0.1.0/example/example5.py +134 -0
- differentiable_tmm-0.1.0/pyproject.toml +49 -0
- differentiable_tmm-0.1.0/setup.cfg +4 -0
- differentiable_tmm-0.1.0/utils/__init__.py +3 -0
- differentiable_tmm-0.1.0/utils/_init_.py +1 -0
- differentiable_tmm-0.1.0/utils/chromaticity_store.py +112 -0
- differentiable_tmm-0.1.0/utils/datatype_restriction.py +32 -0
- differentiable_tmm-0.1.0/utils/illumination_store.py +4852 -0
- differentiable_tmm-0.1.0/utils/observer_store.py +965 -0
- differentiable_tmm-0.1.0/utils/spectrum_interpolation.py +63 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Public API for Differentiable_TMM."""
|
|
2
|
+
|
|
3
|
+
from core import TMM
|
|
4
|
+
from core.color_transformation import DE94, Spectrum2XYZ, XYZ2Lab, XYZ2sRGB
|
|
5
|
+
from utils import spectrum_interpolation
|
|
6
|
+
from utils.illumination_store import illumination
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.0"
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"TMM",
|
|
12
|
+
"spectrum_interpolation",
|
|
13
|
+
"Spectrum2XYZ",
|
|
14
|
+
"XYZ2Lab",
|
|
15
|
+
"XYZ2sRGB",
|
|
16
|
+
"DE94",
|
|
17
|
+
"illumination",
|
|
18
|
+
]
|
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: differentiable_tmm
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Differentiable transfer-matrix method tools for multilayer thin-film optics and color simulation.
|
|
5
|
+
Author: Peiqin
|
|
6
|
+
Project-URL: Homepage, https://github.com/Dav1d-L1/Differentiable_TMM
|
|
7
|
+
Project-URL: Repository, https://github.com/Dav1d-L1/Differentiable_TMM
|
|
8
|
+
Keywords: thin-film,transfer-matrix-method,TMM,optics,differentiable,pytorch,color-science
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
Requires-Dist: matplotlib
|
|
19
|
+
Requires-Dist: numpy
|
|
20
|
+
Requires-Dist: scipy
|
|
21
|
+
Requires-Dist: torch
|
|
22
|
+
Requires-Dist: tqdm
|
|
23
|
+
|
|
24
|
+
# Differentiable_TMM
|
|
25
|
+
|
|
26
|
+
`Differentiable_TMM` is a PyTorch-based transfer-matrix method (TMM) toolkit for
|
|
27
|
+
multilayer thin-film optics. It computes angle- and wavelength-dependent
|
|
28
|
+
reflectance/transmittance and can be connected directly to PyTorch optimizers for
|
|
29
|
+
inverse design of thin-film structures. The repository also includes color
|
|
30
|
+
conversion utilities for converting spectra to CIE XYZ, CIE Lab, and sRGB.
|
|
31
|
+
|
|
32
|
+
All optical lengths in this project use **nanometers (nm)**. Incident angles are
|
|
33
|
+
given in **degrees** when constructing the simulator.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
Install the package and all dependencies, including the example dependencies:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install Differentiable_TMM
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Main API
|
|
44
|
+
|
|
45
|
+
The recommended public API is:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from Differentiable_TMM import (
|
|
49
|
+
TMM,
|
|
50
|
+
spectrum_interpolation,
|
|
51
|
+
Spectrum2XYZ,
|
|
52
|
+
XYZ2Lab,
|
|
53
|
+
XYZ2sRGB,
|
|
54
|
+
DE94,
|
|
55
|
+
)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`TMM` is the transfer-matrix module, `spectrum_interpolation` contains the
|
|
59
|
+
refractive-index helper classes, and the color-conversion classes/functions are
|
|
60
|
+
available directly from `Differentiable_TMM`.
|
|
61
|
+
|
|
62
|
+
## Refractive Index Helpers
|
|
63
|
+
|
|
64
|
+
### `spectrum_interpolation.interpolate_refrective`
|
|
65
|
+
|
|
66
|
+
Creates a callable interpolation object for wavelength-dependent refractive
|
|
67
|
+
indices.
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from Differentiable_TMM import spectrum_interpolation
|
|
71
|
+
|
|
72
|
+
n_fn = spectrum_interpolation.interpolate_refrective(
|
|
73
|
+
wavelength,
|
|
74
|
+
refrective,
|
|
75
|
+
method="1d_interpolate",
|
|
76
|
+
)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Parameters:
|
|
80
|
+
|
|
81
|
+
- `wavelength`: `numpy.ndarray`
|
|
82
|
+
- Shape: `(num_wavelengths,)`
|
|
83
|
+
- Dtype: integer, `np.float32`, or `np.float64`
|
|
84
|
+
- Unit: nm
|
|
85
|
+
- Meaning: sampled wavelengths for the material data.
|
|
86
|
+
- `refrective`: `numpy.ndarray`
|
|
87
|
+
- Shape: `(num_wavelengths,)`
|
|
88
|
+
- Dtype: integer, float, or complex
|
|
89
|
+
- Meaning: refractive index values `n` or complex values `n + 1j*k`.
|
|
90
|
+
- `method`: `str`
|
|
91
|
+
- Supported values: `"1d_interpolate"` and `"cubicspline"`
|
|
92
|
+
- Default: `"1d_interpolate"`
|
|
93
|
+
|
|
94
|
+
Call input:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
n_values = n_fn(query_wavelengths)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
- `query_wavelengths`: `numpy.ndarray`
|
|
101
|
+
- Shape: any 1D shape, usually `(batch_size,)`
|
|
102
|
+
- Unit: nm
|
|
103
|
+
- Values outside the source wavelength range are clipped to the nearest
|
|
104
|
+
available wavelength.
|
|
105
|
+
|
|
106
|
+
Return value:
|
|
107
|
+
|
|
108
|
+
- `n_values`: `numpy.ndarray`
|
|
109
|
+
- Shape: same as `query_wavelengths`
|
|
110
|
+
- Dtype: complex
|
|
111
|
+
- Unit: dimensionless refractive index.
|
|
112
|
+
|
|
113
|
+
Example:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
import numpy as np
|
|
117
|
+
from Differentiable_TMM import spectrum_interpolation
|
|
118
|
+
|
|
119
|
+
data = np.loadtxt("assets/SiO2.txt")
|
|
120
|
+
SiO2_n_fn = spectrum_interpolation.interpolate_refrective(
|
|
121
|
+
data[:, 0] * 1e3,
|
|
122
|
+
data[:, 1] + 1j * data[:, 2],
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
wavelengths = np.array([450.0, 550.0, 650.0])
|
|
126
|
+
n_sio2 = SiO2_n_fn(wavelengths)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### `spectrum_interpolation.constent_refrective`
|
|
130
|
+
|
|
131
|
+
Creates a callable object for a wavelength-independent refractive index.
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
air_n_fn = spectrum_interpolation.constent_refrective(1.0)
|
|
135
|
+
glass_n_fn = spectrum_interpolation.constent_refrective(1.52)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Parameters:
|
|
139
|
+
|
|
140
|
+
- `refrective`: real or complex scalar
|
|
141
|
+
- Examples: `1.0`, `1.52`, `1.5 + 0.01j`
|
|
142
|
+
|
|
143
|
+
Call input:
|
|
144
|
+
|
|
145
|
+
- `value`: `numpy.ndarray`
|
|
146
|
+
- Shape: any 1D shape, usually `(batch_size,)`
|
|
147
|
+
- Unit: nm
|
|
148
|
+
|
|
149
|
+
Return value:
|
|
150
|
+
|
|
151
|
+
- `numpy.ndarray`
|
|
152
|
+
- Shape: same as `value`
|
|
153
|
+
- Dtype: complex
|
|
154
|
+
- Every entry equals the constant refractive index.
|
|
155
|
+
|
|
156
|
+
## Transfer-Matrix Simulation
|
|
157
|
+
|
|
158
|
+
### `TMM.tmm`
|
|
159
|
+
|
|
160
|
+
`TMM.tmm` is the main differentiable transfer-matrix simulator.
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
simulator = TMM.tmm(
|
|
164
|
+
pol="s",
|
|
165
|
+
datatype="complex128",
|
|
166
|
+
theta_array=theta_array,
|
|
167
|
+
wv_array=wv_array,
|
|
168
|
+
)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Constructor parameters:
|
|
172
|
+
|
|
173
|
+
- `pol`: `str`
|
|
174
|
+
- `"s"` for s-polarized light.
|
|
175
|
+
- `"p"` for p-polarized light.
|
|
176
|
+
- `datatype`: `str`
|
|
177
|
+
- `"complex64"` or `"complex128"`.
|
|
178
|
+
- This also determines the real dtype used internally:
|
|
179
|
+
- `"complex64"` -> `torch.float32`
|
|
180
|
+
- `"complex128"` -> `torch.float64`
|
|
181
|
+
- `theta_array`: `torch.Tensor`
|
|
182
|
+
- Shape: `(batch_size,)`
|
|
183
|
+
- Dtype: integer, `torch.float32`, or `torch.float64`
|
|
184
|
+
- Unit: degrees
|
|
185
|
+
- Meaning: incident angle for each simulation point.
|
|
186
|
+
- `wv_array`: `torch.Tensor`
|
|
187
|
+
- Shape: `(batch_size,)`
|
|
188
|
+
- Dtype: integer, `torch.float32`, or `torch.float64`
|
|
189
|
+
- Unit: nm
|
|
190
|
+
- Meaning: vacuum wavelength for each simulation point.
|
|
191
|
+
|
|
192
|
+
`theta_array` and `wv_array` must be on the same device and have the same first
|
|
193
|
+
dimension.
|
|
194
|
+
|
|
195
|
+
### Calling a Simulator
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
result = simulator(
|
|
199
|
+
n=n,
|
|
200
|
+
d=d,
|
|
201
|
+
n0=n0,
|
|
202
|
+
c_list=c_list,
|
|
203
|
+
)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Input tensors:
|
|
207
|
+
|
|
208
|
+
- `n`: `torch.Tensor`
|
|
209
|
+
- Shape: `(batch_size, num_layers)`
|
|
210
|
+
- Dtype: complex dtype matching the simulator, usually `torch.complex128`
|
|
211
|
+
- Meaning: refractive index of every physical layer in the stack.
|
|
212
|
+
- `d`: `torch.Tensor`
|
|
213
|
+
- Shape: `(batch_size, num_layers)`
|
|
214
|
+
- Dtype: real dtype matching the simulator, usually `torch.float64`
|
|
215
|
+
- Unit: nm
|
|
216
|
+
- Meaning: thickness of every physical layer in the stack.
|
|
217
|
+
- `n0`: `torch.Tensor`
|
|
218
|
+
- Shape: `(batch_size, 2)`
|
|
219
|
+
- Dtype: complex or real
|
|
220
|
+
- Meaning:
|
|
221
|
+
- `n0[:, 0]`: incident medium refractive index
|
|
222
|
+
- `n0[:, 1]`: output/substrate-side medium refractive index
|
|
223
|
+
- The incident and output media are treated as incoherent semi-infinite media.
|
|
224
|
+
- `c_list`: `list[str]`
|
|
225
|
+
- Length: `num_layers`
|
|
226
|
+
- Each entry must be:
|
|
227
|
+
- `"c"` for coherent layer
|
|
228
|
+
- `"i"` for incoherent layer
|
|
229
|
+
- The layer count must satisfy:
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
n.shape[1] == d.shape[1] == len(c_list)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Return value:
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
{
|
|
239
|
+
"R": R,
|
|
240
|
+
"T": T,
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
- `R`: `torch.Tensor`
|
|
245
|
+
- Shape: `(batch_size,)`
|
|
246
|
+
- Reflectance.
|
|
247
|
+
- `T`: `torch.Tensor`
|
|
248
|
+
- Shape: `(batch_size,)`
|
|
249
|
+
- Transmittance.
|
|
250
|
+
|
|
251
|
+
The operations are differentiable with respect to differentiable inputs such as
|
|
252
|
+
`d`, so layer thicknesses can be optimized with PyTorch.
|
|
253
|
+
|
|
254
|
+
### Minimal Reflectance/Transmittance Example
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
import numpy as np
|
|
258
|
+
import torch
|
|
259
|
+
from Differentiable_TMM import TMM, spectrum_interpolation
|
|
260
|
+
|
|
261
|
+
device = torch.device("cpu")
|
|
262
|
+
|
|
263
|
+
air_n_fn = spectrum_interpolation.constent_refrective(1.0)
|
|
264
|
+
glass_n_fn = spectrum_interpolation.constent_refrective(1.52)
|
|
265
|
+
film_n_fn = spectrum_interpolation.constent_refrective(2.0)
|
|
266
|
+
|
|
267
|
+
wavelengths_np = np.linspace(400, 700, 31)
|
|
268
|
+
angles_np = np.zeros_like(wavelengths_np)
|
|
269
|
+
|
|
270
|
+
n0 = torch.tensor(
|
|
271
|
+
np.array([air_n_fn(wavelengths_np), air_n_fn(wavelengths_np)]).T,
|
|
272
|
+
dtype=torch.complex128,
|
|
273
|
+
).to(device)
|
|
274
|
+
|
|
275
|
+
n = torch.tensor(
|
|
276
|
+
np.array([glass_n_fn(wavelengths_np), film_n_fn(wavelengths_np)]).T,
|
|
277
|
+
dtype=torch.complex128,
|
|
278
|
+
).to(device)
|
|
279
|
+
|
|
280
|
+
d = torch.tensor(
|
|
281
|
+
np.array([[500000.0, 100.0]] * len(wavelengths_np)),
|
|
282
|
+
dtype=torch.float64,
|
|
283
|
+
).to(device)
|
|
284
|
+
|
|
285
|
+
theta_array = torch.tensor(angles_np, dtype=torch.float64).to(device)
|
|
286
|
+
wv_array = torch.tensor(wavelengths_np, dtype=torch.float64).to(device)
|
|
287
|
+
c_list = ["i", "c"]
|
|
288
|
+
|
|
289
|
+
sim_s = TMM.tmm("s", "complex128", theta_array, wv_array)
|
|
290
|
+
sim_p = TMM.tmm("p", "complex128", theta_array, wv_array)
|
|
291
|
+
|
|
292
|
+
result_s = sim_s(n=n, d=d, n0=n0, c_list=c_list)
|
|
293
|
+
result_p = sim_p(n=n, d=d, n0=n0, c_list=c_list)
|
|
294
|
+
|
|
295
|
+
reflectance = (result_s["R"] + result_p["R"]) / 2
|
|
296
|
+
transmittance = (result_s["T"] + result_p["T"]) / 2
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Color Conversion
|
|
300
|
+
|
|
301
|
+
The color utilities are imported directly from `Differentiable_TMM`.
|
|
302
|
+
|
|
303
|
+
### `Spectrum2XYZ`
|
|
304
|
+
|
|
305
|
+
Converts spectral reflectance or transmittance curves to CIE XYZ.
|
|
306
|
+
|
|
307
|
+
```python
|
|
308
|
+
spectrum_to_xyz = Spectrum2XYZ(
|
|
309
|
+
lightsource="D65",
|
|
310
|
+
observer="CIE 1931 2 Degree Standard Observer",
|
|
311
|
+
device=device,
|
|
312
|
+
datatype="complex128",
|
|
313
|
+
clip=True,
|
|
314
|
+
)
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Parameters:
|
|
318
|
+
|
|
319
|
+
- `lightsource`: `str`
|
|
320
|
+
- Must be one of the keys in `utils.illumination_store.illumination`.
|
|
321
|
+
- Common values include `"A"`, `"D50"`, `"D55"`, `"D65"`, `"D75"`, and
|
|
322
|
+
fluorescent illuminants such as `"FL1"`.
|
|
323
|
+
- `observer`: `str`
|
|
324
|
+
- Supported observers are stored in `utils.observer_store.Observer`.
|
|
325
|
+
- Common values:
|
|
326
|
+
- `"CIE 1931 2 Degree Standard Observer"`
|
|
327
|
+
- `"CIE 1964 10 Degree Standard Observer"`
|
|
328
|
+
- `device`: `torch.device`
|
|
329
|
+
- Device where internal tensors are stored.
|
|
330
|
+
- `datatype`: `str`
|
|
331
|
+
- `"complex64"` or `"complex128"`.
|
|
332
|
+
- `clip`: `bool`
|
|
333
|
+
- If `True`, uses the ASTM E308 practical working wavelength range
|
|
334
|
+
360-780 nm.
|
|
335
|
+
|
|
336
|
+
Useful attributes:
|
|
337
|
+
|
|
338
|
+
- `spectrum_to_xyz.wavelength`
|
|
339
|
+
- `torch.Tensor`
|
|
340
|
+
- Shape: `(num_wavelengths,)`
|
|
341
|
+
- Unit: nm
|
|
342
|
+
- Use this wavelength array when building TMM spectra for color conversion.
|
|
343
|
+
- `spectrum_to_xyz.datatype_real`
|
|
344
|
+
- Real dtype corresponding to the selected complex dtype.
|
|
345
|
+
|
|
346
|
+
Call input:
|
|
347
|
+
|
|
348
|
+
```python
|
|
349
|
+
XYZ = spectrum_to_xyz(reflectances)
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
- `reflectances`: `torch.Tensor`
|
|
353
|
+
- Shape: `(batch_size, num_wavelengths)`
|
|
354
|
+
- Dtype: real or complex tensor compatible with the selected device/dtype
|
|
355
|
+
- Meaning: spectral reflectance or transmittance sampled at
|
|
356
|
+
`spectrum_to_xyz.wavelength`.
|
|
357
|
+
|
|
358
|
+
Return value:
|
|
359
|
+
|
|
360
|
+
- `XYZ`: `torch.Tensor`
|
|
361
|
+
- Shape: `(batch_size, 3)`
|
|
362
|
+
- Columns: `X`, `Y`, `Z`
|
|
363
|
+
|
|
364
|
+
### `XYZ2Lab`
|
|
365
|
+
|
|
366
|
+
Converts CIE XYZ to CIE Lab under a specified illuminant and observer.
|
|
367
|
+
|
|
368
|
+
```python
|
|
369
|
+
xyz_to_lab = XYZ2Lab(
|
|
370
|
+
lightsource="D65",
|
|
371
|
+
observer="CIE 1931 2 Degree Standard Observer",
|
|
372
|
+
device=device,
|
|
373
|
+
datatype="complex128",
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
Lab = xyz_to_lab(XYZ)
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Input:
|
|
380
|
+
|
|
381
|
+
- `XYZ`: `torch.Tensor`
|
|
382
|
+
- Shape: `(batch_size, 3)`
|
|
383
|
+
- Columns: `X`, `Y`, `Z`
|
|
384
|
+
|
|
385
|
+
Output:
|
|
386
|
+
|
|
387
|
+
- `Lab`: `torch.Tensor`
|
|
388
|
+
- Shape: `(batch_size, 3)`
|
|
389
|
+
- Columns: `L*`, `a*`, `b*`
|
|
390
|
+
|
|
391
|
+
### `XYZ2sRGB`
|
|
392
|
+
|
|
393
|
+
Converts CIE XYZ to sRGB.
|
|
394
|
+
|
|
395
|
+
```python
|
|
396
|
+
xyz_to_srgb = XYZ2sRGB(device, datatype="complex128")
|
|
397
|
+
RGB = xyz_to_srgb(XYZ)
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
Input:
|
|
401
|
+
|
|
402
|
+
- `XYZ`: `torch.Tensor`
|
|
403
|
+
- Shape: `(batch_size, 3)`
|
|
404
|
+
|
|
405
|
+
Output:
|
|
406
|
+
|
|
407
|
+
- `RGB`: `torch.Tensor`
|
|
408
|
+
- Shape: `(batch_size, 3)`
|
|
409
|
+
- Columns: red, green, blue
|
|
410
|
+
- Values are not clipped inside the function. For plotting with Matplotlib,
|
|
411
|
+
use:
|
|
412
|
+
|
|
413
|
+
```python
|
|
414
|
+
RGB_plot = np.clip(np.real(RGB.detach().cpu().numpy()), 0, 1)
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### `DE94`
|
|
418
|
+
|
|
419
|
+
Computes CIE94 color difference.
|
|
420
|
+
|
|
421
|
+
```python
|
|
422
|
+
delta_e = DE94(Lab, Lab_target)
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
Inputs:
|
|
426
|
+
|
|
427
|
+
- `Lab`: `torch.Tensor`
|
|
428
|
+
- Shape: `(batch_size, 3)`
|
|
429
|
+
- `Lab_target`: `torch.Tensor`
|
|
430
|
+
- Shape: `(batch_size, 3)`
|
|
431
|
+
|
|
432
|
+
Output:
|
|
433
|
+
|
|
434
|
+
- `delta_e`: `torch.Tensor`
|
|
435
|
+
- Shape: `(batch_size,)`
|
|
436
|
+
|
|
437
|
+
## Differentiable Optimization Pattern
|
|
438
|
+
|
|
439
|
+
A common inverse-design workflow is to optimize unconstrained variables and map
|
|
440
|
+
them into physical layer thickness bounds with a sigmoid:
|
|
441
|
+
|
|
442
|
+
```python
|
|
443
|
+
lower_bound = 1.0
|
|
444
|
+
upper_bound = 2000.0
|
|
445
|
+
|
|
446
|
+
struc_logits = torch.zeros(num_layers, dtype=torch.float64, requires_grad=True)
|
|
447
|
+
optimizer = torch.optim.Adam([struc_logits], lr=0.01)
|
|
448
|
+
|
|
449
|
+
for epoch in range(epochs):
|
|
450
|
+
struc = lower_bound + (upper_bound - lower_bound) * torch.sigmoid(struc_logits)
|
|
451
|
+
d = torch.cat([meta_data, struc.repeat(num_samples, 1)], dim=-1)
|
|
452
|
+
|
|
453
|
+
# Run TMM -> spectrum -> XYZ -> Lab, then compare with target Lab.
|
|
454
|
+
loss.backward()
|
|
455
|
+
optimizer.step()
|
|
456
|
+
optimizer.zero_grad()
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
This keeps every optimized layer thickness inside `[lower_bound, upper_bound]`
|
|
460
|
+
without adding a penalty term.
|
|
461
|
+
|
|
462
|
+
## Examples
|
|
463
|
+
|
|
464
|
+
Example scripts live in the `example/` directory. Run them from the project root:
|
|
465
|
+
|
|
466
|
+
```bash
|
|
467
|
+
python example/example1.py
|
|
468
|
+
python example/example2.py
|
|
469
|
+
python example/example3.py
|
|
470
|
+
python example/example4.py
|
|
471
|
+
python example/example5.py
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
All example PNG outputs are saved in the `assets/` directory.
|
|
475
|
+
|
|
476
|
+
### Example 1: Angle and Thickness Color Map
|
|
477
|
+
|
|
478
|
+
File: `example/example1.py`
|
|
479
|
+
|
|
480
|
+
Purpose:
|
|
481
|
+
|
|
482
|
+
- Simulates a 5-layer coating stack.
|
|
483
|
+
- Sweeps observation angle and one film thickness.
|
|
484
|
+
- Converts reflectance and transmittance spectra to sRGB.
|
|
485
|
+
- Saves two color maps:
|
|
486
|
+
- `assets/example1.png`
|
|
487
|
+
- `assets/example1_1.png`
|
|
488
|
+
|
|
489
|
+
Main output:
|
|
490
|
+
|
|
491
|
+
- Reflectance color map.
|
|
492
|
+
- Transmittance color map.
|
|
493
|
+
|
|
494
|
+
### Example 2: Normal-Incidence Lab Optimization
|
|
495
|
+
|
|
496
|
+
File: `example/example2.py`
|
|
497
|
+
|
|
498
|
+
Purpose:
|
|
499
|
+
|
|
500
|
+
- Optimizes a 5-layer stack at normal incidence.
|
|
501
|
+
- Target color is specified as Lab.
|
|
502
|
+
- Uses sigmoid thickness bounds.
|
|
503
|
+
- Saves:
|
|
504
|
+
- `assets/example2.png`
|
|
505
|
+
|
|
506
|
+
Main output:
|
|
507
|
+
|
|
508
|
+
- Plot of `L*`, `a*`, and `b*` versus optimization epoch.
|
|
509
|
+
- Printed final layer thicknesses in nm.
|
|
510
|
+
|
|
511
|
+
### Example 3: Multi-Angle Lab Optimization
|
|
512
|
+
|
|
513
|
+
File: `example/example3.py`
|
|
514
|
+
|
|
515
|
+
Purpose:
|
|
516
|
+
|
|
517
|
+
- Optimizes a configurable multilayer coating against Lab targets at several
|
|
518
|
+
observation angles.
|
|
519
|
+
- Uses a configurable stack builder:
|
|
520
|
+
|
|
521
|
+
```python
|
|
522
|
+
num_coating_layers = 11
|
|
523
|
+
|
|
524
|
+
def build_layer_refractive_indices(wavelengths):
|
|
525
|
+
layers = [glass_n_fn(wavelengths)]
|
|
526
|
+
coating_n_fns = [SiO2_n_fn, Si3N4_n_fn]
|
|
527
|
+
for i in range(num_coating_layers):
|
|
528
|
+
layers.append(coating_n_fns[i % 2](wavelengths))
|
|
529
|
+
return np.array(layers)
|
|
530
|
+
|
|
531
|
+
def build_c_list():
|
|
532
|
+
return ["i"] + ["c"] * num_coating_layers
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
Saves:
|
|
536
|
+
|
|
537
|
+
- `assets/example3.png`
|
|
538
|
+
- `assets/example3_1.png`
|
|
539
|
+
|
|
540
|
+
Main output:
|
|
541
|
+
|
|
542
|
+
- Lab value versus observation angle.
|
|
543
|
+
- Color bar visualization of the optimized design.
|
|
544
|
+
- Printed final layer thicknesses in nm.
|
|
545
|
+
|
|
546
|
+
### Example 4: Tolerance-Aware Optimization
|
|
547
|
+
|
|
548
|
+
File: `example/example4.py`
|
|
549
|
+
|
|
550
|
+
Purpose:
|
|
551
|
+
|
|
552
|
+
- Optimizes a configurable multilayer coating while sampling thickness tolerance
|
|
553
|
+
variations with Latin hypercube sampling.
|
|
554
|
+
- Uses the same configurable layer count pattern as Example 3.
|
|
555
|
+
- Saves:
|
|
556
|
+
- `assets/example4.png`
|
|
557
|
+
|
|
558
|
+
Main output:
|
|
559
|
+
|
|
560
|
+
- Comparison between initial and optimized designs under a tolerance sweep.
|
|
561
|
+
- Printed initial and final layer thicknesses in nm.
|
|
562
|
+
|
|
563
|
+
### Example 5: Color Under Different Illuminants
|
|
564
|
+
|
|
565
|
+
File: `example/example5.py`
|
|
566
|
+
|
|
567
|
+
Purpose:
|
|
568
|
+
|
|
569
|
+
- Computes reflectance once for a fixed coating design.
|
|
570
|
+
- Converts the same spectrum under all illuminants stored in
|
|
571
|
+
`utils.illumination_store.illumination`.
|
|
572
|
+
- Saves:
|
|
573
|
+
- `assets/example5.png`
|
|
574
|
+
|
|
575
|
+
Main output:
|
|
576
|
+
|
|
577
|
+
- A color bar showing the perceived sRGB color under each illuminant.
|
|
578
|
+
|
|
579
|
+
## Building the Package
|
|
580
|
+
|
|
581
|
+
Clean old builds:
|
|
582
|
+
|
|
583
|
+
```bash
|
|
584
|
+
rm -rf build dist *.egg-info
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
Build source distribution and wheel:
|
|
588
|
+
|
|
589
|
+
```bash
|
|
590
|
+
python -m build
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
The build artifacts will be created in `dist/`.
|
|
594
|
+
|
|
595
|
+
Check the package metadata:
|
|
596
|
+
|
|
597
|
+
```bash
|
|
598
|
+
twine check dist/*
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
Upload to TestPyPI first:
|
|
602
|
+
|
|
603
|
+
```bash
|
|
604
|
+
twine upload --repository testpypi dist/*
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
Upload to PyPI:
|
|
608
|
+
|
|
609
|
+
```bash
|
|
610
|
+
twine upload dist/*
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
After uploading, users can install with the command shown in the installation
|
|
614
|
+
section.
|
|
615
|
+
|
|
616
|
+
## Notes for Maintainers
|
|
617
|
+
|
|
618
|
+
- The recommended public import is `from Differentiable_TMM import ...`.
|
|
619
|
+
- The package still includes `core` and `utils` as top-level packages for
|
|
620
|
+
compatibility with existing scripts.
|
|
621
|
+
- The historical class/function names use `refrective` and `constent` spelling.
|
|
622
|
+
They are kept unchanged to preserve compatibility with the existing code.
|
|
623
|
+
- Keep `Differentiable_TMM/__init__.py` synchronized with the intended public
|
|
624
|
+
API when adding new user-facing functions.
|
|
625
|
+
- `torch` wheels are platform-specific. If users need a CUDA-specific PyTorch
|
|
626
|
+
installation, they should install PyTorch following the official PyTorch
|
|
627
|
+
instructions before installing this package.
|