Commit 0e17609
DeePKS: support collinear spin (nspin=2) (#7433)
* deepks(nspin2): add magnetization-channel data members
Add charge/magnetization data members for collinear nspin=2 DeePKS without
changing the nspin=1 path:
* dm_r_mag : (rho_up - rho_dn) real-space density matrix
* pdm_mag : magnetization projected density matrix
* gedm_mag : dE/d(pdm_mag)
Allocated and freed only when nspin==2; for nspin==1 they stay null/empty and
the existing dm_r/pdm/gedm behave exactly as before. Scaffolding only, no
physics yet. tests/09_DeePKS: 284/284.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* deepks(nspin2): compute charge/magnetization channels, feed 2-channel model
Traditional (non-equivariant) nspin=2 collinear DeePKS now computes both the
charge and magnetization channels and evaluates a 2-channel model:
* update_dmr gains nspin/mag options; setup_deepks fills dm_r_mag = rho_up - rho_dn
* the operator builds pdm_mag and descriptor_mag, stacks charge+magnetization
descriptors into model input (1, nat, 2, des), and autograds in one pass to
fill gedm = dE/d(pdm) and gedm_mag = dE/d(pdm_mag)
The equivariant version stays a separate top-level branch; all magnetization
allocation/use is gated on nspin==2 && !deepks_equiv. The correction is not yet
applied per spin to the Hamiltonian (next step), so nspin=1 is unchanged.
tests/09_DeePKS: 284/284.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* deepks(nspin2): apply per-spin V_delta and forces/stress
SCF correction: calculate_HR takes the gedm to assemble from; for nspin=2
(traditional) the operator rebuilds V_delta_R per spin as
|alpha>(gedm +/- gedm_mag)<alpha| and toggles current_spin like Veff. The
label/output interface now feeds the 2-channel model for nspin=2 (previously
crashed a real 2-channel model post-SCF).
Forces/stress: the single live path is getForceStress -> cal_f_delta
(integral_part/ftable is dead code). cal_f_delta gains an optional
magnetization channel (dmr_mag, gedm_mag) and adds its contribution in the same
pass, so F_delta = dm_r * nlm(gedm) + dm_r_mag * nlm(gedm_mag) with correct
single symmetrize+weight on the stress.
Validation (synthetic 2-channel model):
* non-magnetic CH4: nspin=2 == nspin=1 total energy to 2.5e-9 eV, forces to ~1e-5
* polarized CH4 (nupdown=2): finite-difference force matches analytic to 0.05%
nspin=1 tests/09_DeePKS: 284/284 throughout. nspin=1 path unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* deepks(nspin2): write the dm_eig descriptor label with the spin channel
For nspin=2 (traditional), the interface now builds the magnetization-channel
descriptor before saving labels and reuses it for the 2-channel model. save_npy_d
gains an optional descriptor_mag: when present it writes dm_eig as (nat, 2, des)
- channel 0 = charge, channel 1 = magnetization - matching the 2-channel model
input, so deepks-kit can train on nspin=2 descriptors. nspin=1 writes (nat, des)
as before.
Validated: nspin=2 deepks_out_labels=1 on CH4 produces deepks_dm_eig.npy of
shape (5, 2, 18). nspin=1 tests/09_DeePKS: 284/284.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* deepks(nspin2): spin channel for precalc labels and bandgap
For nspin=2 (traditional), every precalc/output label now carries the
charge/magnetization channel (channel 0 = charge, 1 = magnetization), inserted
before the descriptor dimension to match dm_eig (nat, 2, des):
* gevdm computed for both pdm and pdm_mag
* gradvx (force) and gvepsl (stress): gdmx/gdmepsl built from dm_r_mag, then
gvx/gvepsl per channel, stacked
* orbpre (bandgap): orbital_precalc per channel, stacked (cal_o_delta already
sums spin)
* vdpre / vdrpre (v_delta precalc) and gevdm export: per-channel + stacked
The precalc kernels are unchanged (channel-agnostic); only the interface calls
them per channel and stacks. deepks_spin2 hoisted to function scope.
Validated (synthetic 2-channel model, nupdown=2): label shapes gain the size-2
channel dim (dm_eig (5,2,18), gradvx (5,3,5,2,18), gvepsl (6,5,2,18),
orbpre (2,5,2,18), vdpre (2,33,33,5,2,18)); nspin=1 shapes unchanged.
tests/09_DeePKS (nspin=1): 284/284.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(deepks): add nspin=2 integration tests (29-31)
Add spin-polarized DeePKS integration tests under tests/09_DeePKS, using a
small synthetic 2-channel (charge + magnetization) demo model committed as
Model_ProjOrb/model_nspin2_demo.ptg:
* 29_NO_GO_deepks_scf_nspin2 : gamma SCF + force + stress (net moment)
* 30_NO_KP_deepks_scf_nspin2 : multi-k SCF (net moment)
* 31_NO_GO_deepks_bandgap_nspin2 : output labels (all precalc) + bandgap
All use nupdown to give a real moment so the magnetization channel is active.
tests/09_DeePKS now passes 313/313 (28 original + 3 new) under the standard
np=4 harness.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* deepks(nspin2): guard precalc/bandgap mag-channel stacks on rank 0
The cal_gvx/cal_gvepsl/cal_orbital_precalc/cal_v_delta_precalc helpers
assemble their output tensor only on the root rank; on other ranks the
returned tensor is undefined. Stacking the charge and magnetization
channels unconditionally therefore aborts on non-root ranks with
"tensor does not have a device". Restrict each two-channel torch::stack
to rank 0, matching where the labels are actually written.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* deepks(nspin2): guard undefined autograd grads and fix indentation
torch::autograd::grad(..., allow_unused=true) returns an undefined
gradient for any PDM block the model output does not depend on, which is
now reachable because the magnetization-channel PDM blocks are appended
to grad_inputs. Calling .accessor() on such a tensor aborts; check
.defined() and write zeros for missing gradients before accessing.
Also re-indent the deepks_v_delta == -2 magnetization block to match the
surrounding scope.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* deepks(nspin2): make _mag parameters adjacent and type-consistent
Group each magnetization-channel argument directly after its charge
counterpart and pass it the same way as the base, so the paired
quantities stay together and similarly-named parameters no longer use
different calling conventions (which was easy to misuse):
cal_edelta_gedm: descriptor/descriptor_mag, pdm/pdm_mag, gedm/gedm_mag
cal_f_delta: dmr/dmr_mag, gedm/gedm_mag
save_npy_d: descriptor/descriptor_mag
descriptor_mag and pdm_mag now take const& like their bases; the
two-channel path is selected by descriptor_mag being non-empty. As the
paired arguments are no longer trailing they carry no defaults, so the
nspin=1 / equivariant call sites pass empty containers and nullptr
explicitly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* deepks(nspin2): make _mag arguments a trailing optional group
Move the magnetization-channel arguments into a single optional group at
the end of each signature, with their charge counterparts grouped
immediately before. The _mag group carries defaults (empty container /
nullptr) so nspin=1 and equivariant call sites simply omit them instead
of passing explicit empty/null placeholders.
cal_edelta_gedm(..., descriptor, pdm, gedm, descriptor_mag, pdm_mag, gedm_mag)
cal_f_delta(..., dmr, gedm, dmr_mag, gedm_mag)
save_npy_d(..., descriptor, descriptor_mag)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* deepks(nspin2): apply clang-format to changed lines
No functional change; bring the nspin=2 additions into line with the
project .clang-format (Microsoft style, one argument per line, expanded
single-line braces).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* deepks(nspin2): drop per-call gedm buffer, clarify contributeHR
Two improvements to the nspin=2 path in contributeHR, no functional
change:
- calculate_HR now folds the per-spin combination gedm + sigma*gedm_mag
into the small per-atom gedm copy it already builds, taking the
magnetization channel and sign as optional arguments. This removes the
inlmax x pdm_size temporary buffer (and its inlmax per-call heap
allocations) that was rebuilt on every contributeHR call during SCF.
- The magnetization-channel cal_pdm/cal_descriptor is hoisted into its
own block parallel to the charge-channel preparation, so the flow reads
prepare-charge -> prepare-mag (nspin=2) -> evaluate-model, and the
traditional model evaluation is a single cal_edelta_gedm call (an empty
descriptor_mag selects the single-channel path for nspin=1).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* deepks: bracket whole contributeHR with its timer
The DeePKS contributeHR timer was started and stopped inside the
get_hr_cal() guard. After the nspin=2 per-spin V_delta_R build was moved
out of that guard, the timer ended before that work ran (and was not
started at all on the second-spin call). Start the timer at function
entry and stop it at the end so it brackets all work in every call,
matching Veff::contributeHR.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(deepks): loosen tolerance for nspin=2 bandgap precalc case
The deepks_fpre/spre labels in case 31 are large-magnitude sums (~113,
~47) whose last digits vary at the ~1e-7 level run to run, due to the
nondeterministic OpenMP critical accumulation in calculate_HR. That
occasionally exceeds the default 1e-7 tolerance. Add a per-case
threshold of 1e-5; the small-magnitude labels (edelta, deltas) are
bit-stable and remain tightly checked, and a real regression shifts
them far more than 1e-5.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>1 parent eb4ddf7 commit 0e17609
36 files changed
Lines changed: 752 additions & 91 deletions
File tree
- source/source_lcao
- module_deepks
- test
- module_operator_lcao
- tests/09_DeePKS
- 29_NO_GO_deepks_scf_nspin2
- 30_NO_KP_deepks_scf_nspin2
- 31_NO_GO_deepks_bandgap_nspin2
- Model_ProjOrb
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
276 | 276 | | |
277 | 277 | | |
278 | 278 | | |
279 | | - | |
280 | | - | |
281 | | - | |
282 | | - | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
283 | 295 | | |
284 | 296 | | |
285 | 297 | | |
286 | | - | |
287 | | - | |
288 | | - | |
289 | | - | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
290 | 314 | | |
291 | 315 | | |
292 | 316 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
217 | 217 | | |
218 | 218 | | |
219 | 219 | | |
220 | | - | |
221 | | - | |
| 220 | + | |
222 | 221 | | |
223 | 222 | | |
224 | 223 | | |
225 | 224 | | |
226 | 225 | | |
227 | 226 | | |
228 | 227 | | |
229 | | - | |
230 | 228 | | |
231 | 229 | | |
232 | | - | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
233 | 233 | | |
234 | 234 | | |
235 | 235 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
224 | 224 | | |
225 | 225 | | |
226 | 226 | | |
227 | | - | |
228 | | - | |
| 227 | + | |
229 | 228 | | |
230 | 229 | | |
231 | 230 | | |
232 | 231 | | |
233 | 232 | | |
234 | 233 | | |
235 | 234 | | |
236 | | - | |
237 | 235 | | |
238 | 236 | | |
239 | | - | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
240 | 240 | | |
241 | 241 | | |
242 | 242 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| 14 | + | |
14 | 15 | | |
15 | 16 | | |
16 | 17 | | |
| |||
33 | 34 | | |
34 | 35 | | |
35 | 36 | | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
36 | 47 | | |
37 | 48 | | |
38 | 49 | | |
| |||
124 | 135 | | |
125 | 136 | | |
126 | 137 | | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
127 | 147 | | |
128 | 148 | | |
129 | 149 | | |
| |||
202 | 222 | | |
203 | 223 | | |
204 | 224 | | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
205 | 235 | | |
206 | 236 | | |
207 | 237 | | |
| |||
249 | 279 | | |
250 | 280 | | |
251 | 281 | | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
252 | 287 | | |
253 | 288 | | |
254 | 289 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
79 | 79 | | |
80 | 80 | | |
81 | 81 | | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
82 | 87 | | |
83 | 88 | | |
84 | 89 | | |
| |||
0 commit comments