Repository navigation
Add constant-potential electron control to the i-PI socket driver #8136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
0770459
8bfcec6
6238fde
671dc26
e557b4f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| #ifndef ABACUS_ELECTRONIC_CONTROL_H | ||
| #define ABACUS_ELECTRONIC_CONTROL_H | ||
|
|
||
| namespace ModuleESolver | ||
| { | ||
| // Physical requests and results; no socket or serialization dependencies. | ||
| struct ElectronicControl | ||
| { | ||
| double nelect = 0.0; | ||
| }; | ||
|
|
||
| struct PotentialWindow | ||
| { | ||
| int axis = -1; | ||
| double minimum_A = 0.0; | ||
| double maximum_A = 0.0; | ||
| }; | ||
|
|
||
| struct ElectronicObservables | ||
| { | ||
| double nelect = 0.0; | ||
| double fermi_ry = 0.0; | ||
| double reference_ry = 0.0; | ||
| }; | ||
| } | ||
|
|
||
| #endif |
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would suggest moving this file to source_estate |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| #include "electronic_occupations.h" | ||
| #include "source_base/matrix.h" | ||
| #include "source_base/parallel_reduce.h" | ||
|
|
||
| #include <cmath> | ||
| #include <iomanip> | ||
| #include <sstream> | ||
| #include <stdexcept> | ||
|
|
||
| namespace ModuleESolver | ||
| { | ||
| double checked_occupation_electron_count(const ModuleBase::matrix& weights, | ||
| double requested_nelect, | ||
| int npool, | ||
| int ranks_per_pool) | ||
| { | ||
| int invalid = !std::isfinite(requested_nelect) || requested_nelect <= 0.0 | ||
| || npool <= 0 || ranks_per_pool <= 0; | ||
| double occupied = 0.0; | ||
| double compensation = 0.0; | ||
| for (int ik = 0; ik < weights.nr; ++ik) | ||
| { | ||
| for (int ib = 0; ib < weights.nc; ++ib) | ||
| { | ||
| const double weight = weights(ik, ib); | ||
| if (!std::isfinite(weight)) | ||
| { | ||
| invalid = 1; | ||
| continue; | ||
| } | ||
| // Compensated sum of the local k/spin and band shard. Generalized | ||
| // smearing weights may be negative; only their total is constrained. | ||
| const double corrected = weight - compensation; | ||
| const double next = occupied + corrected; | ||
| compensation = (next - occupied) - corrected; | ||
| occupied = next; | ||
| } | ||
| } | ||
| if (!std::isfinite(occupied)) { invalid = 1; } | ||
| // Synchronize failures before a rank can enter a different collective path. | ||
| Parallel_Reduce::reduce_all(invalid); | ||
| if (invalid) | ||
| { | ||
| throw std::runtime_error("constant-potential electron count requires finite occupations " | ||
| "and a valid target/occupation partition"); | ||
| } | ||
|
|
||
| // Same distribution as the native band-energy sum: remove the replicated | ||
| // copies inside each pool before combining KPAR and BNDPAR contributions. | ||
| Parallel_Reduce::reduce_double_allpool(npool, ranks_per_pool, occupied); | ||
| const double tolerance = 1.0e-8; | ||
| int mismatch = !std::isfinite(occupied) || occupied <= 0.0 | ||
| || std::abs(occupied - requested_nelect) > tolerance; | ||
| Parallel_Reduce::reduce_all(mismatch); | ||
| if (mismatch) | ||
| { | ||
| std::ostringstream message; | ||
| message << std::setprecision(17) | ||
| << "constant-potential occupation electron count mismatch: requested=" << requested_nelect | ||
| << ", occupied=" << occupied << ", absolute tolerance=" << tolerance; | ||
| throw std::runtime_error(message.str()); | ||
| } | ||
| return occupied; | ||
| } | ||
| } |
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. make this a free function |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| #ifndef ABACUS_ELECTRONIC_OCCUPATIONS_H | ||
| #define ABACUS_ELECTRONIC_OCCUPATIONS_H | ||
|
|
||
| namespace ModuleBase | ||
| { | ||
| class matrix; | ||
| } | ||
|
|
||
| namespace ModuleESolver | ||
| { | ||
| // weights already include k-point weights and spin degeneracy. Rows/bands are | ||
| // partitioned across npool groups and replicated on ranks_per_pool ranks. | ||
| // All ranks must participate; invalid occupations or a mismatch fail together. | ||
| double checked_occupation_electron_count(const ModuleBase::matrix& weights, | ||
| double requested_nelect, | ||
| int npool, | ||
| int ranks_per_pool); | ||
| } | ||
|
|
||
| #endif |
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would suggest moving this to source_relax |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| #include "esolver_fp.h" | ||
| #include "electronic_control.h" | ||
| #include "electronic_occupations.h" | ||
| #include "source_estate/param_update.h" | ||
| #include "source_estate/module_pot/electrostatic_potential.h" | ||
| #include "source_io/module_parameter/parameter.h" | ||
| #include "source_hamilt/module_xc/xc_functional.h" | ||
|
|
||
| #include <cmath> | ||
| #include <stdexcept> | ||
|
|
||
| namespace ModuleESolver | ||
| { | ||
| bool ESolver_FP::apply_electronic_control(Parameter& parameters, const ElectronicControl& control) | ||
| { | ||
| const auto& inp = parameters.inp; | ||
| if (inp.assume_isolated == "makov-payne") | ||
| { | ||
| throw std::runtime_error( | ||
| "constant-potential electron control is incompatible with the energy-only Makov-Payne correction"); | ||
| } | ||
| if (inp.esolver_type != "ksdft" || (inp.basis_type != "pw" && inp.basis_type != "lcao") | ||
| || inp.device != "cpu" || inp.precision != "double" || parameters.globalv.use_uspp | ||
| || inp.sc_mag_switch || inp.deepks_scf || inp.rpa || inp.rdmft || inp.dft_plus_dmft) | ||
|
Comment on lines
+22
to
+24
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] Reject energy-only Makov–Payne correction for electron control With a charged cubic cell and
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added the Makov–Payne check at the start of |
||
| { | ||
| throw std::runtime_error( | ||
| "constant-potential control requires CPU double-precision PW/LCAO KSDFT with " | ||
| "norm-conserving pseudopotentials; specialized solver corrections are not supported"); | ||
| } | ||
| const int functional_type = XC_Functional::get_func_type(); | ||
| if (functional_type < 1 || functional_type > 5) | ||
| { | ||
| throw std::runtime_error( | ||
| "constant-potential control requires a supported KS exchange-correlation functional"); | ||
| } | ||
| if (inp.dft_plus_u != 0 && inp.dft_plus_u != 1) | ||
| { | ||
| throw std::runtime_error( | ||
| "constant-potential forces require dft_plus_u=1; " | ||
| "the legacy dft_plus_u=2 force implementation is unavailable"); | ||
| } | ||
| if (inp.dft_plus_u && (inp.init_occ_mat < 0 || inp.init_occ_mat > 1 | ||
| || inp.yukawa_potential || parameters.globalv.uramping > 0.0)) | ||
| { | ||
| throw std::runtime_error( | ||
| "constant-potential DFT+U requires fixed Hubbard U and self-consistent occupations " | ||
| "(init_occ_mat=0/1, yukawa_potential=false, uramping disabled)"); | ||
| } | ||
| if (inp.init_chg == "dm_no_renormalize") | ||
| { throw std::runtime_error("electron-number control requires charge-density renormalization"); } | ||
| if (inp.nspin != 1 && inp.nspin != 2) | ||
| { throw std::runtime_error("constant-potential control supports only nspin=1 or 2"); } | ||
| if (inp.ocp || inp.nbands <= 0 || !std::isfinite(control.nelect) || control.nelect <= 0.0) | ||
| { throw std::runtime_error("invalid total electron number, band capacity, or fixed occupation array"); } | ||
| const bool two_fermi = parameters.globalv.two_fermi; | ||
| if (two_fermi && inp.nspin != 2) | ||
| { throw std::runtime_error("two_fermi electron control requires nspin=2"); } | ||
| const double up = 0.5 * (control.nelect + inp.nupdown); | ||
| const double down = 0.5 * (control.nelect - inp.nupdown); | ||
| if ((two_fermi && (!std::isfinite(inp.nupdown) || up <= 0.0 || down <= 0.0 | ||
| || up > inp.nbands || down > inp.nbands)) | ||
| || control.nelect > 2.0 * inp.nbands) | ||
| { | ||
| throw std::runtime_error( | ||
| "electron number exceeds band capacity or gives invalid spin populations; increase nbands"); | ||
| } | ||
| if (inp.smearing_method == "fixed" | ||
| && (std::abs(control.nelect - std::round(control.nelect)) > 1.0e-8 | ||
| || (two_fermi && (std::abs(up-std::round(up)) > 1.0e-8 || std::abs(down-std::round(down)) > 1.0e-8)))) | ||
| { throw std::runtime_error("fractional electron populations require a smearing occupation method"); } | ||
| if (!pelec || !pelec->charge || !pelec->pot) | ||
| { throw std::runtime_error("electronic state is not initialized for electron control"); } | ||
|
|
||
| elecstate::ParamUpdater::update_electron_count(control.nelect, parameters); | ||
| pelec->eferm.two_efermi = two_fermi; | ||
| pelec->init_nelec_spin(); | ||
| return true; | ||
| } | ||
|
|
||
| bool ESolver_FP::electronic_observables(const UnitCell& ucell, const Parameter& parameters, | ||
| const PotentialWindow& window, ElectronicObservables& result) const | ||
| { | ||
| if (!this->conv_esolver || !pelec || !pelec->charge || !pelec->pot) | ||
| { throw std::runtime_error("electronic observables require a converged SCF state"); } | ||
| const int npool = parameters.inp.kpar * parameters.inp.bndpar; | ||
| result.nelect = checked_occupation_electron_count(pelec->wg, parameters.inp.nelec, | ||
| npool, pw_rhod->poolnproc); | ||
| result.fermi_ry = pelec->eferm.ef; | ||
| if (parameters.globalv.two_fermi) | ||
| { | ||
| // The total-N derivative at fixed N_up-N_down is the mean spin chemical potential. | ||
| result.fermi_ry = 0.5 * (pelec->eferm.ef_up + pelec->eferm.ef_dw); | ||
| } | ||
| if (!std::isfinite(result.fermi_ry)) { throw std::runtime_error("non-finite SCF chemical potential"); } | ||
| if (window.axis >= 0) | ||
| { | ||
| const double* fixed = pw_rhod->nrxx > 0 ? pelec->pot->get_fixed_v() : nullptr; | ||
| const auto* pcc_potential = pelec->pot->pcc_electron_potential(); | ||
| const auto potential = elecstate::electrostatic_potential(ucell, *pw_rhod, *pelec->charge, | ||
| fixed, solvent, parameters.inp, pcc_potential); | ||
| result.reference_ry = elecstate::planar_potential_average(ucell, *pw_rhod, potential, window.axis, | ||
| window.minimum_A, window.maximum_A); | ||
| } | ||
| return true; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest moving this file to source_estate