1
1
import dataclasses
2
2
import pathlib
3
3
import numpy as np
4
+ import scipy
4
5
import astropy .units as u
5
6
import named_arrays as na
6
7
import optika
7
8
8
9
__all__ = [
9
10
"multilayer_design" ,
10
11
"multilayer_witness_measured" ,
12
+ "multilayer_witness_fit" ,
11
13
]
12
14
13
15
14
16
def multilayer_design () -> optika .materials .MultilayerMirror :
15
17
"""
16
18
The as-designed multilayer coating for the ESIS diffraction gratings.
17
19
20
+ Based on the analysis of :cite:t:`Rebellato2018`, this model uses
21
+ :cite:t:`Kortright1988` for the silicon carbide optical constants, and
22
+ :cite:t:`VidalDasilva2010` for the magnesium optical constants.
23
+
18
24
Examples
19
25
--------
20
26
@@ -66,8 +72,22 @@ def multilayer_design() -> optika.materials.MultilayerMirror:
66
72
ax.set_axis_off()
67
73
"""
68
74
75
+ layer_oxide = optika .materials .Layer (
76
+ chemical = "SiO2" ,
77
+ thickness = 1 * u .nm ,
78
+ interface = optika .materials .profiles .ErfInterfaceProfile (1 * u .nm ),
79
+ kwargs_plot = dict (
80
+ color = "gray" ,
81
+ ),
82
+ x_label = 1.1 ,
83
+ )
84
+
69
85
layer_sic = optika .materials .Layer (
70
- chemical = "SiC" ,
86
+ chemical = optika .chemicals .Chemical (
87
+ formula = "SiC" ,
88
+ is_amorphous = True ,
89
+ table = "kortright" ,
90
+ ),
71
91
thickness = 10 * u .nm ,
72
92
interface = optika .materials .profiles .ErfInterfaceProfile (1 * u .nm ),
73
93
kwargs_plot = dict (
@@ -86,15 +106,18 @@ def multilayer_design() -> optika.materials.MultilayerMirror:
86
106
)
87
107
88
108
layer_mg = optika .materials .Layer (
89
- chemical = "Mg" ,
109
+ chemical = optika .chemicals .Chemical (
110
+ formula = "Mg" ,
111
+ table = "fernandez_perea" ,
112
+ ),
90
113
thickness = 30 * u .nm ,
91
114
interface = optika .materials .profiles .ErfInterfaceProfile (1 * u .nm ),
92
115
kwargs_plot = dict (
93
116
color = "pink" ,
94
117
),
95
118
)
96
119
97
- layer_sio2 = optika .materials .Layer (
120
+ layer_substrate = optika .materials .Layer (
98
121
chemical = "SiO2" ,
99
122
thickness = 10 * u .mm ,
100
123
interface = optika .materials .profiles .ErfInterfaceProfile (1 * u .nm ),
@@ -105,13 +128,13 @@ def multilayer_design() -> optika.materials.MultilayerMirror:
105
128
106
129
return optika .materials .MultilayerMirror (
107
130
layers = [
131
+ layer_oxide ,
132
+ layer_sic ,
108
133
dataclasses .replace (
109
- layer_sio2 ,
110
- thickness = 1 * u .nm ,
111
- x_label = 1.1 ,
134
+ layer_al ,
135
+ thickness = 4 * u .nm ,
136
+ interface = optika . materials . profiles . ErfInterfaceProfile ( 1 * u . nm ) ,
112
137
),
113
- layer_sic ,
114
- dataclasses .replace (layer_al , thickness = 4 * u .nm ),
115
138
layer_mg ,
116
139
optika .materials .PeriodicLayerSequence (
117
140
layers = [
@@ -121,9 +144,13 @@ def multilayer_design() -> optika.materials.MultilayerMirror:
121
144
],
122
145
num_periods = 3 ,
123
146
),
124
- dataclasses .replace (layer_al , thickness = 10 * u .nm ),
147
+ dataclasses .replace (
148
+ layer_al ,
149
+ thickness = 10 * u .nm ,
150
+ interface = optika .materials .profiles .ErfInterfaceProfile (1 * u .nm ),
151
+ ),
125
152
],
126
- substrate = layer_sio2 ,
153
+ substrate = layer_substrate ,
127
154
)
128
155
129
156
@@ -219,3 +246,189 @@ def multilayer_witness_measured() -> optika.materials.MeasuredMirror:
219
246
)
220
247
221
248
return result
249
+
250
+
251
+ def multilayer_witness_fit () -> optika .materials .MultilayerMirror :
252
+ r"""
253
+ A multilayer stack fitted to the witness sample measurements given by
254
+ :func:`multilayer_witness_measured`.
255
+
256
+ This fit has five free parameters: the ratio of the thicknesses of
257
+ :math:`\text{Mg}`, :math:`\text{Al}`, and the :math:`\text{SiC}` to their
258
+ as-designed thickness, the roughness of the substrate, and a single
259
+ roughness parameter for all the layers in the multilayer stack.
260
+
261
+ Examples
262
+ --------
263
+
264
+ Plot the fitted vs. measured reflectivity of the grating witness samples.
265
+
266
+ .. jupyter-execute::
267
+
268
+ import numpy as np
269
+ import matplotlib.pyplot as plt
270
+ import astropy.units as u
271
+ import astropy.visualization
272
+ import named_arrays as na
273
+ import optika
274
+ from esis.flights.f1.optics import gratings
275
+
276
+ # Load the measured reflectivity of the witness samples
277
+ multilayer_measured = gratings.materials.multilayer_witness_measured()
278
+ measurement = multilayer_measured.efficiency_measured
279
+
280
+ # Isolate the angle of incidence of the measurement
281
+ angle_incidence = measurement.inputs.direction
282
+
283
+ # Fit a multilayer stack to the measured reflectivity
284
+ multilayer = gratings.materials.multilayer_witness_fit()
285
+
286
+ # Define the rays incident on the multilayer stack that will be used to
287
+ # compute the reflectivity
288
+ rays = optika.rays.RayVectorArray(
289
+ wavelength=na.geomspace(250, 950, axis="wavelength", num=1001) * u.AA,
290
+ direction=na.Cartesian3dVectorArray(
291
+ x=np.sin(angle_incidence),
292
+ y=0,
293
+ z=np.cos(angle_incidence),
294
+ ),
295
+ )
296
+
297
+ # Compute the reflectivity of the fitted multilayer stack
298
+ reflectivity_fit = multilayer.efficiency(
299
+ rays=rays,
300
+ normal=na.Cartesian3dVectorArray(0, 0, -1),
301
+ )
302
+
303
+ # Plot the fitted vs. measured reflectivity
304
+ fig, ax = plt.subplots(constrained_layout=True)
305
+ na.plt.scatter(
306
+ measurement.inputs.wavelength,
307
+ measurement.outputs,
308
+ ax=ax,
309
+ label="measurement"
310
+ );
311
+ na.plt.plot(
312
+ rays.wavelength,
313
+ reflectivity_fit,
314
+ ax=ax,
315
+ axis="wavelength",
316
+ label="fit",
317
+ color="tab:orange",
318
+ );
319
+ ax.set_xlabel(f"wavelength ({rays.wavelength.unit:latex_inline})")
320
+ ax.set_ylabel("reflectivity")
321
+ ax.legend();
322
+
323
+ # Print the fitted multilayer stack
324
+ multilayer
325
+
326
+ Plot a diagram of the fitted multilayer stack
327
+
328
+ .. jupyter-execute::
329
+
330
+ with astropy.visualization.quantity_support():
331
+ fig, ax = plt.subplots()
332
+ multilayer.plot_layers(
333
+ ax=ax,
334
+ thickness_substrate=20 * u.nm,
335
+ )
336
+ ax.set_axis_off()
337
+
338
+ """
339
+
340
+ design = multilayer_design ()
341
+
342
+ measurement = multilayer_witness_measured ()
343
+ unit = u .nm
344
+
345
+ reflectivity = measurement .efficiency_measured .outputs
346
+ angle_incidence = measurement .efficiency_measured .inputs .direction
347
+
348
+ rays = optika .rays .RayVectorArray (
349
+ wavelength = measurement .efficiency_measured .inputs .wavelength ,
350
+ direction = na .Cartesian3dVectorArray (
351
+ x = np .sin (angle_incidence ),
352
+ y = 0 ,
353
+ z = np .cos (angle_incidence ),
354
+ ),
355
+ )
356
+
357
+ normal = na .Cartesian3dVectorArray (0 , 0 , - 1 )
358
+
359
+ def _multilayer (
360
+ ratio_SiC : float ,
361
+ ratio_Al : float ,
362
+ ratio_Mg : float ,
363
+ width_layers : float ,
364
+ width_substrate : float ,
365
+ ):
366
+
367
+ width_layers = width_layers * unit
368
+ width_substrate = width_substrate * unit
369
+
370
+ result = multilayer_design ()
371
+
372
+ result .layers [1 ].thickness = ratio_SiC * design .layers [1 ].thickness
373
+ result .layers [1 ].interface .width = width_layers
374
+
375
+ result .layers [2 ].thickness = ratio_Al * design .layers [2 ].thickness
376
+ result .layers [2 ].interface .width = width_layers
377
+
378
+ result .layers [3 ].thickness = ratio_Mg * design .layers [3 ].thickness
379
+ result .layers [3 ].interface .width = width_layers
380
+
381
+ thickness_Al = ratio_Al * design .layers [~ 1 ].layers [0 ].thickness
382
+ result .layers [~ 1 ].layers [0 ].thickness = thickness_Al
383
+ result .layers [~ 1 ].layers [0 ].interface .width = width_layers
384
+
385
+ thickness_SiC = ratio_SiC * design .layers [~ 1 ].layers [~ 1 ].thickness
386
+ result .layers [~ 1 ].layers [~ 1 ].thickness = thickness_SiC
387
+ result .layers [~ 1 ].layers [~ 1 ].interface .width = width_layers
388
+
389
+ thickness_Mg = ratio_Mg * design .layers [~ 1 ].layers [~ 0 ].thickness
390
+ result .layers [~ 1 ].layers [~ 0 ].thickness = thickness_Mg
391
+ result .layers [~ 1 ].layers [~ 0 ].interface .width = width_layers
392
+
393
+ result .layers [~ 0 ].thickness = ratio_Al * design .layers [~ 0 ].thickness
394
+ result .layers [~ 0 ].interface .width = width_layers
395
+
396
+ result .layers .pop (0 )
397
+
398
+ result .substrate .interface .width = width_substrate
399
+ result .substrate .chemical = "Si"
400
+
401
+ return result
402
+
403
+ def _func (x : np .ndarray ):
404
+
405
+ multilayer = _multilayer (* x )
406
+
407
+ reflectivity_fit = multilayer .efficiency (
408
+ rays = rays ,
409
+ normal = normal ,
410
+ )
411
+
412
+ result = np .sqrt (np .mean (np .square (reflectivity_fit - reflectivity )))
413
+
414
+ return result .ndarray .value
415
+
416
+ x0 = u .Quantity (
417
+ [
418
+ 1 ,
419
+ 1 ,
420
+ 1 ,
421
+ 1 ,
422
+ 1 ,
423
+ ]
424
+ )
425
+
426
+ bounds = [(0 , None )] * len (x0 )
427
+
428
+ fit = scipy .optimize .minimize (
429
+ fun = _func ,
430
+ x0 = x0 ,
431
+ bounds = bounds ,
432
+ )
433
+
434
+ return _multilayer (* fit .x )
0 commit comments