Usage

The everyday entry point is the command-line interfacekapaow 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 AngularMomentum(*values)[source]

Angular momentum quantum numbers.

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.

property n_max: int

The maximum principal 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, where PP_HEADER advertises number_of_wfc=0), the baseline is reconstructed from the neutral-atom Madelung filling, keeping only the outermost subshells whose electron count sums to z_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.

classmethod coerce_keys(v: Any) Any[source]

Coerce integer keys to AngularMomentum.

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.

classmethod coerce_l(v: Any) Any[source]

Coerce integer l to AngMtm.

validate_n_l() Self[source]

Validate that n and l are consistent.

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 --add flags.

The CLI exposes a repeatable --add option whose values are the string forms of BasisExtensionType. 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 returns None.

Returns

BasisExtension | None

The corresponding extension, or None if 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+1 per 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.