This site is version 0.0.1. Most of the code on this site is drafted by an agent and needs review. I apologize for the AI-speak, we are working on rewriting it!
viv-lang.org
Python Viv

vp.nuclear.skyrme_hf

The Skyrme-Hartree-Fock ground state of a spherical closed-shell nucleus: binding energy, radii, single-particle levels and densities, with the forces SLy4, SLy5, SIII or SkM*.

Part of the distribution vipyr-nuclear; it runs from a checkout of the repository. Planned on PyPI as pip install "vipyr[nuclear]".

Give it a nucleus whose protons and neutrons both fill whole shells, such as "16O", "40Ca", "48Ca", "56Ni", "90Zr", "132Sn" or "208Pb", and a Skyrme force. It returns the self-consistent mean-field ground state: the total energy and the energy per nucleon, the proton, neutron and charge radii, the energy of every occupied orbital with its name ("n 3p1/2"), the other bound orbitals of the final mean field, and the densities on the radial mesh.

This is the textbook calculation of nuclear structure (Vautherin and Brink, 1972): each nucleon moves in a field made by all the others, and the equations are solved until the field and the orbitals agree. Lead-208 takes about 0.09 s on 186 mesh points.

It does not do open-shell nuclei. A nucleus whose last orbital would be partly filled, such as calcium-44, is refused with the name of that orbital: treating it needs pairing (BCS or Hartree-Fock-Bogoliubov), which this solver does not have.

Signature

vp.nuclear.skyrme_hf(*, nucleus="16O", force="sly5", t0=preset.t0, t1=preset.t1, t2=preset.t2, t3=preset.t3, x0=preset.x0, x1=preset.x1, x2=preset.x2, x3=preset.x3, w0=preset.w0, alpha=preset.alpha, j2=preset.j2, spacing=0.1, box=..., mixing="pulay", mix=0.5, history=6, e_tol=1.0e-9, max_iter=300)

Parameters

nucleus : str, default "16O"
The nucleus, as its mass number and element symbol ("208Pb"; "Pb208" and "pb-208" also work).
force : str, default "sly5"
The Skyrme force; t0 ... j2 start from its values. Range: one of sly4, sly5, siii, skm*. Case does not matter.
t0 : float, default preset.t0
The force's t0, the contact term.
t1 : float, default preset.t1
The force's t1, the momentum-dependent term even in the relative momentum.
t2 : float, default preset.t2
The force's t2, the term odd in the relative momentum.
t3 : float, default preset.t3
The force's t3, the density-dependent term.
x0 : float, default preset.x0
The spin exchange of the t0 term.
x1 : float, default preset.x1
The spin exchange of the t1 term.
x2 : float, default preset.x2
The spin exchange of the t2 term.
x3 : float, default preset.x3
The spin exchange of the t3 term.
w0 : float, default preset.w0
The spin-orbit strength.
alpha : float, default preset.alpha
The power of the density in the t3 term. Range: >= 0, <= 1.
j2 : bool, default preset.j2
Whether the J^2 terms of t1 and t2 are kept (SLy5 and SIII keep them, SLy4 and SkM* drop them).
spacing : float, default 0.1
The mesh spacing. Range: > 0, <= 1.
box : float, default max(15.0, 1.2 * pow(Z_NUC + N_NUC, 1.0 / 3.0) + 11.5)
The width of the box; the orbitals are zero past it. Range: >= 5, <= 60.
mixing : str, default "pulay"
How each pass's densities are mixed into the next pass's: linear keeps MIX of the new ones; pulay and broyden combine the last HISTORY passes (lib/scf/mix.viv). Range: one of linear, pulay, broyden, every, every simple, every accelerated. Case does not matter.
mix : float, default 0.5
The fraction of each pass's new densities mixed into the old ones; for pulay and broyden, the fraction of their combined residual. Range: > 0, <= 1.
history : int, default 6
How many passes pulay and broyden combine, the current one included (pulay and broyden only; linear ignores it). Range: >= 1, <= 50.
e_tol : float, default 1.0e-9
The iteration stops when the total energy changes by less than this between passes. Range: >= 0.
max_iter : int, default 300
At most this many passes; a run that does not settle returns converged = False. Range: >= 1, <= 10000.

Returns

A frozen SkyrmeResult. Iterating it gives the fields below in order; it also has params, tests, log and check().

z : int
The protons.
n : int
The neutrons.
e_per_a : float, MeV
The energy per nucleon, MeV.
energy : float, MeV
The total energy, MeV.
r_p : float, fm
The protons' root mean square radius, fm.
r_n : float, fm
The neutrons' root mean square radius, fm.
r_ch : float, fm
The charge radius: the proton density folded with a Gaussian of range 0.65 fm, fm.
levels : ndarray[float64], MeV
The occupied orbitals' energies, MeV: the neutrons' lowest first, then the protons'.
labels : str
The names of LEVELS, comma-separated: "n 1s1/2,n 1p3/2,...,p 1s1/2,...".
spectrum : ndarray[float64], MeV
Every bound orbital of the converged fields, MeV: the neutrons' lowest first, then the protons'.
spectrum_labels : str
The names of SPECTRUM, comma-separated.
spectrum_occ : ndarray[float64]
The nucleons in each SPECTRUM orbital: 2j + 1 if occupied, else 0.
iterations : int
The passes run.
converged : bool
Whether the energy settled to E_TOL within MAX_ITER passes.
refills : int
The passes after the first in which the occupation was chosen again, because an orbital changed places.
mesh : ndarray[float64], fm
The mesh points, fm.
rho : ndarray[float64], fm^-3
The number densities, fm^-3, [2, round(BOX / SPACING)]: row 0 neutrons, row 1 protons.
tau : ndarray[float64], fm^-5
The kinetic-energy densities, fm^-5, [2, round(BOX / SPACING)].
jso : ndarray[float64], fm^-4
The spin-orbit densities, fm^-4, [2, round(BOX / SPACING)].

Raises

vipyr.ParameterError
A ValueError: an argument outside its range, choices, rank or shape, or one of a group given without the rest; its .param names the argument.
TypeError
An argument of the wrong type, or an unknown keyword.
vipyr.VivBuildError
An ImportError: no C compiler, or the program's C does not build.
RuntimeError
The program stops with its own message (a combination it rejects, or no convergence where the program treats that as an error).

See also

  • vp.nuclear.shell_model : The lowest states of a nucleus in the nuclear shell model: Z valence protons and N valence neutrons outside a closed core, in one of the shipped spaces (the sd shell with USDB, the p shell with Cohen-Kurath), or in a .sps and .int pair read from disk.

Notes

The energy functional

The energy is the integral of a local density built from three densities of each kind of nucleon : the number density , the kinetic-energy density and the spin-orbit density . Without a subscript, and likewise for and :

The two terms are kept for SLy5 and SIII and dropped for SLy4 and SkM*, as each force was fitted (the j2 parameter). The last two terms are the direct Coulomb energy and its exchange in the Slater approximation. The factor is the one-body center-of-mass correction, which all four forces use; it multiplies in the energy and in the equations below.

The Hartree-Fock equations

For a spherical nucleus each orbital is times a spinor spherical harmonic, and making stationary under changes of the orbitals gives, for ,

with the effective mass , the central field , the spin-orbit field and the Coulomb field :

The occupied orbitals make the densities, , and , the sums running over the occupied of kind .

Discretization

The mesh is for , with at the origin and one step past the last point. The kinetic term is written in conservative form with at the midpoints (past the last point it is the free value), so each block is a symmetric tridiagonal matrix. Derivatives of the densities and fields are centered differences inside and second-order one-sided differences at the ends; integrals are the trapezoid rule; the direct Coulomb potential sums the charge inside and outside each mesh point.

Everything is second order in . Halving the spacing from 0.1 fm to 0.05 fm raises E/A by 0.014 to 0.019 MeV across the seven nuclei and four forces. For lead-208 with SLy4 the changes from 0.1 to 0.05 fm and from 0.05 to 0.025 fm are in the ratio 4.005, and the Richardson value from either pair agrees to MeV per nucleon. Run two spacings and extrapolate when the third decimal of E/A matters.

The default box is 15 fm, or fm when that is wider (18.6 fm for lead-208). Widening it to 30 fm changes E/A by less than MeV for calcium-48, tin-132 and lead-208. Orbitals within about 1 MeV of zero in the spectrum feel the box more than that.

Solving

The iteration starts from Woods-Saxon densities (radius fm, diffuseness 0.6 fm) and . Each pass computes the fields from the densities, builds the matrix of every block with , finds the occupied orbitals, builds new densities and mixes them with the old ones (Pulay's mixing by default, below). It stops when the energy changes by less than e_tol between passes (after at least four). With the defaults the seven nuclei above take 22 to 32 passes with each of the four forces (37 to 66 with mixing="linear").

The loop is lib/scf/loop.viv, with this mesh as its basis, and the mixing is the contract mix_densities of lib/scf/mix.viv. mixing="pulay" (Pulay's mixing, the default) and mixing="broyden" (Johnson's modified Broyden) combine the last history passes; history applies to those two only. mixing="linear" keeps a fraction mix of the new densities. For calcium-40 with SLy4 at e_tol=1e-10 linear mixing takes 40 passes, Pulay 32 and Broyden 24 (gallery/science/plot_scf_mixers.py). Stopped there, the three differ in by up to fm, because the energy settles before the densities do; at e_tol=1e-13 they agree to fm and MeV in E/A. At the defaults (e_tol=1e-9), oxygen-16 with SLy5 by Pulay's mixing is within MeV in E/A and fm in of the run to e_tol=1e-13; by linear mixing, MeV and fm.

The occupation is chosen by energy. In pass 1 every bound state of every block is found by Sturm-sequence bisection, and the protons (and neutrons) go into the lowest orbitals, to each. Later passes refine each occupied orbital by inverse iteration from its previous vector, then use Sturm counts to confirm that it is still the same state of its block and that no empty orbital has dropped below the highest occupied one. When either check fails the occupation is chosen again; refills counts those passes. Calcium-40's , the second orbital of its block, is found the same way as the first.

Limits

  • Spherical symmetry and closed shells only: no pairing and no deformation. A nucleus whose last proton or neutron orbital would be partly filled is refused with the name of that orbital. A closed subshell counts as closed: carbon-12 (six of each in and ) runs and converges, as a spherical nucleus.
  • Bound orbitals only: a nucleus whose occupied orbitals reach zero energy (past a drip line) is refused.
  • Blocks up to and at most four occupied orbitals per block, which covers lead-208 (its protons are the third of their block). spectrum lists bound orbitals within those bounds.
  • The charge radius folds the proton density with a Gaussian of range 0.65 fm, , as skyrme_rpa does; it has no neutron or spin-orbit contributions to the charge density.
  • The constants are skyrme_rpa's: MeV fm, the nucleon mass the average of 938.27231 and 939.56563 MeV, and MeV fm.

Verification

  • Against an independent numpy solver (the reference below, its constants set to these) for all 28 pairs of nucleus and force on meshes of 0.1 and 0.05 fm: E/A agrees to MeV, the radii to fm and the occupied levels to MeV, in the same number of passes, both mixing linearly. That solver diagonalizes each block densely and fills shells in a prescribed order, so the agreement also checks the filling by energy.
  • Oxygen-16 with SLy5 and mixing="linear" gives the bits of the oxygen-16 solver in lib/skyrme/skyrme.viv. Extrapolated to it gives E/A = −8.0250 MeV and = 2.7993 fm, against −8.0261 MeV and 2.7991 fm in the sample output of skyrme_rpa (Colò et al. 2013).
  • The exported library, a native build and Viv's interpreter give the same result bits for every case in skyrmehf.cases.json.

References

  1. D. Vautherin and D. M. Brink, Hartree-Fock calculations with Skyrme's interaction. I. Spherical nuclei, Phys. Rev. C 5, 626 (1972). doi:10.1103/PhysRevC.5.626
  2. E. Chabanat, P. Bonche, P. Haensel, J. Meyer and R. Schaeffer, A Skyrme parametrization from subnuclear to neutron star densities. Part II. Nuclei far from stabilities, Nucl. Phys. A 635, 231 (1998); erratum Nucl. Phys. A 643, 441 (1998). SLy4 and SLy5. doi:10.1016/S0375-9474(98)00180-8
  3. M. Beiner, H. Flocard, Nguyen Van Giai and P. Quentin, Nuclear ground-state properties and self-consistent calculations with the Skyrme interaction. (I). Spherical description, Nucl. Phys. A 238, 29 (1975). SIII. doi:10.1016/0375-9474(75)90338-3
  4. J. Bartel, P. Quentin, M. Brack, C. Guet and H.-B. Håkansson, Towards a better parametrisation of Skyrme-like effective forces: a critical study of the SkM force, Nucl. Phys. A 386, 79 (1982). SkM*. doi:10.1016/0375-9474(82)90403-1
  5. G. Colò, L. Cao, N. Van Giai and L. Capelli, Self-consistent RPA calculations with Skyrme-type interactions: The skyrme_rpa program, Comput. Phys. Commun. 184, 142 (2013). The constants and the charge folding used here, and the oxygen-16 check. doi:10.1016/j.cpc.2012.07.016

Examples

import vipyr as vp
r = vp.nuclear.skyrme_hf()             # oxygen-16 with SLy5
print(r.e_per_a, r.r_ch, r.levels)
assert r.check()
-8.046538617871763 2.7966837276110867 [-36.18659981 -20.63141616 -14.42364525 -32.38991873 -17.15296823
 -11.074404  ]

Calcium-40 with SLy4: the energy per nucleon, the charge radius and the occupied orbitals. Its orbitals are the second state of the block.

import vipyr as vp

r = vp.nuclear.skyrme_hf(nucleus="40Ca", force="SLy4")
print(f"E/A = {r.e_per_a:.4f} MeV, r_ch = {r.r_ch:.4f} fm, {r.iterations} passes")
for label, e in zip(r.labels.split(","), r.levels):
    print(f"{label:9s} {e:8.3f}")
# E/A = -8.6293 MeV, r_ch = 3.5087 fm, 27 passes
# n 1s1/2    -48.337
# ...
# n 2s1/2    -17.272
# n 1d3/2    -15.313
# p 1s1/2    -40.323
# ...
# p 1d3/2     -8.359
E/A = -8.6293 MeV, r_ch = 3.5087 fm, 27 passes
n 1s1/2    -48.337
n 1p3/2    -35.048
n 1p1/2    -31.127
n 1d5/2    -22.035
n 2s1/2    -17.272
n 1d3/2    -15.313
p 1s1/2    -40.323
p 1p3/2    -27.518
p 1p1/2    -23.683
p 1d5/2    -14.903
p 2s1/2    -10.172
p 1d3/2     -8.359

Lead-208 with the four forces, extrapolated to zero mesh spacing. The error of each run goes as , so removes it. The measured values are E/A = −7.867 MeV and = 5.501 fm.

import vipyr as vp

for force in ["SLy4", "SLy5", "SIII", "SkM*"]:
    a = vp.nuclear.skyrme_hf(nucleus="208Pb", force=force, spacing=0.1)
    b = vp.nuclear.skyrme_hf(nucleus="208Pb", force=force, spacing=0.05)
    e = (4 * b.e_per_a - a.e_per_a) / 3
    rch = (4 * b.r_ch - a.r_ch) / 3
    print(f"{force:5s} E/A {e:8.4f} MeV   r_ch {rch:.4f} fm   r_n - r_p {b.r_n - b.r_p:.3f} fm")
# SLy4  E/A  -7.8640 MeV   r_ch 5.5154 fm   r_n - r_p 0.160 fm
# SLy5  E/A  -7.8662 MeV   r_ch 5.5073 fm   r_n - r_p 0.162 fm
# SIII  E/A  -7.8395 MeV   r_ch 5.5813 fm   r_n - r_p 0.125 fm
# SkM*  E/A  -7.8667 MeV   r_ch 5.5093 fm   r_n - r_p 0.169 fm
SLy4  E/A  -7.8640 MeV   r_ch 5.5154 fm   r_n - r_p 0.160 fm
SLy5  E/A  -7.8662 MeV   r_ch 5.5073 fm   r_n - r_p 0.162 fm
SIII  E/A  -7.8395 MeV   r_ch 5.5813 fm   r_n - r_p 0.125 fm
SkM*  E/A  -7.8667 MeV   r_ch 5.5093 fm   r_n - r_p 0.169 fm

The neutron orbitals of lead-208 around the gap at N = 126, occupied and empty, from spectrum.

import vipyr as vp

r = vp.nuclear.skyrme_hf(nucleus="208Pb", force="SkM*")
for label, e, occ in zip(r.spectrum_labels.split(","), r.spectrum, r.spectrum_occ):
    if label.startswith("n") and -12.0 < e < -2.0:
        print(f"{label[2:]:7s} {e:8.3f} MeV  {'occupied' if occ else 'empty'}")
# 2f7/2    -11.913 MeV  occupied
# 1h9/2    -11.414 MeV  occupied
# 1i13/2   -10.233 MeV  occupied
# 3p3/2     -9.195 MeV  occupied
# 2f5/2     -8.892 MeV  occupied
# 3p1/2     -8.082 MeV  occupied
# 2g9/2     -4.186 MeV  empty
# 1j15/2    -2.332 MeV  empty
2f7/2    -11.913 MeV  occupied
1h9/2    -11.414 MeV  occupied
1i13/2   -10.233 MeV  occupied
3p3/2     -9.195 MeV  occupied
2f5/2     -8.892 MeV  occupied
3p1/2     -8.082 MeV  occupied
2g9/2     -4.186 MeV  empty
1j15/2    -2.332 MeV  empty

Each example ran as written when these pages were generated, and the output shown is what it printed.

Checked against

Reference
lib/skyrme/refs/skyrme_hf.py (the personal site's numpy solver, constants set to skyrme_rpa's) for every nucleus and force on two meshes (lib/skyrme/refs/compare.py, lib/skyrme/COMPARISON.md); the post's numpy twin of oxygen-16 and skyrme_rpa (Colò, Cao, Van Giai and Capelli, CPC 184, 142 (2013)) for oxygen-16 with SLy5 (tools/export/test_skyrmehf.py)
Cases
oxygen-16 with SLy5 at the defaults, and six more nuclei with the four forces at E_TOL 10−10 with linear mixing, as the numpy solver mixes (skyrmehf.cases.json)
Tolerance
10−9 against the numpy solver at the same mesh (observed 2 × 10−11 or less: the two eigensolvers' round-off)

Source