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).
spectrumlists 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
- 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
- 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
- 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
- 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
- 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.359Lead-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 fmThe 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 emptyEach 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
- Manifest: lib/skyrme/skyrmehf.export.toml
- Viv: lib/skyrme/skyrmehf.viv
- The defs it reaches and the calls between them: map of the code
- Watch it run, line by line: /explore/walk/skyrme-o16