SBD bindings (sbd)¶
Direct access to SBD’s two diagonalization methods. TPB (tensor-product basis)
diagonalizes in the product of an alpha and a beta determinant list. GDB
(general determinant basis) diagonalizes in an explicit list of determinants, and is
experimental. For use inside a qiskit-addon-sqd loop, see sbd.sbd_solver
instead.
The backend initializes itself on first use, so init() is needed only to choose
a device or communicator explicitly.
Diagonalization¶
- tpb_diag(fcidump, adet, bdet, sbd_data, loadname='', savename='', device=None)[source]¶
Perform TPB diagonalization with data structures.
- Parameters:
fcidump – FCIDump object.
adet – Alpha determinants.
bdet – Beta determinants.
sbd_data – TPB_SBD configuration object.
loadname – Path prefix to load an initial wavefunction from (optional).
savename – Path prefix to save the final wavefunction to (optional). See the note below: for reading amplitudes back,
sbd_data.dump_matrix_form_wfis usually the one you want.device – Backend to run on: ‘cpu’, ‘gpu’ (Thrust), ‘gpu-omp’ (OpenMP offload), or an alias accepted by
init(). None uses the default; seeget_backend().
- Returns:
energy, density, carryover_adet, carryover_bdet, one_p_rdm, two_p_rdm.
- Return type:
dict with keys
Note
The returned dict holds no wavefunction amplitudes. There are two ways to get them, and they write different formats – do not mix them up:
sbd_data.dump_matrix_form_wf = "wf.bin"writes the fulllen(adet) x len(bdet)subspace, gathered onto one rank, as a bare row-major array offloat64with no header – sonp.fromfile(path).reshape(len(adet), len(bdet))reads it. Naming it.dator.txtinstead writes a text table in which every amplitude is labelled with its own alpha and beta bitstring, which is the easiest form to inspect by hand. This is the mechanism this package’s own SQD integration uses.savenameinstead writes one file per b_comm position,f"{savename}{rank_b:06d}.bin", each holding only that rank’s block of the subspace: threesize_theaders (adet_range,bdet_range,det_length), then that block’s alpha and then beta determinant words, thenadet_range x bdet_rangefloat64amplitudes. It pairs withloadnamefor restarting a run, so it is the better choice for checkpointing and the worse one for analysis.Amplitudes are ordered by
adet/bdetas the diagonalizer saw them.sort_bitarrayremoves duplicates as well as sorting, so pass lists already in that form if you need to label the rows and columns yourself.Both files use SBD’s sign convention, which orders a determinant’s creation operators orbital by orbital (alpha 0, beta 0, alpha 1, …). PySCF, and so qiskit-addon-sqd’s
SCIState, puts all alpha operators before all beta ones, so some determinants differ in sign: multiply each amplitude by(-1) ** n, wherencounts, over the occupied beta orbitalsp, the occupied alpha orbitals abovep, before handing it to PySCF. Magnitudes, and so selection by weight, are the same either way.sbd.sbd_solver.solve_sci()converts the amplitudes in itsSCIStatealready.
- tpb_diag_from_files(fcidumpfile, adetfile, sbd_data, loadname='', savename='', device=None)[source]¶
Perform TPB diagonalization from files.
The one determinant file supplies both the alpha and the beta list, so the subspace is the product of that list with itself. For different alpha and beta lists, as an open-shell case needs, load them and call
tpb_diag().- Parameters:
fcidumpfile – Path to FCIDUMP file.
adetfile – Path to the determinant file, used for both alpha and beta.
sbd_data – TPB_SBD configuration object.
loadname – Path prefix to load an initial wavefunction from (optional).
savename – Path prefix to save the final wavefunction to (optional).
device – Backend to run on: ‘cpu’, ‘gpu’ (Thrust), ‘gpu-omp’ (OpenMP offload), or an alias accepted by
init(). None uses the default; seeget_backend().
- Returns:
energy, density, carryover_adet, carryover_bdet, one_p_rdm, two_p_rdm.
- Return type:
dict with keys
Note
As with
tpb_diag(), no amplitudes are returned in the dict; see that function for the two ways of writing them to disk.
- gdb_diag(fcidump, det, sbd_data, loadname='', savename='', device=None, determinant_distribution='', determinant_grid_a=0, determinant_grid_b=0)[source]¶
Perform GDB diagonalization over an explicit list of determinants.
Unlike TPB, which spans the subspace with the Cartesian product of an alpha and a beta determinant list, GDB spans it with the given determinants themselves, so an arbitrary sparse subspace can be diagonalized.
Each determinant is a
2 * norb-bit configuration packed into words ofsbd_data.bit_lengthbits. Bit2 * iis the occupation of spin-alpha orbitaliand bit2 * i + 1that of spin-beta orbitali.from_strings()packs a list of bitstrings into the expected(ndets, words)array; a nested list is accepted and converted.The shard contract.
sbd_data.b_comm_sizedecides whatdetmeans:1— every rank passes the whole basis.> 1— every rank passes its own shard, and the union over b_comm positions is the basis. This is the only way GDB’s memory scales, because b_comm is the only dimension that divides the basis (and with it the excitation lookup); the derived helper dimension divides work but not storage.
The shard index is
rank % b_comm_size, and ranks sharing one must pass identical determinants — the in-memory path does not broadcast the list. Shards must be globally sorted and disjoint: shardistrictly below shardi + 1. All of this is checked, and a violation raises rather than silently diagonalizing the wrong subspace. What cannot be checked is completeness — that the union is the basis you meant — so compare the returnedglobal_dimagainst what you expect.t_comm_sizemust not exceedb_comm_size: GDB runs one task per basis-ring station and there are exactlyb_comm_sizeof them.t_comm_size * b_comm_sizemust divide the rank count exactly. On the Thrust backend the helper dimension must be 1, i.e. every rank goes tot_comm_size * b_comm_size.- Parameters:
fcidump – FCIDump object.
det – Determinants spanning the subspace, or this rank’s shard of them, as an
(ndets, words)array. Must be distinct.sbd_data – GDB_SBD configuration object.
methodmust be 0 or 1 — GDB implements Davidson only, so TPB’s Lanczos methods 2 and 3 do not exist here and are rejected.loadname – Path to load an initial wavefunction (optional).
savename – Path prefix to save the final wavefunction to (optional). SBD writes one file per b_comm position,
f"{savename}{rank_b:06d}.bin", each holding only that shard; rank 0’s file is not the whole wavefunction. GDB has no combined matrix-form dump.device – Backend to run on: ‘cpu’, ‘gpu’ (Thrust), ‘gpu-omp’ (OpenMP offload), or an alias accepted by
init(). None uses the default; seeget_backend(). ‘gpu-omp’ is accepted, but SBD has no OpenMP-offload GDB code, so the run executes on the host; use ‘gpu’ for GDB on GPUs.determinant_distribution – How to place determinants across b_comm — one of
input(keep the shards as given),equal-bra-a(default; equal distinct-alpha count per rank, which is what balances the matvec),count,count-sorted,grid-cyclicorgrid-cyclic-balanced. Ignored whenb_comm_sizeis 1.determinant_grid_a – Grid rows for the grid-cyclic schemes. With
determinant_grid_b, must multiply tob_comm_size; give both or neither, and the default is the factor pair nearest square.determinant_grid_b – Grid columns; see
determinant_grid_a.
- Returns:
dict with keys
energy,density,carryover_det,one_p_rdm,two_p_rdm,local_dim,global_dimanddeterminant_distribution. What is replicated and what is sharded:energy— replicated, bit-identical on every rank.density,one_p_rdm,two_p_rdm— replicated on every rank.carryover_det— this rank’s shard, as an(n, words)array, not the whole list. Forcarryover_type1 it is split over b_comm and duplicated across the helper dimension, so gathering means taking one representative perrank % b_comm_size. For types 2 and 3 it is split over the world communicator with no duplication, so a plain allgather is correct; those types return the parents together with the new candidates, i.e. the next subspace rather than only the additions.local_dim/global_dim— determinants on this rank, and summed over b_comm.
Configuration¶
- TPB_SBD(device=None)[source]¶
Create a TPB_SBD configuration object, for
tpb_diag().Its fields are listed in the tables below.
- GDB_SBD(device=None)[source]¶
Create a GDB_SBD configuration object, for
gdb_diag().Its fields are listed in the tables below.
Both return a configuration object whose fields are set as attributes before it is
passed to a diagonalization, for example config.eps = 1e-8. The defaults are
upstream SBD’s; sbd.sbd_solver.solve_sci() and
solve_sci_batch() set their own unless overridden through
sbd_config.
Fields shared by TPB_SBD and GDB_SBD:
Field |
Meaning |
|---|---|
|
|
|
Davidson stopping tolerance on the residual norm, not on the energy. |
|
Maximum Davidson iterations. Reaching it before |
|
Maximum number of Davidson basis vectors. |
|
Wall-clock limit, in seconds. |
|
Starting vector: |
|
Seed for the random starting vector; used only with |
|
|
|
How SBD selects determinants for a next iteration. |
|
Carryover selection parameters. Which of the two a given |
|
Bits per word when packing determinants. At most 63; 64 is undefined
behavior in SBD’s multi-rank redistribution. For GDB it must also be even
whenever a determinant spans more than one word: an odd value breaks SBD’s
alpha/beta conversion, which can crash or return a wrong energy, in plain
diagonalization as well as in the heatbath expansion. The |
Fields of TPB_SBD only:
Field |
Meaning |
|---|---|
|
MPI ranks spanning the alpha and beta determinant dimensions. |
|
MPI ranks spanning task-level parallelism. The rank count must be a multiple of
|
|
|
|
Shuffle the determinants loaded from file before use. |
|
Path to which SBD writes the wavefunction as an |
|
Thrust backend only. Precompute a determinant index for the whole subspace on the GPU: faster, but far more memory. |
|
Thrust backend only, and used only when |
Fields of GDB_SBD only:
Field |
Meaning |
|---|---|
|
MPI ranks the determinant basis is sharded across. It is the only dimension
that divides memory. With more than 1, each rank passes only its own shard to
|
|
MPI ranks spanning tasks. Must not exceed |
|
|
|
Heatbath selection cutoff: a candidate is added when |
|
Weight below which parents are dropped before expansion. Large values remove the parents the expansion needs. |
|
Number of candidates processed per batch during expansion. |
Inputs¶
- LoadFCIDump(filename, device=None)[source]¶
Load an FCIDUMP file.
- Parameters:
filename – Path to the FCIDUMP file.
device – Backend whose
FCIDumptype to build; None uses the default.
- Returns:
The
FCIDumpobject thattpb_diag()andgdb_diag()take.
- LoadAlphaDets(filename, bit_length, total_bit_length, device=None)[source]¶
Load determinants from a file of bitstrings, one per line.
Each line is packed as
from_strings()does. Upstream sorts the determinants and removes duplicates, so the result is ready to use as a TPB determinant list and may hold fewer determinants than the file has lines.The format is chosen by extension:
.txtfor one bitstring per line,.binfor upstream’s binary format. Any other extension returns an empty list without an error.- Parameters:
filename – Path to the file.
bit_length – Bits per packed word. Must match the config’s
bit_length.total_bit_length – Length of each bitstring,
norbfor the alpha or beta strings of TPB.device – Backend to use; None uses the default.
- Returns:
A list of packed determinants, each a list of words, sorted and distinct.
- sort_bitarray(dets, device=None)[source]¶
Sort determinants into canonical order, removing duplicates.
Diagonalization requires its determinant lists to be in this order.
gdb_diag()sorts its own input, so this reproduces the order it works in.
- from_strings(strings, bit_length, total_bit_length, device=None)[source]¶
Pack many bitstrings into one
(n, words)array.The bulk form of
sbd.from_string, looping in C++ instead of once per determinant across the Python boundary. Prefer it for anything larger than a handful: the per-call form costs tens of thousands of round trips on the vendored inputs alone.Each row holds
total_bit_lengthbits packedbit_lengthto a word, so a bitstring longer thanbit_lengthoccupies more than one word. The packing runs from the right end of the string: its trailingbit_lengthcharacters become word 0, the nextbit_lengthto the left become word 1, and so on, so the leading characters of a multi-word string land in the last word rather than the first:from_strings(["0011"], 20, 4) # -> array([[3]], dtype=uint64) from_strings(["1" + "0" * 69], 62, 70) # -> array([[ 0, 128]], ...) from_strings(["0" * 69 + "1"], 62, 70) # -> array([[1, 0]], ...)
Within a word the rightmost character is the least significant bit, which is why
"0011"packs to3rather than12. Getting this backwards produces a valid-looking but wrong subspace rather than an error, so check round trips withsbd.makestringwhen building determinants by hand.bit_lengthmust match thebit_lengthof the config passed to the diagonalizers, since it sets how many words each determinant occupies.
- sort_bitarray_array(dets, device=None)[source]¶
Sort packed determinants into canonical order, array in and array out.
The array form of
sort_bitarray(), for a determinant list held as an(n, words)array. Deduplicates, like the list form.
Backends and devices¶
- init(device='cpu', comm_backend='mpi')[source]¶
Initialize SBD with MPI and set the default compute device.
Calling
init()explicitly is optional — SBD auto-initializes on first use withdevice='cpu'andcomm_backend='mpi'. Call it explicitly only when you need GPU or want to control startup timing.The device can be overridden per call via the
deviceparameter oftpb_diag(),tpb_diag_from_files(),gdb_diag()andget_backend().- Parameters:
device – Default compute device — ‘cpu’, ‘gpu’, ‘gpu-omp’, or ‘auto’. ‘gpu’ is the NVIDIA-only Thrust backend; ‘gpu-omp’ is OpenMP target offload and serves NVIDIA and AMD alike. Aliases for ‘gpu’: ‘gpu-thrust’, ‘gpu-nvidia’, ‘cuda’. Aliases for ‘gpu-omp’: ‘gpu-omp-offload’, ‘gpu-nvhpc-omp’, ‘gpu-nvidia-omp’, ‘gpu-amd-omp’, ‘gpu-rocm-omp’, ‘rocm’.
comm_backend – Communication backend — ‘mpi’.
- Raises:
RuntimeError – If MPI is not available or no backends are compiled.
- finalize()[source]¶
Finalize SBD and reset session state.
Synchronizes GPU (if used) but does NOT call MPI_Finalize — mpi4py handles MPI lifecycle automatically.
After finalize(), init() can be called again.
- available_backends()[source]¶
Devices whose extension is built and structurally loadable.
Each candidate .so is inspected statically – present on disk, no unresolved shared-library dependencies, matching architecture – without importing it. Importing would run MPI_Init, which hangs on an MPI lacking PMIx support when the process was not started under a launcher.
A listed backend is therefore “present and structurally sound”, not “guaranteed to load”: an ABI/symbol mismatch only shows up on a real dlopen, and surfaces from get_backend(). Reasons for anything excluded are in backend_load_errors(); a backend that is merely not built is recorded there but does not warn, since building a subset is normal.
- backend_load_errors()[source]¶
Why each unusable backend is unusable.
Maps device -> reason, distinguishing “not built” from a missing shared library, an architecture mismatch, or a failed import.
- get_backend(device=None)[source]¶
Get the backend module for the given device.
Auto-initializes SBD if needed. Passing
deviceoverrides the default, which is how you switch backends within one process. The one combination to avoid is the CPU backend together with the OpenMP-offload one (‘gpu-omp’): they share an OpenMP runtime, and loading both leaves offload regions running on the host.sbd.has_backend_conflict()reports it.- Parameters:
device – ‘cpu’, ‘gpu’ (Thrust), ‘gpu-omp’ (OpenMP offload), ‘auto’, an alias accepted by
init(), or None. None means the device set byinit(). If nothing has initialized yet it means ‘auto’, which prefers Thrust, then OpenMP offload, then CPU, whereas the first call oftpb_diag()orgdb_diag()initializes with ‘cpu’. Callinit()with the device you want to avoid depending on call order.- Returns:
The pybind11 backend module (_core_cpu, _core_gpu_thrust, or _core_gpu_omp_offload).
- get_device_id(device=None)[source]¶
Get the device index this rank will use, or -1 if there is none.
Reports what the backend’s own rank-to-device rule yields rather than recomputing it here, so a caller putting another library on the same card cannot drift out of step with SBD. Selects nothing and creates no context, so it is safe to call before any GPU work.
Uses the communicator rank, i.e. the same
gpu_id = rank % num_gpusconvention SBD’s own diagonalization applies, documented inexamples/tpb/README.md.