Usage
The everyday entry point is the command-line interface —
kapaow optimize rc for the bisection rc search, kapaow optimize
spread for the pareto front, kapaow plot periodic-table for the
sweep plots, and kapaow convert / kapaow confine for one-off
file manipulations.
The same building blocks are also available as a Python API.
Basis manipulation
How to extend a pseudoatomic basis set with additional orbitals.
- class AtomicBasis(*, subshells: list[Subshell])[source]
An atomic basis set.
Need to keep track of the (n, l) values of each subshell.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- property l_max: AngularMomentum
The maximum angular momentum quantum number in the basis set.
- to_pseudoatomic_basis() PseudoatomicBasis[source]
Convert to a PseudoatomicBasis.
- classmethod from_upf(upf_path: Path) AtomicBasis[source]
Construct an AtomicBasis from a UPF pseudopotential file.
If the UPF ships pseudoatomic wavefunctions (
<PP_CHI>blocks), the basis is read directly. Otherwise (e.g. SG15 ONCV, wherePP_HEADERadvertisesnumber_of_wfc=0), the baseline is reconstructed from the neutral-atom Madelung filling, keeping only the outermost subshells whose electron count sums toz_valence.
- extend(subshells: list[Subshell]) AtomicBasis[source]
Return a new AtomicBasis with an added subshell.
- model_config = {'arbitrary_types_allowed': True, 'extra': 'forbid', 'validate_assignment': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class PseudoatomicBasis(*, number_of_orbitals: dict[AngularMomentum, int])[source]
A pseudoatomic basis set.
Only need to keep track of the number of orbitals per angular momentum channel.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- classmethod ensure_all_channels_present(v: dict[AngularMomentum, int]) dict[AngularMomentum, int][source]
Ensure all angular momentum channels are present in the dictionary.
- property l_max: AngularMomentum
The maximum angular momentum quantum number in the basis set.
i.e. return the largest l for which number_of_orbitals[l] > 0
- property n_max: int
The maximum principal quantum number in the basis set.
Note that this is for the pseudo-wavefunction, so n_max is 1 if the basis has only 2s and 2p orbitals.
- extend(s: int = 0, p: int = 0, d: int = 0, f: int = 0, g: int = 0) PseudoatomicBasis[source]
Return a new PseudoatomicBasis with added orbitals.
- property total_number_of_orbitals: int
Total number of orbitals per atom, accounting for m-degeneracy.
- property l_values: list[int]
List of l values in the basis set, repeated according to the number of orbitals.
- model_config = {'arbitrary_types_allowed': True, 'extra': 'forbid', 'validate_assignment': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class Subshell(*, n: int, l: AngularMomentum)[source]
A subshell in a pseudoatomic basis set.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {'arbitrary_types_allowed': True, 'extra': 'forbid', 'validate_assignment': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
How to extend a pseudoatomic basis set with additional orbitals.
- class BasisExtension[source]
An extension to a basis set.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- abstractmethod extend(basis: AtomicBasis | PseudoatomicBasis) PseudoatomicBasis[source]
Extend the given basis set and return the new basis set.
- model_config = {'arbitrary_types_allowed': True, 'extra': 'forbid', 'validate_assignment': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class BasisExtensionType(*values)[source]
Type of basis extension.
- property angular_momentum: AngularMomentum | None
Return the angular momentum if this is a channel-specific extension.
- class BasisExtensionViaAddition(*, increment: int = 1)[source]
Add the next subshell to an atomic basis set.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- extend_atomic(basis: AtomicBasis) AtomicBasis[source]
Extend the provided basis by adding the next subshell(s), returning an AtomicBasis.
First checks for gaps in Madelung order between basis subshells (e.g. 5s missing between 4p and 4d for Pd), filtering out core subshells (n < min n of basis). If no valid gaps, adds the next subshell after the outermost.
- extend(basis: AtomicBasis | PseudoatomicBasis) PseudoatomicBasis[source]
Extend the provided basis by adding the next subshell(s).
- model_config = {'arbitrary_types_allowed': True, 'extra': 'forbid', 'validate_assignment': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class BasisExtensionViaChannel(*, channel: AngularMomentum, increment: int = 1)[source]
Add orbitals in a specific angular momentum channel.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- extend(basis: AtomicBasis | PseudoatomicBasis) PseudoatomicBasis[source]
Extend the provided basis by adding orbitals in the specified channel.
- model_config = {'arbitrary_types_allowed': True, 'extra': 'forbid', 'validate_assignment': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class BasisExtensionViaPolarization(*, increment: int = 1)[source]
Add polarization orbitals to a pseudoatomic basis set.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- extend(basis: AtomicBasis | PseudoatomicBasis) PseudoatomicBasis[source]
Extend the provided basis by adding polarization orbitals.
- model_config = {'arbitrary_types_allowed': True, 'extra': 'forbid', 'validate_assignment': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- parse_extension(add: tuple[str, ...]) BasisExtension | None[source]
Build the basis extension implied by a tuple of
--addflags.The CLI exposes a repeatable
--addoption whose values are the string forms ofBasisExtensionType. Each repetition counts as one increment; mixing different kinds in a single call is not supported.Parameters
- add
Tuple of flag strings (e.g.
("subshell", "subshell")or("p",)). An empty tuple returnsNone.
Returns
- BasisExtension | None
The corresponding extension, or
Noneif add is empty.
Raises
- ValueError
If multiple distinct kinds are mixed.
Pseudoatomic solver
Solve the pseudoatomic problem.
- class OrbitalEnergy(*, l: AngularMomentum, n_radial: int, energy: float)[source]
Energy of a single radial orbital from a femdvr solve.
Parameters
- l
Angular momentum channel.
- n_radial
Zero-based index within the l channel (0 = lowest energy in that channel).
- energy
Orbital energy in Hartree.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {'arbitrary_types_allowed': True, 'extra': 'forbid', 'validate_assignment': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class PseudoAtomicInput(*, control: ControlInput = <factory>, sysparams: SysParamsInput = <factory>, solver: SolverInput = <factory>, dft: DFTInput = <factory>, confinement: ConfinementInput = <factory>, output: OutputInput = <factory>)[source]
Top-level input for
solve_pseudo_atomic().Bundles all sub-models needed to configure a pseudo-atomic run.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {'extra': 'forbid', 'validate_assignment': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class PseudoAtomicResult(*, eigenvalues: dict[str, dict[str, list[float]]], energy_shifts: dict[str, list[float]] | None = None)[source]
Output of
solve_pseudo_atomic().Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {'extra': 'forbid', 'validate_assignment': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- compute_spread(dat_file: Path, atomic_basis: AtomicBasis) float[source]
Compute the spatial spread of the outermost radial wavefunction.
Uses \(\Omega = \int dr\, r^4 |R_{nl}(r)|^2\).
Parameters
- dat_file
Path to the Wannier90 .dat file containing the radial wavefunctions.
- atomic_basis
Atomic basis, used to identify the outermost subshell via
get_outermost_wavefunction().
Returns
- float
The spread of the outermost orbital.
- get_outermost_wavefunction(dat_file: Path, atomic_basis: AtomicBasis) tuple[ndarray, ndarray][source]
Extract the outermost radial wavefunction from a Wannier90 .dat file.
The “outermost” orbital is the highest-energy occupied subshell of atomic_basis under Madelung ordering. The solver may produce more orbitals per l channel than the basis declares (
nmax+1per l), so we use the basis orbital count to pick the correct radial function for that channel.Parameters
- dat_file
Path to the Wannier90 .dat file containing the radial wavefunctions.
- atomic_basis
Atomic basis identifying which orbital is the outermost.
Returns
- r, r_nl
The radial grid and the corresponding radial wavefunction.
- read_femdvr_eigenvalues(result: PseudoAtomicResult) list[OrbitalEnergy][source]
Extract per-orbital energies from a
PseudoAtomicResult.Prefers the
"nscf"task (which uses the confinement potential) and falls back to"scf"if"nscf"is absent. Channel keys in the result are string integers ("0"= s,"1"= p, …).Parameters
- result
Result object returned by
solve_pseudoatomic_problem().
Returns
- list[OrbitalEnergy]
One entry per (l, n_radial) pair, in l-then-n order.
- solve_and_export(upf_path: Path, rc: float = 15.0, ri_factor: float = 0.95, extension: BasisExtension | None = None, working_dir: Path = PosixPath('.'), dat_filename: Path | str | None = None, atomic_femdvr_config: PseudoAtomicInput | None = None) tuple[PseudoAtomicResult, Path | None][source]
Solve the pseudoatomic problem, then filter the dat and Bessel files.
This wraps
solve_pseudoatomic_problem()and additionally: - Regenerates the dat file to only include the desired orbitals. - Filters the Bessel HDF5 file to match the desired basis.Returns the solver result and the path to the filtered Bessel HDF5 file (or None if no Bessel file was produced).
- solve_pseudoatomic_problem(upf_path: Path, rc: float = 15.0, ri_factor: float = 0.95, extension: BasisExtension | None = None, working_dir: Path = PosixPath('.'), dat_filename: Path | str | None = None, atomic_femdvr_config: PseudoAtomicInput | None = None, output_wfc_bessel: bool = True, output_wfc_hdf5: bool = False) PseudoAtomicResult[source]
Solve the pseudoatomic problem for a given UPF file with a soft confinement potential.
Set up the atomic-femdvr configuration, run scf + optimize + nscf, and export wavefunctions to the working directory.
Radius search and pareto front
Bisection search for the smallest rc satisfying an energy-shift threshold.
- dump_rc_search_json(rc_value: float, points: list[dict[str, Any]], ri_factor: float, threshold: float, path: Path, upf_path: Path | None = None, add: tuple[str, ...] = ()) None[source]
Write the rc search result and all probed points to a JSON file.
The output also records
kapaow_version(the running CLI’s package version) so downstream tooling can stamp provenance from the JSON rather than guessing from its ownimportlib.metadata.
- find_smallest_rc(upf_path: Path, ri_factor: float, threshold: float, extension: BasisExtension | None = None, rc_min: float = 5.0, rc_max: float = 15.0, tol: float = 0.05, working_dir: Path = PosixPath('tmp/optimize/rc_search')) tuple[float, list[dict[str, Any]]][source]
Find the smallest rc such that all energy shifts are below
threshold.Performs a bisection search over rc at fixed
ri_factor. At each candidate rc the pseudoatomic problem is solved and the maximum absolute energy shift (taken over the original-basis orbitals, as inkapaow.pareto._evaluate_point()) is compared againstthreshold. Failures to converge are treated as not satisfying the threshold.Parameters
- upf_path
Path to the UPF pseudopotential file.
- ri_factor
Fixed inner-radius factor.
- threshold
Energy-shift threshold (in Hartree). The search returns the smallest rc whose maximum energy shift is strictly less than this value.
- extension
Optional basis extension.
- rc_min, rc_max
Bracket for the bisection.
- tol
Absolute tolerance on rc at which to stop bisecting.
- working_dir
Directory for intermediate files.
Returns
- rc_value, points
The smallest rc (to within
tol) satisfying the threshold, and a list of per-probe dicts withrc,max_energy_shift, andsatisfieskeys (in evaluation order).
Raises
- RuntimeError
If even
rc_maxdoes not satisfy the threshold.
Pareto front analysis of spread vs energy shift for varying rc and ri_factor.
- compute_pareto_front(upf_path: Path, extension: BasisExtension | None = None, rc_values: list[float] | None = None, ri_factor_values: list[float] | None = None, working_dir: Path = PosixPath('tmp/optimize/spread'), loglog: bool = False) tuple[list[float], list[float], list[dict[str, Any]]][source]
Scan rc and ri_factor and collect spread and max energy shift for each.
Parameters
- upf_path
Path to the UPF pseudopotential file.
- extension
Optional basis extension.
- rc_values
List of rc values to scan.
- ri_factor_values
List of ri_factor values to scan.
- working_dir
Directory for intermediate files.
Returns
- spreads, max_energy_shifts, metadata
Lists of spread values, max absolute energy shifts, and per-point metadata.
- dump_pareto_json(spreads: list[float], max_energy_shifts: list[float], metadata: list[dict[str, Any]], path: Path, upf_path: Path | None = None) None[source]
Write all grid points to a JSON file, with a
paretoflag for front membership.
- extract_pareto_front(spreads: list[float], max_energy_shifts: list[float]) list[int][source]
Return indices of points on the Pareto front (minimizing both quantities).
A point is Pareto-optimal if no other point has both a smaller spread and a smaller max energy shift.
- find_kink_triplets(spreads: list[float], max_energy_shifts: list[float], threshold: float = np.float64(0.5235987755982988), gap_factor: float = 2.0, loglog: bool = False) list[tuple[int, int, int]][source]
Find Pareto front triplets where the middle point is a kink.
For each triplet (a, b, c) of adjacent Pareto points sorted by spread, compute the turning angle between segments (a→b) and (b→c). Only flag points where the front bows outward (negative derivative change, detected via cross product sign), where the turning angle exceeds threshold (in radians), and where the distance between a and c exceeds gap_factor times the average spacing between adjacent Pareto points.
When loglog is True the geometry is computed in log-space so that the detected kinks match what is visible on a log-log plot.
Returns a list of (i_a, i_b, i_c) index triplets.