Loading...
Searching...
No Matches
factory.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * KohnShamSCFIteratorFactory — dispatch by basis_type and representation_type.
5 *
6 * Two creation paths:
7 * 1. create_kohn_sham_iterator(InputData, execution_policy) — production path.
8 * Performs all method-specific preparation (PP loading, crystal construction,
9 * symmetry, k-points) and transfers resource ownership into the iterator.
10 * The execution policy governs k-point distribution and pool-level reductions.
11 * 2. create_kohn_sham_iterator_from_config(Config) — test-only path with
12 * externally-owned resources.
13 */
14
15#include <memory>
16#include <stdexcept>
17#include <string>
18
19#include "io/input/data.hpp"
27
28namespace q3 {
29
30/**
31 * Create a Kohn-Sham iterator from a pre-built config struct.
32 *
33 * Test-only path: the caller owns the Crystal, KPointsGrid, and
34 * PseudopotentialSet and must keep them alive for the iterator's lifetime.
35 * Production code should use create_kohn_sham_iterator(InputData, policy) instead.
36 */
37inline std::unique_ptr<KohnShamSCFIterator>
39 if (config.basis_type == "planewave" && config.representation_type == "pseudopotential") {
40 return create_planewave_pseudopotential_iterator(std::move(config));
41 }
42 throw std::runtime_error("Unsupported basis_type/representation_type combination: " +
43 config.basis_type + "/" + config.representation_type);
44}
45
46/**
47 * Create a Kohn-Sham iterator from parsed InputData and an execution policy.
48 *
49 * Performs all method-specific preparation:
50 * 1. Loads pseudopotentials (patches species valence_charge)
51 * 2. Builds Crystal from input lattice/atom data
52 * 3. Detects symmetry
53 * 4. Generates and reduces k-points (global grid)
54 * 5. Calls execution_policy.local_kpoint_grid() to get this pool's subset
55 * 6. Dispatches NC vs US based on loaded PP types
56 *
57 * The returned iterator owns the Crystal, local KPointsGrid, and PseudopotentialSet.
58 *
59 * @param input Parsed input parameters.
60 * @param execution_policy Policy governing k-point distribution and pool reductions.
61 * Its lifetime must exceed the iterator's lifetime.
62 */
63inline std::unique_ptr<PlanewaveKohnShamSCFIterator>
65 const execution::PlanewaveExecutionPolicy& execution_policy) {
66 // 1. Load pseudopotentials and patch species valence_charge
67 std::vector<AtomSpecies> patched_species = input.species;
68 PseudopotentialSet pseudopotential_set =
70
71 /**
72 * 2. Build crystal with patched valence charges (shared builder — see
73 * io/input/system_builder.hpp, also used by the q3_core bindings)
74 */
75 Crystal crystal = build_crystal_from_input(input, patched_species);
76
77 // 3. Detect symmetry
79
80 /**
81 * 4. Generate global k-point grid (explicit list or MP generation, IBZ
82 * reduction, LSDA spin-channel weight scaling)
83 */
84 KPointsGrid global_kpoint_grid = build_global_kpoint_grid_from_input(input, crystal);
85
86 /**
87 * 5. Distribute k-points: each pool receives its local subset.
88 * In serial mode this is an identity operation (returns global_kpoint_grid unchanged).
89 * If the requested pool count exceeds the number of IBZ k-points, clamp to the
90 * k-point count and emit a warning. This clamp is Q3's own: an empty
91 * pool would skip wavefunction initialization while other pools call
92 * MPI_Allreduce inside reduce_vector, and the mismatched calls then pair
93 * up across the inter-pool communicator -- MPI_ERR_TRUNCATE at best.
94 * Quantum ESPRESSO does not clamp: it rejects only pool counts that
95 * exceed or do not divide the rank count, and hands surplus pools zero
96 * k-points with an informational message. Its k-point count bounds only
97 * the pool count it picks for itself when the user requested none.
98 */
99 {
100 int number_of_kpoints = global_kpoint_grid.number_of_kpoints();
101
102 /**
103 * With no pool count requested, choose one: the largest divisor of the
104 * rank count that does not exceed the k-point count, which gives every
105 * rank k-points to work on and leaves as few as possible sharing a pool.
106 *
107 * The previous default put every rank in a single pool, so extra ranks
108 * subdivided the FFT instead of taking k-points. That path has no
109 * measured speedup -- it is slower on the supercell it was benchmarked
110 * against -- and it disables gamma-only, stress, checkpointing and
111 * export. Nobody chose it; it was simply what happened when --pools was
112 * omitted.
113 */
114 if (!execution_policy.number_of_pools_was_requested()) {
115 int number_of_processes = execution_policy.number_of_processes();
116 int chosen_number_of_pools = 1;
117 for (int candidate = 1; candidate <= number_of_processes; candidate++) {
118 if (number_of_processes % candidate == 0 && candidate <= number_of_kpoints) {
119 chosen_number_of_pools = candidate;
120 }
121 }
122 if (chosen_number_of_pools != execution_policy.number_of_pools()) {
123 log::info("MPI topology (automatic): {} rank(s) -> {} k-point pool(s) x {} "
124 "rank(s) per pool, from {} irreducible k-point(s)",
125 number_of_processes,
126 chosen_number_of_pools,
127 number_of_processes / chosen_number_of_pools,
128 number_of_kpoints);
129 const_cast<execution::PlanewaveExecutionPolicy&>(execution_policy)
130 .reconfigure_topology(chosen_number_of_pools);
131 }
132
133 /**
134 * If ranks are still left over, they would form a band group by
135 * default. Refuse instead of quietly running the slow path: the
136 * caller can still ask for it explicitly.
137 */
138 if (execution_policy.band_size() > 1) {
139 throw std::invalid_argument(
140 "q3_mpi was started on " + std::to_string(number_of_processes) +
141 " ranks, but this input has only " + std::to_string(number_of_kpoints) +
142 " irreducible k-point(s), so " + std::to_string(execution_policy.band_size()) +
143 " ranks would share each pool. Q3 parallelizes over k-points; ranks sharing a "
144 "pool distribute the FFT instead, which has no measured speedup and disables "
145 "gamma-only, stress, checkpointing and export. Use a rank count that divides "
146 "into single-rank pools (here: " +
147 std::to_string(number_of_kpoints) +
148 "), give the remaining cores to OMP_NUM_THREADS, or ask for the shared-pool "
149 "layout explicitly with --pools.");
150 }
151 }
152
153 int number_of_pools = execution_policy.number_of_pools();
154 if (number_of_pools > number_of_kpoints) {
155 log::warn("Requested k-point pools ({}) exceeds available IBZ k-points ({}). "
156 "Clamping to {} pools to match QE behaviour.",
157 number_of_pools,
158 number_of_kpoints,
159 number_of_kpoints);
160 /**
161 * Delegate to the virtual reconfigure_topology so the MPI policy can
162 * reconfigure its communicators. The serial policy's default no-op
163 * implementation is never reached here (serial always has 1 pool).
164 */
165 const_cast<execution::PlanewaveExecutionPolicy&>(execution_policy)
166 .reconfigure_topology(number_of_kpoints);
167 }
168 }
169 KPointsGrid local_kpoint_grid = execution_policy.local_kpoint_grid(global_kpoint_grid);
170
171 // 6. Build config and dispatch NC vs US
172 KohnShamSCFIteratorConfig scf_config;
173 scf_config.crystal_structure = nullptr;
176 scf_config.maximum_iterations = input.maximum_iterations;
178 scf_config.mixing_mode = input.mixing_mode;
179 scf_config.mixing_beta = input.mixing_beta;
180 scf_config.mixing_history_size = input.mixing_history_size;
182 scf_config.mixing_q0 = input.mixing_q0;
183 scf_config.smearing = input.smearing;
184 scf_config.smearing_width = input.smearing_width;
187 scf_config.total_charge = input.total_charge;
188 scf_config.hubbard = input.hubbard;
194 scf_config.number_of_bands = input.number_of_bands;
199 scf_config.disable_symmetry = input.disable_symmetry;
200 scf_config.calculation = input.calculation;
203 scf_config.band_path = input.band_path;
206 scf_config.execution_policy = &execution_policy;
207
208 /**
209 * PAW first: a PAW dataset also has augmentation charges, so testing that
210 * capability first would route it to the ultrasoft iterator and drop every
211 * one-center term without a word.
212 */
213 bool is_paw =
215 bool needs_augmentation = pseudopotential_set.has_augmentation_charges();
216
217 std::unique_ptr<PlanewavePseudopotentialKohnShamSCFIterator> iterator;
218 if (is_paw) {
219 iterator =
220 std::make_unique<PawPlanewavePseudopotentialKohnShamSCFIterator>(std::move(scf_config));
221 } else if (needs_augmentation) {
222 iterator = std::make_unique<UltrasoftPlanewavePseudopotentialKohnShamSCFIterator>(
223 std::move(scf_config));
224 } else {
225 iterator =
226 std::make_unique<PlanewavePseudopotentialKohnShamSCFIterator>(std::move(scf_config));
227 }
228
229 // Transfer ownership of resources into the iterator.
230 iterator->set_owned_crystal(std::move(crystal));
231 iterator->set_owned_kpoint_grid(std::move(local_kpoint_grid));
232 iterator->set_owned_pseudopotential_set(std::move(pseudopotential_set));
233
234 return iterator;
235}
236
237} // namespace q3
int number_of_kpoints() const
bool has_pseudopotential_type(PseudopotentialType target_type) const
Definition set.hpp:109
bool has_augmentation_charges() const
Definition set.hpp:129
static std::string find_spacegroup(const Crystal &crystal, Real symmetry_precision=math::tolerances::default_symmetry_precision)
Definition symmetry.hpp:73
virtual int number_of_processes() const =0
virtual bool number_of_pools_was_requested() const
Definition init.hpp:87
virtual KPointsGrid local_kpoint_grid(const KPointsGrid &global_kpoint_grid) const =0
virtual int number_of_pools() const =0
void warn(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:187
void info(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:183
PseudopotentialSet load_pseudopotentials(std::vector< AtomSpecies > &species, const std::string &pseudopotential_directory)
Definition loader.hpp:191
Crystal build_crystal_from_input(const InputData &input, const std::vector< AtomSpecies > &species)
std::unique_ptr< PlanewavePseudopotentialKohnShamSCFIterator > create_planewave_pseudopotential_iterator(KohnShamSCFIteratorConfig config)
Definition factory.hpp:32
KPointsGrid build_global_kpoint_grid_from_input(const InputData &input, const Crystal &crystal)
std::unique_ptr< KohnShamSCFIterator > create_kohn_sham_iterator_from_config(KohnShamSCFIteratorConfig config)
Definition factory.hpp:38
std::unique_ptr< PlanewaveKohnShamSCFIterator > create_kohn_sham_iterator(const InputData &input, const execution::PlanewaveExecutionPolicy &execution_policy)
Definition factory.hpp:64
Parsed input data — holds raw values read from input files.
Definition data.hpp:27
std::string dispersion_correction
Definition data.hpp:78
bool use_exact_wavefunction_grid
Definition data.hpp:303
std::vector< std::pair< std::string, Vector3D > > band_path
Definition data.hpp:117
std::string smearing
Definition data.hpp:75
std::string diagonalization_method
Definition data.hpp:113
Real mixing_beta
Definition data.hpp:59
Real charge_density_energy_cutoff
Definition data.hpp:111
std::vector< AtomSpecies > species
Definition data.hpp:153
int hubbard_fixed_occupation_iterations
Definition data.hpp:107
int maximum_iterations
Definition data.hpp:33
std::vector< HubbardStartingEigenvalue > hubbard_starting_eigenvalues
Definition data.hpp:100
bool disable_symmetry
Definition data.hpp:192
std::vector< HubbardSpecification > hubbard
Definition data.hpp:85
std::string calculation
Definition data.hpp:116
int band_path_points_per_segment
Definition data.hpp:118
int methfessel_paxton_order
Definition data.hpp:77
int mixing_history_size
Definition data.hpp:60
Real mixing_q0
Definition data.hpp:74
std::string mixing_preconditioning
Definition data.hpp:73
std::string hubbard_projector_type
Definition data.hpp:93
std::string mixing_mode
Definition data.hpp:58
Real convergence_threshold
Definition data.hpp:32
std::vector< Real > atomic_starting_magnetization
Definition data.hpp:57
std::string pseudopotential_directory
Definition data.hpp:163
Real smearing_width
Definition data.hpp:76
bool use_deterministic_fft_planning
Definition data.hpp:292
int number_of_bands
Definition data.hpp:112
Real wavefunction_energy_cutoff
Definition data.hpp:110
std::string exchange_correlation_functional
Definition data.hpp:41
std::string nonlocal_stress_derivative_method
Definition data.hpp:200
int davidson_basis_expansion_factor
Definition data.hpp:66
std::vector< Real > starting_magnetization
Definition data.hpp:49
Real total_charge
Definition data.hpp:83
int number_of_spin_components
Definition data.hpp:42
std::vector< Real > starting_magnetization
Definition init.hpp:98
std::string hubbard_projector_type
Definition init.hpp:62
std::vector< std::pair< std::string, Vector3D > > band_path
Definition init.hpp:105
std::vector< Real > atomic_starting_magnetization
Definition init.hpp:104
std::vector< HubbardSpecification > hubbard
Definition init.hpp:53
std::string mixing_preconditioning
Definition init.hpp:46
std::string exchange_correlation_functional
Definition init.hpp:42
std::string nonlocal_stress_derivative_method
Definition init.hpp:107
std::vector< HubbardStartingEigenvalue > hubbard_starting_eigenvalues
First-iteration occupation-eigenvalue overrides; empty = no seeding.
Definition init.hpp:55
std::string diagonalization_method
Definition init.hpp:69
std::string dispersion_correction
Definition init.hpp:51
const execution::ExecutionPolicy * execution_policy
Definition init.hpp:118
std::string representation_type
Definition init.hpp:111
int hubbard_fixed_occupation_iterations
Iterations for which the seeded occupation matrix is held; 0 = first iteration only.
Definition init.hpp:57
Crystal * crystal_structure
Definition config.hpp:16