kpenvelope
A solver for the quantum states of holes confined in thin semiconductor layers (wurtzite crystals such as GaN and AlN). It computes the energy levels and wavefunctions from the standard six-band k.p model of Chuang and Chang, solved together with the electrostatics of the charge itself, so the confining potential is not guessed -- it emerges from the balance between the fixed interface charge and the hole gas. Built for polarization-induced two-dimensional hole gases, and usable for any layered wurtzite structure you can supply cited parameters for.
Install
pip install kpenvelope
For development: clone the repository and pip install -e .[test].
Quick start
import numpy as np
from kpenvelope import (gan_rinke2008, sheet_density_from_cm2,
solve_self_consistent)
p = gan_rinke2008() # cited GaN parameter set
z = np.linspace(0.0, 6.0, 97) # grid in nm, from the interface
ps = sheet_density_from_cm2(4.6e13) # measured density, lab units in
res = solve_self_consistent(p, z, ps, temperature_K=300.0)
# res.energies, res.masses, res.density, res.occupations, res.converged
Conventions, stated once: energies in eV on the valence-electron
scale (holes occupy the highest eigenvalues), lengths in nm, sheet
densities in nm^-2. sheet_density_from_cm2 / sheet_density_to_cm2
convert to and from the cm^-2 numbers a lab quotes, exactly.
What it can do
Energy levels and wavefunctions. solve_subbands solves the
six-band problem on a 1D grid at any in-plane momentum, with hard
walls at the grid ends. solve_heterostructure lifts that
restriction: layered stacks with position-dependent material
parameters and band offsets (built with layered_profile), using the
symmetrized discretization that keeps the problem exactly Hermitian
for any profile. Strain enters through the Bir-Pikus terms
(strain_blocks), per layer, when you supply cited deformation
potentials.
Self-consistency with the charge. solve_self_consistent
(hard-wall) and solve_self_consistent_hetero (finite barriers)
iterate the quantum problem with Gauss's law until the potential and
the charge agree, at your measured sheet density and temperature
(temperature_K; the default 0 reproduces the historical cold
filling exactly). The result carries a converged flag, so an
iteration-starved run reports its failure instead of hiding it.
Filling uses either parabolic subbands (closed-form, including the
finite-temperature closed form) or the full computed dispersion on a
k-grid (fill_subbands_kgrid), which refuses a momentum window the
occupied states outgrow.
What the states are made of. band_character resolves each
state into heavy-hole, light-hole and split-off fractions, and
character_vs_k tracks how the composition changes with momentum --
the physics behind "one subband, many masses": the top subband is
pure heavy-hole at zero momentum, and its mass moves as other
components mix in.
Numbers an experiment measures. subband_dispersion and
local_mass give the energy-versus-momentum curves and the local
effective mass (a hole mass is not one number, and a flat band
honestly reports an infinite mass). spin_splitting /
splitting_vs_k quantify the splitting an asymmetric potential
induces. group_velocity and dos_from_dispersion are the
ingredients every transport estimate needs. dipole_matrix and
oscillator_strengths give the intersubband optical matrix elements
and dimensionless strengths that set absorption spectra and detector
design -- and, new in v0.9, depolarization_shift applies the
collective correction between the two: a doped well's measured
resonance sits above the subband spacing by the depolarization shift
(Allen/Tsui/Vinter 1976; Ando/Fowler/Stern 1982), the effect every
absorption experiment must subtract before comparing with the band
structure, with isb_lineshape giving the strength-weighted
spectrum at your measured linewidth.
Calibrate the numbers it refuses to ship
The band offset of YOUR heterostructure and the sheet density in
YOUR well are exactly the numbers no package should invent -- and
both are routinely inferred from the same measured intersubband
resonance. The lab tools close that loop through the package's own
solver, pure NumPy:
from kpenvelope import (fit_band_offset, offset_sensitivity,
sheet_density_from_shift)
# The offset that makes the calculated spacing match your resonance
# (you supply the profile shape; the scale is calibrated):
fit = fit_band_offset(z, params_list, band_edge_unit, e_meas_ev=0.0042,
bracket=(0.4, 1.5), sigma_e_ev=1e-4)
print(fit["offset_ev"], "+/-", fit["sigma_offset_ev"])
# The sheet density from the measured depolarization-shifted
# resonance -- an exact closed-form inversion, not a fit:
inv = sheet_density_from_shift(z, env_i, env_j, e_i, e_j,
e_meas_ev=0.21, eps_r=10.4)
Both refuse rather than guess: a bracket that does not straddle the
measured spacing is refused with the calculated values at both ends;
a transition that barely feels the offset is refused (a deep-well
measurement cannot calibrate a barrier height it never probes --
offset_sensitivity answers that before the measurement); and a
resonance at or below the bare spacing is refused, because the
depolarization shift only pushes the resonance up. One convention
worth knowing: at k = 0 the solver's states come in exactly
degenerate Kramers pairs, so the first intersubband spacing is
between states 0 and 2 -- the tools' default.
The k-linear Rashba term, with your coefficient
Wurtzite crystals lack inversion symmetry, so carriers feel a
k-linear spin-orbit term of the universal form
alpha (sigma_x k_y - sigma_y k_x) -- one of this package's stated
limits until now. rashba_hamiltonian, rashba_splitting and
rashba_spins ship that term for the conduction-band companion
problem with its exact structure asserted as identities (splitting
exactly 2 alpha k; spins exactly in-plane, perpendicular to k, and
opposite between branches; exact Kramers degeneracy at k = 0). No
coefficient ships as a default, on purpose: you pass your sample's
alpha with a citation (for orientation, the measured bulk n-GaN
value is 4.5 +/- 1 meV A, Stefanowicz et al., PRB 89, 205201 (2014),
and GaN/AlGaN two-dimensional electron gases show about 5.5-6 meV A,
PRB 74, 033302 and 74, 113308 (2006)). The valence-band k-linear
terms have a different, parameter-set-specific structure whose
vetted coefficients our sources do not provide; they remain
deliberately not shipped.
Cited parameter sets
No physical number in this package is made up, and none is accepted
without a source -- the reference field of WurtziteParameters is
mandatory.
gan_rinke2008(): the GW-based GaN valence set of Rinke et al., Phys. Rev. B 77, 075202 (2008) (A1..A6, Delta_CR = 10 meV, Delta_SO = 17 meV), as tabulated in Extended Data Table 1 of Chang et al., Nature Electronics 9, 346 (2026). eps_r = 10.4 (field along the c axis) from Barker and Ilegems, Phys. Rev. B 7, 743 (1973).aln_rinke2008(): the matching AlN set (Delta_CR = -295 meV from Rinke et al.; Delta_SO = 22 meV from de Carvalho et al., Appl. Phys. Lett. 97, 232101 (2010)), intended as a barrier material. Its permittivity is deliberately NaN and the self-consistent solver refuses to run on it: no vetted value is shipped, and none is needed for a barrier.demo_single_band(): a synthetic, decoupled set the test suite uses because it has exact textbook solutions. Labeled non-physical.
For any other material, populate WurtziteParameters from the
literature (e.g. Vurgaftman and Meyer, J. Appl. Phys. 94, 3675
(2003)) and record the source. No default band offset is shipped
either: alignments are material- and strain-specific, so
layered_profile takes them from you, with a citation.
How it is checked
Every physics claim in the test suite (67 tests, Python 3.9-3.14, run in CI on every push) is anchored to a closed form, an exact identity, or two independent code paths agreeing -- never to a stored number:
- the assembled matrix is exactly Hermitian with every coupling on, and the layered assembly reduces to the uniform one at machine precision when every layer is the same material;
- the decoupled well reproduces the textbook square-well levels (hard wall and finite barrier, including the analytic decay into the barrier);
- the cited GaN set lands on its closed-form splittings (5.20 and 21.80 meV) and asymptotic masses (1.89 and 0.18 m0);
- the finite-temperature filling matches direct numerical integration of the Fermi-Dirac occupation (two independent code paths) and reduces to the cold filling as T goes to 0;
- filling conserves charge exactly, the k-grid filler agrees with the closed-form filler on an exactly parabolic model, and the strain terms are validated by a structural identity with the kinetic template;
- the intersubband dipoles reproduce the textbook closed forms, obey the f-sum rule, respect parity selection, and are invariant under a shift of the coordinate origin;
- the depolarization geometry integral matches an independent quadrature over the analytic well wavefunctions, is exactly unchanged by a shift of the origin, and scales exactly with the well width; the shift formula's defining identity holds to machine precision.
One comparison against the source paper is on record and stated honestly: a hard-wall run at the measured density puts the gas centroid at 0.62 nm against the paper's 0.568 nm, with the same subband structure; the difference is the filling model (parabolic here, computed-dispersion there). Do not publish self-consistent numbers without checking the barrier and filling model against your own system.
Honest limits
Deliberate scope -- designed out with reasons, not overlooked: scattering lifetimes (roughness, impurities, phonons) need cited screening and roughness parameters per structure, and this package ships no number it cannot source, so it provides the DOS and velocity factors every lifetime integral needs instead; the Poisson solve uses one cited permittivity (not a spatially varying profile); and while the conduction-band k-linear Rashba term now ships (with your cited coefficient), the valence-band k-linear terms beyond the six-band Chuang-Chang model remain not included -- no vetted coefficients in our sources.
Associated paper
T. M. Mahim, A. S. M. Mohsin and M. M. Rahman, "Origin of the conflicting hole masses in the GaN/AlN two-dimensional hole gas" (under review); code for the paper: https://github.com/Tanvir-Mahmud-Mahim/gan-2dhg-masses-lifetimes
and S. L. Chuang and C. S. Chang, Phys. Rev. B 54, 2491 (1996). This package is the general-purpose tool; the paper repository reproduces the specific published study.
Support and governance
Written and maintained by Tanvir Mahmud Mahim (Department of Electrical and Electronic Engineering, BRAC University), who reviews every change and takes the final decision on scope and releases. Design questions are discussed in the open in issues and pull requests, and the standing rule of CONTRIBUTING.md binds the maintainer exactly as it binds contributors: a change that touches physics arrives with a test, and a constant arrives with its source.
Support runs through the issue tracker. Usage questions are welcome alongside bug reports; a docstring that left a unit or a sign convention unclear is treated as a documentation bug, not user error. While the version is below 1.0 the API may still move between minor versions; such changes are called out in the release notes.
License
Apache-2.0. Every release is archived on Zenodo under the concept DOI 10.5281/zenodo.22015269, which always resolves to the latest version.
Metadata
Release files for kpenvelope 0.11.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| kpenvelope-0.11.0.tar.gz | 54.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| kpenvelope-0.11.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 97.0 kB
Release files / kpenvelope-0.11.0.tar.gz
| Download URL | kpenvelope-0.11.0.tar.gz |
|---|---|
| Size | 54.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
10f4ecf041ad3821f8418ac1d6447bfce0d26265128fffc782e98f07272f82da
|
|
BLAKE2b-256 checksum How to use checksums |
00368a38c1eba640bd55d80356faaf688c97da4f281283f2e6cbded2d124d1ca
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.
Transparency logRelease files / kpenvelope-0.11.0-py3-none-any.whl
| Download URL | kpenvelope-0.11.0-py3-none-any.whl |
|---|---|
| Size | 42.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6b9ab28e7d1dbd9737c139b83a5ea345ad96738f0b49185dc108548786494689
|
|
BLAKE2b-256 checksum How to use checksums |
da87f211111356bb60e05561db20b4eedc470821c19eabc469ad672ae217eb92
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.
Transparency log