# -*- coding: utf-8 -*-
+"""
+Created on Tue Mar 22 11:33:59 2022
+
+@author: z5228379
+"""
+
+""" Optimizing a double-layer MgF2/Ta2O5 anti-reflection coating for "infinitely-thick"
+GaAs. Minimize reflection * AM0 spectrum (weighted reflectance).
+
+To use yabox for the DE, we need to define a class which sets up the problem and has an
+'evaluate' function within it, which will actually calculate the value we are trying to
+minimize for each set of parameters.
+
+The "if __name__ == "__main__" construction is used to avoid issues with parallel processing on Windows.
+The issues arises because the multiprocessing module uses a different process on Windows than on UNIX
+systems which will throw errors if this construction is not used.
+"""
+from typing import Sequence
+import numpy as np
+import matplotlib.pyplot as plt
+
+from solcore import material
+from solcore.optics.tmm import OptiStack, calculate_rat
+from solcore.light_source import LightSource
+
+
+from solcore.optimization import DE
+from solcore.interpolate import interp1d
+
+
+class CalcRDiff:
+def__init__(self):
+""" Make the wavelength and the materials n and k data object attributes.
+
+ The n and k data are extracted from the Solcore materials rather than using
+ the material directly because there is currently an issue with using the
+ Solcore material class in parallel computations.
+ """
+self.wl = np.linspace(300, 2000, 200)
+
+
+
+ wl_n, n, wl_k, k = np.loadtxt("C:/Users/z5228379/Downloads/ZnO.csv",
+ delimiter=",", unpack=True,encoding='utf-8-sig')
+
+ wl_n = wl_n[~np.isnan(wl_n)]
+ n = n[~np.isnan(n)]
+
+ n_wl = interp1d(wl_n, n)
+ k_wl = interp1d(wl_k, k)
+
+
+self.ZnO= [
+self.wl,
+ n_wl(self.wl),
+ k_wl(self.wl)
+ ]
+
+
+
+
+ wl_n, n, wl_k, k = np.loadtxt("C:/Users/z5228379/Downloads/ALn&k.csv",
+ delimiter=",", unpack=True,encoding='utf-8-sig')
+
+
+ n_wl = interp1d(wl_n, n)
+ k_wl = interp1d(wl_k, k)
+
+
+self.Al= [
+self.wl,
+ n_wl(self.wl),
+ k_wl(self.wl)
+ ]
+
+self.Si = [
+self.wl,
+ material("Si")().n(self.wl *1e-9),
+ material("Si")().k(self.wl *1e-9),
+ ]
+
+
+ spectr = LightSource(
+ source_type="standard",
+ version="AM1.5g",
+ x=self.wl,
+ output_units="photon_flux_per_m",
+ concentration=1,
+ ).spectrum(self.wl *1e-9)[1]
+
+
+self.spectrum = spectr /max(spectr)
+
+
+def reflectance(self, x: Sequence[float]) ->float:
+""" Create a list with the format [thickness, wavelengths, n_data, k_data] for
+ each layer.
+
+ This is one of the acceptable formats in which OptiStack can take information
+ (look at the Solcore documentation or at the OptiStack code for more info)
+ We set no_back_reflection to True because we DO NOT want to include reflection
+ at the back surface (assume GaAs is infinitely thick)
+
+ :param x: List with the thicknesses of the two layers in the ARC.
+ :return: Array with the reflection at each wavelength
+ """
+
+ Si = material("Si")()
+
+ arc = [[x[0]] +self.ZnO, [x[1]] +self.Al]
+ full_stack = OptiStack(arc, no_back_reflection=False, substrate=Si)
+return calculate_rat(full_stack, self.wl, no_back_reflection=False)["R"]
+
+def absorption(self, x: Sequence[float]) ->float:
+
+
+ Si = material("Si")()
+
+ arc = [[x[0]] +self.ZnO, [x[1]] +self.Al]
+ full_stack = OptiStack(arc, no_back_reflection=False, substrate=Si)
+return calculate_rat(full_stack, self.wl, no_back_reflection=False)["A"]
+def transmission(self, x: Sequence[float]) ->float:
+
+
+ Si = material("Si")()
+
+ arc = [[x[0]] +self.ZnO, [x[1]] +self.Al]
+
+
+ full_stack = OptiStack(arc, no_back_reflection=False, substrate=Si)
+return calculate_rat(full_stack, self.wl, no_back_reflection=False)["T"]
+def evaluate(self, x: Sequence[float]) ->float:
+""" Returns the number the DA algorithm has to minimise.
+
+ In this case, this is the weighted reflectance
+
+ :param x: List with the thicknesses of the two layers in the ARC.
+ :return: weighted reflectance
+ """
+return np.mean(self.reflectance(x) *self.spectrum)
+
+def plot(self, x: Sequence[float]) ->None:
+""" Plots the reflectance
+
+ :param x: List with the thicknesses of the two layers in the ARC.
+ :return: None
+ """
+ plt.figure()
+ plt.plot(self.wl, self.reflectance(x), label="Reflectance")
+ plt.plot(self.wl, self.absorption(x), label="Absorption")
+ plt.plot(self.wl, self.transmission(x), label="Transmission")
+
+ plt.xlabel("Wavelength (nm)")
+ plt.ylabel("R/A/T")
+ plt.legend()
+ plt.show()
+
+def plot_weighted(self, x: Sequence[float]) ->None:
+""" Plots the weighted reflectance.
+
+ :param x: List with the thicknesses of the two layers in the ARC.
+ :return: None
+ """
+ plt.figure()
+ plt.plot(self.wl, self.reflectance(x) *self.spectrum)
+ plt.xlabel("Wavelength (nm)")
+ plt.ylabel("R weighted by AM0")
+ plt.show()
+
+
+def main():
+
+
+# number of iterations for Differential Evolution
+ maxiters =70
+
+# class the DE algorithm is going to use, as defined above
+ PDE_class = CalcRDiff()
+
+# Pass the function which will be minimized to the PDE (parallel differential evolution)
+# solver. PDE calculates the results for each population in parallel to speed up the
+# overall process
+
+# =============================================================================
+# PDE_obj = DE(PDE_class.evaluate, bounds=[[0, 250], [0, 250], [0, 250], [0, 250]], maxiters=maxiters)
+# =============================================================================
+ PDE_obj = DE(PDE_class.evaluate, bounds=[[0, 100], [10,15]], maxiters=maxiters)
+# PDE_obj = DE(PDE_class.evaluate, bounds=[[0, 500]], maxiters=maxiters)
+# solve, i.e. minimize the problem
+ res = PDE_obj.solve()
+
+"""
+ PDE_obj.solve() returns 5 things:
+ - res[0] is a list of the parameters which gave the minimized value
+ - res[1] is that minimized value
+ - res[2] is the evolution of the best population (the best population from each
+ iteration
+ - res[3] is the evolution of the minimized value, i.e. the fitness over each iteration
+ - res[4] is the evolution of the mean fitness over the iterations
+ """
+ best_pop = res[0]
+print("Parameters for best result:", best_pop, res[1])
+
+ PDE_class.plot(best_pop)
+ PDE_class.plot_weighted(best_pop)
+
+if__name__=='__main__':
+ main()
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/search.json b/docs/search.json
index 609404c..1840e53 100644
--- a/docs/search.json
+++ b/docs/search.json
@@ -6,6 +6,13 @@
"section": "",
"text": "This is the website for the solcore-education GitHub, where we host readable versions of Solcore and RayFlare examples (see the Tutorials tab above). Note that this is not an introductory Python course, or a course about the fundamentals of solar cells.\nThe examples on this website are hosted in Jupyter Notebook (.ipynb) format for readability. To run the examples yourself, you can find standard .py versions on the GitHub here. We recommend using these rather than the Notebook versions.\nPackage requirements\nTo use the examples on this website, you will need to install Solcore and RayFlare (the links take you to installation instructions for each package). In the simplest case, you can install them with:\npip install solcore rayflare\nBut this will not install all functionality, as detailed in the documentation for both packages.\nThe only other dependency, which is used for plotting, is seaborn, which you can install simply with:\npip install seaborn"
},
+ {
+ "objectID": "other/ARC_optimization-SchottkyCell.html",
+ "href": "other/ARC_optimization-SchottkyCell.html",
+ "title": "Optimizating ARC for a Schottky cell",
+ "section": "",
+ "text": "# -*- coding: utf-8 -*-\n\"\"\"\nCreated on Tue Mar 22 11:33:59 2022\n\n@author: z5228379\n\"\"\"\n\n\"\"\" Optimizing a double-layer MgF2/Ta2O5 anti-reflection coating for \"infinitely-thick\"\nGaAs. Minimize reflection * AM0 spectrum (weighted reflectance).\n\nTo use yabox for the DE, we need to define a class which sets up the problem and has an\n'evaluate' function within it, which will actually calculate the value we are trying to\nminimize for each set of parameters.\n\nThe \"if __name__ == \"__main__\" construction is used to avoid issues with parallel processing on Windows.\nThe issues arises because the multiprocessing module uses a different process on Windows than on UNIX\nsystems which will throw errors if this construction is not used.\n\"\"\"\nfrom typing import Sequence\nimport numpy as np\nimport matplotlib.pyplot as plt\n\nfrom solcore import material\nfrom solcore.optics.tmm import OptiStack, calculate_rat\nfrom solcore.light_source import LightSource\n\n\nfrom solcore.optimization import DE\nfrom solcore.interpolate import interp1d\n\n\nclass CalcRDiff:\n def __init__(self):\n \"\"\" Make the wavelength and the materials n and k data object attributes.\n\n The n and k data are extracted from the Solcore materials rather than using\n the material directly because there is currently an issue with using the\n Solcore material class in parallel computations.\n \"\"\"\n self.wl = np.linspace(300, 2000, 200)\n \n\n \n wl_n, n, wl_k, k = np.loadtxt(\"C:/Users/z5228379/Downloads/ZnO.csv\", \n delimiter=\",\", unpack=True,encoding='utf-8-sig')\n \n wl_n = wl_n[~np.isnan(wl_n)]\n n = n[~np.isnan(n)]\n\n n_wl = interp1d(wl_n, n)\n k_wl = interp1d(wl_k, k)\n \n \n self.ZnO= [\n self.wl,\n n_wl(self.wl),\n k_wl(self.wl)\n ]\n \n \n\n \n wl_n, n, wl_k, k = np.loadtxt(\"C:/Users/z5228379/Downloads/ALn&k.csv\", \n delimiter=\",\", unpack=True,encoding='utf-8-sig')\n\n \n n_wl = interp1d(wl_n, n)\n k_wl = interp1d(wl_k, k)\n \n \n self.Al= [\n self.wl,\n n_wl(self.wl),\n k_wl(self.wl)\n ]\n \n self.Si = [\n self.wl,\n material(\"Si\")().n(self.wl * 1e-9),\n material(\"Si\")().k(self.wl * 1e-9),\n ]\n\n \n spectr = LightSource(\n source_type=\"standard\",\n version=\"AM1.5g\",\n x=self.wl,\n output_units=\"photon_flux_per_m\",\n concentration=1,\n ).spectrum(self.wl * 1e-9)[1]\n\n \n self.spectrum = spectr / max(spectr)\n \n\n def reflectance(self, x: Sequence[float]) -> float:\n \"\"\" Create a list with the format [thickness, wavelengths, n_data, k_data] for\n each layer.\n\n This is one of the acceptable formats in which OptiStack can take information\n (look at the Solcore documentation or at the OptiStack code for more info)\n We set no_back_reflection to True because we DO NOT want to include reflection\n at the back surface (assume GaAs is infinitely thick)\n\n :param x: List with the thicknesses of the two layers in the ARC.\n :return: Array with the reflection at each wavelength\n \"\"\"\n \n Si = material(\"Si\")()\n \n arc = [[x[0]] + self.ZnO, [x[1]] + self.Al]\n full_stack = OptiStack(arc, no_back_reflection=False, substrate=Si)\n return calculate_rat(full_stack, self.wl, no_back_reflection=False)[\"R\"]\n \n def absorption(self, x: Sequence[float]) -> float:\n \n \n Si = material(\"Si\")()\n \n arc = [[x[0]] + self.ZnO, [x[1]] + self.Al]\n full_stack = OptiStack(arc, no_back_reflection=False, substrate=Si)\n return calculate_rat(full_stack, self.wl, no_back_reflection=False)[\"A\"]\n def transmission(self, x: Sequence[float]) -> float:\n\n \n Si = material(\"Si\")()\n \n arc = [[x[0]] + self.ZnO, [x[1]] + self.Al]\n\n \n full_stack = OptiStack(arc, no_back_reflection=False, substrate=Si)\n return calculate_rat(full_stack, self.wl, no_back_reflection=False)[\"T\"]\n def evaluate(self, x: Sequence[float]) -> float:\n \"\"\" Returns the number the DA algorithm has to minimise.\n\n In this case, this is the weighted reflectance\n\n :param x: List with the thicknesses of the two layers in the ARC.\n :return: weighted reflectance\n \"\"\"\n return np.mean(self.reflectance(x) * self.spectrum)\n\n def plot(self, x: Sequence[float]) -> None:\n \"\"\" Plots the reflectance\n\n :param x: List with the thicknesses of the two layers in the ARC.\n :return: None\n \"\"\"\n plt.figure()\n plt.plot(self.wl, self.reflectance(x), label=\"Reflectance\")\n plt.plot(self.wl, self.absorption(x), label=\"Absorption\")\n plt.plot(self.wl, self.transmission(x), label=\"Transmission\")\n \n plt.xlabel(\"Wavelength (nm)\")\n plt.ylabel(\"R/A/T\")\n plt.legend()\n plt.show()\n\n def plot_weighted(self, x: Sequence[float]) -> None:\n \"\"\" Plots the weighted reflectance.\n\n :param x: List with the thicknesses of the two layers in the ARC.\n :return: None\n \"\"\"\n plt.figure()\n plt.plot(self.wl, self.reflectance(x) * self.spectrum)\n plt.xlabel(\"Wavelength (nm)\")\n plt.ylabel(\"R weighted by AM0\")\n plt.show()\n\n\ndef main():\n\n\n # number of iterations for Differential Evolution\n maxiters = 70\n\n # class the DE algorithm is going to use, as defined above\n PDE_class = CalcRDiff()\n\n # Pass the function which will be minimized to the PDE (parallel differential evolution)\n # solver. PDE calculates the results for each population in parallel to speed up the\n # overall process\n\n# =============================================================================\n# PDE_obj = DE(PDE_class.evaluate, bounds=[[0, 250], [0, 250], [0, 250], [0, 250]], maxiters=maxiters)\n# =============================================================================\n PDE_obj = DE(PDE_class.evaluate, bounds=[[0, 100], [10,15]], maxiters=maxiters)\n # PDE_obj = DE(PDE_class.evaluate, bounds=[[0, 500]], maxiters=maxiters)\n # solve, i.e. minimize the problem\n res = PDE_obj.solve()\n\n \"\"\"\n PDE_obj.solve() returns 5 things:\n - res[0] is a list of the parameters which gave the minimized value\n - res[1] is that minimized value\n - res[2] is the evolution of the best population (the best population from each \n iteration\n - res[3] is the evolution of the minimized value, i.e. the fitness over each iteration\n - res[4] is the evolution of the mean fitness over the iterations\n \"\"\"\n best_pop = res[0]\n print(\"Parameters for best result:\", best_pop, res[1])\n\n PDE_class.plot(best_pop)\n PDE_class.plot_weighted(best_pop)\n\nif __name__ == '__main__':\n main()"
+ },
{
"objectID": "solcore-workshop/notebooks/6b-arc_optimization.html",
"href": "solcore-workshop/notebooks/6b-arc_optimization.html",
@@ -158,7 +165,7 @@
"href": "solcore-workshop/notebooks/9a-GaInP_GaAs_Si_grating.html#setting-up",
"title": "Section 9a: Planar III-V on planar Si, with rear grating",
"section": "Setting up",
- "text": "Setting up\n\nfrom solcore import material, si\nfrom solcore.absorption_calculator import search_db, download_db\nimport os\nfrom solcore.structure import Layer\nfrom solcore.light_source import LightSource\nfrom rayflare.transfer_matrix_method import tmm_structure\nfrom rayflare.options import default_options\nfrom rayflare.structure import Interface, BulkLayer, Structure\nfrom rayflare.matrix_formalism import process_structure, calculate_RAT\nfrom solcore.constants import q\nimport numpy as np\nimport matplotlib.pyplot as plt\n\nAs before, we load some materials from the refractiveindex.info database. The MgF\\(_2\\) and Ta\\(_2\\)O\\(_5\\) are the same as the ARC example; the SU8 is a negative photoresist which was used in the reference paper The optical constants for silver are also loaded from a reliable literature source. Note that the exact compositions of some semiconductor alloy layers (InGaP, AlInP and AlGaAs) are not given in the paper and are thus reasonable guesses.\n\ndownload_db() # only needs to be run once\n\nMgF2_pageid = search_db(os.path.join(\"MgF2\", \"Rodriguez-de Marcos\"))[0][0];\nTa2O5_pageid = search_db(os.path.join(\"Ta2O5\", \"Rodriguez-de Marcos\"))[0][0];\nSU8_pageid = search_db(\"SU8\")[0][0];\nAg_pageid = search_db(os.path.join(\"Ag\", \"Jiang\"))[0][0];\n\nMgF2 = material(str(MgF2_pageid), nk_db=True)();\nTa2O5 = material(str(Ta2O5_pageid), nk_db=True)();\nSU8 = material(str(SU8_pageid), nk_db=True)();\nAg = material(str(Ag_pageid), nk_db=True)();\n\nwindow = material(\"AlInP\")(Al=0.52)\nGaInP = material(\"GaInP\")(In=0.5)\nAlGaAs = material(\"AlGaAs\")(Al=0.8)\nGaAs = material(\"GaAs\")()\nSi = material(\"Si\")\n\nAir = material(\"Air\")()\nAl2O3 = material(\"Al2O3P\")()\nAl = material(\"Al\")()"
+ "text": "Setting up\n\nfrom solcore import material, si\nfrom solcore.absorption_calculator import search_db, download_db\nimport os\nfrom solcore.structure import Layer\nfrom solcore.light_source import LightSource\nfrom rayflare.transfer_matrix_method import tmm_structure\nfrom rayflare.options import default_options\nfrom rayflare.structure import Interface, BulkLayer, Structure\nfrom rayflare.matrix_formalism import process_structure, calculate_RAT\nfrom solcore.constants import q\nimport numpy as np\nimport matplotlib.pyplot as plt\n\nAs before, we load some materials from the refractiveindex.info database. The MgF\\(_2\\) and Ta\\(_2\\)O\\(_5\\) are the same as the ARC example; the SU8 is a negative photoresist which was used in the reference paper The optical constants for silver are also loaded from a reliable literature source. Note that the exact compositions of some semiconductor alloy layers (InGaP, AlInP and AlGaAs) are not given in the paper and are thus reasonable guesses.\n\ndownload_db() # only needs to be run once\n\n\nMgF2_pageid = search_db(os.path.join(\"MgF2\", \"Rodriguez-de Marcos\"))[0][0];\nTa2O5_pageid = search_db(os.path.join(\"Ta2O5\", \"Rodriguez-de Marcos\"))[0][0];\nSU8_pageid = search_db(\"SU8\")[0][0];\nAg_pageid = search_db(os.path.join(\"Ag\", \"Jiang\"))[0][0];\n\nMgF2 = material(str(MgF2_pageid), nk_db=True)();\nTa2O5 = material(str(Ta2O5_pageid), nk_db=True)();\nSU8 = material(str(SU8_pageid), nk_db=True)();\nAg = material(str(Ag_pageid), nk_db=True)();\n\nwindow = material(\"AlInP\")(Al=0.52)\nGaInP = material(\"GaInP\")(In=0.5)\nAlGaAs = material(\"AlGaAs\")(Al=0.8)\nGaAs = material(\"GaAs\")()\nSi = material(\"Si\")\n\nAir = material(\"Air\")()\nAl2O3 = material(\"Al2O3P\")()\nAl = material(\"Al\")()"
},
{
"objectID": "solcore-workshop/notebooks/9a-GaInP_GaAs_Si_grating.html#defining-the-cell-layers",
@@ -179,7 +186,7 @@
"href": "solcore-workshop/notebooks/9a-GaInP_GaAs_Si_grating.html#cell-with-rear-grating",
"title": "Section 9a: Planar III-V on planar Si, with rear grating",
"section": "Cell with rear grating",
- "text": "Cell with rear grating\nNow, for the cell with a grating on the rear, we have a multi-scale problem where we must combine the calculation of absorption in a very thick (compared to the wavelengths of light) layer of Si with the effect of a wavelength-scale (1000 nm pitch) diffraction grating. For this, we will use the Angular Redistribution Matrix Method (ARMM) which was also used in Example 8.\nThe front surface of the cell (i.e. all the layers on top of Si) are planar, and can be treated using TMM. The rear surface of the cell, which has a crossed grating consisting of silver and SU8, must be treated with RCWA to account for diffraction. The thick Si layer will be the bulk coupling layer between these two interfaces.\nFirst, we set up the rear grating surface; we must define its lattice vectors, and place the Ag rectangle in the unit cell of the grating. More details on how unit cells of different shapes can be defined for the RCWA solver can be found here.\n\nx = 1000\n\nd_vectors = ((x, 0), (0, x))\narea_fill_factor = 0.4\nhw = np.sqrt(area_fill_factor) * 500\n\nback_materials = [Layer(width=si(\"250nm\"),\n material=SU8,\n geometry=[{\"type\": \"rectangle\", \"mat\": Ag, \"center\": (x / 2, x / 2),\n \"halfwidths\": (hw, hw), \"angle\": 0}],\n )]\n\nNow, we define the Si bulk layer, and the III-V layers which go in the front interface. Finally, we put everything together into the ARMM Structure, also giving the incidence and transmission materials.\n\nbulk_Si = BulkLayer(280e-6, Si(), name=\"Si_bulk\")\n\nIII_V_layers = ARC + GaInP_junction + tunnel_1 + GaAs_junction + tunnel_2\n\nfront_surf_planar = Interface(\"TMM\", layers=III_V_layers, name=\"III_V_front\",\n coherent=True)\n\nback_surf_grating = Interface(\n \"RCWA\",\n layers=back_materials,\n name=\"crossed_grating_back\",\n d_vectors=d_vectors,\n rcwa_orders=60,\n)\n\ncell_grating = Structure(\n [front_surf_planar, bulk_Si, back_surf_grating],\n incidence=Air,\n transmission=Ag,\n)\n\nBecause RCWA calculations are very slow compared to TMM, it makes sense to only carry out the RCWA calculation at wavelengths where the grating has any effect. Depending on the wavelength, all the incident light may be absorbed in the III-V layers or in its first pass through the Si, so it never reaches the grating. We check this by seeing which wavelengths have even a small amount of transmission into the silver back mirror, and only doing the new calculation at these wavelengths. At shorter wavelengths, the results previously calculated using TMM can be used.\n\nwl_rcwa = wl[tmm_result['T'] > 1e-4] # check where transmission fraction is bigger\n# than 1E-4\n\noptions.wavelengths = wl_rcwa\noptions.project_name = \"III_V_Si_cell\"\noptions.n_theta_bins = 40\noptions.c_azimuth = 0.25\n\nprocess_structure(cell_grating, options, save_location='current')\nresults_armm = calculate_RAT(cell_grating, options, save_location='current')\nRAT = results_armm[0]"
+ "text": "Cell with rear grating\nNow, for the cell with a grating on the rear, we have a multi-scale problem where we must combine the calculation of absorption in a very thick (compared to the wavelengths of light) layer of Si with the effect of a wavelength-scale (1000 nm pitch) diffraction grating. For this, we will use the Angular Redistribution Matrix Method (ARMM) which was also used in Example 8.\nThe front surface of the cell (i.e. all the layers on top of Si) are planar, and can be treated using TMM. The rear surface of the cell, which has a crossed grating consisting of silver and SU8, must be treated with RCWA to account for diffraction. The thick Si layer will be the bulk coupling layer between these two interfaces.\nFirst, we set up the rear grating surface; we must define its lattice vectors, and place the Ag rectangle in the unit cell of the grating. More details on how unit cells of different shapes can be defined for the RCWA solver can be found here.\n\nx = 1000\n\nd_vectors = ((x, 0), (0, x))\narea_fill_factor = 0.4\nhw = np.sqrt(area_fill_factor) * 500\n\nback_materials = [Layer(width=si(\"250nm\"),\n material=SU8,\n geometry=[{\"type\": \"rectangle\", \"mat\": Ag, \"center\": (x / 2, x / 2),\n \"halfwidths\": (hw, hw), \"angle\": 0}],\n )]\n\nNow, we define the Si bulk layer, and the III-V layers which go in the front interface. Finally, we put everything together into the ARMM Structure, also giving the incidence and transmission materials.\n\nbulk_Si = BulkLayer(280e-6, Si(), name=\"Si_bulk\")\n\nIII_V_layers = ARC + GaInP_junction + tunnel_1 + GaAs_junction + tunnel_2\n\nfront_surf_planar = Interface(\"TMM\", layers=III_V_layers, name=\"III_V_front\",\n coherent=True)\n\nback_surf_grating = Interface(\n \"RCWA\",\n layers=back_materials,\n name=\"crossed_grating_back\",\n d_vectors=d_vectors,\n rcwa_orders=60,\n)\n\ncell_grating = Structure(\n [front_surf_planar, bulk_Si, back_surf_grating],\n incidence=Air,\n transmission=Ag,\n)\n\nBecause RCWA calculations are very slow compared to TMM, it makes sense to only carry out the RCWA calculation at wavelengths where the grating has any effect. Depending on the wavelength, all the incident light may be absorbed in the III-V layers or in its first pass through the Si, so it never reaches the grating. We check this by seeing which wavelengths have even a small amount of transmission into the silver back mirror, and only doing the new calculation at these wavelengths. At shorter wavelengths, the results previously calculated using TMM can be used.\n\nwl_rcwa = wl[tmm_result['T'] > 1e-4] # check where transmission fraction is bigger\n# than 1E-4\n\noptions.wavelengths = wl_rcwa\noptions.project_name = \"III_V_Si_cell\"\noptions.n_theta_bins = 30\noptions.c_azimuth = 0.25\n\nprocess_structure(cell_grating, options, save_location='current')\nresults_armm = calculate_RAT(cell_grating, options, save_location='current')\nRAT = results_armm[0]"
},
{
"objectID": "solcore-workshop/notebooks/9a-GaInP_GaAs_Si_grating.html#comparison-of-planar-and-grating-cell",
@@ -196,228 +203,242 @@
"text": "Questions\n\nWhy does the grating only affect the absorption in Si at long wavelengths?\nWhat is the reason for using the angular redistribution matrix method, rather than defining an RCWA-only structure (rcwa_structure)?"
},
{
- "objectID": "solcore-workshop/workshop2023.html",
- "href": "solcore-workshop/workshop2023.html",
- "title": "Solcore Workshop 2023",
- "section": "",
- "text": "Click here to view all the slides.\nOutline:\nDay 1:\n\nIntroduction to Solcore & computer modelling (lecture)\nIntegration for limiting current, limiting voltage model\nShockley-Queisser efficiency limit and detailed balance (DB) junction model (lecture)\n\nDay 2:\n\nIntroduction to drift-diffusion junction model, depletion approximation (lecture) & spectral irradiance\nThe depletion approximation: Si cell and GaAs cell\nOptical modelling using the transfer-matrix model (TMM):\n\nTMM introduction\nOptimizing an anti-reflection coating\n\nPlanar III-V on Si tandem solar cell\n\nDay 3:\n\nOptical absorption in textured Si: ray-tracing for pyramid textures, rigorous coupled-wave analysis (RCWA) for nano-scale gratings\nIII-V/Si cells with light-trapping structures:\n\nPlanar III-V wafer-bonded to silicon with planar front using e.g. epoxy\nPlanar III-V bonded to textured silicon with diffraction grating on rear\n\nConformal perovskite on silicon tandem cells"
- },
- {
- "objectID": "solar-cell-simulation/notebooks/7a-optimization.html",
- "href": "solar-cell-simulation/notebooks/7a-optimization.html",
- "title": "Example 7a: Simple optimization",
+ "objectID": "solcore-workshop/notebooks/6a-TMM_introduction.html",
+ "href": "solcore-workshop/notebooks/6a-TMM_introduction.html",
+ "title": "Section 6a: Basic cell optics",
"section": "",
- "text": "In a few of the previous examples, we have used anti-reflection coatings. In Example 5a, we introduced a nanophotonic grating for light trapping. But how do you find out the right thickness for the anti-reflection coating layer(s), or the right dimensions for a light-trapping grating? This is where optimization comes in. Here, we will look at a very simple ‘brute-force’ optimization for a single or double-layer ARC, and a more complicated framework for running optimizations using Solcore/RayFlare and a differential evolution algorithm in Example 7b.\nimport numpy as np\nimport os\nimport matplotlib.pyplot as plt\n\nfrom solcore import material, si\nfrom solcore.solar_cell import Layer\nfrom solcore.light_source import LightSource\nfrom solcore.absorption_calculator import search_db\n\nfrom rayflare.transfer_matrix_method import tmm_structure\nfrom rayflare.options import default_options\nimport seaborn as sns"
+ "text": "In this script, we will build on the TMM model from example 1(a) and look at the effects of interference.\nimport numpy as np\nimport matplotlib.pyplot as plt\n\nfrom solcore import material, si\nfrom solcore.solar_cell import Layer\nfrom solcore.absorption_calculator import calculate_rat, OptiStack\nimport seaborn as sns"
},
{
- "objectID": "solar-cell-simulation/notebooks/7a-optimization.html#setting-up",
- "href": "solar-cell-simulation/notebooks/7a-optimization.html#setting-up",
- "title": "Example 7a: Simple optimization",
+ "objectID": "solcore-workshop/notebooks/6a-TMM_introduction.html#setting-up",
+ "href": "solcore-workshop/notebooks/6a-TMM_introduction.html#setting-up",
+ "title": "Section 6a: Basic cell optics",
"section": "Setting up",
- "text": "Setting up\n\nopts = default_options()\n\nwavelengths = np.linspace(300, 1200, 800)*1e-9\n\nAM15g = LightSource(source_type=\"standard\", version=\"AM1.5g\", output_units=\"photon_flux_per_m\")\nspectrum = AM15g.spectrum(wavelengths)[1]\nnormalised_spectrum = spectrum/np.max(spectrum)\n\nopts.wavelengths = wavelengths\nopts.coherent = False\nopts.coherency_list = ['c', 'i']\n\nSi = material(\"Si\")()\nSiN = material(\"Si3N4\")()\nAg = material(\"Ag\")()\nAir = material(\"Air\")()"
- },
- {
- "objectID": "solar-cell-simulation/notebooks/7a-optimization.html#single-layer-arc",
- "href": "solar-cell-simulation/notebooks/7a-optimization.html#single-layer-arc",
- "title": "Example 7a: Simple optimization",
- "section": "Single-layer ARC",
- "text": "Single-layer ARC\nHere, we will calculate the behaviour of a single-layer SiN anti-reflection coating on Si while changing the ARC thickness between 0 and 200 nm. We will consider two values to optimize: the mean reflectance mean_R, and the reflectance weighted by the photon flux in an AM1.5G spectrum (weighted_R). The reason for considering the second value is that it is more useful to suppress reflection at wavelengths where there are more photons which could be absorbed.\nWe will loop through the different ARC thicknesses in d_range, build the structure for each case, and then calculate the reflectance. We then save the mean reflected and weighted mean reflectance in the corresponding arrays. We also plot the reflectance for each 15th loop (this is just so the plot does not get too crowded).\n\nd_range = np.linspace(0, 200, 200)\n\nmean_R = np.empty_like(d_range)\nweighted_R = np.empty_like(d_range)\n\ncols = sns.cubehelix_palette(np.ceil(len(d_range)/15))\n\nplt.figure()\njcol = 0\n\nfor i1, d in enumerate(d_range):\n\n struct = tmm_structure([Layer(si(d, 'nm'), SiN), Layer(si('300um'), Si)], incidence=Air, transmission=Ag)\n RAT = struct.calculate(opts)\n\n if i1 % 15 == 0:\n plt.plot(wavelengths*1e9, RAT['R'], label=str(np.round(d, 0)), color=cols[jcol])\n jcol += 1\n\n mean_R[i1] = np.mean(RAT['R'])\n weighted_R[i1] = np.mean(RAT['R']*normalised_spectrum)\n\nplt.legend()\nplt.show()\n\n\n\n\nWe now find at which index mean_R and weighted_R are minimised using np.argmin, and use this to print the ARC thickness at which this occurs (rounded to 1 decimal place).\n\nprint('Minimum mean reflection occurs at d = ' + str(np.round(d_range[np.argmin(mean_R)], 1)) + ' nm')\nprint('Minimum weighted reflection occurs at d = ' + str(np.round(d_range[np.argmin(weighted_R)], 1)) + ' nm')\n\nMinimum mean reflection occurs at d = 70.4 nm\nMinimum weighted reflection occurs at d = 75.4 nm\n\n\nWe see that the values of \\(d\\) for the two different ways of optimizing are very similar, but not exactly the same, as we would expect. The minimum in both cases occurs around 70 nm. We can also plot the evolution of the mean and weighted \\(R\\) with ARC thickness \\(d\\):\n\nplt.figure()\nplt.plot(d_range, mean_R, label='Mean reflection')\nplt.plot(d_range[np.argmin(mean_R)], np.min(mean_R), 'ok')\nplt.plot(d_range, weighted_R, label='Weighted mean reflection')\nplt.plot(d_range[np.argmin(weighted_R)], np.min(weighted_R), 'ok')\nplt.xlabel('d$_{SiN}$')\nplt.ylabel('(Weighted) mean reflection 300-1200 nm')\nplt.show()\n\n\n\n\nAnd the actual reflectance with wavelength for the two different optimizations:\n\nstruct = tmm_structure([Layer(si(d_range[np.argmin(mean_R)], 'nm'), SiN), Layer(si('300um'), Si)], incidence=Air, transmission=Ag)\nRAT_1 = struct.calculate(opts)\n\nstruct = tmm_structure([Layer(si(d_range[np.argmin(weighted_R)], 'nm'), SiN), Layer(si('300um'), Si)], incidence=Air, transmission=Ag)\nRAT_2 = struct.calculate(opts)\n\nplt.figure()\nplt.plot(wavelengths*1e9, RAT_1['R'], label='Mean R minimum')\nplt.plot(wavelengths*1e9, RAT_2['R'], label='Weighted R minimum')\nplt.legend()\nplt.xlabel(\"Wavelength (nm)\")\nplt.ylabel(\"R\")\nplt.show()\n\n\n\n\nWe see that the two reflectance curves are very similar."
+ "text": "Setting up\nFirst, let’s define some materials:\n\nSi = material(\"Si\")\nSiN = material(\"Si3N4\")()\nAg = material(\"Ag\")()\n\nNote the second set of brackets (or lack thereof). The Solcore material system essentially operates in two stages; we first call the material function with the name of the material we want to use, for example Si = material(“Si”), which creates a general Python class corresponding to that material. We then call this class to specify further details, such as the temperature, doping level, or alloy composition (where relavant). This happens below when defining Si_n and Si_p; both are use the Si class defined above, and adding further details to the material. For the definitions of SiN and Ag above, we do both steps in a single line, hence the two sets of brackets.\n\nSi_n = Si(Nd=si(\"1e21cm-3\"), hole_diffusion_length=si(\"10um\"))\nSi_p = Si(Na=si(\"1e16cm-3\"), electron_diffusion_length=si(\"400um\"))\n\nTo look at the effect of interference in the Si layer at different thicknesses, we make a list of thicknesses to test (evenly spaced on a log scale from 400 nm to 300 um):\n\nSi_thicknesses = np.linspace(np.log(0.4e-6), np.log(300e-6), 8)\nSi_thicknesses = np.exp(Si_thicknesses)\n\nwavelengths = si(np.linspace(300, 1200, 400), \"nm\")\n\noptions = {\n \"recalculate_absorption\": True,\n \"optics_method\": \"TMM\",\n \"wavelength\": wavelengths\n }\n\nMake a color palette using the seaborn package to make the plots look nicer\n\ncolors = sns.color_palette('rocket', n_colors=len(Si_thicknesses))\ncolors.reverse()\n\ncreate an ARC layer:\n\nARC_layer = Layer(width=si('75nm'), material=SiN)"
},
{
- "objectID": "solar-cell-simulation/notebooks/7a-optimization.html#double-layer-arc",
- "href": "solar-cell-simulation/notebooks/7a-optimization.html#double-layer-arc",
- "title": "Example 7a: Simple optimization",
- "section": "Double-layer ARC",
- "text": "Double-layer ARC\nWe will now consider a similar situation, but for a double-layer MgF\\(_2\\)/Ta\\(_2\\)O\\(_5\\) ARC on GaAs. We search for materials in the refractiveindex.info database (see Example 2a), and use only the part of the solar spectrum relevant for absorption in GaAs (in this case, there is no benefit to reducing absorption above the GaAs bandgap around 900 nm). We will only consider the weighted mean \\(R\\) in this case.\n\npageid_MgF2 = search_db(os.path.join(\"MgF2\", \"Rodriguez-de Marcos\"))[0][0]\npageid_Ta2O5 = search_db(os.path.join(\"Ta2O5\", \"Rodriguez-de Marcos\"))[0][0]\n\nGaAs = material(\"GaAs\")()\nMgF2 = material(str(pageid_MgF2), nk_db=True)()\nTa2O5 = material(str(pageid_Ta2O5), nk_db=True)()\n\nMgF2_thickness = np.linspace(50, 100, 20)\nTa2O5_thickness = np.linspace(30, 80, 20)\n\nweighted_R_matrix = np.zeros((len(MgF2_thickness), len(Ta2O5_thickness)))\n\nwavelengths_GaAs = wavelengths[wavelengths < 900e-9]\nnormalised_spectrum_GaAs = normalised_spectrum[wavelengths < 900e-9]\n\nopts.coherent = True\nopts.wavelengths = wavelengths_GaAs\n\nWe now have two thicknesses to loop through; otherwise, the procedure is similar to the single-layer ARC example.\n\nfor i1, d_MgF2 in enumerate(MgF2_thickness):\n for j1, d_Ta2O5 in enumerate(Ta2O5_thickness):\n struct = tmm_structure([Layer(si(d_MgF2, 'nm'), MgF2), Layer(si(d_Ta2O5, 'nm'), Ta2O5),\n Layer(si('20um'), GaAs)],\n incidence=Air, transmission=Ag)\n RAT = struct.calculate(opts)\n\n weighted_R_matrix[i1, j1] = np.mean(RAT['R'] * normalised_spectrum_GaAs)\n\n# find the row and column indices of the minimum weighted R value\nri, ci = np.unravel_index(weighted_R_matrix.argmin(), weighted_R_matrix.shape)\n\nWe plot the total absorption (\\(1-R\\)) in the structure with the optimized ARC, and print the thicknesses of MgF\\(_2\\) and Ta\\(_2\\)O\\(_5\\) at which this occurs:\n\nplt.figure()\nplt.imshow(1-weighted_R_matrix, extent=[min(Ta2O5_thickness), max(Ta2O5_thickness),\n min(MgF2_thickness), max(MgF2_thickness)],\n origin='lower', aspect='equal')\nplt.plot(Ta2O5_thickness[ci], MgF2_thickness[ri], 'xk')\nplt.colorbar()\nplt.xlabel(\"Ta$_2$O$_5$ thickness (nm)\")\nplt.ylabel(\"MgF$_2$ thickness (nm)\")\nplt.show()\n\nprint(\"Minimum reflection occurs at MgF2 / Ta2O5 thicknesses of %.1f / %.1f nm \"\n % (MgF2_thickness[ri], Ta2O5_thickness[ci]))\n\n\n\n\nMinimum reflection occurs at MgF2 / Ta2O5 thicknesses of 73.7 / 53.7 nm \n\n\nFor these two examples, where we are only trying to optimize one and two parameters respectively across a relatively small range, using a method (TMM) which executes quickly, brute force searching is possible. However, as we introduce more parameters, a wider parameter space, and slower simulation methods, it may no longer be computationally tractable."
+ "objectID": "solcore-workshop/notebooks/6a-TMM_introduction.html#effect-of-si-thickness",
+ "href": "solcore-workshop/notebooks/6a-TMM_introduction.html#effect-of-si-thickness",
+ "title": "Section 6a: Basic cell optics",
+ "section": "Effect of Si thickness",
+ "text": "Effect of Si thickness\nNow we are going to loop through the different Si thicknesses generated above, and create a simple solar cell-like structure. Because we will only do an optical calculation, we don’t need to define a junction and can just make a simple stack of layers.\nWe then calculate reflection, absorption and transmission (RAT) for two different situations: 1. a fully coherent stack 2. assuming the silicon layer is incoherent. This means that light which enters the Si layer cannot interfere with itself, but light in the ARC layer can still show interference. In very thick layers (much thicker than the wavelength of light being considered) this is likely to be more physically accurate because real light does not have infinite coherence length; i.e. if you measured wavelength-dependent transmission or reflection of a Si wafer hundreds of microns thick you would not expect to see interference fringes.\nPLOT 1\n\nplt.figure()\n\nfor i1, Si_t in enumerate(Si_thicknesses):\n\n base_layer = Layer(width=Si_t, material=Si_p) # silicon layer\n solar_cell = OptiStack([ARC_layer, base_layer]) # OptiStack (optical stack) to feed into calculate_rat function\n\n # Coherent calculation:\n RAT_c = calculate_rat(solar_cell, wavelengths*1e9, no_back_reflection=False) # coherent calculation\n # For historical reasons, Solcore's default setting is to ignore reflection at the back of the cell (i.e. at the\n # interface between the final material in the stack and the substrate). Hence we need to tell the calculate_rat\n # function NOT to ignore this reflection (no_back_reflection=False).\n\n # Calculation assuming no interference in the silicon (\"incoherent\"):\n RAT_i = calculate_rat(solar_cell, wavelengths*1e9, no_back_reflection=False,\n coherent=False, coherency_list=['c', 'i']) # partially coherent: ARC is coherent, Si is not\n\n # Plot the results:\n plt.plot(wavelengths*1e9, RAT_c[\"A\"], color=colors[i1], label=str(round(Si_t*1e6, 1)), alpha=0.7)\n plt.plot(wavelengths*1e9, RAT_i[\"A\"], '--', color=colors[i1])\n\nplt.legend(title=r\"Thickness ($\\mu$m)\")\nplt.xlim(300, 1300)\nplt.ylim(0, 1.02)\nplt.ylabel(\"Absorption\")\nplt.title(\"(1) Absorption in Si with varying thickness\")\nplt.show()\n\n\n\n\nWe can see that the coherent calculations (solid lines) show clear interference fringes which depend on the Si thickness. The incoherent calculations do not have these fringes and seem to lie around the average of the interference fringes. For both sets of calculations, we see increasing absorption as the Si gets thicker, as expected."
},
{
- "objectID": "solar-cell-simulation/notebooks/1a-simple_cell.html",
- "href": "solar-cell-simulation/notebooks/1a-simple_cell.html",
- "title": "Example 1a: Simple Si solar cell",
- "section": "",
- "text": "In this first set of examples, we will look at simple planar solar cells (Si and GaAs).\nIn this script, we will look at the difference between Beer-Lambert absorption calculations, using the Fresnel equations for front-surface reflection, and using the transfer-matrix model.\nFirst, lets import some very commonly-used Python packages:\nimport numpy as np\nimport matplotlib.pyplot as plt\nNumpy is a Python library which adds supports for multi-dimensional data arrays and matrices, so it is very useful for storing and handling data. You will probably use it in every Solcore script you write. Here, it is imported under the alias ‘np’, which you will see used below. matplotlib is used for making plots, and is imported under the alias ‘plt’. Both the ‘np’ and ‘plt’ aliases are extremely commonly used in Python programming.\nNow, let’s import some things from Solcore (which will be explained as we use them):\nfrom solcore import material, si\nfrom solcore.solar_cell import SolarCell, Layer, Junction\nfrom solcore.solar_cell_solver import solar_cell_solver\nfrom solcore.interpolate import interp1d"
+ "objectID": "solcore-workshop/notebooks/6a-TMM_introduction.html#effect-of-reflective-substrate",
+ "href": "solcore-workshop/notebooks/6a-TMM_introduction.html#effect-of-reflective-substrate",
+ "title": "Section 6a: Basic cell optics",
+ "section": "Effect of reflective substrate",
+ "text": "Effect of reflective substrate\nNow we repeat the calculation, but with an Ag substrate under the Si. Previously, we did not specify the substrate and so it was assumed by Solcore to be air (\\(n\\) = 1, \\(\\kappa\\) = 0).\nPLOT 2\n\nplt.figure()\n\nfor i1, Si_t in enumerate(Si_thicknesses):\n\n base_layer = Layer(width=Si_t, material=Si_p)\n\n # As before, but now we specify the substrate to be silver:\n solar_cell = OptiStack([ARC_layer, base_layer], substrate=Ag)\n\n RAT_c = calculate_rat(solar_cell, wavelengths*1e9, no_back_reflection=False)\n RAT_i = calculate_rat(solar_cell, wavelengths*1e9, no_back_reflection=False,\n coherent=False, coherency_list=['c', 'i'])\n plt.plot(wavelengths*1e9, RAT_c[\"A\"], color=colors[i1],\n label=str(round(Si_t*1e6, 1)), alpha=0.7)\n plt.plot(wavelengths*1e9, RAT_i[\"A\"], '--', color=colors[i1])\n\nplt.legend(title=r\"Thickness ($\\mu$m)\")\nplt.xlim(300, 1300)\nplt.ylim(0, 1.02)\nplt.ylabel(\"Absorption\")\nplt.title(\"(2) Absorption in Si with varying thickness (Ag substrate)\")\nplt.show()\n\n\n\n\nWe see that the interference fringes get more prominent in the coherent calculation, due to higher reflection at the rear Si/Ag surface compared to Ag/Air. We also see a slightly boosted absorption at long wavelengths at all thicknesses, again due to improved reflection at the rear surface"
},
{
- "objectID": "solar-cell-simulation/notebooks/1a-simple_cell.html#defining-materials",
- "href": "solar-cell-simulation/notebooks/1a-simple_cell.html#defining-materials",
- "title": "Example 1a: Simple Si solar cell",
- "section": "Defining materials",
- "text": "Defining materials\nTo define our solar cell, we first want to define some materials. Then we want to organise those materials into Layers, organise those layers into a Junction, and then finally define a SolarCell with that Junction.\nFirst, let’s define a Si material. Silicon, along with many other semiconductors, dielectrics, and metals common in solar cells, is included in Solcore’s database:\n\nSi = material(\"Si\")\nGaAs = material(\"GaAs\")\n\nThis creates an instance of the Si and GaAs materials. However, to use this in a solar cell we need to do specify some more information, such as the doping level. The ‘si’ function comes in handy here to convert all quantities to based units e.g. m, m^(-3)…\n\nSi_n = Si(Nd=si(\"1e21cm-3\"), hole_diffusion_length=si(\"10um\"), relative_permittivity=11.7)\nSi_p = Si(Na=si(\"1e16cm-3\"), electron_diffusion_length=si(\"400um\"), relative_permittivity=11.7)"
+ "objectID": "solcore-workshop/notebooks/6a-TMM_introduction.html#effect-of-polarization-and-angle-of-incidence",
+ "href": "solcore-workshop/notebooks/6a-TMM_introduction.html#effect-of-polarization-and-angle-of-incidence",
+ "title": "Section 6a: Basic cell optics",
+ "section": "Effect of polarization and angle of incidence",
+ "text": "Effect of polarization and angle of incidence\nFinally, we look at the effect of incidence angle and polarization of the light hitting the cell.\nPLOT 3\n\nangles = [0, 30, 60, 70, 80, 89] # angles in degrees\n\nARC_layer = Layer(width=si('75nm'), material=SiN)\nbase_layer = Layer(width=si(\"100um\"), material=Si_p)\n\ncolors = sns.cubehelix_palette(n_colors=len(angles))\n\nplt.figure()\n\nfor i1, theta in enumerate(angles):\n\n solar_cell = OptiStack([ARC_layer, base_layer])\n\n RAT_s = calculate_rat(solar_cell, wavelengths*1e9, angle=theta,\n pol='s',\n no_back_reflection=False,\n coherent=False, coherency_list=['c', 'i'])\n RAT_p = calculate_rat(solar_cell, wavelengths*1e9, angle=theta,\n pol='p',\n no_back_reflection=False,\n coherent=False, coherency_list=['c', 'i'])\n\n plt.plot(wavelengths*1e9, RAT_s[\"A\"], color=colors[i1], label=str(round(theta)))\n plt.plot(wavelengths*1e9, RAT_p[\"A\"], '--', color=colors[i1])\n\nplt.legend(title=r\"$\\theta (^\\circ)$\")\nplt.xlim(300, 1300)\nplt.ylim(0, 1.02)\nplt.ylabel(\"Absorption\")\nplt.title(\"(3) Absorption in Si with varying angle of incidence\")\nplt.show()\n\n\n\n\nFor normal incidence (\\(\\theta = 0^\\circ\\)), s (solid lines) and p (dashed lines) polarization are equivalent. As the incidence angle increases, in general absorption is higher for p-polarized light (due to lower reflection). Usually, sunlight is modelled as unpolarized light, which computationally is usually done by averaging the results for s and p-polarized light."
},
{
- "objectID": "solar-cell-simulation/notebooks/1a-simple_cell.html#defining-layers",
- "href": "solar-cell-simulation/notebooks/1a-simple_cell.html#defining-layers",
- "title": "Example 1a: Simple Si solar cell",
- "section": "Defining layers",
- "text": "Defining layers\nNow we define the emitter and base layers we will have in the solar cell; we specify their thickness, the material they are made of and the role they play within the cell (emitter or base)\n\nemitter_layer = Layer(width=si(\"600nm\"), material=Si_n, role='emitter')\nbase_layer = Layer(width=si(\"200um\"), material=Si_p, role='base')\n\ncreate the p-n junction using the layers defined above. We set kind=“DA” to tell Solcore to use the Depletion Approximation in the calculation (we will discuss the different electrical solver options more later on):\n\nSi_junction = Junction([emitter_layer, base_layer], kind=\"DA\")"
+ "objectID": "solcore-workshop/notebooks/6a-TMM_introduction.html#conclusions",
+ "href": "solcore-workshop/notebooks/6a-TMM_introduction.html#conclusions",
+ "title": "Section 6a: Basic cell optics",
+ "section": "Conclusions",
+ "text": "Conclusions\nWe have now seen some effects of interference in layers of different thicknesses, and seen the effect of adding a highly reflective substrate. So we already have two strategies for light-trapping/improving the absorption in a solar cell: adding an anti-reflection coating (in example 1a), to reduce front-surface reflection and get more light into the cell, and adding a highly reflective layer at the back, to reduce loss through the back of the cell and keep light trapped in the cell."
},
{
- "objectID": "solar-cell-simulation/notebooks/1a-simple_cell.html#setting-user-options",
- "href": "solar-cell-simulation/notebooks/1a-simple_cell.html#setting-user-options",
- "title": "Example 1a: Simple Si solar cell",
- "section": "Setting user options",
- "text": "Setting user options\nWavelengths we want to use in the calculations; wavelengths between 300 and 1200 nm, at 200 evenly spaced intervals:\n\nwavelengths = si(np.linspace(300, 1200, 200), \"nm\")\n\nNote that here and above in defining the layers and materials we have used the “si()” function: you can use this to automatically convert quantities in other units to base SI units (e.g. nanometres to metres).\nNow we specify some options for running the calculation. Initially we want to use the Beer-Lambert absorption law to calculate the optics of the cell (“BL”). We set the wavelengths we want to use, and we set “recalculate_absorption” to True so that further down in the script when we try different optics methods, Solcore knows we want to re-calculate the optics of the cell. We specify the options in a Python format called a dictionary:\n\noptions = {\n \"recalculate_absorption\": True,\n \"optics_method\": \"BL\",\n \"wavelength\": wavelengths\n }"
+ "objectID": "solcore-workshop/notebooks/6a-TMM_introduction.html#questions",
+ "href": "solcore-workshop/notebooks/6a-TMM_introduction.html#questions",
+ "title": "Section 6a: Basic cell optics",
+ "section": "Questions",
+ "text": "Questions\n\nWhy are the interference fringes stronger when adding a silver back mirror, compared to having air behind the Si?\nWe modelled s and p-polarized light - how do we normally model unpolarized light?"
},
{
- "objectID": "solar-cell-simulation/notebooks/1a-simple_cell.html#running-cell-simulations",
- "href": "solar-cell-simulation/notebooks/1a-simple_cell.html#running-cell-simulations",
- "title": "Example 1a: Simple Si solar cell",
- "section": "Running cell simulations",
- "text": "Running cell simulations\nDefine the solar cell; in this case it is very simple and we just have a single junction:\n\nsolar_cell = SolarCell([Si_junction])\n\nNow use solar_cell_solver to calculate the QE of the cell; we can ask solar_cell_solver to calculate ‘qe’, ‘optics’ or ‘iv’.\n\nsolar_cell_solver(solar_cell, 'qe', options)\n\nSolving optics of the solar cell...\nSolving QE of the solar cell...\n\n\nPLOT 1: plotting the QE in the Si junction, as well as the fraction of light absorbed in the junction and reflected:\n\nplt.figure()\nplt.plot(wavelengths*1e9, 100*solar_cell[0].eqe(wavelengths), 'k-', label=\"EQE\")\nplt.plot(wavelengths*1e9, 100*solar_cell[0].layer_absorption, label='Absorptance (A)')\nplt.plot(wavelengths*1e9, 100*solar_cell.reflected, label='Reflectance (R)')\nplt.legend()\nplt.xlabel(\"Wavelength (nm)\")\nplt.ylabel(\"QE/Absorptance (%)\")\nplt.title(\"(1) QE of Si cell - Beer-Lambert absorption\")\nplt.show()"
+ "objectID": "solar-cell-simulation/notebooks/2a-optical_constants.html",
+ "href": "solar-cell-simulation/notebooks/2a-optical_constants.html",
+ "title": "Example 2a: Optical constant sources",
+ "section": "",
+ "text": "In the first set of scripts focusing on the Si cell, we used different optical models to calculate total absorption and absorption profiles. These absorption profiles are used by the electrical models (if using the DA or PDD model). However, we didn’t discuss what actually goes into these optical models, which are the optical constants (either the complex refractive index, \\(n + i \\kappa\\) (\\(\\kappa\\) is the extinction coefficient), or equivalently the dielectric function \\(\\epsilon_1 + i \\epsilon_2\\)). In these two examples we will discuss what these values are, how to get them, and how to model them.\nfrom solcore.absorption_calculator.nk_db import download_db, search_db, create_nk_txt\nfrom solcore.absorption_calculator import calculate_rat, OptiStack\nfrom solcore.material_system import create_new_material\nfrom solcore import material\nfrom solcore import si\nfrom solcore.structure import Layer\n\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom os import remove\n\nimport seaborn as sns"
},
{
- "objectID": "solar-cell-simulation/notebooks/1a-simple_cell.html#adding-front-surface-reflection-fresnel",
- "href": "solar-cell-simulation/notebooks/1a-simple_cell.html#adding-front-surface-reflection-fresnel",
- "title": "Example 1a: Simple Si solar cell",
- "section": "Adding front-surface reflection: Fresnel",
- "text": "Adding front-surface reflection: Fresnel\nNow, to make this calculation a bit more realistic, there are a few things we could do. We could load some measured front surface reflectance from a file, or we could calculate the reflectance. To calculate the reflectance, there are many approaches we could take; we are going to explore two of them here.\nIf we assume the silicon is infinitely thick (or at least much thicker than the wavelengths we care about) then the reflectance will approach the reflectivity of a simple air/Si interface. We can calculate what this is using the Fresnel equation for reflectivity.\n\ndef calculate_R_Fresnel(incidence_n, transmission_n, wl):\n # return a function that gives the value of R (at normal incidence) at the input wavelengths\n\n Rs = np.abs((incidence_n - transmission_n)/(incidence_n + transmission_n))**2\n\n return interp1d(wl, Rs)\n\ncomplex reflective index at our wavelengths for the transmission medium (Si). The incidence_n = 1 (air).\n\ntrns_n = Si_n.n(wavelengths) + 1j*Si_n.k(wavelengths)\nreflectivity_fn = calculate_R_Fresnel(1, trns_n, wavelengths)\n\nwe define the solar cell again, with the same layers but now supplying the function for the externally-calculated reflectivity, and calculate the optics (reflection, absorption, transmission):\n\nsolar_cell_fresnel = SolarCell([Si_junction], reflectivity=reflectivity_fn)\n\nsolar_cell_solver(solar_cell_fresnel, 'optics', options)\n\nSolving optics of the solar cell..."
+ "objectID": "solar-cell-simulation/notebooks/2a-optical_constants.html#adding-custom-materials",
+ "href": "solar-cell-simulation/notebooks/2a-optical_constants.html#adding-custom-materials",
+ "title": "Example 2a: Optical constant sources",
+ "section": "Adding custom materials",
+ "text": "Adding custom materials\nIf we want to use a material which is not in Solcore’s database, or perhaps we want to use measured data rather than data from a literature source, we can add a material to the database. We need to have n and k data, and (optionally) a parameter file in the correct format - see examples of parameter files in e.g. material_data/Adachi/binaries.txt inside Solcore’s source files. These parameter files contain things like the bandgap, lattice constant, effective carrier masses, etc.\nHere, we create a new material, silicon-germanium-tin, from input files. Here, the parameters in SiGeSn_params.txt have been copied directly from Ge. The last argument, with the parameters file, is optional; if you exclude it the material will be added with just the n and k values and no further information specified (useful if you just want to do optical calculations).\n\ncreate_new_material('SiGeSn', 'data/SiGeSn_n.txt', 'data/SiGeSn_k.txt', 'data/SiGeSn_params.txt')\n\nWhen adding custom materials - or getting the refractive index database - the information will be stored by default in your home directory. You can change thethe SOLCORE_USER_DATA environmental variable in the config file to your prefered location or, by default, it will be in your home directory, in a (hidden) directory called .solcore.\nWe can now create an instance of it like any Solcore material:\n\nwl = si(np.arange(300, 1700, 5), 'nm')\n\nSiGeSn = material('SiGeSn')()\nGe = material('Ge')()\n\nPLOT 1: Comparing the optical constants of SiGeSn and Ge.\n\nplt.figure()\nplt.plot(wl*1e9, SiGeSn.n(wl), 'r-', label='SiGeSn n')\nplt.plot(wl*1e9, SiGeSn.k(wl), 'k-', label=r'SiGeSn $\\kappa$')\n\nplt.plot(wl*1e9, Ge.n(wl), 'r--', label='Ge n')\nplt.plot(wl*1e9, Ge.k(wl), 'k--', label=r'Ge $\\kappa$')\n\nplt.xlabel('Wavelength (nm)')\nplt.ylabel(r'SiGeSn n / $\\kappa$')\nplt.legend()\nplt.title(\"(1) Optical constants of Ge and SiGeSn\")\nplt.show()"
},
{
- "objectID": "solar-cell-simulation/notebooks/1a-simple_cell.html#adding-front-surface-reflection-tmm",
- "href": "solar-cell-simulation/notebooks/1a-simple_cell.html#adding-front-surface-reflection-tmm",
- "title": "Example 1a: Simple Si solar cell",
- "section": "Adding front surface reflection: TMM",
- "text": "Adding front surface reflection: TMM\nFinally, we do the same again but now instead of supplying the external reflectivity we ask set the optics_method to “TMM” (Transfer Matrix Method), to correctly calculate reflection at the front surface:\n\nSi_junction = Junction([emitter_layer, base_layer], kind=\"DA\")\n\nsolar_cell_TMM = SolarCell([Si_junction])\n\nSet some more options:\n\noptions[\"optics_method\"] = \"TMM\"\nvoltages = np.linspace(0, 1.1, 100)\noptions[\"light_iv\"] = True\noptions[\"mpp\"] = True\noptions[\"voltages\"] = voltages\n\nwe calculate the QE and the IV (we set the light_iv option to True; if we don’t do this, Solcore just calculates the dark IV). We also ask Solcore to find the maximum power point (mpp) so we can get the efficiency.\n\nsolar_cell_solver(solar_cell_TMM, 'iv', options)\nsolar_cell_solver(solar_cell_TMM, 'qe', options)\n\nSolving optics of the solar cell...\nTreating layer(s) 1 incoherently\nCalculating RAT...\nCalculating absorption profile...\nSolving IV of the junctions...\nSolving IV of the tunnel junctions...\nSolving IV of the total solar cell...\nSolving optics of the solar cell...\nTreating layer(s) 1 incoherently\nCalculating RAT...\nCalculating absorption profile...\nSolving QE of the solar cell...\n\n\nPLOT 2: here we plot the reflection, transmission, and absorption calculated with the Fresnel equation defined above, and with the TMM solver in Solcore, showing that for this simple situation (no anti-reflection coating, thick Si junction) they are exactly equivalent.\n\nplt.figure()\nplt.plot(wavelengths*1e9, 100*solar_cell_TMM.reflected, color='firebrick', label = \"R (TMM)\")\nplt.plot(wavelengths*1e9, 100*solar_cell_fresnel.reflected, '--', color='orangered', label = \"R (Fresnel)\")\nplt.plot(wavelengths*1e9, 100*solar_cell_TMM.absorbed, color='dimgrey', label = \"A (TMM)\")\nplt.plot(wavelengths*1e9, 100*solar_cell_fresnel.absorbed, '--', color='lightgrey', label = \"A (Fresnel)\")\nplt.plot(wavelengths*1e9, 100*solar_cell_TMM.transmitted, color='blue', label = \"T (TMM)\")\nplt.plot(wavelengths*1e9, 100*solar_cell_fresnel.transmitted, '--', color='dodgerblue', label = \"T (Fresnel)\")\nplt.ylim(0, 100)\nplt.legend()\nplt.title(\"(2) Optics of Si cell - Fresnel/TMM\")\nplt.show()\n\n\n\n\nPLOT 3: As above for the TMM calculation, plotting the EQE as well, which will be slightly lower than the absorption because not all the carriers are collected. Comparing to plot (1), we can see we now have lower absorption due to the inclusion of front surface reflection.\n\nplt.figure()\nplt.plot(wavelengths*1e9, 100*solar_cell_TMM[0].eqe(wavelengths), 'k-', label=\"EQE\")\nplt.plot(wavelengths*1e9, 100*solar_cell_TMM[0].layer_absorption, label='A')\nplt.plot(wavelengths*1e9, 100*solar_cell_TMM.reflected, label=\"R\")\nplt.plot(wavelengths*1e9, 100*solar_cell_TMM.transmitted, label=\"T\")\nplt.title(\"(3) QE of Si cell (no ARC) - TMM\")\nplt.legend()\nplt.xlabel(\"Wavelength (nm)\")\nplt.ylabel(\"QE/Absorptance (%)\")\nplt.ylim(0, 100)\nplt.show()"
+ "objectID": "solar-cell-simulation/notebooks/2a-optical_constants.html#using-the-refractiveindex.info-database",
+ "href": "solar-cell-simulation/notebooks/2a-optical_constants.html#using-the-refractiveindex.info-database",
+ "title": "Example 2a: Optical constant sources",
+ "section": "Using the refractiveindex.info database",
+ "text": "Using the refractiveindex.info database\nSolcore can also directly interface with the database from www.refractiveindex.info, which contains around 3000 sets of \\(n\\)/\\(\\kappa\\) data for a large number of different materials. Before the first use, it is necessary to download the database. This only needs to be done once, so you can comment this line out after it’s done:\n\ndownload_db()\n\nNow we can search the database to select an appropriate entry. Search by element/chemical formula, or by the name of the author who published the data. In this case, we look for silver.\n\nsearch_db('Ag', exact=True); # semicolon suppresses additional output in Jupyter Notebook. Do not need to use.\n\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\n17 results found.\npageid shelf book page filepath hasrefractive hasextinction rangeMin rangeMax points\n0 main Ag Johnson main/Ag/Johnson.yml 1 1 0.1879 1.937 49\n1 main Ag Choi main/Ag/Choi.yml 1 1 1.231 6.988 84\n2 main Ag Jiang main/Ag/Jiang.yml 1 1 0.3 2.0 1701\n3 main Ag Yang main/Ag/Yang.yml 1 1 0.27 24.92 525\n4 main Ag McPeak main/Ag/McPeak.yml 1 1 0.3 1.7 141\n5 main Ag Babar main/Ag/Babar.yml 1 1 0.2066 12.4 69\n6 main Ag Wu main/Ag/Wu.yml 1 1 0.287493 0.999308 450\n7 main Ag Werner main/Ag/Werner.yml 1 1 0.017586 2.479684 150\n8 main Ag Stahrenberg main/Ag/Stahrenberg.yml 1 1 0.12782 0.49594 361\n9 main Ag Windt main/Ag/Windt.yml 1 1 0.00236 0.12157 36\n10 main Ag Hagemann main/Ag/Hagemann.yml 1 1 2.48e-06 248.0 148\n11 main Ag Ciesielski main/Ag/Ciesielski.yml 1 1 0.19077 20.912 333\n12 main Ag Ciesielski-Ge main/Ag/Ciesielski-Ge.yml 1 1 0.19077 20.912 333\n13 main Ag Ciesielski-Ni main/Ag/Ciesielski-Ni.yml 1 1 0.19077 15.811 332\n14 main Ag Rakic-BB main/Ag/Rakic-BB.yml 1 1 0.24797 12.398 200\n15 main Ag Rakic-LD main/Ag/Rakic-LD.yml 1 1 0.24797 12.398 200\n16 main Ag Werner-DFT main/Ag/Werner-DFT.yml 1 1 0.017586 2.479684 150\n\n\nThis prints out, line by line, matching entries. This shows us entries with “pageid”s 0 to 16 correspond to silver.\nLet’s compare the optical behaviour of some of those sources:\n\npageid = 0, Johnson\npageid = 2, Jiang\npageid = 4, McPeak\npageid = 10, Hagemann\npageid = 14, Rakic (BB)\n\n(The pageid values listed here are for the 2021-07-18 version of the refractiveindex.info database; this can change with different versions of the database)\nNow, we create instances of materials with the optical constants from the database. The name (when using Solcore’s built-in materials, this would just be the name of the material or alloy, like ‘GaAs’) is the pageid, AS A STRING, while the flag nk_db must be set to True to tell Solcore to look in the previously downloaded database from refractiveindex.info\n\nAg_Joh = material(name='0', nk_db=True)()\nAg_Jia = material(name='2', nk_db=True)()\nAg_McP = material(name='4', nk_db=True)()\nAg_Hag = material(name='10', nk_db=True)()\nAg_Rak = material(name='14', nk_db=True)()\nAg_Sol = material(name='Ag')() # Solcore built-in (from SOPRA)\n\nNow we plot the \\(n\\) and \\(\\kappa\\) data. Note that not all the data covers the full wavelength range, so the \\(n\\)/\\(\\kappa\\) value gets extrapolated from the last point in the dataset to cover any missing values.\nPLOT 2: \\(n\\) and \\(\\kappa\\) values for Ag from different literature sources\n\nnames = ['Johnson', 'Jiang', 'McPeak', 'Hagemann', 'Rakic', 'Solcore built-in']\n\nwl = si(np.arange(250, 900, 5), 'nm')\n\nplt.figure(figsize=(8,4))\n\nplt.subplot(121)\n# We can plot all the n values in one line:\nplt.plot(wl*1e9, np.array([Ag_Joh.n(wl), Ag_Jia.n(wl), Ag_McP.n(wl),\n Ag_Hag.n(wl), Ag_Rak.n(wl), Ag_Sol.n(wl)]).T);\nplt.legend(labels=names)\nplt.xlabel(\"Wavelength (nm)\")\nplt.title(\"(2) $n$ and $\\kappa$ values for Ag from different literature sources\")\nplt.ylabel(\"n\")\n\nplt.subplot(122)\nplt.plot(wl*1e9, np.array([Ag_Joh.k(wl), Ag_Jia.k(wl), Ag_McP.k(wl),\n Ag_Hag.k(wl), Ag_Rak.k(wl), Ag_Sol.k(wl)]).T)\nplt.legend(labels=names)\nplt.xlabel(\"Wavelength (nm)\")\nplt.ylabel(\"$\\kappa$\")\nplt.show()\n\nCompare performance as a back mirror on a GaAs ‘cell’; we make a solar cell-like structure with a very thin GaAs absorber (50 nm) and a silver back mirror.\nPLOT 3: compare absorption in GaAs and Ag for a solar cell-like structure, using Ag data from different sources\nSolid line: absorption in GaAs Dashed line: absorption in Ag\n\nGaAs = material('GaAs')()\n\ncolors = sns.color_palette('husl', n_colors=len(names))\n\nplt.figure()\nfor c, Ag_mat in enumerate([Ag_Joh, Ag_Jia, Ag_McP, Ag_Hag, Ag_Rak, Ag_Sol]):\n my_solar_cell = OptiStack([Layer(width=si('50nm'), material = GaAs)], substrate=Ag_mat)\n RAT = calculate_rat(my_solar_cell, wl*1e9, no_back_reflection=False)\n GaAs_abs = RAT[\"A_per_layer\"][1]\n Ag_abs = RAT[\"T\"]\n plt.plot(wl*1e9, GaAs_abs, color=colors[c], linestyle='-', label=names[c])\n plt.plot(wl*1e9, Ag_abs, color=colors[c], linestyle='--')\n\nplt.legend()\nplt.xlabel(\"Wavelength (nm)\")\nplt.ylabel(\"Absorbed\")\nplt.title(\"(3) Absorption in GaAs depending on silver optical constants\")\nplt.show()"
},
{
- "objectID": "solar-cell-simulation/notebooks/1a-simple_cell.html#adding-an-arc",
- "href": "solar-cell-simulation/notebooks/1a-simple_cell.html#adding-an-arc",
- "title": "Example 1a: Simple Si solar cell",
- "section": "Adding an ARC",
- "text": "Adding an ARC\nNow we will try adding a simple anti-reflection coating (ARC), a single layer of silicon nitride (Si3N4):\n\nSiN = material(\"Si3N4\")()\n\nSi_junction = Junction([emitter_layer, base_layer], kind=\"DA\")\n\nsolar_cell_TMM_ARC = SolarCell([Layer(width=si(75, \"nm\"), material=SiN), Si_junction])\n\nsolar_cell_solver(solar_cell_TMM_ARC, 'qe', options)\nsolar_cell_solver(solar_cell_TMM_ARC, 'iv', options)\n\nSolving optics of the solar cell...\nTreating layer(s) 2 incoherently\nCalculating RAT...\nCalculating absorption profile...\nSolving QE of the solar cell...\nSolving optics of the solar cell...\nTreating layer(s) 2 incoherently\nCalculating RAT...\nCalculating absorption profile...\nSolving IV of the junctions...\nSolving IV of the tunnel junctions...\nSolving IV of the total solar cell...\n\n\nPLOT 4: Absorption, EQE, reflection and transmission for the cell with a simple one-layer ARC. We see the reflection is significantly reduced from the previous plot leading to higher absorption/EQE.\n\nplt.figure()\nplt.plot(wavelengths*1e9, 100*solar_cell_TMM_ARC[1].eqe(wavelengths), 'k-', label=\"EQE\")\nplt.plot(wavelengths*1e9, 100*solar_cell_TMM_ARC[1].layer_absorption, label='A')\nplt.plot(wavelengths*1e9, 100*solar_cell_TMM_ARC.reflected, label=\"R\")\nplt.plot(wavelengths*1e9, 100*solar_cell_TMM_ARC.transmitted, label=\"T\")\nplt.legend()\nplt.title(\"(4) QE of Si cell (ARC) - TMM\")\nplt.xlabel(\"Wavelength (nm)\")\nplt.ylabel(\"QE/Absorptance (%)\")\nplt.ylim(0, 100)\nplt.show()\n\n\n\n\nPLOT 5: Compare the IV curves of the cells with and without an ARC. The efficiency is also shown on the plot. Note that because we didn’t specify a light source, Solcore will assume we want to use AM1.5G; in later examples we will set the light source used for IV simulations explicitly.\n\nplt.figure()\nplt.plot(voltages, -solar_cell_TMM[0].iv(voltages)/10, label=\"No ARC\")\nplt.plot(voltages, -solar_cell_TMM_ARC[1].iv(voltages)/10, label=\"75 nm SiN\")\nplt.text(0.5, solar_cell_TMM.iv[\"Isc\"]/10, str(round(solar_cell_TMM.iv[\"Eta\"]*100, 1)) + ' %')\nplt.text(0.5, solar_cell_TMM_ARC.iv[\"Isc\"]/10, str(round(solar_cell_TMM_ARC.iv[\"Eta\"]*100, 1)) + ' %')\nplt.ylim(0, 38)\nplt.xlim(0, 0.8)\nplt.legend()\nplt.xlabel(\"V (V)\")\nplt.ylabel(r\"J (mA/cm$^2$)\")\nplt.title(\"(5) IV curve of Si cell with and without ARC\")\nplt.show()"
+ "objectID": "solar-cell-simulation/notebooks/2a-optical_constants.html#adding-refractiveindex.info-materials-to-solcores-database",
+ "href": "solar-cell-simulation/notebooks/2a-optical_constants.html#adding-refractiveindex.info-materials-to-solcores-database",
+ "title": "Example 2a: Optical constant sources",
+ "section": "Adding refractiveindex.info materials to Solcore’s database",
+ "text": "Adding refractiveindex.info materials to Solcore’s database\nFinally, we can combine the two methods above and add a material from the refractiveindex.info database to Solcore’s database.\nThe search_db function will print the search results, but it also creates a list of lists with details of all the search results. results[0] contains the first entry; results[0][0] is the ‘pageid’ of the first search result.\nThe function create_nk_txt creates files containing the optical constant data in the format required by Solcore. These are saved in the current working directory.\n\nresults = search_db('Diamond')\n\ncreate_nk_txt(pageid=results[0][0], file='C_Diamond')\n\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\n1 results found.\npageid shelf book page filepath hasrefractive hasextinction rangeMin rangeMax points\n2897 3d crystals diamond main/C/Phillip.yml 1 1 0.035424054 10.0 176\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\nMaterial main/C/Phillip.yml loaded.\nWrote C_Diamond_n.txt\nWrote C_Diamond_k.txt\n\n\nWe now use these files to create a new material in the Solcore database:\n\ncreate_new_material(mat_name='Diamond', n_source='C_Diamond_n.txt', k_source='C_Diamond_k.txt')\n\nMaterial created with optical constants n and k only.\n\n\nWe can now delete the files with the Diamond data, since they have been copied into the user-defined materials directory:\n\nremove(\"C_diamond_n.txt\")\nremove(\"C_diamond_k.txt\")\n\nNow we can use this material as we would any material from Solcore’s database:\nPLOT 4: Optical constants of diamond\n\nDiamond = material('Diamond')()\n\nplt.figure()\nplt.plot(si(np.arange(100, 800, 5), 'nm') * 1e9, Diamond.n(si(np.arange(100, 800, 5), 'nm')))\nplt.plot(si(np.arange(100, 800, 5), 'nm') * 1e9, Diamond.k(si(np.arange(100, 800, 5), 'nm')))\nplt.title(\"(4) Optical constants for diamond\")\nplt.show()"
},
{
- "objectID": "solar-cell-simulation/notebooks/1a-simple_cell.html#conclusions",
- "href": "solar-cell-simulation/notebooks/1a-simple_cell.html#conclusions",
- "title": "Example 1a: Simple Si solar cell",
+ "objectID": "solar-cell-simulation/notebooks/2a-optical_constants.html#conclusions",
+ "href": "solar-cell-simulation/notebooks/2a-optical_constants.html#conclusions",
+ "title": "Example 2a: Optical constant sources",
"section": "Conclusions",
- "text": "Conclusions\nWe see that the cell with an ARC has a significantly higher Jsc, and a slightly higher Voc, than the bare Si cell. In reality, most Si cells have a textured surface rather than a planar surface with an ARC; this will be discussed later in the course.\nOverall, some things we can take away from the examples in this script: - The Beer-Lambert law is a very simple way to calculate absorption in a cell, but won’t take into account important effects such as front-surface reflection or the effects of anti-reflection coatings - Using the transfer-matrix method (TMM) we can account for front surface reflection and interference effects which make e.g. ARCs effective. In the simple situation of a thick cell without any front surface layers, it is equivalent to simply calculation the reflection with the Fresnel equations and assuming Beer-Lambert absorption in the cell. - Adding a simple, one-layer ARC can significantly reduce front-surface reflection for a single-junction cell, leading to improved short-circuit current."
+ "text": "Conclusions\nSo, we have at least 4 different ways of getting optical constants:\n\nFrom Solcore’s database\nBy adding our own material data to Solcore’s database\nBy using the refractiveindex.info database directly\nSimilarly, we can add materials from the refractiveindex.info database to Solcore’s database\n\nIf we add materials to the database, we can also choose to add non-optical parameters."
},
{
- "objectID": "solar-cell-simulation/notebooks/7b-optimization.html",
- "href": "solar-cell-simulation/notebooks/7b-optimization.html",
- "title": "Example 7b: More advanced optimization",
+ "objectID": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html",
+ "href": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html",
+ "title": "Example 5a: Ultra-thin GaAs cell with diffraction grating",
"section": "",
- "text": "This example looks at optimizing a four-junction Ga\\(_{0.5}\\)In\\(_{0.5}\\)P/GaAs/SiGeSn/Ge cell, using a differential evolution (DE) algorithm.\nFirst, using a purely optical TMM simulation to calculate the photogenerated current in each sub-cell, we get an estimate of the overall thickness of each material we will need to achieve current-matching. The thing to optimize is then the current of the current-limiting cell in the structure; in other words we want to maximize the lowest sub-cell current, to achieve current-matching with the highest possible current. Since the differential evolution algorithm as implemented does a minimization, we are actually minimizing the negative of this value.\nOnce we have good initial values for the total layer thicknesses, we use full electrical simulation to determine the n and p type layer thicknesses to calculate a maximum possible efficiency for the 4J device.\nTo use yabox (used by Solcore’s the optimization module for the DE) we need to define a class which sets up the problem and has an ‘evaluate’ function, which will actually calculate the value we are trying to minimize for a set of parameters.\nNote: There is an issue in some versions of PyCharm with this example due to the parallel execution. To avoid this, make sure you “Run” the example as opposed to using “Run in Python Console”.\nimport numpy as np\nimport os\n\nfrom solcore import material, si\n\nimport matplotlib.pyplot as plt\n\nfrom solcore.optics.tmm import OptiStack\nfrom solcore.optics.tmm import calculate_rat\n\nfrom solcore.optimization import PDE, DE\nfrom solcore.light_source import LightSource\n\nfrom solcore.solar_cell import SolarCell\nfrom solcore.structure import Junction, Layer\nfrom solcore.solar_cell_solver import solar_cell_solver\nfrom solcore.constants import q, kb\nfrom solcore.absorption_calculator import search_db\nFirst add SiGeSn optical constants to the database:\nfrom solcore.material_system import create_new_material\n\ncreate_new_material(\"SiGeSn\", os.path.join(\"data\", \"SiGeSn_n.txt\"),\n os.path.join(\"data\", \"SiGeSn_k.txt\"), os.path.join(\"data\", \"SiGeSn_params.txt\"))\n# Note: comment out these lines after the material has been added to avoid being asked\n# each time if you want to overwrite it.\nn_iters_optics = 50\nn_iters_device = 20"
+ "text": "In previous examples, we have considered a few different methods used to improve absorption in solar cells: anti-reflection coatings, to decrease front-surface reflection, metallic rear mirrors to reduce transmission and increase the path length of light in the cell, and textured surfaces (with pyramids) which are used on Si cells to reduce reflection and increase the path length of light in the cell. Another method which can be used for light-trapping is the inclusion of periodic structures such as diffraction gratings or photonic crystals; here, we will consider an ultra-thin (80 nm) GaAs cell with a diffraction grating.\nThis example is based on the simulations done for this paper.\nNote: This example requires that you have a working S4 installation.\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport os\n\nfrom solcore import si, material\nfrom solcore.structure import Layer\nfrom solcore.light_source import LightSource\nfrom solcore.solar_cell import SolarCell\nfrom solcore.constants import q\nfrom solcore.absorption_calculator import search_db\n\nfrom rayflare.rigorous_coupled_wave_analysis.rcwa import rcwa_structure\nfrom rayflare.transfer_matrix_method.tmm import tmm_structure\nfrom rayflare.options import default_options"
},
{
- "objectID": "solar-cell-simulation/notebooks/7b-optimization.html#optical-simulation",
- "href": "solar-cell-simulation/notebooks/7b-optimization.html#optical-simulation",
- "title": "Example 7b: More advanced optimization",
- "section": "Optical simulation",
- "text": "Optical simulation\nThis example has a more complicated structure than the previous examples, and is based around the use of Python classes. For both steps of the optimization outlined above, we define a class which contains methods which generate all the information to run the simulations, and an evaluate method which actually returns the quantity to be optimized.\nThe methods defined in the calc_min_Jsc class below, which sets up the optical part of the optimization, are:\n\n__init__: This always has to be defined for a class, as it initializes a member of the class when we call the class as calc_min_Jsc(). In this case, it sets up variables we need in the simulation, such as the wavelength, light source, and optical constants of the materials.\ncalculate: This actually runs the core of the calculation by calling calculate_rat from Solcore to run a TMM calculation; the argument of the function is a list of the layer thicknesses. It returns the absorption in each layer of the cell, the transmission, and reflection.\nevaluate: This function will be used to evaluate the figure of merit for the optimization. It calls calculate and then calculates the maximum possible current in each sub-cell using the photon flux. It then finds the limiting (minimum) \\(J_{sc}\\) out of the four sub-cells, and returns the negative of this value. The way the DE algorithm is implemented means it will always try to minimize what the evaluate function returns, so although we want to maximize the limiting \\(J_{sc}\\), we must implement this as minimizing its negative.\nplot: Takes the results from calculate and plots them to visualize our results.\n\n\nclass calc_min_Jsc():\n\n def __init__(self):\n # Initialize an instance of the class; set some information which will be used in each iteration of the calculation:\n # materials, wavelengths, the light source\n\n wl = np.linspace(300, 1900, 800)\n\n # Materials\n SiGeSn = material('SiGeSn')()\n\n GaAs = material('GaAs')()\n InGaP = material('GaInP')(In=0.5)\n Ge = material('Ge')()\n\n Ta2O5_index = search_db(os.path.join(\"Ta2O5\", \"Rodriguez\"))[0][0]\n\n # We make these attributes of 'self' so they can be accessed by the class object\n # We are also creating lists of wavelengths and corresponding n and k data from\n # the Solcore materials - the reason for this is that there is currently an issue with using the Solcore\n # material class in parallel computations. Thus the information for the n and k data is saved here as a list\n # rather than a material object (see the documentation of OptiStack for the different acceptable formats\n # to pass optical constants for an OptiStack)\n\n self.wl = wl\n self.SiGeSn = [self.wl, SiGeSn.n(self.wl*1e-9), SiGeSn.k(self.wl*1e-9)]\n self.Ge = [self.wl, Ge.n(self.wl*1e-9), Ge.k(self.wl*1e-9)]\n\n self.InGaP = [self.wl, InGaP.n(self.wl*1e-9), InGaP.k(self.wl*1e-9)]\n self.GaAs = [self.wl, GaAs.n(self.wl*1e-9), GaAs.k(self.wl*1e-9)]\n self.MgF2 = [self.wl, material('MgF2')().n(self.wl*1e-9), material('MgF2')().k(self.wl*1e-9)]\n\n self.Ta2O5 = [self.wl, material(str(Ta2O5_index),\n nk_db=True)().n(self.wl*1e-9), material(str(Ta2O5_index),\n nk_db=True)().k(self.wl*1e-9)]\n\n # Assuming an AM1.5G spectrum\n self.spectr = LightSource(source_type='standard', version='AM1.5g', x=self.wl,\n output_units='photon_flux_per_nm', concentration=1).spectrum(self.wl)[1]\n\n def calculate(self, x):\n # x[0] = MgF2 thickness (anti-reflection coating)\n # x[1] = Ta2O5 thickness (anti-reflection coating)\n # x[2] = InGaP (top junction) thickness\n # x[3] = GaAs (second junction) thickness\n # x[4] = SiGeSn (third junction) thickness\n\n # Keep the thickness of the bottom cell constant; from a purely optical point of view, this should be infinitely thick,\n # so there is no point in optimizing the thickness\n\n SC = [[x[0]] + self.MgF2, [x[1]] + self.Ta2O5, [x[2]] + self.InGaP, [x[3]] + self.GaAs, [x[4]] + self.SiGeSn,\n [300e3] + self.Ge]\n\n # create the OptiStack\n full_stack = OptiStack(SC, no_back_reflection=False)\n\n # calculate reflection, transmission, and absorption in each layer. We are specifying that the last layer,\n # a very thick Ge substrate, should be treated incoherently, otherwise we would see very narrow, unphysical oscillations\n # in the R/A/T spectra.\n\n c_list = ['c']*len(SC)\n c_list[-1] = \"i\"\n\n RAT = calculate_rat(full_stack, self.wl, no_back_reflection=False, coherent=False,\n coherency_list=c_list)\n\n # extract absorption per layer\n A_InGaP = RAT['A_per_layer'][3]\n A_GaAs = RAT['A_per_layer'][4]\n A_SiGeSn = RAT['A_per_layer'][5]\n A_Ge = RAT['A_per_layer'][6]\n\n return A_InGaP, A_GaAs, A_SiGeSn, A_Ge, RAT['T'], RAT['R']\n\n def evaluate(self, x):\n\n A_InGaP, A_GaAs, A_SiGeSn, A_Ge, _, _ = self.calculate(x)\n\n # Calculate photo-generated currents using the AM1.5 G spectrum for each layer -- this is the current with 100%\n # internal quantum efficiency (i.e. every absorbed photon generates an electron-hole pair which is collected).\n Jsc_InGaP = 0.1 * q * np.trapz(A_InGaP * self.spectr, self.wl)\n Jsc_GaAs = 0.1 * q * np.trapz(A_GaAs * self.spectr, self.wl)\n Jsc_SiGeSn = 0.1 * q * np.trapz(A_SiGeSn * self.spectr, self.wl)\n Jsc_Ge = 0.1 * q * np.trapz(A_Ge * self.spectr, self.wl)\n\n # Find the limiting current by checking which junction has the lowest current. Then take the negative since\n # we need to minimize (not maximize)\n limiting_Jsc = -min([Jsc_InGaP, Jsc_GaAs, Jsc_SiGeSn, Jsc_Ge])\n\n return limiting_Jsc\n\n def plot(self, x):\n\n A_InGaP, A_GaAs, A_SiGeSn, A_Ge, T, R = self.calculate(x)\n\n plt.figure()\n plt.plot(self.wl, A_InGaP, label='InGaP')\n plt.plot(self.wl, A_GaAs, label='A_GaAs')\n plt.plot(self.wl, A_SiGeSn, label='SiGeSn')\n plt.plot(self.wl, A_Ge, label = 'Ge')\n plt.plot(self.wl, T, label='T')\n plt.plot(self.wl, R, label='R')\n plt.legend()\n plt.xlabel('Wavelength (nm)')\n plt.ylabel('R/A/T')\n plt.show()\n\nNow that we have defined a class containing the relevant information and methods for the optimization process, we need to make an instance of that class for the DE algorithm to use.\n\nDE_class = calc_min_Jsc()\n\nWe also set the upper and lower bounds on thickness for each layer:\n\nMgF2 (ARC layer 1)\nTa2O5 (ARC layer 2)\nGaInP (top junction)\nGaAs (2nd junction)\nSiGeSn (3rd junction)\n\nWe will not optimize the thickness of the bottom Ge cell at this stage; from a purely optical point of view, this should be infinitely thick to maximize absorption, which is of course not the case for a device. We will set the thickness of the Ge at 300 \\(\\mu\\)m.\n\nbounds_optics = [[10,150], [10,105], [200, 1000], [500, 10000], [500, 10000]]\n\nNow, we pass the function which will be minimized to the DE (parallel differential evolution) solver. The bounds argument sets upper and lower bounds for each parameter. PDE_obj contains all the information to run the DE but does not actually start the calculation (like the calc_min_Jsc class defined above, DE is a class and not a function.\nTo actually run the DE, we use the .solve() method of the DE object class:\n\nPDE_obj = DE(DE_class.evaluate, bounds=bounds_optics, maxiters=n_iters_optics)\n\nres = PDE_obj.solve()\n\nNote: Due to issues with parallel computations depending on your operating system etc., we used the DE class here. There is also a parallelized version of this class, called PDE, which is already implemented above. If you are running this example on your own computer, you can run the example in parallel by simple changing DE to PDE.\nPDE_obj.solve() returns a list of five items: - res[0] is a list of the parameters which gave the overall minimized value at the end of the process - res[1] is that minimized value - res[2] is the evolution of the best population (the best population from each iteration) - res[3] is the evolution of the minimized value, i.e. the best fitness in iteration - res[4] is the evolution of the mean fitness over the iterations\nLet’s plot the absorption in each layer using the optimized thicknesses:\n\n# best population:\nbest_pop = res[0]\n\nprint('parameters for best result:', best_pop, '\\n', 'optimized Jsc value (mA/cm2):', -res[1])\n\n# plot the result at these best parameters\nDE_class.plot(best_pop)\n\nparameters for best result: [ 119.25428258 83.4922207 550.47007392 2040.13817627 1420.56539299] \n optimized Jsc value (mA/cm2): 14.031909365709433\n\n\n\n\n\nAnd the evolution of the best and mean fitness with each iteration of the DE algorithm:\n\nbest_fitn_evo = res[3]\nmean_fitn_evo = res[4]\n\n# plot evolution of the fitness of the best population per iteration\n\nplt.figure()\nplt.plot(-best_fitn_evo, '-k', label='Best fitness')\nplt.plot(-mean_fitn_evo, '-r', label='Mean fitness')\nplt.xlabel('Iteration')\nplt.ylabel('Fitness')\nplt.legend()\nplt.show()\n\n\n\n\nWe see that the fitness of the best population ‘jumps’ every few iterations as a new best population is found, while the mean fitness increases slowly as the whole population gradually improves. Ideally, we would like to see the fitness converging, but it may be necessary to increase the number of iterations to achieve this."
+ "objectID": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html#setting-up",
+ "href": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html#setting-up",
+ "title": "Example 5a: Ultra-thin GaAs cell with diffraction grating",
+ "section": "Setting up",
+ "text": "Setting up\nFirst, defining all the materials. We are just going to do an optical simulation, so don’t have to worry about doping levels and other parameters which would affect the electrical performance of the cell.\n\nInAlP = material('AlInP')(Al=0.5) # In0.5Al0.5P\nGaAs = material('GaAs')()\nInGaP = material('GaInP')(In=0.5) # Ga0.5In0.5P\nSiN = material('Si3N4')() # for the ARC\nAl2O3 = material('Al2O3P')() # for the ARC\n\nAir = material('Air')()\n\nThe optical constants used for the silver are very important, so we search for a known reliable dataset (from this paper).\n\nAg_pageid = search_db(os.path.join(\"Ag\", \"Jiang\"))[0][0]\nAg = material(str(Ag_pageid), nk_db=True)()\n\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\n1 results found.\npageid shelf book page filepath hasrefractive hasextinction rangeMin rangeMax points\n2 main Ag Jiang main/Ag/Jiang.yml 1 1 0.3 2.0 1701\n\n\nAM0 spectrum (photon flux) for calculating currents. For space applications (i.e. above the atmosphere) we are often interested in AM0. We use Solcore’s LightSource class, then extract the AM0 photon flux at the wavelengths we are going to use in the simulations.\n\nwavelengths = np.linspace(303, 1000, 200) * 1e-9\n\nAM0_ls = LightSource(source_type='standard', version='AM0', x=wavelengths, output_units=\"photon_flux_per_m\")\nAM0 = AM0_ls.spectrum(x=wavelengths)[1] # Photon flux; used to calculate photogenerated current later on\n\nSetting options. We choose s polarization because, for normal incidence, there will not be a difference in the results for \\(s\\) and \\(p\\) polarization (and thus for unpolarized light, u, which would be calculated as the average of the results for \\(s\\) and \\(p\\) polarization. We could set the polarization to u for equivalent results (at normal incidence only), but this would take longer because then RayFlare has to run two calculations and then average them.\nThe other key option is the number of Fourier orders retained: rigorous coupled-wave analysis (RCWA) is a Fourier-space method, and we have to specify how many Fourier orders should be retained in the calculation. As we increase the number of orders, the calculation should become more accurate and eventually converge, but the computation time increases (it scales with the cube of the number of orders).\n\noptions = default_options()\noptions.pol = 's'\noptions.wavelengths = wavelengths\noptions.orders = 100 # Reduce the number of orders to speed up the calculation."
},
{
- "objectID": "solar-cell-simulation/notebooks/7b-optimization.html#device-optimization",
- "href": "solar-cell-simulation/notebooks/7b-optimization.html#device-optimization",
- "title": "Example 7b: More advanced optimization",
- "section": "Device optimization",
- "text": "Device optimization\nAs discussed above, we approach this optimization in two phases: a faster optical simulation to get approximate total thicknesses for each junction, and then a device optimization. We take a very similar approach and define a class to contain the information and methods needed for the device optimization:\n\nclass optimize_device():\n\n def __init__(self, ARC_thickness):\n self.ARC = ARC_thickness\n self.position = [1e-10] * 10 + [5e-8] # 0.1 nm spacing in all layers except the Ge\n\n\n def make_cell(self, x):\n\n #x[0]: total InGaP thickness\n #x[1]: total InGaAs thickness\n #x[2]: total SiGeSn thickness\n #x[3]: total Ge thickness\n\n #x[4]: InGaP n thickness\n #x[5]: InGaAs n thickness\n #x[6]: SiGeSn n thickness\n #x[7]: Ge n thickness\n\n e_charge = si('1eV')\n\n # materials\n Ta2O5_index = search_db(os.path.join(\"Ta2O5\", \"Rodriguez\"))[0][0]\n SiGeSn = material('SiGeSn')\n\n GaAs = material('GaAs')\n InGaP = material('GaInP')\n Ge = material('Ge')\n MgF2 = material('MgF2')()\n Ta2O5 = material(str(Ta2O5_index), nk_db=True)()\n AlInP = material(\"AlInP\")\n\n window_material = AlInP(Al=0.52)\n\n GaInP_mobility_h = 0.03 #\n GaInP_lifetime_h = 1e-8\n GaInP_D_h = GaInP_mobility_h * kb * 300 / e_charge\n GaInP_L_h = np.sqrt(GaInP_D_h * GaInP_lifetime_h)\n GaInP_mobility_e = 0.015\n GaInP_lifetime_e = 1e-8\n GaInP_D_e = GaInP_mobility_e * kb * 300 / e_charge\n GaInP_L_e = np.sqrt(GaInP_D_e * GaInP_lifetime_e)\n\n top_cell_n_material = InGaP(In=0.5, Nd=si(\"2e18cm-3\"), hole_diffusion_length=GaInP_L_h,\n hole_mobility=GaInP_mobility_h)\n top_cell_p_material = InGaP(In=0.5, Na=si(\"2e17cm-3\"), electron_diffusion_length=GaInP_L_e,\n electron_mobility=GaInP_mobility_e)\n\n # MID CELL - GaAs\n\n GaAs_mobility_h = 0.85 #\n GaAs_lifetime_h = 1e-8\n GaAs_D_h = GaAs_mobility_h * kb * 300 / e_charge\n GaAs_L_h = np.sqrt(GaAs_D_h * GaAs_lifetime_h)\n GaAs_mobility_e = 0.08\n GaAs_lifetime_e = 1e-8\n GaAs_D_e = GaAs_mobility_e * kb * 300 / e_charge\n GaAs_L_e = np.sqrt(GaAs_D_e * GaAs_lifetime_e)\n\n mid_cell_n_material = GaAs(Nd=si(\"1e18cm-3\"), hole_diffusion_length=GaAs_L_h,\n hole_mobility=GaAs_mobility_h)\n mid_cell_p_material = GaAs(Na=si(\"1e17cm-3\"), electron_diffusion_length=GaAs_L_e,\n electron_mobility=GaAs_mobility_e)\n\n\n SiGeSn.band_gap = si('0.77eV') # from PL measurement\n SiGeSn_L_h = si('0.35um')\n SiGeSn_L_e = si('5um')\n SiGeSn_lifetime_e = 1e-6\n SiGeSn_lifetime_h = 1e-6\n SiGeSn_mobility_h = SiGeSn_L_h ** 2 * e_charge / (SiGeSn_lifetime_h * kb * 300)\n SiGeSn_mobility_e = SiGeSn_L_e ** 2 * e_charge / (SiGeSn_lifetime_e * kb * 300)\n\n pen_cell_n_material = SiGeSn(Nd=si(\"1e18cm-3\"), hole_diffusion_length=SiGeSn_L_h,\n relative_permittivity=16, hole_mobility=SiGeSn_mobility_h)\n pen_cell_p_material = SiGeSn(Na=si(\"1e17cm-3\"), electron_diffusion_length=SiGeSn_L_e,\n relative_permittivity=16, electron_mobility=SiGeSn_mobility_e)\n\n Ge_lifetime_h = 1e-6\n Ge_L_h = si('500nm')\n Ge_mobility_h = Ge_L_h ** 2 * e_charge / (Ge_lifetime_h * kb * 300)\n Ge_mobility_e = 0.18\n Ge_lifetime_e = 1e-6\n Ge_D_e = Ge_mobility_e * kb * 300 / e_charge\n Ge_L_e = np.sqrt(Ge_D_e * Ge_lifetime_e)\n\n bot_cell_n_material = Ge(Nd=si(\"2e18cm-3\"), hole_diffusion_length=Ge_L_h,\n hole_mobility=Ge_mobility_h)\n bot_cell_p_material = Ge(Na=si(\"1e17cm-3\"), electron_diffusion_length=Ge_L_e,\n electron_mobility=Ge_mobility_e)\n\n\n\n solar_cell = SolarCell([\n Layer(si(self.ARC[0], 'nm'), material=MgF2), Layer(si(self.ARC[1], 'nm'), material=Ta2O5),\n Junction([Layer(si(25, 'nm'), material=window_material, role='window'),\n Layer(si(x[4], 'nm'), material=top_cell_n_material, role='emitter'),\n Layer(si(x[0]-x[4], 'nm'), material=top_cell_p_material, role='base'),\n ], sn=1, sp=1, kind='DA'),\n Junction([Layer(si(x[5], 'nm'), material=mid_cell_n_material, role='emitter'),\n Layer(si(x[1]-x[5], 'nm'), material=mid_cell_p_material, role='base'),\n ], sn=1, sp=1, kind='DA'),\n Junction([Layer(si(x[6], 'nm'), material=pen_cell_n_material, role='emitter'),\n Layer(si(x[2]-x[6], 'nm'), material=pen_cell_p_material, role='base'),\n ], sn=1, sp=1, kind='DA'),\n Junction([Layer(si(x[7], 'nm'), material=bot_cell_n_material, role='emitter'),\n Layer(si(x[3]-x[7], 'nm'), material=bot_cell_p_material, role='base'),\n ], sn=1, sp=1, kind='DA'),\n ], shading=0.0, substrate=bot_cell_n_material)\n\n return solar_cell\n\n def calculate(self, x):\n\n light_source = LightSource(source_type='standard', version='AM1.5g')\n\n wl = np.linspace(300, 1850, 500) * 1e-9\n\n solar_cell = self.make_cell(x)\n\n V = np.linspace(0, 3.5, 300)\n solar_cell_solver(solar_cell, 'iv',\n user_options={'voltages': V, 'light_iv': True, 'wavelength': wl, 'mpp': True,\n 'light_source': light_source,\n 'optics_method': 'TMM', 'BL_correction': True,\n 'position': self.position})\n\n return wl, solar_cell\n\n def evaluate(self, x):\n\n _, solar_cell = self.calculate(x)\n efficiency = solar_cell.iv[\"Eta\"]\n\n return -efficiency\n\n def plot(self, x):\n\n wl, solar_cell = self.calculate(x)\n\n V = solar_cell.iv['IV'][0]\n\n efficiency = solar_cell.iv[\"Eta\"]\n pmax = solar_cell.iv[\"Pmpp\"]\n ff = solar_cell.iv[\"FF\"]\n voc = solar_cell.iv[\"Voc\"]\n isc = solar_cell.iv[\"Isc\"]\n\n plt.figure()\n\n plt.plot(V, solar_cell.iv['IV'][1] / 10, 'k', linewidth=3, label='Total')\n plt.plot(V, -solar_cell[2].iv(V) / 10, 'b', label='GaInP')\n plt.plot(V, -solar_cell[3].iv(V) / 10, 'g', label='GaAs')\n plt.plot(V, -solar_cell[4].iv(V) / 10, 'r', label='SiGeSn')\n plt.plot(V, -solar_cell[5].iv(V) / 10, 'y', label='Ge')\n plt.text(2, 10, '$\\eta = $' + str(round(efficiency * 100, 1)) + '%')\n plt.text(2, 8,'Pmax='+str(round(pmax,1))+'W/m$^2$')\n plt.text(2, 9, 'FF = ' + str(round(ff * 100, 1)) + '%')\n plt.text(2,7,'Voc='+str(round(voc,1))+'V')\n plt.text(2,6, 'Jsc='+str(round(0.1*isc,1))+'mA/cm$^2$')\n\n plt.legend()\n plt.ylim(0, 18)\n plt.xlim(0, 3.5)\n plt.ylabel('Current (mA/cm$^2$)')\n plt.xlabel('Voltage (V)')\n\n plt.show()\n\n solar_cell_solver(solar_cell, 'qe',\n user_options={'wavelength': wl, 'optics_method': 'TMM', 'BL_correction': True, 'position': self.position})\n\n plt.figure()\n plt.plot(wl * 1e9, solar_cell[2].eqe(wl) * 100, 'b', label='InGaP')\n plt.plot(wl * 1e9, solar_cell[3].eqe(wl) * 100, 'g', label='InGaAs')\n plt.plot(wl * 1e9, solar_cell[4].eqe(wl) * 100, 'r', label='SiGeSn')\n plt.plot(wl * 1e9, solar_cell[5].eqe(wl) * 100, 'y', label='Ge')\n plt.plot(wl * 1e9, solar_cell.absorbed * 100, 'k--', label='Absorption')\n # plt.plot(wl * 1e9, solar_cell[5].eqe(wl)*100, 'y', label='Ge')\n\n plt.legend(loc='upper right')\n plt.xlim(290, 1850)\n plt.ylim(0, 100)\n plt.ylabel('EQE (%)')\n plt.xlabel('Wavelength (nm)')\n plt.show()\n\nNow that the layer thicknesses have been optimized from an optical point of view, we want to design the device (or at least a simplified version, by calculating a more realistic EQE. Obviously additional parameters like the doping of the layers could be varied too. The list of parameters x will be:\n\nx[0]: total InGaP thickness\nx[1]: total InGaAs thickness\nx[2]: total SiGeSn thickness\nx[3]: total Ge thickness\nx[4]: InGaP emitter thickness\nx[5]: InGaAs emitter thickness\nx[6]: SiGeSn emitter thickness\nx[7]: Ge emitter thickness\n\nWe will keep the ARC thicknesses fixed at the exact values obtained in the optical simulation. For the other layers, we generate upper and lower bounds: total layer thickness between 75% and 125% of values fitted in TMM calculation. For Ge, we set the starting value at 200 \\(\\mu\\)m.\n\nstarting_params = np.append(best_pop[2:], [200000])\n\nlower = 0.75*starting_params\nupper = 1.25*starting_params\n\n# upper and lower bounds for the n-type (highly doped) layers\nlower_ntype = [20, 20, 20, 20]\n\nupper_ntype = [200, 300, 300, 500]\n\nall_lower = np.append(lower, lower_ntype)\n\nall_upper = np.append(upper, upper_ntype)\n\n# full list of bounds\nall_bounds = np.stack((all_lower, all_upper)).T\n\nSimilar to the optical simulation above, we now create an object of this class (setting the ARC thickness when we create the class), then create an object of the DE class, and call the .solve method.\n\n# DE calculation for the electrical simulation\n\nDE_class_DA = optimize_device(best_pop[0:2])\n\n# default population size = 5*number of params\nPDE_obj_DA = DE(DE_class_DA.evaluate, bounds=all_bounds, maxiters=n_iters_device)\n\n# solve, i.e. minimize the problem\nres_DA = PDE_obj_DA.solve()\n\nWe plot the QE and IV for the best population:\n\nbest_pop_DA = res_DA[0]\n\nprint('parameters for best result:', best_pop_DA, 'optimized efficiency (%)', res_DA[1]*100)\n\n# plot the result at these best parameters\nDE_class_DA.plot(best_pop_DA)\n\nparameters for best result: [5.64211912e+02 1.67613064e+03 1.08983977e+03 1.99726177e+05\n 1.71208031e+02 1.18811297e+02 4.59410919e+01 1.07605130e+02] optimized efficiency (%) -35.688813260065366\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\n1 results found.\npageid shelf book page filepath hasrefractive hasextinction rangeMin rangeMax points\n475 main Ta2O5 Rodriguez-de_Marcos main/Ta2O5/Rodriguez-de Marcos.yml 1 1 0.0294938 1.51429 212\nSolving optics of the solar cell...\nTreating layer(s) 10 incoherently\nCalculating RAT...\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\nMaterial main/Ta2O5/Rodriguez-de Marcos.yml loaded.\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\nMaterial main/Ta2O5/Rodriguez-de Marcos.yml loaded.\nCalculating absorption profile...\nSolving IV of the junctions...\nSolving IV of the tunnel junctions...\nSolving IV of the total solar cell...\nSolving optics of the solar cell...\nAlready calculated reflection, transmission and absorption profile - not recalculating. Set recalculate_absorption to True in the options if you want absorption to be calculated again.\nSolving QE of the solar cell...\n\n\n\n\n\n/Users/phoebe/Documents/develop/solcore5/solcore/analytic_solar_cells/depletion_approximation.py:617: RuntimeWarning: invalid value encountered in true_divide\n iqe = j_sc / current_absorbed\n\n\n\n\n\n\nbest_pop_evo = res_DA[2]\nbest_fitn_evo = res_DA[3]\nmean_fitn_evo = res_DA[4]\nfinal_fitness = res_DA[1]\n\n# plot evolution of the fitness of the best population per iteration\n\nplt.figure()\nplt.plot(-best_fitn_evo, '-k', label='Best fitness')\nplt.plot(-mean_fitn_evo, '-r', label='Mean fitness')\nplt.xlabel('Iteration')\nplt.ylabel('Fitness')\nplt.legend()\nplt.show()"
+ "objectID": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html#on-substrate-device",
+ "href": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html#on-substrate-device",
+ "title": "Example 5a: Ultra-thin GaAs cell with diffraction grating",
+ "section": "On-substrate device",
+ "text": "On-substrate device\nThis device is still on the GaAs substrate (it is also inverted compared to the other devices, since it represents the device before patterning and lift-off). We create the simulation object using tmm_structure class, and then use the .calculate function defined for the class to calculate the reflection, absorption per layer, and transmission.\n\nprint(\"Calculating on-substrate device...\")\n\nstruct = SolarCell([Layer(si('20nm'), InAlP), Layer(si('85nm'), GaAs),\n Layer(si('20nm'), InGaP)])\n\n# make TMM structure for planar device\nTMM_setup = tmm_structure(struct, incidence=Air, transmission=GaAs)\n\n# calculate\nRAT_TMM_onsubs = TMM_setup.calculate(options)\n\nAbs_onsubs = RAT_TMM_onsubs['A_per_layer'][:,1] # absorption in GaAs\n# indexing of A_per_layer is [wavelengths, layers]\n\nR_onsubs = RAT_TMM_onsubs['R']\nT_onsubs = RAT_TMM_onsubs['T']\n\nCalculating on-substrate device..."
},
{
- "objectID": "solar-cell-simulation/notebooks/7b-optimization.html#comparison",
- "href": "solar-cell-simulation/notebooks/7b-optimization.html#comparison",
- "title": "Example 7b: More advanced optimization",
- "section": "Comparison",
- "text": "Comparison\nCompare the total layer thicknesses obtained from the optical and electrical simulations:\n\nprint(\"Comparison of thicknesses from optical/electrical optimization:\")\nprint('GaInP total thickness: %.1f/%.1f nm' % (best_pop[2], best_pop_DA[0]))\nprint('GaAs total thickness: %.1f/%.1f nm' % (best_pop[3], best_pop_DA[1]))\nprint('SiGeSn total thickness: %.1f/%.1f nm' % (best_pop[4], best_pop_DA[2]))\n\nComparison of thicknesses from optical/electrical optimization:\nGaInP total thickness: 550.5/564.2 nm\nGaAs total thickness: 2040.1/1676.1 nm\nSiGeSn total thickness: 1420.6/1089.8 nm\n\n\nNOTE: You may have an issue running the parallel version of this example (change DE to PDE) if you are using Windows. To get around this, you need to use the if __name__ == \"__main__\" construction. The issue arises because the multiprocessing module uses a different process on Windows than on UNIX systems which will throw errors if this construction is not used. You need to put everything apart from the module imports at the top of the script and the class definitions inside a function called main, and execute this with:\n\n# if __name__ == '__main__':\n# main()"
+ "objectID": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html#planar-silver-mirror-device",
+ "href": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html#planar-silver-mirror-device",
+ "title": "Example 5a: Ultra-thin GaAs cell with diffraction grating",
+ "section": "Planar silver mirror device",
+ "text": "Planar silver mirror device\nThis device is now flipped (in fabrication terms, the back mirror was applied and then the device lifted off). It has a silver back mirror, which should increase reflection and the rear surface so that less light is lost to the substrate.\n\nprint(\"Calculating planar Ag mirror device...\")\n\nsolar_cell_TMM = SolarCell([Layer(material=InGaP, width=si('20nm')),\n Layer(material=GaAs, width=si('85nm')),\n Layer(material=InAlP, width=si('20nm'))],\n substrate=Ag)\n\nTMM_setup = tmm_structure(solar_cell_TMM, incidence=Air, transmission=Ag)\n\nRAT_TMM = TMM_setup.calculate(options)\n\nAbs_TMM = RAT_TMM['A_per_layer'][:, 1]\nAbs_TMM_InAlPonly = RAT_TMM['A_per_layer'][:, 2]\nAbs_TMM_InGaPonly = RAT_TMM['A_per_layer'][:, 0]\nR_TMM = RAT_TMM['R']\nT_TMM = RAT_TMM['T']\n\nCalculating planar Ag mirror device...\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\nMaterial main/Ag/Jiang.yml loaded.\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\nMaterial main/Ag/Jiang.yml loaded."
},
{
- "objectID": "solar-cell-simulation/notebooks/1c-simple_cell.html",
- "href": "solar-cell-simulation/notebooks/1c-simple_cell.html",
- "title": "Example 1c: Electrical models",
- "section": "",
- "text": "In the first two examples, we mostly focused on different optical models and how they can be applied to an Si cell. Here we will look at different electrical models, roughly in increasing order of how ‘realistic’ they are expected to be:\nimport numpy as np\nimport matplotlib.pyplot as plt\n\nfrom solcore.solar_cell import SolarCell, Layer, Junction\nfrom solcore.solar_cell_solver import solar_cell_solver\nfrom solcore.absorption_calculator import OptiStack, calculate_rat\n\nfrom solcore import material, si\n\nfrom solcore.interpolate import interp1d"
+ "objectID": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html#nanophotonic-grating-device",
+ "href": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html#nanophotonic-grating-device",
+ "title": "Example 5a: Ultra-thin GaAs cell with diffraction grating",
+ "section": "Nanophotonic grating device",
+ "text": "Nanophotonic grating device\nFinally, this device has a grating made of silver and SiN, followed by a planar silver back mirror; this leads to diffraction, increasing the path length of light in the cell, while keeping reflection high. Here we use the rcwa_structure class, which is used in the same way as tmm_structure above and rt_structure in Example 4a. Because we are now dealing with a structure which is periodic in the \\(x\\) and \\(y\\) directions (unlike the TMM structures, which are uniform in the plane), we have to specify the size of the unit cell (this does not have to be square; here we have a triangular unit cell to give a grating with a hexagonal layout of circles). Otherwise, the grating layer is specified as normal but with a geometry argument which lists the shapes in the grating and the material they are made of.\nThere are many additional options which can be specified for S4 (which is used to actually run the RCWA calculations); more detail can be found in its documentation here.\n\nprint(\"Calculating nanophotonic grating device...\")\n\nx = 600\n\n# lattice vectors for the grating. Units are in nm!\nsize = ((x, 0), (x / 2, np.sin(np.pi / 3) * x))\n\nropt = dict(LatticeTruncation='Circular',\n DiscretizedEpsilon=False,\n DiscretizationResolution=8,\n PolarizationDecomposition=False,\n PolarizationBasis='Default',\n #LanczosSmoothing=dict(Power=2, Width=1),\n LanczosSmoothing=False,\n SubpixelSmoothing=False,\n ConserveMemory=False,\n WeismannFormulation=False,\n Verbosity=0)\n\noptions.S4_options = ropt\n\n# grating layers\ngrating = [Layer(width=si(100, 'nm'), material=SiN, geometry=[{'type': 'circle', 'mat': Ag, 'center': (0, 0),\n 'radius': x/3, 'angle': 0}])] # actual grating part of grating\n\n\n# DTL device without anti-reflection coating\nsolar_cell = SolarCell([Layer(material=InGaP, width=si('20nm')),\n Layer(material=GaAs, width=si('85nm')),\n Layer(material=InAlP, width=si('20nm'))] + grating,\n substrate=Ag)\n\n# make RCWA structure\nS4_setup = rcwa_structure(solar_cell, size, options, Air, Ag)\n\n# calculate\n\nRAT = S4_setup.calculate(options)\n\nAbs_DTL = RAT['A_per_layer'][:,1] # absorption in GaAs\n\nR_DTL = RAT['R']\nT_DTL = RAT['T']\n\nCalculating nanophotonic grating device..."
},
{
- "objectID": "solar-cell-simulation/notebooks/1c-simple_cell.html#setting-up",
- "href": "solar-cell-simulation/notebooks/1c-simple_cell.html#setting-up",
- "title": "Example 1c: Electrical models",
- "section": "Setting up",
- "text": "Setting up\nDefine some materials:\n\nGaAs = material(\"GaAs\")()\nAl2O3 = material(\"Al2O3\")()\nAg = material(\"Ag\")()\n\nwavelengths = si(np.linspace(300, 950, 200), \"nm\")\n\nWe are going to do an optical calculation first to get absorption for a GaAs layer; we will use this as an estimate for the EQE as input for the two-diode model.\n\nOS = OptiStack([Layer(si(\"3um\"), GaAs)], substrate=Ag)\n\nCalculate reflection/absorption/transmission (note that we have to give the wavelength to this function in nm rather than m!)\n\nRAT = calculate_rat(OS, wavelength=wavelengths*1e9, no_back_reflection=False)\n\nCreate a function which interpolates the absorption - note that we pass a function which returns the absorption when given a wavelength to the Junction, rather than a table of values!\n\neqe_func = interp1d(wavelengths, RAT[\"A\"])"
+ "objectID": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html#nanophotonic-grating-device-with-arc",
+ "href": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html#nanophotonic-grating-device-with-arc",
+ "title": "Example 5a: Ultra-thin GaAs cell with diffraction grating",
+ "section": "Nanophotonic grating device with ARC",
+ "text": "Nanophotonic grating device with ARC\nThis device is exactly like the previous one, but with the additional of a simple single-layer anti-reflection coating.\n\nprint(\"Calculating nanophotonic grating device with ARC...\")\n\n# DTL device with anti-reflection coating\nsolar_cell = SolarCell([Layer(material=Al2O3, width=si('70nm')),\n Layer(material=InGaP, width=si('20nm')),\n Layer(material=GaAs, width=si('85nm')),\n Layer(material=InAlP, width=si('20nm'))] + grating,\n substrate=Ag)\n\n# make RCWA structure\nS4_setup = rcwa_structure(solar_cell, size, options, Air, Ag)\n\n# calculate\nRAT_ARC = S4_setup.calculate(options)\n\nAbs_DTL_ARC = RAT_ARC['A_per_layer'][:,2] # absorption in GaAs + InGaP\n\nR_DTL_ARC = RAT_ARC['R']\nT_DTL_ARC = RAT_ARC['T']\n\nCalculating nanophotonic grating device with ARC..."
},
{
- "objectID": "solar-cell-simulation/notebooks/1c-simple_cell.html#d-and-db-junctions",
- "href": "solar-cell-simulation/notebooks/1c-simple_cell.html#d-and-db-junctions",
- "title": "Example 1c: Electrical models",
- "section": "2D and DB junctions",
- "text": "2D and DB junctions\nDefine the 2D junction with reasonable parameters for GaAs. The units of j01 and j01 are A/m^2. The units for the resistances are (Ohm m)^2. We use the standard ideality factors (1 and 2 respectively) for the two diodes:\n\ntwod_junction = Junction(kind='2D', n1=1, n2=2, j01=3e-17, j02=1e-7,\n R_series=6e-4, R_shunt=5e4, eqe=eqe_func)\n\nDefine two instances of a detailed-balance type junction. In both cases, there will be a sharp absorption onset at the bandgap (1.42 eV for GaAs). By specifying A, we set the fraction of light above the bandgap that is absorbed (A = 1 means 100% absorption above the gap).\n\ndb_junction_A1 = Junction(kind='DB', Eg=1.42, A=1, R_shunt=1e4, n=1)\ndb_junction = Junction(kind='DB', Eg=1.42, A=0.8, R_shunt=1e4, n=1)\n\nV = np.linspace(0, 1.5, 200)\n\nSet some options and define solar cells based on these junctions:\n\nopts = {'voltages': V, 'light_iv': True, 'wavelength': wavelengths, 'mpp': True}\n\nsolar_cell_db_A1 = SolarCell([db_junction_A1])\nsolar_cell_db = SolarCell([db_junction])\nsolar_cell_2d = SolarCell([twod_junction])\n\nCalculate and plot the IV curves:\n\nsolar_cell_solver(solar_cell_db_A1, 'iv', user_options=opts)\nsolar_cell_solver(solar_cell_db, 'iv', user_options=opts)\nsolar_cell_solver(solar_cell_2d, 'iv', user_options=opts)\n\nSolving optics of the solar cell...\nSolving IV of the junctions...\nSolving IV of the tunnel junctions...\nSolving IV of the total solar cell...\nSolving optics of the solar cell...\nSolving IV of the junctions...\nSolving IV of the tunnel junctions...\nSolving IV of the total solar cell...\nSolving optics of the solar cell...\nWarning: A junction of kind \"2D\" found. Junction ignored in the optics calculation!\nSolving IV of the junctions...\nSolving IV of the tunnel junctions...\nSolving IV of the total solar cell...\n\n\nPLOT 1: IV curves for the DB and 2D models.\n\nplt.figure()\nplt.plot(*solar_cell_db_A1.iv[\"IV\"], label='Detailed balance (Eg = 1.44 eV, A = 1)')\nplt.plot(*solar_cell_db.iv[\"IV\"], label='Detailed balance (Eg = 1.44 eV, A = 0.8)')\nplt.plot(*solar_cell_2d.iv[\"IV\"], '--', label='Two-diode')\nplt.xlim(0, 1.5)\nplt.ylim(0, 500)\nplt.xlabel(\"V (V)\")\nplt.ylabel(\"J (A/m$^2$)\")\nplt.legend()\nplt.title('(1) IV curves calculated through detailed balance and two-diode models')\nplt.show()\n\n\n\n\nAs we expect, the two DB solar cells have a very similar shape, but the A = 1 case has a higher Jsc. The two-diode model has a lower current, which makes sense as it’s EQE is specified based on a more realistic absorption calculation which includes front-surface reflection and an absorption edge which is not infinitely sharp at the bandgap, as is assumed by the detailed balance model."
+ "objectID": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html#plotting-results",
+ "href": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html#plotting-results",
+ "title": "Example 5a: Ultra-thin GaAs cell with diffraction grating",
+ "section": "Plotting results",
+ "text": "Plotting results\nPLOT 1: Comparing the absorption in GaAs for the four different device architectures\n\n\npal = sns.color_palette(\"husl\", 4)\n\nfig = plt.figure(figsize=(6.4, 4.8))\n\nplt.plot(wavelengths*1e9, 100*Abs_onsubs, color=pal[0], label=\"On substrate\")\nplt.plot(wavelengths*1e9, 100*Abs_TMM, color=pal[1], label=\"Planar mirror\")\nplt.plot(wavelengths*1e9, 100*Abs_DTL, color=pal[2], label=\"Nanophotonic grating (no ARC)\")\nplt.plot(wavelengths*1e9, 100*Abs_DTL_ARC, color=pal[3], label=\"Nanophotonic grating (with ARC)\")\n\nplt.xlim(300, 950)\nplt.ylim(0, 100)\nplt.xlabel('Wavelength (nm)')\nplt.ylabel('EQE (%)')\nplt.legend(loc='upper left')\nplt.show()\n\n\n\n\nWe see that the addition of a planar silver mirror significantly boosts the absorption around 700 nm, essentially by creating a Fabry-Perot (thin film) cavity in the semiconductor layers through high reflection at the rear of the cell. The grating introduces multiple resonances relating to different diffraction orders and waveguide modes in the structure, which boosts the absorption especially close to the absorption edge in comparison to the planar devices.\nPLOT 2: Absorption per layer in the planar Ag device.\n\nfig = plt.figure(figsize=(6.4, 4.8))\nplt.stackplot(wavelengths*1e9,\n [100*Abs_TMM, 100*Abs_TMM_InGaPonly, 100*Abs_TMM_InAlPonly],\n colors=pal,\n labels=['Absorbed in GaAs', 'Absorbed in InGaP', 'Absorbed in InAlP'])\nplt.xlim(300, 950)\nplt.ylim(0, 90)\nplt.xlabel('Wavelength (nm)')\nplt.ylabel('EQE (%)')\nplt.legend(loc='upper right')\nplt.show()\n\n\n\n\nThe plot above shows that at short wavelengths, even very thin layers (in this case, 20 nm of InGaP) can absorb a very significant fraction of the incident radiation. Depending on the device, the carriers generated here may or may not be extracted as current."
},
{
- "objectID": "solar-cell-simulation/notebooks/1c-simple_cell.html#da-and-pdd-junctions",
- "href": "solar-cell-simulation/notebooks/1c-simple_cell.html#da-and-pdd-junctions",
- "title": "Example 1c: Electrical models",
- "section": "DA and PDD junctions",
- "text": "DA and PDD junctions\nNow let’s consider the two slightly more complex models, which will actually take into account the absorption profile of light in the cell and the distribution of charge carriers; the depletion approximation and the Poisson drift-diffusion solver.\nNote: for the PDD example to work, the PDD solver must be installed correctly; see the Solcore documentation for more information.\n\nT = 293 # ambient temperature\n\nwindow = material('AlGaAs')(T=T, Na=si(\"5e18cm-3\"), Al=0.8)\np_GaAs = material('GaAs')(T=T, Na=si(\"1e18cm-3\"), electron_diffusion_length=si(\"400nm\"))\nn_GaAs = material('GaAs')(T=T, Nd=si(\"8e16cm-3\"), hole_diffusion_length=si(\"8um\"))\nbsf = material('GaAs')(T=T, Nd=si(\"2e18cm-3\"))\n\nSC_layers = [Layer(width=si('150nm'), material=p_GaAs, role=\"Emitter\"),\n Layer(width=si('2850nm'), material=n_GaAs, role=\"Base\"),\n Layer(width=si('200nm'), material=bsf, role=\"BSF\")]\n\nsn and sp are the surface recombination velocities (in m/sec). sn is the SRV for the n-doped junction, sp for the p-doped junction.\n\n# Depletion approximation:\nsolar_cell_da = SolarCell(\n [Layer(width=si(\"90nm\"), material=Al2O3), Layer(width=si('20nm'),\n material=window, role=\"Window\"),\n Junction(SC_layers, sn=5e4, sp=5e4, kind='DA')],\n R_series=0, substrate=Ag\n)\n\n\n# Drift-diffusion solver:\nsolar_cell_pdd = SolarCell(\n [Layer(width=si(\"90nm\"), material=Al2O3), Layer(width=si('20nm'),\n material=window, role=\"Window\"),\n Junction(SC_layers, sn=5e4, sp=5e4, kind='PDD')],\n R_series=0, substrate=Ag\n)\n\nIn both cases, we set the series resistance to 0. Other loss factors, such as shading, are also assumed to be zero by default.\n\nopts[\"optics_method\"] = \"TMM\" # Use the transfer-matrix method to calculate the cell's optics\nopts[\"position\"] = 1e-10 # This is the spacing used when calculating the depth-dependent absorption (0.1 nm)\nopts[\"no_back_reflection\"] = False\n\nsolar_cell_solver(solar_cell_da, \"iv\", user_options=opts);\nsolar_cell_solver(solar_cell_da, \"qe\", user_options=opts);\n\nsolar_cell_solver(solar_cell_pdd, \"iv\", user_options=opts);\nsolar_cell_solver(solar_cell_pdd, \"qe\", user_options=opts);\n\nPLOT 2: IV curves for the DA and PDD models\n\nplt.figure()\nplt.plot(*solar_cell_da.iv[\"IV\"], label=\"Depletion approximation\")\nplt.plot(*solar_cell_pdd.iv[\"IV\"], '--', label=\"Poisson Drift Diffusion\")\nplt.xlim(0, 1.2)\nplt.ylim(0, 330)\nplt.legend()\nplt.xlabel(\"V (V)\")\nplt.ylabel(\"J (A/m$^2$)\")\nplt.title('(2) IV curves from depletion approximation and drift-diffusion models')\nplt.show()\n\n\n\n\n\n\n\nPLOT 3: EQE and absorption calculated for the PDD and DA models.\n\nplt.figure()\nplt.plot(wavelengths*1e9, 100*solar_cell_da[2].eqe(wavelengths), 'k-', label=\"EQE (DA)\")\nplt.plot(wavelengths*1e9, 100*solar_cell_pdd[2].eqe(wavelengths), 'k--', label=\"EQE (PDD)\")\nplt.plot(wavelengths*1e9, 100*solar_cell_da[2].layer_absorption, 'r-', label=\"A (DA)\")\nplt.plot(wavelengths*1e9, 100*solar_cell_pdd[2].layer_absorption, 'b--', label=\"A (PDD)\")\nplt.legend()\nplt.xlabel(\"Wavelength (nm)\")\nplt.ylabel(\"EQE/A (%)\")\nplt.title('(3) EQE and absorption from depletion approximation and drift-diffusion models')\nplt.show()"
+ "objectID": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html#calculating-photogenerated-current",
+ "href": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html#calculating-photogenerated-current",
+ "title": "Example 5a: Ultra-thin GaAs cell with diffraction grating",
+ "section": "Calculating photogenerated current",
+ "text": "Calculating photogenerated current\nFinally, we use the photon flux in the AM0 spectrum to calculate the maximum possible achievable current for these devices.\n\nonsubs = 0.1 * q * np.trapz(Abs_onsubs*AM0, wavelengths)\nAg = 0.1 * q * np.trapz(Abs_TMM*AM0, wavelengths)\nDTL = 0.1 * q * np.trapz(Abs_DTL*AM0, wavelengths)\nDTL_ARC = 0.1 * q * np.trapz(Abs_DTL_ARC*AM0, wavelengths)\n\n\nprint('On substrate device current: %.1f mA/cm2 ' % onsubs)\nprint('Planar Ag mirror device current: %.1f mA/cm2 ' % Ag)\nprint('Nanophotonic grating (no ARC) device current: %.1f mA/cm2 ' % DTL)\nprint('Nanophotonic grating (with ARC) device current: %.1f mA/cm2 ' % DTL_ARC)\n\nOn substrate device current: 9.5 mA/cm2 \nPlanar Ag mirror device current: 13.8 mA/cm2 \nNanophotonic grating (no ARC) device current: 18.0 mA/cm2 \nNanophotonic grating (with ARC) device current: 23.0 mA/cm2 \n\n\nAs expected, simply adding a planar mirror boosts the current significantly. The addition of a nanophotonic grating gives a further boost (note that the grating we used here is optimized in terms of grating pitch (period) and dimension; not all gratings would give a boost in current, and some may even reduce performance due to e.g. parasitic absorption)."
},
{
- "objectID": "solar-cell-simulation/notebooks/4a-textured_Si_cell.html",
- "href": "solar-cell-simulation/notebooks/4a-textured_Si_cell.html",
- "title": "Example 4a: Textured Si cell",
+ "objectID": "solar-cell-simulation/notebooks/3a-triple_junction.html",
+ "href": "solar-cell-simulation/notebooks/3a-triple_junction.html",
+ "title": "Example 3a: Triple junction cell",
"section": "",
- "text": "In this example, we will introduce RayFlare, which is a package which is closely interlinked with Solcore and extends its optical capabilities. One of the features it has is a ray-tracer, which is useful when modelling e.g. Si solar cells with textured surfaces. We will compare the result with PVLighthouse’s wafer ray tracer.\nFor more information on how ray-tracing works, see RayFlare’s documentation.\nimport numpy as np\nimport os\n\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nfrom rayflare.ray_tracing import rt_structure\nfrom rayflare.textures import regular_pyramids, planar_surface\nfrom rayflare.options import default_options\nfrom rayflare.utilities import make_absorption_function\n\nfrom solcore.absorption_calculator import search_db\nfrom solcore import material, si\nfrom solcore.solar_cell import SolarCell, Layer, Junction\nfrom solcore.solar_cell_solver import solar_cell_solver\nfrom solcore.solar_cell_solver import default_options as defaults_solcore"
- },
- {
- "objectID": "solar-cell-simulation/notebooks/4a-textured_Si_cell.html#setting-up",
- "href": "solar-cell-simulation/notebooks/4a-textured_Si_cell.html#setting-up",
- "title": "Example 4a: Textured Si cell",
- "section": "Setting up",
- "text": "Setting up\nFirst, setting up Solcore materials. We use a specific set of Si optical constants from this paper. These are included in the refractiveindex.info database, so we take them from there. This is the same data we used for the PVLighthouse calculation which we are going to compare to.\n\nAir = material('Air')()\nSi_Green = search_db(os.path.join(\"Si\", \"Green-2008\"))[0][0]\nSi_RT = material(str(Si_Green), nk_db=True)()\n\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\n1 results found.\npageid shelf book page filepath hasrefractive hasextinction rangeMin rangeMax points\n687 main Si Green-2008 main/Si/Green-2008.yml 1 1 0.25 1.45 121\n\n\nThe calc variable is a switch: if True, run the calculation; if False, load the result of the previous calculation. Will need to run at least once to generate the results!\nWe use this ‘switch’ to avoid re-running the whole ray-tracing calculation (which can be time-consuming) each time we want to look at the results.\n\ncalc = True\n\nSetting options:\n\nwl = np.linspace(300, 1201, 50) * 1e-9\noptions = default_options()\noptions.wavelengths = wl\n\n# setting up some colours for plotting\npal = sns.color_palette(\"husl\", 4)\n\nnx and ny are the number of point to scan across in the x & y directions in the unit cell. Decrease this to speed up the calculation (but increase noise in results). We also set the total number of rays traced, and depth spacing for the absorption profile calculation.\n\nnxy = 25\noptions.nx = nxy\noptions.ny = nxy\noptions.n_rays = 4 * nxy ** 2 # Number of rays to be traced at each wavelength:\noptions.depth_spacing = si('50nm') # depth spacing for the absorption profile\noptions.parallel = True # this is the default - if you do not want the code to run in parallel, change to False\n\nLoad the result of the PVLighthouse calculation for comparison:\n\nPVlighthouse = np.loadtxt(os.path.join(\"data\", \"RAT_data_300um_2um_55.csv\"), delimiter=',', skiprows=1)\n\nDefine surface for the ray-tracing: a planar surface, and a surface with regular pyramids.\n\nflat_surf = planar_surface(size=2) # pyramid size in microns\ntriangle_surf = regular_pyramids(55, upright=False, size=2)\n\nSet up the ray-tracing structure: this is a list of textures of length n, and then a list of materials of length n-1. So far a single layer, we define a front surface and a back surface (n = 2), and specify the material in between those two surfaces (n-1 = 1). We also specify the width of each material, and the incidence medium (above the first interface) and the transmission medium (below the last interface.\n\nrtstr = rt_structure(textures=[triangle_surf, flat_surf],\n materials = [Si_RT],\n widths=[si('300um')], incidence=Air, transmission=Air)"
+ "text": "In the previous examples, we have considered only single-junction cells. However, a major part of Solcore’s capability lies in modelling multi-junction solar cells. In this example, we will look at a triple junction InGaP/GaAs/Ge cell at 1 Sun and under concentration.\nimport numpy as np\nimport os\nimport matplotlib.pyplot as plt\n\nfrom solcore import siUnits, material, si\nfrom solcore.solar_cell import SolarCell\nfrom solcore.structure import Junction, Layer\nfrom solcore.solar_cell_solver import solar_cell_solver\nfrom solcore.light_source import LightSource\nfrom solcore.absorption_calculator import search_db\n\nwl = np.linspace(300, 1850, 700) * 1e-9\nWe define our light source, the AM1.5G spectrum, which will be used for I-V calculations (not under concentration):\nlight_source = LightSource(source_type='standard', x=wl, version='AM1.5g')\nNow we need to build the solar cell layer by layer.\nNote: you need to have downloaded the refractiveindex.info database for these to work. See Example 2a.\nMgF2_pageid = search_db(os.path.join(\"MgF2\", \"Rodriguez-de Marcos\"))[0][0];\nZnS_pageid = search_db(os.path.join(\"ZnS\", \"Querry\"))[0][0];\nMgF2 = material(str(MgF2_pageid), nk_db=True)();\nZnS = material(str(ZnS_pageid), nk_db=True)();\n\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\n1 results found.\npageid shelf book page filepath hasrefractive hasextinction rangeMin rangeMax points\n234 main MgF2 Rodriguez-de_Marcos main/MgF2/Rodriguez-de Marcos.yml 1 1 0.0299919 2.00146 960\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\n1 results found.\npageid shelf book page filepath hasrefractive hasextinction rangeMin rangeMax points\n623 main ZnS Querry main/ZnS/Querry.yml 1 1 0.22 166.6667 312\nTo minimize front surface reflection, we use a four-layer anti-reflection coating (ARC):\nARC = [Layer(si(\"100nm\"), MgF2), Layer(si(\"15nm\"), ZnS), Layer(si(\"15nm\"), MgF2), Layer(si(\"50nm\"), ZnS)]"
},
{
- "objectID": "solar-cell-simulation/notebooks/4a-textured_Si_cell.html#running-ray-tracing-calculation",
- "href": "solar-cell-simulation/notebooks/4a-textured_Si_cell.html#running-ray-tracing-calculation",
- "title": "Example 4a: Textured Si cell",
- "section": "Running ray-tracing calculation",
- "text": "Running ray-tracing calculation\nRun the calculation, if calc was set to True, otherwise load the results. We save the reflection, transmission, and total absorption in an array called result_RAT and the absorption profile as profile_rt.\n\nif calc:\n # This executes if calc = True (set at the top of the script): actually run the ray-tracing:\n result = rtstr.calculate_profile(options)\n\n # Put the results (Reflection, front surface reflection, transmission, absorption in the Si) in an array:\n result_RAT = np.vstack((options['wavelengths']*1e9,\n result['R'], result['R0'], result['T'], result['A_per_layer'][:,0])).T\n\n # absorption profile:\n profile_rt = result['profile']\n\n # save the results:\n np.savetxt(os.path.join(\"results\", \"rayflare_fullrt_300um_2umpyramids_300_1200nm.txt\"), result_RAT)\n np.savetxt(os.path.join(\"results\", \"rayflare_fullrt_300um_2umpyramids_300_1200nm_profile.txt\"), result['profile'])\n\nelse:\n # If calc = False, load results from previous run.\n result_RAT = np.loadtxt(os.path.join(\"results\", \"rayflare_fullrt_300um_2umpyramids_300_1200nm.txt\"))\n profile_rt = np.loadtxt(os.path.join(\"results\", \"rayflare_fullrt_300um_2umpyramids_300_1200nm_profile.txt\"))\n\nPLOT 1: results of ray-tracing from RayFlare and PVLighthouse, showing the reflection, absorption and transmission.\n\nplt.figure()\nplt.plot(result_RAT[:,0], result_RAT[:,1], '-o', color=pal[0], label=r'R$_{total}$', fillstyle='none')\nplt.plot(result_RAT[:,0], result_RAT[:,2], '-o', color=pal[1], label=r'R$_0$', fillstyle='none')\nplt.plot(result_RAT[:,0], result_RAT[:,3], '-o', color=pal[2], label=r'T', fillstyle='none')\nplt.plot(result_RAT[:,0], result_RAT[:,4], '-o', color=pal[3], label=r'A', fillstyle='none')\nplt.plot(PVlighthouse[:, 0], PVlighthouse[:, 2], '--', color=pal[0])\nplt.plot(PVlighthouse[:, 0], PVlighthouse[:, 9], '--', color=pal[2])\nplt.plot(PVlighthouse[:, 0], PVlighthouse[:, 3], '--', color=pal[1])\nplt.plot(PVlighthouse[:, 0], PVlighthouse[:, 5], '--', color=pal[3])\nplt.plot(-1, -1, '-ok', label='RayFlare')\nplt.plot(-1, -1, '--k', label='PVLighthouse')\nplt.xlabel('Wavelength (nm)')\nplt.ylabel('R / A / T')\nplt.ylim(0, 1)\nplt.xlim(300, 1200)\nplt.legend()\nplt.title(\"(1) R/A/T for pyramid-textured Si, calculated with RayFlare and PVLighthouse\")\nplt.show()"
+ "objectID": "solar-cell-simulation/notebooks/3a-triple_junction.html#top-cell-gainp",
+ "href": "solar-cell-simulation/notebooks/3a-triple_junction.html#top-cell-gainp",
+ "title": "Example 3a: Triple junction cell",
+ "section": "Top cell: GaInP",
+ "text": "Top cell: GaInP\nNow we build the top cell, which requires the n and p sides of GaInP and a window layer. We also add some extra parameters needed for the calculation which are not included in the materials database, such as the minority carriers diffusion lengths.\n\nAlInP = material(\"AlInP\")\nInGaP = material(\"GaInP\")\nwindow_material = AlInP(Al=0.52)\n\ntop_cell_n_material = InGaP(In=0.49, Nd=siUnits(2e18, \"cm-3\"), hole_diffusion_length=si(\"200nm\"))\ntop_cell_p_material = InGaP(In=0.49, Na=siUnits(1e17, \"cm-3\"), electron_diffusion_length=si(\"1um\"))"
},
{
- "objectID": "solar-cell-simulation/notebooks/4a-textured_Si_cell.html#using-optical-results-in-solcore",
- "href": "solar-cell-simulation/notebooks/4a-textured_Si_cell.html#using-optical-results-in-solcore",
- "title": "Example 4a: Textured Si cell",
- "section": "Using optical results in Solcore",
- "text": "Using optical results in Solcore\nSo far, we have done a purely optical calculation; however, if we want to use this information to do an EQE or IV calculation, we can, by using the ability of Solcore to accept external optics data (we used this in Example 1a already). To use Solcore’s device simulation capabilities (QE/IV), we need to create a function which gives the depth-dependent absorption profile. The argument of the function is the position (in m) in the cell, which can be an array, and the function returns an array with the absorption at these depths at every wavelength with dimensions (n_wavelengths, n_positions).\nRayFlare has the make_absorption_function to automatically make this function, as required by Solcore, from RayFlare’s output data. diff_absorb_fn here is the function we need to pass to Solcore (so it is not an array of values!). We need to provide the profile data, the structure that was being simulated, user options and specify whether we used the angular redistribution matrix method (which in this case we did not, so we set matrix_method=False; see [Example 6a]](6a-multiscale_models.ipynb) for a similar example which does use this method).\n\nposition, diff_absorb_fn = make_absorption_function(profile_rt, rtstr, options, matrix_method=False)\n\nNow we feed this into Solcore; we will define a solar cell model using the depletion approximation (see Example 1c).\nWe need a p-n junction; we make sure the total width of the p-n junction is equal to the width of the Si used above in the ray-tracing calculation (rtrst.widths[0]).\n\nSi_base = material(\"Si\")\n\nn_material_Si_width = si(\"500nm\")\np_material_Si_width = rtstr.widths[0] - n_material_Si_width\n\nn_material_Si = Si_base(Nd=si(1e21, \"cm-3\"), hole_diffusion_length=si(\"10um\"),\n electron_mobility=50e-4, relative_permittivity=11.68)\np_material_Si = Si_base(Na=si(1e16, \"cm-3\"), electron_diffusion_length=si(\"290um\"),\n hole_mobility=400e-4, relative_permittivity=11.68)\n\nOptions for Solcore (note that these are separate from the RayFlare options we set above!):\n\noptions_sc = defaults_solcore\noptions_sc.optics_method = \"external\"\noptions_sc.position = np.arange(0, rtstr.width, options.depth_spacing)\noptions_sc.light_iv = True\noptions_sc.wavelength = wl\noptions_sc.theta = options.theta_in*180/np.pi\nV = np.linspace(0, 1, 200)\noptions_sc.voltages = V\n\nMake the solar cell, passing the absorption function we made above, and the reflection (an array with the R value at each wavelength), and calculate the QE and I-V characteristics.\n\nsolar_cell = SolarCell(\n [\n Junction([Layer(width=n_material_Si_width, material=n_material_Si, role='emitter'),\n Layer(width=p_material_Si_width, material=p_material_Si, role='base')],\n sn=1, sp=1, kind='DA')\n ],\n external_reflected=result_RAT[:,1],\n external_absorbed=diff_absorb_fn)\n\nsolar_cell_solver(solar_cell, 'qe', options_sc)\nsolar_cell_solver(solar_cell, 'iv', options_sc)\n\nSolving optics of the solar cell...\nSolving QE of the solar cell...\nSolving optics of the solar cell...\nAlready calculated reflection, transmission and absorption profile - not recalculating. Set recalculate_absorption to True in the options if you want absorption to be calculated again.\nSolving IV of the junctions...\nSolving IV of the tunnel junctions...\nSolving IV of the total solar cell...\n\n\nPLOT 2: EQE and absorption of Si cell with optics calculated through ray-tracing\n\nplt.figure()\nplt.plot(wl*1e9, solar_cell.absorbed, 'k-', label='Absorbed (integrated)')\nplt.plot(wl*1e9, solar_cell[0].eqe(wl), 'r-', label='EQE')\nplt.plot(wl*1e9, result_RAT[:,4], 'r--', label='Absorbed - RT')\nplt.ylim(0,1)\nplt.legend()\nplt.xlabel('Wavelength (nm)')\nplt.ylabel('R/A')\nplt.title(\"(2) EQE/absorption from electrical model\")\nplt.show()\n\n\n\n\nPLOT 3: Light IV of Si cell with optics calculated through ray-tracing\n\nplt.figure()\nplt.plot(V, -solar_cell[0].iv(V), 'r')\nplt.ylim(-20, 400)\nplt.xlim(0, 0.8)\nplt.legend()\nplt.ylabel('Current (A/m$^2$)')\nplt.xlabel('Voltage (V)')\nplt.title(\"(3) IV characteristics\")\nplt.show()\n\nNo artists with labels found to put in legend. Note that artists whose label start with an underscore are ignored when legend() is called with no argument."
+ "objectID": "solar-cell-simulation/notebooks/3a-triple_junction.html#middle-cell-gaas",
+ "href": "solar-cell-simulation/notebooks/3a-triple_junction.html#middle-cell-gaas",
+ "title": "Example 3a: Triple junction cell",
+ "section": "Middle cell: GaAs",
+ "text": "Middle cell: GaAs\n\nGaAs = material(\"GaAs\")\n\nmid_cell_n_material = GaAs(Nd=siUnits(3e18, \"cm-3\"), hole_diffusion_length=si(\"500nm\"))\nmid_cell_p_material = GaAs(Na=siUnits(1e17, \"cm-3\"), electron_diffusion_length=si(\"5um\"))"
},
{
- "objectID": "solar-cell-simulation/notebooks/6a-multiscale_models.html",
- "href": "solar-cell-simulation/notebooks/6a-multiscale_models.html",
- "title": "Example 6a: Silicon HIT cell",
- "section": "",
- "text": "In Example 4a, we looked at a solar cell made of a single layer of Si with pyramidal texturing. In reality, a solar cell will have a more complicated structure with thin layers deposited on the front side to act as e.g. selective transport layers for carriers. This adds a layer of complication to the ray-tracing process, because we can no longer rely on the Fresnel equations to calculate the angle and wavelength-dependent reflection and transmission probabilities; we might get absorption in the surface layers, and we need to take into account interference in the surface layers. To do this, we can combine ray-tracing and the transfer-matrix method; we can calculate the reflection, absorption and transmission probabilities using TMM, and use those probabilities in our ray-tracing calculations. In RayFlare, this functionality is implemented as part of the angular redistribution matrix functionality.\nThis example is (loosely) based on the simulations done for this paper which looks at the absorptivity/emissivity of silicon heterojunction (HJT or HIT) cells, although here we will only look at the usual wavelengths for the photovoltaic operation of silicon solar cells rather than the infrared.\nfrom solcore import material, si\nfrom solcore.light_source import LightSource\nfrom solcore.constants import q\nfrom solcore.solar_cell import SolarCell, Layer, Junction\nfrom solcore.solar_cell_solver import default_options as defaults_solcore, solar_cell_solver\n\nfrom rayflare.textures import regular_pyramids\nfrom rayflare.structure import Interface, BulkLayer, Structure\nfrom rayflare.matrix_formalism import calculate_RAT, process_structure\nfrom rayflare.options import default_options\nfrom rayflare.utilities import make_absorption_function\n\nimport numpy as np\nimport os\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom scipy.ndimage.filters import gaussian_filter1d\n\nfrom cycler import cycler"
+ "objectID": "solar-cell-simulation/notebooks/3a-triple_junction.html#bottom-cell-ge",
+ "href": "solar-cell-simulation/notebooks/3a-triple_junction.html#bottom-cell-ge",
+ "title": "Example 3a: Triple junction cell",
+ "section": "Bottom cell: Ge",
+ "text": "Bottom cell: Ge\n\nGe = material(\"Ge\")\n\nbot_cell_n_material = Ge(Nd=siUnits(2e18, \"cm-3\"), hole_diffusion_length=si(\"800nm\"), hole_mobility=0.01)\nbot_cell_p_material = Ge(Na=siUnits(1e17, \"cm-3\"), electron_diffusion_length=si(\"50um\"), electron_mobility=0.1)"
},
{
- "objectID": "solar-cell-simulation/notebooks/6a-multiscale_models.html#setting-up",
- "href": "solar-cell-simulation/notebooks/6a-multiscale_models.html#setting-up",
- "title": "Example 6a: Silicon HIT cell",
+ "objectID": "solar-cell-simulation/notebooks/3a-triple_junction.html#putting-the-cell-together",
+ "href": "solar-cell-simulation/notebooks/3a-triple_junction.html#putting-the-cell-together",
+ "title": "Example 3a: Triple junction cell",
+ "section": "Putting the cell together",
+ "text": "Putting the cell together\nAnd, finally, we put everything together, adding also the surface recombination velocities. We also add some shading due to the metallisation of the cell = 5%, and a finite series resistance.\n\nsolar_cell = SolarCell(\n ARC +\n [\n Junction([Layer(si(\"20nm\"), material=window_material, role='window'),\n Layer(si(\"100nm\"), material=top_cell_n_material, role='emitter'),\n Layer(si(\"560nm\"), material=top_cell_p_material, role='base'),\n ], sn=1, sp=1, kind='DA'),\n Junction([Layer(si(\"200nm\"), material=mid_cell_n_material, role='emitter'),\n Layer(si(\"3000nm\"), material=mid_cell_p_material, role='base'),\n ], sn=1, sp=1, kind='DA'),\n Junction([Layer(si(\"400nm\"), material=bot_cell_n_material, role='emitter'),\n Layer(si(\"100um\"), material=bot_cell_p_material, role='base'),\n ], sn=1, sp=1, kind='DA'),\n ], shading=0.05, R_series=2e-6)"
+ },
+ {
+ "objectID": "solar-cell-simulation/notebooks/3a-triple_junction.html#setting-the-depth-spacing",
+ "href": "solar-cell-simulation/notebooks/3a-triple_junction.html#setting-the-depth-spacing",
+ "title": "Example 3a: Triple junction cell",
+ "section": "Setting the depth spacing",
+ "text": "Setting the depth spacing\nThe ‘position’ user option in Solcore determines at which z-points the absorption profile is calculated. You can specify this is multiple different ways:\n\na vector which specifies each position (in m) at which the depth should be calculated\na single number which specifies the spacing (in m) to generate the position vector, e.g. 1e-9 for 1 nm spacing\na list of numbers which specify the spacing (in m) to be used in each layer. This list can have EITHER the length of the number of individual layers + the number of junctions in the cell object, OR the length of the total number of individual layers including layers inside junctions.\n\nHere we use the final options, setting the spacing to use per junction/layer. We use 0.1 nm for all layers except the final layer, the Ge, where we use 10 nm.\n\nposition = len(solar_cell) * [0.1e-9]\nposition[-1] = 10e-9 # Indexing with -1 in a Python list/array gives you the last element\n\nNow that we have made the cell and set the options, we calculate and plot the EQE.\nPLOT 1: EQE of a triple junction cell, comparing TMM and BL optical methods\n\nplt.figure()\n\n# First calculate with TMM optical method\nsolar_cell_solver(solar_cell, 'qe', user_options={'wavelength': wl, 'optics_method': \"TMM\",\n 'position': position, 'recalculate_absorption': True})\n\nplt.plot(wl * 1e9, solar_cell[4].eqe(wl) * 100, 'b', label='GaInP (TMM)')\nplt.plot(wl * 1e9, solar_cell[5].eqe(wl) * 100, 'g', label='InGaAs (TMM)')\nplt.plot(wl * 1e9, solar_cell[6].eqe(wl) * 100, 'r', label='Ge (TMM)')\nplt.plot(wl * 1e9, 100 * (1 - solar_cell.reflected), 'k--', label='1-R (TMM)')\n\n# Recalculate with simple Beer-Lambert (BL) law absorption to compare\nsolar_cell_solver(solar_cell, 'qe', user_options={'wavelength': wl, 'optics_method': \"BL\",\n 'position': position, 'recalculate_absorption': True})\n\nplt.plot(wl * 1e9, solar_cell[4].eqe(wl) * 100, 'b--', alpha=0.5, label='GaInP (BL)')\nplt.plot(wl * 1e9, solar_cell[5].eqe(wl) * 100, 'g--', alpha=0.5, label='InGaAs (BL)')\nplt.plot(wl * 1e9, solar_cell[6].eqe(wl) * 100, 'r--', alpha=0.5, label='Ge (BL)')\nplt.legend()\nplt.ylim(0, 100)\nplt.ylabel('EQE (%)')\nplt.xlabel('Wavelength (nm)')\nplt.tight_layout()\nplt.title(\"(1) EQE and absorption for 3J cell using TMM and BL optical methods\")\nplt.show()\n\nSolving optics of the solar cell...\nTreating layer(s) 10 incoherently\nCalculating RAT...\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\nMaterial main/MgF2/Rodriguez-de Marcos.yml loaded.\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\nMaterial main/MgF2/Rodriguez-de Marcos.yml loaded.\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\nMaterial main/ZnS/Querry.yml loaded.\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\nMaterial main/ZnS/Querry.yml loaded.\nCalculating absorption profile...\nSolving QE of the solar cell...\nSolving optics of the solar cell...\nSolving QE of the solar cell...\n\n\n/Users/phoebe/Documents/develop/solcore5/solcore/analytic_solar_cells/depletion_approximation.py:617: RuntimeWarning: invalid value encountered in true_divide\n iqe = j_sc / current_absorbed\n/Users/phoebe/Documents/develop/solcore5/solcore/analytic_solar_cells/depletion_approximation.py:617: RuntimeWarning: invalid value encountered in true_divide\n iqe = j_sc / current_absorbed\n\n\n\n\n\nWe see that the BL absorption is higher everywhere, because it does not include any front-surface reflection. In the TMM calculation, we see interference fringes and some front-surface reflection (though due to the 4-layer ARC, the reflection is quite low everywhere).\nNow we calculate and plot the light IV under the AM1.5G spectrum, using the TMM optical method\nPLOT 2: Light IV for triple-junction cell\n\nV = np.linspace(0, 3, 300)\nsolar_cell_solver(solar_cell, 'iv', user_options={'voltages': V, 'light_iv': True,\n 'wavelength': wl, 'mpp': True,\n 'light_source': light_source,\n 'recalculate_absorption': True,\n 'optics_method': \"TMM\"})\n\nplt.figure()\nplt.plot(V, solar_cell.iv['IV'][1], 'k', linewidth=3, label='Total')\nplt.plot(V, -solar_cell[4].iv(V), 'b', label='GaInP')\nplt.plot(V, -solar_cell[5].iv(V), 'g', label='InGaAs')\nplt.plot(V, -solar_cell[6].iv(V), 'r', label='Ge')\nplt.text(1.4, 220, 'Efficieny (%): ' + str(np.round(solar_cell.iv['Eta'] * 100, 1)))\nplt.text(1.4, 200, 'FF (%): ' + str(np.round(solar_cell.iv['FF'] * 100, 1)))\nplt.text(1.4, 180, r'V$_{oc}$ (V): ' + str(np.round(solar_cell.iv[\"Voc\"], 2)))\nplt.text(1.4, 160, r'I$_{sc}$ (A/m$^2$): ' + str(np.round(solar_cell.iv[\"Isc\"], 2)))\n\nplt.legend()\nplt.ylim(0, 250)\nplt.xlim(0, 3)\nplt.ylabel('Current (A/m$^2$)')\nplt.xlabel('Voltage (V)')\nplt.title(\"(2) IV characteristics of 3J cell\")\n\nplt.show()\n\nSolving optics of the solar cell...\nTreating layer(s) 10 incoherently\nCalculating RAT...\nCalculating absorption profile...\nSolving IV of the junctions...\nSolving IV of the tunnel junctions...\nSolving IV of the total solar cell..."
+ },
+ {
+ "objectID": "solar-cell-simulation/notebooks/3a-triple_junction.html#cell-behaviour-under-concentration",
+ "href": "solar-cell-simulation/notebooks/3a-triple_junction.html#cell-behaviour-under-concentration",
+ "title": "Example 3a: Triple junction cell",
+ "section": "Cell behaviour under concentration",
+ "text": "Cell behaviour under concentration\nMulti-junction cells are often used in concetrator PV applications. Here, we look at the effect of increasing the concentration on the \\(V_{oc}\\), \\(J_{sc}\\) and the efficiency \\(\\eta\\). Note that in order to reproduce the behaviour we see in real concentrator cells (initial increase in efficiency due to increased \\(V_{oc}\\), with a reduction in efficiency at very high concentrations due to a decrease in fill factor due to series resistance), we need to specify a series resistance in the cell definition above; if not, the simulated efficiency would continue to increase.\nWe consider concentrations between 1x and 3000x, linearly spaced on a log scale:\n\nconcentration = np.linspace(np.log(1), np.log(3000), 20)\nconcentration = np.exp(concentration)\n\nCreate empty arrays to store the data (this is preferable to simply appending data in a loop since it pre-allocates the memory needed to store the arrays):\n\nEffs = np.empty_like(concentration) # creates an empty array with the same shape as the concentration array\nVocs = np.empty_like(concentration)\nIscs = np.empty_like(concentration)\n\nV = np.linspace(0, 3.5, 300)\n\nLoop through the concentrations. We use only the direct spectrum (AM1.5D) since diffuse light will not be concentrated:\n\nfor i1, conc in enumerate(concentration):\n\n # We make a light source with the concentration being considered. We also use AM1.5D (direct only) rather than AM1.5G\n # (direct + diffuse):\n light_conc = LightSource(source_type='standard', x=wl, version='AM1.5d', concentration=conc)\n\n solar_cell_solver(solar_cell, 'iv', user_options={'voltages': V, 'light_iv': True,\n 'wavelength': wl, 'mpp': True,\n 'light_source': light_conc});\n\n # Save the calculated values in the arrays:\n Effs[i1] = solar_cell.iv[\"Eta\"] * 100\n Vocs[i1] = solar_cell.iv[\"Voc\"]\n Iscs[i1] = solar_cell.iv[\"Isc\"]\n\nPLOT 3: Efficiency, open-circuit voltage and short-circuit current at different concentrations for the 3J cell\n\nplt.figure(figsize=(10, 3))\nplt.subplot(131)\nplt.semilogx(concentration, Effs, '-o')\nplt.ylabel('Efficiency (%)')\nplt.xlabel('Concentration')\nplt.title(\"(3) Efficiency, V$_{oc}$ and J$_{sc}$ vs. concentration for 3J cell\")\n\nplt.subplot(132)\nplt.semilogx(concentration, Vocs, '-o')\nplt.ylabel(r'V$_{OC}$ (V)')\nplt.xlabel('Concentration')\n\nplt.subplot(133)\nplt.plot(concentration, Iscs / 10000, '-o')\nplt.ylabel(r'J$_{SC}$ (A/cm$^2$)')\nplt.xlabel('Concentration')\nplt.tight_layout()\nplt.show()"
+ },
+ {
+ "objectID": "solar-cell-simulation/notebooks/1b-simple_cell.html",
+ "href": "solar-cell-simulation/notebooks/1b-simple_cell.html",
+ "title": "Example 1b: Basic cell optics",
+ "section": "",
+ "text": "In this script, we will build on the TMM model from example 1(a) and look at the effects of interference.\nimport numpy as np\nimport matplotlib.pyplot as plt\n\nfrom solcore import material, si\nfrom solcore.solar_cell import Layer\nfrom solcore.absorption_calculator import calculate_rat, OptiStack\nimport seaborn as sns"
+ },
+ {
+ "objectID": "solar-cell-simulation/notebooks/1b-simple_cell.html#setting-up",
+ "href": "solar-cell-simulation/notebooks/1b-simple_cell.html#setting-up",
+ "title": "Example 1b: Basic cell optics",
"section": "Setting up",
- "text": "Setting up\nWe add some new materials to Solcore’s database from data files - you only need to add these once, after that you can comment these lines out.\n\nfrom solcore.material_system import create_new_material\n\ncreate_new_material('aSi_i', os.path.join(\"data\", \"model_i_a_silicon_n.txt\"),\n os.path.join(\"data\", \"model_i_a_silicon_k.txt\"))\ncreate_new_material('ITO_measured', os.path.join(\"data\", \"front_ITO_n.txt\"),\n os.path.join(\"data\", \"front_ITO_k.txt\"))\n\nSyntaxError: invalid syntax (2638323969.py, line 3)\n\n\nSetting user options. Several of these (wavelength, number of rays, nx, ny) have been encountered in previous examples. However, because we are using the angular redistribution matrix method for the first time, there are some new ones. Please see RayFlare’s documentation for more detailed information.\n\nproject_name: When we run the simulation, we will generate large matrices which will eventually be multiplied together. These have to be stored somewhere. This is the name of the folder where they will be stored.\nn_theta_bins: The number of polar angle bins to divide the hemisphere into when calculating the redistribution matrices.\nI_thresh: Threshold intensity at which to stop matrix multiplication (i.e. when almost all the light has been reflected, transmitted or absorbed)\nbulk_profile: True or False; whether to calculate the absorption profile in the bulk material (Si in this case).\n\n\nwavelengths = np.linspace(300, 1200, 80)*1e-9\n\noptions = default_options()\noptions.wavelengths = wavelengths\noptions.project_name = 'HIT_example'\noptions.n_rays = 10000 # Reduce this (or the number of wavelengths) to speed up the example! Note that this is the TOTAL number of rays (all angle bins) per wavelength\noptions.n_theta_bins = 20\noptions.nx = 5\noptions.ny = 5\noptions.I_thresh = 0.005\noptions.bulk_profile = True\n\nWe now define the materials and layer structures for the front and rear surfaces. In this case, the front and rear layers are the materials which will be deposited on top of the pyramids (amorphous silicon and ITO); the crystalline silicon itself will be the bulk material which connects the two interfaces.\n\nSi = material('Si')()\nAir = material('Air')()\nITO = material('ITO_measured')()\n\nAg = material('Ag')()\naSi = material('aSi_i')()\n\nfront_materials = [Layer(80e-9, ITO), Layer(13e-9, aSi)]\nback_materials = [Layer(13e-9, aSi), Layer(240e-9, ITO)]\n\nNow we define the front and rear surfaces using the Interface class. Whether pyramids are upright or inverted is relative to front incidence, so if the same etch is applied to both sides of a slab of silicon, one surface will have ‘upright’ pyramids and the other side will have ‘not upright’ (inverted) pyramids in the model. The arguments required for Interface depend on the optical method which is being used; in this case, we must provide the surface texture, layer stack, and whether the layers are to be treated coherently (i.e. affected by thin-film interference). We also have to give the surfaces a name; as with the project name, this is to store the matrices which will be calculated.\n\nsurf = regular_pyramids(elevation_angle=55, upright=True) # elevation angle is relative to horizontal plane\nsurf_back = regular_pyramids(elevation_angle=55, upright=False)\n\nfront_surf = Interface('RT_TMM', texture=surf, layers=front_materials, name='HIT_front', coherent=True)\nback_surf = Interface('RT_TMM', texture=surf_back, layers=back_materials, name='HIT_back', coherent=True)\n\nbulk_Si = BulkLayer(170e-6, Si, name='Si_bulk') # bulk thickness in m\n\nFinally, we build the whole structure inside RayFlare’s Structure class, also specifying the incidence medium (above the cell) and the transmission medium (below the cell).\n\nSC = Structure([front_surf, bulk_Si, back_surf], incidence=Air, transmission=Ag)"
+ "text": "Setting up\nFirst, let’s define some materials:\n\nSi = material(\"Si\")\nSiN = material(\"Si3N4\")()\nAg = material(\"Ag\")()\n\nNote the second set of brackets (or lack thereof). The Solcore material system essentially operates in two stages; we first call the material function with the name of the material we want to use, for example Si = material(“Si”), which creates a general Python class corresponding to that material. We then call this class to specify further details, such as the temperature, doping level, or alloy composition (where relavant). This happens below when defining Si_n and Si_p; both are use the Si class defined above, and adding further details to the material. For the definitions of SiN and Ag above, we do both steps in a single line, hence the two sets of brackets.\n\nSi_n = Si(Nd=si(\"1e21cm-3\"), hole_diffusion_length=si(\"10um\"))\nSi_p = Si(Na=si(\"1e16cm-3\"), electron_diffusion_length=si(\"400um\"))\n\nTo look at the effect of interference in the Si layer at different thicknesses, we make a list of thicknesses to test (evenly spaced on a log scale from 400 nm to 300 um):\n\nSi_thicknesses = np.linspace(np.log(0.4e-6), np.log(300e-6), 8)\nSi_thicknesses = np.exp(Si_thicknesses)\n\nwavelengths = si(np.linspace(300, 1200, 400), \"nm\")\n\noptions = {\n \"recalculate_absorption\": True,\n \"optics_method\": \"TMM\",\n \"wavelength\": wavelengths\n }\n\nMake a color palette using the seaborn package to make the plots look nicer\n\ncolors = sns.color_palette('rocket', n_colors=len(Si_thicknesses))\ncolors.reverse()\n\ncreate an ARC layer:\n\nARC_layer = Layer(width=si('75nm'), material=SiN)"
},
{
- "objectID": "solar-cell-simulation/notebooks/6a-multiscale_models.html#generating-results",
- "href": "solar-cell-simulation/notebooks/6a-multiscale_models.html#generating-results",
- "title": "Example 6a: Silicon HIT cell",
- "section": "Generating results",
- "text": "Generating results\nNow we are ready to start running calculations. This happens in two phases:\n\nCall process_structure to check each surface in the structure, and generate angular redistribution matrices (if it does not find existing ones)\nCall calculate_rat to run the matrix multiplication and generate reflection, transmission and absorption results\n\n\nprocess_structure(SC, options, save_location=\"current\")\n# save_location = current means that the folder with the redistribution matrix will be created in the current working directory. By default, it is saved in a folder called\n# RayFlare_results in your home directory (~).\nresults = calculate_RAT(SC, options, save_location=\"current\")\n\nMaking lookuptable for element 0 in structure\nMaking lookuptable for element 2 in structure\nRay tracing with TMM lookup table for element 0 in structure\nCalculating matrix only for incidence theta/phi\nRay tracing with TMM lookup table for element 2 in structure\nAfter iteration 1 : maximum power fraction remaining = 0.5629448135186488\nAfter iteration 2 : maximum power fraction remaining = 0.3425270607560974\nAfter iteration 3 : maximum power fraction remaining = 0.20990358983841642\nAfter iteration 4 : maximum power fraction remaining = 0.12862942930538473\nAfter iteration 5 : maximum power fraction remaining = 0.0788271578176077\nAfter iteration 6 : maximum power fraction remaining = 0.04832164906096151\nAfter iteration 7 : maximum power fraction remaining = 0.029625034965812196\nAfter iteration 8 : maximum power fraction remaining = 0.018162348111822744\nAfter iteration 9 : maximum power fraction remaining = 0.011134523091393584\nAfter iteration 10 : maximum power fraction remaining = 0.006826006659534447\nAfter iteration 11 : maximum power fraction remaining = 0.004184675817775243\n\n\nThe structure of the results returned by calculate_RAT is quite complicated; it is explained on this page.\n\nRAT = results[0]\nresults_per_pass = results[1]\n\nR_per_pass = np.sum(results_per_pass['r'][0], axis=2)\nR_0 = R_per_pass[0]\nR_escape = np.sum(R_per_pass[1:, :], axis=0)\n\n# results_per_pass: sum over passes to get overall absorption in each layer.\nresults_per_layer_front = np.sum(results_per_pass['a'][0], axis=0)\n\nresults_per_layer_back = np.sum(results_per_pass['a'][1], axis=0)\n\nTo get the maximum current we could achieve based on these optical results, we calculate the photogenerated current using the AM1.5G spectrum.\n\nspectr_flux = LightSource(source_type='standard', version='AM1.5g', x=wavelengths,\n output_units='photon_flux_per_m', concentration=1).spectrum(wavelengths)[1]\n\nJph_Si = q * np.trapz(RAT['A_bulk'][0] * spectr_flux, wavelengths)/10 # mA/cm2\n\nprint(\"Photogenerated current in Si = %.1f mA/cm2\" % Jph_Si)\n\nPhotogenerated current in Si = 37.5 mA/cm2"
+ "objectID": "solar-cell-simulation/notebooks/1b-simple_cell.html#effect-of-si-thickness",
+ "href": "solar-cell-simulation/notebooks/1b-simple_cell.html#effect-of-si-thickness",
+ "title": "Example 1b: Basic cell optics",
+ "section": "Effect of Si thickness",
+ "text": "Effect of Si thickness\nNow we are going to loop through the different Si thicknesses generated above, and create a simple solar cell-like structure. Because we will only do an optical calculation, we don’t need to define a junction and can just make a simple stack of layers.\nWe then calculate reflection, absorption and transmission (RAT) for two different situations: 1. a fully coherent stack 2. assuming the silicon layer is incoherent. This means that light which enters the Si layer cannot interfere with itself, but light in the ARC layer can still show interference. In very thick layers (much thicker than the wavelength of light being considered) this is likely to be more physically accurate because real light does not have infinite coherence length; i.e. if you measured wavelength-dependent transmission or reflection of a Si wafer hundreds of microns thick you would not expect to see interference fringes.\nPLOT 1\n\nplt.figure()\n\nfor i1, Si_t in enumerate(Si_thicknesses):\n\n base_layer = Layer(width=Si_t, material=Si_p) # silicon layer\n solar_cell = OptiStack([ARC_layer, base_layer]) # OptiStack (optical stack) to feed into calculate_rat function\n\n # Coherent calculation:\n RAT_c = calculate_rat(solar_cell, wavelengths*1e9, no_back_reflection=False) # coherent calculation\n # For historical reasons, Solcore's default setting is to ignore reflection at the back of the cell (i.e. at the\n # interface between the final material in the stack and the substrate). Hence we need to tell the calculate_rat\n # function NOT to ignore this reflection (no_back_reflection=False).\n\n # Calculation assuming no interference in the silicon (\"incoherent\"):\n RAT_i = calculate_rat(solar_cell, wavelengths*1e9, no_back_reflection=False,\n coherent=False, coherency_list=['c', 'i']) # partially coherent: ARC is coherent, Si is not\n\n # Plot the results:\n plt.plot(wavelengths*1e9, RAT_c[\"A\"], color=colors[i1], label=str(round(Si_t*1e6, 1)), alpha=0.7)\n plt.plot(wavelengths*1e9, RAT_i[\"A\"], '--', color=colors[i1])\n\nplt.legend(title=r\"Thickness ($\\mu$m)\")\nplt.xlim(300, 1300)\nplt.ylim(0, 1.02)\nplt.ylabel(\"Absorption\")\nplt.title(\"(1) Absorption in Si with varying thickness\")\nplt.show()\n\n\n\n\nWe can see that the coherent calculations (solid lines) show clear interference fringes which depend on the Si thickness. The incoherent calculations do not have these fringes and seem to lie around the average of the interference fringes. For both sets of calculations, we see increasing absorption as the Si gets thicker, as expected."
},
{
- "objectID": "solar-cell-simulation/notebooks/6a-multiscale_models.html#plotting",
- "href": "solar-cell-simulation/notebooks/6a-multiscale_models.html#plotting",
- "title": "Example 6a: Silicon HIT cell",
- "section": "Plotting",
- "text": "Plotting\nNow, we plot where all incident light goes: reflection, absorption in each layer, and transmission into the substrate in the simulation. Note that we set the substrate as Ag for the simulation; although in the actual device the Ag is not infinitely thick, in practice it is thick enough that all light which enters the Ag will be absorbed there so we can treat is as the final material.\nPLOT 1: Reflection and absorption in the Si HIT cell\n\n# Stack results for plotting\nallres = np.hstack((RAT['T'].T, results_per_layer_back,\n RAT['A_bulk'].T, results_per_layer_front)).T\n\n# Create colors for plotting\npal = sns.cubehelix_palette(allres.shape[0] + 1, start=.5, rot=-.9)\npal.reverse()\n\n# Update default colours used by matplotlib\ncols = cycler('color', pal)\nparams = {'axes.prop_cycle': cols}\nplt.rcParams.update(params)\n\n# plot total R, A, T\nfig = plt.figure(figsize=(6,4))\nax = plt.subplot(111)\nax.plot(options['wavelengths']*1e9, R_escape + R_0, '-k', label=r'$R_{total}$')\nax.plot(options['wavelengths']*1e9, R_0, '--k', label=r'$R_0$')\nax.stackplot(options['wavelengths']*1e9, allres,\n labels=['Ag (transmitted)', 'Back ITO', 'a-Si (back)', 'Bulk Si',\n 'a-Si (front)', 'Front ITO'\n ])\nax.set_xlabel(r'Wavelength ($\\mu$m)')\nax.set_ylabel('Absorption/Emissivity')\nax.set_xlim(min(options['wavelengths']*1e9), max(options['wavelengths']*1e9))\nax.set_ylim(0, 1)\nplt.legend(loc='center left', bbox_to_anchor=(1, 0.5))\nplt.tight_layout()\nplt.show()\n\n\n\n\nWe see that over this wavelength range, most of the absorption happens in the bulk Si, as expected. However, at short wavelengths we see significant absorption in the front surface layers, and at long wavelengths we see absorption in the back layers and Ag. We have plotted both the total reflection \\(R_{total}\\) and \\(R_0\\), which is the light which is lost due to initial reflection at the front surface. Up to around 1000 nm, these lines coincide (total reflection is entirely due to initial front surface reflection). At longer wavelengths, we see that \\(R_0\\) is lower than \\(R_{total}\\), as now a significant fraction of the total reflection is from light which enters the cell, makes at least one full forward and backward pass, and then leaves the cell through the front surface. This becomes significant close to the bandgap as the Si becomes transparent.\nIn the plot above we see stochastic noise due to the ray-tracing simulation; we could increase the number of rays to reduce this, which also increases the computation time. Below, we re-plot the same data with some smoothing.\nPLOT 2: Reflection and absorption in the Si HIT cell, with smoothed data\n\nysmoothed = gaussian_filter1d(np.vstack((allres, RAT[\"R\"])), sigma=2, axis=1)\n\n# plot total R, A, T - smoothed\nfig = plt.figure(figsize=(6,4))\nax = plt.subplot(111)\nax.stackplot(options['wavelengths']*1e9, ysmoothed,\n labels=['Ag (transmitted)', 'Back ITO', 'a-Si (back)', 'Bulk Si',\n 'a-Si (front)', 'Front ITO', 'R'\n ])\nax.set_xlabel(r'Wavelength ($\\mu$m)')\nax.set_ylabel('Absorption/Emissivity')\nax.set_xlim(min(options['wavelengths']*1e9), max(options['wavelengths']*1e9))\nax.set_ylim(0, 1)\nplt.legend(loc='center left', bbox_to_anchor=(1, 0.5))\nplt.tight_layout()\nplt.show()"
+ "objectID": "solar-cell-simulation/notebooks/1b-simple_cell.html#effect-of-reflective-substrate",
+ "href": "solar-cell-simulation/notebooks/1b-simple_cell.html#effect-of-reflective-substrate",
+ "title": "Example 1b: Basic cell optics",
+ "section": "Effect of reflective substrate",
+ "text": "Effect of reflective substrate\nNow we repeat the calculation, but with an Ag substrate under the Si. Previously, we did not specify the substrate and so it was assumed by Solcore to be air (n=1, k=0).\nPLOT 2\n\nplt.figure()\n\nfor i1, Si_t in enumerate(Si_thicknesses):\n\n base_layer = Layer(width=Si_t, material=Si_p)\n\n # As before, but now we specify the substrate to be silver:\n solar_cell = OptiStack([ARC_layer, base_layer], substrate=Ag)\n\n RAT_c = calculate_rat(solar_cell, wavelengths*1e9, no_back_reflection=False)\n RAT_i = calculate_rat(solar_cell, wavelengths*1e9, no_back_reflection=False,\n coherent=False, coherency_list=['c', 'i'])\n plt.plot(wavelengths*1e9, RAT_c[\"A\"], color=colors[i1],\n label=str(round(Si_t*1e6, 1)), alpha=0.7)\n plt.plot(wavelengths*1e9, RAT_i[\"A\"], '--', color=colors[i1])\n\nplt.legend(title=r\"Thickness ($\\mu$m)\")\nplt.xlim(300, 1300)\nplt.ylim(0, 1.02)\nplt.ylabel(\"Absorption\")\nplt.title(\"(2) Absorption in Si with varying thickness (Ag substrate)\")\nplt.show()\n\n\n\n\nWe see that the interference fringes get more prominent in the coherent calculation, due to higher reflection at the rear Si/Ag surface compared to Ag/Air. We also see a slightly boosted absorption at long wavelengths at all thicknesses, again due to improved reflection at the rear surface"
},
{
- "objectID": "solar-cell-simulation/notebooks/6a-multiscale_models.html#device-simulations",
- "href": "solar-cell-simulation/notebooks/6a-multiscale_models.html#device-simulations",
- "title": "Example 6a: Silicon HIT cell",
- "section": "Device simulations",
- "text": "Device simulations\nAs we did in Example 4a, we can now feed the results from RayFlare’s optical calculation into Solcore to run electrical simulations. We generate the absorption profile function, then specify materials and layers for the solar cell structure.\n\nprofile_Si = results[3][0]\nexternal_R = RAT['R'][0, :]\n\npositions, absorb_fn = make_absorption_function([None, profile_Si, None], SC, options, matrix_method=True)\n\nSi_SC = material(\"Si\")\nGaAs_SC = material(\"GaAs\")\nT = 300\n\np_material_Si = Si_SC(T=T, Na=si(1e21, \"cm-3\"), electron_diffusion_length=si(\"10um\"), hole_mobility=50e-4)\nn_material_Si = Si_SC(T=T, Nd=si(1e16, \"cm-3\"), hole_diffusion_length=si(\"290um\"), electron_mobility=400e-4)\n\nAs we noted in Example 4a, we need to specify the user options for Solcore separately (though they should of course be consistent with the options we gave RayFlare above, where relevant!). We set options, create the solar cell structure, and run QE and IV calculations:\n\noptions_sc = defaults_solcore\noptions_sc.optics_method = \"external\"\noptions_sc.position = positions\noptions_sc.light_iv = True\noptions_sc.wavelength = wavelengths\noptions_sc.mpp = True\noptions_sc.theta = options.theta_in*180/np.pi\nV = np.linspace(0, 2.5, 250)\noptions_sc.voltages = V\n\nsolar_cell = SolarCell([Layer(80e-9, ITO),\n Layer(13e-9, aSi),\n Junction([Layer(500e-9, p_material_Si, role=\"emitter\"),\n Layer(bulk_Si.width-500e-9, n_material_Si, role=\"base\")], kind=\"DA\"),\n Layer(13e-9, aSi),\n Layer(240e-9, ITO)],\n external_reflected = external_R,\n external_absorbed = absorb_fn)\n\n\n\nsolar_cell_solver(solar_cell, 'qe', options_sc)\nsolar_cell_solver(solar_cell, 'iv', options_sc)\n\nNow we can plot the results. To check things are consistent, we will plot the total absorption in Si calculated above with RayFlare, and solar_cell.absorbed, which is the result Solcore gives for total absorption. We also plot the EQE, which should be the same as or lower than the absorption.\nPLOT 3: Absorption in the Si and EQE.\n\nplt.figure()\nplt.plot(options['wavelengths']*1e9, RAT[\"A_bulk\"][0], 'r-')\nplt.plot(wavelengths*1e9, solar_cell.absorbed, 'k--', label='Absorbed (integrated)')\nplt.plot(wavelengths*1e9, solar_cell[2].eqe(wavelengths), 'b-', label='Si EQE')\nplt.ylim(0,1)\nplt.legend()\nplt.xlabel('Wavelength (nm)')\nplt.ylabel('R/A')\nplt.show()\n\n\n\n\nPLOT 4: Current-voltage behaviour of the Si cell under illumination\n\nplt.figure()\nplt.plot(V, solar_cell.iv['IV'][1], '-k')\nplt.ylim(-20, 380)\nplt.xlim(0, 0.85)\nplt.ylabel('Current (A/m$^2$)')\nplt.xlabel('Voltage (V)')\nplt.show()"
+ "objectID": "solar-cell-simulation/notebooks/1b-simple_cell.html#effect-of-polarization-and-angle-of-incidence",
+ "href": "solar-cell-simulation/notebooks/1b-simple_cell.html#effect-of-polarization-and-angle-of-incidence",
+ "title": "Example 1b: Basic cell optics",
+ "section": "Effect of polarization and angle of incidence",
+ "text": "Effect of polarization and angle of incidence\nFinally, we look at the effect of incidence angle and polarization of the light hitting the cell.\nPLOT 3\n\nangles = [0, 30, 60, 70, 80, 89] # angles in degrees\n\nARC_layer = Layer(width=si('75nm'), material=SiN)\nbase_layer = Layer(width=si(\"100um\"), material=Si_p)\n\ncolors = sns.cubehelix_palette(n_colors=len(angles))\n\nplt.figure()\n\nfor i1, theta in enumerate(angles):\n\n solar_cell = OptiStack([ARC_layer, base_layer])\n\n RAT_s = calculate_rat(solar_cell, wavelengths*1e9, angle=theta,\n pol='s',\n no_back_reflection=False,\n coherent=False, coherency_list=['c', 'i'])\n RAT_p = calculate_rat(solar_cell, wavelengths*1e9, angle=theta,\n pol='p',\n no_back_reflection=False,\n coherent=False, coherency_list=['c', 'i'])\n\n plt.plot(wavelengths*1e9, RAT_s[\"A\"], color=colors[i1], label=str(round(theta)))\n plt.plot(wavelengths*1e9, RAT_p[\"A\"], '--', color=colors[i1])\n\nplt.legend(title=r\"$\\theta (^\\circ)$\")\nplt.xlim(300, 1300)\nplt.ylim(0, 1.02)\nplt.ylabel(\"Absorption\")\nplt.title(\"(3) Absorption in Si with varying thickness\")\nplt.show()\n\n\n\n\nFor normal incidence (\\(\\theta = 0^\\circ\\)), s (solid lines) and p (dashed lines) polarization are equivalent. As the incidence angle increases, in general absorption is higher for p-polarized light (due to lower reflection). Usually, sunlight is modelled as unpolarized light, which computationally is usually done by averaging the results for s and p-polarized light."
},
{
- "objectID": "solar-cell-simulation/tutorials.html",
- "href": "solar-cell-simulation/tutorials.html",
- "title": "Tutorials",
+ "objectID": "solar-cell-simulation/notebooks/1b-simple_cell.html#conclusions",
+ "href": "solar-cell-simulation/notebooks/1b-simple_cell.html#conclusions",
+ "title": "Example 1b: Basic cell optics",
+ "section": "Conclusions",
+ "text": "Conclusions\nWe have now seen some effects of interference in layers of different thicknesses, and seen the effect of adding a highly reflective substrate. So we already have two strategies for light-trapping/improving the absorption in a solar cell: adding an anti-reflection coating (in example 1a), to reduce front-surface reflection and get more light into the cell, and adding a highly reflective layer at the back, to reduce loss through the back of the cell and keep light trapped in the cell."
+ },
+ {
+ "objectID": "solar-cell-simulation/notebooks/2b-optical_constants.html",
+ "href": "solar-cell-simulation/notebooks/2b-optical_constants.html",
+ "title": "Example 2b: Optical constant models",
"section": "",
- "text": "This is the website for the solcore-education GitHub, where we host readable versions of Solcore and RayFlare examples (see the sidebar on the left). Note that this is not an introductory Python course, or a course about the fundamentals of solar cells.\nThe examples on this website are hosted in Jupyter Notebook (.ipynb) format for readability. To run the examples yourself, you can find standard .py versions on the GitHub here. We recommend using these rather than the Notebook versions.\nPackage requirements\nTo use the examples on this website, you will need to install Solcore and RayFlare (the links take you to installation instructions for each package). In the simplest case, you can install them with:\npip install solcore rayflare\nBut this will not install all functionality, as detailed in the documentation for both packages.\nThe only other dependency, which is used for plotting, is seaborn, which you can install simply with:\npip install seaborn"
+ "text": "We may want to model the optical constants of a material using analytic expressions, rather than just take data from a table; this can be useful when e.g. fitting ellipsometry data for a material with unknown optical constants, or if you do not have refractive index data for a material but have some information about where critical points in the band structure occur. In this example we will consider a simple model for a dielectric material, and a more complex model for GaAs, a semiconductor.\n\nimport matplotlib.pyplot as plt\nimport numpy as np\n\nfrom solcore.absorption_calculator import search_db\nfrom solcore.absorption_calculator.cppm import Custom_CPPB\nfrom solcore.absorption_calculator.dielectric_constant_models import Oscillator\nfrom solcore.absorption_calculator.dielectric_constant_models import DielectricConstantModel, Cauchy\nfrom solcore.structure import Structure\nfrom solcore import material\n\nwl = np.linspace(300, 950, 200)*1e-9\n\nWe search the database for BK7 (borosilicate crown glass) and select the second entry, “Ohara” (index 1). We then select the first item in that list, which is the pageid of the entry - this is what we need to tell Solcore what item to access in the database.\n\npageid = search_db(\"BK7\")[1][0];\nBK7 = material(str(pageid), nk_db=True)()\n\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\n18 results found.\npageid shelf book page filepath hasrefractive hasextinction rangeMin rangeMax points\n963 glass BK7 SCHOTT glass/schott/N-BK7.yml 1 1 0.3 2.5 25\n964 glass BK7 OHARA glass/ohara/S-BSL7.yml 1 1 0.29 2.4 31\n965 glass BK7 HIKARI glass/hikari/J-BK7A.yml 1 1 0.365015 2.05809 33\n966 glass BK7 CDGM glass/cdgm/H-K9L.yml 1 1 0.365 1.711 34\n967 glass BK7 HOYA glass/hoya/BSC7.yml 1 1 0.36501 1.01398 38\n968 glass BK7 SUMITA glass/sumita/K-BK7.yml 1 1 0.36 1.55 25\n969 glass BK7 LZOS glass/lzos/K8.yml 1 0 0.365 2.3254 31\n1090 glass SCHOTT-BK BK7G18 glass/schott/BK7G18.yml 1 1 0.38 2.5 18\n1091 glass SCHOTT-BK N-BK7 glass/schott/N-BK7.yml 1 1 0.3 2.5 25\n1092 glass SCHOTT-BK N-BK7HT glass/schott/N-BK7HT.yml 1 1 0.3 2.5 25\n1093 glass SCHOTT-BK N-BK7HTi glass/schott/N-BK7HTi.yml 1 1 0.3 2.5 25\n1095 glass SCHOTT-BK P-BK7 glass/schott/P-BK7.yml 1 1 0.31 2.5 24\n1683 glass HIKARI-BK E-BK7 glass/hikari/E-BK7.yml 1 1 0.4 0.7 32\n1684 glass HIKARI-BK J-BK7 glass/hikari/J-BK7.yml 1 1 0.365015 2.05809 33\n1685 glass HIKARI-BK J-BK7A glass/hikari/J-BK7A.yml 1 1 0.365015 2.05809 33\n2452 glass SUMITA-BK K-BK7 glass/sumita/K-BK7.yml 1 1 0.36 1.55 25\n2819 other BK7_matching_liquid Cargille index-matching liquids/cargille/BK7_matching_liquid.yml 1 0 0.31 1.55 200\n2904 3d glass BK7 glass/schott/N-BK7.yml 1 1 0.3 2.5 25\n\n\nNext, we define a Cauchy oscillator model. We put this into the DielectricConstantModel class; in theory, we could add as many oscillators as we want here.\nThe parameters for the Cauchy model for BK7 are from Wikipedia: https://en.wikipedia.org/wiki/Cauchy%27s_equation\n\ncauchy = Cauchy(An=1.5046, Bn=0.00420, Cn=0, Ak=0, Bk=0, Ck=0)\nmodel = DielectricConstantModel(e_inf=0, oscillators=[cauchy])\n\nCalculate the dielectric function which result from the Cauchy model, then get the \\(n\\) and \\(\\kappa\\) data from the database BK7 material for the complex refractive index:\n\neps = model.dielectric_constants(wl*1e9)\nnk = BK7.n(wl) + 1j*BK7.k(wl)\n\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\nMaterial glass/ohara/S-BSL7.yml loaded.\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\nMaterial glass/ohara/S-BSL7.yml loaded.\n\n\nCalculate the dielectric function by squaring the refractive index:\n\neps_db = nk**2\n\nPLOT 1: Plot the database values of e_1 (real part of the dielectric function) against the Cauchy model values:\n\nplt.figure()\nplt.plot(wl*1e9, np.real(eps), label='Cauchy model')\nplt.plot(wl*1e9, np.real(eps_db), '--', label='Database values')\nplt.legend()\nplt.ylabel(r'$\\epsilon_1$')\nplt.xlabel('Wavelength (nm)')\nplt.title(\"(1) Dielectric function for BK7 glass\")\nplt.show()\n\n\n\n\nHere, we have just looked at the real part of the dielectric function, but you can include absorption (non-zero e_2) in the dielectric constant models too.\nNow let’s look at a more complicated CPPB (Critical Point Parabolic Band) model for GaAs. First, read in experimental data for GaAs dielectric function (from Palik)…\n\nPalik_Eps1 = np.loadtxt(\"data/Palik_GaAs_Eps1.csv\", delimiter=',', unpack=False)\nPalik_Eps2 = np.loadtxt(\"data/Palik_GaAs_Eps2.csv\", delimiter=',', unpack=False)\n\nGenerate a list of energies over which to calculate the model dielectric function and create the CPPB_model Class object:\n\nE = np.linspace(0.2, 5, 1000)\nCPPB_Model = Custom_CPPB()\n\nThe Material_Params method loads in the desired material parameters as a dictionary (for some common materials):\n\nMatParams = CPPB_Model.Material_Params(\"GaAs\")\n\nParameters can be customised by assigning to the correct dictionary key:\n\nMatParams[\"B1\"] = 5.8\nMatParams[\"B1s\"] = 1.0\nMatParams[\"Gamma_Eg_ID\"] = 0.3\nMatParams[\"Alpha_Eg_ID\"] = 0.0\nMatParams[\"E1\"] = 2.8\nMatParams[\"E1_d1\"] = 2.9\nMatParams[\"Gamma_E1\"] = 0.1\nMatParams[\"E2\"] = 4.72\nMatParams[\"C\"] = 3.0\nMatParams[\"Alpha_E2\"] = 0.04\nMatParams[\"Gamma_E2\"] = 0.19\n\nMust define a structure object containing the required oscillator functions. The oscillator type and material parameters are both passed to individual ‘Oscillators’ in the structure:\n\nAdachi_GaAs = Structure([\n Oscillator(oscillator_type=\"E0andE0_d0\", material_parameters=MatParams),\n Oscillator(oscillator_type=\"E1andE1_d1\", material_parameters=MatParams),\n Oscillator(oscillator_type=\"E_ID\", material_parameters=MatParams),\n Oscillator(oscillator_type=\"E2\", material_parameters=MatParams)\n])\n\nOutput = CPPB_Model.eps_calc(Adachi_GaAs, E)\n\nPLOT 2: real and imaginary part of the dielectric constant, showing the individual contributions of the critical points.\n\nfig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(9, 4.5))\n\n# Subplot I :: Real part of the dielectric function.\nax1.set_xlim(0, 5.3)\nax1.set_ylim(-14, 27)\n\nax1.plot(Palik_Eps1[:, 0], Palik_Eps1[:, 1], label=\"Exp. Data (Palik)\",\n marker='o', ls='none', markerfacecolor='none', markeredgecolor=\"red\")\n\nax1.plot(E, Output[\"eps\"].real, color=\"navy\", label=\"Total\")\nax1.plot(E, Output[\"components\"][0].real, color=\"orangered\", ls='--', label=\"$E_0$ and $E_0+\\Delta_0$\")\nax1.plot(E, Output[\"components\"][1].real, color=\"dodgerblue\", ls='--', label=\"$E_1$ and $E_1+\\Delta_1$\")\nax1.plot(E, Output[\"components\"][2].real, color=\"limegreen\", ls='--', label=\"$E_{ID}$ (Indirect)\")\nax1.plot(E, Output[\"components\"][3].real, color=\"gold\", ls='--', label=\"$E_2$\")\n\nax1.set_xlabel(\"Energy (eV)\")\nax1.set_ylabel(\"$\\epsilon_1 (\\omega)$\")\nax1.set_title(\"(2) CPPB model for GaAs compared with experimental data\")\nax1.text(0.05, 0.05, '(a)', transform=ax1.transAxes, fontsize=12)\n\n# Subplot II :: Imaginary part of the dielectric function.\n\nax2.plot(Palik_Eps2[:, 0], Palik_Eps2[:, 1], label=\"Exp. Data (Palik)\",\n marker='o', ls='none', markerfacecolor='none', markeredgecolor=\"red\")\n\nax2.plot(E, Output[\"eps\"].imag, color=\"Navy\", label=\"Total\")\nax2.plot(E, Output[\"components\"][0].imag, color=\"orangered\", ls='--', label=\"$E_0$ and $E_0+\\Delta_0$\")\nax2.plot(E, Output[\"components\"][1].imag, color=\"dodgerblue\", ls='--', label=\"$E_1$ and $E_1+\\Delta_1$\")\nax2.plot(E, Output[\"components\"][2].imag, color=\"limegreen\", ls='--', label=\"$E_{ID}$ (Indirect)\")\nax2.plot(E, Output[\"components\"][3].imag, color=\"gold\", ls='--', label=\"$E_2$\")\nax2.set_xlim(0, 5.3)\nax2.set_ylim(0, 27)\n\nax2.set_xlabel(\"Energy (eV)\")\nax2.set_ylabel(\"$\\epsilon_2 (\\omega)$\")\nax2.text(0.05, 0.05, '(b)', transform=ax2.transAxes, fontsize=12)\nax2.legend(loc=\"upper left\", frameon=False)\nplt.tight_layout()\nplt.show()"
},
{
"objectID": "solar-cell-simulation/notebooks/6b-multiscale_models.html",
@@ -455,242 +476,277 @@
"text": "Visualizing the redistribution matrices\nRayFlare has built-in functions to make it easier to load and plot the redistribution matrices you have generated. First, based on the options we set, we generate the angle_vector which lists the \\(\\theta\\) (polar angle) and \\(\\phi\\) (azimuthal angle) values in each bin of the angular redistribution matrices.\n\ntheta_intv, phi_intv, angle_vector = make_angle_vector(options['n_theta_bins'], options['phi_symmetry'],\n options['c_azimuth'])\n\npalhf = sns.cubehelix_palette(256, start=.5, rot=-.9)\npalhf.reverse()\nseamap = mpl.colors.ListedColormap(palhf)\n\nNow we find the path where the matrices are stored. We calculated the redistribution matrices at different wavelengths, but we are just going to plot them at a single wavelength, so we find which index in the matrix that wavelength corresponds to.\n\npath = get_savepath('current', options.project_name)\n\nwl_to_plot = 1100e-9\n\nwl_index = np.argmin(np.abs(wavelengths-wl_to_plot))\n\nThe redistribution matrices are stored in a sparse matrix format (this is much more efficient, since usually many matrix entries will be zero. A sparse matrix format lists only the non-zero entries). Using the path we found above and the name of the surface, we load the sparse matrix, select only the wavelength we are interested in, and convert it to a normal NumPy array (a “dense” matrix):\n\nsprs = load_npz(os.path.join(path, SC_fig8[2].name + 'frontRT.npz'))\n# Load the redistribution matrices for the bottom surface in SC_fig8 (this is the grating)\n\nfull = sprs[wl_index].todense()\n# Convert from sparse format to normal numpy array\n\nThe indexing of the sparse array is (wavelength, angular bin out, angular bin in). To make the information easier to interpret, RayFlare has the theta_summary function which will sum over all the azimuthal bins at a certain \\(\\theta\\), and returns the results in xarray format. We then select only the first half of the matrix; the data for reflection and transmission are stored in the same array (0 to \\(2 \\pi\\) radians), but we pick out only the reflection part.\n\nsummat = theta_summary(full, angle_vector, options['n_theta_bins'], front_or_rear='front')\n# Front or rear refers to front or rear incidence on the surface, rather than the front or rear surface of the\n# whole structure\n\nsummat_g = summat[:options['n_theta_bins'], :]\n# We select the FIRST half of the matrix: this is redistribution into the upper half plane, so because we are\n# looking at incident light coming from this half-plane, this corresponds to reflection\n\nWe can use xarray’s functionality to transform the coordinates from \\(\\theta\\) to \\(\\sin\\theta\\), and then rename the coordinates (this will automatically label the plots below).\n\nsummat_g = summat_g.assign_coords({r'$\\theta_{in}$': np.sin(summat_g.coords[r'$\\theta_{in}$']).data,\n r'$\\theta_{out}$': np.sin(summat_g.coords[r'$\\theta_{out}$']).data})\n\nsummat_g = summat_g.rename({r'$\\theta_{in}$': r'$\\sin(\\theta_{in})$', r'$\\theta_{out}$': r'$\\sin(\\theta_{out})$'})\n\nNow repeat this for another surface (the pyramidal surface):\n\nsprs = load_npz(os.path.join(path, SC_fig8[0].name + 'rearRT.npz'))\n# Load the redistribution matrices for the top surface in SC_fig8 (this is the pyramids)\n\nfull = sprs[wl_index].todense()\n\nsummat = theta_summary(full, angle_vector, options['n_theta_bins'], front_or_rear='rear')\n# Front or rear refers to front or rear incidence on the surface, rather than the front or rear surface of the\n# whole structure\n\nsummat_r = summat[options['n_theta_bins']:, :]\n# We select the SECOND half of the matrix: this is redistribution into the lower half plane, so because we are\n# looking at incident light coming from this half-plane, this corresponds to reflection\n\nsummat_r = summat_r.assign_coords({r'$\\theta_{in}$': np.sin(summat_r.coords[r'$\\theta_{in}$']).data,\n r'$\\theta_{out}$': np.sin(summat_r.coords[r'$\\theta_{out}$']).data})\n\nsummat_r = summat_r.rename({r'$\\theta_{in}$': r'$\\sin(\\theta_{in})$', r'$\\theta_{out}$': r'$\\sin(\\theta_{out})$'})\n\nPlot the redistribution matrices:\nPLOT 2: Redistribution matrices for reflection into Silicon from the diffraction grating (left) and pyramid surface (right).\n\nfig = plt.figure(figsize=(10,4))\nax = plt.subplot(121)\nax = summat_g.plot.imshow(ax=ax, cmap=seamap, vmax=0.3)\nax = plt.subplot(122)\nax = summat_r.plot.imshow(ax=ax, cmap=seamap)\nplt.show()\n\n\n\n\nThe left plot shows redistribution of light upon reflection from the rear grating. The right plot shows redistribution of light which hits the pyramidal front surface from the inside and is reflected back into the cell. We could plot equivalent matrices for e.g. transmission through the rear grating or light escaping when incident on the pyramid surface from the inside of the structure."
},
{
- "objectID": "solar-cell-simulation/notebooks/2b-optical_constants.html",
- "href": "solar-cell-simulation/notebooks/2b-optical_constants.html",
- "title": "Example 2b: Optical constant models",
+ "objectID": "solar-cell-simulation/tutorials.html",
+ "href": "solar-cell-simulation/tutorials.html",
+ "title": "Tutorials",
"section": "",
- "text": "We may want to model the optical constants of a material using analytic expressions, rather than just take data from a table; this can be useful when e.g. fitting ellipsometry data for a material with unknown optical constants, or if you do not have refractive index data for a material but have some information about where critical points in the band structure occur. In this example we will consider a simple model for a dielectric material, and a more complex model for GaAs, a semiconductor.\n\nimport matplotlib.pyplot as plt\nimport numpy as np\n\nfrom solcore.absorption_calculator import search_db\nfrom solcore.absorption_calculator.cppm import Custom_CPPB\nfrom solcore.absorption_calculator.dielectric_constant_models import Oscillator\nfrom solcore.absorption_calculator.dielectric_constant_models import DielectricConstantModel, Cauchy\nfrom solcore.structure import Structure\nfrom solcore import material\n\nwl = np.linspace(300, 950, 200)*1e-9\n\nWe search the database for BK7 (borosilicate crown glass) and select the second entry, “Ohara” (index 1). We then select the first item in that list, which is the pageid of the entry - this is what we need to tell Solcore what item to access in the database.\n\npageid = search_db(\"BK7\")[1][0];\nBK7 = material(str(pageid), nk_db=True)()\n\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\n18 results found.\npageid shelf book page filepath hasrefractive hasextinction rangeMin rangeMax points\n963 glass BK7 SCHOTT glass/schott/N-BK7.yml 1 1 0.3 2.5 25\n964 glass BK7 OHARA glass/ohara/S-BSL7.yml 1 1 0.29 2.4 31\n965 glass BK7 HIKARI glass/hikari/J-BK7A.yml 1 1 0.365015 2.05809 33\n966 glass BK7 CDGM glass/cdgm/H-K9L.yml 1 1 0.365 1.711 34\n967 glass BK7 HOYA glass/hoya/BSC7.yml 1 1 0.36501 1.01398 38\n968 glass BK7 SUMITA glass/sumita/K-BK7.yml 1 1 0.36 1.55 25\n969 glass BK7 LZOS glass/lzos/K8.yml 1 0 0.365 2.3254 31\n1090 glass SCHOTT-BK BK7G18 glass/schott/BK7G18.yml 1 1 0.38 2.5 18\n1091 glass SCHOTT-BK N-BK7 glass/schott/N-BK7.yml 1 1 0.3 2.5 25\n1092 glass SCHOTT-BK N-BK7HT glass/schott/N-BK7HT.yml 1 1 0.3 2.5 25\n1093 glass SCHOTT-BK N-BK7HTi glass/schott/N-BK7HTi.yml 1 1 0.3 2.5 25\n1095 glass SCHOTT-BK P-BK7 glass/schott/P-BK7.yml 1 1 0.31 2.5 24\n1683 glass HIKARI-BK E-BK7 glass/hikari/E-BK7.yml 1 1 0.4 0.7 32\n1684 glass HIKARI-BK J-BK7 glass/hikari/J-BK7.yml 1 1 0.365015 2.05809 33\n1685 glass HIKARI-BK J-BK7A glass/hikari/J-BK7A.yml 1 1 0.365015 2.05809 33\n2452 glass SUMITA-BK K-BK7 glass/sumita/K-BK7.yml 1 1 0.36 1.55 25\n2819 other BK7_matching_liquid Cargille index-matching liquids/cargille/BK7_matching_liquid.yml 1 0 0.31 1.55 200\n2904 3d glass BK7 glass/schott/N-BK7.yml 1 1 0.3 2.5 25\n\n\nNext, we define a Cauchy oscillator model. We put this into the DielectricConstantModel class; in theory, we could add as many oscillators as we want here.\nThe parameters for the Cauchy model for BK7 are from Wikipedia: https://en.wikipedia.org/wiki/Cauchy%27s_equation\n\ncauchy = Cauchy(An=1.5046, Bn=0.00420, Cn=0, Ak=0, Bk=0, Ck=0)\nmodel = DielectricConstantModel(e_inf=0, oscillators=[cauchy])\n\nCalculate the dielectric function which result from the Cauchy model, then get the \\(n\\) and \\(\\kappa\\) data from the database BK7 material for the complex refractive index:\n\neps = model.dielectric_constants(wl*1e9)\nnk = BK7.n(wl) + 1j*BK7.k(wl)\n\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\nMaterial glass/ohara/S-BSL7.yml loaded.\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\nMaterial glass/ohara/S-BSL7.yml loaded.\n\n\nCalculate the dielectric function by squaring the refractive index:\n\neps_db = nk**2\n\nPLOT 1: Plot the database values of e_1 (real part of the dielectric function) against the Cauchy model values:\n\nplt.figure()\nplt.plot(wl*1e9, np.real(eps), label='Cauchy model')\nplt.plot(wl*1e9, np.real(eps_db), '--', label='Database values')\nplt.legend()\nplt.ylabel(r'$\\epsilon_1$')\nplt.xlabel('Wavelength (nm)')\nplt.title(\"(1) Dielectric function for BK7 glass\")\nplt.show()\n\n\n\n\nHere, we have just looked at the real part of the dielectric function, but you can include absorption (non-zero e_2) in the dielectric constant models too.\nNow let’s look at a more complicated CPPB (Critical Point Parabolic Band) model for GaAs. First, read in experimental data for GaAs dielectric function (from Palik)…\n\nPalik_Eps1 = np.loadtxt(\"data/Palik_GaAs_Eps1.csv\", delimiter=',', unpack=False)\nPalik_Eps2 = np.loadtxt(\"data/Palik_GaAs_Eps2.csv\", delimiter=',', unpack=False)\n\nGenerate a list of energies over which to calculate the model dielectric function and create the CPPB_model Class object:\n\nE = np.linspace(0.2, 5, 1000)\nCPPB_Model = Custom_CPPB()\n\nThe Material_Params method loads in the desired material parameters as a dictionary (for some common materials):\n\nMatParams = CPPB_Model.Material_Params(\"GaAs\")\n\nParameters can be customised by assigning to the correct dictionary key:\n\nMatParams[\"B1\"] = 5.8\nMatParams[\"B1s\"] = 1.0\nMatParams[\"Gamma_Eg_ID\"] = 0.3\nMatParams[\"Alpha_Eg_ID\"] = 0.0\nMatParams[\"E1\"] = 2.8\nMatParams[\"E1_d1\"] = 2.9\nMatParams[\"Gamma_E1\"] = 0.1\nMatParams[\"E2\"] = 4.72\nMatParams[\"C\"] = 3.0\nMatParams[\"Alpha_E2\"] = 0.04\nMatParams[\"Gamma_E2\"] = 0.19\n\nMust define a structure object containing the required oscillator functions. The oscillator type and material parameters are both passed to individual ‘Oscillators’ in the structure:\n\nAdachi_GaAs = Structure([\n Oscillator(oscillator_type=\"E0andE0_d0\", material_parameters=MatParams),\n Oscillator(oscillator_type=\"E1andE1_d1\", material_parameters=MatParams),\n Oscillator(oscillator_type=\"E_ID\", material_parameters=MatParams),\n Oscillator(oscillator_type=\"E2\", material_parameters=MatParams)\n])\n\nOutput = CPPB_Model.eps_calc(Adachi_GaAs, E)\n\nPLOT 2: real and imaginary part of the dielectric constant, showing the individual contributions of the critical points.\n\nfig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(9, 4.5))\n\n# Subplot I :: Real part of the dielectric function.\nax1.set_xlim(0, 5.3)\nax1.set_ylim(-14, 27)\n\nax1.plot(Palik_Eps1[:, 0], Palik_Eps1[:, 1], label=\"Exp. Data (Palik)\",\n marker='o', ls='none', markerfacecolor='none', markeredgecolor=\"red\")\n\nax1.plot(E, Output[\"eps\"].real, color=\"navy\", label=\"Total\")\nax1.plot(E, Output[\"components\"][0].real, color=\"orangered\", ls='--', label=\"$E_0$ and $E_0+\\Delta_0$\")\nax1.plot(E, Output[\"components\"][1].real, color=\"dodgerblue\", ls='--', label=\"$E_1$ and $E_1+\\Delta_1$\")\nax1.plot(E, Output[\"components\"][2].real, color=\"limegreen\", ls='--', label=\"$E_{ID}$ (Indirect)\")\nax1.plot(E, Output[\"components\"][3].real, color=\"gold\", ls='--', label=\"$E_2$\")\n\nax1.set_xlabel(\"Energy (eV)\")\nax1.set_ylabel(\"$\\epsilon_1 (\\omega)$\")\nax1.set_title(\"(2) CPPB model for GaAs compared with experimental data\")\nax1.text(0.05, 0.05, '(a)', transform=ax1.transAxes, fontsize=12)\n\n# Subplot II :: Imaginary part of the dielectric function.\n\nax2.plot(Palik_Eps2[:, 0], Palik_Eps2[:, 1], label=\"Exp. Data (Palik)\",\n marker='o', ls='none', markerfacecolor='none', markeredgecolor=\"red\")\n\nax2.plot(E, Output[\"eps\"].imag, color=\"Navy\", label=\"Total\")\nax2.plot(E, Output[\"components\"][0].imag, color=\"orangered\", ls='--', label=\"$E_0$ and $E_0+\\Delta_0$\")\nax2.plot(E, Output[\"components\"][1].imag, color=\"dodgerblue\", ls='--', label=\"$E_1$ and $E_1+\\Delta_1$\")\nax2.plot(E, Output[\"components\"][2].imag, color=\"limegreen\", ls='--', label=\"$E_{ID}$ (Indirect)\")\nax2.plot(E, Output[\"components\"][3].imag, color=\"gold\", ls='--', label=\"$E_2$\")\nax2.set_xlim(0, 5.3)\nax2.set_ylim(0, 27)\n\nax2.set_xlabel(\"Energy (eV)\")\nax2.set_ylabel(\"$\\epsilon_2 (\\omega)$\")\nax2.text(0.05, 0.05, '(b)', transform=ax2.transAxes, fontsize=12)\nax2.legend(loc=\"upper left\", frameon=False)\nplt.tight_layout()\nplt.show()"
+ "text": "This is the website for the solcore-education GitHub, where we host readable versions of Solcore and RayFlare examples (see the sidebar on the left). Note that this is not an introductory Python course, or a course about the fundamentals of solar cells.\nThe examples on this website are hosted in Jupyter Notebook (.ipynb) format for readability. To run the examples yourself, you can find standard .py versions on the GitHub here. We recommend using these rather than the Notebook versions.\nPackage requirements\nTo use the examples on this website, you will need to install Solcore and RayFlare (the links take you to installation instructions for each package). In the simplest case, you can install them with:\npip install solcore rayflare\nBut this will not install all functionality, as detailed in the documentation for both packages.\nThe only other dependency, which is used for plotting, is seaborn, which you can install simply with:\npip install seaborn"
},
{
- "objectID": "solar-cell-simulation/notebooks/1b-simple_cell.html",
- "href": "solar-cell-simulation/notebooks/1b-simple_cell.html",
- "title": "Example 1b: Basic cell optics",
+ "objectID": "solar-cell-simulation/notebooks/6a-multiscale_models.html",
+ "href": "solar-cell-simulation/notebooks/6a-multiscale_models.html",
+ "title": "Example 6a: Silicon HIT cell",
"section": "",
- "text": "In this script, we will build on the TMM model from example 1(a) and look at the effects of interference.\nimport numpy as np\nimport matplotlib.pyplot as plt\n\nfrom solcore import material, si\nfrom solcore.solar_cell import Layer\nfrom solcore.absorption_calculator import calculate_rat, OptiStack\nimport seaborn as sns"
+ "text": "In Example 4a, we looked at a solar cell made of a single layer of Si with pyramidal texturing. In reality, a solar cell will have a more complicated structure with thin layers deposited on the front side to act as e.g. selective transport layers for carriers. This adds a layer of complication to the ray-tracing process, because we can no longer rely on the Fresnel equations to calculate the angle and wavelength-dependent reflection and transmission probabilities; we might get absorption in the surface layers, and we need to take into account interference in the surface layers. To do this, we can combine ray-tracing and the transfer-matrix method; we can calculate the reflection, absorption and transmission probabilities using TMM, and use those probabilities in our ray-tracing calculations. In RayFlare, this functionality is implemented as part of the angular redistribution matrix functionality.\nThis example is (loosely) based on the simulations done for this paper which looks at the absorptivity/emissivity of silicon heterojunction (HJT or HIT) cells, although here we will only look at the usual wavelengths for the photovoltaic operation of silicon solar cells rather than the infrared.\nfrom solcore import material, si\nfrom solcore.light_source import LightSource\nfrom solcore.constants import q\nfrom solcore.solar_cell import SolarCell, Layer, Junction\nfrom solcore.solar_cell_solver import default_options as defaults_solcore, solar_cell_solver\n\nfrom rayflare.textures import regular_pyramids\nfrom rayflare.structure import Interface, BulkLayer, Structure\nfrom rayflare.matrix_formalism import calculate_RAT, process_structure\nfrom rayflare.options import default_options\nfrom rayflare.utilities import make_absorption_function\n\nimport numpy as np\nimport os\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom scipy.ndimage.filters import gaussian_filter1d\n\nfrom cycler import cycler"
},
{
- "objectID": "solar-cell-simulation/notebooks/1b-simple_cell.html#setting-up",
- "href": "solar-cell-simulation/notebooks/1b-simple_cell.html#setting-up",
- "title": "Example 1b: Basic cell optics",
+ "objectID": "solar-cell-simulation/notebooks/6a-multiscale_models.html#setting-up",
+ "href": "solar-cell-simulation/notebooks/6a-multiscale_models.html#setting-up",
+ "title": "Example 6a: Silicon HIT cell",
"section": "Setting up",
- "text": "Setting up\nFirst, let’s define some materials:\n\nSi = material(\"Si\")\nSiN = material(\"Si3N4\")()\nAg = material(\"Ag\")()\n\nNote the second set of brackets (or lack thereof). The Solcore material system essentially operates in two stages; we first call the material function with the name of the material we want to use, for example Si = material(“Si”), which creates a general Python class corresponding to that material. We then call this class to specify further details, such as the temperature, doping level, or alloy composition (where relavant). This happens below when defining Si_n and Si_p; both are use the Si class defined above, and adding further details to the material. For the definitions of SiN and Ag above, we do both steps in a single line, hence the two sets of brackets.\n\nSi_n = Si(Nd=si(\"1e21cm-3\"), hole_diffusion_length=si(\"10um\"))\nSi_p = Si(Na=si(\"1e16cm-3\"), electron_diffusion_length=si(\"400um\"))\n\nTo look at the effect of interference in the Si layer at different thicknesses, we make a list of thicknesses to test (evenly spaced on a log scale from 400 nm to 300 um):\n\nSi_thicknesses = np.linspace(np.log(0.4e-6), np.log(300e-6), 8)\nSi_thicknesses = np.exp(Si_thicknesses)\n\nwavelengths = si(np.linspace(300, 1200, 400), \"nm\")\n\noptions = {\n \"recalculate_absorption\": True,\n \"optics_method\": \"TMM\",\n \"wavelength\": wavelengths\n }\n\nMake a color palette using the seaborn package to make the plots look nicer\n\ncolors = sns.color_palette('rocket', n_colors=len(Si_thicknesses))\ncolors.reverse()\n\ncreate an ARC layer:\n\nARC_layer = Layer(width=si('75nm'), material=SiN)"
+ "text": "Setting up\nWe add some new materials to Solcore’s database from data files - you only need to add these once, after that you can comment these lines out.\n\nfrom solcore.material_system import create_new_material\n\ncreate_new_material('aSi_i', os.path.join(\"data\", \"model_i_a_silicon_n.txt\"),\n os.path.join(\"data\", \"model_i_a_silicon_k.txt\"))\ncreate_new_material('ITO_measured', os.path.join(\"data\", \"front_ITO_n.txt\"),\n os.path.join(\"data\", \"front_ITO_k.txt\"))\n\nSyntaxError: invalid syntax (2638323969.py, line 3)\n\n\nSetting user options. Several of these (wavelength, number of rays, nx, ny) have been encountered in previous examples. However, because we are using the angular redistribution matrix method for the first time, there are some new ones. Please see RayFlare’s documentation for more detailed information.\n\nproject_name: When we run the simulation, we will generate large matrices which will eventually be multiplied together. These have to be stored somewhere. This is the name of the folder where they will be stored.\nn_theta_bins: The number of polar angle bins to divide the hemisphere into when calculating the redistribution matrices.\nI_thresh: Threshold intensity at which to stop matrix multiplication (i.e. when almost all the light has been reflected, transmitted or absorbed)\nbulk_profile: True or False; whether to calculate the absorption profile in the bulk material (Si in this case).\n\n\nwavelengths = np.linspace(300, 1200, 80)*1e-9\n\noptions = default_options()\noptions.wavelengths = wavelengths\noptions.project_name = 'HIT_example'\noptions.n_rays = 10000 # Reduce this (or the number of wavelengths) to speed up the example! Note that this is the TOTAL number of rays (all angle bins) per wavelength\noptions.n_theta_bins = 20\noptions.nx = 5\noptions.ny = 5\noptions.I_thresh = 0.005\noptions.bulk_profile = True\n\nWe now define the materials and layer structures for the front and rear surfaces. In this case, the front and rear layers are the materials which will be deposited on top of the pyramids (amorphous silicon and ITO); the crystalline silicon itself will be the bulk material which connects the two interfaces.\n\nSi = material('Si')()\nAir = material('Air')()\nITO = material('ITO_measured')()\n\nAg = material('Ag')()\naSi = material('aSi_i')()\n\nfront_materials = [Layer(80e-9, ITO), Layer(13e-9, aSi)]\nback_materials = [Layer(13e-9, aSi), Layer(240e-9, ITO)]\n\nNow we define the front and rear surfaces using the Interface class. Whether pyramids are upright or inverted is relative to front incidence, so if the same etch is applied to both sides of a slab of silicon, one surface will have ‘upright’ pyramids and the other side will have ‘not upright’ (inverted) pyramids in the model. The arguments required for Interface depend on the optical method which is being used; in this case, we must provide the surface texture, layer stack, and whether the layers are to be treated coherently (i.e. affected by thin-film interference). We also have to give the surfaces a name; as with the project name, this is to store the matrices which will be calculated.\n\nsurf = regular_pyramids(elevation_angle=55, upright=True) # elevation angle is relative to horizontal plane\nsurf_back = regular_pyramids(elevation_angle=55, upright=False)\n\nfront_surf = Interface('RT_TMM', texture=surf, layers=front_materials, name='HIT_front', coherent=True)\nback_surf = Interface('RT_TMM', texture=surf_back, layers=back_materials, name='HIT_back', coherent=True)\n\nbulk_Si = BulkLayer(170e-6, Si, name='Si_bulk') # bulk thickness in m\n\nFinally, we build the whole structure inside RayFlare’s Structure class, also specifying the incidence medium (above the cell) and the transmission medium (below the cell).\n\nSC = Structure([front_surf, bulk_Si, back_surf], incidence=Air, transmission=Ag)"
},
{
- "objectID": "solar-cell-simulation/notebooks/1b-simple_cell.html#effect-of-si-thickness",
- "href": "solar-cell-simulation/notebooks/1b-simple_cell.html#effect-of-si-thickness",
- "title": "Example 1b: Basic cell optics",
- "section": "Effect of Si thickness",
- "text": "Effect of Si thickness\nNow we are going to loop through the different Si thicknesses generated above, and create a simple solar cell-like structure. Because we will only do an optical calculation, we don’t need to define a junction and can just make a simple stack of layers.\nWe then calculate reflection, absorption and transmission (RAT) for two different situations: 1. a fully coherent stack 2. assuming the silicon layer is incoherent. This means that light which enters the Si layer cannot interfere with itself, but light in the ARC layer can still show interference. In very thick layers (much thicker than the wavelength of light being considered) this is likely to be more physically accurate because real light does not have infinite coherence length; i.e. if you measured wavelength-dependent transmission or reflection of a Si wafer hundreds of microns thick you would not expect to see interference fringes.\nPLOT 1\n\nplt.figure()\n\nfor i1, Si_t in enumerate(Si_thicknesses):\n\n base_layer = Layer(width=Si_t, material=Si_p) # silicon layer\n solar_cell = OptiStack([ARC_layer, base_layer]) # OptiStack (optical stack) to feed into calculate_rat function\n\n # Coherent calculation:\n RAT_c = calculate_rat(solar_cell, wavelengths*1e9, no_back_reflection=False) # coherent calculation\n # For historical reasons, Solcore's default setting is to ignore reflection at the back of the cell (i.e. at the\n # interface between the final material in the stack and the substrate). Hence we need to tell the calculate_rat\n # function NOT to ignore this reflection (no_back_reflection=False).\n\n # Calculation assuming no interference in the silicon (\"incoherent\"):\n RAT_i = calculate_rat(solar_cell, wavelengths*1e9, no_back_reflection=False,\n coherent=False, coherency_list=['c', 'i']) # partially coherent: ARC is coherent, Si is not\n\n # Plot the results:\n plt.plot(wavelengths*1e9, RAT_c[\"A\"], color=colors[i1], label=str(round(Si_t*1e6, 1)), alpha=0.7)\n plt.plot(wavelengths*1e9, RAT_i[\"A\"], '--', color=colors[i1])\n\nplt.legend(title=r\"Thickness ($\\mu$m)\")\nplt.xlim(300, 1300)\nplt.ylim(0, 1.02)\nplt.ylabel(\"Absorption\")\nplt.title(\"(1) Absorption in Si with varying thickness\")\nplt.show()\n\n\n\n\nWe can see that the coherent calculations (solid lines) show clear interference fringes which depend on the Si thickness. The incoherent calculations do not have these fringes and seem to lie around the average of the interference fringes. For both sets of calculations, we see increasing absorption as the Si gets thicker, as expected."
+ "objectID": "solar-cell-simulation/notebooks/6a-multiscale_models.html#generating-results",
+ "href": "solar-cell-simulation/notebooks/6a-multiscale_models.html#generating-results",
+ "title": "Example 6a: Silicon HIT cell",
+ "section": "Generating results",
+ "text": "Generating results\nNow we are ready to start running calculations. This happens in two phases:\n\nCall process_structure to check each surface in the structure, and generate angular redistribution matrices (if it does not find existing ones)\nCall calculate_rat to run the matrix multiplication and generate reflection, transmission and absorption results\n\n\nprocess_structure(SC, options, save_location=\"current\")\n# save_location = current means that the folder with the redistribution matrix will be created in the current working directory. By default, it is saved in a folder called\n# RayFlare_results in your home directory (~).\nresults = calculate_RAT(SC, options, save_location=\"current\")\n\nMaking lookuptable for element 0 in structure\nMaking lookuptable for element 2 in structure\nRay tracing with TMM lookup table for element 0 in structure\nCalculating matrix only for incidence theta/phi\nRay tracing with TMM lookup table for element 2 in structure\nAfter iteration 1 : maximum power fraction remaining = 0.5629448135186488\nAfter iteration 2 : maximum power fraction remaining = 0.3425270607560974\nAfter iteration 3 : maximum power fraction remaining = 0.20990358983841642\nAfter iteration 4 : maximum power fraction remaining = 0.12862942930538473\nAfter iteration 5 : maximum power fraction remaining = 0.0788271578176077\nAfter iteration 6 : maximum power fraction remaining = 0.04832164906096151\nAfter iteration 7 : maximum power fraction remaining = 0.029625034965812196\nAfter iteration 8 : maximum power fraction remaining = 0.018162348111822744\nAfter iteration 9 : maximum power fraction remaining = 0.011134523091393584\nAfter iteration 10 : maximum power fraction remaining = 0.006826006659534447\nAfter iteration 11 : maximum power fraction remaining = 0.004184675817775243\n\n\nThe structure of the results returned by calculate_RAT is quite complicated; it is explained on this page.\n\nRAT = results[0]\nresults_per_pass = results[1]\n\nR_per_pass = np.sum(results_per_pass['r'][0], axis=2)\nR_0 = R_per_pass[0]\nR_escape = np.sum(R_per_pass[1:, :], axis=0)\n\n# results_per_pass: sum over passes to get overall absorption in each layer.\nresults_per_layer_front = np.sum(results_per_pass['a'][0], axis=0)\n\nresults_per_layer_back = np.sum(results_per_pass['a'][1], axis=0)\n\nTo get the maximum current we could achieve based on these optical results, we calculate the photogenerated current using the AM1.5G spectrum.\n\nspectr_flux = LightSource(source_type='standard', version='AM1.5g', x=wavelengths,\n output_units='photon_flux_per_m', concentration=1).spectrum(wavelengths)[1]\n\nJph_Si = q * np.trapz(RAT['A_bulk'][0] * spectr_flux, wavelengths)/10 # mA/cm2\n\nprint(\"Photogenerated current in Si = %.1f mA/cm2\" % Jph_Si)\n\nPhotogenerated current in Si = 37.5 mA/cm2"
},
{
- "objectID": "solar-cell-simulation/notebooks/1b-simple_cell.html#effect-of-reflective-substrate",
- "href": "solar-cell-simulation/notebooks/1b-simple_cell.html#effect-of-reflective-substrate",
- "title": "Example 1b: Basic cell optics",
- "section": "Effect of reflective substrate",
- "text": "Effect of reflective substrate\nNow we repeat the calculation, but with an Ag substrate under the Si. Previously, we did not specify the substrate and so it was assumed by Solcore to be air (n=1, k=0).\nPLOT 2\n\nplt.figure()\n\nfor i1, Si_t in enumerate(Si_thicknesses):\n\n base_layer = Layer(width=Si_t, material=Si_p)\n\n # As before, but now we specify the substrate to be silver:\n solar_cell = OptiStack([ARC_layer, base_layer], substrate=Ag)\n\n RAT_c = calculate_rat(solar_cell, wavelengths*1e9, no_back_reflection=False)\n RAT_i = calculate_rat(solar_cell, wavelengths*1e9, no_back_reflection=False,\n coherent=False, coherency_list=['c', 'i'])\n plt.plot(wavelengths*1e9, RAT_c[\"A\"], color=colors[i1],\n label=str(round(Si_t*1e6, 1)), alpha=0.7)\n plt.plot(wavelengths*1e9, RAT_i[\"A\"], '--', color=colors[i1])\n\nplt.legend(title=r\"Thickness ($\\mu$m)\")\nplt.xlim(300, 1300)\nplt.ylim(0, 1.02)\nplt.ylabel(\"Absorption\")\nplt.title(\"(2) Absorption in Si with varying thickness (Ag substrate)\")\nplt.show()\n\n\n\n\nWe see that the interference fringes get more prominent in the coherent calculation, due to higher reflection at the rear Si/Ag surface compared to Ag/Air. We also see a slightly boosted absorption at long wavelengths at all thicknesses, again due to improved reflection at the rear surface"
+ "objectID": "solar-cell-simulation/notebooks/6a-multiscale_models.html#plotting",
+ "href": "solar-cell-simulation/notebooks/6a-multiscale_models.html#plotting",
+ "title": "Example 6a: Silicon HIT cell",
+ "section": "Plotting",
+ "text": "Plotting\nNow, we plot where all incident light goes: reflection, absorption in each layer, and transmission into the substrate in the simulation. Note that we set the substrate as Ag for the simulation; although in the actual device the Ag is not infinitely thick, in practice it is thick enough that all light which enters the Ag will be absorbed there so we can treat is as the final material.\nPLOT 1: Reflection and absorption in the Si HIT cell\n\n# Stack results for plotting\nallres = np.hstack((RAT['T'].T, results_per_layer_back,\n RAT['A_bulk'].T, results_per_layer_front)).T\n\n# Create colors for plotting\npal = sns.cubehelix_palette(allres.shape[0] + 1, start=.5, rot=-.9)\npal.reverse()\n\n# Update default colours used by matplotlib\ncols = cycler('color', pal)\nparams = {'axes.prop_cycle': cols}\nplt.rcParams.update(params)\n\n# plot total R, A, T\nfig = plt.figure(figsize=(6,4))\nax = plt.subplot(111)\nax.plot(options['wavelengths']*1e9, R_escape + R_0, '-k', label=r'$R_{total}$')\nax.plot(options['wavelengths']*1e9, R_0, '--k', label=r'$R_0$')\nax.stackplot(options['wavelengths']*1e9, allres,\n labels=['Ag (transmitted)', 'Back ITO', 'a-Si (back)', 'Bulk Si',\n 'a-Si (front)', 'Front ITO'\n ])\nax.set_xlabel(r'Wavelength ($\\mu$m)')\nax.set_ylabel('Absorption/Emissivity')\nax.set_xlim(min(options['wavelengths']*1e9), max(options['wavelengths']*1e9))\nax.set_ylim(0, 1)\nplt.legend(loc='center left', bbox_to_anchor=(1, 0.5))\nplt.tight_layout()\nplt.show()\n\n\n\n\nWe see that over this wavelength range, most of the absorption happens in the bulk Si, as expected. However, at short wavelengths we see significant absorption in the front surface layers, and at long wavelengths we see absorption in the back layers and Ag. We have plotted both the total reflection \\(R_{total}\\) and \\(R_0\\), which is the light which is lost due to initial reflection at the front surface. Up to around 1000 nm, these lines coincide (total reflection is entirely due to initial front surface reflection). At longer wavelengths, we see that \\(R_0\\) is lower than \\(R_{total}\\), as now a significant fraction of the total reflection is from light which enters the cell, makes at least one full forward and backward pass, and then leaves the cell through the front surface. This becomes significant close to the bandgap as the Si becomes transparent.\nIn the plot above we see stochastic noise due to the ray-tracing simulation; we could increase the number of rays to reduce this, which also increases the computation time. Below, we re-plot the same data with some smoothing.\nPLOT 2: Reflection and absorption in the Si HIT cell, with smoothed data\n\nysmoothed = gaussian_filter1d(np.vstack((allres, RAT[\"R\"])), sigma=2, axis=1)\n\n# plot total R, A, T - smoothed\nfig = plt.figure(figsize=(6,4))\nax = plt.subplot(111)\nax.stackplot(options['wavelengths']*1e9, ysmoothed,\n labels=['Ag (transmitted)', 'Back ITO', 'a-Si (back)', 'Bulk Si',\n 'a-Si (front)', 'Front ITO', 'R'\n ])\nax.set_xlabel(r'Wavelength ($\\mu$m)')\nax.set_ylabel('Absorption/Emissivity')\nax.set_xlim(min(options['wavelengths']*1e9), max(options['wavelengths']*1e9))\nax.set_ylim(0, 1)\nplt.legend(loc='center left', bbox_to_anchor=(1, 0.5))\nplt.tight_layout()\nplt.show()"
},
{
- "objectID": "solar-cell-simulation/notebooks/1b-simple_cell.html#effect-of-polarization-and-angle-of-incidence",
- "href": "solar-cell-simulation/notebooks/1b-simple_cell.html#effect-of-polarization-and-angle-of-incidence",
- "title": "Example 1b: Basic cell optics",
- "section": "Effect of polarization and angle of incidence",
- "text": "Effect of polarization and angle of incidence\nFinally, we look at the effect of incidence angle and polarization of the light hitting the cell.\nPLOT 3\n\nangles = [0, 30, 60, 70, 80, 89] # angles in degrees\n\nARC_layer = Layer(width=si('75nm'), material=SiN)\nbase_layer = Layer(width=si(\"100um\"), material=Si_p)\n\ncolors = sns.cubehelix_palette(n_colors=len(angles))\n\nplt.figure()\n\nfor i1, theta in enumerate(angles):\n\n solar_cell = OptiStack([ARC_layer, base_layer])\n\n RAT_s = calculate_rat(solar_cell, wavelengths*1e9, angle=theta,\n pol='s',\n no_back_reflection=False,\n coherent=False, coherency_list=['c', 'i'])\n RAT_p = calculate_rat(solar_cell, wavelengths*1e9, angle=theta,\n pol='p',\n no_back_reflection=False,\n coherent=False, coherency_list=['c', 'i'])\n\n plt.plot(wavelengths*1e9, RAT_s[\"A\"], color=colors[i1], label=str(round(theta)))\n plt.plot(wavelengths*1e9, RAT_p[\"A\"], '--', color=colors[i1])\n\nplt.legend(title=r\"$\\theta (^\\circ)$\")\nplt.xlim(300, 1300)\nplt.ylim(0, 1.02)\nplt.ylabel(\"Absorption\")\nplt.title(\"(3) Absorption in Si with varying thickness\")\nplt.show()\n\n\n\n\nFor normal incidence (\\(\\theta = 0^\\circ\\)), s (solid lines) and p (dashed lines) polarization are equivalent. As the incidence angle increases, in general absorption is higher for p-polarized light (due to lower reflection). Usually, sunlight is modelled as unpolarized light, which computationally is usually done by averaging the results for s and p-polarized light."
+ "objectID": "solar-cell-simulation/notebooks/6a-multiscale_models.html#device-simulations",
+ "href": "solar-cell-simulation/notebooks/6a-multiscale_models.html#device-simulations",
+ "title": "Example 6a: Silicon HIT cell",
+ "section": "Device simulations",
+ "text": "Device simulations\nAs we did in Example 4a, we can now feed the results from RayFlare’s optical calculation into Solcore to run electrical simulations. We generate the absorption profile function, then specify materials and layers for the solar cell structure.\n\nprofile_Si = results[3][0]\nexternal_R = RAT['R'][0, :]\n\npositions, absorb_fn = make_absorption_function([None, profile_Si, None], SC, options, matrix_method=True)\n\nSi_SC = material(\"Si\")\nGaAs_SC = material(\"GaAs\")\nT = 300\n\np_material_Si = Si_SC(T=T, Na=si(1e21, \"cm-3\"), electron_diffusion_length=si(\"10um\"), hole_mobility=50e-4)\nn_material_Si = Si_SC(T=T, Nd=si(1e16, \"cm-3\"), hole_diffusion_length=si(\"290um\"), electron_mobility=400e-4)\n\nAs we noted in Example 4a, we need to specify the user options for Solcore separately (though they should of course be consistent with the options we gave RayFlare above, where relevant!). We set options, create the solar cell structure, and run QE and IV calculations:\n\noptions_sc = defaults_solcore\noptions_sc.optics_method = \"external\"\noptions_sc.position = positions\noptions_sc.light_iv = True\noptions_sc.wavelength = wavelengths\noptions_sc.mpp = True\noptions_sc.theta = options.theta_in*180/np.pi\nV = np.linspace(0, 2.5, 250)\noptions_sc.voltages = V\n\nsolar_cell = SolarCell([Layer(80e-9, ITO),\n Layer(13e-9, aSi),\n Junction([Layer(500e-9, p_material_Si, role=\"emitter\"),\n Layer(bulk_Si.width-500e-9, n_material_Si, role=\"base\")], kind=\"DA\"),\n Layer(13e-9, aSi),\n Layer(240e-9, ITO)],\n external_reflected = external_R,\n external_absorbed = absorb_fn)\n\n\n\nsolar_cell_solver(solar_cell, 'qe', options_sc)\nsolar_cell_solver(solar_cell, 'iv', options_sc)\n\nNow we can plot the results. To check things are consistent, we will plot the total absorption in Si calculated above with RayFlare, and solar_cell.absorbed, which is the result Solcore gives for total absorption. We also plot the EQE, which should be the same as or lower than the absorption.\nPLOT 3: Absorption in the Si and EQE.\n\nplt.figure()\nplt.plot(options['wavelengths']*1e9, RAT[\"A_bulk\"][0], 'r-')\nplt.plot(wavelengths*1e9, solar_cell.absorbed, 'k--', label='Absorbed (integrated)')\nplt.plot(wavelengths*1e9, solar_cell[2].eqe(wavelengths), 'b-', label='Si EQE')\nplt.ylim(0,1)\nplt.legend()\nplt.xlabel('Wavelength (nm)')\nplt.ylabel('R/A')\nplt.show()\n\n\n\n\nPLOT 4: Current-voltage behaviour of the Si cell under illumination\n\nplt.figure()\nplt.plot(V, solar_cell.iv['IV'][1], '-k')\nplt.ylim(-20, 380)\nplt.xlim(0, 0.85)\nplt.ylabel('Current (A/m$^2$)')\nplt.xlabel('Voltage (V)')\nplt.show()"
},
{
- "objectID": "solar-cell-simulation/notebooks/1b-simple_cell.html#conclusions",
- "href": "solar-cell-simulation/notebooks/1b-simple_cell.html#conclusions",
- "title": "Example 1b: Basic cell optics",
- "section": "Conclusions",
- "text": "Conclusions\nWe have now seen some effects of interference in layers of different thicknesses, and seen the effect of adding a highly reflective substrate. So we already have two strategies for light-trapping/improving the absorption in a solar cell: adding an anti-reflection coating (in example 1a), to reduce front-surface reflection and get more light into the cell, and adding a highly reflective layer at the back, to reduce loss through the back of the cell and keep light trapped in the cell."
+ "objectID": "solar-cell-simulation/notebooks/4a-textured_Si_cell.html",
+ "href": "solar-cell-simulation/notebooks/4a-textured_Si_cell.html",
+ "title": "Example 4a: Textured Si cell",
+ "section": "",
+ "text": "In this example, we will introduce RayFlare, which is a package which is closely interlinked with Solcore and extends its optical capabilities. One of the features it has is a ray-tracer, which is useful when modelling e.g. Si solar cells with textured surfaces. We will compare the result with PVLighthouse’s wafer ray tracer.\nFor more information on how ray-tracing works, see RayFlare’s documentation.\nimport numpy as np\nimport os\n\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nfrom rayflare.ray_tracing import rt_structure\nfrom rayflare.textures import regular_pyramids, planar_surface\nfrom rayflare.options import default_options\nfrom rayflare.utilities import make_absorption_function\n\nfrom solcore.absorption_calculator import search_db\nfrom solcore import material, si\nfrom solcore.solar_cell import SolarCell, Layer, Junction\nfrom solcore.solar_cell_solver import solar_cell_solver\nfrom solcore.solar_cell_solver import default_options as defaults_solcore"
},
{
- "objectID": "solar-cell-simulation/notebooks/3a-triple_junction.html",
- "href": "solar-cell-simulation/notebooks/3a-triple_junction.html",
- "title": "Example 3a: Triple junction cell",
+ "objectID": "solar-cell-simulation/notebooks/4a-textured_Si_cell.html#setting-up",
+ "href": "solar-cell-simulation/notebooks/4a-textured_Si_cell.html#setting-up",
+ "title": "Example 4a: Textured Si cell",
+ "section": "Setting up",
+ "text": "Setting up\nFirst, setting up Solcore materials. We use a specific set of Si optical constants from this paper. These are included in the refractiveindex.info database, so we take them from there. This is the same data we used for the PVLighthouse calculation which we are going to compare to.\n\nAir = material('Air')()\nSi_Green = search_db(os.path.join(\"Si\", \"Green-2008\"))[0][0]\nSi_RT = material(str(Si_Green), nk_db=True)()\n\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\n1 results found.\npageid shelf book page filepath hasrefractive hasextinction rangeMin rangeMax points\n687 main Si Green-2008 main/Si/Green-2008.yml 1 1 0.25 1.45 121\n\n\nThe calc variable is a switch: if True, run the calculation; if False, load the result of the previous calculation. Will need to run at least once to generate the results!\nWe use this ‘switch’ to avoid re-running the whole ray-tracing calculation (which can be time-consuming) each time we want to look at the results.\n\ncalc = True\n\nSetting options:\n\nwl = np.linspace(300, 1201, 50) * 1e-9\noptions = default_options()\noptions.wavelengths = wl\n\n# setting up some colours for plotting\npal = sns.color_palette(\"husl\", 4)\n\nnx and ny are the number of point to scan across in the x & y directions in the unit cell. Decrease this to speed up the calculation (but increase noise in results). We also set the total number of rays traced, and depth spacing for the absorption profile calculation.\n\nnxy = 25\noptions.nx = nxy\noptions.ny = nxy\noptions.n_rays = 4 * nxy ** 2 # Number of rays to be traced at each wavelength:\noptions.depth_spacing = si('50nm') # depth spacing for the absorption profile\noptions.parallel = True # this is the default - if you do not want the code to run in parallel, change to False\n\nLoad the result of the PVLighthouse calculation for comparison:\n\nPVlighthouse = np.loadtxt(os.path.join(\"data\", \"RAT_data_300um_2um_55.csv\"), delimiter=',', skiprows=1)\n\nDefine surface for the ray-tracing: a planar surface, and a surface with regular pyramids.\n\nflat_surf = planar_surface(size=2) # pyramid size in microns\ntriangle_surf = regular_pyramids(55, upright=False, size=2)\n\nSet up the ray-tracing structure: this is a list of textures of length n, and then a list of materials of length n-1. So far a single layer, we define a front surface and a back surface (n = 2), and specify the material in between those two surfaces (n-1 = 1). We also specify the width of each material, and the incidence medium (above the first interface) and the transmission medium (below the last interface.\n\nrtstr = rt_structure(textures=[triangle_surf, flat_surf],\n materials = [Si_RT],\n widths=[si('300um')], incidence=Air, transmission=Air)"
+ },
+ {
+ "objectID": "solar-cell-simulation/notebooks/4a-textured_Si_cell.html#running-ray-tracing-calculation",
+ "href": "solar-cell-simulation/notebooks/4a-textured_Si_cell.html#running-ray-tracing-calculation",
+ "title": "Example 4a: Textured Si cell",
+ "section": "Running ray-tracing calculation",
+ "text": "Running ray-tracing calculation\nRun the calculation, if calc was set to True, otherwise load the results. We save the reflection, transmission, and total absorption in an array called result_RAT and the absorption profile as profile_rt.\n\nif calc:\n # This executes if calc = True (set at the top of the script): actually run the ray-tracing:\n result = rtstr.calculate_profile(options)\n\n # Put the results (Reflection, front surface reflection, transmission, absorption in the Si) in an array:\n result_RAT = np.vstack((options['wavelengths']*1e9,\n result['R'], result['R0'], result['T'], result['A_per_layer'][:,0])).T\n\n # absorption profile:\n profile_rt = result['profile']\n\n # save the results:\n np.savetxt(os.path.join(\"results\", \"rayflare_fullrt_300um_2umpyramids_300_1200nm.txt\"), result_RAT)\n np.savetxt(os.path.join(\"results\", \"rayflare_fullrt_300um_2umpyramids_300_1200nm_profile.txt\"), result['profile'])\n\nelse:\n # If calc = False, load results from previous run.\n result_RAT = np.loadtxt(os.path.join(\"results\", \"rayflare_fullrt_300um_2umpyramids_300_1200nm.txt\"))\n profile_rt = np.loadtxt(os.path.join(\"results\", \"rayflare_fullrt_300um_2umpyramids_300_1200nm_profile.txt\"))\n\nPLOT 1: results of ray-tracing from RayFlare and PVLighthouse, showing the reflection, absorption and transmission.\n\nplt.figure()\nplt.plot(result_RAT[:,0], result_RAT[:,1], '-o', color=pal[0], label=r'R$_{total}$', fillstyle='none')\nplt.plot(result_RAT[:,0], result_RAT[:,2], '-o', color=pal[1], label=r'R$_0$', fillstyle='none')\nplt.plot(result_RAT[:,0], result_RAT[:,3], '-o', color=pal[2], label=r'T', fillstyle='none')\nplt.plot(result_RAT[:,0], result_RAT[:,4], '-o', color=pal[3], label=r'A', fillstyle='none')\nplt.plot(PVlighthouse[:, 0], PVlighthouse[:, 2], '--', color=pal[0])\nplt.plot(PVlighthouse[:, 0], PVlighthouse[:, 9], '--', color=pal[2])\nplt.plot(PVlighthouse[:, 0], PVlighthouse[:, 3], '--', color=pal[1])\nplt.plot(PVlighthouse[:, 0], PVlighthouse[:, 5], '--', color=pal[3])\nplt.plot(-1, -1, '-ok', label='RayFlare')\nplt.plot(-1, -1, '--k', label='PVLighthouse')\nplt.xlabel('Wavelength (nm)')\nplt.ylabel('R / A / T')\nplt.ylim(0, 1)\nplt.xlim(300, 1200)\nplt.legend()\nplt.title(\"(1) R/A/T for pyramid-textured Si, calculated with RayFlare and PVLighthouse\")\nplt.show()"
+ },
+ {
+ "objectID": "solar-cell-simulation/notebooks/4a-textured_Si_cell.html#using-optical-results-in-solcore",
+ "href": "solar-cell-simulation/notebooks/4a-textured_Si_cell.html#using-optical-results-in-solcore",
+ "title": "Example 4a: Textured Si cell",
+ "section": "Using optical results in Solcore",
+ "text": "Using optical results in Solcore\nSo far, we have done a purely optical calculation; however, if we want to use this information to do an EQE or IV calculation, we can, by using the ability of Solcore to accept external optics data (we used this in Example 1a already). To use Solcore’s device simulation capabilities (QE/IV), we need to create a function which gives the depth-dependent absorption profile. The argument of the function is the position (in m) in the cell, which can be an array, and the function returns an array with the absorption at these depths at every wavelength with dimensions (n_wavelengths, n_positions).\nRayFlare has the make_absorption_function to automatically make this function, as required by Solcore, from RayFlare’s output data. diff_absorb_fn here is the function we need to pass to Solcore (so it is not an array of values!). We need to provide the profile data, the structure that was being simulated, user options and specify whether we used the angular redistribution matrix method (which in this case we did not, so we set matrix_method=False; see [Example 6a]](6a-multiscale_models.ipynb) for a similar example which does use this method).\n\nposition, diff_absorb_fn = make_absorption_function(profile_rt, rtstr, options, matrix_method=False)\n\nNow we feed this into Solcore; we will define a solar cell model using the depletion approximation (see Example 1c).\nWe need a p-n junction; we make sure the total width of the p-n junction is equal to the width of the Si used above in the ray-tracing calculation (rtrst.widths[0]).\n\nSi_base = material(\"Si\")\n\nn_material_Si_width = si(\"500nm\")\np_material_Si_width = rtstr.widths[0] - n_material_Si_width\n\nn_material_Si = Si_base(Nd=si(1e21, \"cm-3\"), hole_diffusion_length=si(\"10um\"),\n electron_mobility=50e-4, relative_permittivity=11.68)\np_material_Si = Si_base(Na=si(1e16, \"cm-3\"), electron_diffusion_length=si(\"290um\"),\n hole_mobility=400e-4, relative_permittivity=11.68)\n\nOptions for Solcore (note that these are separate from the RayFlare options we set above!):\n\noptions_sc = defaults_solcore\noptions_sc.optics_method = \"external\"\noptions_sc.position = np.arange(0, rtstr.width, options.depth_spacing)\noptions_sc.light_iv = True\noptions_sc.wavelength = wl\noptions_sc.theta = options.theta_in*180/np.pi\nV = np.linspace(0, 1, 200)\noptions_sc.voltages = V\n\nMake the solar cell, passing the absorption function we made above, and the reflection (an array with the R value at each wavelength), and calculate the QE and I-V characteristics.\n\nsolar_cell = SolarCell(\n [\n Junction([Layer(width=n_material_Si_width, material=n_material_Si, role='emitter'),\n Layer(width=p_material_Si_width, material=p_material_Si, role='base')],\n sn=1, sp=1, kind='DA')\n ],\n external_reflected=result_RAT[:,1],\n external_absorbed=diff_absorb_fn)\n\nsolar_cell_solver(solar_cell, 'qe', options_sc)\nsolar_cell_solver(solar_cell, 'iv', options_sc)\n\nSolving optics of the solar cell...\nSolving QE of the solar cell...\nSolving optics of the solar cell...\nAlready calculated reflection, transmission and absorption profile - not recalculating. Set recalculate_absorption to True in the options if you want absorption to be calculated again.\nSolving IV of the junctions...\nSolving IV of the tunnel junctions...\nSolving IV of the total solar cell...\n\n\nPLOT 2: EQE and absorption of Si cell with optics calculated through ray-tracing\n\nplt.figure()\nplt.plot(wl*1e9, solar_cell.absorbed, 'k-', label='Absorbed (integrated)')\nplt.plot(wl*1e9, solar_cell[0].eqe(wl), 'r-', label='EQE')\nplt.plot(wl*1e9, result_RAT[:,4], 'r--', label='Absorbed - RT')\nplt.ylim(0,1)\nplt.legend()\nplt.xlabel('Wavelength (nm)')\nplt.ylabel('R/A')\nplt.title(\"(2) EQE/absorption from electrical model\")\nplt.show()\n\n\n\n\nPLOT 3: Light IV of Si cell with optics calculated through ray-tracing\n\nplt.figure()\nplt.plot(V, -solar_cell[0].iv(V), 'r')\nplt.ylim(-20, 400)\nplt.xlim(0, 0.8)\nplt.legend()\nplt.ylabel('Current (A/m$^2$)')\nplt.xlabel('Voltage (V)')\nplt.title(\"(3) IV characteristics\")\nplt.show()\n\nNo artists with labels found to put in legend. Note that artists whose label start with an underscore are ignored when legend() is called with no argument."
+ },
+ {
+ "objectID": "solar-cell-simulation/notebooks/1c-simple_cell.html",
+ "href": "solar-cell-simulation/notebooks/1c-simple_cell.html",
+ "title": "Example 1c: Electrical models",
"section": "",
- "text": "In the previous examples, we have considered only single-junction cells. However, a major part of Solcore’s capability lies in modelling multi-junction solar cells. In this example, we will look at a triple junction InGaP/GaAs/Ge cell at 1 Sun and under concentration.\nimport numpy as np\nimport os\nimport matplotlib.pyplot as plt\n\nfrom solcore import siUnits, material, si\nfrom solcore.solar_cell import SolarCell\nfrom solcore.structure import Junction, Layer\nfrom solcore.solar_cell_solver import solar_cell_solver\nfrom solcore.light_source import LightSource\nfrom solcore.absorption_calculator import search_db\n\nwl = np.linspace(300, 1850, 700) * 1e-9\nWe define our light source, the AM1.5G spectrum, which will be used for I-V calculations (not under concentration):\nlight_source = LightSource(source_type='standard', x=wl, version='AM1.5g')\nNow we need to build the solar cell layer by layer.\nNote: you need to have downloaded the refractiveindex.info database for these to work. See Example 2a.\nMgF2_pageid = search_db(os.path.join(\"MgF2\", \"Rodriguez-de Marcos\"))[0][0];\nZnS_pageid = search_db(os.path.join(\"ZnS\", \"Querry\"))[0][0];\nMgF2 = material(str(MgF2_pageid), nk_db=True)();\nZnS = material(str(ZnS_pageid), nk_db=True)();\n\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\n1 results found.\npageid shelf book page filepath hasrefractive hasextinction rangeMin rangeMax points\n234 main MgF2 Rodriguez-de_Marcos main/MgF2/Rodriguez-de Marcos.yml 1 1 0.0299919 2.00146 960\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\n1 results found.\npageid shelf book page filepath hasrefractive hasextinction rangeMin rangeMax points\n623 main ZnS Querry main/ZnS/Querry.yml 1 1 0.22 166.6667 312\nTo minimize front surface reflection, we use a four-layer anti-reflection coating (ARC):\nARC = [Layer(si(\"100nm\"), MgF2), Layer(si(\"15nm\"), ZnS), Layer(si(\"15nm\"), MgF2), Layer(si(\"50nm\"), ZnS)]"
+ "text": "In the first two examples, we mostly focused on different optical models and how they can be applied to an Si cell. Here we will look at different electrical models, roughly in increasing order of how ‘realistic’ they are expected to be:\nimport numpy as np\nimport matplotlib.pyplot as plt\n\nfrom solcore.solar_cell import SolarCell, Layer, Junction\nfrom solcore.solar_cell_solver import solar_cell_solver\nfrom solcore.absorption_calculator import OptiStack, calculate_rat\n\nfrom solcore import material, si\n\nfrom solcore.interpolate import interp1d"
},
{
- "objectID": "solar-cell-simulation/notebooks/3a-triple_junction.html#top-cell-gainp",
- "href": "solar-cell-simulation/notebooks/3a-triple_junction.html#top-cell-gainp",
- "title": "Example 3a: Triple junction cell",
- "section": "Top cell: GaInP",
- "text": "Top cell: GaInP\nNow we build the top cell, which requires the n and p sides of GaInP and a window layer. We also add some extra parameters needed for the calculation which are not included in the materials database, such as the minority carriers diffusion lengths.\n\nAlInP = material(\"AlInP\")\nInGaP = material(\"GaInP\")\nwindow_material = AlInP(Al=0.52)\n\ntop_cell_n_material = InGaP(In=0.49, Nd=siUnits(2e18, \"cm-3\"), hole_diffusion_length=si(\"200nm\"))\ntop_cell_p_material = InGaP(In=0.49, Na=siUnits(1e17, \"cm-3\"), electron_diffusion_length=si(\"1um\"))"
+ "objectID": "solar-cell-simulation/notebooks/1c-simple_cell.html#setting-up",
+ "href": "solar-cell-simulation/notebooks/1c-simple_cell.html#setting-up",
+ "title": "Example 1c: Electrical models",
+ "section": "Setting up",
+ "text": "Setting up\nDefine some materials:\n\nGaAs = material(\"GaAs\")()\nAl2O3 = material(\"Al2O3\")()\nAg = material(\"Ag\")()\n\nwavelengths = si(np.linspace(300, 950, 200), \"nm\")\n\nWe are going to do an optical calculation first to get absorption for a GaAs layer; we will use this as an estimate for the EQE as input for the two-diode model.\n\nOS = OptiStack([Layer(si(\"3um\"), GaAs)], substrate=Ag)\n\nCalculate reflection/absorption/transmission (note that we have to give the wavelength to this function in nm rather than m!)\n\nRAT = calculate_rat(OS, wavelength=wavelengths*1e9, no_back_reflection=False)\n\nCreate a function which interpolates the absorption - note that we pass a function which returns the absorption when given a wavelength to the Junction, rather than a table of values!\n\neqe_func = interp1d(wavelengths, RAT[\"A\"])"
},
{
- "objectID": "solar-cell-simulation/notebooks/3a-triple_junction.html#middle-cell-gaas",
- "href": "solar-cell-simulation/notebooks/3a-triple_junction.html#middle-cell-gaas",
- "title": "Example 3a: Triple junction cell",
- "section": "Middle cell: GaAs",
- "text": "Middle cell: GaAs\n\nGaAs = material(\"GaAs\")\n\nmid_cell_n_material = GaAs(Nd=siUnits(3e18, \"cm-3\"), hole_diffusion_length=si(\"500nm\"))\nmid_cell_p_material = GaAs(Na=siUnits(1e17, \"cm-3\"), electron_diffusion_length=si(\"5um\"))"
+ "objectID": "solar-cell-simulation/notebooks/1c-simple_cell.html#d-and-db-junctions",
+ "href": "solar-cell-simulation/notebooks/1c-simple_cell.html#d-and-db-junctions",
+ "title": "Example 1c: Electrical models",
+ "section": "2D and DB junctions",
+ "text": "2D and DB junctions\nDefine the 2D junction with reasonable parameters for GaAs. The units of j01 and j01 are A/m^2. The units for the resistances are (Ohm m)^2. We use the standard ideality factors (1 and 2 respectively) for the two diodes:\n\ntwod_junction = Junction(kind='2D', n1=1, n2=2, j01=3e-17, j02=1e-7,\n R_series=6e-4, R_shunt=5e4, eqe=eqe_func)\n\nDefine two instances of a detailed-balance type junction. In both cases, there will be a sharp absorption onset at the bandgap (1.42 eV for GaAs). By specifying A, we set the fraction of light above the bandgap that is absorbed (A = 1 means 100% absorption above the gap).\n\ndb_junction_A1 = Junction(kind='DB', Eg=1.42, A=1, R_shunt=1e4, n=1)\ndb_junction = Junction(kind='DB', Eg=1.42, A=0.8, R_shunt=1e4, n=1)\n\nV = np.linspace(0, 1.5, 200)\n\nSet some options and define solar cells based on these junctions:\n\nopts = {'voltages': V, 'light_iv': True, 'wavelength': wavelengths, 'mpp': True}\n\nsolar_cell_db_A1 = SolarCell([db_junction_A1])\nsolar_cell_db = SolarCell([db_junction])\nsolar_cell_2d = SolarCell([twod_junction])\n\nCalculate and plot the IV curves:\n\nsolar_cell_solver(solar_cell_db_A1, 'iv', user_options=opts)\nsolar_cell_solver(solar_cell_db, 'iv', user_options=opts)\nsolar_cell_solver(solar_cell_2d, 'iv', user_options=opts)\n\nSolving optics of the solar cell...\nSolving IV of the junctions...\nSolving IV of the tunnel junctions...\nSolving IV of the total solar cell...\nSolving optics of the solar cell...\nSolving IV of the junctions...\nSolving IV of the tunnel junctions...\nSolving IV of the total solar cell...\nSolving optics of the solar cell...\nWarning: A junction of kind \"2D\" found. Junction ignored in the optics calculation!\nSolving IV of the junctions...\nSolving IV of the tunnel junctions...\nSolving IV of the total solar cell...\n\n\nPLOT 1: IV curves for the DB and 2D models.\n\nplt.figure()\nplt.plot(*solar_cell_db_A1.iv[\"IV\"], label='Detailed balance (Eg = 1.44 eV, A = 1)')\nplt.plot(*solar_cell_db.iv[\"IV\"], label='Detailed balance (Eg = 1.44 eV, A = 0.8)')\nplt.plot(*solar_cell_2d.iv[\"IV\"], '--', label='Two-diode')\nplt.xlim(0, 1.5)\nplt.ylim(0, 500)\nplt.xlabel(\"V (V)\")\nplt.ylabel(\"J (A/m$^2$)\")\nplt.legend()\nplt.title('(1) IV curves calculated through detailed balance and two-diode models')\nplt.show()\n\n\n\n\nAs we expect, the two DB solar cells have a very similar shape, but the A = 1 case has a higher Jsc. The two-diode model has a lower current, which makes sense as it’s EQE is specified based on a more realistic absorption calculation which includes front-surface reflection and an absorption edge which is not infinitely sharp at the bandgap, as is assumed by the detailed balance model."
},
{
- "objectID": "solar-cell-simulation/notebooks/3a-triple_junction.html#bottom-cell-ge",
- "href": "solar-cell-simulation/notebooks/3a-triple_junction.html#bottom-cell-ge",
- "title": "Example 3a: Triple junction cell",
- "section": "Bottom cell: Ge",
- "text": "Bottom cell: Ge\n\nGe = material(\"Ge\")\n\nbot_cell_n_material = Ge(Nd=siUnits(2e18, \"cm-3\"), hole_diffusion_length=si(\"800nm\"), hole_mobility=0.01)\nbot_cell_p_material = Ge(Na=siUnits(1e17, \"cm-3\"), electron_diffusion_length=si(\"50um\"), electron_mobility=0.1)"
+ "objectID": "solar-cell-simulation/notebooks/1c-simple_cell.html#da-and-pdd-junctions",
+ "href": "solar-cell-simulation/notebooks/1c-simple_cell.html#da-and-pdd-junctions",
+ "title": "Example 1c: Electrical models",
+ "section": "DA and PDD junctions",
+ "text": "DA and PDD junctions\nNow let’s consider the two slightly more complex models, which will actually take into account the absorption profile of light in the cell and the distribution of charge carriers; the depletion approximation and the Poisson drift-diffusion solver.\nNote: for the PDD example to work, the PDD solver must be installed correctly; see the Solcore documentation for more information.\n\nT = 293 # ambient temperature\n\nwindow = material('AlGaAs')(T=T, Na=si(\"5e18cm-3\"), Al=0.8)\np_GaAs = material('GaAs')(T=T, Na=si(\"1e18cm-3\"), electron_diffusion_length=si(\"400nm\"))\nn_GaAs = material('GaAs')(T=T, Nd=si(\"8e16cm-3\"), hole_diffusion_length=si(\"8um\"))\nbsf = material('GaAs')(T=T, Nd=si(\"2e18cm-3\"))\n\nSC_layers = [Layer(width=si('150nm'), material=p_GaAs, role=\"Emitter\"),\n Layer(width=si('2850nm'), material=n_GaAs, role=\"Base\"),\n Layer(width=si('200nm'), material=bsf, role=\"BSF\")]\n\nsn and sp are the surface recombination velocities (in m/sec). sn is the SRV for the n-doped junction, sp for the p-doped junction.\n\n# Depletion approximation:\nsolar_cell_da = SolarCell(\n [Layer(width=si(\"90nm\"), material=Al2O3), Layer(width=si('20nm'),\n material=window, role=\"Window\"),\n Junction(SC_layers, sn=5e4, sp=5e4, kind='DA')],\n R_series=0, substrate=Ag\n)\n\n\n# Drift-diffusion solver:\nsolar_cell_pdd = SolarCell(\n [Layer(width=si(\"90nm\"), material=Al2O3), Layer(width=si('20nm'),\n material=window, role=\"Window\"),\n Junction(SC_layers, sn=5e4, sp=5e4, kind='PDD')],\n R_series=0, substrate=Ag\n)\n\nIn both cases, we set the series resistance to 0. Other loss factors, such as shading, are also assumed to be zero by default.\n\nopts[\"optics_method\"] = \"TMM\" # Use the transfer-matrix method to calculate the cell's optics\nopts[\"position\"] = 1e-10 # This is the spacing used when calculating the depth-dependent absorption (0.1 nm)\nopts[\"no_back_reflection\"] = False\n\nsolar_cell_solver(solar_cell_da, \"iv\", user_options=opts);\nsolar_cell_solver(solar_cell_da, \"qe\", user_options=opts);\n\nsolar_cell_solver(solar_cell_pdd, \"iv\", user_options=opts);\nsolar_cell_solver(solar_cell_pdd, \"qe\", user_options=opts);\n\nPLOT 2: IV curves for the DA and PDD models\n\nplt.figure()\nplt.plot(*solar_cell_da.iv[\"IV\"], label=\"Depletion approximation\")\nplt.plot(*solar_cell_pdd.iv[\"IV\"], '--', label=\"Poisson Drift Diffusion\")\nplt.xlim(0, 1.2)\nplt.ylim(0, 330)\nplt.legend()\nplt.xlabel(\"V (V)\")\nplt.ylabel(\"J (A/m$^2$)\")\nplt.title('(2) IV curves from depletion approximation and drift-diffusion models')\nplt.show()\n\n\n\n\n\n\n\nPLOT 3: EQE and absorption calculated for the PDD and DA models.\n\nplt.figure()\nplt.plot(wavelengths*1e9, 100*solar_cell_da[2].eqe(wavelengths), 'k-', label=\"EQE (DA)\")\nplt.plot(wavelengths*1e9, 100*solar_cell_pdd[2].eqe(wavelengths), 'k--', label=\"EQE (PDD)\")\nplt.plot(wavelengths*1e9, 100*solar_cell_da[2].layer_absorption, 'r-', label=\"A (DA)\")\nplt.plot(wavelengths*1e9, 100*solar_cell_pdd[2].layer_absorption, 'b--', label=\"A (PDD)\")\nplt.legend()\nplt.xlabel(\"Wavelength (nm)\")\nplt.ylabel(\"EQE/A (%)\")\nplt.title('(3) EQE and absorption from depletion approximation and drift-diffusion models')\nplt.show()"
},
{
- "objectID": "solar-cell-simulation/notebooks/3a-triple_junction.html#putting-the-cell-together",
- "href": "solar-cell-simulation/notebooks/3a-triple_junction.html#putting-the-cell-together",
- "title": "Example 3a: Triple junction cell",
- "section": "Putting the cell together",
- "text": "Putting the cell together\nAnd, finally, we put everything together, adding also the surface recombination velocities. We also add some shading due to the metallisation of the cell = 5%, and a finite series resistance.\n\nsolar_cell = SolarCell(\n ARC +\n [\n Junction([Layer(si(\"20nm\"), material=window_material, role='window'),\n Layer(si(\"100nm\"), material=top_cell_n_material, role='emitter'),\n Layer(si(\"560nm\"), material=top_cell_p_material, role='base'),\n ], sn=1, sp=1, kind='DA'),\n Junction([Layer(si(\"200nm\"), material=mid_cell_n_material, role='emitter'),\n Layer(si(\"3000nm\"), material=mid_cell_p_material, role='base'),\n ], sn=1, sp=1, kind='DA'),\n Junction([Layer(si(\"400nm\"), material=bot_cell_n_material, role='emitter'),\n Layer(si(\"100um\"), material=bot_cell_p_material, role='base'),\n ], sn=1, sp=1, kind='DA'),\n ], shading=0.05, R_series=2e-6)"
+ "objectID": "solar-cell-simulation/notebooks/7b-optimization.html",
+ "href": "solar-cell-simulation/notebooks/7b-optimization.html",
+ "title": "Example 7b: More advanced optimization",
+ "section": "",
+ "text": "This example looks at optimizing a four-junction Ga\\(_{0.5}\\)In\\(_{0.5}\\)P/GaAs/SiGeSn/Ge cell, using a differential evolution (DE) algorithm.\nFirst, using a purely optical TMM simulation to calculate the photogenerated current in each sub-cell, we get an estimate of the overall thickness of each material we will need to achieve current-matching. The thing to optimize is then the current of the current-limiting cell in the structure; in other words we want to maximize the lowest sub-cell current, to achieve current-matching with the highest possible current. Since the differential evolution algorithm as implemented does a minimization, we are actually minimizing the negative of this value.\nOnce we have good initial values for the total layer thicknesses, we use full electrical simulation to determine the n and p type layer thicknesses to calculate a maximum possible efficiency for the 4J device.\nTo use yabox (used by Solcore’s the optimization module for the DE) we need to define a class which sets up the problem and has an ‘evaluate’ function, which will actually calculate the value we are trying to minimize for a set of parameters.\nNote: There is an issue in some versions of PyCharm with this example due to the parallel execution. To avoid this, make sure you “Run” the example as opposed to using “Run in Python Console”.\nimport numpy as np\nimport os\n\nfrom solcore import material, si\n\nimport matplotlib.pyplot as plt\n\nfrom solcore.optics.tmm import OptiStack\nfrom solcore.optics.tmm import calculate_rat\n\nfrom solcore.optimization import PDE, DE\nfrom solcore.light_source import LightSource\n\nfrom solcore.solar_cell import SolarCell\nfrom solcore.structure import Junction, Layer\nfrom solcore.solar_cell_solver import solar_cell_solver\nfrom solcore.constants import q, kb\nfrom solcore.absorption_calculator import search_db\nFirst add SiGeSn optical constants to the database:\nfrom solcore.material_system import create_new_material\n\ncreate_new_material(\"SiGeSn\", os.path.join(\"data\", \"SiGeSn_n.txt\"),\n os.path.join(\"data\", \"SiGeSn_k.txt\"), os.path.join(\"data\", \"SiGeSn_params.txt\"))\n# Note: comment out these lines after the material has been added to avoid being asked\n# each time if you want to overwrite it.\nn_iters_optics = 50\nn_iters_device = 20"
},
{
- "objectID": "solar-cell-simulation/notebooks/3a-triple_junction.html#setting-the-depth-spacing",
- "href": "solar-cell-simulation/notebooks/3a-triple_junction.html#setting-the-depth-spacing",
- "title": "Example 3a: Triple junction cell",
- "section": "Setting the depth spacing",
- "text": "Setting the depth spacing\nThe ‘position’ user option in Solcore determines at which z-points the absorption profile is calculated. You can specify this is multiple different ways:\n\na vector which specifies each position (in m) at which the depth should be calculated\na single number which specifies the spacing (in m) to generate the position vector, e.g. 1e-9 for 1 nm spacing\na list of numbers which specify the spacing (in m) to be used in each layer. This list can have EITHER the length of the number of individual layers + the number of junctions in the cell object, OR the length of the total number of individual layers including layers inside junctions.\n\nHere we use the final options, setting the spacing to use per junction/layer. We use 0.1 nm for all layers except the final layer, the Ge, where we use 10 nm.\n\nposition = len(solar_cell) * [0.1e-9]\nposition[-1] = 10e-9 # Indexing with -1 in a Python list/array gives you the last element\n\nNow that we have made the cell and set the options, we calculate and plot the EQE.\nPLOT 1: EQE of a triple junction cell, comparing TMM and BL optical methods\n\nplt.figure()\n\n# First calculate with TMM optical method\nsolar_cell_solver(solar_cell, 'qe', user_options={'wavelength': wl, 'optics_method': \"TMM\",\n 'position': position, 'recalculate_absorption': True})\n\nplt.plot(wl * 1e9, solar_cell[4].eqe(wl) * 100, 'b', label='GaInP (TMM)')\nplt.plot(wl * 1e9, solar_cell[5].eqe(wl) * 100, 'g', label='InGaAs (TMM)')\nplt.plot(wl * 1e9, solar_cell[6].eqe(wl) * 100, 'r', label='Ge (TMM)')\nplt.plot(wl * 1e9, 100 * (1 - solar_cell.reflected), 'k--', label='1-R (TMM)')\n\n# Recalculate with simple Beer-Lambert (BL) law absorption to compare\nsolar_cell_solver(solar_cell, 'qe', user_options={'wavelength': wl, 'optics_method': \"BL\",\n 'position': position, 'recalculate_absorption': True})\n\nplt.plot(wl * 1e9, solar_cell[4].eqe(wl) * 100, 'b--', alpha=0.5, label='GaInP (BL)')\nplt.plot(wl * 1e9, solar_cell[5].eqe(wl) * 100, 'g--', alpha=0.5, label='InGaAs (BL)')\nplt.plot(wl * 1e9, solar_cell[6].eqe(wl) * 100, 'r--', alpha=0.5, label='Ge (BL)')\nplt.legend()\nplt.ylim(0, 100)\nplt.ylabel('EQE (%)')\nplt.xlabel('Wavelength (nm)')\nplt.tight_layout()\nplt.title(\"(1) EQE and absorption for 3J cell using TMM and BL optical methods\")\nplt.show()\n\nSolving optics of the solar cell...\nTreating layer(s) 10 incoherently\nCalculating RAT...\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\nMaterial main/MgF2/Rodriguez-de Marcos.yml loaded.\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\nMaterial main/MgF2/Rodriguez-de Marcos.yml loaded.\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\nMaterial main/ZnS/Querry.yml loaded.\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\nMaterial main/ZnS/Querry.yml loaded.\nCalculating absorption profile...\nSolving QE of the solar cell...\nSolving optics of the solar cell...\nSolving QE of the solar cell...\n\n\n/Users/phoebe/Documents/develop/solcore5/solcore/analytic_solar_cells/depletion_approximation.py:617: RuntimeWarning: invalid value encountered in true_divide\n iqe = j_sc / current_absorbed\n/Users/phoebe/Documents/develop/solcore5/solcore/analytic_solar_cells/depletion_approximation.py:617: RuntimeWarning: invalid value encountered in true_divide\n iqe = j_sc / current_absorbed\n\n\n\n\n\nWe see that the BL absorption is higher everywhere, because it does not include any front-surface reflection. In the TMM calculation, we see interference fringes and some front-surface reflection (though due to the 4-layer ARC, the reflection is quite low everywhere).\nNow we calculate and plot the light IV under the AM1.5G spectrum, using the TMM optical method\nPLOT 2: Light IV for triple-junction cell\n\nV = np.linspace(0, 3, 300)\nsolar_cell_solver(solar_cell, 'iv', user_options={'voltages': V, 'light_iv': True,\n 'wavelength': wl, 'mpp': True,\n 'light_source': light_source,\n 'recalculate_absorption': True,\n 'optics_method': \"TMM\"})\n\nplt.figure()\nplt.plot(V, solar_cell.iv['IV'][1], 'k', linewidth=3, label='Total')\nplt.plot(V, -solar_cell[4].iv(V), 'b', label='GaInP')\nplt.plot(V, -solar_cell[5].iv(V), 'g', label='InGaAs')\nplt.plot(V, -solar_cell[6].iv(V), 'r', label='Ge')\nplt.text(1.4, 220, 'Efficieny (%): ' + str(np.round(solar_cell.iv['Eta'] * 100, 1)))\nplt.text(1.4, 200, 'FF (%): ' + str(np.round(solar_cell.iv['FF'] * 100, 1)))\nplt.text(1.4, 180, r'V$_{oc}$ (V): ' + str(np.round(solar_cell.iv[\"Voc\"], 2)))\nplt.text(1.4, 160, r'I$_{sc}$ (A/m$^2$): ' + str(np.round(solar_cell.iv[\"Isc\"], 2)))\n\nplt.legend()\nplt.ylim(0, 250)\nplt.xlim(0, 3)\nplt.ylabel('Current (A/m$^2$)')\nplt.xlabel('Voltage (V)')\nplt.title(\"(2) IV characteristics of 3J cell\")\n\nplt.show()\n\nSolving optics of the solar cell...\nTreating layer(s) 10 incoherently\nCalculating RAT...\nCalculating absorption profile...\nSolving IV of the junctions...\nSolving IV of the tunnel junctions...\nSolving IV of the total solar cell..."
+ "objectID": "solar-cell-simulation/notebooks/7b-optimization.html#optical-simulation",
+ "href": "solar-cell-simulation/notebooks/7b-optimization.html#optical-simulation",
+ "title": "Example 7b: More advanced optimization",
+ "section": "Optical simulation",
+ "text": "Optical simulation\nThis example has a more complicated structure than the previous examples, and is based around the use of Python classes. For both steps of the optimization outlined above, we define a class which contains methods which generate all the information to run the simulations, and an evaluate method which actually returns the quantity to be optimized.\nThe methods defined in the calc_min_Jsc class below, which sets up the optical part of the optimization, are:\n\n__init__: This always has to be defined for a class, as it initializes a member of the class when we call the class as calc_min_Jsc(). In this case, it sets up variables we need in the simulation, such as the wavelength, light source, and optical constants of the materials.\ncalculate: This actually runs the core of the calculation by calling calculate_rat from Solcore to run a TMM calculation; the argument of the function is a list of the layer thicknesses. It returns the absorption in each layer of the cell, the transmission, and reflection.\nevaluate: This function will be used to evaluate the figure of merit for the optimization. It calls calculate and then calculates the maximum possible current in each sub-cell using the photon flux. It then finds the limiting (minimum) \\(J_{sc}\\) out of the four sub-cells, and returns the negative of this value. The way the DE algorithm is implemented means it will always try to minimize what the evaluate function returns, so although we want to maximize the limiting \\(J_{sc}\\), we must implement this as minimizing its negative.\nplot: Takes the results from calculate and plots them to visualize our results.\n\n\nclass calc_min_Jsc():\n\n def __init__(self):\n # Initialize an instance of the class; set some information which will be used in each iteration of the calculation:\n # materials, wavelengths, the light source\n\n wl = np.linspace(300, 1900, 800)\n\n # Materials\n SiGeSn = material('SiGeSn')()\n\n GaAs = material('GaAs')()\n InGaP = material('GaInP')(In=0.5)\n Ge = material('Ge')()\n\n Ta2O5_index = search_db(os.path.join(\"Ta2O5\", \"Rodriguez\"))[0][0]\n\n # We make these attributes of 'self' so they can be accessed by the class object\n # We are also creating lists of wavelengths and corresponding n and k data from\n # the Solcore materials - the reason for this is that there is currently an issue with using the Solcore\n # material class in parallel computations. Thus the information for the n and k data is saved here as a list\n # rather than a material object (see the documentation of OptiStack for the different acceptable formats\n # to pass optical constants for an OptiStack)\n\n self.wl = wl\n self.SiGeSn = [self.wl, SiGeSn.n(self.wl*1e-9), SiGeSn.k(self.wl*1e-9)]\n self.Ge = [self.wl, Ge.n(self.wl*1e-9), Ge.k(self.wl*1e-9)]\n\n self.InGaP = [self.wl, InGaP.n(self.wl*1e-9), InGaP.k(self.wl*1e-9)]\n self.GaAs = [self.wl, GaAs.n(self.wl*1e-9), GaAs.k(self.wl*1e-9)]\n self.MgF2 = [self.wl, material('MgF2')().n(self.wl*1e-9), material('MgF2')().k(self.wl*1e-9)]\n\n self.Ta2O5 = [self.wl, material(str(Ta2O5_index),\n nk_db=True)().n(self.wl*1e-9), material(str(Ta2O5_index),\n nk_db=True)().k(self.wl*1e-9)]\n\n # Assuming an AM1.5G spectrum\n self.spectr = LightSource(source_type='standard', version='AM1.5g', x=self.wl,\n output_units='photon_flux_per_nm', concentration=1).spectrum(self.wl)[1]\n\n def calculate(self, x):\n # x[0] = MgF2 thickness (anti-reflection coating)\n # x[1] = Ta2O5 thickness (anti-reflection coating)\n # x[2] = InGaP (top junction) thickness\n # x[3] = GaAs (second junction) thickness\n # x[4] = SiGeSn (third junction) thickness\n\n # Keep the thickness of the bottom cell constant; from a purely optical point of view, this should be infinitely thick,\n # so there is no point in optimizing the thickness\n\n SC = [[x[0]] + self.MgF2, [x[1]] + self.Ta2O5, [x[2]] + self.InGaP, [x[3]] + self.GaAs, [x[4]] + self.SiGeSn,\n [300e3] + self.Ge]\n\n # create the OptiStack\n full_stack = OptiStack(SC, no_back_reflection=False)\n\n # calculate reflection, transmission, and absorption in each layer. We are specifying that the last layer,\n # a very thick Ge substrate, should be treated incoherently, otherwise we would see very narrow, unphysical oscillations\n # in the R/A/T spectra.\n\n c_list = ['c']*len(SC)\n c_list[-1] = \"i\"\n\n RAT = calculate_rat(full_stack, self.wl, no_back_reflection=False, coherent=False,\n coherency_list=c_list)\n\n # extract absorption per layer\n A_InGaP = RAT['A_per_layer'][3]\n A_GaAs = RAT['A_per_layer'][4]\n A_SiGeSn = RAT['A_per_layer'][5]\n A_Ge = RAT['A_per_layer'][6]\n\n return A_InGaP, A_GaAs, A_SiGeSn, A_Ge, RAT['T'], RAT['R']\n\n def evaluate(self, x):\n\n A_InGaP, A_GaAs, A_SiGeSn, A_Ge, _, _ = self.calculate(x)\n\n # Calculate photo-generated currents using the AM1.5 G spectrum for each layer -- this is the current with 100%\n # internal quantum efficiency (i.e. every absorbed photon generates an electron-hole pair which is collected).\n Jsc_InGaP = 0.1 * q * np.trapz(A_InGaP * self.spectr, self.wl)\n Jsc_GaAs = 0.1 * q * np.trapz(A_GaAs * self.spectr, self.wl)\n Jsc_SiGeSn = 0.1 * q * np.trapz(A_SiGeSn * self.spectr, self.wl)\n Jsc_Ge = 0.1 * q * np.trapz(A_Ge * self.spectr, self.wl)\n\n # Find the limiting current by checking which junction has the lowest current. Then take the negative since\n # we need to minimize (not maximize)\n limiting_Jsc = -min([Jsc_InGaP, Jsc_GaAs, Jsc_SiGeSn, Jsc_Ge])\n\n return limiting_Jsc\n\n def plot(self, x):\n\n A_InGaP, A_GaAs, A_SiGeSn, A_Ge, T, R = self.calculate(x)\n\n plt.figure()\n plt.plot(self.wl, A_InGaP, label='InGaP')\n plt.plot(self.wl, A_GaAs, label='A_GaAs')\n plt.plot(self.wl, A_SiGeSn, label='SiGeSn')\n plt.plot(self.wl, A_Ge, label = 'Ge')\n plt.plot(self.wl, T, label='T')\n plt.plot(self.wl, R, label='R')\n plt.legend()\n plt.xlabel('Wavelength (nm)')\n plt.ylabel('R/A/T')\n plt.show()\n\nNow that we have defined a class containing the relevant information and methods for the optimization process, we need to make an instance of that class for the DE algorithm to use.\n\nDE_class = calc_min_Jsc()\n\nWe also set the upper and lower bounds on thickness for each layer:\n\nMgF2 (ARC layer 1)\nTa2O5 (ARC layer 2)\nGaInP (top junction)\nGaAs (2nd junction)\nSiGeSn (3rd junction)\n\nWe will not optimize the thickness of the bottom Ge cell at this stage; from a purely optical point of view, this should be infinitely thick to maximize absorption, which is of course not the case for a device. We will set the thickness of the Ge at 300 \\(\\mu\\)m.\n\nbounds_optics = [[10,150], [10,105], [200, 1000], [500, 10000], [500, 10000]]\n\nNow, we pass the function which will be minimized to the DE (parallel differential evolution) solver. The bounds argument sets upper and lower bounds for each parameter. PDE_obj contains all the information to run the DE but does not actually start the calculation (like the calc_min_Jsc class defined above, DE is a class and not a function.\nTo actually run the DE, we use the .solve() method of the DE object class:\n\nPDE_obj = DE(DE_class.evaluate, bounds=bounds_optics, maxiters=n_iters_optics)\n\nres = PDE_obj.solve()\n\nNote: Due to issues with parallel computations depending on your operating system etc., we used the DE class here. There is also a parallelized version of this class, called PDE, which is already implemented above. If you are running this example on your own computer, you can run the example in parallel by simple changing DE to PDE.\nPDE_obj.solve() returns a list of five items: - res[0] is a list of the parameters which gave the overall minimized value at the end of the process - res[1] is that minimized value - res[2] is the evolution of the best population (the best population from each iteration) - res[3] is the evolution of the minimized value, i.e. the best fitness in iteration - res[4] is the evolution of the mean fitness over the iterations\nLet’s plot the absorption in each layer using the optimized thicknesses:\n\n# best population:\nbest_pop = res[0]\n\nprint('parameters for best result:', best_pop, '\\n', 'optimized Jsc value (mA/cm2):', -res[1])\n\n# plot the result at these best parameters\nDE_class.plot(best_pop)\n\nparameters for best result: [ 119.25428258 83.4922207 550.47007392 2040.13817627 1420.56539299] \n optimized Jsc value (mA/cm2): 14.031909365709433\n\n\n\n\n\nAnd the evolution of the best and mean fitness with each iteration of the DE algorithm:\n\nbest_fitn_evo = res[3]\nmean_fitn_evo = res[4]\n\n# plot evolution of the fitness of the best population per iteration\n\nplt.figure()\nplt.plot(-best_fitn_evo, '-k', label='Best fitness')\nplt.plot(-mean_fitn_evo, '-r', label='Mean fitness')\nplt.xlabel('Iteration')\nplt.ylabel('Fitness')\nplt.legend()\nplt.show()\n\n\n\n\nWe see that the fitness of the best population ‘jumps’ every few iterations as a new best population is found, while the mean fitness increases slowly as the whole population gradually improves. Ideally, we would like to see the fitness converging, but it may be necessary to increase the number of iterations to achieve this."
},
{
- "objectID": "solar-cell-simulation/notebooks/3a-triple_junction.html#cell-behaviour-under-concentration",
- "href": "solar-cell-simulation/notebooks/3a-triple_junction.html#cell-behaviour-under-concentration",
- "title": "Example 3a: Triple junction cell",
- "section": "Cell behaviour under concentration",
- "text": "Cell behaviour under concentration\nMulti-junction cells are often used in concetrator PV applications. Here, we look at the effect of increasing the concentration on the \\(V_{oc}\\), \\(J_{sc}\\) and the efficiency \\(\\eta\\). Note that in order to reproduce the behaviour we see in real concentrator cells (initial increase in efficiency due to increased \\(V_{oc}\\), with a reduction in efficiency at very high concentrations due to a decrease in fill factor due to series resistance), we need to specify a series resistance in the cell definition above; if not, the simulated efficiency would continue to increase.\nWe consider concentrations between 1x and 3000x, linearly spaced on a log scale:\n\nconcentration = np.linspace(np.log(1), np.log(3000), 20)\nconcentration = np.exp(concentration)\n\nCreate empty arrays to store the data (this is preferable to simply appending data in a loop since it pre-allocates the memory needed to store the arrays):\n\nEffs = np.empty_like(concentration) # creates an empty array with the same shape as the concentration array\nVocs = np.empty_like(concentration)\nIscs = np.empty_like(concentration)\n\nV = np.linspace(0, 3.5, 300)\n\nLoop through the concentrations. We use only the direct spectrum (AM1.5D) since diffuse light will not be concentrated:\n\nfor i1, conc in enumerate(concentration):\n\n # We make a light source with the concentration being considered. We also use AM1.5D (direct only) rather than AM1.5G\n # (direct + diffuse):\n light_conc = LightSource(source_type='standard', x=wl, version='AM1.5d', concentration=conc)\n\n solar_cell_solver(solar_cell, 'iv', user_options={'voltages': V, 'light_iv': True,\n 'wavelength': wl, 'mpp': True,\n 'light_source': light_conc});\n\n # Save the calculated values in the arrays:\n Effs[i1] = solar_cell.iv[\"Eta\"] * 100\n Vocs[i1] = solar_cell.iv[\"Voc\"]\n Iscs[i1] = solar_cell.iv[\"Isc\"]\n\nPLOT 3: Efficiency, open-circuit voltage and short-circuit current at different concentrations for the 3J cell\n\nplt.figure(figsize=(10, 3))\nplt.subplot(131)\nplt.semilogx(concentration, Effs, '-o')\nplt.ylabel('Efficiency (%)')\nplt.xlabel('Concentration')\nplt.title(\"(3) Efficiency, V$_{oc}$ and J$_{sc}$ vs. concentration for 3J cell\")\n\nplt.subplot(132)\nplt.semilogx(concentration, Vocs, '-o')\nplt.ylabel(r'V$_{OC}$ (V)')\nplt.xlabel('Concentration')\n\nplt.subplot(133)\nplt.plot(concentration, Iscs / 10000, '-o')\nplt.ylabel(r'J$_{SC}$ (A/cm$^2$)')\nplt.xlabel('Concentration')\nplt.tight_layout()\nplt.show()"
+ "objectID": "solar-cell-simulation/notebooks/7b-optimization.html#device-optimization",
+ "href": "solar-cell-simulation/notebooks/7b-optimization.html#device-optimization",
+ "title": "Example 7b: More advanced optimization",
+ "section": "Device optimization",
+ "text": "Device optimization\nAs discussed above, we approach this optimization in two phases: a faster optical simulation to get approximate total thicknesses for each junction, and then a device optimization. We take a very similar approach and define a class to contain the information and methods needed for the device optimization:\n\nclass optimize_device():\n\n def __init__(self, ARC_thickness):\n self.ARC = ARC_thickness\n self.position = [1e-10] * 10 + [5e-8] # 0.1 nm spacing in all layers except the Ge\n\n\n def make_cell(self, x):\n\n #x[0]: total InGaP thickness\n #x[1]: total InGaAs thickness\n #x[2]: total SiGeSn thickness\n #x[3]: total Ge thickness\n\n #x[4]: InGaP n thickness\n #x[5]: InGaAs n thickness\n #x[6]: SiGeSn n thickness\n #x[7]: Ge n thickness\n\n e_charge = si('1eV')\n\n # materials\n Ta2O5_index = search_db(os.path.join(\"Ta2O5\", \"Rodriguez\"))[0][0]\n SiGeSn = material('SiGeSn')\n\n GaAs = material('GaAs')\n InGaP = material('GaInP')\n Ge = material('Ge')\n MgF2 = material('MgF2')()\n Ta2O5 = material(str(Ta2O5_index), nk_db=True)()\n AlInP = material(\"AlInP\")\n\n window_material = AlInP(Al=0.52)\n\n GaInP_mobility_h = 0.03 #\n GaInP_lifetime_h = 1e-8\n GaInP_D_h = GaInP_mobility_h * kb * 300 / e_charge\n GaInP_L_h = np.sqrt(GaInP_D_h * GaInP_lifetime_h)\n GaInP_mobility_e = 0.015\n GaInP_lifetime_e = 1e-8\n GaInP_D_e = GaInP_mobility_e * kb * 300 / e_charge\n GaInP_L_e = np.sqrt(GaInP_D_e * GaInP_lifetime_e)\n\n top_cell_n_material = InGaP(In=0.5, Nd=si(\"2e18cm-3\"), hole_diffusion_length=GaInP_L_h,\n hole_mobility=GaInP_mobility_h)\n top_cell_p_material = InGaP(In=0.5, Na=si(\"2e17cm-3\"), electron_diffusion_length=GaInP_L_e,\n electron_mobility=GaInP_mobility_e)\n\n # MID CELL - GaAs\n\n GaAs_mobility_h = 0.85 #\n GaAs_lifetime_h = 1e-8\n GaAs_D_h = GaAs_mobility_h * kb * 300 / e_charge\n GaAs_L_h = np.sqrt(GaAs_D_h * GaAs_lifetime_h)\n GaAs_mobility_e = 0.08\n GaAs_lifetime_e = 1e-8\n GaAs_D_e = GaAs_mobility_e * kb * 300 / e_charge\n GaAs_L_e = np.sqrt(GaAs_D_e * GaAs_lifetime_e)\n\n mid_cell_n_material = GaAs(Nd=si(\"1e18cm-3\"), hole_diffusion_length=GaAs_L_h,\n hole_mobility=GaAs_mobility_h)\n mid_cell_p_material = GaAs(Na=si(\"1e17cm-3\"), electron_diffusion_length=GaAs_L_e,\n electron_mobility=GaAs_mobility_e)\n\n\n SiGeSn.band_gap = si('0.77eV') # from PL measurement\n SiGeSn_L_h = si('0.35um')\n SiGeSn_L_e = si('5um')\n SiGeSn_lifetime_e = 1e-6\n SiGeSn_lifetime_h = 1e-6\n SiGeSn_mobility_h = SiGeSn_L_h ** 2 * e_charge / (SiGeSn_lifetime_h * kb * 300)\n SiGeSn_mobility_e = SiGeSn_L_e ** 2 * e_charge / (SiGeSn_lifetime_e * kb * 300)\n\n pen_cell_n_material = SiGeSn(Nd=si(\"1e18cm-3\"), hole_diffusion_length=SiGeSn_L_h,\n relative_permittivity=16, hole_mobility=SiGeSn_mobility_h)\n pen_cell_p_material = SiGeSn(Na=si(\"1e17cm-3\"), electron_diffusion_length=SiGeSn_L_e,\n relative_permittivity=16, electron_mobility=SiGeSn_mobility_e)\n\n Ge_lifetime_h = 1e-6\n Ge_L_h = si('500nm')\n Ge_mobility_h = Ge_L_h ** 2 * e_charge / (Ge_lifetime_h * kb * 300)\n Ge_mobility_e = 0.18\n Ge_lifetime_e = 1e-6\n Ge_D_e = Ge_mobility_e * kb * 300 / e_charge\n Ge_L_e = np.sqrt(Ge_D_e * Ge_lifetime_e)\n\n bot_cell_n_material = Ge(Nd=si(\"2e18cm-3\"), hole_diffusion_length=Ge_L_h,\n hole_mobility=Ge_mobility_h)\n bot_cell_p_material = Ge(Na=si(\"1e17cm-3\"), electron_diffusion_length=Ge_L_e,\n electron_mobility=Ge_mobility_e)\n\n\n\n solar_cell = SolarCell([\n Layer(si(self.ARC[0], 'nm'), material=MgF2), Layer(si(self.ARC[1], 'nm'), material=Ta2O5),\n Junction([Layer(si(25, 'nm'), material=window_material, role='window'),\n Layer(si(x[4], 'nm'), material=top_cell_n_material, role='emitter'),\n Layer(si(x[0]-x[4], 'nm'), material=top_cell_p_material, role='base'),\n ], sn=1, sp=1, kind='DA'),\n Junction([Layer(si(x[5], 'nm'), material=mid_cell_n_material, role='emitter'),\n Layer(si(x[1]-x[5], 'nm'), material=mid_cell_p_material, role='base'),\n ], sn=1, sp=1, kind='DA'),\n Junction([Layer(si(x[6], 'nm'), material=pen_cell_n_material, role='emitter'),\n Layer(si(x[2]-x[6], 'nm'), material=pen_cell_p_material, role='base'),\n ], sn=1, sp=1, kind='DA'),\n Junction([Layer(si(x[7], 'nm'), material=bot_cell_n_material, role='emitter'),\n Layer(si(x[3]-x[7], 'nm'), material=bot_cell_p_material, role='base'),\n ], sn=1, sp=1, kind='DA'),\n ], shading=0.0, substrate=bot_cell_n_material)\n\n return solar_cell\n\n def calculate(self, x):\n\n light_source = LightSource(source_type='standard', version='AM1.5g')\n\n wl = np.linspace(300, 1850, 500) * 1e-9\n\n solar_cell = self.make_cell(x)\n\n V = np.linspace(0, 3.5, 300)\n solar_cell_solver(solar_cell, 'iv',\n user_options={'voltages': V, 'light_iv': True, 'wavelength': wl, 'mpp': True,\n 'light_source': light_source,\n 'optics_method': 'TMM', 'BL_correction': True,\n 'position': self.position})\n\n return wl, solar_cell\n\n def evaluate(self, x):\n\n _, solar_cell = self.calculate(x)\n efficiency = solar_cell.iv[\"Eta\"]\n\n return -efficiency\n\n def plot(self, x):\n\n wl, solar_cell = self.calculate(x)\n\n V = solar_cell.iv['IV'][0]\n\n efficiency = solar_cell.iv[\"Eta\"]\n pmax = solar_cell.iv[\"Pmpp\"]\n ff = solar_cell.iv[\"FF\"]\n voc = solar_cell.iv[\"Voc\"]\n isc = solar_cell.iv[\"Isc\"]\n\n plt.figure()\n\n plt.plot(V, solar_cell.iv['IV'][1] / 10, 'k', linewidth=3, label='Total')\n plt.plot(V, -solar_cell[2].iv(V) / 10, 'b', label='GaInP')\n plt.plot(V, -solar_cell[3].iv(V) / 10, 'g', label='GaAs')\n plt.plot(V, -solar_cell[4].iv(V) / 10, 'r', label='SiGeSn')\n plt.plot(V, -solar_cell[5].iv(V) / 10, 'y', label='Ge')\n plt.text(2, 10, '$\\eta = $' + str(round(efficiency * 100, 1)) + '%')\n plt.text(2, 8,'Pmax='+str(round(pmax,1))+'W/m$^2$')\n plt.text(2, 9, 'FF = ' + str(round(ff * 100, 1)) + '%')\n plt.text(2,7,'Voc='+str(round(voc,1))+'V')\n plt.text(2,6, 'Jsc='+str(round(0.1*isc,1))+'mA/cm$^2$')\n\n plt.legend()\n plt.ylim(0, 18)\n plt.xlim(0, 3.5)\n plt.ylabel('Current (mA/cm$^2$)')\n plt.xlabel('Voltage (V)')\n\n plt.show()\n\n solar_cell_solver(solar_cell, 'qe',\n user_options={'wavelength': wl, 'optics_method': 'TMM', 'BL_correction': True, 'position': self.position})\n\n plt.figure()\n plt.plot(wl * 1e9, solar_cell[2].eqe(wl) * 100, 'b', label='InGaP')\n plt.plot(wl * 1e9, solar_cell[3].eqe(wl) * 100, 'g', label='InGaAs')\n plt.plot(wl * 1e9, solar_cell[4].eqe(wl) * 100, 'r', label='SiGeSn')\n plt.plot(wl * 1e9, solar_cell[5].eqe(wl) * 100, 'y', label='Ge')\n plt.plot(wl * 1e9, solar_cell.absorbed * 100, 'k--', label='Absorption')\n # plt.plot(wl * 1e9, solar_cell[5].eqe(wl)*100, 'y', label='Ge')\n\n plt.legend(loc='upper right')\n plt.xlim(290, 1850)\n plt.ylim(0, 100)\n plt.ylabel('EQE (%)')\n plt.xlabel('Wavelength (nm)')\n plt.show()\n\nNow that the layer thicknesses have been optimized from an optical point of view, we want to design the device (or at least a simplified version, by calculating a more realistic EQE. Obviously additional parameters like the doping of the layers could be varied too. The list of parameters x will be:\n\nx[0]: total InGaP thickness\nx[1]: total InGaAs thickness\nx[2]: total SiGeSn thickness\nx[3]: total Ge thickness\nx[4]: InGaP emitter thickness\nx[5]: InGaAs emitter thickness\nx[6]: SiGeSn emitter thickness\nx[7]: Ge emitter thickness\n\nWe will keep the ARC thicknesses fixed at the exact values obtained in the optical simulation. For the other layers, we generate upper and lower bounds: total layer thickness between 75% and 125% of values fitted in TMM calculation. For Ge, we set the starting value at 200 \\(\\mu\\)m.\n\nstarting_params = np.append(best_pop[2:], [200000])\n\nlower = 0.75*starting_params\nupper = 1.25*starting_params\n\n# upper and lower bounds for the n-type (highly doped) layers\nlower_ntype = [20, 20, 20, 20]\n\nupper_ntype = [200, 300, 300, 500]\n\nall_lower = np.append(lower, lower_ntype)\n\nall_upper = np.append(upper, upper_ntype)\n\n# full list of bounds\nall_bounds = np.stack((all_lower, all_upper)).T\n\nSimilar to the optical simulation above, we now create an object of this class (setting the ARC thickness when we create the class), then create an object of the DE class, and call the .solve method.\n\n# DE calculation for the electrical simulation\n\nDE_class_DA = optimize_device(best_pop[0:2])\n\n# default population size = 5*number of params\nPDE_obj_DA = DE(DE_class_DA.evaluate, bounds=all_bounds, maxiters=n_iters_device)\n\n# solve, i.e. minimize the problem\nres_DA = PDE_obj_DA.solve()\n\nWe plot the QE and IV for the best population:\n\nbest_pop_DA = res_DA[0]\n\nprint('parameters for best result:', best_pop_DA, 'optimized efficiency (%)', res_DA[1]*100)\n\n# plot the result at these best parameters\nDE_class_DA.plot(best_pop_DA)\n\nparameters for best result: [5.64211912e+02 1.67613064e+03 1.08983977e+03 1.99726177e+05\n 1.71208031e+02 1.18811297e+02 4.59410919e+01 1.07605130e+02] optimized efficiency (%) -35.688813260065366\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\n1 results found.\npageid shelf book page filepath hasrefractive hasextinction rangeMin rangeMax points\n475 main Ta2O5 Rodriguez-de_Marcos main/Ta2O5/Rodriguez-de Marcos.yml 1 1 0.0294938 1.51429 212\nSolving optics of the solar cell...\nTreating layer(s) 10 incoherently\nCalculating RAT...\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\nMaterial main/Ta2O5/Rodriguez-de Marcos.yml loaded.\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\nMaterial main/Ta2O5/Rodriguez-de Marcos.yml loaded.\nCalculating absorption profile...\nSolving IV of the junctions...\nSolving IV of the tunnel junctions...\nSolving IV of the total solar cell...\nSolving optics of the solar cell...\nAlready calculated reflection, transmission and absorption profile - not recalculating. Set recalculate_absorption to True in the options if you want absorption to be calculated again.\nSolving QE of the solar cell...\n\n\n\n\n\n/Users/phoebe/Documents/develop/solcore5/solcore/analytic_solar_cells/depletion_approximation.py:617: RuntimeWarning: invalid value encountered in true_divide\n iqe = j_sc / current_absorbed\n\n\n\n\n\n\nbest_pop_evo = res_DA[2]\nbest_fitn_evo = res_DA[3]\nmean_fitn_evo = res_DA[4]\nfinal_fitness = res_DA[1]\n\n# plot evolution of the fitness of the best population per iteration\n\nplt.figure()\nplt.plot(-best_fitn_evo, '-k', label='Best fitness')\nplt.plot(-mean_fitn_evo, '-r', label='Mean fitness')\nplt.xlabel('Iteration')\nplt.ylabel('Fitness')\nplt.legend()\nplt.show()"
},
{
- "objectID": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html",
- "href": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html",
- "title": "Example 5a: Ultra-thin GaAs cell with diffraction grating",
+ "objectID": "solar-cell-simulation/notebooks/7b-optimization.html#comparison",
+ "href": "solar-cell-simulation/notebooks/7b-optimization.html#comparison",
+ "title": "Example 7b: More advanced optimization",
+ "section": "Comparison",
+ "text": "Comparison\nCompare the total layer thicknesses obtained from the optical and electrical simulations:\n\nprint(\"Comparison of thicknesses from optical/electrical optimization:\")\nprint('GaInP total thickness: %.1f/%.1f nm' % (best_pop[2], best_pop_DA[0]))\nprint('GaAs total thickness: %.1f/%.1f nm' % (best_pop[3], best_pop_DA[1]))\nprint('SiGeSn total thickness: %.1f/%.1f nm' % (best_pop[4], best_pop_DA[2]))\n\nComparison of thicknesses from optical/electrical optimization:\nGaInP total thickness: 550.5/564.2 nm\nGaAs total thickness: 2040.1/1676.1 nm\nSiGeSn total thickness: 1420.6/1089.8 nm\n\n\nNOTE: You may have an issue running the parallel version of this example (change DE to PDE) if you are using Windows. To get around this, you need to use the if __name__ == \"__main__\" construction. The issue arises because the multiprocessing module uses a different process on Windows than on UNIX systems which will throw errors if this construction is not used. You need to put everything apart from the module imports at the top of the script and the class definitions inside a function called main, and execute this with:\n\n# if __name__ == '__main__':\n# main()"
+ },
+ {
+ "objectID": "solar-cell-simulation/notebooks/1a-simple_cell.html",
+ "href": "solar-cell-simulation/notebooks/1a-simple_cell.html",
+ "title": "Example 1a: Simple Si solar cell",
"section": "",
- "text": "In previous examples, we have considered a few different methods used to improve absorption in solar cells: anti-reflection coatings, to decrease front-surface reflection, metallic rear mirrors to reduce transmission and increase the path length of light in the cell, and textured surfaces (with pyramids) which are used on Si cells to reduce reflection and increase the path length of light in the cell. Another method which can be used for light-trapping is the inclusion of periodic structures such as diffraction gratings or photonic crystals; here, we will consider an ultra-thin (80 nm) GaAs cell with a diffraction grating.\nThis example is based on the simulations done for this paper.\nNote: This example requires that you have a working S4 installation.\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport os\n\nfrom solcore import si, material\nfrom solcore.structure import Layer\nfrom solcore.light_source import LightSource\nfrom solcore.solar_cell import SolarCell\nfrom solcore.constants import q\nfrom solcore.absorption_calculator import search_db\n\nfrom rayflare.rigorous_coupled_wave_analysis.rcwa import rcwa_structure\nfrom rayflare.transfer_matrix_method.tmm import tmm_structure\nfrom rayflare.options import default_options"
+ "text": "In this first set of examples, we will look at simple planar solar cells (Si and GaAs).\nIn this script, we will look at the difference between Beer-Lambert absorption calculations, using the Fresnel equations for front-surface reflection, and using the transfer-matrix model.\nFirst, lets import some very commonly-used Python packages:\nimport numpy as np\nimport matplotlib.pyplot as plt\nNumpy is a Python library which adds supports for multi-dimensional data arrays and matrices, so it is very useful for storing and handling data. You will probably use it in every Solcore script you write. Here, it is imported under the alias ‘np’, which you will see used below. matplotlib is used for making plots, and is imported under the alias ‘plt’. Both the ‘np’ and ‘plt’ aliases are extremely commonly used in Python programming.\nNow, let’s import some things from Solcore (which will be explained as we use them):\nfrom solcore import material, si\nfrom solcore.solar_cell import SolarCell, Layer, Junction\nfrom solcore.solar_cell_solver import solar_cell_solver\nfrom solcore.interpolate import interp1d"
},
{
- "objectID": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html#setting-up",
- "href": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html#setting-up",
- "title": "Example 5a: Ultra-thin GaAs cell with diffraction grating",
- "section": "Setting up",
- "text": "Setting up\nFirst, defining all the materials. We are just going to do an optical simulation, so don’t have to worry about doping levels and other parameters which would affect the electrical performance of the cell.\n\nInAlP = material('AlInP')(Al=0.5) # In0.5Al0.5P\nGaAs = material('GaAs')()\nInGaP = material('GaInP')(In=0.5) # Ga0.5In0.5P\nSiN = material('Si3N4')() # for the ARC\nAl2O3 = material('Al2O3P')() # for the ARC\n\nAir = material('Air')()\n\nThe optical constants used for the silver are very important, so we search for a known reliable dataset (from this paper).\n\nAg_pageid = search_db(os.path.join(\"Ag\", \"Jiang\"))[0][0]\nAg = material(str(Ag_pageid), nk_db=True)()\n\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\n1 results found.\npageid shelf book page filepath hasrefractive hasextinction rangeMin rangeMax points\n2 main Ag Jiang main/Ag/Jiang.yml 1 1 0.3 2.0 1701\n\n\nAM0 spectrum (photon flux) for calculating currents. For space applications (i.e. above the atmosphere) we are often interested in AM0. We use Solcore’s LightSource class, then extract the AM0 photon flux at the wavelengths we are going to use in the simulations.\n\nwavelengths = np.linspace(303, 1000, 200) * 1e-9\n\nAM0_ls = LightSource(source_type='standard', version='AM0', x=wavelengths, output_units=\"photon_flux_per_m\")\nAM0 = AM0_ls.spectrum(x=wavelengths)[1] # Photon flux; used to calculate photogenerated current later on\n\nSetting options. We choose s polarization because, for normal incidence, there will not be a difference in the results for \\(s\\) and \\(p\\) polarization (and thus for unpolarized light, u, which would be calculated as the average of the results for \\(s\\) and \\(p\\) polarization. We could set the polarization to u for equivalent results (at normal incidence only), but this would take longer because then RayFlare has to run two calculations and then average them.\nThe other key option is the number of Fourier orders retained: rigorous coupled-wave analysis (RCWA) is a Fourier-space method, and we have to specify how many Fourier orders should be retained in the calculation. As we increase the number of orders, the calculation should become more accurate and eventually converge, but the computation time increases (it scales with the cube of the number of orders).\n\noptions = default_options()\noptions.pol = 's'\noptions.wavelengths = wavelengths\noptions.orders = 100 # Reduce the number of orders to speed up the calculation."
+ "objectID": "solar-cell-simulation/notebooks/1a-simple_cell.html#defining-materials",
+ "href": "solar-cell-simulation/notebooks/1a-simple_cell.html#defining-materials",
+ "title": "Example 1a: Simple Si solar cell",
+ "section": "Defining materials",
+ "text": "Defining materials\nTo define our solar cell, we first want to define some materials. Then we want to organise those materials into Layers, organise those layers into a Junction, and then finally define a SolarCell with that Junction.\nFirst, let’s define a Si material. Silicon, along with many other semiconductors, dielectrics, and metals common in solar cells, is included in Solcore’s database:\n\nSi = material(\"Si\")\nGaAs = material(\"GaAs\")\n\nThis creates an instance of the Si and GaAs materials. However, to use this in a solar cell we need to do specify some more information, such as the doping level. The ‘si’ function comes in handy here to convert all quantities to based units e.g. m, m^(-3)…\n\nSi_n = Si(Nd=si(\"1e21cm-3\"), hole_diffusion_length=si(\"10um\"), relative_permittivity=11.7)\nSi_p = Si(Na=si(\"1e16cm-3\"), electron_diffusion_length=si(\"400um\"), relative_permittivity=11.7)"
},
{
- "objectID": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html#on-substrate-device",
- "href": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html#on-substrate-device",
- "title": "Example 5a: Ultra-thin GaAs cell with diffraction grating",
- "section": "On-substrate device",
- "text": "On-substrate device\nThis device is still on the GaAs substrate (it is also inverted compared to the other devices, since it represents the device before patterning and lift-off). We create the simulation object using tmm_structure class, and then use the .calculate function defined for the class to calculate the reflection, absorption per layer, and transmission.\n\nprint(\"Calculating on-substrate device...\")\n\nstruct = SolarCell([Layer(si('20nm'), InAlP), Layer(si('85nm'), GaAs),\n Layer(si('20nm'), InGaP)])\n\n# make TMM structure for planar device\nTMM_setup = tmm_structure(struct, incidence=Air, transmission=GaAs)\n\n# calculate\nRAT_TMM_onsubs = TMM_setup.calculate(options)\n\nAbs_onsubs = RAT_TMM_onsubs['A_per_layer'][:,1] # absorption in GaAs\n# indexing of A_per_layer is [wavelengths, layers]\n\nR_onsubs = RAT_TMM_onsubs['R']\nT_onsubs = RAT_TMM_onsubs['T']\n\nCalculating on-substrate device..."
+ "objectID": "solar-cell-simulation/notebooks/1a-simple_cell.html#defining-layers",
+ "href": "solar-cell-simulation/notebooks/1a-simple_cell.html#defining-layers",
+ "title": "Example 1a: Simple Si solar cell",
+ "section": "Defining layers",
+ "text": "Defining layers\nNow we define the emitter and base layers we will have in the solar cell; we specify their thickness, the material they are made of and the role they play within the cell (emitter or base)\n\nemitter_layer = Layer(width=si(\"600nm\"), material=Si_n, role='emitter')\nbase_layer = Layer(width=si(\"200um\"), material=Si_p, role='base')\n\ncreate the p-n junction using the layers defined above. We set kind=“DA” to tell Solcore to use the Depletion Approximation in the calculation (we will discuss the different electrical solver options more later on):\n\nSi_junction = Junction([emitter_layer, base_layer], kind=\"DA\")"
},
{
- "objectID": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html#planar-silver-mirror-device",
- "href": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html#planar-silver-mirror-device",
- "title": "Example 5a: Ultra-thin GaAs cell with diffraction grating",
- "section": "Planar silver mirror device",
- "text": "Planar silver mirror device\nThis device is now flipped (in fabrication terms, the back mirror was applied and then the device lifted off). It has a silver back mirror, which should increase reflection and the rear surface so that less light is lost to the substrate.\n\nprint(\"Calculating planar Ag mirror device...\")\n\nsolar_cell_TMM = SolarCell([Layer(material=InGaP, width=si('20nm')),\n Layer(material=GaAs, width=si('85nm')),\n Layer(material=InAlP, width=si('20nm'))],\n substrate=Ag)\n\nTMM_setup = tmm_structure(solar_cell_TMM, incidence=Air, transmission=Ag)\n\nRAT_TMM = TMM_setup.calculate(options)\n\nAbs_TMM = RAT_TMM['A_per_layer'][:, 1]\nAbs_TMM_InAlPonly = RAT_TMM['A_per_layer'][:, 2]\nAbs_TMM_InGaPonly = RAT_TMM['A_per_layer'][:, 0]\nR_TMM = RAT_TMM['R']\nT_TMM = RAT_TMM['T']\n\nCalculating planar Ag mirror device...\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\nMaterial main/Ag/Jiang.yml loaded.\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\nMaterial main/Ag/Jiang.yml loaded."
+ "objectID": "solar-cell-simulation/notebooks/1a-simple_cell.html#setting-user-options",
+ "href": "solar-cell-simulation/notebooks/1a-simple_cell.html#setting-user-options",
+ "title": "Example 1a: Simple Si solar cell",
+ "section": "Setting user options",
+ "text": "Setting user options\nWavelengths we want to use in the calculations; wavelengths between 300 and 1200 nm, at 200 evenly spaced intervals:\n\nwavelengths = si(np.linspace(300, 1200, 200), \"nm\")\n\nNote that here and above in defining the layers and materials we have used the “si()” function: you can use this to automatically convert quantities in other units to base SI units (e.g. nanometres to metres).\nNow we specify some options for running the calculation. Initially we want to use the Beer-Lambert absorption law to calculate the optics of the cell (“BL”). We set the wavelengths we want to use, and we set “recalculate_absorption” to True so that further down in the script when we try different optics methods, Solcore knows we want to re-calculate the optics of the cell. We specify the options in a Python format called a dictionary:\n\noptions = {\n \"recalculate_absorption\": True,\n \"optics_method\": \"BL\",\n \"wavelength\": wavelengths\n }"
},
{
- "objectID": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html#nanophotonic-grating-device",
- "href": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html#nanophotonic-grating-device",
- "title": "Example 5a: Ultra-thin GaAs cell with diffraction grating",
- "section": "Nanophotonic grating device",
- "text": "Nanophotonic grating device\nFinally, this device has a grating made of silver and SiN, followed by a planar silver back mirror; this leads to diffraction, increasing the path length of light in the cell, while keeping reflection high. Here we use the rcwa_structure class, which is used in the same way as tmm_structure above and rt_structure in Example 4a. Because we are now dealing with a structure which is periodic in the \\(x\\) and \\(y\\) directions (unlike the TMM structures, which are uniform in the plane), we have to specify the size of the unit cell (this does not have to be square; here we have a triangular unit cell to give a grating with a hexagonal layout of circles). Otherwise, the grating layer is specified as normal but with a geometry argument which lists the shapes in the grating and the material they are made of.\nThere are many additional options which can be specified for S4 (which is used to actually run the RCWA calculations); more detail can be found in its documentation here.\n\nprint(\"Calculating nanophotonic grating device...\")\n\nx = 600\n\n# lattice vectors for the grating. Units are in nm!\nsize = ((x, 0), (x / 2, np.sin(np.pi / 3) * x))\n\nropt = dict(LatticeTruncation='Circular',\n DiscretizedEpsilon=False,\n DiscretizationResolution=8,\n PolarizationDecomposition=False,\n PolarizationBasis='Default',\n #LanczosSmoothing=dict(Power=2, Width=1),\n LanczosSmoothing=False,\n SubpixelSmoothing=False,\n ConserveMemory=False,\n WeismannFormulation=False,\n Verbosity=0)\n\noptions.S4_options = ropt\n\n# grating layers\ngrating = [Layer(width=si(100, 'nm'), material=SiN, geometry=[{'type': 'circle', 'mat': Ag, 'center': (0, 0),\n 'radius': x/3, 'angle': 0}])] # actual grating part of grating\n\n\n# DTL device without anti-reflection coating\nsolar_cell = SolarCell([Layer(material=InGaP, width=si('20nm')),\n Layer(material=GaAs, width=si('85nm')),\n Layer(material=InAlP, width=si('20nm'))] + grating,\n substrate=Ag)\n\n# make RCWA structure\nS4_setup = rcwa_structure(solar_cell, size, options, Air, Ag)\n\n# calculate\n\nRAT = S4_setup.calculate(options)\n\nAbs_DTL = RAT['A_per_layer'][:,1] # absorption in GaAs\n\nR_DTL = RAT['R']\nT_DTL = RAT['T']\n\nCalculating nanophotonic grating device..."
+ "objectID": "solar-cell-simulation/notebooks/1a-simple_cell.html#running-cell-simulations",
+ "href": "solar-cell-simulation/notebooks/1a-simple_cell.html#running-cell-simulations",
+ "title": "Example 1a: Simple Si solar cell",
+ "section": "Running cell simulations",
+ "text": "Running cell simulations\nDefine the solar cell; in this case it is very simple and we just have a single junction:\n\nsolar_cell = SolarCell([Si_junction])\n\nNow use solar_cell_solver to calculate the QE of the cell; we can ask solar_cell_solver to calculate ‘qe’, ‘optics’ or ‘iv’.\n\nsolar_cell_solver(solar_cell, 'qe', options)\n\nSolving optics of the solar cell...\nSolving QE of the solar cell...\n\n\nPLOT 1: plotting the QE in the Si junction, as well as the fraction of light absorbed in the junction and reflected:\n\nplt.figure()\nplt.plot(wavelengths*1e9, 100*solar_cell[0].eqe(wavelengths), 'k-', label=\"EQE\")\nplt.plot(wavelengths*1e9, 100*solar_cell[0].layer_absorption, label='Absorptance (A)')\nplt.plot(wavelengths*1e9, 100*solar_cell.reflected, label='Reflectance (R)')\nplt.legend()\nplt.xlabel(\"Wavelength (nm)\")\nplt.ylabel(\"QE/Absorptance (%)\")\nplt.title(\"(1) QE of Si cell - Beer-Lambert absorption\")\nplt.show()"
},
{
- "objectID": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html#nanophotonic-grating-device-with-arc",
- "href": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html#nanophotonic-grating-device-with-arc",
- "title": "Example 5a: Ultra-thin GaAs cell with diffraction grating",
- "section": "Nanophotonic grating device with ARC",
- "text": "Nanophotonic grating device with ARC\nThis device is exactly like the previous one, but with the additional of a simple single-layer anti-reflection coating.\n\nprint(\"Calculating nanophotonic grating device with ARC...\")\n\n# DTL device with anti-reflection coating\nsolar_cell = SolarCell([Layer(material=Al2O3, width=si('70nm')),\n Layer(material=InGaP, width=si('20nm')),\n Layer(material=GaAs, width=si('85nm')),\n Layer(material=InAlP, width=si('20nm'))] + grating,\n substrate=Ag)\n\n# make RCWA structure\nS4_setup = rcwa_structure(solar_cell, size, options, Air, Ag)\n\n# calculate\nRAT_ARC = S4_setup.calculate(options)\n\nAbs_DTL_ARC = RAT_ARC['A_per_layer'][:,2] # absorption in GaAs + InGaP\n\nR_DTL_ARC = RAT_ARC['R']\nT_DTL_ARC = RAT_ARC['T']\n\nCalculating nanophotonic grating device with ARC..."
+ "objectID": "solar-cell-simulation/notebooks/1a-simple_cell.html#adding-front-surface-reflection-fresnel",
+ "href": "solar-cell-simulation/notebooks/1a-simple_cell.html#adding-front-surface-reflection-fresnel",
+ "title": "Example 1a: Simple Si solar cell",
+ "section": "Adding front-surface reflection: Fresnel",
+ "text": "Adding front-surface reflection: Fresnel\nNow, to make this calculation a bit more realistic, there are a few things we could do. We could load some measured front surface reflectance from a file, or we could calculate the reflectance. To calculate the reflectance, there are many approaches we could take; we are going to explore two of them here.\nIf we assume the silicon is infinitely thick (or at least much thicker than the wavelengths we care about) then the reflectance will approach the reflectivity of a simple air/Si interface. We can calculate what this is using the Fresnel equation for reflectivity.\n\ndef calculate_R_Fresnel(incidence_n, transmission_n, wl):\n # return a function that gives the value of R (at normal incidence) at the input wavelengths\n\n Rs = np.abs((incidence_n - transmission_n)/(incidence_n + transmission_n))**2\n\n return interp1d(wl, Rs)\n\ncomplex reflective index at our wavelengths for the transmission medium (Si). The incidence_n = 1 (air).\n\ntrns_n = Si_n.n(wavelengths) + 1j*Si_n.k(wavelengths)\nreflectivity_fn = calculate_R_Fresnel(1, trns_n, wavelengths)\n\nwe define the solar cell again, with the same layers but now supplying the function for the externally-calculated reflectivity, and calculate the optics (reflection, absorption, transmission):\n\nsolar_cell_fresnel = SolarCell([Si_junction], reflectivity=reflectivity_fn)\n\nsolar_cell_solver(solar_cell_fresnel, 'optics', options)\n\nSolving optics of the solar cell..."
},
{
- "objectID": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html#plotting-results",
- "href": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html#plotting-results",
- "title": "Example 5a: Ultra-thin GaAs cell with diffraction grating",
- "section": "Plotting results",
- "text": "Plotting results\nPLOT 1: Comparing the absorption in GaAs for the four different device architectures\n\n\npal = sns.color_palette(\"husl\", 4)\n\nfig = plt.figure(figsize=(6.4, 4.8))\n\nplt.plot(wavelengths*1e9, 100*Abs_onsubs, color=pal[0], label=\"On substrate\")\nplt.plot(wavelengths*1e9, 100*Abs_TMM, color=pal[1], label=\"Planar mirror\")\nplt.plot(wavelengths*1e9, 100*Abs_DTL, color=pal[2], label=\"Nanophotonic grating (no ARC)\")\nplt.plot(wavelengths*1e9, 100*Abs_DTL_ARC, color=pal[3], label=\"Nanophotonic grating (with ARC)\")\n\nplt.xlim(300, 950)\nplt.ylim(0, 100)\nplt.xlabel('Wavelength (nm)')\nplt.ylabel('EQE (%)')\nplt.legend(loc='upper left')\nplt.show()\n\n\n\n\nWe see that the addition of a planar silver mirror significantly boosts the absorption around 700 nm, essentially by creating a Fabry-Perot (thin film) cavity in the semiconductor layers through high reflection at the rear of the cell. The grating introduces multiple resonances relating to different diffraction orders and waveguide modes in the structure, which boosts the absorption especially close to the absorption edge in comparison to the planar devices.\nPLOT 2: Absorption per layer in the planar Ag device.\n\nfig = plt.figure(figsize=(6.4, 4.8))\nplt.stackplot(wavelengths*1e9,\n [100*Abs_TMM, 100*Abs_TMM_InGaPonly, 100*Abs_TMM_InAlPonly],\n colors=pal,\n labels=['Absorbed in GaAs', 'Absorbed in InGaP', 'Absorbed in InAlP'])\nplt.xlim(300, 950)\nplt.ylim(0, 90)\nplt.xlabel('Wavelength (nm)')\nplt.ylabel('EQE (%)')\nplt.legend(loc='upper right')\nplt.show()\n\n\n\n\nThe plot above shows that at short wavelengths, even very thin layers (in this case, 20 nm of InGaP) can absorb a very significant fraction of the incident radiation. Depending on the device, the carriers generated here may or may not be extracted as current."
+ "objectID": "solar-cell-simulation/notebooks/1a-simple_cell.html#adding-front-surface-reflection-tmm",
+ "href": "solar-cell-simulation/notebooks/1a-simple_cell.html#adding-front-surface-reflection-tmm",
+ "title": "Example 1a: Simple Si solar cell",
+ "section": "Adding front surface reflection: TMM",
+ "text": "Adding front surface reflection: TMM\nFinally, we do the same again but now instead of supplying the external reflectivity we ask set the optics_method to “TMM” (Transfer Matrix Method), to correctly calculate reflection at the front surface:\n\nSi_junction = Junction([emitter_layer, base_layer], kind=\"DA\")\n\nsolar_cell_TMM = SolarCell([Si_junction])\n\nSet some more options:\n\noptions[\"optics_method\"] = \"TMM\"\nvoltages = np.linspace(0, 1.1, 100)\noptions[\"light_iv\"] = True\noptions[\"mpp\"] = True\noptions[\"voltages\"] = voltages\n\nwe calculate the QE and the IV (we set the light_iv option to True; if we don’t do this, Solcore just calculates the dark IV). We also ask Solcore to find the maximum power point (mpp) so we can get the efficiency.\n\nsolar_cell_solver(solar_cell_TMM, 'iv', options)\nsolar_cell_solver(solar_cell_TMM, 'qe', options)\n\nSolving optics of the solar cell...\nTreating layer(s) 1 incoherently\nCalculating RAT...\nCalculating absorption profile...\nSolving IV of the junctions...\nSolving IV of the tunnel junctions...\nSolving IV of the total solar cell...\nSolving optics of the solar cell...\nTreating layer(s) 1 incoherently\nCalculating RAT...\nCalculating absorption profile...\nSolving QE of the solar cell...\n\n\nPLOT 2: here we plot the reflection, transmission, and absorption calculated with the Fresnel equation defined above, and with the TMM solver in Solcore, showing that for this simple situation (no anti-reflection coating, thick Si junction) they are exactly equivalent.\n\nplt.figure()\nplt.plot(wavelengths*1e9, 100*solar_cell_TMM.reflected, color='firebrick', label = \"R (TMM)\")\nplt.plot(wavelengths*1e9, 100*solar_cell_fresnel.reflected, '--', color='orangered', label = \"R (Fresnel)\")\nplt.plot(wavelengths*1e9, 100*solar_cell_TMM.absorbed, color='dimgrey', label = \"A (TMM)\")\nplt.plot(wavelengths*1e9, 100*solar_cell_fresnel.absorbed, '--', color='lightgrey', label = \"A (Fresnel)\")\nplt.plot(wavelengths*1e9, 100*solar_cell_TMM.transmitted, color='blue', label = \"T (TMM)\")\nplt.plot(wavelengths*1e9, 100*solar_cell_fresnel.transmitted, '--', color='dodgerblue', label = \"T (Fresnel)\")\nplt.ylim(0, 100)\nplt.legend()\nplt.title(\"(2) Optics of Si cell - Fresnel/TMM\")\nplt.show()\n\n\n\n\nPLOT 3: As above for the TMM calculation, plotting the EQE as well, which will be slightly lower than the absorption because not all the carriers are collected. Comparing to plot (1), we can see we now have lower absorption due to the inclusion of front surface reflection.\n\nplt.figure()\nplt.plot(wavelengths*1e9, 100*solar_cell_TMM[0].eqe(wavelengths), 'k-', label=\"EQE\")\nplt.plot(wavelengths*1e9, 100*solar_cell_TMM[0].layer_absorption, label='A')\nplt.plot(wavelengths*1e9, 100*solar_cell_TMM.reflected, label=\"R\")\nplt.plot(wavelengths*1e9, 100*solar_cell_TMM.transmitted, label=\"T\")\nplt.title(\"(3) QE of Si cell (no ARC) - TMM\")\nplt.legend()\nplt.xlabel(\"Wavelength (nm)\")\nplt.ylabel(\"QE/Absorptance (%)\")\nplt.ylim(0, 100)\nplt.show()"
},
{
- "objectID": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html#calculating-photogenerated-current",
- "href": "solar-cell-simulation/notebooks/5a-ultrathin_GaAs_cell.html#calculating-photogenerated-current",
- "title": "Example 5a: Ultra-thin GaAs cell with diffraction grating",
- "section": "Calculating photogenerated current",
- "text": "Calculating photogenerated current\nFinally, we use the photon flux in the AM0 spectrum to calculate the maximum possible achievable current for these devices.\n\nonsubs = 0.1 * q * np.trapz(Abs_onsubs*AM0, wavelengths)\nAg = 0.1 * q * np.trapz(Abs_TMM*AM0, wavelengths)\nDTL = 0.1 * q * np.trapz(Abs_DTL*AM0, wavelengths)\nDTL_ARC = 0.1 * q * np.trapz(Abs_DTL_ARC*AM0, wavelengths)\n\n\nprint('On substrate device current: %.1f mA/cm2 ' % onsubs)\nprint('Planar Ag mirror device current: %.1f mA/cm2 ' % Ag)\nprint('Nanophotonic grating (no ARC) device current: %.1f mA/cm2 ' % DTL)\nprint('Nanophotonic grating (with ARC) device current: %.1f mA/cm2 ' % DTL_ARC)\n\nOn substrate device current: 9.5 mA/cm2 \nPlanar Ag mirror device current: 13.8 mA/cm2 \nNanophotonic grating (no ARC) device current: 18.0 mA/cm2 \nNanophotonic grating (with ARC) device current: 23.0 mA/cm2 \n\n\nAs expected, simply adding a planar mirror boosts the current significantly. The addition of a nanophotonic grating gives a further boost (note that the grating we used here is optimized in terms of grating pitch (period) and dimension; not all gratings would give a boost in current, and some may even reduce performance due to e.g. parasitic absorption)."
+ "objectID": "solar-cell-simulation/notebooks/1a-simple_cell.html#adding-an-arc",
+ "href": "solar-cell-simulation/notebooks/1a-simple_cell.html#adding-an-arc",
+ "title": "Example 1a: Simple Si solar cell",
+ "section": "Adding an ARC",
+ "text": "Adding an ARC\nNow we will try adding a simple anti-reflection coating (ARC), a single layer of silicon nitride (Si3N4):\n\nSiN = material(\"Si3N4\")()\n\nSi_junction = Junction([emitter_layer, base_layer], kind=\"DA\")\n\nsolar_cell_TMM_ARC = SolarCell([Layer(width=si(75, \"nm\"), material=SiN), Si_junction])\n\nsolar_cell_solver(solar_cell_TMM_ARC, 'qe', options)\nsolar_cell_solver(solar_cell_TMM_ARC, 'iv', options)\n\nSolving optics of the solar cell...\nTreating layer(s) 2 incoherently\nCalculating RAT...\nCalculating absorption profile...\nSolving QE of the solar cell...\nSolving optics of the solar cell...\nTreating layer(s) 2 incoherently\nCalculating RAT...\nCalculating absorption profile...\nSolving IV of the junctions...\nSolving IV of the tunnel junctions...\nSolving IV of the total solar cell...\n\n\nPLOT 4: Absorption, EQE, reflection and transmission for the cell with a simple one-layer ARC. We see the reflection is significantly reduced from the previous plot leading to higher absorption/EQE.\n\nplt.figure()\nplt.plot(wavelengths*1e9, 100*solar_cell_TMM_ARC[1].eqe(wavelengths), 'k-', label=\"EQE\")\nplt.plot(wavelengths*1e9, 100*solar_cell_TMM_ARC[1].layer_absorption, label='A')\nplt.plot(wavelengths*1e9, 100*solar_cell_TMM_ARC.reflected, label=\"R\")\nplt.plot(wavelengths*1e9, 100*solar_cell_TMM_ARC.transmitted, label=\"T\")\nplt.legend()\nplt.title(\"(4) QE of Si cell (ARC) - TMM\")\nplt.xlabel(\"Wavelength (nm)\")\nplt.ylabel(\"QE/Absorptance (%)\")\nplt.ylim(0, 100)\nplt.show()\n\n\n\n\nPLOT 5: Compare the IV curves of the cells with and without an ARC. The efficiency is also shown on the plot. Note that because we didn’t specify a light source, Solcore will assume we want to use AM1.5G; in later examples we will set the light source used for IV simulations explicitly.\n\nplt.figure()\nplt.plot(voltages, -solar_cell_TMM[0].iv(voltages)/10, label=\"No ARC\")\nplt.plot(voltages, -solar_cell_TMM_ARC[1].iv(voltages)/10, label=\"75 nm SiN\")\nplt.text(0.5, solar_cell_TMM.iv[\"Isc\"]/10, str(round(solar_cell_TMM.iv[\"Eta\"]*100, 1)) + ' %')\nplt.text(0.5, solar_cell_TMM_ARC.iv[\"Isc\"]/10, str(round(solar_cell_TMM_ARC.iv[\"Eta\"]*100, 1)) + ' %')\nplt.ylim(0, 38)\nplt.xlim(0, 0.8)\nplt.legend()\nplt.xlabel(\"V (V)\")\nplt.ylabel(r\"J (mA/cm$^2$)\")\nplt.title(\"(5) IV curve of Si cell with and without ARC\")\nplt.show()"
},
{
- "objectID": "solar-cell-simulation/notebooks/2a-optical_constants.html",
- "href": "solar-cell-simulation/notebooks/2a-optical_constants.html",
- "title": "Example 2a: Optical constant sources",
+ "objectID": "solar-cell-simulation/notebooks/1a-simple_cell.html#conclusions",
+ "href": "solar-cell-simulation/notebooks/1a-simple_cell.html#conclusions",
+ "title": "Example 1a: Simple Si solar cell",
+ "section": "Conclusions",
+ "text": "Conclusions\nWe see that the cell with an ARC has a significantly higher Jsc, and a slightly higher Voc, than the bare Si cell. In reality, most Si cells have a textured surface rather than a planar surface with an ARC; this will be discussed later in the course.\nOverall, some things we can take away from the examples in this script: - The Beer-Lambert law is a very simple way to calculate absorption in a cell, but won’t take into account important effects such as front-surface reflection or the effects of anti-reflection coatings - Using the transfer-matrix method (TMM) we can account for front surface reflection and interference effects which make e.g. ARCs effective. In the simple situation of a thick cell without any front surface layers, it is equivalent to simply calculation the reflection with the Fresnel equations and assuming Beer-Lambert absorption in the cell. - Adding a simple, one-layer ARC can significantly reduce front-surface reflection for a single-junction cell, leading to improved short-circuit current."
+ },
+ {
+ "objectID": "solar-cell-simulation/notebooks/7a-optimization.html",
+ "href": "solar-cell-simulation/notebooks/7a-optimization.html",
+ "title": "Example 7a: Simple optimization",
"section": "",
- "text": "In the first set of scripts focusing on the Si cell, we used different optical models to calculate total absorption and absorption profiles. These absorption profiles are used by the electrical models (if using the DA or PDD model). However, we didn’t discuss what actually goes into these optical models, which are the optical constants (either the complex refractive index, \\(n + i \\kappa\\) (\\(\\kappa\\) is the extinction coefficient), or equivalently the dielectric function \\(\\epsilon_1 + i \\epsilon_2\\)). In these two examples we will discuss what these values are, how to get them, and how to model them.\nfrom solcore.absorption_calculator.nk_db import download_db, search_db, create_nk_txt\nfrom solcore.absorption_calculator import calculate_rat, OptiStack\nfrom solcore.material_system import create_new_material\nfrom solcore import material\nfrom solcore import si\nfrom solcore.structure import Layer\n\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom os import remove\n\nimport seaborn as sns"
+ "text": "In a few of the previous examples, we have used anti-reflection coatings. In Example 5a, we introduced a nanophotonic grating for light trapping. But how do you find out the right thickness for the anti-reflection coating layer(s), or the right dimensions for a light-trapping grating? This is where optimization comes in. Here, we will look at a very simple ‘brute-force’ optimization for a single or double-layer ARC, and a more complicated framework for running optimizations using Solcore/RayFlare and a differential evolution algorithm in Example 7b.\nimport numpy as np\nimport os\nimport matplotlib.pyplot as plt\n\nfrom solcore import material, si\nfrom solcore.solar_cell import Layer\nfrom solcore.light_source import LightSource\nfrom solcore.absorption_calculator import search_db\n\nfrom rayflare.transfer_matrix_method import tmm_structure\nfrom rayflare.options import default_options\nimport seaborn as sns"
},
{
- "objectID": "solar-cell-simulation/notebooks/2a-optical_constants.html#adding-custom-materials",
- "href": "solar-cell-simulation/notebooks/2a-optical_constants.html#adding-custom-materials",
- "title": "Example 2a: Optical constant sources",
- "section": "Adding custom materials",
- "text": "Adding custom materials\nIf we want to use a material which is not in Solcore’s database, or perhaps we want to use measured data rather than data from a literature source, we can add a material to the database. We need to have n and k data, and (optionally) a parameter file in the correct format - see examples of parameter files in e.g. material_data/Adachi/binaries.txt inside Solcore’s source files. These parameter files contain things like the bandgap, lattice constant, effective carrier masses, etc.\nHere, we create a new material, silicon-germanium-tin, from input files. Here, the parameters in SiGeSn_params.txt have been copied directly from Ge. The last argument, with the parameters file, is optional; if you exclude it the material will be added with just the n and k values and no further information specified (useful if you just want to do optical calculations).\n\ncreate_new_material('SiGeSn', 'data/SiGeSn_n.txt', 'data/SiGeSn_k.txt', 'data/SiGeSn_params.txt')\n\nWhen adding custom materials - or getting the refractive index database - the information will be stored by default in your home directory. You can change thethe SOLCORE_USER_DATA environmental variable in the config file to your prefered location or, by default, it will be in your home directory, in a (hidden) directory called .solcore.\nWe can now create an instance of it like any Solcore material:\n\nwl = si(np.arange(300, 1700, 5), 'nm')\n\nSiGeSn = material('SiGeSn')()\nGe = material('Ge')()\n\nPLOT 1: Comparing the optical constants of SiGeSn and Ge.\n\nplt.figure()\nplt.plot(wl*1e9, SiGeSn.n(wl), 'r-', label='SiGeSn n')\nplt.plot(wl*1e9, SiGeSn.k(wl), 'k-', label=r'SiGeSn $\\kappa$')\n\nplt.plot(wl*1e9, Ge.n(wl), 'r--', label='Ge n')\nplt.plot(wl*1e9, Ge.k(wl), 'k--', label=r'Ge $\\kappa$')\n\nplt.xlabel('Wavelength (nm)')\nplt.ylabel(r'SiGeSn n / $\\kappa$')\nplt.legend()\nplt.title(\"(1) Optical constants of Ge and SiGeSn\")\nplt.show()"
+ "objectID": "solar-cell-simulation/notebooks/7a-optimization.html#setting-up",
+ "href": "solar-cell-simulation/notebooks/7a-optimization.html#setting-up",
+ "title": "Example 7a: Simple optimization",
+ "section": "Setting up",
+ "text": "Setting up\n\nopts = default_options()\n\nwavelengths = np.linspace(300, 1200, 800)*1e-9\n\nAM15g = LightSource(source_type=\"standard\", version=\"AM1.5g\", output_units=\"photon_flux_per_m\")\nspectrum = AM15g.spectrum(wavelengths)[1]\nnormalised_spectrum = spectrum/np.max(spectrum)\n\nopts.wavelengths = wavelengths\nopts.coherent = False\nopts.coherency_list = ['c', 'i']\n\nSi = material(\"Si\")()\nSiN = material(\"Si3N4\")()\nAg = material(\"Ag\")()\nAir = material(\"Air\")()"
},
{
- "objectID": "solar-cell-simulation/notebooks/2a-optical_constants.html#using-the-refractiveindex.info-database",
- "href": "solar-cell-simulation/notebooks/2a-optical_constants.html#using-the-refractiveindex.info-database",
- "title": "Example 2a: Optical constant sources",
- "section": "Using the refractiveindex.info database",
- "text": "Using the refractiveindex.info database\nSolcore can also directly interface with the database from www.refractiveindex.info, which contains around 3000 sets of \\(n\\)/\\(\\kappa\\) data for a large number of different materials. Before the first use, it is necessary to download the database. This only needs to be done once, so you can comment this line out after it’s done:\n\ndownload_db()\n\nNow we can search the database to select an appropriate entry. Search by element/chemical formula, or by the name of the author who published the data. In this case, we look for silver.\n\nsearch_db('Ag', exact=True); # semicolon suppresses additional output in Jupyter Notebook. Do not need to use.\n\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\n17 results found.\npageid shelf book page filepath hasrefractive hasextinction rangeMin rangeMax points\n0 main Ag Johnson main/Ag/Johnson.yml 1 1 0.1879 1.937 49\n1 main Ag Choi main/Ag/Choi.yml 1 1 1.231 6.988 84\n2 main Ag Jiang main/Ag/Jiang.yml 1 1 0.3 2.0 1701\n3 main Ag Yang main/Ag/Yang.yml 1 1 0.27 24.92 525\n4 main Ag McPeak main/Ag/McPeak.yml 1 1 0.3 1.7 141\n5 main Ag Babar main/Ag/Babar.yml 1 1 0.2066 12.4 69\n6 main Ag Wu main/Ag/Wu.yml 1 1 0.287493 0.999308 450\n7 main Ag Werner main/Ag/Werner.yml 1 1 0.017586 2.479684 150\n8 main Ag Stahrenberg main/Ag/Stahrenberg.yml 1 1 0.12782 0.49594 361\n9 main Ag Windt main/Ag/Windt.yml 1 1 0.00236 0.12157 36\n10 main Ag Hagemann main/Ag/Hagemann.yml 1 1 2.48e-06 248.0 148\n11 main Ag Ciesielski main/Ag/Ciesielski.yml 1 1 0.19077 20.912 333\n12 main Ag Ciesielski-Ge main/Ag/Ciesielski-Ge.yml 1 1 0.19077 20.912 333\n13 main Ag Ciesielski-Ni main/Ag/Ciesielski-Ni.yml 1 1 0.19077 15.811 332\n14 main Ag Rakic-BB main/Ag/Rakic-BB.yml 1 1 0.24797 12.398 200\n15 main Ag Rakic-LD main/Ag/Rakic-LD.yml 1 1 0.24797 12.398 200\n16 main Ag Werner-DFT main/Ag/Werner-DFT.yml 1 1 0.017586 2.479684 150\n\n\nThis prints out, line by line, matching entries. This shows us entries with “pageid”s 0 to 16 correspond to silver.\nLet’s compare the optical behaviour of some of those sources:\n\npageid = 0, Johnson\npageid = 2, Jiang\npageid = 4, McPeak\npageid = 10, Hagemann\npageid = 14, Rakic (BB)\n\n(The pageid values listed here are for the 2021-07-18 version of the refractiveindex.info database; this can change with different versions of the database)\nNow, we create instances of materials with the optical constants from the database. The name (when using Solcore’s built-in materials, this would just be the name of the material or alloy, like ‘GaAs’) is the pageid, AS A STRING, while the flag nk_db must be set to True to tell Solcore to look in the previously downloaded database from refractiveindex.info\n\nAg_Joh = material(name='0', nk_db=True)()\nAg_Jia = material(name='2', nk_db=True)()\nAg_McP = material(name='4', nk_db=True)()\nAg_Hag = material(name='10', nk_db=True)()\nAg_Rak = material(name='14', nk_db=True)()\nAg_Sol = material(name='Ag')() # Solcore built-in (from SOPRA)\n\nNow we plot the \\(n\\) and \\(\\kappa\\) data. Note that not all the data covers the full wavelength range, so the \\(n\\)/\\(\\kappa\\) value gets extrapolated from the last point in the dataset to cover any missing values.\nPLOT 2: \\(n\\) and \\(\\kappa\\) values for Ag from different literature sources\n\nnames = ['Johnson', 'Jiang', 'McPeak', 'Hagemann', 'Rakic', 'Solcore built-in']\n\nwl = si(np.arange(250, 900, 5), 'nm')\n\nplt.figure(figsize=(8,4))\n\nplt.subplot(121)\n# We can plot all the n values in one line:\nplt.plot(wl*1e9, np.array([Ag_Joh.n(wl), Ag_Jia.n(wl), Ag_McP.n(wl),\n Ag_Hag.n(wl), Ag_Rak.n(wl), Ag_Sol.n(wl)]).T);\nplt.legend(labels=names)\nplt.xlabel(\"Wavelength (nm)\")\nplt.title(\"(2) $n$ and $\\kappa$ values for Ag from different literature sources\")\nplt.ylabel(\"n\")\n\nplt.subplot(122)\nplt.plot(wl*1e9, np.array([Ag_Joh.k(wl), Ag_Jia.k(wl), Ag_McP.k(wl),\n Ag_Hag.k(wl), Ag_Rak.k(wl), Ag_Sol.k(wl)]).T)\nplt.legend(labels=names)\nplt.xlabel(\"Wavelength (nm)\")\nplt.ylabel(\"$\\kappa$\")\nplt.show()\n\nCompare performance as a back mirror on a GaAs ‘cell’; we make a solar cell-like structure with a very thin GaAs absorber (50 nm) and a silver back mirror.\nPLOT 3: compare absorption in GaAs and Ag for a solar cell-like structure, using Ag data from different sources\nSolid line: absorption in GaAs Dashed line: absorption in Ag\n\nGaAs = material('GaAs')()\n\ncolors = sns.color_palette('husl', n_colors=len(names))\n\nplt.figure()\nfor c, Ag_mat in enumerate([Ag_Joh, Ag_Jia, Ag_McP, Ag_Hag, Ag_Rak, Ag_Sol]):\n my_solar_cell = OptiStack([Layer(width=si('50nm'), material = GaAs)], substrate=Ag_mat)\n RAT = calculate_rat(my_solar_cell, wl*1e9, no_back_reflection=False)\n GaAs_abs = RAT[\"A_per_layer\"][1]\n Ag_abs = RAT[\"T\"]\n plt.plot(wl*1e9, GaAs_abs, color=colors[c], linestyle='-', label=names[c])\n plt.plot(wl*1e9, Ag_abs, color=colors[c], linestyle='--')\n\nplt.legend()\nplt.xlabel(\"Wavelength (nm)\")\nplt.ylabel(\"Absorbed\")\nplt.title(\"(3) Absorption in GaAs depending on silver optical constants\")\nplt.show()"
+ "objectID": "solar-cell-simulation/notebooks/7a-optimization.html#single-layer-arc",
+ "href": "solar-cell-simulation/notebooks/7a-optimization.html#single-layer-arc",
+ "title": "Example 7a: Simple optimization",
+ "section": "Single-layer ARC",
+ "text": "Single-layer ARC\nHere, we will calculate the behaviour of a single-layer SiN anti-reflection coating on Si while changing the ARC thickness between 0 and 200 nm. We will consider two values to optimize: the mean reflectance mean_R, and the reflectance weighted by the photon flux in an AM1.5G spectrum (weighted_R). The reason for considering the second value is that it is more useful to suppress reflection at wavelengths where there are more photons which could be absorbed.\nWe will loop through the different ARC thicknesses in d_range, build the structure for each case, and then calculate the reflectance. We then save the mean reflected and weighted mean reflectance in the corresponding arrays. We also plot the reflectance for each 15th loop (this is just so the plot does not get too crowded).\n\nd_range = np.linspace(0, 200, 200)\n\nmean_R = np.empty_like(d_range)\nweighted_R = np.empty_like(d_range)\n\ncols = sns.cubehelix_palette(np.ceil(len(d_range)/15))\n\nplt.figure()\njcol = 0\n\nfor i1, d in enumerate(d_range):\n\n struct = tmm_structure([Layer(si(d, 'nm'), SiN), Layer(si('300um'), Si)], incidence=Air, transmission=Ag)\n RAT = struct.calculate(opts)\n\n if i1 % 15 == 0:\n plt.plot(wavelengths*1e9, RAT['R'], label=str(np.round(d, 0)), color=cols[jcol])\n jcol += 1\n\n mean_R[i1] = np.mean(RAT['R'])\n weighted_R[i1] = np.mean(RAT['R']*normalised_spectrum)\n\nplt.legend()\nplt.show()\n\n\n\n\nWe now find at which index mean_R and weighted_R are minimised using np.argmin, and use this to print the ARC thickness at which this occurs (rounded to 1 decimal place).\n\nprint('Minimum mean reflection occurs at d = ' + str(np.round(d_range[np.argmin(mean_R)], 1)) + ' nm')\nprint('Minimum weighted reflection occurs at d = ' + str(np.round(d_range[np.argmin(weighted_R)], 1)) + ' nm')\n\nMinimum mean reflection occurs at d = 70.4 nm\nMinimum weighted reflection occurs at d = 75.4 nm\n\n\nWe see that the values of \\(d\\) for the two different ways of optimizing are very similar, but not exactly the same, as we would expect. The minimum in both cases occurs around 70 nm. We can also plot the evolution of the mean and weighted \\(R\\) with ARC thickness \\(d\\):\n\nplt.figure()\nplt.plot(d_range, mean_R, label='Mean reflection')\nplt.plot(d_range[np.argmin(mean_R)], np.min(mean_R), 'ok')\nplt.plot(d_range, weighted_R, label='Weighted mean reflection')\nplt.plot(d_range[np.argmin(weighted_R)], np.min(weighted_R), 'ok')\nplt.xlabel('d$_{SiN}$')\nplt.ylabel('(Weighted) mean reflection 300-1200 nm')\nplt.show()\n\n\n\n\nAnd the actual reflectance with wavelength for the two different optimizations:\n\nstruct = tmm_structure([Layer(si(d_range[np.argmin(mean_R)], 'nm'), SiN), Layer(si('300um'), Si)], incidence=Air, transmission=Ag)\nRAT_1 = struct.calculate(opts)\n\nstruct = tmm_structure([Layer(si(d_range[np.argmin(weighted_R)], 'nm'), SiN), Layer(si('300um'), Si)], incidence=Air, transmission=Ag)\nRAT_2 = struct.calculate(opts)\n\nplt.figure()\nplt.plot(wavelengths*1e9, RAT_1['R'], label='Mean R minimum')\nplt.plot(wavelengths*1e9, RAT_2['R'], label='Weighted R minimum')\nplt.legend()\nplt.xlabel(\"Wavelength (nm)\")\nplt.ylabel(\"R\")\nplt.show()\n\n\n\n\nWe see that the two reflectance curves are very similar."
},
{
- "objectID": "solar-cell-simulation/notebooks/2a-optical_constants.html#adding-refractiveindex.info-materials-to-solcores-database",
- "href": "solar-cell-simulation/notebooks/2a-optical_constants.html#adding-refractiveindex.info-materials-to-solcores-database",
- "title": "Example 2a: Optical constant sources",
- "section": "Adding refractiveindex.info materials to Solcore’s database",
- "text": "Adding refractiveindex.info materials to Solcore’s database\nFinally, we can combine the two methods above and add a material from the refractiveindex.info database to Solcore’s database.\nThe search_db function will print the search results, but it also creates a list of lists with details of all the search results. results[0] contains the first entry; results[0][0] is the ‘pageid’ of the first search result.\nThe function create_nk_txt creates files containing the optical constant data in the format required by Solcore. These are saved in the current working directory.\n\nresults = search_db('Diamond')\n\ncreate_nk_txt(pageid=results[0][0], file='C_Diamond')\n\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\n1 results found.\npageid shelf book page filepath hasrefractive hasextinction rangeMin rangeMax points\n2897 3d crystals diamond main/C/Phillip.yml 1 1 0.035424054 10.0 176\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\nMaterial main/C/Phillip.yml loaded.\nWrote C_Diamond_n.txt\nWrote C_Diamond_k.txt\n\n\nWe now use these files to create a new material in the Solcore database:\n\ncreate_new_material(mat_name='Diamond', n_source='C_Diamond_n.txt', k_source='C_Diamond_k.txt')\n\nMaterial created with optical constants n and k only.\n\n\nWe can now delete the files with the Diamond data, since they have been copied into the user-defined materials directory:\n\nremove(\"C_diamond_n.txt\")\nremove(\"C_diamond_k.txt\")\n\nNow we can use this material as we would any material from Solcore’s database:\nPLOT 4: Optical constants of diamond\n\nDiamond = material('Diamond')()\n\nplt.figure()\nplt.plot(si(np.arange(100, 800, 5), 'nm') * 1e9, Diamond.n(si(np.arange(100, 800, 5), 'nm')))\nplt.plot(si(np.arange(100, 800, 5), 'nm') * 1e9, Diamond.k(si(np.arange(100, 800, 5), 'nm')))\nplt.title(\"(4) Optical constants for diamond\")\nplt.show()"
+ "objectID": "solar-cell-simulation/notebooks/7a-optimization.html#double-layer-arc",
+ "href": "solar-cell-simulation/notebooks/7a-optimization.html#double-layer-arc",
+ "title": "Example 7a: Simple optimization",
+ "section": "Double-layer ARC",
+ "text": "Double-layer ARC\nWe will now consider a similar situation, but for a double-layer MgF\\(_2\\)/Ta\\(_2\\)O\\(_5\\) ARC on GaAs. We search for materials in the refractiveindex.info database (see Example 2a), and use only the part of the solar spectrum relevant for absorption in GaAs (in this case, there is no benefit to reducing absorption above the GaAs bandgap around 900 nm). We will only consider the weighted mean \\(R\\) in this case.\n\npageid_MgF2 = search_db(os.path.join(\"MgF2\", \"Rodriguez-de Marcos\"))[0][0]\npageid_Ta2O5 = search_db(os.path.join(\"Ta2O5\", \"Rodriguez-de Marcos\"))[0][0]\n\nGaAs = material(\"GaAs\")()\nMgF2 = material(str(pageid_MgF2), nk_db=True)()\nTa2O5 = material(str(pageid_Ta2O5), nk_db=True)()\n\nMgF2_thickness = np.linspace(50, 100, 20)\nTa2O5_thickness = np.linspace(30, 80, 20)\n\nweighted_R_matrix = np.zeros((len(MgF2_thickness), len(Ta2O5_thickness)))\n\nwavelengths_GaAs = wavelengths[wavelengths < 900e-9]\nnormalised_spectrum_GaAs = normalised_spectrum[wavelengths < 900e-9]\n\nopts.coherent = True\nopts.wavelengths = wavelengths_GaAs\n\nWe now have two thicknesses to loop through; otherwise, the procedure is similar to the single-layer ARC example.\n\nfor i1, d_MgF2 in enumerate(MgF2_thickness):\n for j1, d_Ta2O5 in enumerate(Ta2O5_thickness):\n struct = tmm_structure([Layer(si(d_MgF2, 'nm'), MgF2), Layer(si(d_Ta2O5, 'nm'), Ta2O5),\n Layer(si('20um'), GaAs)],\n incidence=Air, transmission=Ag)\n RAT = struct.calculate(opts)\n\n weighted_R_matrix[i1, j1] = np.mean(RAT['R'] * normalised_spectrum_GaAs)\n\n# find the row and column indices of the minimum weighted R value\nri, ci = np.unravel_index(weighted_R_matrix.argmin(), weighted_R_matrix.shape)\n\nWe plot the total absorption (\\(1-R\\)) in the structure with the optimized ARC, and print the thicknesses of MgF\\(_2\\) and Ta\\(_2\\)O\\(_5\\) at which this occurs:\n\nplt.figure()\nplt.imshow(1-weighted_R_matrix, extent=[min(Ta2O5_thickness), max(Ta2O5_thickness),\n min(MgF2_thickness), max(MgF2_thickness)],\n origin='lower', aspect='equal')\nplt.plot(Ta2O5_thickness[ci], MgF2_thickness[ri], 'xk')\nplt.colorbar()\nplt.xlabel(\"Ta$_2$O$_5$ thickness (nm)\")\nplt.ylabel(\"MgF$_2$ thickness (nm)\")\nplt.show()\n\nprint(\"Minimum reflection occurs at MgF2 / Ta2O5 thicknesses of %.1f / %.1f nm \"\n % (MgF2_thickness[ri], Ta2O5_thickness[ci]))\n\n\n\n\nMinimum reflection occurs at MgF2 / Ta2O5 thicknesses of 73.7 / 53.7 nm \n\n\nFor these two examples, where we are only trying to optimize one and two parameters respectively across a relatively small range, using a method (TMM) which executes quickly, brute force searching is possible. However, as we introduce more parameters, a wider parameter space, and slower simulation methods, it may no longer be computationally tractable."
},
{
- "objectID": "solar-cell-simulation/notebooks/2a-optical_constants.html#conclusions",
- "href": "solar-cell-simulation/notebooks/2a-optical_constants.html#conclusions",
- "title": "Example 2a: Optical constant sources",
- "section": "Conclusions",
- "text": "Conclusions\nSo, we have at least 4 different ways of getting optical constants:\n\nFrom Solcore’s database\nBy adding our own material data to Solcore’s database\nBy using the refractiveindex.info database directly\nSimilarly, we can add materials from the refractiveindex.info database to Solcore’s database\n\nIf we add materials to the database, we can also choose to add non-optical parameters."
+ "objectID": "solcore-workshop/workshop2023.html",
+ "href": "solcore-workshop/workshop2023.html",
+ "title": "Solcore Workshop 2023",
+ "section": "",
+ "text": "Click here to view all the slides.\nOutline:\nDay 1:\n\nIntroduction to Solcore & computer modelling (lecture)\nIntegration for limiting current, limiting voltage model\nShockley-Queisser efficiency limit and detailed balance (DB) junction model (lecture)\n\nDay 2:\n\nIntroduction to drift-diffusion junction model, depletion approximation (lecture) & spectral irradiance\nThe depletion approximation: Si cell and GaAs cell\nOptical modelling using the transfer-matrix model (TMM):\n\nTMM introduction\nOptimizing an anti-reflection coating\n\nPlanar III-V on Si tandem solar cell\n\nDay 3:\n\nOptical absorption in textured Si: ray-tracing for pyramid textures, rigorous coupled-wave analysis (RCWA) for nano-scale gratings\nIII-V/Si cells with light-trapping structures:\n\nPlanar III-V wafer-bonded to silicon with planar front using e.g. epoxy\nPlanar III-V bonded to textured silicon with diffraction grating on rear\n\nConformal perovskite on silicon tandem cells"
},
{
- "objectID": "solcore-workshop/notebooks/6a-TMM_introduction.html",
- "href": "solcore-workshop/notebooks/6a-TMM_introduction.html",
- "title": "Section 6a: Basic cell optics",
+ "objectID": "solcore-workshop/notebooks/9b-GaInP_GaAs_Si_pyramids_new.html",
+ "href": "solcore-workshop/notebooks/9b-GaInP_GaAs_Si_pyramids_new.html",
+ "title": "Section 9b: Planar III-V epoxy-bonded to textured Si",
"section": "",
- "text": "In this script, we will build on the TMM model from example 1(a) and look at the effects of interference.\nimport numpy as np\nimport matplotlib.pyplot as plt\n\nfrom solcore import material, si\nfrom solcore.solar_cell import Layer\nfrom solcore.absorption_calculator import calculate_rat, OptiStack\nimport seaborn as sns"
+ "text": "The structure in this example is based on that of the previous example (9a), but with the planar bottom Si cell replaced by a Si cell with a pyramidal texture, bonded to the III-V top cells with a low-index epoxy/glass layer.\nWe could use the angular redistribution matrix method as in the previous example - however, because in this example we only need to use TMM and ray-tracing (RT), we can use the ray-tracing method with integrated RT directly (this is generally faster, because we do not need to calculate the behaviour of the surfaces for every angle of incidence)."
},
{
- "objectID": "solcore-workshop/notebooks/6a-TMM_introduction.html#setting-up",
- "href": "solcore-workshop/notebooks/6a-TMM_introduction.html#setting-up",
- "title": "Section 6a: Basic cell optics",
+ "objectID": "solcore-workshop/notebooks/9b-GaInP_GaAs_Si_pyramids_new.html#setting-up",
+ "href": "solcore-workshop/notebooks/9b-GaInP_GaAs_Si_pyramids_new.html#setting-up",
+ "title": "Section 9b: Planar III-V epoxy-bonded to textured Si",
"section": "Setting up",
- "text": "Setting up\nFirst, let’s define some materials:\n\nSi = material(\"Si\")\nSiN = material(\"Si3N4\")()\nAg = material(\"Ag\")()\n\nNote the second set of brackets (or lack thereof). The Solcore material system essentially operates in two stages; we first call the material function with the name of the material we want to use, for example Si = material(“Si”), which creates a general Python class corresponding to that material. We then call this class to specify further details, such as the temperature, doping level, or alloy composition (where relavant). This happens below when defining Si_n and Si_p; both are use the Si class defined above, and adding further details to the material. For the definitions of SiN and Ag above, we do both steps in a single line, hence the two sets of brackets.\n\nSi_n = Si(Nd=si(\"1e21cm-3\"), hole_diffusion_length=si(\"10um\"))\nSi_p = Si(Na=si(\"1e16cm-3\"), electron_diffusion_length=si(\"400um\"))\n\nTo look at the effect of interference in the Si layer at different thicknesses, we make a list of thicknesses to test (evenly spaced on a log scale from 400 nm to 300 um):\n\nSi_thicknesses = np.linspace(np.log(0.4e-6), np.log(300e-6), 8)\nSi_thicknesses = np.exp(Si_thicknesses)\n\nwavelengths = si(np.linspace(300, 1200, 400), \"nm\")\n\noptions = {\n \"recalculate_absorption\": True,\n \"optics_method\": \"TMM\",\n \"wavelength\": wavelengths\n }\n\nMake a color palette using the seaborn package to make the plots look nicer\n\ncolors = sns.color_palette('rocket', n_colors=len(Si_thicknesses))\ncolors.reverse()\n\ncreate an ARC layer:\n\nARC_layer = Layer(width=si('75nm'), material=SiN)"
+ "text": "Setting up\nWe load relevant packages and define materials, the same as in the previous example.\n\nfrom solcore import material, si\nfrom solcore.absorption_calculator import search_db, download_db\nimport os\nfrom solcore.structure import Layer\nfrom solcore.light_source import LightSource\nfrom rayflare.ray_tracing import rt_structure\nfrom rayflare.transfer_matrix_method import tmm_structure\nfrom rayflare.textures import planar_surface, regular_pyramids\nfrom rayflare.options import default_options\nfrom solcore.constants import q\nimport numpy as np\nimport matplotlib.pyplot as plt\n\n# download_db()\n\n\nMgF2_pageid = search_db(os.path.join(\"MgF2\", \"Rodriguez-de Marcos\"))[0][0];\nTa2O5_pageid = search_db(os.path.join(\"Ta2O5\", \"Rodriguez-de Marcos\"))[0][0];\nSU8_pageid = search_db(\"SU8\")[0][0];\nAg_pageid = search_db(os.path.join(\"Ag\", \"Jiang\"))[0][0];\n\nepoxy = material(\"BK7\")()\n\nMgF2 = material(str(MgF2_pageid), nk_db=True)();\nTa2O5 = material(str(Ta2O5_pageid), nk_db=True)();\nSU8 = material(str(SU8_pageid), nk_db=True)();\nAg = material(str(Ag_pageid), nk_db=True)();\n\nwindow = material(\"AlInP\")(Al=0.52)\nGaInP = material(\"GaInP\")\nAlGaAs = material(\"AlGaAs\")\n\nAir = material(\"Air\")()\n\nGaAs = material(\"GaAs\")\n\nSi = material(\"Si\")\n\nAl2O3 = material(\"Al2O3P\")()\nAl = material(\"Al\")()\n\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\n1 results found.\npageid shelf book page filepath hasrefractive hasextinction rangeMin rangeMax points\n234 main MgF2 Rodriguez-de_Marcos main/MgF2/Rodriguez-de Marcos.yml 1 1 0.0299919 2.00146 960\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\n1 results found.\npageid shelf book page filepath hasrefractive hasextinction rangeMin rangeMax points\n475 main Ta2O5 Rodriguez-de_Marcos main/Ta2O5/Rodriguez-de Marcos.yml 1 1 0.0294938 1.51429 212\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\n2 results found.\npageid shelf book page filepath hasrefractive hasextinction rangeMin rangeMax points\n2835 other negative_tone_photoresists Microchem_SU8_2000 other/resists/Microchem SU-8 2000.yml 1 0 0.32 0.8 200\n2836 other negative_tone_photoresists Microchem_SU8_3000 other/resists/Microchem SU-8 3000.yml 1 0 0.32 1.7 200\nDatabase file found at /Users/phoebe/.solcore/nk/nk.db\n1 results found.\npageid shelf book page filepath hasrefractive hasextinction rangeMin rangeMax points\n2 main Ag Jiang main/Ag/Jiang.yml 1 1 0.3 2.0 1701\n\n\nWe define the layers we will need, as before. We specify the thickness of the silicon (280 \\(\\mu\\)m) and epoxy (1 mm) at the top:\n\nd_Si = 280e-6 # thickness of Si wafer\nd_epoxy = 1e-3 # thickness of epoxy\n\nARC = [\n Layer(110e-9, MgF2),\n Layer(65e-9, Ta2O5),\n]\n\nGaInP_junction = [\n Layer(17e-9, window),\n Layer(400e-9, GaInP(In=0.50)),\n Layer(100e-9, AlGaAs(Al=0.8))]\n\n# 100 nm TJ\ntunnel_1 = [\n Layer(80e-9, AlGaAs(Al=0.8)),\n Layer(20e-9, GaInP(In=0.5)),\n]\n\nGaAs_junction = [\n Layer(17e-9, GaInP(In=0.5), role=\"window\"),\n Layer(1050e-9, GaAs()),\n Layer(70e-9, AlGaAs(Al=0.8), role=\"bsf\")]\n\nspacer_ARC = [\n Layer(80e-9, Ta2O5),\n]"
},
{
- "objectID": "solcore-workshop/notebooks/6a-TMM_introduction.html#effect-of-si-thickness",
- "href": "solcore-workshop/notebooks/6a-TMM_introduction.html#effect-of-si-thickness",
- "title": "Section 6a: Basic cell optics",
- "section": "Effect of Si thickness",
- "text": "Effect of Si thickness\nNow we are going to loop through the different Si thicknesses generated above, and create a simple solar cell-like structure. Because we will only do an optical calculation, we don’t need to define a junction and can just make a simple stack of layers.\nWe then calculate reflection, absorption and transmission (RAT) for two different situations: 1. a fully coherent stack 2. assuming the silicon layer is incoherent. This means that light which enters the Si layer cannot interfere with itself, but light in the ARC layer can still show interference. In very thick layers (much thicker than the wavelength of light being considered) this is likely to be more physically accurate because real light does not have infinite coherence length; i.e. if you measured wavelength-dependent transmission or reflection of a Si wafer hundreds of microns thick you would not expect to see interference fringes.\nPLOT 1\n\nplt.figure()\n\nfor i1, Si_t in enumerate(Si_thicknesses):\n\n base_layer = Layer(width=Si_t, material=Si_p) # silicon layer\n solar_cell = OptiStack([ARC_layer, base_layer]) # OptiStack (optical stack) to feed into calculate_rat function\n\n # Coherent calculation:\n RAT_c = calculate_rat(solar_cell, wavelengths*1e9, no_back_reflection=False) # coherent calculation\n # For historical reasons, Solcore's default setting is to ignore reflection at the back of the cell (i.e. at the\n # interface between the final material in the stack and the substrate). Hence we need to tell the calculate_rat\n # function NOT to ignore this reflection (no_back_reflection=False).\n\n # Calculation assuming no interference in the silicon (\"incoherent\"):\n RAT_i = calculate_rat(solar_cell, wavelengths*1e9, no_back_reflection=False,\n coherent=False, coherency_list=['c', 'i']) # partially coherent: ARC is coherent, Si is not\n\n # Plot the results:\n plt.plot(wavelengths*1e9, RAT_c[\"A\"], color=colors[i1], label=str(round(Si_t*1e6, 1)), alpha=0.7)\n plt.plot(wavelengths*1e9, RAT_i[\"A\"], '--', color=colors[i1])\n\nplt.legend(title=r\"Thickness ($\\mu$m)\")\nplt.xlim(300, 1300)\nplt.ylim(0, 1.02)\nplt.ylabel(\"Absorption\")\nplt.title(\"(1) Absorption in Si with varying thickness\")\nplt.show()\n\n\n\n\nWe can see that the coherent calculations (solid lines) show clear interference fringes which depend on the Si thickness. The incoherent calculations do not have these fringes and seem to lie around the average of the interference fringes. For both sets of calculations, we see increasing absorption as the Si gets thicker, as expected."
+ "objectID": "solcore-workshop/notebooks/9b-GaInP_GaAs_Si_pyramids_new.html#defining-the-cell-layers",
+ "href": "solcore-workshop/notebooks/9b-GaInP_GaAs_Si_pyramids_new.html#defining-the-cell-layers",
+ "title": "Section 9b: Planar III-V epoxy-bonded to textured Si",
+ "section": "Defining the cell layers",
+ "text": "Defining the cell layers\nThere are three interfaces in the cell which will define the structure to simulate:\n\nthe III-V/epoxy interface, where the epoxy itself will be treated as a bulk layer in the simulation\nthe epoxy/Si interface, where the Si has a pyramidal texture (the Si itself is another bulk layer in the simulation).\nthe rear surface of the cell, where the Si again has a pyramidal texture (and we assume there is a silver back mirror behind the cell)\n\nThese 3 interfaces are defined here, using the pre-defined textures for a planar surface or regular pyramids:\n\nfront_layers = ARC + GaInP_junction + tunnel_1 + GaAs_junction + spacer_ARC\n\nfront_surf = planar_surface(\n interface_layers = front_layers\n)\n\nSi_front = regular_pyramids(\n elevation_angle=50,\n upright=True\n)\n\nSi_back = regular_pyramids(\n elevation_angle=50,\n upright=False\n)\n\nfixed h 0.5958767962971049\nfixed h 0.5958767962971049\n\n\nNow we set relevant options for the solver. We set the number of rays to trace at each wavelength (more rays will make the result less noisy, but increase computation time) and whether to calculate the absorption profile in the bulk layers (no, in this case). The randomize_surface options determines whether the ray keeps track of its positions in the unit cell while travelling between surfaces; we set this to False to mimic random pyramids.\n\noptions = default_options()\n\nwl = np.arange(300, 1201, 10) * 1e-9\nAM15G = LightSource(source_type=\"standard\", version=\"AM1.5g\", x=wl,\n output_units=\"photon_flux_per_m\")\n\noptions.wavelengths = wl\noptions.project_name = \"III_V_Si_cell\"\n\n# options for ray-tracing\noptions.randomize_surface = True\noptions.n_rays = 1000\noptions.bulk_profile = False\noptions.theta_in = 45*np.pi/180"
},
{
- "objectID": "solcore-workshop/notebooks/6a-TMM_introduction.html#effect-of-reflective-substrate",
- "href": "solcore-workshop/notebooks/6a-TMM_introduction.html#effect-of-reflective-substrate",
- "title": "Section 6a: Basic cell optics",
- "section": "Effect of reflective substrate",
- "text": "Effect of reflective substrate\nNow we repeat the calculation, but with an Ag substrate under the Si. Previously, we did not specify the substrate and so it was assumed by Solcore to be air (\\(n\\) = 1, \\(\\kappa\\) = 0).\nPLOT 2\n\nplt.figure()\n\nfor i1, Si_t in enumerate(Si_thicknesses):\n\n base_layer = Layer(width=Si_t, material=Si_p)\n\n # As before, but now we specify the substrate to be silver:\n solar_cell = OptiStack([ARC_layer, base_layer], substrate=Ag)\n\n RAT_c = calculate_rat(solar_cell, wavelengths*1e9, no_back_reflection=False)\n RAT_i = calculate_rat(solar_cell, wavelengths*1e9, no_back_reflection=False,\n coherent=False, coherency_list=['c', 'i'])\n plt.plot(wavelengths*1e9, RAT_c[\"A\"], color=colors[i1],\n label=str(round(Si_t*1e6, 1)), alpha=0.7)\n plt.plot(wavelengths*1e9, RAT_i[\"A\"], '--', color=colors[i1])\n\nplt.legend(title=r\"Thickness ($\\mu$m)\")\nplt.xlim(300, 1300)\nplt.ylim(0, 1.02)\nplt.ylabel(\"Absorption\")\nplt.title(\"(2) Absorption in Si with varying thickness (Ag substrate)\")\nplt.show()\n\n\n\n\nWe see that the interference fringes get more prominent in the coherent calculation, due to higher reflection at the rear Si/Ag surface compared to Ag/Air. We also see a slightly boosted absorption at long wavelengths at all thicknesses, again due to improved reflection at the rear surface"
+ "objectID": "solcore-workshop/notebooks/9b-GaInP_GaAs_Si_pyramids_new.html#defining-the-structures",
+ "href": "solcore-workshop/notebooks/9b-GaInP_GaAs_Si_pyramids_new.html#defining-the-structures",
+ "title": "Section 9b: Planar III-V epoxy-bonded to textured Si",
+ "section": "Defining the structures",
+ "text": "Defining the structures\nFinally, we define the ray-tracing structure we will use, using the interfaces, bulk materials, and options set above. Because we want to calculate the reflection/absorption/transmission probabilities at the front surface using TMM, we set the use_TMM argument to True. We also define a completely planar cell with the same layer thicknesses etc. to compare and evaluate the effect of the textures Si surfaces.\n\nsolar_cell = rt_structure(\n textures=[front_surf, Si_front, Si_back],\n materials=[epoxy, Si()],\n widths=[d_epoxy, d_Si],\n incidence=Air,\n transmission=Ag,\n options=options,\n use_TMM=True,\n save_location=\"current\", # lookup table save location\n overwrite=True, # whether to overwrite any previously existing results, if found\n)\n\n# options for TMM\noptions.coherent = False\noptions.coherency_list = len(front_layers)*['c'] + ['i']*2\n\nsolar_cell_planar = tmm_structure(\n layer_stack = front_layers + [Layer(d_epoxy, epoxy), Layer(d_Si, Si())],\n incidence=Air,\n transmission=Ag,\n)\n\nPre-computing TMM lookup table(s)"
},
{
- "objectID": "solcore-workshop/notebooks/6a-TMM_introduction.html#effect-of-polarization-and-angle-of-incidence",
- "href": "solcore-workshop/notebooks/6a-TMM_introduction.html#effect-of-polarization-and-angle-of-incidence",
- "title": "Section 6a: Basic cell optics",
- "section": "Effect of polarization and angle of incidence",
- "text": "Effect of polarization and angle of incidence\nFinally, we look at the effect of incidence angle and polarization of the light hitting the cell.\nPLOT 3\n\nangles = [0, 30, 60, 70, 80, 89] # angles in degrees\n\nARC_layer = Layer(width=si('75nm'), material=SiN)\nbase_layer = Layer(width=si(\"100um\"), material=Si_p)\n\ncolors = sns.cubehelix_palette(n_colors=len(angles))\n\nplt.figure()\n\nfor i1, theta in enumerate(angles):\n\n solar_cell = OptiStack([ARC_layer, base_layer])\n\n RAT_s = calculate_rat(solar_cell, wavelengths*1e9, angle=theta,\n pol='s',\n no_back_reflection=False,\n coherent=False, coherency_list=['c', 'i'])\n RAT_p = calculate_rat(solar_cell, wavelengths*1e9, angle=theta,\n pol='p',\n no_back_reflection=False,\n coherent=False, coherency_list=['c', 'i'])\n\n plt.plot(wavelengths*1e9, RAT_s[\"A\"], color=colors[i1], label=str(round(theta)))\n plt.plot(wavelengths*1e9, RAT_p[\"A\"], '--', color=colors[i1])\n\nplt.legend(title=r\"$\\theta (^\\circ)$\")\nplt.xlim(300, 1300)\nplt.ylim(0, 1.02)\nplt.ylabel(\"Absorption\")\nplt.title(\"(3) Absorption in Si with varying angle of incidence\")\nplt.show()\n\n\n\n\nFor normal incidence (\\(\\theta = 0^\\circ\\)), s (solid lines) and p (dashed lines) polarization are equivalent. As the incidence angle increases, in general absorption is higher for p-polarized light (due to lower reflection). Usually, sunlight is modelled as unpolarized light, which computationally is usually done by averaging the results for s and p-polarized light."
+ "objectID": "solcore-workshop/notebooks/9b-GaInP_GaAs_Si_pyramids_new.html#calculations",
+ "href": "solcore-workshop/notebooks/9b-GaInP_GaAs_Si_pyramids_new.html#calculations",
+ "title": "Section 9b: Planar III-V epoxy-bonded to textured Si",
+ "section": "Calculations",
+ "text": "Calculations\nCalculate the R/A/T for the planar reference cell:\n\ntmm_result = solar_cell_planar.calculate(options=options)\n\nGaInP_A_tmm = tmm_result['A_per_layer'][:,3]\nGaAs_A_tmm = tmm_result['A_per_layer'][:,8]\nSi_A_tmm = tmm_result['A_per_layer'][:,len(front_layers)+1]\n\nJmax_GaInP_tmm = q*np.trapz(GaInP_A_tmm*AM15G.spectrum()[1], x=wl)/10\nJmax_GaAs_tmm = q*np.trapz(GaAs_A_tmm*AM15G.spectrum()[1], x=wl)/10\nJmax_Si_tmm = q*np.trapz(Si_A_tmm*AM15G.spectrum()[1], x=wl)/10\n\nCalculate the R/A/T for the textured cell:\n\nrt_result = solar_cell.calculate(options=options)\n\nGaInP_absorption_ARC = rt_result['A_per_interface'][0][:,3]\nGaAs_absorption_ARC = rt_result['A_per_interface'][0][:,8]\nSi_absorption_ARC = rt_result['A_per_layer'][:,1]\n\nJmax_GaInP = q*np.trapz(GaInP_absorption_ARC*AM15G.spectrum()[1], x=wl)/10\nJmax_GaAs = q*np.trapz(GaAs_absorption_ARC*AM15G.spectrum()[1], x=wl)/10\nJmax_Si = q*np.trapz(Si_absorption_ARC*AM15G.spectrum()[1], x=wl)/10"
},
{
- "objectID": "solcore-workshop/notebooks/6a-TMM_introduction.html#conclusions",
- "href": "solcore-workshop/notebooks/6a-TMM_introduction.html#conclusions",
- "title": "Section 6a: Basic cell optics",
- "section": "Conclusions",
- "text": "Conclusions\nWe have now seen some effects of interference in layers of different thicknesses, and seen the effect of adding a highly reflective substrate. So we already have two strategies for light-trapping/improving the absorption in a solar cell: adding an anti-reflection coating (in example 1a), to reduce front-surface reflection and get more light into the cell, and adding a highly reflective layer at the back, to reduce loss through the back of the cell and keep light trapped in the cell."
+ "objectID": "solcore-workshop/notebooks/9b-GaInP_GaAs_Si_pyramids_new.html#plotting-the-results",
+ "href": "solcore-workshop/notebooks/9b-GaInP_GaAs_Si_pyramids_new.html#plotting-the-results",
+ "title": "Section 9b: Planar III-V epoxy-bonded to textured Si",
+ "section": "Plotting the results",
+ "text": "Plotting the results\nFinally, we plot the results; the solid lines show the results for the textured Si cell (calculated using ray-tracing), the dashed lines for the planar cell (calculated using TMM). The maximum possible currents are shown in the plot, with the value in brackets for Si being for the planar cell.\n\nplt.figure(figsize=(6,3))\nplt.plot(wl * 1e9, GaInP_absorption_ARC, \"-k\", label=\"GaInP\")\nplt.plot(wl * 1e9, GaAs_absorption_ARC, \"-b\", label=\"GaAs\")\nplt.plot(wl * 1e9, Si_absorption_ARC, \"-r\", label=\"Si\")\nplt.plot(wl * 1e9, GaInP_A_tmm, \"--k\")\nplt.plot(wl * 1e9, GaAs_A_tmm, \"--b\")\nplt.plot(wl * 1e9, Si_A_tmm, \"--r\")\nplt.plot(wl * 1e9, rt_result['R'], '-', color='grey', label=\"Reflected\")\nplt.plot(wl * 1e9, tmm_result['R'], '--', color='grey')\n\nplt.text(420, 0.55, r\"{:.1f} mA/cm$^2$\".format(Jmax_GaInP))\nplt.text(670, 0.55, r\"{:.1f} mA/cm$^2$\".format(Jmax_GaAs))\nplt.text(870, 0.55, r\"{:.1f} mA/cm$^2$\".format(Jmax_Si))\nplt.text(870, 0.45, r\"({:.1f} mA/cm$^2)$\".format(Jmax_Si_tmm))\n\nplt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)\nplt.tight_layout()\nplt.show()"
},
{
- "objectID": "solcore-workshop/notebooks/6a-TMM_introduction.html#questions",
- "href": "solcore-workshop/notebooks/6a-TMM_introduction.html#questions",
- "title": "Section 6a: Basic cell optics",
- "section": "Questions",
- "text": "Questions\n\nWhy are the interference fringes stronger when adding a silver back mirror, compared to having air behind the Si?\nWe modelled s and p-polarized light - how do we normally model unpolarized light?"
+ "objectID": "solcore-workshop/notebooks/9b-GaInP_GaAs_Si_pyramids_new.html#questionschallenges",
+ "href": "solcore-workshop/notebooks/9b-GaInP_GaAs_Si_pyramids_new.html#questionschallenges",
+ "title": "Section 9b: Planar III-V epoxy-bonded to textured Si",
+ "section": "Questions/challenges",
+ "text": "Questions/challenges\n\nDoes it make sense to do a ray-tracing calculation for short wavelengths? For this structure, can you speed up the calculation and avoid the random noise at short wavelengths?\nHow much current is lost to parasitic absorption in e.g. tunnel junctions, window layers etc.?\nHow can we reduce reflection at the epoxy interfaces?\nIf the epoxy/glass layer is much thicker than the relevant incident wavelengths, and not absorbing, does the exact thickness matter in the simulation?\nWhat happens if only the rear surface is textured? Would a structure without the front texture have other advantages?\nWhy does the Si have lower absorption/limiting current in this structure compared to the previous example?"
},
{
"objectID": "solcore-workshop/notebooks/2-Efficiency_limits.html",
diff --git a/docs/solar-cell-simulation/notebooks/1a-simple_cell.html b/docs/solar-cell-simulation/notebooks/1a-simple_cell.html
index cb68d51..8accd72 100644
--- a/docs/solar-cell-simulation/notebooks/1a-simple_cell.html
+++ b/docs/solar-cell-simulation/notebooks/1a-simple_cell.html
@@ -2,7 +2,7 @@
-
+
@@ -127,6 +127,10 @@
Solcore Workshop 2023
+
Section 9a: Planar III-V on planar Si, with rear grating
In this example, we will build two structures similar to those described in this paper. These are both triple-junction, two-terminal GaInP/GaAs/Si cells; one cell is planar, while the other has a diffraction grating deposited on the rear of the bottom Si cell to boost its current.
Setting up
-
+
from solcore import material, sifrom solcore.absorption_calculator import search_db, download_dbimport os
@@ -281,122 +285,123 @@
Setting up
import matplotlib.pyplot as plt
As before, we load some materials from the refractiveindex.info database. The MgF\(_2\) and Ta\(_2\)O\(_5\) are the same as the ARC example; the SU8 is a negative photoresist which was used in the reference paper The optical constants for silver are also loaded from a reliable literature source. Note that the exact compositions of some semiconductor alloy layers (InGaP, AlInP and AlGaAs) are not given in the paper and are thus reasonable guesses.
Now we define the layers for the III-V top junctions, and the Si wafer, grouping them together in a logical way. In this example, we will only do optical simulations, so we will not set e.g. diffusion lengths or doping levels.
As for Example 7, to get physically reasonable results we must treat the very thick layers in the structure incoherently. The coh_layers variable sums up how many thin layers (which must be treated coherently) must be included in the coherency_list options.
Planar cell
Now we define the planar cell, and options for the solver:
Run the TMM calculation for the planar cell, and then extract the relevant layer absorptions. These are used to calculate limiting currents (100% internal quantum efficiency), which are displayed on the plot with the absorption in each layer.
Run the TMM calculation for the planar cell, and then extract the relevant layer absorptions. These are used to calculate limiting currents (100% internal quantum efficiency), which are displayed on the plot with the absorption in each layer.
Database file found at /Users/phoebe/.solcore/nk/nk.db
Material main/Ag/Jiang.yml loaded.
@@ -412,7 +417,7 @@
Planar cell
Material main/Ta2O5/Rodriguez-de Marcos.yml loaded.
-
+
@@ -421,83 +426,83 @@
Cell with rear grat
Now, for the cell with a grating on the rear, we have a multi-scale problem where we must combine the calculation of absorption in a very thick (compared to the wavelengths of light) layer of Si with the effect of a wavelength-scale (1000 nm pitch) diffraction grating. For this, we will use the Angular Redistribution Matrix Method (ARMM) which was also used in Example 8.
The front surface of the cell (i.e. all the layers on top of Si) are planar, and can be treated using TMM. The rear surface of the cell, which has a crossed grating consisting of silver and SU8, must be treated with RCWA to account for diffraction. The thick Si layer will be the bulk coupling layer between these two interfaces.
First, we set up the rear grating surface; we must define its lattice vectors, and place the Ag rectangle in the unit cell of the grating. More details on how unit cells of different shapes can be defined for the RCWA solver can be found here.
Now, we define the Si bulk layer, and the III-V layers which go in the front interface. Finally, we put everything together into the ARMM Structure, also giving the incidence and transmission materials.
Because RCWA calculations are very slow compared to TMM, it makes sense to only carry out the RCWA calculation at wavelengths where the grating has any effect. Depending on the wavelength, all the incident light may be absorbed in the III-V layers or in its first pass through the Si, so it never reaches the grating. We check this by seeing which wavelengths have even a small amount of transmission into the silver back mirror, and only doing the new calculation at these wavelengths. At shorter wavelengths, the results previously calculated using TMM can be used.
We extract the relevant absorption per layer, and use it to calculate the new limiting current for the Si junction. The plot compares the absorption in the Si with and without the grating.
Section 9b: Planar III-V epoxy-bonded to textured Si
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
The structure in this example is based on that of the previous example (9a), but with the planar bottom Si cell replaced by a Si cell with a pyramidal texture, bonded to the III-V top cells with a low-index epoxy/glass layer.
+
We could use the angular redistribution matrix method as in the previous example - however, because in this example we only need to use TMM and ray-tracing (RT), we can use the ray-tracing method with integrated RT directly (this is generally faster, because we do not need to calculate the behaviour of the surfaces for every angle of incidence).
+
+
Setting up
+
We load relevant packages and define materials, the same as in the previous example.
fixed h 0.5958767962971049
+fixed h 0.5958767962971049
+
+
+
Now we set relevant options for the solver. We set the number of rays to trace at each wavelength (more rays will make the result less noisy, but increase computation time) and whether to calculate the absorption profile in the bulk layers (no, in this case). The randomize_surface options determines whether the ray keeps track of its positions in the unit cell while travelling between surfaces; we set this to False to mimic random pyramids.
Finally, we define the ray-tracing structure we will use, using the interfaces, bulk materials, and options set above. Because we want to calculate the reflection/absorption/transmission probabilities at the front surface using TMM, we set the use_TMM argument to True. We also define a completely planar cell with the same layer thicknesses etc. to compare and evaluate the effect of the textures Si surfaces.
Finally, we plot the results; the solid lines show the results for the textured Si cell (calculated using ray-tracing), the dashed lines for the planar cell (calculated using TMM). The maximum possible currents are shown in the plot, with the value in brackets for Si being for the planar cell.
Does it make sense to do a ray-tracing calculation for short wavelengths? For this structure, can you speed up the calculation and avoid the random noise at short wavelengths?
+
How much current is lost to parasitic absorption in e.g. tunnel junctions, window layers etc.?
+
How can we reduce reflection at the epoxy interfaces?
+
If the epoxy/glass layer is much thicker than the relevant incident wavelengths, and not absorbing, does the exact thickness matter in the simulation?
+
What happens if only the rear surface is textured? Would a structure without the front texture have other advantages?
+
Why does the Si have lower absorption/limiting current in this structure compared to the previous example?
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/solcore-workshop/notebooks/9b-GaInP_GaAs_Si_pyramids_new_files/figure-html/cell-10-output-1.png b/docs/solcore-workshop/notebooks/9b-GaInP_GaAs_Si_pyramids_new_files/figure-html/cell-10-output-1.png
new file mode 100644
index 0000000..e6ce646
Binary files /dev/null and b/docs/solcore-workshop/notebooks/9b-GaInP_GaAs_Si_pyramids_new_files/figure-html/cell-10-output-1.png differ
diff --git a/docs/solcore-workshop/workshop2023.html b/docs/solcore-workshop/workshop2023.html
index 8174ffe..5d6f635 100644
--- a/docs/solcore-workshop/workshop2023.html
+++ b/docs/solcore-workshop/workshop2023.html
@@ -2,7 +2,7 @@
-
+
@@ -93,6 +93,10 @@
Solcore Workshop 2023
+
diff --git a/other/ARC_optimization-SchottkyCell.ipynb b/other/ARC_optimization-SchottkyCell.ipynb
new file mode 100644
index 0000000..ccd0f1b
--- /dev/null
+++ b/other/ARC_optimization-SchottkyCell.ipynb
@@ -0,0 +1,256 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "source": [
+ "# Optimizating ARC for a Schottky cell"
+ ],
+ "metadata": {
+ "collapsed": false
+ },
+ "id": "156703599f1a01b5"
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "outputs": [],
+ "source": [
+ "# -*- coding: utf-8 -*-\n",
+ "\"\"\"\n",
+ "Created on Tue Mar 22 11:33:59 2022\n",
+ "\n",
+ "@author: z5228379\n",
+ "\"\"\"\n",
+ "\n",
+ "\"\"\" Optimizing a double-layer MgF2/Ta2O5 anti-reflection coating for \"infinitely-thick\"\n",
+ "GaAs. Minimize reflection * AM0 spectrum (weighted reflectance).\n",
+ "\n",
+ "To use yabox for the DE, we need to define a class which sets up the problem and has an\n",
+ "'evaluate' function within it, which will actually calculate the value we are trying to\n",
+ "minimize for each set of parameters.\n",
+ "\n",
+ "The \"if __name__ == \"__main__\" construction is used to avoid issues with parallel processing on Windows.\n",
+ "The issues arises because the multiprocessing module uses a different process on Windows than on UNIX\n",
+ "systems which will throw errors if this construction is not used.\n",
+ "\"\"\"\n",
+ "from typing import Sequence\n",
+ "import numpy as np\n",
+ "import matplotlib.pyplot as plt\n",
+ "\n",
+ "from solcore import material\n",
+ "from solcore.optics.tmm import OptiStack, calculate_rat\n",
+ "from solcore.light_source import LightSource\n",
+ "\n",
+ "\n",
+ "from solcore.optimization import DE\n",
+ "from solcore.interpolate import interp1d\n",
+ "\n",
+ "\n",
+ "class CalcRDiff:\n",
+ " def __init__(self):\n",
+ " \"\"\" Make the wavelength and the materials n and k data object attributes.\n",
+ "\n",
+ " The n and k data are extracted from the Solcore materials rather than using\n",
+ " the material directly because there is currently an issue with using the\n",
+ " Solcore material class in parallel computations.\n",
+ " \"\"\"\n",
+ " self.wl = np.linspace(300, 2000, 200)\n",
+ " \n",
+ "\n",
+ " \n",
+ " wl_n, n, wl_k, k = np.loadtxt(\"C:/Users/z5228379/Downloads/ZnO.csv\", \n",
+ " delimiter=\",\", unpack=True,encoding='utf-8-sig')\n",
+ " \n",
+ " wl_n = wl_n[~np.isnan(wl_n)]\n",
+ " n = n[~np.isnan(n)]\n",
+ "\n",
+ " n_wl = interp1d(wl_n, n)\n",
+ " k_wl = interp1d(wl_k, k)\n",
+ " \n",
+ " \n",
+ " self.ZnO= [\n",
+ " self.wl,\n",
+ " n_wl(self.wl),\n",
+ " k_wl(self.wl)\n",
+ " ]\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " wl_n, n, wl_k, k = np.loadtxt(\"C:/Users/z5228379/Downloads/ALn&k.csv\", \n",
+ " delimiter=\",\", unpack=True,encoding='utf-8-sig')\n",
+ "\n",
+ " \n",
+ " n_wl = interp1d(wl_n, n)\n",
+ " k_wl = interp1d(wl_k, k)\n",
+ " \n",
+ " \n",
+ " self.Al= [\n",
+ " self.wl,\n",
+ " n_wl(self.wl),\n",
+ " k_wl(self.wl)\n",
+ " ]\n",
+ " \n",
+ " self.Si = [\n",
+ " self.wl,\n",
+ " material(\"Si\")().n(self.wl * 1e-9),\n",
+ " material(\"Si\")().k(self.wl * 1e-9),\n",
+ " ]\n",
+ "\n",
+ " \n",
+ " spectr = LightSource(\n",
+ " source_type=\"standard\",\n",
+ " version=\"AM1.5g\",\n",
+ " x=self.wl,\n",
+ " output_units=\"photon_flux_per_m\",\n",
+ " concentration=1,\n",
+ " ).spectrum(self.wl * 1e-9)[1]\n",
+ "\n",
+ " \n",
+ " self.spectrum = spectr / max(spectr)\n",
+ " \n",
+ "\n",
+ " def reflectance(self, x: Sequence[float]) -> float:\n",
+ " \"\"\" Create a list with the format [thickness, wavelengths, n_data, k_data] for\n",
+ " each layer.\n",
+ "\n",
+ " This is one of the acceptable formats in which OptiStack can take information\n",
+ " (look at the Solcore documentation or at the OptiStack code for more info)\n",
+ " We set no_back_reflection to True because we DO NOT want to include reflection\n",
+ " at the back surface (assume GaAs is infinitely thick)\n",
+ "\n",
+ " :param x: List with the thicknesses of the two layers in the ARC.\n",
+ " :return: Array with the reflection at each wavelength\n",
+ " \"\"\"\n",
+ " \n",
+ " Si = material(\"Si\")()\n",
+ " \n",
+ " arc = [[x[0]] + self.ZnO, [x[1]] + self.Al]\n",
+ " full_stack = OptiStack(arc, no_back_reflection=False, substrate=Si)\n",
+ " return calculate_rat(full_stack, self.wl, no_back_reflection=False)[\"R\"]\n",
+ " \n",
+ " def absorption(self, x: Sequence[float]) -> float:\n",
+ " \n",
+ " \n",
+ " Si = material(\"Si\")()\n",
+ " \n",
+ " arc = [[x[0]] + self.ZnO, [x[1]] + self.Al]\n",
+ " full_stack = OptiStack(arc, no_back_reflection=False, substrate=Si)\n",
+ " return calculate_rat(full_stack, self.wl, no_back_reflection=False)[\"A\"]\n",
+ " def transmission(self, x: Sequence[float]) -> float:\n",
+ "\n",
+ " \n",
+ " Si = material(\"Si\")()\n",
+ " \n",
+ " arc = [[x[0]] + self.ZnO, [x[1]] + self.Al]\n",
+ "\n",
+ " \n",
+ " full_stack = OptiStack(arc, no_back_reflection=False, substrate=Si)\n",
+ " return calculate_rat(full_stack, self.wl, no_back_reflection=False)[\"T\"]\n",
+ " def evaluate(self, x: Sequence[float]) -> float:\n",
+ " \"\"\" Returns the number the DA algorithm has to minimise.\n",
+ "\n",
+ " In this case, this is the weighted reflectance\n",
+ "\n",
+ " :param x: List with the thicknesses of the two layers in the ARC.\n",
+ " :return: weighted reflectance\n",
+ " \"\"\"\n",
+ " return np.mean(self.reflectance(x) * self.spectrum)\n",
+ "\n",
+ " def plot(self, x: Sequence[float]) -> None:\n",
+ " \"\"\" Plots the reflectance\n",
+ "\n",
+ " :param x: List with the thicknesses of the two layers in the ARC.\n",
+ " :return: None\n",
+ " \"\"\"\n",
+ " plt.figure()\n",
+ " plt.plot(self.wl, self.reflectance(x), label=\"Reflectance\")\n",
+ " plt.plot(self.wl, self.absorption(x), label=\"Absorption\")\n",
+ " plt.plot(self.wl, self.transmission(x), label=\"Transmission\")\n",
+ " \n",
+ " plt.xlabel(\"Wavelength (nm)\")\n",
+ " plt.ylabel(\"R/A/T\")\n",
+ " plt.legend()\n",
+ " plt.show()\n",
+ "\n",
+ " def plot_weighted(self, x: Sequence[float]) -> None:\n",
+ " \"\"\" Plots the weighted reflectance.\n",
+ "\n",
+ " :param x: List with the thicknesses of the two layers in the ARC.\n",
+ " :return: None\n",
+ " \"\"\"\n",
+ " plt.figure()\n",
+ " plt.plot(self.wl, self.reflectance(x) * self.spectrum)\n",
+ " plt.xlabel(\"Wavelength (nm)\")\n",
+ " plt.ylabel(\"R weighted by AM0\")\n",
+ " plt.show()\n",
+ "\n",
+ "\n",
+ "def main():\n",
+ "\n",
+ "\n",
+ " # number of iterations for Differential Evolution\n",
+ " maxiters = 70\n",
+ "\n",
+ " # class the DE algorithm is going to use, as defined above\n",
+ " PDE_class = CalcRDiff()\n",
+ "\n",
+ " # Pass the function which will be minimized to the PDE (parallel differential evolution)\n",
+ " # solver. PDE calculates the results for each population in parallel to speed up the\n",
+ " # overall process\n",
+ "\n",
+ "# =============================================================================\n",
+ "# PDE_obj = DE(PDE_class.evaluate, bounds=[[0, 250], [0, 250], [0, 250], [0, 250]], maxiters=maxiters)\n",
+ "# =============================================================================\n",
+ " PDE_obj = DE(PDE_class.evaluate, bounds=[[0, 100], [10,15]], maxiters=maxiters)\n",
+ " # PDE_obj = DE(PDE_class.evaluate, bounds=[[0, 500]], maxiters=maxiters)\n",
+ " # solve, i.e. minimize the problem\n",
+ " res = PDE_obj.solve()\n",
+ "\n",
+ " \"\"\"\n",
+ " PDE_obj.solve() returns 5 things:\n",
+ " - res[0] is a list of the parameters which gave the minimized value\n",
+ " - res[1] is that minimized value\n",
+ " - res[2] is the evolution of the best population (the best population from each \n",
+ " iteration\n",
+ " - res[3] is the evolution of the minimized value, i.e. the fitness over each iteration\n",
+ " - res[4] is the evolution of the mean fitness over the iterations\n",
+ " \"\"\"\n",
+ " best_pop = res[0]\n",
+ " print(\"Parameters for best result:\", best_pop, res[1])\n",
+ "\n",
+ " PDE_class.plot(best_pop)\n",
+ " PDE_class.plot_weighted(best_pop)\n",
+ "\n",
+ "if __name__ == '__main__':\n",
+ " main()\n",
+ "\n"
+ ],
+ "metadata": {
+ "collapsed": false
+ },
+ "id": "initial_id"
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 2
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython2",
+ "version": "2.7.6"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/other/ARC_optimization-SchottkyCell.py b/other/ARC_optimization-SchottkyCell.py
new file mode 100644
index 0000000..93bc461
--- /dev/null
+++ b/other/ARC_optimization-SchottkyCell.py
@@ -0,0 +1,210 @@
+# -*- coding: utf-8 -*-
+"""
+Created on Tue Mar 22 11:33:59 2022
+
+@author: z5228379
+"""
+
+""" Optimizing a double-layer MgF2/Ta2O5 anti-reflection coating for "infinitely-thick"
+GaAs. Minimize reflection * AM0 spectrum (weighted reflectance).
+
+To use yabox for the DE, we need to define a class which sets up the problem and has an
+'evaluate' function within it, which will actually calculate the value we are trying to
+minimize for each set of parameters.
+
+The "if __name__ == "__main__" construction is used to avoid issues with parallel processing on Windows.
+The issues arises because the multiprocessing module uses a different process on Windows than on UNIX
+systems which will throw errors if this construction is not used.
+"""
+from typing import Sequence
+import numpy as np
+import matplotlib.pyplot as plt
+
+from solcore import material
+from solcore.optics.tmm import OptiStack, calculate_rat
+from solcore.light_source import LightSource
+
+
+from solcore.optimization import DE
+from solcore.interpolate import interp1d
+
+
+class CalcRDiff:
+ def __init__(self):
+ """ Make the wavelength and the materials n and k data object attributes.
+
+ The n and k data are extracted from the Solcore materials rather than using
+ the material directly because there is currently an issue with using the
+ Solcore material class in parallel computations.
+ """
+ self.wl = np.linspace(300, 2000, 200)
+
+
+
+ wl_n, n, wl_k, k = np.loadtxt("C:/Users/z5228379/Downloads/ZnO.csv",
+ delimiter=",", unpack=True,encoding='utf-8-sig')
+
+ wl_n = wl_n[~np.isnan(wl_n)]
+ n = n[~np.isnan(n)]
+
+ n_wl = interp1d(wl_n, n)
+ k_wl = interp1d(wl_k, k)
+
+
+ self.ZnO= [
+ self.wl,
+ n_wl(self.wl),
+ k_wl(self.wl)
+ ]
+
+
+
+
+ wl_n, n, wl_k, k = np.loadtxt("C:/Users/z5228379/Downloads/ALn&k.csv",
+ delimiter=",", unpack=True,encoding='utf-8-sig')
+
+
+ n_wl = interp1d(wl_n, n)
+ k_wl = interp1d(wl_k, k)
+
+
+ self.Al= [
+ self.wl,
+ n_wl(self.wl),
+ k_wl(self.wl)
+ ]
+
+ self.Si = [
+ self.wl,
+ material("Si")().n(self.wl * 1e-9),
+ material("Si")().k(self.wl * 1e-9),
+ ]
+
+
+ spectr = LightSource(
+ source_type="standard",
+ version="AM1.5g",
+ x=self.wl,
+ output_units="photon_flux_per_m",
+ concentration=1,
+ ).spectrum(self.wl * 1e-9)[1]
+
+
+ self.spectrum = spectr / max(spectr)
+
+
+ def reflectance(self, x: Sequence[float]) -> float:
+ """ Create a list with the format [thickness, wavelengths, n_data, k_data] for
+ each layer.
+
+ This is one of the acceptable formats in which OptiStack can take information
+ (look at the Solcore documentation or at the OptiStack code for more info)
+ We set no_back_reflection to True because we DO NOT want to include reflection
+ at the back surface (assume GaAs is infinitely thick)
+
+ :param x: List with the thicknesses of the two layers in the ARC.
+ :return: Array with the reflection at each wavelength
+ """
+
+ Si = material("Si")()
+
+ arc = [[x[0]] + self.ZnO, [x[1]] + self.Al]
+ full_stack = OptiStack(arc, no_back_reflection=False, substrate=Si)
+ return calculate_rat(full_stack, self.wl, no_back_reflection=False)["R"]
+
+ def absorption(self, x: Sequence[float]) -> float:
+
+
+ Si = material("Si")()
+
+ arc = [[x[0]] + self.ZnO, [x[1]] + self.Al]
+ full_stack = OptiStack(arc, no_back_reflection=False, substrate=Si)
+ return calculate_rat(full_stack, self.wl, no_back_reflection=False)["A"]
+ def transmission(self, x: Sequence[float]) -> float:
+
+
+ Si = material("Si")()
+
+ arc = [[x[0]] + self.ZnO, [x[1]] + self.Al]
+
+
+ full_stack = OptiStack(arc, no_back_reflection=False, substrate=Si)
+ return calculate_rat(full_stack, self.wl, no_back_reflection=False)["T"]
+ def evaluate(self, x: Sequence[float]) -> float:
+ """ Returns the number the DA algorithm has to minimise.
+
+ In this case, this is the weighted reflectance
+
+ :param x: List with the thicknesses of the two layers in the ARC.
+ :return: weighted reflectance
+ """
+ return np.mean(self.reflectance(x) * self.spectrum)
+
+ def plot(self, x: Sequence[float]) -> None:
+ """ Plots the reflectance
+
+ :param x: List with the thicknesses of the two layers in the ARC.
+ :return: None
+ """
+ plt.figure()
+ plt.plot(self.wl, self.reflectance(x), label="Reflectance")
+ plt.plot(self.wl, self.absorption(x), label="Absorption")
+ plt.plot(self.wl, self.transmission(x), label="Transmission")
+
+ plt.xlabel("Wavelength (nm)")
+ plt.ylabel("R/A/T")
+ plt.legend()
+ plt.show()
+
+ def plot_weighted(self, x: Sequence[float]) -> None:
+ """ Plots the weighted reflectance.
+
+ :param x: List with the thicknesses of the two layers in the ARC.
+ :return: None
+ """
+ plt.figure()
+ plt.plot(self.wl, self.reflectance(x) * self.spectrum)
+ plt.xlabel("Wavelength (nm)")
+ plt.ylabel("R weighted by AM0")
+ plt.show()
+
+
+def main():
+
+
+ # number of iterations for Differential Evolution
+ maxiters = 70
+
+ # class the DE algorithm is going to use, as defined above
+ PDE_class = CalcRDiff()
+
+ # Pass the function which will be minimized to the PDE (parallel differential evolution)
+ # solver. PDE calculates the results for each population in parallel to speed up the
+ # overall process
+
+# =============================================================================
+# PDE_obj = DE(PDE_class.evaluate, bounds=[[0, 250], [0, 250], [0, 250], [0, 250]], maxiters=maxiters)
+# =============================================================================
+ PDE_obj = DE(PDE_class.evaluate, bounds=[[0, 100], [10,15]], maxiters=maxiters)
+ # PDE_obj = DE(PDE_class.evaluate, bounds=[[0, 500]], maxiters=maxiters)
+ # solve, i.e. minimize the problem
+ res = PDE_obj.solve()
+
+ """
+ PDE_obj.solve() returns 5 things:
+ - res[0] is a list of the parameters which gave the minimized value
+ - res[1] is that minimized value
+ - res[2] is the evolution of the best population (the best population from each
+ iteration
+ - res[3] is the evolution of the minimized value, i.e. the fitness over each iteration
+ - res[4] is the evolution of the mean fitness over the iterations
+ """
+ best_pop = res[0]
+ print("Parameters for best result:", best_pop, res[1])
+
+ PDE_class.plot(best_pop)
+ PDE_class.plot_weighted(best_pop)
+
+if __name__ == '__main__':
+ main()
+
diff --git a/other/other.qmd b/other/other.qmd
new file mode 100644
index 0000000..326d53b
--- /dev/null
+++ b/other/other.qmd
@@ -0,0 +1,3 @@
+---
+title: Other
+---
\ No newline at end of file