Google Summer of Code · 2026 QC-Devs / FFPrime

Multipole Electrostatics
in FFPrime

A new electrostatics module for FFPrime — Cartesian and spherical multipole potentials and fields, a unifying multipole container, completed stretch-goal analysis utilities, and the validation to prove it.

MentorsProfessor Esteban Vohringer · Carlos Castillo-Orellana · Omid Hosseinzadeh
01 · The Problem

Why FFPrime needs multipole electrostatics

FFPrime derives molecular-mechanics force-field parameters directly from a quantum-chemical electron density, using Atoms-in-Molecules (AIM) partitioning rather than empirical fitting. A single point charge per atom — the simplest possible electrostatic model — can't represent the anisotropy of a lone pair or a polarized bond, and AIM partitioning already produces richer information than that: dipole and quadrupole moments fall directly out of the same density partitioning.

What was missing was a way to use that information: a module turning atomic multipole moments into an actual electrostatic potential and field, in both representations the field commonly uses — Cartesian tensors and real spherical harmonics — with the numerical rigor scientific software demands.

Point charges
insufficient for anisotropic electrostatics
AIM partitioning
already provides multipole moments
FFPrime
needed a way to use them
02 · What I Built

Four pieces, one module

01

Cartesian Multipoles

✓ Merged
Potential · Field · Vectorization

Given atomic charges, dipole vectors, and traceless quadrupole tensors, cartesian.py computes the electrostatic potential and field at an arbitrary array of field points — fully vectorized with np.einsum, no per-atom Python loop.

Technical details +
Functions
monopole/dipole/quadrupole_potential, _field, plus total_potential/total_field
Inputs
atcharges (N,), dipoles (N,3), quadrupoles (N,3,3), atomic units
Edge cases
Distances below 1e-12 are treated as inf to suppress singular self-contributions
def monopole_potential(atcharges, atcoords, points):
    # V(r) = sum_i  q_i / |r - r_i|
    _, _, safe_r = compute_displacement(atcoords, points)
    return np.sum(atcharges[np.newaxis, :] / safe_r, axis=1)
02

Spherical Representation

✓ Merged
Stone convention · Conversions · Evaluation

spherical.py adds the real spherical-harmonic representation used by force fields like AMOEBA: bidirectional Cartesian↔spherical conversion for dipoles and quadrupoles, plus direct potential evaluation without a Cartesian round-trip.

Technical details +
Convention
Stone, Theory of Intermolecular Forces — Q10=p_z, Q11c=p_x, Q11s=p_y; quadrupole off-diagonals carry a √3 factor
Functions
dipole/quadrupole_cartesian_to_spherical (+ inverse), spherical_total_potential/field
Field path
Converts to Cartesian and reuses the tested quadrupole_field, rather than a separate spherical-field derivation
03

Expansion + Analysis Utilities

✓ Merged
MultipoleExpansion · Field Projection · Cosine Similarity · RMS Deviation

expansion.py adds MultipoleExpansion, a typed container built via from_cartesian()/from_spherical(). As a completed stretch goal beyond the core multipole implementation, analysis.py adds three electrostatic-field comparison utilities: field projection, cosine similarity, and RMS deviation.

Technical details +
Attributes
atcoords/atcharges/atdipoles/atquadrupoles — IOData naming, adopted after reviewer feedback
Container only
No .potential()/.field() method yet — evaluation still calls cartesian.py/spherical.py directly
analysis.py
project_field, cosine_similarity, rms_deviation
expansion = MultipoleExpansion.from_spherical(
    atcoords=atcoords, atcharges=atcharges,
    atdipoles=spherical_dipoles,      # (N, 3)
    atquadrupoles=spherical_quads,    # (N, 5)
)
04

Validation + Molecular Example

● PR #19 under review
Tests · Water

Five test modules cover analytical, gradient, and cross-representation checks. One example notebook applies the module to a real MBIS-partitioned molecule.

03 · Inside the Implementation

From moments to a validated field

Every contribution above moves through the same pipeline — the conceptual backbone of the project.

Multipole moments Cartesian ↔ spherical Potential Field Analysis Validation
Story 01

The Stone Convention

Convention
A spherical multipole moment means nothing without a fixed normalization convention — textbooks and force fields disagree on √3 factors, ordering, and trace handling.
Decision
Standardize on A. J. Stone's Theory of Intermolecular Forces convention — the same one AMOEBA-family force fields use.
Implementation
Fixed at every layer: storage in MultipoleExpansion, the conversion functions, spherical_total_potential, and the field path's conversion back to Cartesian.
Validation
Cartesian/spherical path-equivalence tests confirm both representations of the same physical multipole agree. Fixed for the quadrupole field specifically in commit d160e72, part of PR #18.
Story 02

The Quadrupole Field Correction

"A passing test is not the same as a correct derivative."

Under the convention used by this implementation, the quadrupole potential is V = Θabrarb/r⁵. A first-pass gradient missed that a symmetric Θ is contracted twice with r — off by exactly a factor of two.

V = Θr·r/r⁵ E = −∇V factor-of-two discrepancy finite-difference check correction
View implementation +
term1 = 5 * Qrr[..., None] * r_vecs / safe_r[..., None] ** 7
# Theta is symmetric: d/dr_c(Theta_ab r_a r_b) picks up Theta_cb r_b
# from BOTH the a=c and b=c contractions -- hence the factor of 2.
term2 = 2.0 * Qr / safe_r[..., None] ** 5  # corrected factor of 2
return np.sum(term1 - term2, axis=1)
Excerpt, cartesian.pyquadrupole_field(), verified symbolically and via finite differences in test_multipole.py and test_cartesian_quadrupole_field.py.
04 · Validation

Proving correctness, not just running code

Analytical
Gradient
Cartesian ↔ Spherical
Edge cases
Analysis

Five dedicated test modules cover the main validation categories — closed-form checks, finite-difference gradient consistency (E = −∇V), and agreement between the Cartesian and spherical evaluation paths — with shared utilities in utils.py handling singularity-safe edge cases rather than standing as a test module of its own. Exact pass/fail counts are reported inline in PR review discussion rather than independently reproduced, so no aggregate test-count figure is claimed on this page.

View tests +
05 · From Code to Molecule

Water

The notebook is part of PR #19 — opened, not yet merged.

Water

● Under review
Electrostatic potential of water evaluated with monopole, monopole plus dipole, and monopole plus dipole plus quadrupole expansions.
Monopole → +dipole → +quadrupole electrostatic potential, water (a.u., Bohr)

MBIS atomic multipoles from water.fchk are evaluated progressively through a MultipoleExpansion — monopole, then monopole + dipole, then monopole + dipole + quadrupole — showing how each higher-order moment reshapes the electrostatic potential.

This is a demonstration of the multipole implementation on a real molecule, not a claim about performance or scalability — no benchmarking data exists in the repository.

06 · Timeline & Contributions

From first PR to open review

June 2026

Core

  • #13 Cartesian core merged
  • #14 Validation + fixes merged
July 2026

Spherical + Validation

  • #15 Spherical representation merged
  • #18 Expansion, Stone fix & analysis merged
August 2026

Examples + Review

  • #19 Water example opened
  • Open as of this report
View full contribution history +
#13
Adds multipole.py (monopole/dipole/quadrupole potential & field, vectorized) + test_multipole.py. Merged Jun 12, 2026.
#14
Input-shape validation; corrects a quadrupole formula error. Merged Jun 23, 2026.
#15
Direct spherical dipole/quadrupole potential; splits module into cartesian.py/spherical.py. Reviewer feedback requested smaller PRs and atcharges/atcoords renaming to match IOData. Merged Jul 3, 2026.
#18
Adds expansion.py, analysis.py; fixes quadrupole field convention; renames to at* attributes; adds three test modules. Merged Jul 31, 2026.
#19
Adds examples/multipole_water.ipynb plus MBIS input/reference data. Opened Aug 2, 2026 — demonstration only.
#5
Early dipole/quadrupole draft, closed the same day #13 merged — superseded by #13.
#11
Early Cartesian↔spherical conversion utilities, closed on GSoC start — functionality reintroduced via #15.
07 · Challenges & What I Learned

Three lessons

01
Correctness

"A passing test is not the same as a correct derivative."

The quadrupole field's missing factor of 2 didn't fail loudly — it required deliberately checking E = −∇V against finite differences rather than trusting a formula "looked right." Fixed in commit d160e72, verified in test_cartesian_quadrupole_field.py.

02
Conventions

"Scientific conventions become software contracts."

Adopting Stone's convention explicitly — and propagating it through conversion, evaluation, and tests — mattered more to correctness than any single function's implementation.

03
Review

"Open-source review changes how you scope work."

Reviewer feedback on PR #15's size and naming led directly to smaller, more focused PRs afterward, and to renaming attributes to match IOData's at-prefixed convention.

Next steps include exposing electrostatic evaluation directly through MultipoleExpansion, extending the implementation to higher-order moments, and adding quantitative benchmarking. The Water demonstration remains under review in PR #19.

08 · Final Reflection
Before

Atomic moments existed without a downstream way to evaluate an electrostatic potential or field, and no spherical-harmonic representation to interoperate with the rest of the force-field literature.

During

Cartesian → spherical → potential → field → analysis → validation — built outward from a vectorized core, under an explicit convention, with tests designed to catch the errors a passing suite can hide.

After

A tested, reviewed, merged multipole electrostatics package spanning both representations, together with completed stretch-goal analysis utilities and a molecular demonstration in active review.