Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Python-based PDD solver option and fix sign convention inconsistencies #265

Merged
merged 27 commits into from
Nov 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
19f73a2
sesame testing, add folder and files for sesame
phoebe-p Oct 14, 2023
247f978
processing materials for sesame
phoebe-p Oct 14, 2023
48d763f
outline of sesame IV and QE methods
phoebe-p Oct 15, 2023
369ed49
iv_sesame seems to be working (outputs not yet in right format)
phoebe-p Oct 15, 2023
709b087
add sesame to requirements, add example. QE and IV working
phoebe-p Oct 18, 2023
223695d
QE and IV calculation working for Si
phoebe-p Oct 24, 2023
f2eac22
fix bug with NaN in IV results, fix bug in GaAsN bowing parameter data
phoebe-p Oct 27, 2023
cbdebb8
adding tests and examples
phoebe-p Nov 3, 2023
70a709a
adding build for Python 3.12, updating automated test Python versions
phoebe-p Nov 3, 2023
9efdf84
newer version of cibuildwheel
phoebe-p Nov 3, 2023
7ed0502
try to fix BLAS issue
phoebe-p Nov 3, 2023
1372de7
try to fix BLAS issue
phoebe-p Nov 3, 2023
61e4d5d
try to fix BLAS issue
phoebe-p Nov 3, 2023
5b4e684
identified issue with signs
phoebe-p Nov 8, 2023
3cbc296
DA/PDD voltage direction issue seems to be fixed
phoebe-p Nov 9, 2023
78dfc00
DA and PDD have same sign conventions
phoebe-p Nov 9, 2023
6a33c6d
updated test for new DA sign convention
phoebe-p Nov 9, 2023
0e83d69
calculate lifetime or diffusion length from mobility and the other qu…
phoebe-p Nov 9, 2023
bd66158
modify examples. Add error for decreasing voltages and internal_voltages
phoebe-p Nov 9, 2023
b8e5120
add warning for wrong voltage sign to DA solver
phoebe-p Nov 9, 2023
e875d73
update ARC RAT example
phoebe-p Nov 9, 2023
884febb
add Si cell example. Check material files for recombination propertie…
phoebe-p Nov 9, 2023
a7fdc05
update tests and examples. Make parallel implementation of eqe possible
phoebe-p Nov 9, 2023
2900bce
fix failing tests
phoebe-p Nov 10, 2023
a7a4c7a
fix failing tests
phoebe-p Nov 10, 2023
e6f8683
Merge branch 'update_build_312' into pdd_sesame
phoebe-p Nov 10, 2023
692c986
Further Sesame changes
phoebe-p Nov 10, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions .github/workflows/build_deploy_wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches:
- main
- develop

tags:
- "**"

Expand All @@ -28,7 +29,8 @@ jobs:
- [ macos-11, macosx, arm64] # cross compiled
- [ windows-2019, win, AMD64 ]

python: [[ "cp37", "3.7" ], [ "cp38", "3.8" ], [ "cp39", "3.9" ], [ "cp310", "3.10" ], [ "cp311", "3.11" ]]
python: [[ "cp37", "3.7" ], [ "cp38", "3.8" ], [ "cp39", "3.9" ],
[ "cp310", "3.10" ], [ "cp311", "3.11" ], ["cp312", "3.12"]]

name: Build wheel for ${{ matrix.python[0] }}-${{ matrix.buildplat[1] }} ${{ matrix.buildplat[2] }}

Expand All @@ -48,17 +50,18 @@ jobs:
echo "c:\rtools40\ucrt64\bin;" >> $env:GITHUB_PATH

- name: Install cibuildwheel
run: python -m pip install cibuildwheel==2.11.4
run: python -m pip install cibuildwheel==2.16.2

- name: Build Solcore
if: >-
( ! contains(matrix.buildplat[2], 'arm64' ) )
uses: pypa/cibuildwheel@v2.11.4
uses: pypa/cibuildwheel@v2.16.2
env:
CIBW_BUILD: ${{ matrix.python[0] }}-${{ matrix.buildplat[1] }}*
CIBW_ARCHS: ${{ matrix.buildplat[2] }}
CIBW_ENVIRONMENT_PASS_LINUX: RUNNER_OS
CIBW_BEFORE_BUILD_MACOS: brew reinstall gfortran
CIBW_BEFORE_BUILD_LINUX: python -m pip install numpy --config-settings=setup-args="-Dallow-noblas=true"

- name: Set extra env for arm64
if: >-
Expand All @@ -69,7 +72,7 @@ jobs:

- name: Cross-build Solcore for arm64
if: ${{ (matrix.python[0] != 'cp37') && ( contains(matrix.buildplat[2], 'arm64') )}} # image not present for python3.7
uses: pypa/cibuildwheel@v2.11.4
uses: pypa/cibuildwheel@v2.16.2
env:
CIBW_BUILD: ${{ matrix.python[0] }}-${{ matrix.buildplat[1] }}*
CIBW_ARCHS: ${{ matrix.buildplat[2] }}
Expand Down
29 changes: 15 additions & 14 deletions .github/workflows/test_unit_and_examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: [ "3.7", "3.8", "3.9", "3.10" ]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]

steps:
- name: Checkout
Expand Down Expand Up @@ -53,13 +53,14 @@ jobs:

- name: Install Python dependecies
run: |
pip install pytest meson-python ninja cython numpy git+https://github.com/scientific-python/devpy@v0.1
python3 -m devpy install-dependencies -test-dep
pip install numpy --config-settings=setup-args="-Dallow-noblas=true"
pip install pytest meson-python ninja cython spin
python3 -m spin install-dependencies -test-dep

- name: Install S4
if: matrix.os != 'windows-latest'
run: |
pip install wheel
pip install wheel setuptools
git clone https://github.com/phoebe-p/S4
cd S4
make S4_pyext
Expand All @@ -68,14 +69,14 @@ jobs:

- name: Build solcore
run: |
python -m devpy build -- -Dwith_pdd=true -Dinstall_test=true
python -m spin build -- -Dwith_pdd=true -Dinstall_test=true

- name: Unit and functional tests (MacOS and Linux)
if: matrix.os != 'windows-latest'
env:
SOLCORE_SPICE: ngspice
run: |
python -m devpy test -- -r a -v --cov=solcore/ --ignore=solcore/tests/test_examples.py -n "auto"
python -m spin test -- -r a -v --cov=solcore/ --ignore=solcore/tests/test_examples.py -n "auto"

- name: Unit and functional tests (Windows)
if: matrix.os == 'windows-latest'
Expand All @@ -88,7 +89,7 @@ jobs:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
run: |
python -m pip install codecov
python -m devpy codecov
python -m spin codecov


test_examples:
Expand All @@ -98,7 +99,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: [ "3.7", "3.8", "3.9", "3.10" ]
python-version: ["3.9", "3.10", "3.11", "3.12"]

steps:
- name: Checkout
Expand Down Expand Up @@ -133,13 +134,13 @@ jobs:

- name: Install Python dependecies
run: |
pip install pytest meson-python ninja cython numpy git+https://github.com/scientific-python/devpy@v0.1
python3 -m devpy install-dependencies -test-dep
pip install pytest meson-python ninja cython numpy spin
python3 -m spin install-dependencies -test-dep

- name: Install S4
if: matrix.os != 'windows-latest'
run: |
pip install wheel
pip install wheel setuptools
git clone https://github.com/phoebe-p/S4
cd S4
make S4_pyext
Expand All @@ -148,16 +149,16 @@ jobs:

- name: Build solcore
run: |
python -m devpy build -- -Dwith_pdd=true -Dinstall_test=true
python -m spin build -- -Dwith_pdd=true -Dinstall_test=true

- name: Unit and functional tests (MacOS and Linux)
if: matrix.os != 'windows-latest'
env:
SOLCORE_SPICE: ngspice
run: |
python -m devpy test -- -r a -v solcore/tests/test_examples.py -n "auto"
python -m spin test -- -r a -v solcore/tests/test_examples.py -n "auto"

# - name: Unit and functional tests (Windows)
# if: matrix.os == 'windows-latest'
# run: |
# python -m devpy test -- -r a -v solcore/tests/test_examples.py
# python -m spin test -- -r a -v solcore/tests/test_examples.py
4 changes: 2 additions & 2 deletions .devpy/cmds.py → .spin/cmds.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import click
from devpy import util
from devpy.cmds.meson import _get_site_packages
from spin import util
from spin.cmds.meson import _get_site_packages


@click.command()
Expand Down
Binary file modified docs/source/Examples/DA_iv.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/source/Examples/DA_qe.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/source/Examples/RAT_of_ARC.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
194 changes: 111 additions & 83 deletions docs/source/Examples/example_3J_with_DA_solver.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,128 +6,156 @@ Example of a 3J solar cell calculated with the DA solver
.. image:: DA_qe.png
:width: 40%

- Required extra files, available in `Solcore's Github repository (Examples folder) <https://github.com/dalonsoa/solcore5>`_:

- MgF-ZnS_AR.csv
- in01gaas.csv
- Ge-Palik.csv

.. code-block:: Python

import numpy as np
import matplotlib.pyplot as plt

from solcore import siUnits, material, si
from solcore.interpolate import interp1d
from solcore.solar_cell import SolarCell
from solcore.structure import Junction, Layer
from solcore.solar_cell_solver import solar_cell_solver
from solcore.light_source import LightSource

all_materials = []

# Define materials for the anti-reflection coating:
MgF2 = material("MgF2")()
ZnS = material("ZnScub")()

def this_dir_file(f):
from pathlib import Path
return str(Path(__file__).parent / "data" / f)
ARC_layers = [Layer(si("100nm"), material=MgF2),
Layer(si("50nm"), material=ZnS)]

# TOP CELL - InGaP
# Now 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 using the
# depletion approximation: the minority carriers diffusion lengths and the doping.

# We need to build the solar cell layer by layer.
# We start from the AR coating. In this case, we load it from an an external file
refl_nm = np.loadtxt(this_dir_file("MgF-ZnS_AR.csv"), unpack=True, delimiter=",")
ref = interp1d(x=siUnits(refl_nm[0], "nm"), y=refl_nm[1], bounds_error=False, fill_value=0)

# TOP CELL - GaInP
# Now we build the top cell, which requires the n and p sides of GaInP and a window layer.
# We also load the absorption coefficient from an external file. We also add some extra parameters needed for the
# calculation such as the minority carriers diffusion lengths
AlInP = material("AlInP")
InGaP = material("GaInP")
window_material = AlInP(Al=0.52)
top_cell_n_material = InGaP(In=0.49, Nd=siUnits(2e18, "cm-3"), hole_diffusion_length=si("200nm"))
top_cell_p_material = InGaP(In=0.49, Na=siUnits(1e17, "cm-3"), electron_diffusion_length=si("1um"))

all_materials.append(window_material)
all_materials.append(top_cell_n_material)
all_materials.append(top_cell_p_material)

# MID CELL - InGaAs
# We add manually the absorption coefficient of InGaAs since the one contained in the database doesn't cover
# enough range, keeping in mind that the data has to be provided as a function that takes wavelengths (m) as input and
# returns absorption (1/m)
InGaAs = material("InGaAs")
InGaAs_alpha = np.loadtxt(this_dir_file("in01gaas.csv"), unpack=True, delimiter=",")
InGaAs.alpha = interp1d(x=1240e-9 / InGaAs_alpha[0][::-1], y=InGaAs_alpha[1][::-1], bounds_error=False, fill_value=0)
top_cell_n_material = InGaP(In=0.49, Nd=siUnits(2e18, "cm-3"),
hole_diffusion_length=si("200nm"))
top_cell_p_material = InGaP(In=0.49, Na=siUnits(1e17, "cm-3"),
electron_diffusion_length=si("2um"))
# MID CELL - GaAs
GaAs = material("GaAs")

mid_cell_n_material = InGaAs(In=0.01, Nd=siUnits(3e18, "cm-3"), hole_diffusion_length=si("500nm"))
mid_cell_p_material = InGaAs(In=0.01, Na=siUnits(1e17, "cm-3"), electron_diffusion_length=si("5um"))

all_materials.append(mid_cell_n_material)
all_materials.append(mid_cell_p_material)
mid_cell_n_material = GaAs(In=0.01, Nd=siUnits(3e18, "cm-3"),
hole_diffusion_length=si("500nm"))
mid_cell_p_material = GaAs(In=0.01, Na=siUnits(1e17, "cm-3"),
electron_diffusion_length=si("5um"))

# BOTTOM CELL - Ge
# We add manually the absorption coefficient of Ge since the one contained in the database doesn't cover
# enough range.
Ge = material("Ge")
Ge_alpha = np.loadtxt(this_dir_file("Ge-Palik.csv"), unpack=True, delimiter=",")
Ge.alpha = interp1d(x=1240e-9 / Ge_alpha[0][::-1], y=Ge_alpha[1][::-1], bounds_error=False, fill_value=0)

bot_cell_n_material = Ge(Nd=siUnits(2e18, "cm-3"), hole_diffusion_length=si("800nm"))
bot_cell_p_material = Ge(Na=siUnits(1e17, "cm-3"), electron_diffusion_length=si("50um"))

all_materials.append(bot_cell_n_material)
all_materials.append(bot_cell_p_material)

# We add some other properties to the materials, assumed the same in all cases, for simplicity.
# If different, we should have added them above in the definition of the materials.
for mat in all_materials:
mat.hole_mobility = 5e-2
mat.electron_mobility = 3.4e-3
mat.hole_mobility = 3.4e-3
mat.electron_mobility = 5e-2
mat.relative_permittivity = 9

# And, finally, we put everything together, adding also the surface recombination velocities. We also add some shading
# due to the metallisation of the cell = 8%, and indicate it has an area of 0.7x0.7 mm2 (converted to m2)

bot_cell_n_material = Ge(Nd=siUnits(2e18, "cm-3"),
hole_diffusion_length=si("800nm"))
bot_cell_p_material = Ge(Na=siUnits(1e17, "cm-3"),
electron_diffusion_length=si("50um"))

# Now that the layers are configured, we can now assemble the triple junction solar
# cell. Note that we also specify a metal shading of 2% and a cell area of $1cm^{2}$.
# SolCore calculates the EQE for all three junctions and light-IV showing the relative
# contribution of each sub-cell. We set "kind = 'DA'" to use the depletion
# approximation. We can also set the surface recombination velocities, where sn
# refers to the surface recombination velocity at the n-type surface, and sp refers
# to the SRV on the p-type side.

solar_cell = SolarCell(
[
ARC_layers +
[
Junction([Layer(si("25nm"), material=window_material, role='window'),
Layer(si("100nm"), material=top_cell_n_material, role='emitter'),
Layer(si("600nm"), material=top_cell_p_material, role='base'),
], sn=1, sp=1, kind='DA'),
Layer(si("400nm"), material=top_cell_p_material, role='base'),
], sn=si("1e5cm s-1"), sp=si("1e5cm s-1"), kind='DA'),
Junction([Layer(si("200nm"), material=mid_cell_n_material, role='emitter'),
Layer(si("3000nm"), material=mid_cell_p_material, role='base'),
], sn=1, sp=1, kind='DA'),
], sn=si("1e5cm s-1"), sp=si("1e5cm s-1"), kind='DA'),
Junction([Layer(si("400nm"), material=bot_cell_n_material, role='emitter'),
Layer(si("100um"), material=bot_cell_p_material, role='base'),
], sn=1, sp=1, kind='DA'),
], reflectivity=ref, shading=0.08, cell_area=0.7 * 0.7 / 1e4)

wl = np.linspace(300, 1800, 700) * 1e-9
solar_cell_solver(solar_cell, 'qe', user_options={'wavelength': wl})
], sn=si("1e5cm s-1"), sp=si("1e5cm s-1"), kind='DA')
],
shading=0.02, cell_area=1 * 1 / 1e4)

# Choose wavelength range (in m):
wl = np.linspace(280, 1850, 700) * 1e-9

# Calculate the EQE for the solar cell:
solar_cell_solver(solar_cell, 'qe', user_options={'wavelength': wl,
'da_mode': 'green',
'optics_method': 'TMM'
})
# we pass options to use the TMM optical method to calculate realistic R, A and T
# values with interference in the ARC (and semiconductor) layers. We can also choose
# which solver mode to use for the depletion approximation. The default is 'green',
# which uses the (faster) Green's function method. The other method is 'bvp'.

# Plot the EQE and absorption of the individual junctions. Note that we can access
# the properties of the first junction (ignoring any other layers, such as the ARC,
# which are not part of a junction) using solar_cell(0), and the second junction using
# solar_cell(1), etc.

plt.figure(1)
plt.plot(wl * 1e9, solar_cell[0].eqe(wl) * 100, 'b', label='GaInP')
plt.plot(wl * 1e9, solar_cell[1].eqe(wl) * 100, 'g', label='InGaAs')
plt.plot(wl * 1e9, solar_cell[2].eqe(wl) * 100, 'r', label='Ge')

plt.plot(wl * 1e9, solar_cell(0).eqe(wl) * 100, 'b', label='GaInP QE')
plt.plot(wl * 1e9, solar_cell(1).eqe(wl) * 100, 'g', label='GaAs QE')
plt.plot(wl * 1e9, solar_cell(2).eqe(wl) * 100, 'r', label='Ge QE')
plt.fill_between(wl * 1e9, solar_cell(0).layer_absorption * 100, 0, alpha=0.3,
label='GaInP Abs.', color='b')
plt.fill_between(wl * 1e9, solar_cell(1).layer_absorption * 100, 0, alpha=0.3,
label='GaAs Abs.', color='g')
plt.fill_between(wl * 1e9, solar_cell(2).layer_absorption * 100, 0, alpha=0.3,
label='Ge Abs.', color='r')

plt.plot(wl*1e9, 100*(1-solar_cell.reflected), '--k', label="100 - Reflectivity")
plt.legend()
plt.ylim(0, 100)
plt.ylabel('EQE (%)')
plt.xlabel('Wavelength (nm)')
plt.show()

# Set up the AM0 (space) solar spectrum for the light I-V calculation:
am0 = LightSource(source_type='standard',version='AM0',x=wl,
output_units='photon_flux_per_m')


# Set up the voltage range for the overall cell (at which the total I-V will be
# calculated) as well as the internal voltages which are used to calculate the results
# for the individual junctions. The range of the internal_voltages should generally
# be wider than that for the voltages.

V = np.linspace(0, 3, 300)
solar_cell_solver(solar_cell, 'iv', user_options={'voltages': V, 'light_iv': True, 'wavelength': wl})
# this is an n-p cell, so we need to scan negative voltages

V = np.linspace(-3, 0, 300)
internal_voltages = np.linspace(-4, 2, 400)

# Calculate the current-voltage relationship under illumination:

solar_cell_solver(solar_cell, 'iv', user_options={'light_source': am0,
'voltages': V,
'internal_voltages': internal_voltages,
'light_iv': True,
'wavelength': wl,
'optics_method': 'TMM',
'mpp': True,
})

# We pass the same options as for solving the EQE, but also set 'light_iv' and 'mpp' to
# True to indicate we want the IV curve under illumination and to find the maximum
# power point (MPP). We also pass the AM0 light source and voltages created above.

plt.figure(2)
plt.plot(V, solar_cell.iv['IV'][1], 'k', linewidth=3, label='Total')
plt.plot(V, -solar_cell[0].iv(V), 'b', label='GaInP')
plt.plot(V, -solar_cell[1].iv(V), 'g', label='InGaAs')
plt.plot(V, -solar_cell[2].iv(V), 'r', label='Ge')
plt.plot(abs(V), -solar_cell.iv['IV'][1]/10, 'k', linewidth=3, label='3J cell')
plt.plot(abs(V), solar_cell(0).iv(V)/10, 'b', label='InGaP sub-cell')
plt.plot(abs(V), solar_cell(1).iv(V)/10, 'g', label='GaAs sub-cell')
plt.plot(abs(V), solar_cell(2).iv(V)/10, 'r', label='Ge sub-cell')
plt.text(0.5,30,f'Jsc= {abs(solar_cell.iv.Isc/10):.2f} mA.cm' + r'$^{-2}$')
plt.text(0.5,28,f'Voc= {abs(solar_cell.iv.Voc):.2f} V')
plt.text(0.5,26,f'FF= {solar_cell.iv.FF*100:.2f} %')
plt.text(0.5,24,f'Eta= {solar_cell.iv.Eta*100:.2f} %')

plt.legend()
plt.ylim(0, 200)
plt.ylim(0, 33)
plt.xlim(0, 3)
plt.ylabel('Current (A/m$^2$)')
plt.ylabel('Current (mA/cm$^2$)')
plt.xlabel('Voltage (V)')

plt.show()
Loading
Loading