Skip to content

kups.potential.classical.coulomb

Coulomb electrostatic potential for vacuum/non-periodic systems.

This module provides a simple pairwise Coulomb potential for charged systems without periodic boundary conditions. For periodic systems with long-range electrostatics, use Ewald summation instead.

Potential: \(U = \frac{1}{4\pi\epsilon_0} \sum_{i<j} \frac{q_i q_j}{r_{ij}}\)

IsCoulombVacuumState

Bases: Protocol

Protocol for states providing all inputs for the Coulomb vacuum potential.

Source code in src/kups/potential/classical/coulomb.py
class IsCoulombVacuumState(Protocol):
    """Protocol for states providing all inputs for the Coulomb vacuum potential."""

    @property
    def particles(self) -> Table[ParticleId, IsCoulombGraphParticles]: ...
    @property
    def systems(self) -> Table[SystemId, HasUnitCell]: ...
    @property
    def neighborlist(self) -> NearestNeighborList: ...
    @property
    def coulomb_cutoff(self) -> Table[SystemId, Array]: ...

coulomb_vacuum_energy(inp)

Compute Coulomb electrostatic energy for all charge pairs.

Calculates pairwise electrostatic energy using Coulomb's law and sums over all systems. Accounts for double counting.

Parameters:

Name Type Description Default
inp CoulombVacuumInput

Graph potential input

required

Returns:

Type Description
WithPatch[Table[SystemId, Energy], IdPatch]

Total electrostatic energy per system

Source code in src/kups/potential/classical/coulomb.py
def coulomb_vacuum_energy(
    inp: CoulombVacuumInput,
) -> WithPatch[Table[SystemId, Energy], IdPatch]:
    """Compute Coulomb electrostatic energy for all charge pairs.

    Calculates pairwise electrostatic energy using Coulomb's law and sums
    over all systems. Accounts for double counting.

    Args:
        inp: Graph potential input

    Returns:
        Total electrostatic energy per system
    """
    edg = inp.graph.particles[inp.graph.edges.indices]
    qij = edg.charges[:, 0] * edg.charges[:, 1]
    dists = jnp.linalg.norm(inp.graph.edge_shifts[:, 0], axis=-1)
    energies = inp.graph.edge_batch_mask.sum_over(qij / dists) / 2 * TO_STANDARD_UNITS
    assert len(energies) == inp.graph.batch_size
    return WithPatch(energies, IdPatch())

make_coulomb_vacuum_from_state(state, probe=None, *, compute_position_and_unitcell_gradients=False)

make_coulomb_vacuum_from_state(
    state: Lens[State, IsCoulombVacuumState],
    probe: None = None,
    *,
    compute_position_and_unitcell_gradients: Literal[
        False
    ] = ...,
) -> Potential[State, EmptyType, EmptyType, Any]
make_coulomb_vacuum_from_state(
    state: Lens[State, IsCoulombVacuumState],
    probe: None = None,
    *,
    compute_position_and_unitcell_gradients: Literal[True],
) -> Potential[State, PositionAndUnitCell, EmptyType, Any]
make_coulomb_vacuum_from_state(
    state: Lens[State, IsCoulombVacuumState],
    probe: Probe[
        State,
        P,
        IsRadiusGraphProbe[IsCoulombGraphParticles],
    ],
    *,
    compute_position_and_unitcell_gradients: Literal[
        False
    ] = ...,
) -> Potential[State, EmptyType, EmptyType, P]
make_coulomb_vacuum_from_state(
    state: Lens[State, IsCoulombVacuumState],
    probe: Probe[
        State,
        P,
        IsRadiusGraphProbe[IsCoulombGraphParticles],
    ],
    *,
    compute_position_and_unitcell_gradients: Literal[True],
) -> Potential[State, PositionAndUnitCell, EmptyType, P]

Create a Coulomb vacuum potential from a typed state, optionally with incremental updates.

Convenience wrapper around make_coulomb_vacuum_potential. When probe is None, creates a plain potential for states satisfying IsCoulombVacuumState. When a probe is provided, wires incremental patch-based updates for the same state type.

Parameters:

Name Type Description Default
state Any

Lens into the sub-state providing particles, systems, and neighbor list.

required
probe Any

Detects which particles and neighbor-list edges changed since the last step. Pass None (default) for a non-incremental potential.

None
compute_position_and_unitcell_gradients bool

When True, compute gradients w.r.t. particle positions and lattice vectors.

False

Returns:

Type Description
Any

Configured Coulomb vacuum Potential.

Source code in src/kups/potential/classical/coulomb.py
def make_coulomb_vacuum_from_state(
    state: Any,
    probe: Any = None,
    *,
    compute_position_and_unitcell_gradients: bool = False,
) -> Any:
    """Create a Coulomb vacuum potential from a typed state, optionally with incremental updates.

    Convenience wrapper around
    [make_coulomb_vacuum_potential][kups.potential.classical.coulomb.make_coulomb_vacuum_potential].
    When ``probe`` is ``None``, creates a plain potential for states satisfying
    [IsCoulombVacuumState][kups.potential.classical.coulomb.IsCoulombVacuumState].
    When a ``probe`` is provided, wires incremental patch-based updates for the same state type.

    Args:
        state: Lens into the sub-state providing particles, systems, and neighbor list.
        probe: Detects which particles and neighbor-list edges changed since the last step.
            Pass ``None`` (default) for a non-incremental potential.
        compute_position_and_unitcell_gradients: When ``True``, compute gradients
            w.r.t. particle positions and lattice vectors.

    Returns:
        Configured Coulomb vacuum [Potential][kups.core.potential.Potential].
    """
    gradient_lens: Any = EMPTY_LENS
    patch_idx_view: Any = None
    if compute_position_and_unitcell_gradients:
        gradient_lens = SimpleLens[CoulombVacuumInput, PositionAndUnitCell](
            lambda x: PositionAndUnitCell(
                x.graph.particles.map_data(lambda p: p.positions),
                x.graph.systems.map_data(lambda s: s.unitcell),
            )
        )
        patch_idx_view = position_and_unitcell_idx_view
    if probe is not None:
        patch_idx_view = patch_idx_view or empty_patch_idx_view
    return make_coulomb_vacuum_potential(
        state.focus(lambda x: x.particles),
        state.focus(lambda x: x.systems),
        state.focus(lambda x: x.coulomb_cutoff),
        state.focus(lambda x: x.neighborlist),
        probe,
        gradient_lens,
        EMPTY_LENS,
        EMPTY_LENS,
        patch_idx_view,
    )

make_coulomb_vacuum_potential(particles_view, systems_view, cutoffs_view, neighborlist_view, probe, gradient_lens, hessian_lens, hessian_idx_view, patch_idx_view=None, out_cache_lens=None)

Create simple Coulomb potential for non-periodic systems.

Computes pairwise electrostatic interactions using Coulomb's law. Suitable for gas-phase or cluster systems. For periodic/bulk systems, use Ewald summation for proper treatment of long-range electrostatics.

Parameters:

Name Type Description Default
particles_view View[State, Table[ParticleId, IsCoulombGraphParticles]]

Extracts indexed particle data (positions, charges, system index)

required
systems_view View[State, Table[SystemId, HasUnitCell]]

Extracts indexed system data (unit cell)

required
cutoffs_view View[State, Table[SystemId, Array]]

Extracts cutoff array from state

required
neighborlist_view View[State, NearestNeighborList]

Extracts neighbor list

required
probe Probe[State, Ptch, IsRadiusGraphProbe[IsCoulombGraphParticles]] | None

Grouped probe for incremental updates (particles, neighborlist_after, neighborlist_before)

required
gradient_lens Lens[CoulombVacuumInput, Gradients]

Specifies gradients to compute

required
hessian_lens Lens[Gradients, Hessians]

Specifies Hessians to compute

required
hessian_idx_view View[State, Hessians]

Hessian index structure

required
patch_idx_view View[State, PotentialOut[Gradients, Hessians]] | None

Cached output index structure (optional)

None
out_cache_lens Lens[State, PotentialOut[Gradients, Hessians]] | None

Cache location lens (optional)

None

Returns:

Type Description
Potential[State, Gradients, Hessians, Ptch]

Coulomb potential for vacuum.

Source code in src/kups/potential/classical/coulomb.py
def make_coulomb_vacuum_potential[
    State,
    Ptch: Patch,
    Gradients,
    Hessians,
](
    particles_view: View[State, Table[ParticleId, IsCoulombGraphParticles]],
    systems_view: View[State, Table[SystemId, HasUnitCell]],
    cutoffs_view: View[State, Table[SystemId, Array]],
    neighborlist_view: View[State, NearestNeighborList],
    probe: Probe[State, Ptch, IsRadiusGraphProbe[IsCoulombGraphParticles]] | None,
    gradient_lens: Lens[CoulombVacuumInput, Gradients],
    hessian_lens: Lens[Gradients, Hessians],
    hessian_idx_view: View[State, Hessians],
    patch_idx_view: View[State, PotentialOut[Gradients, Hessians]] | None = None,
    out_cache_lens: Lens[State, PotentialOut[Gradients, Hessians]] | None = None,
) -> Potential[State, Gradients, Hessians, Ptch]:
    """Create simple Coulomb potential for non-periodic systems.

    Computes pairwise electrostatic interactions using Coulomb's law. Suitable for
    gas-phase or cluster systems. For periodic/bulk systems, use
    [Ewald summation][kups.potential.classical.ewald] for proper treatment of
    long-range electrostatics.

    Args:
        particles_view: Extracts indexed particle data (positions, charges, system index)
        systems_view: Extracts indexed system data (unit cell)
        cutoffs_view: Extracts cutoff array from state
        neighborlist_view: Extracts neighbor list
        probe: Grouped probe for incremental updates (particles, neighborlist_after, neighborlist_before)
        gradient_lens: Specifies gradients to compute
        hessian_lens: Specifies Hessians to compute
        hessian_idx_view: Hessian index structure
        patch_idx_view: Cached output index structure (optional)
        out_cache_lens: Cache location lens (optional)

    Returns:
        Coulomb potential for vacuum.
    """
    radius_graph_fn = RadiusGraphConstructor(
        particles=particles_view,
        systems=systems_view,
        cutoffs=cutoffs_view,
        neighborlist=neighborlist_view,
        probe=probe,
    )
    composer = LocalGraphSumComposer(
        graph_constructor=radius_graph_fn,
        parameter_view=lambda _: None,
    )
    potential = PotentialFromEnergy(
        composer=composer,
        energy_fn=coulomb_vacuum_energy,
        gradient_lens=gradient_lens,
        hessian_lens=hessian_lens,
        hessian_idx_view=hessian_idx_view,
        cache_lens=out_cache_lens,
        patch_idx_view=patch_idx_view,
    )
    return potential