Scheutjens-Fleer lattice self-consistent-field (SF-SCF) theory in pure Python/NumPy - a readable, extensively validated reimplementation with Namics/sfbox-compatible input and output
Project description
PySFBox — Scheutjens–Fleer SCF lattice theory in pure Python/NumPy
Scheutjens–Fleer lattice SCF in pure Python/NumPy that reads Namics input files and writes Namics output files, so existing input files, analysis and plotting scripts, and course material keep working — no C++ toolchain required.
Install (Python ≥ 3.9; the only dependency is NumPy):
pip install git+https://github.com/mark-vis/pysfbox.git # adds a `pysfbox` command to your PATH
pysfbox my_input.in # writes my_input.kal / my_input.pro
pysfbox runs from any folder and writes its .kal/.pro next to each input
file. Or, without installing — just clone the repo (or drop the pysfbox/
folder next to your inputs) and run the module directly:
pip install numpy
python -m pysfbox my_input.in # run from the folder that contains pysfbox/
GPL-3.0-or-later (see LICENSE and NOTICE; pysfbox/sfnewton.py carries its
own Wageningen copyright notice and governing terms).
Regression tests: python tests/run_tests.py. The tests/ folder doubles as
an example collection — every supported feature has a small, commented .in
file there (brushes, adsorption, charged walls, weak polyelectrolytes,
branched molecules, 2D/3D grids, searches).
Keyword reference: REFERENCE.md documents every input keyword — allowed
values, defaults, and what each computes — as a lookup manual.
Validation
PySFBox is validated against the compiled Namics: dozens of regression cases agree with the C++ oracle at or near machine precision, a 28-case exercise set reproduces the compiled Namics output case for case, and thermodynamic observables (free energy, grand potential) agree to the shared convergence floor. A depletion profile additionally cross-validates against an independent SF-SCF implementation to ~4e-13, and the physics has passed two full first-principles reviews with adversarial verification. Where PySFBox departs from Namics it does so deliberately — implementing the corrected physics where Namics has confirmed bugs (the fixed-potential Poisson factor 2, the refined-lattice bond length, the weak-charge free-energy/chemical-potential index terms) — each noted in the relevant section below.
What it supports (v1)
- Input: the Namics
key : name : parameter : valueformat,//comments,startblocks (settings accumulate, like Namics),alias(#N#substitution), andvarparameter scans (scan/step/end_value), including alias scans (scan : N-value). - Lattice: one, two, or three gradient directions (
gradients : 1/2/3), as in Namics. 1-gradient: planar, cylindrical, spherical,simple_cubic(λ=1/6) orhexagonal(λ=1/4), lattice refinementFJC_choices : 5, 7, …(spherical/cylindrical),offset_first_layershifts the radial origin. 2-gradient (simple_cubic,FJC 3, neutral/linear): flat (x,y) and cylindrical (r,z); 3-gradient: flat (x,y,z). N-D usesn_layers_x/y/zand per-axis boundslowerbound_x,upperbound_y, … (mirror/surface/periodic); one unified finite-volume lattice handles every geometry. - mon:
freedomfree / frozen / pinned,frozen_range/pinned_range(1-gradient1;3,lowerbound,upperbound,firstlayer,lastlayer; 2D/3D box grammarxlo,ylo[,zlo];xhi,yhi[,zhi]),chi_Xinteraction parameters. - mol: linear multi-block compositions
(X)1(A)200(G)1AND branched architectures — bracket side chains(C)18[(O)1](O)1...(nestable; several[..][..]on one node), symmetric dendrimers/stars@dend(X,(A)200(G)1,5), combs@comb((B)10;B,(A)25,(B)9,100;(B)1), and group-repeat sugar(C)12((O)1(C)2)5(O)1, on any 1-gradient geometry;freedomfree (phibulk), restricted (northeta), solvent. - search / super-iteration:
var : mol-X : search : theta(orn/phibulk) paired with a target —var : sys-NN : grand_potential : 0,... : free_energy : V, orvar : mol-Y : mu / theta / n / phibulk : V— runs an outer scalar root-find (regula falsi) that adjusts the searched quantity until the target observable hits its value, each step a full warm-started SCF solve. (Theconstraint:delta/Laplace_pressuremembrane-balance search of the Namics examples is not supported.) - Electrostatics:
mon : valence(fixed charges, strong electrolytes),mon : epsilon(relative permittivity, default 80),mon : e.psi0/kT(fixed surface potential on a frozen electrode layer),mol : freedom : neutralizer(auto-adjusts its bulk fraction for an electroneutral bulk),lat : bondlength(meters). The dimensionless potential psi joins the iteration; profiles viapro : sys : X : psiandq. Works on planar, cylindrical and spherical lattices (the curved Poisson uses face-area-weighted fluxes;tests/charged_sphere.in,tests/charged_cylinder.inmatch the compiled Namics at the convergence floor, and the spherical potential reproduces analytic Debye–Hückel screening). Validated against the compiled Namics at machine precision (tests/polyelectrolyte_wall.in); fixed-potential systems (tests/edl_fixed_psi.in) validate against Debye theory instead, because PySFBox deliberately corrects a factor-2 bug in the Namics fixed-potential Poisson branch (Debye lengths come out √2 too short there). Refined curved charged lattices (FJC_choices > 3) are supported and refinement-consistent: the Poisson equation keeps the physical bond length, where Namics' Debye lengths scale spuriously with the refinement — so expect a deviation from an unpatched Namics on that combination. Curved fixed-potential electrodes raise a clear message. - Weak (multistate) charges:
state : AH : mon : A+state : AH : valence : 0attach annealed internal states to a segment;reaction : weak : equation : 1(AH) + 1(H2O) = 1(AM) + 1(H3O)+reaction : weak : pK : 7set their equilibria;state : H3O : alphabulk : 1e-7anchors the pH (site-fraction based, as in Namics: "pH" = −log10 alphabulk_H3O). The bulk ionisation is solved to machine precision (Henderson–Hasselbalch exactly, in site fractions with the water activity included); locally every state equilibrates with the fields — charge regulation comes out automatically. Titration scans:var : state-H3O : scan : alphabulkwithscale : exponential(steps= steps per decade, as in Namics). Per-state outputs: kaltheta_AM,alphabulk_AM,phibulk_AM,theta_exc_AM(on the parent mon),mol : X : mu-AM; prophi-AM,alpha-AM,u-AM. States inherit the mon's chi (state-level chi overrides); the mon-level valence is ignored once states exist. Validated against the compiled Namics at the convergence floor on the NamicspolE.inexample and a 73-step titration (tests/weak_*.in); PySFBox implements corrected physics in a few spots where Namics has known bugs, so F/mu comparisons with an unpatched Namics differ whenever state–state chi is nonzero. - sys:
initial_guess—polymer_adsorption/membrane/micelleanalytic starting potentials (the Namics formulas, e.g. u = −λ·χ at solid-adjacent sites), honored on the first calculation only, exactly like Namics;previous_result/noneaccepted; guess files andmembrane_torusraise a clear message. - Solver: the Namics pseudohessian quasi-Newton (a licensed Python
translation of
sfnewton.cpp) as primary, with Anderson-accelerated Picard and two rescue stages as fallbacks (readsnewton : ... : tolerance / iterationlimit / deltamax / m / method; the Namicsmethodchoicespseudohessian/DIIS/Picard/LBFGS/hessianmap onto this cascade —hessianis the Namics full-Hessian mode, recomputing a finite-difference Hessian every iteration). Propagators are renormalised with log-domain bookkeeping, so long or strongly adsorbing chains cannot overflow. Large 2D/3D grids (beyond the dense pseudohessian) are handled by the Anderson stage. - Output:
.kal(header + one row per calculation/var step,%.16e) and.pro(x at half-integer layers +%.20gcolumns), with Namics file numbering. Unknown output properties printNiNplus a warning — exactly like Namics. Implemented kal properties:sysgrand_potential, free_energy (andfree_energy (po)), iterations, residual;latn_layers / volume;moltheta, theta_exc, phibulk, Mu/mu (chemical potential), mu-STATE, n, N, chainlength, GN, phiM, phiMax, phiMin,<alias>-value;montheta, theta_exc, phibulk, chi_X, 1st_M_phi_z, 2nd_M_phi_z, RMS, fluctuations, and per-state alphabulk_S / valence_S / phibulk_S / theta_S / theta_exc_S. Profiles (pro):mol : X : phi,mon : X : phi,mon : X : u,mon : X : phi-S / alpha-S / u-S,sys : X : alpha / psi / q.
Beyond this release
Full-Namics features that this release does not implement raise a clear
NotImplementedError naming the feature, rather than silently producing
wrong numbers — so an input either runs correctly or tells you exactly what
it needs. Currently outside scope: ring and asymmetric-dendrimer
architectures, the constraint:delta / Laplace_pressure membrane-balance
search, initial-guess files, and mesodyn/cleng/teng calculations.
The one-gradient path (planar / cylindrical / spherical, including
FJC_choices refinement) covers the full feature set above. The two- and
three-gradient lattices — 2D flat, 2D cylindrical (r,z), 3D flat, as in
Namics — currently cover neutral linear chains on simple_cubic; charged and
branched systems there need gradients : 1. Curved fixed-potential
electrodes raise. For weak charges, unbalanced reactions and states on frozen
monomers are rejected (the latter also in Namics).
Extending (where things live)
The module layout mirrors the Namics classes, so the C++ original is the roadmap for extensions:
| file | contents | Namics counterpart |
|---|---|---|
inputreader.py |
input parsing, alias, var | input.cpp |
lattice.py |
1-gradient geometry, ghost layers, site averages, moments | lattice.cpp, LGrad1.cpp |
latticend.py |
2- and 3-gradient finite-volume lattice | LGrad2.cpp, LGrad3.cpp |
model.py |
segments, molecules (linear + branched trees), renormalised propagators | segment.cpp, molecule.cpp, mol_linear.cpp, mol_branched.cpp, mol_comb.cpp, mol_dendrimer.cpp |
reactions.py |
internal states, reactions, bulk ionisation solve | state.cpp, reaction.cpp |
system.py |
residual, masks, observables, solver cascade, initial guesses | system.cpp, solve_scf.cpp |
sfnewton.py |
pseudohessian quasi-Newton (licensed translation) | sfnewton.cpp |
output.py, runner.py |
.kal/.pro writers, start/var loops, search | output.cpp, namics.cpp, variate.cpp |
Examples: new output properties = extend the tables in System.get_value /
System.get_profile; new chain architectures = generalise the tree walk in
Molecule (see mol_branched.cpp).
Convergence envelope
The primary solver IS the Namics quasi-Newton (pseudohessian): sfnewton.py
is a faithful, licensed Python translation of sfnewton.cpp, run from the
warm-start guess with Namics-like iteration counts. Anderson-accelerated
Picard (with a damped-Picard warmup on cold starts) is the fallback, plus
two rescue stages (extended-budget pseudohessian, one-shot full-Hessian
anchor) for the stiffest cases. This converges the entire shipped
validation set, including the extreme corner — very strong adsorption of very long
chains (the shipped homopolymer_adsorption.in, N = 10000, chi_Si = -6)
converges via the extended rescue and matches the compiled Namics exactly.
Genuine non-convergence still fails gracefully (a clear "no convergence"
message with actionable advice, never a wrong number). var scans
warm-start each step from the previous solution — ramping a parameter
(e.g. a co-solvent) is the intended way into hard regimes.
Speed
The default path is deliberately pure NumPy — one dependency, every equation
inspectable — running the same pseudohessian quasi-Newton as Namics, with
Namics-like iteration counts. On a 28-case benchmark set the compiled Namics
C++ takes ~18 s where PySFBox takes ~2 minutes, and every one of those 28 cases
matches the compiled Namics output: typical single problems solve in seconds,
the stiffest shipped case (homopolymer_adsorption.in, N = 10000, chi_Si = −6)
in a few minutes. For throughput-bound parameter sweeps, the same input files
run unchanged on the compiled Namics — compatibility works in both directions.
Caveats for quantitative use
- The primary solver is a translation of Namics' quasi-Newton; iteration counts are Namics-like but not identical (fallback stages differ), and converged solutions agree to the shared convergence floor.
- The grand potential and free energy implement the standard uncharged SF
expressions; cross-validated against compiled Namics on a two-brush case
(free energy 6e-9, grand potential 5e-8 relative — the shared convergence
floor; see
tests/two_brushes_quick.in). For research use, spot-check your own geometry the same way. Profiles, theta, theta_exc, and moments follow the Namics definitions exactly (LGrad1::Moment,WeightedSum). - Default tolerance 1e-7, iterationlimit 1000, deltamax 0.1 (as in Namics).
Acknowledgements & lineage
PySFBox is a reimplementation of the Scheutjens–Fleer self-consistent-field
lattice theory as realised in sfbox/Namics by F.A.M. Leermakers and the
Physical Chemistry group at Wageningen University — the input/output formats,
property names, and numerical conventions all follow Namics so that existing
material keeps working. pysfbox/sfnewton.py is a Python translation of
Namics' sfnewton.cpp and carries the original Wageningen University
copyright and reproduction notice in its file header, which governs that
file. Many thanks to Frans Leermakers for sfbox and Namics — the theory and
the code that PySFBox builds on — and for discussions along the way.
PySFBox itself is licensed under the GNU General Public License v3.0 or later
(GPL-3.0-or-later) — see LICENSE and NOTICE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pysfbox-1.0.0.tar.gz.
File metadata
- Download URL: pysfbox-1.0.0.tar.gz
- Upload date:
- Size: 83.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3d4c8621e6a1f6e5637e7586a3087ff0d6a7f08e67662c4f66bb4fc285a697c6
|
|
| MD5 |
d940ae3f68825dc6f8dbe23cdbcfca4f
|
|
| BLAKE2b-256 |
cbec0e98fc06bcc54eb034cb4eb3417606c699d0e79f3a6c4078b1573ac34498
|
Provenance
The following attestation bundles were made for pysfbox-1.0.0.tar.gz:
Publisher:
publish.yml on mark-vis/pysfbox
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pysfbox-1.0.0.tar.gz -
Subject digest:
3d4c8621e6a1f6e5637e7586a3087ff0d6a7f08e67662c4f66bb4fc285a697c6 - Sigstore transparency entry: 2163088830
- Sigstore integration time:
-
Permalink:
mark-vis/pysfbox@71e72d90968118ae1aea4cd27218d3200e26bc86 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/mark-vis
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@71e72d90968118ae1aea4cd27218d3200e26bc86 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pysfbox-1.0.0-py3-none-any.whl.
File metadata
- Download URL: pysfbox-1.0.0-py3-none-any.whl
- Upload date:
- Size: 83.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6e125913f0740db279ca39e9002700dcaa2c15d8655f1120916b19724f538d65
|
|
| MD5 |
e5c9a013589059db21d78bd939d7a7ab
|
|
| BLAKE2b-256 |
ffcb42f977aeb4ca1e427ebf0d4da8a521d85c73b443c246b94ad4c8df36c96d
|
Provenance
The following attestation bundles were made for pysfbox-1.0.0-py3-none-any.whl:
Publisher:
publish.yml on mark-vis/pysfbox
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pysfbox-1.0.0-py3-none-any.whl -
Subject digest:
6e125913f0740db279ca39e9002700dcaa2c15d8655f1120916b19724f538d65 - Sigstore transparency entry: 2163088910
- Sigstore integration time:
-
Permalink:
mark-vis/pysfbox@71e72d90968118ae1aea4cd27218d3200e26bc86 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/mark-vis
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@71e72d90968118ae1aea4cd27218d3200e26bc86 -
Trigger Event:
push
-
Statement type: