def arc_reflection_model(wavelength, thickness=125, fraction_abraded=0, porosity=0.3, fraction_dust=0, aoi=8, n0=1.0003): """ Return the reflection values for a model of an aged ARC. The reflection is a linear combination of reflection from a thin film of a variable thickness and the reflection from BK7 glass. Parameters ---------- wavelength : ndarray wavelength in nm thickness thickness of ARC in nm. fraction_abraded fraction of coating loss. 1 corresponds to 100% of the coating area removed and only underlying glass is present, 0 corresponds to the coating covering the entire sample. fraction_dust fraction of module area covered by dust with reflectivity of 1. A value of 0 corresponds to no dust (clean sample), 1 to full dust coverage (reflectivity of 1). aoi angle of incidence in degrees. Returns ------- reflectance : ndarray Reflectance of sample at the values of wavelength specified. """ index_substrate = refractive_index_glass(wavelength) index_film = refractive_index_porous_silica(wavelength, porosity=porosity) glass_reflectance = single_interface_reflectance( n0=n0, n1=index_substrate, polarization='mixed', aoi=aoi) thin_film_R = thin_film_reflectance(index_film=index_film, index_substrate=index_substrate, film_thickness=thickness, aoi=aoi, wavelength=wavelength) reflectance = (1 - fraction_dust) * ( fraction_abraded * glass_reflectance + ( 1 - fraction_abraded) * thin_film_R) + fraction_dust return reflectance
def fit_arc_reflection_spectrum(wavelength, reflectance, x0=None, aoi=8, model='d', fixed=None, verbose=False, method='basinhopping', niter=20, wavelength_min=450, wavelength_max=1000): """ This function fits an ARC model to the reflection spectrum. The ARC model is described in the function arc_reflection_model. Parameters ---------- wavelength : ndarray Wavelength in nm reflectance : ndarray Fractional reflection between 0 and 1, unitless. x0 : dict Startpoint for fitting algorithm. aoi : float Angle of incidence of light model : str Different variation of the same model are available. The model is described under the function `arc_reflection_model`. The fixed values are specified in the input 'fixed'. 'TP' - thickness and poristy are fit, fraction_abraded and fraction_dust set to fixed values. 'TPA' - thickness, porosity and fraction_abraded are fit, fraction_dust is fixed. 'TPAD' - thickness, porosity, fraction_abraded and fraction_dust are fit. 'TAD' - thickness, fraction_abraded, fraction_dust are fit, porosity is a fixed value. fixed : dict Dictionary of parameters to be fixed. Default is: fixed = {'thickness': 125, 'fraction_abraded': 0, 'fraction_dust': 0, 'porosity': 0.3} verbose : bool Whether to print output at each iteration. method : str Optimization method, can be 'minimize' or 'basinhopping' niter : int Number of basinhopping steps if method == 'basinhopping' wavelength_min : float Lower bound for wavelength values used in fit. wavelength_max : float Upper bound for wavelength values used in fit. Returns ------- result : dict Dictionary of best fit values. ret Output of optimizer. """ if np.mean(reflectance) > 1: print( 'Warning: check that reflectance is a fractional value between 0 and 1.') if x0 == None: x0 = estimate_arc_reflection_model_params(wavelength, reflectance) fixed_default = {'thickness': 125, 'fraction_abraded': 0, 'fraction_dust': 0, 'porosity': 0.3} if fixed == None: fixed = fixed_default else: for p in fixed_default: if p not in fixed: fixed[p] = fixed_default[p] # print('x0: ', x0) scale = {'thickness': 0.01, 'fraction_abraded': 1, 'fraction_dust': 1000, 'porosity': 1} if model == 'TPA': x0_list = [x0['thickness'] * scale['thickness'], x0['fraction_abraded'] * scale['fraction_abraded'], x0['porosity'] * scale['porosity'] ] elif model == 'TAD': x0_list = [x0['thickness'] * scale['thickness'], x0['fraction_abraded'] * scale['fraction_abraded'], x0['fraction_dust'] * scale['fraction_dust'] ] elif model == 'TPAD': x0_list = [x0['thickness'] * scale['thickness'], x0['fraction_abraded'] * scale['fraction_abraded'], x0['fraction_dust'] * scale['fraction_dust'], x0['porosity'] * scale['porosity'], ] elif model == 'TP': x0_list = [x0['thickness'] * scale['thickness'], x0['porosity'] * scale['porosity'], ] else: raise Exception('model options are "TP", "TPA", "TPAD" or "TAD"') # Increase by a factor of 100 to improve numeric accuracy. reflectance = reflectance * 100 reflectance[reflectance < 0] = 0 reflectance[reflectance > 100] = 100 cax = np.logical_and(wavelength > wavelength_min, wavelength < wavelength_max) wavelength_calc = wavelength.copy() index_substrate = refractive_index_glass(wavelength) glass_reflectance_calc = single_interface_reflectance(n0=1.0003, n1=index_substrate, aoi=aoi, polarization='mixed', ) # # Get interpolator for correct # if not aoi == 8: # raise Exception('aoi must be 8 degrees.') thickness_min = 50 thickness_max = 200 porosity_max = 0.499 if model == 'TPA': bounds = [(thickness_min * scale['thickness'], thickness_max * scale['thickness']), (0, 1 * scale['fraction_abraded']), (0, porosity_max * scale['porosity']), ] elif model == 'TAD': bounds = [(thickness_min * scale['thickness'], thickness_max * scale['thickness']), (0, 1 * scale['fraction_abraded']), (0, 1 * scale['fraction_dust']), ] elif model == 'TPAD': bounds = [(thickness_min * scale['thickness'], thickness_max * scale['thickness']), (0, 1 * scale['fraction_abraded']), (0, 1 * scale['fraction_dust']), (0, porosity_max * scale['porosity']), ] elif model == 'TP': bounds = [(thickness_min * scale['thickness'], thickness_max * scale['thickness']), (0, porosity_max * scale['porosity']), ] def arc_model_c(wavelength, thickness, fraction_abraded, fraction_dust, porosity): index_film = refractive_index_porous_silica(wavelength=wavelength, porosity=porosity) index_substrate = refractive_index_glass(wavelength=wavelength) thin_film_R = thin_film_reflectance( index_film=index_film, index_substrate=index_substrate, film_thickness=thickness, wavelength=wavelength, aoi=aoi ) # # thin_film_R = thin_film_reflection_fast( # wavelength=wavelength, # thickness=thickness, # aoi=aoi, # porosity=porosity) # # index_film = index_porous_silica(wavelength=wavelength, # porosity=porosity) # thin_film_reflectance = thin_film_reflection( # polarization='mixed', # wavelength=wavelength, # d_list=[np.inf, thickness, np.inf], # index_film=index_film, # index_substrate=index_glass, # aoi=aoi) glass_reflectance = np.interp(wavelength, wavelength_calc, glass_reflectance_calc) reflectance = (1 - fraction_dust) * ( fraction_abraded * glass_reflectance + ( 1 - fraction_abraded) * thin_film_R) + fraction_dust return 100 * reflectance def arc_coating_error_function(x): # print('x: ',x) if model == 'TPA': thickness = x[0] / scale['thickness'] fraction_abraded = x[1] / scale['fraction_abraded'] porosity = x[2] / scale['porosity'] reflectance_model = arc_model_c(wavelength, thickness, fraction_abraded=fraction_abraded, fraction_dust=fixed[ 'fraction_dust'], porosity=porosity) elif model == 'TAD': thickness = x[0] / scale['thickness'] fraction_abraded = x[1] / scale['fraction_abraded'] fraction_dust = x[2] / scale['fraction_dust'] reflectance_model = arc_model_c(wavelength, thickness, fraction_abraded, fraction_dust, porosity=fixed['porosity']) # if verbose: # print( # 'Thickness: {:03.2f}, Fraction Abraded: {:.1%}, Fraction dust: {:.1%}'.format( # thickness, # fraction_abraded, # fraction_dust)) elif model == 'TPAD': thickness = x[0] / scale['thickness'] fraction_abraded = x[1] / scale['fraction_abraded'] fraction_dust = x[2] / scale['fraction_dust'] porosity = x[3] / scale['porosity'] reflectance_model = arc_model_c(wavelength, thickness, fraction_abraded, fraction_dust, porosity) elif model == 'TP': thickness = x[0] / scale['thickness'] porosity = x[1] / scale['porosity'] reflectance_model = arc_model_c(wavelength, thickness, fraction_abraded=fixed[ 'fraction_abraded'], fraction_dust=fixed[ 'fraction_dust'], porosity=porosity) else: raise Exception('model type unknown') residual = np.mean( np.sqrt(np.abs(reflectance_model - reflectance) ** 2)) return residual if method == 'minimize': res = minimize(arc_coating_error_function, x0=x0_list, options=dict( # maxiter=100, disp=verbose ), bounds=bounds ) elif method == 'basinhopping': def basinhopping_callback(x, f, accept): if model == 'TPA' and verbose: # print('x:', x) print( '--\nThickness: {:03.2f}, Fraction Abraded: {:.1%}, Porosity: {:.1%}'.format( x[0] / scale['thickness'], x[1] / scale['fraction_abraded'], x[2] / scale['porosity'] ) ) res = basinhopping(arc_coating_error_function, x0=x0_list, niter=niter, minimizer_kwargs={'bounds': bounds}, disp=verbose, callback=basinhopping_callback ) if model == 'TPA': result = {'thickness': res['x'][0] / scale['thickness'], 'fraction_abraded': res['x'][1] / scale['fraction_abraded'], 'fraction_dust': fixed['fraction_dust'], 'porosity': res['x'][2] / scale['porosity']} elif model == 'TAD': result = {'thickness': res['x'][0] / scale['thickness'], 'fraction_abraded': res['x'][1] / scale['fraction_abraded'], 'fraction_dust': res['x'][2] / scale['fraction_dust'], 'porosity': fixed['porosity']} elif model == 'TPAD': result = {'thickness': res['x'][0] / scale['thickness'], 'fraction_abraded': res['x'][1] / scale['fraction_abraded'], 'fraction_dust': res['x'][2] / scale['fraction_dust'], 'porosity': res['x'][3] / scale['porosity'], } elif model == 'TP': result = {'thickness': res['x'][0] / scale['thickness'], 'fraction_abraded': fixed['fraction_abraded'], 'fraction_dust': fixed['fraction_dust'], 'porosity': res['x'][1] / scale['porosity'], } return result, res
"""Example for printing a list of the refractive index of BK7 glass, useful for performing a light reference in the spectrometer software. """ import numpy as np from pvarc.materials import refractive_index_glass from pvarc import single_interface_reflectance wavelength = np.arange(190, 1125, 10) index_glass = refractive_index_glass(wavelength, type='BK7') reflectance = single_interface_reflectance(n0=1.0003, n1=index_glass, aoi=8.0, polarization='mixed') print('Glass reflectance') for k in range(len(wavelength)): print('{}\t{:.5f}'.format(wavelength[k], reflectance[k]))
model='TPA', aoi=8, wavelength_min=450, wavelength_max=1000, method='basinhopping', verbose=True) wavelength_extend = np.linspace(300, 1250, 1000) reflection_fit = arc_reflection_model(wavelength_extend, **x) # Calculate solar weighted photon reflection (SWPR) using fit swpr = solar_weighted_photon_reflectance(wavelength_extend, reflection_fit) # Calculate SWPR for glass reference index_glass = refractive_index_glass(wavelength_extend) reflection_BK7 = single_interface_reflectance(n0=1.0003, n1=index_glass, aoi=8) swpr_bk7 = solar_weighted_photon_reflectance(wavelength_extend, reflection_BK7) # Calculate power enhancement due to coating. power_enchancement = swpr_bk7 - swpr # Compare fit vs simulated value. print('--\nComparison of true values vs. best fit') for p in ['thickness', 'porosity', 'fraction_abraded']: print('{}.\t True: {:.2f}, Fit: {:.2f}, '.format(p, param_true[p], x[p])) # Plot theory. plt.plot(wavelength_extend, 100 * reflection_fit, label='Fit',
fixed={'fraction_abraded': 0}, method='minimize') print('Time for fit: {:.2f}s'.format(time() - start_time)) # Get the reflectance for the fitted model wavelength_extend = np.linspace(300, 1250, 1000) reflectance_fit = arc_reflection_model(wavelength_extend, **x) # Calculate solar weighted photon reflection (SWPR) using fit swpr = solar_weighted_photon_reflectance(wavelength_extend, reflectance_fit) # Calculate SWPR for glass reference index_substrate = refractive_index_glass(wavelength_extend) reflection_glass = single_interface_reflectance(n0=1.0003, n1=index_substrate, aoi=8, polarization='mixed') swpr_glass = solar_weighted_photon_reflectance(wavelength_extend, reflection_glass) # Calculate power enhancement due to coating. power_enchancement = swpr_glass - swpr # Plot theory. plt.plot( wavelength_extend, 100 * reflectance_fit, label='Fit', linewidth=3, color=[1, 0.5, 0, 0.5],