diff --git a/docs/user_guide/runtime_parameters.yml b/docs/user_guide/runtime_parameters.yml index 299a64b6..805e7c74 100644 --- a/docs/user_guide/runtime_parameters.yml +++ b/docs/user_guide/runtime_parameters.yml @@ -149,6 +149,32 @@ blocks: - api.sqrt_coulomb_threshold - api.n_bands_sigc + - heading: QSGW + text: | + These experimental driver parameters apply only to `task = qsgw` or + `task = qsgw_band`. Same-grid analytic head-only updates are enabled + with `replace_w_head = true` and `option_dielect_func = 4`. + The aligned velocity remains in the immutable `mf0` basis while live + eigenvalues and eigenvectors are updated after each diagonalization. + Independent-grid head/wing and Hartree updates are not supported. + Linear mixing is regression-tested for molecular QSGW only; the + current solid-state benchmark uses `qsgw_mixer = none`. + In an ABACUS Vxc manifest, an `OUT.MAT_XC` matrix already expressed + in the fixed KS-state basis must use `basis state` and + `gauge mf0_state`, regardless of an `_nao` filename suffix. Use + `basis nao` and `gauge ao_bloch` only for a genuine AO matrix. + fields: + - driver.qsgw_input_contract + - driver.qsgw_mixer + - driver.qsgw_mixing_beta + - driver.qsgw_min_iter + - driver.qsgw_max_iter + - driver.qsgw_band0_unoccupied_keep + - driver.qsgw_band0_cut_mode + - driver.qsgw_band0_cut_shift_ha + - driver.qsgw_write_iteration_matrices + - driver.qsgw_convergence_tolerance_ev + - heading: LibRI Filtering text: | These parameters are active for LibRI-enabled builds and LibRI routing. diff --git a/driver/CMakeLists.txt b/driver/CMakeLists.txt index 544037b0..eb7b8219 100644 --- a/driver/CMakeLists.txt +++ b/driver/CMakeLists.txt @@ -25,6 +25,7 @@ list(APPEND exe_sources tasks/g0w0_band.cpp tasks/rpa.cpp tasks/print_minimax.cpp + tasks/qsgw.cpp # tasks/test.cpp # task_qsgw.cpp # task_qsgwA.cpp diff --git a/driver/driver.cpp b/driver/driver.cpp index f3d17c16..31134531 100644 --- a/driver/driver.cpp +++ b/driver/driver.cpp @@ -45,7 +45,17 @@ DriverParams::DriverParams(): sf_omega_end(1.0), sf_omega_step(0.1), sf_state_start(-1), - sf_state_end(-1) + sf_state_end(-1), + qsgw_input_contract("qsgw_input.contract"), + qsgw_mixer("none"), + qsgw_mixing_beta(0.2), + qsgw_min_iter(1), + qsgw_max_iter(10), + qsgw_band0_unoccupied_keep(10), + qsgw_band0_cut_mode(0), + qsgw_band0_cut_shift_ha(20.0), + qsgw_write_iteration_matrices(false), + qsgw_convergence_tolerance_ev(1.0e-4) { } @@ -97,6 +107,24 @@ std::string DriverParams::format() ss << "cs_R_threshold = " << cs_threshold << std::endl; ss << "i_state_low = " << i_state_low << std::endl; ss << "i_state_high = " << i_state_high << std::endl; + if (task == "qsgw" || task == "qsgw_band") + { + ss << "qsgw_input_contract = " << qsgw_input_contract << std::endl; + ss << "qsgw_mixer = " << qsgw_mixer << std::endl; + ss << "qsgw_mixing_beta = " << qsgw_mixing_beta << std::endl; + ss << "qsgw_min_iter = " << qsgw_min_iter << std::endl; + ss << "qsgw_max_iter = " << qsgw_max_iter << std::endl; + ss << "qsgw_band0_unoccupied_keep = " + << qsgw_band0_unoccupied_keep << std::endl; + ss << "qsgw_band0_cut_mode = " << qsgw_band0_cut_mode + << std::endl; + ss << "qsgw_band0_cut_shift_ha = " + << qsgw_band0_cut_shift_ha << std::endl; + ss << "qsgw_convergence_tolerance_ev = " + << qsgw_convergence_tolerance_ev << std::endl; + ss << "qsgw_write_iteration_matrices = " << std::boolalpha + << qsgw_write_iteration_matrices << std::endl; + } if (output_gw_spec_func) { ss << "sf_omega_start = " << sf_omega_start << std::endl; diff --git a/driver/driver.h b/driver/driver.h index eb83b0a6..6f6934a5 100644 --- a/driver/driver.h +++ b/driver/driver.h @@ -250,6 +250,77 @@ struct DriverParams //! Experimental int sf_state_end; + //! Provenance and semantic contract filename for all QSGW input files. + //! The path is interpreted relative to `input_dir`. + //! @par Default + //! `qsgw_input.contract` + //! @par Status + //! Experimental + std::string qsgw_input_contract; + + //! Fixed-basis Hamiltonian mixing (`none` or `linear`). + //! @par Default + //! `none` + //! @par Status + //! Experimental + std::string qsgw_mixer; + + //! Linear Hamiltonian mixing fraction. + //! @par Default + //! 0.2 + //! @par Status + //! Experimental + double qsgw_mixing_beta; + + //! Minimum number of QSGW updates before convergence may terminate the run. + //! @par Default + //! 1 + //! @par Status + //! Experimental + int qsgw_min_iter; + + //! Maximum number of QSGW updates. + //! @par Default + //! 10 + //! @par Status + //! Experimental + int qsgw_max_iter; + + //! Number of unoccupied states retained by qsgw_band H_QSGW cut. + //! @par Default + //! 10 + //! @par Status + //! Experimental + int qsgw_band0_unoccupied_keep; + + //! qsgw_band H_QSGW cut: 0 uncut, 1 KS diagonal, 2 shifted KS diagonal. + //! @par Default + //! 0 + //! @par Status + //! Experimental + int qsgw_band0_cut_mode; + + //! Hamiltonian energy shift applied to cut states in mode 2, in Hartree. + //! @par Default + //! 20.0 + //! @par Status + //! Experimental + double qsgw_band0_cut_shift_ha; + + //! Write per-iteration matrices used by numerical regression tests. + //! @par Default + //! false + //! @par Status + //! Experimental + bool qsgw_write_iteration_matrices; + + //! Maximum eigenvalue change used for convergence, in eV. + //! @par Default + //! 1.0e-4 + //! @par Status + //! Experimental + double qsgw_convergence_tolerance_ev; + std::string format(); DriverParams(); diff --git a/driver/inputfile.cpp b/driver/inputfile.cpp index 47c0d68b..229c2311 100644 --- a/driver/inputfile.cpp +++ b/driver/inputfile.cpp @@ -7,6 +7,9 @@ #include #include #include +#include +#include +#include #include "driver.h" @@ -67,9 +70,120 @@ static std::string check_dirpath(const std::string &dirpath) #define _parse_switch(obj, name) parser.parse_bool(#name, btmp, flag); if (flag == 0) obj.name = get_switch(btmp); #define _parse_string_post(obj, name, post) parser.parse_string(#name, stmp, flag); if (flag == 0) obj.name = post(stmp); +static bool get_last_assigned_value(const InputParser &parser, + const std::string &name, + std::string &value) +{ + const std::string space = "[ \\r\\f\\t]*"; + const std::regex assignment( + "(^|[\\r\\n])" + space + name + space + "=" + + "([^\\r\\n#!]*)", + std::regex_constants::ECMAScript | std::regex_constants::icase); + const std::string ¶ms = parser.get_params(); + bool found = false; + for (std::sregex_iterator it(params.begin(), params.end(), assignment), end; + it != end; ++it) + { + value = (*it)[2].str(); + found = true; + } + if (!found) + return false; + + const auto first = std::find_if_not( + value.begin(), value.end(), + [](const unsigned char ch) { return std::isspace(ch); }); + const auto last = std::find_if_not( + value.rbegin(), value.rend(), + [](const unsigned char ch) { return std::isspace(ch); }).base(); + value = first < last ? std::string(first, last) : std::string{}; + return true; +} + +static void parse_qsgw_string(const InputParser &parser, + const std::string &name, + std::string &value) +{ + std::string token; + if (!get_last_assigned_value(parser, name, token)) + return; + if (token.empty()) + throw std::runtime_error(name + " must not be empty"); + value = token; +} + +static void parse_qsgw_int(const InputParser &parser, + const std::string &name, + int &value) +{ + std::string token; + if (!get_last_assigned_value(parser, name, token)) + return; + try + { + std::size_t consumed = 0; + const int parsed = std::stoi(token, &consumed); + if (consumed != token.size()) + throw std::invalid_argument("trailing input"); + value = parsed; + } + catch (const std::exception &) + { + throw std::runtime_error(name + " must be a valid integer"); + } +} + +static void parse_qsgw_double(const InputParser &parser, + const std::string &name, + double &value) +{ + std::string token; + if (!get_last_assigned_value(parser, name, token)) + return; + std::replace(token.begin(), token.end(), 'd', 'e'); + std::replace(token.begin(), token.end(), 'D', 'E'); + try + { + std::size_t consumed = 0; + const double parsed = std::stod(token, &consumed); + if (consumed != token.size()) + throw std::invalid_argument("trailing input"); + value = parsed; + } + catch (const std::exception &) + { + throw std::runtime_error(name + + " must be a valid floating-point value"); + } +} + +static void parse_qsgw_bool(const InputParser &parser, + const std::string &name, + bool &value) +{ + std::string token; + if (!get_last_assigned_value(parser, name, token)) + return; + std::transform(token.begin(), token.end(), token.begin(), + [](const unsigned char ch) { + return static_cast(std::tolower(ch)); + }); + if (token == "true" || token == "t" || token == ".t.") + value = true; + else if (token == "false" || token == "f" || token == ".f.") + value = false; + else + throw std::runtime_error(name + " must be true or false"); +} + static void validate_input_parameters() { const auto ¶ms = driver::driver_params; + std::string task = params.task; + std::transform(task.begin(), task.end(), task.begin(), + [](const unsigned char ch) { + return static_cast(std::tolower(ch)); + }); if (params.output_gw_spec_func) { if (params.sf_omega_step <= 0.0) @@ -80,6 +194,46 @@ static void validate_input_parameters() && params.sf_state_end <= params.sf_state_start) throw std::runtime_error("sf_state_end must be greater than sf_state_start"); } + if (task == "qsgw" || task == "qsgw_band") + { + if (driver::opts.use_kpara_scf_eigvec == LIBRPA_SWITCH_ON) + throw std::runtime_error( + "QSGW fixed-basis iteration requires replicated SCF wavefunctions; set use_kpara_scf_eigvec = false"); + if (params.qsgw_input_contract.empty()) + throw std::runtime_error("qsgw_input_contract must not be empty"); + if (params.qsgw_mixer != "none" && params.qsgw_mixer != "linear") + throw std::runtime_error("qsgw_mixer must be none or linear"); + if (!(params.qsgw_mixing_beta > 0.0 && + params.qsgw_mixing_beta <= 1.0) || + !std::isfinite(params.qsgw_mixing_beta)) + throw std::runtime_error("qsgw_mixing_beta must be in (0, 1]"); + if (params.qsgw_min_iter < 1 || + params.qsgw_max_iter < params.qsgw_min_iter) + throw std::runtime_error( + "qsgw iteration bounds must satisfy 1 <= min <= max"); + if (params.qsgw_band0_unoccupied_keep < 0) + throw std::runtime_error( + "qsgw_band0_unoccupied_keep must be non-negative"); + if (params.qsgw_band0_cut_mode < 0 || + params.qsgw_band0_cut_mode > 2) + throw std::runtime_error( + "qsgw_band0_cut_mode must be 0, 1, or 2"); + if (!std::isfinite(params.qsgw_band0_cut_shift_ha)) + throw std::runtime_error( + "qsgw_band0_cut_shift_ha must be finite"); + if (!(params.qsgw_convergence_tolerance_ev > 0.0) || + !std::isfinite(params.qsgw_convergence_tolerance_ev)) + throw std::runtime_error( + "qsgw_convergence_tolerance_ev must be finite and positive"); + if (params.use_pyatb) + throw std::runtime_error( + "QSGW independent PyATB head updates are unsupported; use the same-grid velocity input"); + if (driver::opts.replace_w_head == LIBRPA_SWITCH_ON && + driver::opts.option_dielect_func != 4) + throw std::runtime_error( + "QSGW supports only analytic head-only mode; set option_dielect_func = 4"); + // QSGW inherits the upstream EXX, GW, and RPA symmetry switches. + } } void parse_inputfile_to_params(const std::string &fn) @@ -134,6 +288,51 @@ void parse_inputfile_to_params(const std::string &fn) _parse_string(driver_params, fn_dielfunc); _parse_string(driver_params, fn_vxc_scf); _parse_string(driver_params, fn_band_kpath_info); + std::string task_normalized = driver_params.task; + std::transform(task_normalized.begin(), task_normalized.end(), + task_normalized.begin(), [](const unsigned char ch) { + return static_cast(std::tolower(ch)); + }); + if (task_normalized == "qsgw" || task_normalized == "qsgw_band") + { + for (const std::string& removed : { + "qsgw_update_hartree", + "qsgw_hartree_coulomb", + "qsgw_hartree_normalization", + "qsgw_export_hamiltonian_for_pyatb", + "qsgw_hr_export_full_mp_rgrid"}) + { + std::string ignored; + if (get_last_assigned_value(parser, removed, ignored)) + { + throw std::runtime_error( + removed + " is not supported by the head-only QSGW workflow"); + } + } + parse_qsgw_string(parser, "qsgw_input_contract", + driver_params.qsgw_input_contract); + parse_qsgw_string(parser, "qsgw_mixer", driver_params.qsgw_mixer); + std::transform(driver_params.qsgw_mixer.begin(), + driver_params.qsgw_mixer.end(), + driver_params.qsgw_mixer.begin(), + [](const unsigned char ch) { + return static_cast(std::tolower(ch)); + }); + parse_qsgw_double(parser, "qsgw_mixing_beta", + driver_params.qsgw_mixing_beta); + parse_qsgw_int(parser, "qsgw_min_iter", driver_params.qsgw_min_iter); + parse_qsgw_int(parser, "qsgw_max_iter", driver_params.qsgw_max_iter); + parse_qsgw_int(parser, "qsgw_band0_unoccupied_keep", + driver_params.qsgw_band0_unoccupied_keep); + parse_qsgw_int(parser, "qsgw_band0_cut_mode", + driver_params.qsgw_band0_cut_mode); + parse_qsgw_double(parser, "qsgw_band0_cut_shift_ha", + driver_params.qsgw_band0_cut_shift_ha); + parse_qsgw_bool(parser, "qsgw_write_iteration_matrices", + driver_params.qsgw_write_iteration_matrices); + parse_qsgw_double(parser, "qsgw_convergence_tolerance_ev", + driver_params.qsgw_convergence_tolerance_ev); + } _parse_int(driver_params, version_coul_reader); _parse_int(driver_params, version_lri_reader); diff --git a/driver/task.cpp b/driver/task.cpp index 5d04142e..214135ac 100644 --- a/driver/task.cpp +++ b/driver/task.cpp @@ -71,6 +71,8 @@ static std::map> map_task_func_impl{ {task_t::RPA, task_rpa}, {task_t::EXX, task_exx}, {task_t::EXX_band, task_exx_band}, + {task_t::QSGW, task_qsgw}, + {task_t::QSGW_band, task_qsgw_band}, }; void run_task(const task_t &task) diff --git a/driver/task.h b/driver/task.h index 35ed8209..88c06268 100644 --- a/driver/task.h +++ b/driver/task.h @@ -32,6 +32,8 @@ void task_exx(); void task_exx_band(); void task_screened_coulomb_real_freq(); void task_print_minimax(); +void task_qsgw(); +void task_qsgw_band(); void task_test(); void run_task(const task_t &task); diff --git a/driver/tasks/qsgw.cpp b/driver/tasks/qsgw.cpp new file mode 100644 index 00000000..42bf0e2a --- /dev/null +++ b/driver/tasks/qsgw.cpp @@ -0,0 +1,1261 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../../src/api/dataset_helper.h" +#include "../../src/api/instance_manager.h" +#include "../../src/io/fs.h" +#include "../../src/io/global_io.h" +#include "../../src/io/input_elsi.h" +#include "../../src/qsgw/band_bvk_remap.h" +#include "../../src/qsgw/band_output.h" +#include "../../src/qsgw/convergence.h" +#include "../../src/qsgw/correlation_potential.h" +#include "../../src/qsgw/distributed_matrix.h" +#include "../../src/qsgw/effective_hamiltonian.h" +#include "../../src/qsgw/fixed_basis.h" +#include "../../src/qsgw/hamiltonian_cut.h" +#include "../../src/qsgw/hamiltonian_mixing.h" +#include "../../src/qsgw/input_contract.h" +#include "../../src/qsgw/iteration_trace.h" +#include "../../src/qsgw/occupation.h" +#include "../../src/qsgw/projection_target.h" +#include "../../src/qsgw/sha256.h" +#include "../../src/qsgw/vxc_io.h" +#include "../../src/utils/constants.h" +#include "../../src/utils/profiler.h" +#include "../driver.h" +#include "../read_data.h" +#include "../reader_coulomb.h" +#include "../task.h" + +namespace +{ + +using librpa_int::Matz; +using librpa_int::MeanField; +using librpa_int::Vector3_Order; +using librpa_int::cplxdb; +using librpa_int::qsgw::ScopedReferenceEigenvectors; +using librpa_int::qsgw::SpinKMatrixMap; +using librpa_int::qsgw::VelocityMatrix; +using SigmaMatrixMap = librpa_int::qsgw::SpinKFrequencyMatrixMap; + +class ScopedSigmaMatrixRetention +{ +public: + explicit ScopedSigmaMatrixRetention(bool& flag) + : flag_(flag), original_(flag_) + { + flag_ = true; + } + + ~ScopedSigmaMatrixRetention() noexcept + { + flag_ = original_; + } + + ScopedSigmaMatrixRetention(const ScopedSigmaMatrixRetention&) = delete; + ScopedSigmaMatrixRetention& operator=( + const ScopedSigmaMatrixRetention&) = delete; + +private: + bool& flag_; + bool original_; +}; + +class ScopedLegacyHeadOccupations +{ +public: + explicit ScopedLegacyHeadOccupations(MeanField& meanfield) + : meanfield_(meanfield), + live_weights_(meanfield.get_weight()) + { + auto& weights = meanfield_.get_weight(); + for (const auto& spin_weights : weights) + { + if (spin_weights.nr != meanfield_.get_n_kpoints() || + spin_weights.nc != meanfield_.get_n_bands()) + { + throw std::invalid_argument( + "QSGW head occupation matrix has an invalid shape"); + } + } + for (auto& spin_weights : weights) + { + for (int kpoint = 1; kpoint < spin_weights.nr; ++kpoint) + { + for (int band = 0; band < spin_weights.nc; ++band) + spin_weights(kpoint, band) = spin_weights(0, band); + } + } + } + + ~ScopedLegacyHeadOccupations() noexcept + { + meanfield_.get_weight().swap(live_weights_); + } + + ScopedLegacyHeadOccupations( + const ScopedLegacyHeadOccupations&) = delete; + ScopedLegacyHeadOccupations& operator=( + const ScopedLegacyHeadOccupations&) = delete; + +private: + MeanField& meanfield_; + std::vector live_weights_; +}; + +template +void collective_root_stage( + const librpa_int::MpiCommHandler& communicator, + const std::string& label, + Function&& function) +{ + std::exception_ptr root_error; + int failed = 0; + if (communicator.is_root()) + { + try + { + function(); + } + catch (const std::exception& error) + { + failed = 1; + root_error = std::current_exception(); + librpa_int::global::lib_printf( + LIBRPA_VERBOSE_CRITICAL, "%s failed: %s\n", + label.c_str(), error.what()); + } + catch (...) + { + failed = 1; + root_error = std::current_exception(); + } + } + communicator.bcast(&failed, 1, 0); + if (failed) + { + if (communicator.is_root()) std::rethrow_exception(root_error); + throw LIBRPA_RUNTIME_ERROR(label + " failed on root"); + } +} + +std::string resolve_input_path(const std::string& base, + const std::string& path) +{ + return librpa_int::is_absolute_path(path) + ? path + : librpa_int::join_path(base, path); +} + +std::filesystem::path resolved_absolute_path( + const std::string& base, + const std::string& path) +{ + const std::filesystem::path value(path); + const std::filesystem::path resolved = value.is_absolute() + ? value + : std::filesystem::path(base) / value; + return std::filesystem::absolute(resolved).lexically_normal(); +} + +bool starts_with(const std::string& text, const std::string& prefix) +{ + return text.rfind(prefix, 0) == 0; +} + +std::vector discover_prefixed_reader_files( + const std::string& prefix, + const std::string& excluded_prefix = {}) +{ + const std::string& input_dir = driver::driver_params.input_dir; + const std::vector discovered = + librpa_int::discover_files_with_prefix(input_dir, prefix); + std::vector result; + for (const std::string& path : discovered) + { + const std::string filename = + std::filesystem::path(path).filename().string(); + if (!excluded_prefix.empty() && + starts_with(excluded_prefix, prefix) && + starts_with(filename, excluded_prefix)) + { + continue; + } + const std::filesystem::path resolved = + std::filesystem::absolute(path).lexically_normal(); + librpa_int::require_readable_file(resolved.string()); + result.push_back(resolved); + } + if (result.empty()) + { + throw std::invalid_argument( + "QSGW reader file set is empty for prefix " + prefix); + } + return result; +} + +std::vector discover_coulomb_reader_files( + const std::string& prefix) +{ + std::vector result = + discover_prefixed_reader_files(prefix); + int version = driver::driver_params.version_coul_reader; + if (version < 0) + { + version = detect_coulomb_reader_version( + driver::driver_params.input_dir, prefix); + } + if (version == 0) + { + result.erase( + std::remove_if( + result.begin(), result.end(), + [](const std::filesystem::path& path) { + return path.extension() != ".txt"; + }), + result.end()); + } + else if (version != 1) + { + throw std::invalid_argument( + "Unsupported QSGW Coulomb reader version " + + std::to_string(version)); + } + if (result.empty()) + { + throw std::invalid_argument( + "QSGW Coulomb reader file set is empty for prefix " + prefix); + } + return result; +} + +std::vector discover_scf_wavefunction_files() +{ + return discover_prefixed_reader_files( + driver::driver_params.prefix_eigvecs_scf); +} + +std::vector discover_reader_static_files() +{ + using librpa_int::path_exists; + const auto candidate = [](const std::string& filename) { + return resolved_absolute_path( + driver::driver_params.input_dir, filename); + }; + std::set files; + const auto add = [&](const std::filesystem::path& path) { + librpa_int::require_readable_file(path.string()); + files.insert(path); + }; + const auto add_all = [&](const std::vector& paths) { + for (const std::filesystem::path& path : paths) add(path); + }; + + add(candidate(driver::driver_params.fn_stru)); + const std::filesystem::path basis_wfc = + candidate(driver::driver_params.fn_basis_wfc); + const std::filesystem::path basis_aux = + candidate(driver::driver_params.fn_basis_aux); + const std::filesystem::path basis = + candidate(driver::driver_params.fn_basis); + if (path_exists(basis_wfc.string().c_str()) && + path_exists(basis_aux.string().c_str())) + { + add(basis_wfc); + add(basis_aux); + } + else if (path_exists(basis.string().c_str())) + { + add(basis); + } + + add_all(discover_prefixed_reader_files( + driver::driver_params.prefix_lri_coeff, + driver::driver_params.prefix_lri_coeff_shrink)); + if (driver::get_bool(driver::opts.use_shrink_abfs)) + { + for (const std::string& filename : { + driver::driver_params.fn_basis_aux_shrink, + std::string("basis_out_shrink"), + std::string("basis_out.shrink_backup")}) + { + const std::filesystem::path path = candidate(filename); + if (path_exists(path.string().c_str())) + { + add(path); + break; + } + } + add_all(discover_prefixed_reader_files( + driver::driver_params.prefix_lri_coeff_shrink, + driver::driver_params.prefix_lri_coeff)); + add_all(discover_prefixed_reader_files( + driver::driver_params.prefix_shrink_sinvS)); + } + add_all(discover_coulomb_reader_files( + driver::driver_params.prefix_coul_full)); + add_all(discover_coulomb_reader_files( + driver::driver_params.prefix_coul_cut)); + return {files.begin(), files.end()}; +} + +void validate_same_grid_velocity_binding( + const librpa_int::qsgw::QsgwInputContract& contract, + const std::string& contract_base, + const int n_kpoints) +{ + std::vector expected = + librpa_int::qsgw::resolve_same_grid_velocity_paths( + contract.producer(), driver::driver_params.input_dir, n_kpoints); + + std::vector declared; + for (const auto& file : contract.files("velocity_mf0")) + { + declared.push_back( + resolved_absolute_path(contract_base, file.file)); + } + std::sort(expected.begin(), expected.end()); + std::sort(declared.begin(), declared.end()); + if (declared != expected) + { + throw std::invalid_argument( + "QSGW velocity_mf0 contract files do not exactly match the same-grid head-only files read by the driver"); + } +} + +void prepare_stage_one_symmetry_context(librpa_int::Dataset& dataset) +{ + const bool symmetry_reduced_scf_grid = + dataset.pbc.kfrac_list.size() < dataset.pbc.kfrac_list_full.size(); + if (!symmetry_reduced_scf_grid) return; + + const bool all_symmetry_routes_enabled = + driver::get_bool(driver::opts.use_symmetry_exx) && + driver::get_bool(driver::opts.use_symmetry_gw) && + driver::get_bool(driver::opts.use_symmetry_rpa); + if (all_symmetry_routes_enabled) + librpa_int::initialize_symmetry_context(dataset, true); +} + +void validate_stage_one_contract( + const librpa_int::qsgw::QsgwInputContract& contract, + const librpa_int::Dataset& dataset, + const std::string& contract_base, + const librpa_int::qsgw::HeadwingGridMode headwing_grid, + const bool compute_band) +{ + using namespace librpa_int::qsgw; + validate_qsgw_execution_modes(contract, headwing_grid, compute_band); + if (contract.n_spins() != dataset.mf.get_n_spins() || + contract.n_bands() != dataset.mf.get_n_bands() || + contract.n_aos() != dataset.mf.get_n_aos() || + contract.n_scf_kpoints() != dataset.mf.get_n_kpoints() || + contract.n_scf_kpoints() != + static_cast(dataset.pbc.kfrac_list.size())) + { + throw std::invalid_argument( + "QSGW input-contract dimensions do not match the loaded mf0 dataset"); + } + validate_projection_target( + dataset.mf, dataset.pbc.kfrac_list, + contract.n_spins(), dataset.mf.get_n_spinor(), contract.n_aos(), + "grid"); + const std::string bz_sampling_path = resolve_input_path( + driver::driver_params.input_dir, + driver::driver_params.fn_bz_sampling); + const std::string loaded_kpoint_file = + librpa_int::path_exists(bz_sampling_path.c_str()) + ? bz_sampling_path + : resolve_input_path(driver::driver_params.input_dir, + driver::driver_params.fn_stru); + validate_scf_input_binding( + contract, contract_base, + resolved_absolute_path(driver::driver_params.input_dir, + driver::driver_params.fn_eigocc_scf), + discover_scf_wavefunction_files(), loaded_kpoint_file, + discover_reader_static_files()); + const bool symmetry_reduced_scf_grid = + dataset.pbc.kfrac_list.size() < dataset.pbc.kfrac_list_full.size(); + if (symmetry_reduced_scf_grid) + { + const bool all_symmetry_routes_enabled = + driver::get_bool(driver::opts.use_symmetry_exx) && + driver::get_bool(driver::opts.use_symmetry_gw) && + driver::get_bool(driver::opts.use_symmetry_rpa); + if (!all_symmetry_routes_enabled || + !dataset.symmetry_context.available || + dataset.symmetry_context.kstars.size() != + dataset.pbc.kfrac_list.size() || + dataset.symmetry_context.count_kstar_members() != + dataset.pbc.kfrac_list_full.size()) + { + throw std::invalid_argument( + "QSGW symmetry-reduced SCF input requires complete EXX/GW/RPA k-star restoration"); + } + } + const bool producer_matches_constants = + (contract.producer() == QsgwProducer::FhiAims && + driver::driver_params.constants_choice == "aims") || + (contract.producer() == QsgwProducer::Abacus && + driver::driver_params.constants_choice == "internal"); + if (!producer_matches_constants) + { + throw std::invalid_argument( + "QSGW input-contract producer does not match constants_choice"); + } + if (headwing_grid == HeadwingGridMode::ScfGrid) + { + validate_same_grid_velocity_binding( + contract, contract_base, dataset.mf.get_n_kpoints()); + } + if (compute_band) + { + const int complete_dimension = + dataset.mf.get_n_aos() * dataset.mf.get_n_spinor(); + if (dataset.mf.get_n_bands() != complete_dimension || + dataset.mf_band.get_n_bands() != complete_dimension) + { + throw std::invalid_argument( + "QSGW fixed-basis band rotation requires complete square grid and band references"); + } + if (contract.n_band_kpoints() != dataset.mf_band.get_n_kpoints() || + contract.n_band_kpoints() != + static_cast(dataset.kfrac_band_list.size())) + { + throw std::invalid_argument( + "QSGW band input-contract dimensions do not match the loaded band reference"); + } + validate_projection_target( + dataset.mf_band, dataset.kfrac_band_list, + dataset.mf.get_n_spins(), dataset.mf.get_n_spinor(), + dataset.mf.get_n_aos(), "band"); + validate_band_reference_binding( + contract, contract_base, driver::driver_params.input_dir, + driver::driver_params.fn_band_kpath_info); + } +} + +SpinKMatrixMap load_vxc_manifest_root( + const std::string& manifest_path, + const MeanField& reference, + const std::vector>& kpoints, + const librpa_int::qsgw::VxcDatasetKind dataset_kind) +{ + using namespace librpa_int; + using namespace librpa_int::qsgw; + + require_readable_file(manifest_path); + std::ifstream manifest_stream(manifest_path); + const VxcManifest manifest = + VxcManifest::parse(manifest_stream, manifest_path); + if (manifest.producer() == "abacus" && reference.get_n_spinor() != 1) + { + throw std::invalid_argument( + "ABACUS QSGW Vxc currently requires n_spinor=1"); + } + const int expected_dimension = + manifest.basis() == VxcBasis::Nao + ? reference.get_n_aos() * reference.get_n_spinor() + : reference.get_n_bands(); + manifest.validate(dataset_kind, reference.get_n_spins(), + kpoints, expected_dimension, expected_dimension, + 1.0e-8); + const std::string manifest_directory = parent_path(manifest_path); + manifest.validate_file_hashes(manifest_directory); + + SpinKMatrixMap result; + for (int spin = 0; spin < reference.get_n_spins(); ++spin) + { + for (int kpoint = 0; kpoint < reference.get_n_kpoints(); ++kpoint) + { + const VxcManifestEntry& entry = manifest.at(spin, kpoint); + const std::string matrix_path = + resolve_input_path(manifest_directory, entry.file); + require_readable_file(matrix_path); + Matz input; + if (manifest.producer() == "abacus") + { + std::ifstream stream(matrix_path); + input = read_abacus_vxc_ha(stream, matrix_path); + } + else + { + input = load_matrix_cplx(matrix_path, MAJOR::COL); + } + result[spin][kpoint] = prepare_vxc_in_fixed_state_basis( + input, manifest.basis(), reference, spin, kpoint); + } + } + return result; +} + +SpinKMatrixMap copy_exx_root(const librpa_int::Exx& exchange, + const MeanField& reference) +{ + SpinKMatrixMap result; + for (int spin = 0; spin < reference.get_n_spins(); ++spin) + { + for (int kpoint = 0; kpoint < reference.get_n_kpoints(); ++kpoint) + { + const auto spin_it = exchange.exx_KS.find(spin); + if (spin_it == exchange.exx_KS.end()) + throw std::runtime_error("QSGW EXX spin channel is missing"); + const auto k_it = spin_it->second.find(kpoint); + if (k_it == spin_it->second.end() || + k_it->second.nr() != reference.get_n_bands() || + k_it->second.nc() != reference.get_n_bands()) + { + throw std::runtime_error( + "QSGW EXX fixed-basis matrix is missing or malformed"); + } + result[spin][kpoint] = k_it->second.copy(); + } + } + return result; +} + +SigmaMatrixMap collect_sigma_root( + const librpa_int::G0W0& self_energy, + const MeanField& reference, + const std::vector& frequencies, + const librpa_int::MpiCommHandler& communicator) +{ + using librpa_int::qsgw::collect_blacs_matrix_root; + SigmaMatrixMap result; + for (int spin = 0; spin < reference.get_n_spins(); ++spin) + { + for (int kpoint = 0; kpoint < reference.get_n_kpoints(); ++kpoint) + { + for (const double frequency : frequencies) + { + const Matz* local = nullptr; + const auto spin_it = self_energy.sigc_is_ik_f_KS.find(spin); + if (spin_it != self_energy.sigc_is_ik_f_KS.end()) + { + const auto k_it = spin_it->second.find(kpoint); + if (k_it != spin_it->second.end()) + { + const auto f_it = k_it->second.find(frequency); + if (f_it != k_it->second.end()) local = &f_it->second; + } + } + int valid = local != nullptr && + local->major() == librpa_int::MAJOR::COL && + local->nr() == + self_energy.desc_sigc_is_ik_f_KS.m_loc() && + local->nc() == + self_energy.desc_sigc_is_ik_f_KS.n_loc() + ? 1 + : 0; + int all_valid = 0; + communicator.allreduce(&valid, &all_valid, 1, MPI_MIN); + if (!all_valid) + throw std::runtime_error( + "QSGW distributed fixed-basis Sigma is incomplete"); + Matz full = collect_blacs_matrix_root( + *local, self_energy.desc_sigc_is_ik_f_KS); + if (communicator.is_root()) + { + if (full.nr() != reference.get_n_bands() || + full.nc() != reference.get_n_bands()) + { + throw std::runtime_error( + "QSGW collected Sigma has an invalid shape"); + } + result[spin][kpoint][frequency] = std::move(full); + } + } + } + } + return result; +} + +SpinKMatrixMap build_correlation_map( + const MeanField& live, + const SigmaMatrixMap& sigma, + const std::vector& frequencies, + const LibrpaOptions& options) +{ + using namespace librpa_int::qsgw; + CorrelationPotentialSettings settings; + settings.mode = CorrelationPotentialMode::ModeB; + settings.n_params_anacon = options.n_params_anacon; + settings.n_params_anacon_resample = + options.n_params_anacon_resample < 0 + ? options.n_params_anacon + : options.n_params_anacon_resample; + + SpinKMatrixMap result; + for (int spin = 0; spin < live.get_n_spins(); ++spin) + { + for (int kpoint = 0; kpoint < live.get_n_kpoints(); ++kpoint) + { + result[spin][kpoint] = build_qsgw_correlation_potential( + live, frequencies, sigma.at(spin).at(kpoint), + spin, kpoint, settings); + } + } + return result; +} + +void write_contract_header(std::ostream& output, + const std::string& contract_path, + const std::string& contract_sha256, + const bool update_head, + const bool compute_band) +{ + const bool use_symmetry_exx = + driver::get_bool(driver::opts.use_symmetry_exx); + const bool use_symmetry_gw = + driver::get_bool(driver::opts.use_symmetry_gw); + const bool use_symmetry_rpa = + driver::get_bool(driver::opts.use_symmetry_rpa); + output << "# qsgw_contract_version 6\n" + << "# fixed_basis immutable_mf0\n" + << "# live_update eigenvalues_wfc\n" + << "# velocity " + << (update_head ? "fixed_reference" : "disabled_stage1") + << "\n" + << "# head " + << (update_head ? "scf_grid_analytic_live" : "disabled_stage1") + << "\n" + << "# wing disabled_stage1\n" + << "# symmetry exx_" << (use_symmetry_exx ? "on" : "off") + << "_gw_" << (use_symmetry_gw ? "on" : "off") + << "_rpa_" << (use_symmetry_rpa ? "on" : "off") << "\n" + << "# hartree disabled\n" + << "# band " + << (compute_band + ? "fixed_reference_rotation_live" + : "disabled_stage1") + << "\n" + << "# h_qsgw_cut " + << (compute_band ? "band_postprocess" : "disabled_non_band") + << "\n"; + if (compute_band) + { + output << "# qsgw_band0_unoccupied_keep " + << driver::driver_params.qsgw_band0_unoccupied_keep << "\n" + << "# qsgw_band0_cut_mode " + << driver::driver_params.qsgw_band0_cut_mode << "\n" + << "# qsgw_band0_cut_shift_ha " << std::setprecision(17) + << driver::driver_params.qsgw_band0_cut_shift_ha << "\n"; + } + output << "# qsgw_input_contract " << contract_path << "\n" + << "# qsgw_input_contract_sha256 " << contract_sha256 << "\n" + << "# qsgw_mixer " << driver::driver_params.qsgw_mixer << "\n" + << "# qsgw_mixing_beta " << std::setprecision(17) + << driver::driver_params.qsgw_mixing_beta << "\n"; +} + +void run_qsgw_stage_one(const bool compute_band) +{ + using namespace driver; + using namespace librpa_int; + using namespace librpa_int::global; + using namespace librpa_int::qsgw; + + const bool update_head = + driver::get_bool(opts.replace_w_head) && + opts.option_dielect_func == 4; + if (driver_params.use_pyatb) + { + throw LIBRPA_RUNTIME_ERROR( + "QSGW independent PyATB head updates are unsupported; use the same-grid velocity input"); + } + if (driver::get_bool(opts.replace_w_head) && !update_head) + { + throw LIBRPA_RUNTIME_ERROR( + "QSGW supports only analytic head-only mode; set option_dielect_func = 4"); + } + if (driver::get_bool(opts.use_kpara_scf_eigvec)) + { + throw LIBRPA_RUNTIME_ERROR( + "QSGW fixed-basis iteration requires replicated SCF wavefunctions; set use_kpara_scf_eigvec = false"); + } + + profiler.start("qsgw", "QSGW fixed-basis self-consistent calculation"); + const auto dataset = api::get_dataset_instance(h); + if (opts.parallel_routing != LIBRPA_ROUTING_LIBRI && + opts.parallel_routing != LIBRPA_ROUTING_AUTO) + { + throw LIBRPA_RUNTIME_ERROR( + "QSGW stage one requires parallel_routing=libri or auto"); + } + if (compute_band) + { + const std::string band_kpath_path = resolve_input_path( + driver_params.input_dir, driver_params.fn_band_kpath_info); + read_band_kpath_info(band_kpath_path); + dataset->comm_h.barrier(); + read_band_meanfield_data(driver_params.input_dir); + dataset->comm_h.barrier(); + } + read_Vq_row(driver_params.input_dir, driver_params.prefix_coul_cut, + opts.vq_threshold, local_atpair, true, + driver_params.version_coul_reader, + driver::get_bool(opts.use_shrink_abfs)); + const HeadwingGridMode headwing_grid = + update_head ? HeadwingGridMode::ScfGrid + : HeadwingGridMode::Disabled; + + const std::string contract_path = resolve_input_path( + driver_params.input_dir, driver_params.qsgw_input_contract); + std::optional input_contract; + std::string contract_sha256; + int contract_producer = -1; + collective_root_stage(dataset->comm_h, "QSGW input preflight", [&] { + require_readable_file(contract_path); + std::ifstream stream(contract_path); + input_contract = QsgwInputContract::parse(stream, contract_path); + const std::string base = parent_path(contract_path); + input_contract->validate_file_hashes(base); + prepare_stage_one_symmetry_context(*dataset); + validate_stage_one_contract( + *input_contract, *dataset, base, headwing_grid, compute_band); + contract_producer = static_cast(input_contract->producer()); + contract_sha256 = sha256_file(contract_path); + }); + dataset->comm_h.bcast(&contract_producer, 1, 0); + if (contract_producer != static_cast(QsgwProducer::Abacus) && + contract_producer != static_cast(QsgwProducer::FhiAims)) + { + throw LIBRPA_RUNTIME_ERROR( + "QSGW input producer broadcast is invalid"); + } + + const MeanField reference = dataset->mf; + const double electron_count = physical_electron_count( + reference, dataset->pbc.weight_k); + const OccupationResult initial_occupations = analyze_qsgw_occupations( + reference, dataset->pbc.weight_k, electron_count); + const SpinKMatrixMap reference_hamiltonian = + build_reference_hamiltonian(reference); + + VelocityMatrix reference_velocity; + if (update_head) + { + read_headwing_input(driver_params.input_dir, false); + reference_velocity = dataset->velocity_matrix; + if (contract_producer == + static_cast(QsgwProducer::FhiAims)) + { + prepare_fhi_aims_interband_velocity( + reference_velocity, reference); + align_distributed_velocity_to_reference_wfc( + dataset->p_headwing->get_meanfield_df(), reference, + reference_velocity, dataset->comm_h); + } + } + + std::optional band_reference; + SpinKMatrixMap band_reference_hamiltonian; + if (compute_band) + { + dataset->mf_band.get_efermi() = reference.get_efermi(); + band_reference = dataset->mf_band; + band_reference_hamiltonian = + build_reference_hamiltonian(*band_reference); + } + + SpinKMatrixMap dft_vxc; + SpinKMatrixMap dft_vxc_band; + collective_root_stage(dataset->comm_h, "QSGW Vxc preflight", [&] { + const auto& records = input_contract->files("vxc_scf_manifest"); + if (records.size() != 1) + throw std::invalid_argument( + "QSGW contract requires exactly one SCF Vxc manifest"); + const std::string path = resolve_input_path( + parent_path(contract_path), records.front().file); + dft_vxc = load_vxc_manifest_root( + path, reference, dataset->pbc.kfrac_list, + VxcDatasetKind::ScfGrid); + if (compute_band) + { + const auto& band_records = + input_contract->files("vxc_band_manifest"); + if (band_records.size() != 1) + throw std::invalid_argument( + "QSGW contract requires exactly one band Vxc manifest"); + const std::string band_path = resolve_input_path( + parent_path(contract_path), band_records.front().file); + dft_vxc_band = load_vxc_manifest_root( + band_path, *band_reference, dataset->kfrac_band_list, + VxcDatasetKind::BandPath); + } + }); + + HamiltonianCutOptions cut_options; + cut_options.unoccupied_keep = + driver_params.qsgw_band0_unoccupied_keep; + cut_options.mode = hamiltonian_cut_mode_from_int( + driver_params.qsgw_band0_cut_mode); + cut_options.shift_ha = driver_params.qsgw_band0_cut_shift_ha; + SpinKMatrixMap current_hamiltonian = compute_band + ? apply_hamiltonian_cut( + reference_hamiltonian, reference_hamiltonian, dataset->mf, + cut_options) + : reference_hamiltonian; + SpinKMatrixMap current_band_hamiltonian = compute_band + ? apply_hamiltonian_cut( + band_reference_hamiltonian, band_reference_hamiltonian, + dataset->mf_band, cut_options) + : band_reference_hamiltonian; + std::optional mixer; + if (driver_params.qsgw_mixer == "linear") + { + MixingOptions options; + options.mode = MixingMode::Linear; + options.beta = driver_params.qsgw_mixing_beta; + mixer.emplace(options); + if (dataset->comm_h.is_root()) + { + if (compute_band) + mixer->initialize(current_hamiltonian, + current_band_hamiltonian); + else + mixer->initialize(current_hamiltonian); + } + } + + std::ofstream trace; + std::ofstream eigenvalue_trace; + std::ofstream matrix_trace; + collective_root_stage(dataset->comm_h, "QSGW trace setup", [&] { + trace.open(join_path(opts.output_dir, "qsgw_iterations.dat")); + eigenvalue_trace.open( + join_path(opts.output_dir, "qsgw_eigenvalues.dat")); + if (driver_params.qsgw_write_iteration_matrices) + matrix_trace.open(join_path(opts.output_dir, "qsgw_matrices.dat")); + if (!trace || !eigenvalue_trace || + (driver_params.qsgw_write_iteration_matrices && !matrix_trace)) + throw std::runtime_error("Cannot open QSGW trace output"); + write_contract_header( + trace, contract_path, contract_sha256, + update_head, compute_band); + write_contract_header(eigenvalue_trace, contract_path, + contract_sha256, + update_head, compute_band); + write_iteration_summary_header(trace); + write_eigenvalue_trace_header(eigenvalue_trace); + if (driver_params.qsgw_write_iteration_matrices) + { + write_contract_header(matrix_trace, contract_path, + contract_sha256, + update_head, compute_band); + write_matrix_trace_header(matrix_trace); + write_matrix_component_trace( + matrix_trace, 0, IterationChannel::Grid, "h0", + reference_hamiltonian); + write_matrix_component_trace( + matrix_trace, 0, IterationChannel::Grid, "vxc_dft", + dft_vxc); + write_wavefunction_trace( + matrix_trace, 0, IterationChannel::Grid, reference); + write_occupation_trace( + matrix_trace, 0, IterationChannel::Grid, dataset->mf); + if (compute_band) + { + write_matrix_component_trace( + matrix_trace, 0, IterationChannel::Band, "h0", + band_reference_hamiltonian); + write_matrix_component_trace( + matrix_trace, 0, IterationChannel::Band, "vxc_dft", + dft_vxc_band); + write_wavefunction_trace( + matrix_trace, 0, IterationChannel::Band, + *band_reference); + write_occupation_trace( + matrix_trace, 0, IterationChannel::Band, + dataset->mf_band); + } + } + IterationSummary summary; + summary.iteration = 0; + summary.fermi_energy_ev = reference.get_efermi() * HA2EV; + summary.gap_ev = initial_occupations.gap * HA2EV; + summary.electron_count = initial_occupations.electron_count; + summary.has_mixing_decision = false; + write_iteration_summary(trace, summary); + write_eigenvalue_trace(eigenvalue_trace, 0, + IterationChannel::Grid, reference, + dataset->pbc.kfrac_list); + if (compute_band) + { + write_eigenvalue_trace( + eigenvalue_trace, 0, IterationChannel::Band, + *band_reference, dataset->kfrac_band_list); + } + }); + + bool converged = false; + int completed_iterations = 0; + for (int iteration = 1; + iteration <= driver_params.qsgw_max_iter; ++iteration) + { + completed_iterations = iteration; + const EigenvalueSnapshot previous = eigenvalue_snapshot(dataset->mf); + dataset->invalidate_compute_objects(); + if (update_head) + { + dataset->velocity_matrix = reference_velocity; + dataset->p_headwing.reset(); + initialize_ds_tfgrids(*dataset, opts); + // Legacy option-4 head reused the first k-point occupation row. + // Limit that compatibility behavior to head construction. + ScopedLegacyHeadOccupations legacy_head_occupations(dataset->mf); + initialize_ds_headwing(*dataset, opts, false); + } + h.build_g0w0_sigma(opts); + if (!dataset->p_exx || !dataset->p_g0w0) + throw LIBRPA_RUNTIME_ERROR( + "QSGW failed to build live EXX/Sigma real-space objects"); + if (update_head && !dataset->p_headwing) + throw LIBRPA_RUNTIME_ERROR( + "QSGW head-only object was not rebuilt on the live frequency grid"); + + { + // The upstream projection path uses the output flag as its + // full-matrix retention gate. Restore it before leaving this + // scope so QSGW does not change the user-facing output setting. + ScopedSigmaMatrixRetention retain_sigma_matrices( + dataset->p_g0w0->output_sigc_ks_mat_kf); + ScopedReferenceEigenvectors fixed_basis_projection( + dataset->mf, reference); + dataset->p_exx->build_KS_kgrid_blacs( + dataset->blacs_h, + opts.use_gpu_replace_scalapack == LIBRPA_SWITCH_ON); + dataset->p_g0w0->build_sigc_matrix_KS_kgrid_blacs( + dataset->blacs_h, + opts.use_gpu_replace_scalapack == LIBRPA_SWITCH_ON); + } + const std::vector frequencies = + dataset->tfg.get_freq_nodes(); + const SigmaMatrixMap sigma = collect_sigma_root( + *dataset->p_g0w0, reference, frequencies, dataset->comm_h); + + SpinKMatrixMap exchange; + collective_root_stage(dataset->comm_h, "QSGW EXX collection", [&] { + exchange = copy_exx_root(*dataset->p_exx, reference); + }); + + SigmaMatrixMap sigma_band; + SpinKMatrixMap exchange_band; + if (compute_band) + { + dataset->p_exx->reset_kspace(); + dataset->p_g0w0->reset_kspace(); + const auto bvk_remap = + librpa_int::qsgw::build_legacy_band_bvk_remap( + dataset->atoms, dataset->pbc, opts.option_bvk_remap); + { + ScopedSigmaMatrixRetention retain_sigma_matrices( + dataset->p_g0w0->output_sigc_ks_mat_kf); + dataset->p_exx->build_KS_band_blacs( + band_reference->get_eigenvectors(), + dataset->kfrac_band_list, bvk_remap, + dataset->blacs_h, + opts.use_gpu_replace_scalapack == + LIBRPA_SWITCH_ON); + dataset->p_g0w0->build_sigc_matrix_KS_band_blacs( + band_reference->get_eigenvectors(), + dataset->kfrac_band_list, bvk_remap, + dataset->blacs_h, + opts.use_gpu_replace_scalapack == + LIBRPA_SWITCH_ON); + } + sigma_band = collect_sigma_root( + *dataset->p_g0w0, *band_reference, + frequencies, dataset->comm_h); + collective_root_stage( + dataset->comm_h, "QSGW band EXX collection", [&] { + exchange_band = copy_exx_root( + *dataset->p_exx, *band_reference); + }); + } + + SpinKMatrixMap mixed_hamiltonian; + SpinKMatrixMap mixed_band_hamiltonian; + SpinKMatrixMap band_exchange_output; + double residual_l2 = 0.0; + double residual_max = 0.0; + std::optional mixing_decision; + std::string matrix_rows; + collective_root_stage(dataset->comm_h, "QSGW Hamiltonian update", [&] { + const SpinKMatrixMap correlation = build_correlation_map( + dataset->mf, sigma, frequencies, opts); + SpinKMatrixMap correlation_band; + if (compute_band) + { + correlation_band = build_correlation_map( + dataset->mf_band, sigma_band, frequencies, opts); + } + const SpinKMatrixMap raw_uncut = assemble_effective_hamiltonian( + reference_hamiltonian, dft_vxc, exchange, correlation); + const SpinKMatrixMap raw = compute_band + ? apply_hamiltonian_cut( + raw_uncut, reference_hamiltonian, dataset->mf, + cut_options) + : raw_uncut; + SpinKMatrixMap raw_band; + if (compute_band) + { + band_exchange_output = exchange_band; + const SpinKMatrixMap raw_band_uncut = + assemble_effective_hamiltonian( + band_reference_hamiltonian, dft_vxc_band, + exchange_band, correlation_band); + raw_band = apply_hamiltonian_cut( + raw_band_uncut, band_reference_hamiltonian, + dataset->mf_band, cut_options); + } + const auto residual = measure_spin_k_hamiltonian_residual( + raw, current_hamiltonian); + residual_l2 = residual.l2; + residual_max = residual.maximum; + if (mixer) + { + SpinKHamiltonianMixResult result = compute_band + ? mixer->mix(raw, raw_band) + : mixer->mix(raw); + mixed_hamiltonian = std::move(result.grid); + if (compute_band) + { + if (!result.band) + throw std::runtime_error( + "QSGW synchronized mixer did not return a band Hamiltonian"); + mixed_band_hamiltonian = std::move(*result.band); + } + residual_l2 = result.residual_l2; + residual_max = result.residual_max; + mixing_decision = std::move(result.decision); + } + else + { + mixed_hamiltonian = raw; + if (compute_band) mixed_band_hamiltonian = raw_band; + } + if (compute_band) + { + mixed_hamiltonian = apply_hamiltonian_cut( + mixed_hamiltonian, reference_hamiltonian, dataset->mf, + cut_options); + mixed_band_hamiltonian = apply_hamiltonian_cut( + mixed_band_hamiltonian, band_reference_hamiltonian, + dataset->mf_band, cut_options); + if (mixer) + { + // The cut is an exact constraint, not a slowly mixed + // residual. Keep the linear mixer's next input identical + // to the Hamiltonian used for diagonalization. + mixer->initialize(mixed_hamiltonian, + mixed_band_hamiltonian); + } + } + current_hamiltonian = mixed_hamiltonian; + if (compute_band) + current_band_hamiltonian = mixed_band_hamiltonian; + + if (driver_params.qsgw_write_iteration_matrices) + { + std::ostringstream rows; + write_frequency_matrix_component_trace( + rows, iteration, IterationChannel::Grid, + "sigma_c_iw", sigma); + write_matrix_component_trace( + rows, iteration, IterationChannel::Grid, "exx", + exchange); + write_matrix_component_trace( + rows, iteration, IterationChannel::Grid, "vc", + correlation); + write_matrix_component_trace( + rows, iteration, IterationChannel::Grid, "raw_h", raw); + write_matrix_component_trace( + rows, iteration, IterationChannel::Grid, "mixed_h", + mixed_hamiltonian); + if (compute_band) + { + write_matrix_component_trace( + rows, iteration, IterationChannel::Band, "exx", + exchange_band); + write_matrix_component_trace( + rows, iteration, IterationChannel::Band, "vc", + correlation_band); + write_matrix_component_trace( + rows, iteration, IterationChannel::Band, "raw_h", + raw_band); + write_matrix_component_trace( + rows, iteration, IterationChannel::Band, "mixed_h", + mixed_band_hamiltonian); + } + matrix_rows = rows.str(); + } + }); + + broadcast_spin_k_matrix_map( + mixed_hamiltonian, 0, dataset->comm_h); + if (compute_band) + { + broadcast_spin_k_matrix_map( + mixed_band_hamiltonian, 0, dataset->comm_h); + } + const FixedBasisDiagonalizationResult diagonalization = + diagonalize_in_reference_basis( + dataset->mf, reference, mixed_hamiltonian); + std::optional band_diagonalization; + if (compute_band) + { + band_diagonalization = diagonalize_in_reference_basis( + dataset->mf_band, *band_reference, + mixed_band_hamiltonian); + dataset->is_band_calc_done = true; + } + const OccupationResult occupations = update_qsgw_occupations( + dataset->mf, reference, dataset->pbc.weight_k, electron_count); + if (compute_band) + dataset->mf_band.get_efermi() = occupations.chemical_potential; + const double maximum_change_ev = + max_eigenvalue_change(dataset->mf, previous) * HA2EV; + + int converged_flag = 0; + collective_root_stage(dataset->comm_h, "QSGW reporting", [&] { + converged_flag = qsgw_iteration_converged( + iteration, driver_params.qsgw_min_iter, + maximum_change_ev, + driver_params.qsgw_convergence_tolerance_ev) + ? 1 + : 0; + IterationSummary summary; + summary.iteration = iteration; + summary.maximum_eigenvalue_change_ev = maximum_change_ev; + summary.residual_l2_ha = residual_l2; + summary.residual_max_ha = residual_max; + summary.fermi_energy_ev = + occupations.chemical_potential * HA2EV; + summary.gap_ev = occupations.gap * HA2EV; + summary.electron_count = occupations.electron_count; + summary.converged = converged_flag != 0; + summary.has_mixing_decision = mixing_decision.has_value(); + if (mixing_decision) + { + summary.requested_mode = mixing_decision->requested_mode; + summary.applied_mode = mixing_decision->applied_mode; + summary.beta = mixing_decision->beta; + summary.fell_back = mixing_decision->fell_back; + summary.reciprocal_condition = + mixing_decision->reciprocal_condition; + summary.coefficients = mixing_decision->coefficients; + summary.fallback_reason = + mixing_decision->fallback_reason; + } + write_iteration_summary(trace, summary); + write_eigenvalue_trace( + eigenvalue_trace, iteration, IterationChannel::Grid, + dataset->mf, dataset->pbc.kfrac_list); + if (compute_band) + { + write_eigenvalue_trace( + eigenvalue_trace, iteration, IterationChannel::Band, + dataset->mf_band, dataset->kfrac_band_list); + for (int spin = 0; + spin < dataset->mf_band.get_n_spins(); ++spin) + { + const auto make_filename = [&](const std::string& prefix) { + std::ostringstream name; + name << prefix << spin + 1 << '_' << iteration + << ".dat"; + return join_path(opts.output_dir, name.str()); + }; + std::ofstream ks_output( + make_filename("KS_band_spin_")); + std::ofstream exx_output( + make_filename("EXX_band_spin_")); + std::ofstream qsgw_output( + make_filename("QSGW_band_spin_")); + write_qsgw_band_spin_tables( + ks_output, exx_output, qsgw_output, + dataset->mf_band, *band_reference, + dataset->kfrac_band_list, + band_reference_hamiltonian, dft_vxc_band, + band_exchange_output, spin, + occupations.chemical_potential); + } + } + if (driver_params.qsgw_write_iteration_matrices) + { + matrix_trace << matrix_rows; + write_matrix_component_trace( + matrix_trace, iteration, IterationChannel::Grid, + "rotation_u", diagonalization.unitary); + write_wavefunction_trace( + matrix_trace, iteration, IterationChannel::Grid, + dataset->mf); + write_occupation_trace( + matrix_trace, iteration, IterationChannel::Grid, + dataset->mf); + if (compute_band) + { + write_matrix_component_trace( + matrix_trace, iteration, IterationChannel::Band, + "rotation_u", band_diagonalization->unitary); + write_wavefunction_trace( + matrix_trace, iteration, IterationChannel::Band, + dataset->mf_band); + write_occupation_trace( + matrix_trace, iteration, IterationChannel::Band, + dataset->mf_band); + } + } + trace.flush(); + eigenvalue_trace.flush(); + if (driver_params.qsgw_write_iteration_matrices) + matrix_trace.flush(); + lib_printf( + "QSGW iteration %d: max_delta=% .8e eV gap=% .8f eV mixer=%s%s\n", + iteration, maximum_change_ev, occupations.gap * HA2EV, + driver_params.qsgw_mixer.c_str(), + converged_flag ? ", converged" : ""); + }); + dataset->comm_h.bcast(&converged_flag, 1, 0); + converged = converged_flag != 0; + if (converged) break; + } + + if (dataset->comm_h.is_root()) + { + if (!converged) + lib_printf(LIBRPA_VERBOSE_WARN, + "QSGW reached qsgw_max_iter=%d without convergence\n", + driver_params.qsgw_max_iter); + lib_printf("QSGW completed iterations: %d\n", completed_iterations); + } + profiler.stop("qsgw"); +} + +} // namespace + +void driver::task_qsgw() +{ + run_qsgw_stage_one(false); +} + +void driver::task_qsgw_band() +{ + run_qsgw_stage_one(true); +} diff --git a/driver/test/CMakeLists.txt b/driver/test/CMakeLists.txt index c8b7b03e..17f14338 100644 --- a/driver/test/CMakeLists.txt +++ b/driver/test/CMakeLists.txt @@ -4,3 +4,13 @@ add_executable( ../input_parser.cpp ) add_test(NAME test_input_parser COMMAND $) + +add_executable( + test_qsgw_inputfile + test_qsgw_inputfile.cpp + ../driver.cpp + ../input_parser.cpp + ../inputfile.cpp +) +target_link_libraries(test_qsgw_inputfile PRIVATE rpa_lib) +add_test(NAME test_qsgw_inputfile COMMAND $) diff --git a/driver/test/test_qsgw_inputfile.cpp b/driver/test/test_qsgw_inputfile.cpp new file mode 100644 index 00000000..c63123a7 --- /dev/null +++ b/driver/test/test_qsgw_inputfile.cpp @@ -0,0 +1,341 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "../driver.h" +#include "../inputfile.h" + +#include +#include +#include +#include +#include +#include + +namespace +{ + +class TemporaryInput +{ +public: + explicit TemporaryInput(const std::string& contents) + : path_("test_qsgw_inputfile.tmp") + { + std::ofstream output(path_, std::ios::trunc); + output << contents; + } + + ~TemporaryInput() { std::remove(path_.c_str()); } + const std::string& path() const { return path_; } + +private: + std::string path_; +}; + +void reset() +{ + driver::driver_params = driver::DriverParams{}; + driver::opts = librpa::Options{}; + driver::n_spinor = 1; +} + +void parse(const std::string& contents) +{ + reset(); + const TemporaryInput input(contents); + parse_inputfile_to_params(input.path()); +} + +template +void assert_throws(Function&& function) +{ + bool threw = false; + try + { + function(); + } + catch (const std::exception&) + { + threw = true; + } + assert(threw); +} + +template +void assert_throws_with_message(Function&& function, + const std::string& expected) +{ + bool threw = false; + try + { + function(); + } + catch (const std::exception& error) + { + threw = true; + assert(std::string(error.what()).find(expected) != + std::string::npos); + } + assert(threw); +} + +std::string valid_qsgw_prefix() +{ + return "task = qsgw\n"; +} + +std::string valid_qsgw_band_prefix() +{ + return "task = qsgw_band\n"; +} + +void test_defaults_and_explicit_linear_mixing() +{ + const driver::DriverParams defaults; + assert(defaults.qsgw_input_contract == "qsgw_input.contract"); + assert(defaults.qsgw_mixer == "none"); + assert(std::abs(defaults.qsgw_mixing_beta - 0.2) < 1.0e-14); + assert(defaults.qsgw_min_iter == 1); + assert(defaults.qsgw_max_iter == 10); + assert(defaults.qsgw_band0_unoccupied_keep == 10); + assert(defaults.qsgw_band0_cut_mode == 0); + assert(std::abs(defaults.qsgw_band0_cut_shift_ha - 20.0) < 1.0e-14); + + parse(valid_qsgw_prefix() + + "qsgw_input_contract = manifests/si.contract\n" + "qsgw_mixer = NONE\n" + "qsgw_mixing_beta = 3.5D-1\n" + "qsgw_min_iter = 5\n" + "qsgw_max_iter = 10\n" + "qsgw_write_iteration_matrices = true\n" + "qsgw_convergence_tolerance_ev = 2e-5\n"); + assert(driver::driver_params.qsgw_input_contract == + "manifests/si.contract"); + assert(driver::driver_params.qsgw_mixer == "none"); + assert(std::abs(driver::driver_params.qsgw_mixing_beta - 0.35) < + 1.0e-14); + assert(driver::driver_params.qsgw_min_iter == 5); + assert(driver::driver_params.qsgw_max_iter == 10); + assert(driver::driver_params.qsgw_write_iteration_matrices); + const std::string formatted = driver::driver_params.format(); + assert(formatted.find("qsgw_input_contract") != std::string::npos); + assert(formatted.find("qsgw_write_iteration_matrices = true") != + std::string::npos); +} + +void test_qsgw_accepts_only_same_grid_head_only() +{ + for (const std::string& prefix : + {valid_qsgw_prefix(), valid_qsgw_band_prefix()}) + { + parse(prefix + + "replace_w_head = true\n" + "option_dielect_func = 4\n"); + assert(driver::opts.replace_w_head == LIBRPA_SWITCH_ON); + assert(driver::opts.option_dielect_func == 4); + assert(!driver::driver_params.use_pyatb); + + assert_throws_with_message([&] { + parse(prefix + + "replace_w_head = true\n" + "option_dielect_func = 3\n"); + }, "QSGW supports only analytic head-only mode"); + assert_throws_with_message([&] { + parse(prefix + + "replace_w_head = true\n" + "option_dielect_func = 4\n" + "use_pyatb = true\n"); + }, "QSGW independent PyATB head updates are unsupported"); + assert_throws_with_message([&] { + parse(prefix + "use_pyatb = true\n"); + }, "QSGW independent PyATB head updates are unsupported"); + } +} + +void test_qsgw_requires_replicated_scf_wavefunctions() +{ + const std::string message = + "QSGW fixed-basis iteration requires replicated SCF wavefunctions"; + for (const std::string& prefix : + {valid_qsgw_prefix(), valid_qsgw_band_prefix()}) + { + assert_throws_with_message([&] { + parse(prefix + "use_kpara_scf_eigvec = true\n"); + }, message); + } +} + +void test_qsgw_band_uses_the_qsgw_contract_and_iteration_controls() +{ + parse(valid_qsgw_band_prefix() + + "qsgw_input_contract = manifests/si-band.contract\n" + "qsgw_mixer = LINEAR\n" + "qsgw_mixing_beta = 0.27\n" + "qsgw_min_iter = 2\n" + "qsgw_max_iter = 5\n" + "qsgw_band0_unoccupied_keep = 7\n" + "qsgw_band0_cut_mode = 1\n" + "qsgw_band0_cut_shift_ha = 3.5\n" + "qsgw_write_iteration_matrices = true\n"); + assert(driver::driver_params.qsgw_input_contract == + "manifests/si-band.contract"); + assert(driver::driver_params.qsgw_mixer == "linear"); + assert(std::abs(driver::driver_params.qsgw_mixing_beta - 0.27) < + 1.0e-14); + assert(driver::driver_params.qsgw_min_iter == 2); + assert(driver::driver_params.qsgw_max_iter == 5); + assert(driver::driver_params.qsgw_band0_unoccupied_keep == 7); + assert(driver::driver_params.qsgw_band0_cut_mode == 1); + assert(std::abs(driver::driver_params.qsgw_band0_cut_shift_ha - 3.5) < + 1.0e-14); + assert(driver::driver_params.qsgw_write_iteration_matrices); + assert(driver::driver_params.format().find( + "qsgw_input_contract = manifests/si-band.contract") != + std::string::npos); + assert(driver::driver_params.format().find( + "qsgw_band0_cut_mode = 1") != std::string::npos); +} + +void test_qsgw_does_not_require_the_g0w0_matrix_dump_switch() +{ + parse("task = qsgw\noutput_gw_sigc_ks_mat_kf = false\n"); + assert(driver::opts.output_gw_sigc_ks_mat_kf == LIBRPA_SWITCH_OFF); + + parse("task = qsgw_band\n"); + assert(driver::opts.output_gw_sigc_ks_mat_kf == LIBRPA_SWITCH_OFF); +} + +void test_qsgw_preserves_upstream_crystal_symmetry_options() +{ + parse(valid_qsgw_prefix() + "use_symmetry_exx = true\n"); + assert(driver::opts.use_symmetry_exx == LIBRPA_SWITCH_ON); + assert(driver::opts.use_symmetry_gw == LIBRPA_SWITCH_OFF); + assert(driver::opts.use_symmetry_rpa == LIBRPA_SWITCH_OFF); + + parse(valid_qsgw_prefix() + "use_symmetry_gw = true\n"); + assert(driver::opts.use_symmetry_exx == LIBRPA_SWITCH_OFF); + assert(driver::opts.use_symmetry_gw == LIBRPA_SWITCH_ON); + assert(driver::opts.use_symmetry_rpa == LIBRPA_SWITCH_ON); + + parse(valid_qsgw_prefix() + "use_symmetry_rpa = true\n"); + assert(driver::opts.use_symmetry_exx == LIBRPA_SWITCH_OFF); + assert(driver::opts.use_symmetry_gw == LIBRPA_SWITCH_OFF); + assert(driver::opts.use_symmetry_rpa == LIBRPA_SWITCH_ON); + + parse(valid_qsgw_prefix() + + "use_symmetry_exx = true\n" + "use_symmetry_gw = true\n" + "use_symmetry_rpa = true\n"); + assert(driver::opts.use_symmetry_exx == LIBRPA_SWITCH_ON); + assert(driver::opts.use_symmetry_gw == LIBRPA_SWITCH_ON); + assert(driver::opts.use_symmetry_rpa == LIBRPA_SWITCH_ON); +} + +void test_staged_qsgw_rejects_ambiguous_or_unsupported_inputs() +{ + for (const std::string& removed : { + "qsgw_update_hartree = false\n", + "qsgw_hartree_coulomb = full\n", + "qsgw_hartree_normalization = weighted_occupations\n", + "qsgw_export_hamiltonian_for_pyatb = false\n", + "qsgw_hr_export_full_mp_rgrid = false\n"}) + { + assert_throws_with_message([&] { + parse(valid_qsgw_prefix() + removed); + }, "is not supported by the head-only QSGW workflow"); + } + assert_throws([&] { + parse(valid_qsgw_prefix() + "qsgw_mixer = pulay\n"); + }); + assert_throws([&] { + parse(valid_qsgw_prefix() + "qsgw_mixing_beta = 0\n"); + }); + assert_throws([&] { + parse(valid_qsgw_prefix() + + "qsgw_min_iter = 5\nqsgw_max_iter = 4\n"); + }); + assert_throws([&] { + parse(valid_qsgw_band_prefix() + + "qsgw_band0_unoccupied_keep = -1\n"); + }); + assert_throws([&] { + parse(valid_qsgw_band_prefix() + + "qsgw_band0_cut_mode = 3\n"); + }); + assert_throws([&] { + parse(valid_qsgw_band_prefix() + + "qsgw_band0_cut_shift_ha = nan\n"); + }); + assert_throws_with_message([&] { + parse(valid_qsgw_prefix() + + "qsgw_max_iter = ten\n"); + }, "qsgw_max_iter must be a valid integer"); + assert_throws_with_message([&] { + parse(valid_qsgw_prefix() + + "qsgw_mixing_beta = 0.2garbage\n"); + }, "qsgw_mixing_beta must be a valid floating-point value"); + assert_throws_with_message([&] { + parse(valid_qsgw_prefix() + + "qsgw_max_iter = 5\n" + "qsgw_max_iter = invalid\n"); + }, "qsgw_max_iter must be a valid integer"); + assert_throws([&] { + parse(valid_qsgw_prefix() + + "replace_w_head = true\noption_dielect_func = 0\n"); + }); + assert_throws([&] { + parse(valid_qsgw_prefix() + + "replace_w_head = true\noption_dielect_func = 2\n"); + }); +} + +void test_g0w0_parser_defaults_are_unchanged() +{ + parse( + "task = g0w0\n" + "qsgw_mixer = invalid\n" + "qsgw_mixing_beta = -1\n" + "qsgw_band0_unoccupied_keep = -1\n" + "qsgw_band0_cut_mode = 99\n" + "qsgw_band0_cut_shift_ha = nan\n" + "use_symmetry_exx = true\n" + "use_symmetry_gw = true\n" + "use_symmetry_rpa = true\n"); + assert(driver::driver_params.qsgw_mixer == "none"); + assert(std::abs(driver::driver_params.qsgw_mixing_beta - 0.2) < + 1.0e-14); + assert(driver::driver_params.qsgw_band0_unoccupied_keep == 10); + assert(driver::driver_params.qsgw_band0_cut_mode == 0); + assert(std::abs(driver::driver_params.qsgw_band0_cut_shift_ha - 20.0) < + 1.0e-14); + assert(driver::opts.output_gw_sigc_ks_mat_kf == LIBRPA_SWITCH_OFF); + assert(driver::opts.use_symmetry_exx == LIBRPA_SWITCH_ON); + assert(driver::opts.use_symmetry_gw == LIBRPA_SWITCH_ON); + assert(driver::opts.use_symmetry_rpa == LIBRPA_SWITCH_ON); + assert(driver::driver_params.format().find("qsgw_") == + std::string::npos); + + parse( + "task = g0w0\n" + "replace_w_head = true\n" + "option_dielect_func = 3\n" + "use_pyatb = true\n"); + assert(driver::opts.replace_w_head == LIBRPA_SWITCH_ON); + assert(driver::opts.option_dielect_func == 3); + assert(driver::driver_params.use_pyatb); +} + +} // namespace + +int main() +{ + test_defaults_and_explicit_linear_mixing(); + test_qsgw_accepts_only_same_grid_head_only(); + test_qsgw_requires_replicated_scf_wavefunctions(); + test_qsgw_band_uses_the_qsgw_contract_and_iteration_controls(); + test_qsgw_does_not_require_the_g0w0_matrix_dump_switch(); + test_qsgw_preserves_upstream_crystal_symmetry_options(); + test_staged_qsgw_rejects_ambiguous_or_unsupported_inputs(); + test_g0w0_parser_defaults_are_unchanged(); + return 0; +} diff --git a/regression_tests/backend/comparisons/cmp_qsgw.py b/regression_tests/backend/comparisons/cmp_qsgw.py new file mode 100644 index 00000000..d3dd5521 --- /dev/null +++ b/regression_tests/backend/comparisons/cmp_qsgw.py @@ -0,0 +1,621 @@ +import math +import re + + +__all__ = [ + "eigenvalue_trace", + "iteration_summary", +] + + +HA2EV = 27.211386245988 + +EIGENVALUE_HEADER = ( + "# iter channel spin kpoint kx ky kz band energy_eV" +) +SUMMARY_HEADER = ( + "# iter max_delta_eV residual_l2_Ha residual_max_Ha " + "efermi_eV gap_eV electron_count requested_mode applied_mode beta " + "fallback rcond coefficient_l1 coefficient_count converged " + "coefficients fallback_reason" +) + +CONTRACT_KEYS = frozenset(( + "qsgw_contract_version", + "fixed_basis", + "live_update", + "velocity", + "head", + "wing", + "symmetry", + "hartree", + "band", + "h_qsgw_cut", + "qsgw_band0_unoccupied_keep", + "qsgw_band0_cut_mode", + "qsgw_band0_cut_shift_ha", + "qsgw_input_contract", + "qsgw_input_contract_sha256", + "qsgw_mixer", + "qsgw_mixing_beta", +)) +REQUIRED_CONTRACT_KEYS = frozenset(( + "qsgw_contract_version", + "fixed_basis", + "live_update", + "velocity", + "head", + "wing", + "symmetry", + "hartree", + "band", + "h_qsgw_cut", + "qsgw_input_contract", + "qsgw_input_contract_sha256", + "qsgw_mixer", + "qsgw_mixing_beta", +)) + + +def eigenvalue_trace(tolerance_ha="1e-6", coordinate_tolerance="1e-12", + precision="3"): + """Compare keyed QSGW eigenvalue traces using a Hartree tolerance.""" + tolerance_ha = _positive_float( + tolerance_ha, "tolerance_ha", allow_zero=True) + coordinate_tolerance = _positive_float( + coordinate_tolerance, "coordinate_tolerance", allow_zero=True) + precision = int(precision) + + def inner(test_files, reference_files): + try: + pairs = _paired_file_texts(test_files, reference_files) + maximum_energy = 0.0 + maximum_coordinate = 0.0 + maximum_location = None + nvalues = 0 + for filename, test_text, reference_text in pairs: + contract = _require_same_contract( + test_text, reference_text, filename) + test_rows = _parse_eigenvalue_trace( + test_text, "test {}".format(filename)) + reference_rows = _parse_eigenvalue_trace( + reference_text, "reference {}".format(filename)) + _validate_eigenvalue_trajectory( + test_rows, contract, "test {}".format(filename)) + _validate_eigenvalue_trajectory( + reference_rows, contract, + "reference {}".format(filename)) + mismatch = _key_mismatch(test_rows, reference_rows) + if mismatch is not None: + return False, ( + "{}: eigenvalue key set mismatch; {}" + .format(filename, mismatch) + ) + for key in sorted(test_rows): + test_coordinate, test_energy = test_rows[key] + reference_coordinate, reference_energy = \ + reference_rows[key] + coordinate_difference = max( + abs(x - y) for x, y in + zip(test_coordinate, reference_coordinate)) + if coordinate_difference > coordinate_tolerance: + return False, ( + "{}: k-point coordinate mismatch at {}: " + "{:.6E} > {:.6E}" + .format(filename, key, coordinate_difference, + coordinate_tolerance) + ) + energy_difference = ( + abs(test_energy - reference_energy) / HA2EV + ) + if energy_difference > maximum_energy: + maximum_energy = energy_difference + maximum_location = (filename, key) + maximum_coordinate = max( + maximum_coordinate, coordinate_difference) + nvalues += 1 + message = ( + "max abs eigenvalue diff = {:.{p}E} Ha " + "(tol = {:.{p}E} Ha), max k-point coordinate diff = " + "{:.{p}E} over {} values" + ).format(maximum_energy, tolerance_ha, maximum_coordinate, + nvalues, p=precision) + if maximum_location is not None: + message += ", max at {} key {}".format(*maximum_location) + return maximum_energy <= tolerance_ha, message + except (TypeError, ValueError) as error: + return False, str(error) + + return inner + + +def iteration_summary(energy_tolerance_ev="1e-5", + residual_tolerance_ha="1e-8", + scalar_tolerance="1e-10", + coefficient_tolerance="1e-12", precision="3"): + """Compare QSGW iteration summaries with field-specific tolerances.""" + energy_tolerance_ev = _positive_float( + energy_tolerance_ev, "energy_tolerance_ev", allow_zero=True) + residual_tolerance_ha = _positive_float( + residual_tolerance_ha, "residual_tolerance_ha", allow_zero=True) + scalar_tolerance = _positive_float( + scalar_tolerance, "scalar_tolerance", allow_zero=True) + coefficient_tolerance = _positive_float( + coefficient_tolerance, "coefficient_tolerance", allow_zero=True) + precision = int(precision) + + energy_fields = ("max_delta_eV", "efermi_eV", "gap_eV") + residual_fields = ("residual_l2_Ha", "residual_max_Ha") + scalar_fields = ("electron_count", "beta", "rcond") + exact_fields = ( + "requested_mode", "applied_mode", "fallback", "coefficient_count", + "converged", "fallback_reason", + ) + + def inner(test_files, reference_files): + try: + pairs = _paired_file_texts(test_files, reference_files) + maxima = { + name: 0.0 + for name in energy_fields + residual_fields + scalar_fields + } + maximum_coefficient = 0.0 + niterations = 0 + for filename, test_text, reference_text in pairs: + _require_same_contract(test_text, reference_text, filename) + test_rows = _parse_iteration_summary( + test_text, "test {}".format(filename)) + reference_rows = _parse_iteration_summary( + reference_text, "reference {}".format(filename)) + mismatch = _key_mismatch(test_rows, reference_rows) + if mismatch is not None: + return False, ( + "{}: iteration key set mismatch; {}" + .format(filename, mismatch) + ) + for iteration in sorted(test_rows): + test_row = test_rows[iteration] + reference_row = reference_rows[iteration] + for field in exact_fields: + if test_row[field] != reference_row[field]: + return False, ( + "{}: iteration {} {} mismatch: {} != {}" + .format(filename, iteration, field, + test_row[field], + reference_row[field]) + ) + for fields, tolerance in ( + (energy_fields, energy_tolerance_ev), + (residual_fields, residual_tolerance_ha), + (scalar_fields, scalar_tolerance), + ): + for field in fields: + difference = abs( + test_row[field] - reference_row[field]) + maxima[field] = max(maxima[field], difference) + if difference > tolerance: + return False, ( + "{}: iteration {} {} difference " + "{:.6E} exceeds {:.6E}" + .format(filename, iteration, field, + difference, tolerance) + ) + test_coefficients = test_row["coefficients"] + reference_coefficients = \ + reference_row["coefficients"] + if len(test_coefficients) != len( + reference_coefficients): + return False, ( + "{}: iteration {} coefficient count mismatch" + .format(filename, iteration) + ) + for index, (test_value, reference_value) in enumerate( + zip(test_coefficients, + reference_coefficients)): + difference = abs(test_value - reference_value) + maximum_coefficient = max( + maximum_coefficient, difference) + if difference > coefficient_tolerance: + return False, ( + "{}: iteration {} coefficient {} " + "difference {:.6E} exceeds {:.6E}" + .format(filename, iteration, index, + difference, + coefficient_tolerance) + ) + coefficient_l1_difference = abs( + test_row["coefficient_l1"] - + reference_row["coefficient_l1"]) + if coefficient_l1_difference > coefficient_tolerance: + return False, ( + "{}: iteration {} coefficient_l1 difference " + "{:.6E} exceeds {:.6E}" + .format(filename, iteration, + coefficient_l1_difference, + coefficient_tolerance) + ) + niterations += 1 + + message = ( + "max gap diff = {:.{p}E} eV, " + "max eigenvalue-change diff = {:.{p}E} eV, " + "max residual diff = {:.{p}E} Ha, " + "max coefficient diff = {:.{p}E} over {} iterations" + ).format( + maxima["gap_eV"], maxima["max_delta_eV"], + max(maxima[name] for name in residual_fields), + maximum_coefficient, niterations, p=precision) + return True, message + except (TypeError, ValueError) as error: + return False, str(error) + + return inner + + +def _positive_float(value, label, allow_zero=False): + result = float(value) + if not math.isfinite(result) or result < 0.0 or ( + result == 0.0 and not allow_zero): + relation = "nonnegative" if allow_zero else "positive" + raise ValueError("{} must be finite and {}".format(label, relation)) + return result + + +def _paired_file_texts(test_files, reference_files): + filenames = set(test_files) | set(reference_files) + if not filenames: + raise ValueError("no files found") + pairs = [] + for filename in sorted(filenames, key=str): + if filename not in test_files or filename not in reference_files: + raise ValueError("missing file {}".format(filename)) + pairs.append(( + filename, + _single_text(test_files[filename], filename), + _single_text(reference_files[filename], filename), + )) + return pairs + + +def _single_text(raw, filename): + if isinstance(raw, str): + return raw + values = list(raw) + if len(values) != 1 or not isinstance(values[0], str): + raise ValueError( + "{} must contain one complete trace".format(filename)) + return values[0] + + +def _require_header(text, expected, label): + comments = [ + line.strip() for line in text.splitlines() + if line.strip().startswith("#") + ] + if expected not in comments: + raise ValueError("{}: missing trace header".format(label)) + + +def _require_same_contract(test_text, reference_text, filename): + test_contract = _parse_contract( + test_text, "test {}".format(filename)) + reference_contract = _parse_contract( + reference_text, "reference {}".format(filename)) + if test_contract != reference_contract: + differing = sorted( + key for key in set(test_contract) | set(reference_contract) + if test_contract.get(key) != reference_contract.get(key) + ) + raise ValueError( + "{}: QSGW contract differs for {}" + .format(filename, differing)) + return test_contract + + +def _parse_contract(text, label): + values = {} + for line_number, line in enumerate(text.splitlines(), 1): + line = line.strip() + if not line.startswith("#"): + continue + fields = line[1:].strip().split(None, 1) + if len(fields) != 2 or fields[0] not in CONTRACT_KEYS: + continue + key, value = fields[0], fields[1].strip() + if key in values: + raise ValueError( + "{}:{}: duplicate QSGW contract key {}" + .format(label, line_number, key)) + values[key] = value + + missing = sorted(REQUIRED_CONTRACT_KEYS - set(values)) + if missing: + raise ValueError( + "{}: missing QSGW contract keys {}".format(label, missing)) + try: + version = int(values["qsgw_contract_version"]) + except ValueError as error: + raise ValueError( + "{}: invalid QSGW contract version".format(label)) from error + if version != 6: + raise ValueError( + "{}: unsupported QSGW contract version {}" + .format(label, version)) + values["qsgw_contract_version"] = version + + for key, required in ( + ("fixed_basis", "immutable_mf0"), + ("live_update", "eigenvalues_wfc"), + ("wing", "disabled_stage1"), + ("hartree", "disabled"), + ): + if values[key] != required: + raise ValueError( + "{}: invalid {} QSGW contract".format(label, key)) + + symmetry = values["symmetry"] + if re.fullmatch( + r"exx_(?:on|off)_gw_(?:on|off)_rpa_(?:on|off)", + symmetry) is None: + raise ValueError( + "{}: invalid symmetry QSGW contract".format(label)) + + head_velocity = { + "disabled_stage1": "disabled_stage1", + "scf_grid_analytic_live": "fixed_reference", + } + head = values["head"] + if head not in head_velocity or values["velocity"] != \ + head_velocity[head]: + raise ValueError( + "{}: inconsistent head/velocity QSGW contract".format(label)) + + band = values["band"] + if band not in ( + "disabled_stage1", "fixed_reference_rotation_live"): + raise ValueError( + "{}: invalid band QSGW contract".format(label)) + cut_keys = ( + "qsgw_band0_unoccupied_keep", + "qsgw_band0_cut_mode", + "qsgw_band0_cut_shift_ha", + ) + cut_contract = values["h_qsgw_cut"] + if cut_contract == "disabled_non_band": + if band != "disabled_stage1" or any( + key in values for key in cut_keys): + raise ValueError( + "{}: inconsistent disabled H_QSGW cut contract" + .format(label)) + elif cut_contract == "band_postprocess": + if band != "fixed_reference_rotation_live": + raise ValueError( + "{}: H_QSGW cut requires band postprocessing" + .format(label)) + missing_cut = [key for key in cut_keys if key not in values] + if missing_cut: + raise ValueError( + "{}: enabled H_QSGW cut is missing contract fields {}" + .format(label, missing_cut)) + try: + unoccupied_keep = int( + values["qsgw_band0_unoccupied_keep"]) + cut_mode = int(values["qsgw_band0_cut_mode"]) + except ValueError as error: + raise ValueError( + "{}: invalid H_QSGW cut integer contract" + .format(label)) from error + cut_shift = _finite_float( + values["qsgw_band0_cut_shift_ha"]) + if unoccupied_keep < 0 or cut_mode not in (0, 1, 2): + raise ValueError( + "{}: invalid H_QSGW cut contract".format(label)) + values["qsgw_band0_unoccupied_keep"] = unoccupied_keep + values["qsgw_band0_cut_mode"] = cut_mode + values["qsgw_band0_cut_shift_ha"] = cut_shift + else: + raise ValueError( + "{}: invalid H_QSGW cut contract".format(label)) + + if not values["qsgw_input_contract"]: + raise ValueError( + "{}: empty QSGW input contract path".format(label)) + sha256 = values["qsgw_input_contract_sha256"].lower() + if re.fullmatch(r"[0-9a-f]{64}", sha256) is None: + raise ValueError( + "{}: invalid QSGW input contract SHA256".format(label)) + values["qsgw_input_contract_sha256"] = sha256 + + if values["qsgw_mixer"] not in ("none", "linear"): + raise ValueError( + "{}: invalid QSGW mixer contract".format(label)) + beta = _finite_float(values["qsgw_mixing_beta"]) + if not 0.0 < beta <= 1.0: + raise ValueError( + "{}: invalid QSGW mixing beta".format(label)) + values["qsgw_mixing_beta"] = beta + return values + + +def _parse_eigenvalue_trace(text, label): + _require_header(text, EIGENVALUE_HEADER, label) + rows = {} + for line_number, line in enumerate(text.splitlines(), 1): + line = line.strip() + if not line or line.startswith("#"): + continue + fields = line.split() + if len(fields) != 9: + raise ValueError( + "{}:{}: eigenvalue row has {} columns" + .format(label, line_number, len(fields))) + try: + iteration, channel, spin, kpoint = map(int, fields[:4]) + coordinate = tuple( + _finite_float(value) for value in fields[4:7]) + band = int(fields[7]) + energy = _finite_float(fields[8]) + except ValueError as error: + raise ValueError( + "{}:{}: invalid eigenvalue row" + .format(label, line_number)) from error + if min(iteration, spin, kpoint, band) < 0 or channel not in (0, 1): + raise ValueError( + "{}:{}: invalid eigenvalue index" + .format(label, line_number)) + key = (iteration, channel, spin, kpoint, band) + if key in rows: + raise ValueError( + "{}:{}: duplicate eigenvalue row {}" + .format(label, line_number, key)) + rows[key] = (coordinate, energy) + if not rows: + raise ValueError("{}: no eigenvalue rows found".format(label)) + return rows + + +def _validate_eigenvalue_trajectory(rows, contract, label): + iterations = _continuous_iterations( + {key[0] for key in rows}, label) + expected_channels = {0} + if contract["band"] == "fixed_reference_rotation_live": + expected_channels.add(1) + observed_channels = {key[1] for key in rows} + if observed_channels != expected_channels: + raise ValueError( + "{}: eigenvalue channel set differs from the QSGW contract" + .format(label)) + + baseline = {} + for channel in sorted(expected_channels): + keys = { + key[1:] for key in rows + if key[0] == 0 and key[1] == channel + } + if not keys: + raise ValueError( + "{}: iteration zero channel {} has no eigenvalues" + .format(label, channel)) + baseline[channel] = keys + + for iteration in iterations: + for channel in sorted(expected_channels): + keys = { + key[1:] for key in rows + if key[0] == iteration and key[1] == channel + } + if keys != baseline[channel]: + raise ValueError( + "{}: iteration {} channel {} eigenvalue layout " + "differs from iteration zero" + .format(label, iteration, channel)) + + +def _continuous_iterations(iterations, label): + observed = sorted(iterations) + if not observed or observed[0] != 0: + raise ValueError("{}: iteration zero is missing".format(label)) + if observed[-1] < 1: + raise ValueError( + "{}: no completed QSGW iteration is present".format(label)) + expected = list(range(observed[-1] + 1)) + if observed != expected: + raise ValueError( + "{}: QSGW iterations are not continuous: {}" + .format(label, observed)) + return observed + + +def _parse_iteration_summary(text, label): + _require_header(text, SUMMARY_HEADER, label) + rows = {} + for line_number, line in enumerate(text.splitlines(), 1): + line = line.strip() + if not line or line.startswith("#"): + continue + fields = line.split() + if len(fields) != 17: + raise ValueError( + "{}:{}: summary row has {} columns" + .format(label, line_number, len(fields))) + try: + iteration = int(fields[0]) + row = { + "max_delta_eV": _finite_float(fields[1]), + "residual_l2_Ha": _finite_float(fields[2]), + "residual_max_Ha": _finite_float(fields[3]), + "efermi_eV": _finite_float(fields[4]), + "gap_eV": _finite_float(fields[5]), + "electron_count": _finite_float(fields[6]), + "requested_mode": int(fields[7]), + "applied_mode": int(fields[8]), + "beta": _finite_float(fields[9]), + "fallback": int(fields[10]), + "rcond": _finite_float(fields[11]), + "coefficient_l1": _finite_float(fields[12]), + "coefficient_count": int(fields[13]), + "converged": int(fields[14]), + "coefficients": _parse_coefficients(fields[15]), + "fallback_reason": fields[16], + } + except ValueError as error: + raise ValueError( + "{}:{}: invalid summary row" + .format(label, line_number)) from error + nonnegative_fields = ( + "max_delta_eV", "residual_l2_Ha", "residual_max_Ha", + "gap_eV", "electron_count", "beta", "rcond", + "coefficient_l1", + ) + if iteration < 0 or any( + row[name] < 0.0 for name in nonnegative_fields): + raise ValueError( + "{}:{}: invalid summary value" + .format(label, line_number)) + if row["coefficient_count"] != len(row["coefficients"]): + raise ValueError( + "{}:{}: coefficient count is inconsistent" + .format(label, line_number)) + coefficient_l1 = sum( + abs(value) for value in row["coefficients"]) + if abs(coefficient_l1 - row["coefficient_l1"]) > \ + 1.0e-12 * max(1.0, coefficient_l1): + raise ValueError( + "{}:{}: coefficient L1 norm is inconsistent" + .format(label, line_number)) + if iteration in rows: + raise ValueError( + "{}:{}: duplicate iteration {}" + .format(label, line_number, iteration)) + rows[iteration] = row + if not rows: + raise ValueError("{}: no iteration rows found".format(label)) + _continuous_iterations(rows, label) + return rows + + +def _parse_coefficients(value): + if value == "none": + return tuple() + coefficients = tuple( + _finite_float(item) for item in value.split(",")) + if not coefficients: + raise ValueError("empty coefficient list") + return coefficients + + +def _finite_float(value): + result = float(value.replace("D", "E").replace("d", "E")) + if not math.isfinite(result): + raise ValueError("non-finite value") + return result + + +def _key_mismatch(test, reference): + test_keys = set(test) + reference_keys = set(reference) + if test_keys == reference_keys: + return None + missing = sorted(reference_keys - test_keys, key=str)[:3] + extra = sorted(test_keys - reference_keys, key=str)[:3] + return "missing={}, extra={}".format(missing, extra) diff --git a/regression_tests/backend/comparisons/test_cmp_qsgw.py b/regression_tests/backend/comparisons/test_cmp_qsgw.py new file mode 100644 index 00000000..937c0b99 --- /dev/null +++ b/regression_tests/backend/comparisons/test_cmp_qsgw.py @@ -0,0 +1,239 @@ +import unittest + +import cmp_qsgw + + +CONTRACT_HEADER = ( + "# qsgw_contract_version 6\n" + "# fixed_basis immutable_mf0\n" + "# live_update eigenvalues_wfc\n" + "# velocity disabled_stage1\n" + "# head disabled_stage1\n" + "# wing disabled_stage1\n" + "# symmetry exx_off_gw_off_rpa_off\n" + "# hartree disabled\n" + "# band disabled_stage1\n" + "# h_qsgw_cut disabled_non_band\n" + "# qsgw_input_contract qsgw_input.contract\n" + "# qsgw_input_contract_sha256 " + "a" * 64 + "\n" + "# qsgw_mixer linear\n" + "# qsgw_mixing_beta 0.2\n" +) +EIGENVALUE_HEADER = CONTRACT_HEADER + ( + "# iter channel spin kpoint kx ky kz band energy_eV\n" +) +SUMMARY_HEADER = CONTRACT_HEADER + ( + "# iter max_delta_eV residual_l2_Ha residual_max_Ha " + "efermi_eV gap_eV electron_count requested_mode applied_mode beta " + "fallback rcond coefficient_l1 coefficient_count converged " + "coefficients fallback_reason\n" +) + + +class TestQsgwEigenvalueTrace(unittest.TestCase): + + def _trace(self, energy_ev, kx=0.0): + return EIGENVALUE_HEADER + ( + "0 0 0 0 {:.17e} 0.0 0.0 0 5.00000000000000000e-1\n" + "1 0 0 0 {:.17e} 0.0 0.0 0 {:.17e}\n" + ).format(kx, kx, energy_ev) + + def _compare(self, test, reference, **kwargs): + compare = cmp_qsgw.eigenvalue_trace(**kwargs) + return compare( + {"qsgw_eigenvalues.dat": test}, + {"qsgw_eigenvalues.dat": reference}, + ) + + def test_energy_difference_within_hartree_tolerance_passes(self): + reference = self._trace(1.0) + test = self._trace( + 1.0 + 0.5 * cmp_qsgw.HA2EV * 1.0e-6) + + passed, message = self._compare( + test, reference, tolerance_ha="1e-6") + + self.assertTrue(passed, message) + self.assertIn("max abs eigenvalue diff", message) + + def test_energy_difference_above_hartree_tolerance_fails(self): + reference = self._trace(1.0) + test = self._trace( + 1.0 + 2.0 * cmp_qsgw.HA2EV * 1.0e-6) + + passed, message = self._compare( + test, reference, tolerance_ha="1e-6") + + self.assertFalse(passed) + self.assertIn("max abs eigenvalue diff", message) + + def test_kpoint_coordinate_mismatch_fails(self): + passed, message = self._compare( + self._trace(1.0, kx=1.0e-6), + self._trace(1.0), + coordinate_tolerance="1e-12", + ) + + self.assertFalse(passed) + self.assertIn("k-point coordinate", message) + + def test_band_contract_requires_band_trace_channel(self): + header = EIGENVALUE_HEADER.replace( + "# band disabled_stage1\n" + "# h_qsgw_cut disabled_non_band", + "# band fixed_reference_rotation_live\n" + "# h_qsgw_cut band_postprocess\n" + "# qsgw_band0_unoccupied_keep 2\n" + "# qsgw_band0_cut_mode 0\n" + "# qsgw_band0_cut_shift_ha 20", + ) + trace = header + ( + "0 0 0 0 0.0 0.0 0.0 0 0.5\n" + "1 0 0 0 0.0 0.0 0.0 0 1.0\n" + ) + + passed, message = self._compare(trace, trace) + + self.assertFalse(passed) + self.assertIn("channel set", message) + + +class TestQsgwIterationSummary(unittest.TestCase): + + def _trace(self, gap=1.0, applied_mode=0): + return SUMMARY_HEADER + ( + "0 0.0 0.0 0.0 -1.0 {:.17e} 8.0 " + "-1 -1 2.0e-1 0 1.0 0.0 0 0 none none\n" + "1 1.0e-3 2.0e-4 1.0e-4 -1.0 {:.17e} 8.0 " + "0 {} 2.0e-1 0 1.0 1.0 1 0 1.0 none\n" + ).format(gap, gap, applied_mode) + + def _compare(self, test, reference, **kwargs): + compare = cmp_qsgw.iteration_summary(**kwargs) + return compare( + {"qsgw_iterations.dat": test}, + {"qsgw_iterations.dat": reference}, + ) + + def test_summary_within_field_tolerances_passes(self): + passed, message = self._compare( + self._trace(gap=1.0 + 5.0e-6), + self._trace(gap=1.0), + energy_tolerance_ev="1e-5", + ) + + self.assertTrue(passed, message) + self.assertIn("max gap diff", message) + + def test_gap_above_tolerance_fails(self): + passed, message = self._compare( + self._trace(gap=1.0 + 2.0e-5), + self._trace(gap=1.0), + energy_tolerance_ev="1e-5", + ) + + self.assertFalse(passed) + self.assertIn("gap", message) + + def test_discrete_mixing_metadata_mismatch_fails(self): + passed, message = self._compare( + self._trace(applied_mode=1), + self._trace(applied_mode=0), + ) + + self.assertFalse(passed) + self.assertIn("applied_mode", message) + + def test_contract_mismatch_fails(self): + test = self._trace().replace( + "# qsgw_mixing_beta 0.2", + "# qsgw_mixing_beta 0.3", + ) + + passed, message = self._compare(test, self._trace()) + + self.assertFalse(passed) + self.assertIn("contract differs", message) + + def test_missing_contract_fails(self): + trace = self._trace().replace(CONTRACT_HEADER, "") + + passed, message = self._compare(trace, trace) + + self.assertFalse(passed) + self.assertIn("missing QSGW contract", message) + + def test_iteration_zero_is_required(self): + trace = self._trace().replace( + "0 0.0 0.0 0.0 -1.0 1.00000000000000000e+00 8.0 " + "-1 -1 2.0e-1 0 1.0 0.0 0 0 none none\n", + "", + ) + + passed, message = self._compare(trace, trace) + + self.assertFalse(passed) + self.assertIn("iteration zero", message) + + def test_supported_head_only_contract_passes(self): + trace = ( + self._trace() + .replace( + "# velocity disabled_stage1", + "# velocity fixed_reference", + ) + .replace( + "# head disabled_stage1", + "# head scf_grid_analytic_live", + ) + ) + + passed, message = self._compare(trace, trace) + + self.assertTrue(passed, message) + + def test_invalid_symmetry_or_head_velocity_contract_fails(self): + invalid_symmetry = self._trace().replace( + "# symmetry exx_off_gw_off_rpa_off", + "# symmetry crystal_reduction", + ) + passed, message = self._compare( + invalid_symmetry, invalid_symmetry) + self.assertFalse(passed) + self.assertIn("symmetry", message) + + inconsistent_head = self._trace().replace( + "# head disabled_stage1", + "# head scf_grid_analytic_live", + ) + passed, message = self._compare( + inconsistent_head, inconsistent_head) + self.assertFalse(passed) + self.assertIn("head/velocity", message) + + def test_unsupported_hartree_wing_or_legacy_contract_fails(self): + hartree = self._trace().replace( + "# hartree disabled", "# hartree delta_density") + passed, message = self._compare(hartree, hartree) + self.assertFalse(passed) + self.assertIn("hartree", message) + + wing = self._trace().replace( + "# wing disabled_stage1", + "# wing scf_grid_analytic_live", + ) + passed, message = self._compare(wing, wing) + self.assertFalse(passed) + self.assertIn("wing", message) + + legacy = self._trace().replace( + "# qsgw_contract_version 6", + "# qsgw_contract_version 5", + ) + passed, message = self._compare(legacy, legacy) + self.assertFalse(passed) + self.assertIn("version", message) + + +if __name__ == "__main__": + unittest.main() diff --git a/regression_tests/backend/test_qsgw_driver_wiring.py b/regression_tests/backend/test_qsgw_driver_wiring.py new file mode 100644 index 00000000..4193d021 --- /dev/null +++ b/regression_tests/backend/test_qsgw_driver_wiring.py @@ -0,0 +1,118 @@ +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[2] +QSGW_DRIVER = REPO_ROOT / "driver" / "tasks" / "qsgw.cpp" + + +def function_body(source: str, signature: str) -> str: + start = source.index(signature) + opening = source.index("{", start) + depth = 0 + for position in range(opening, len(source)): + if source[position] == "{": + depth += 1 + elif source[position] == "}": + depth -= 1 + if depth == 0: + return source[opening : position + 1] + raise AssertionError(f"unterminated function: {signature}") + + +def test_qsgw_reuses_upstream_gw_in_an_immutable_reference_basis() -> None: + source = QSGW_DRIVER.read_text() + runner = function_body(source, "void run_qsgw_stage_one(") + normalized = " ".join(runner.split()) + + assert "const MeanField reference = dataset->mf;" in runner + assert "ScopedReferenceEigenvectors fixed_basis_projection(" in runner + assert "h.build_g0w0_sigma(opts);" in runner + assert "dataset->p_exx->build_KS_kgrid_blacs(" in runner + assert "dataset->p_g0w0->build_sigc_matrix_KS_kgrid_blacs(" in runner + assert ( + "diagonalize_in_reference_basis( dataset->mf, reference, " + "mixed_hamiltonian" in normalized + ) + + +def test_qsgw_supports_only_same_grid_analytic_head_updates() -> None: + source = QSGW_DRIVER.read_text() + runner = function_body(source, "void run_qsgw_stage_one(") + normalized = " ".join(runner.split()) + + assert "opts.option_dielect_func == 4" in runner + assert "QSGW independent PyATB head updates are unsupported" in runner + assert "read_headwing_input(driver_params.input_dir, false);" in runner + assert ( + "if (contract_producer == static_cast(QsgwProducer::FhiAims)) " + "{ prepare_fhi_aims_interband_velocity( reference_velocity, reference); " + "align_distributed_velocity_to_reference_wfc(" + ) in normalized + assert runner.count( + "align_distributed_velocity_to_reference_wfc(" + ) == 1 + assert "if (update_head)" in runner + assert "dataset->velocity_matrix = reference_velocity;" in runner + assert "dataset->p_headwing.reset();" in runner + assert "ScopedLegacyHeadOccupations legacy_head_occupations(" in runner + assert "initialize_ds_tfgrids(*dataset, opts);" in runner + assert "initialize_ds_headwing(*dataset, opts, false);" in runner + assert ( + runner.index("dataset->velocity_matrix = reference_velocity;") + < runner.index("dataset->p_headwing.reset();") + < runner.index("initialize_ds_tfgrids(*dataset, opts);") + < runner.index( + "ScopedLegacyHeadOccupations legacy_head_occupations(" + ) + < runner.index("initialize_ds_headwing(*dataset, opts, false);") + < runner.index("h.build_g0w0_sigma(opts);") + ) + + +def test_qsgw_band_supplies_actual_r_legacy_bvk_mapping_to_upstream() -> None: + source = QSGW_DRIVER.read_text() + assert '#include "../../src/qsgw/band_bvk_remap.h"' in source + runner = function_body(source, "void run_qsgw_stage_one(") + normalized = " ".join(runner.split()) + assert ( + "const auto bvk_remap = " + "librpa_int::qsgw::build_legacy_band_bvk_remap(" + ) in normalized + assert ( + normalized.count( + "dataset->kfrac_band_list, bvk_remap, dataset->blacs_h" + ) + == 2 + ) + + +def test_qsgw_band_uses_the_fixed_band_reference_and_writes_each_iteration() -> None: + source = QSGW_DRIVER.read_text() + runner = function_body(source, "void run_qsgw_stage_one(") + normalized = " ".join(runner.split()) + + assert "band_reference = dataset->mf_band;" in runner + assert "dataset->p_exx->build_KS_band_blacs(" in runner + assert "dataset->p_g0w0->build_sigc_matrix_KS_band_blacs(" in runner + assert ( + "diagonalize_in_reference_basis( dataset->mf_band, " + "*band_reference, mixed_band_hamiltonian)" in normalized + ) + assert "write_qsgw_band_spin_tables(" in runner + assert '"QSGW_band_spin_"' in runner + + +def test_clean_driver_contains_no_hartree_or_hamiltonian_export_path() -> None: + source = QSGW_DRIVER.read_text() + forbidden = { + "hartree_workflow.h", + "hartree_route.h", + "operator_fourier.h", + "abacus_csr.h", + "qsgw_update_hartree", + "qsgw_export_hamiltonian_for_pyatb", + "project_grid_operator_to_band(", + "write_abacus_hamiltonian_csr(", + } + for token in forbidden: + assert token not in source diff --git a/regression_tests/refs/qsgw_aims_Si_k333_headonly_libri/librpa/librpa.d/qsgw_eigenvalues.dat b/regression_tests/refs/qsgw_aims_Si_k333_headonly_libri/librpa/librpa.d/qsgw_eigenvalues.dat new file mode 100644 index 00000000..2b0a6f49 --- /dev/null +++ b/regression_tests/refs/qsgw_aims_Si_k333_headonly_libri/librpa/librpa.d/qsgw_eigenvalues.dat @@ -0,0 +1,1473 @@ +# qsgw_contract_version 6 +# fixed_basis immutable_mf0 +# live_update eigenvalues_wfc +# velocity fixed_reference +# head scf_grid_analytic_live +# wing disabled_stage1 +# symmetry exx_off_gw_off_rpa_off +# hartree disabled +# band disabled_stage1 +# h_qsgw_cut disabled_non_band +# qsgw_input_contract ../dataset/qsgw_input.head-only.contract +# qsgw_input_contract_sha256 789a44049ff4f45caabbf7155cc3a2f9c20a40dbc400c130628a5fa36bb9c3b7 +# qsgw_mixer none +# qsgw_mixing_beta 0.20000000000000001 +# iter channel spin kpoint kx ky kz band energy_eV +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 0 -1.79077515426670539e+03 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 1 -1.79077515426338664e+03 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 2 -1.40539060267501384e+02 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 3 -1.40531197704116067e+02 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 4 -9.65645914617211787e+01 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 5 -9.65645699647956377e+01 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 6 -9.65645590039257087e+01 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 7 -9.65475416731007954e+01 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 8 -9.65475202199917675e+01 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 9 -9.65475094307998631e+01 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 10 -1.79578716025416618e+01 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 11 -5.58209970134677036e+00 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 12 -5.58041988624430108e+00 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 13 -5.57965745989785677e+00 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 14 -2.60826745064143628e+00 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 15 -2.55034054857527881e+00 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 16 -2.54916940142883108e+00 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 17 -2.54791831437168614e+00 +0 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 0 -1.79077515426629975e+03 +0 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 1 -1.79077515426440982e+03 +0 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 2 -1.40537730848234361e+02 +0 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 3 -1.40532529220149115e+02 +0 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 4 -9.65655431994414357e+01 +0 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 5 -9.65655215122127686e+01 +0 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 6 -9.65632774822601334e+01 +0 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 7 -9.65488025518269382e+01 +0 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 8 -9.65465905929338959e+01 +0 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 9 -9.65465692604958150e+01 +0 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 10 -1.65770214977284738e+01 +0 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 11 -1.11621169560388616e+01 +0 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 12 -6.83584375283288104e+00 +0 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 13 -6.83275990802196898e+00 +0 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 14 -3.25669083228908596e+00 +0 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 15 -4.58044942863903182e-01 +0 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 16 -4.52767690373226006e-01 +0 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 17 4.22389447848834898e+00 +0 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 0 -1.79077515426629702e+03 +0 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 1 -1.79077515426440709e+03 +0 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 2 -1.40537730848234389e+02 +0 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 3 -1.40532529220149115e+02 +0 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 4 -9.65655431994415210e+01 +0 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 5 -9.65655215122127402e+01 +0 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 6 -9.65632774822604318e+01 +0 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 7 -9.65488025518271371e+01 +0 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 8 -9.65465905929347770e+01 +0 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 9 -9.65465692604958292e+01 +0 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 10 -1.65770214977284667e+01 +0 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 11 -1.11621169560391884e+01 +0 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 12 -6.83584375283268297e+00 +0 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 13 -6.83275990802197253e+00 +0 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 14 -3.25669083228978495e+00 +0 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 15 -4.58044942864451576e-01 +0 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 16 -4.52767690373229392e-01 +0 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 17 4.22389447848807276e+00 +0 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 0 -1.79077515426629861e+03 +0 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 1 -1.79077515426440596e+03 +0 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 2 -1.40537730891990634e+02 +0 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 3 -1.40532529261621193e+02 +0 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 4 -9.65655221178071486e+01 +0 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 5 -9.65655124010658454e+01 +0 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 6 -9.65633075096776992e+01 +0 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 7 -9.65488325367872307e+01 +0 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 8 -9.65465695289059056e+01 +0 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 9 -9.65465601497768375e+01 +0 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 10 -1.65770756540435329e+01 +0 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 11 -1.11617738928454813e+01 +0 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 12 -6.83611146369627409e+00 +0 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 13 -6.83332620988546324e+00 +0 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 14 -3.25747545956989404e+00 +0 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 15 -4.55269474644126537e-01 +0 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 16 -4.54325965302114332e-01 +0 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 17 4.22395806894476511e+00 +0 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 0 -1.79077515426597915e+03 +0 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 1 -1.79077515426466903e+03 +0 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 2 -1.40537096478584601e+02 +0 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 3 -1.40533164385283129e+02 +0 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 4 -9.65664983160666424e+01 +0 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 5 -9.65664951520443822e+01 +0 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 6 -9.65603224636630699e+01 +0 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 7 -9.65517977632591027e+01 +0 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 8 -9.65455973031549490e+01 +0 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 9 -9.65455940906807371e+01 +0 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 10 -1.60203081890677730e+01 +0 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 11 -1.09419297708523491e+01 +0 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 12 -8.30269151589940968e+00 +0 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 13 -8.30141899632387315e+00 +0 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 14 -3.66981326202703251e+00 +0 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 15 -8.56070781022172600e-01 +0 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 16 3.42814770692685977e+00 +0 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 17 3.42882362587525646e+00 +0 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 0 -1.79077515426584841e+03 +0 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 1 -1.79077515426522768e+03 +0 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 2 -1.40536113657983663e+02 +0 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 3 -1.40534147421218961e+02 +0 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 4 -9.65672035296678928e+01 +0 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 5 -9.65664384893032377e+01 +0 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 6 -9.65589675063458941e+01 +0 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 7 -9.65531553892570713e+01 +0 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 8 -9.65456501556131315e+01 +0 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 9 -9.65448844382048179e+01 +0 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 10 -1.47192995602249859e+01 +0 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 11 -1.27257446531081531e+01 +0 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 12 -1.01370695979458390e+01 +0 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 13 -8.00516642893685848e+00 +0 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 14 -2.24783906649817355e+00 +0 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 15 2.24775466550331782e+00 +0 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 16 2.66064928678206591e+00 +0 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 17 4.04450511621512021e+00 +0 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 0 -1.79077515426629861e+03 +0 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 1 -1.79077515426440573e+03 +0 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 2 -1.40537730891990435e+02 +0 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 3 -1.40532529261621050e+02 +0 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 4 -9.65655221178072196e+01 +0 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 5 -9.65655124010652770e+01 +0 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 6 -9.65633075096769602e+01 +0 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 7 -9.65488325367874012e+01 +0 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 8 -9.65465695289058772e+01 +0 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 9 -9.65465601497762833e+01 +0 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 10 -1.65770756540434512e+01 +0 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 11 -1.11617738928455221e+01 +0 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 12 -6.83611146369627232e+00 +0 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 13 -6.83332620988549522e+00 +0 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 14 -3.25747545956991180e+00 +0 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 15 -4.55269474644134475e-01 +0 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 16 -4.54325965301897339e-01 +0 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 17 4.22395806894470560e+00 +0 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 0 -1.79077515426584887e+03 +0 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 1 -1.79077515426522700e+03 +0 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 2 -1.40536113657984060e+02 +0 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 3 -1.40534147421218989e+02 +0 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 4 -9.65672035296683049e+01 +0 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 5 -9.65664384893032235e+01 +0 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 6 -9.65589675063460788e+01 +0 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 7 -9.65531553892574408e+01 +0 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 8 -9.65456501556130888e+01 +0 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 9 -9.65448844382054006e+01 +0 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 10 -1.47192995602246022e+01 +0 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 11 -1.27257446531076912e+01 +0 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 12 -1.01370695979454055e+01 +0 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 13 -8.00516642893686026e+00 +0 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 14 -2.24783906649810161e+00 +0 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 15 2.24775466550345326e+00 +0 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 16 2.66064928678206325e+00 +0 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 17 4.04450511621463082e+00 +0 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 0 -1.79077515426597984e+03 +0 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 1 -1.79077515426466971e+03 +0 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 2 -1.40537096478584573e+02 +0 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 3 -1.40533164385283300e+02 +0 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 4 -9.65664983160663866e+01 +0 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 5 -9.65664951520443537e+01 +0 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 6 -9.65603224636634820e+01 +0 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 7 -9.65517977632599980e+01 +0 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 8 -9.65455973031549632e+01 +0 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 9 -9.65455940906806660e+01 +0 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 10 -1.60203081890676948e+01 +0 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 11 -1.09419297708522212e+01 +0 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 12 -8.30269151589839183e+00 +0 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 13 -8.30141899632387670e+00 +0 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 14 -3.66981326202689839e+00 +0 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 15 -8.56070781021761706e-01 +0 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 16 3.42814770692685133e+00 +0 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 17 3.42882362587529643e+00 +0 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 0 -1.79077515426629998e+03 +0 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 1 -1.79077515426441028e+03 +0 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 2 -1.40537730872973526e+02 +0 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 3 -1.40532529254301807e+02 +0 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 4 -9.65655387726072405e+01 +0 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 5 -9.65655128457962348e+01 +0 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 6 -9.65632902590139679e+01 +0 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 7 -9.65488153199761996e+01 +0 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 8 -9.65465864121898960e+01 +0 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 9 -9.65465604710435912e+01 +0 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 10 -1.65770426462764817e+01 +0 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 11 -1.11614713920289166e+01 +0 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 12 -6.83446538699572503e+00 +0 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 13 -6.83429150168891386e+00 +0 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 14 -3.25766509552006323e+00 +0 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 15 -4.54437384613968154e-01 +0 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 16 -4.54226208775820872e-01 +0 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 17 4.22326177825138060e+00 +0 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 0 -1.79077515426598029e+03 +0 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 1 -1.79077515426467085e+03 +0 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 2 -1.40537096465036626e+02 +0 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 3 -1.40533164374879163e+02 +0 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 4 -9.65665109306626874e+01 +0 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 5 -9.65664880982970999e+01 +0 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 6 -9.65603168638611180e+01 +0 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 7 -9.65517921406837445e+01 +0 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 8 -9.65456098473799358e+01 +0 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 9 -9.65455870853775053e+01 +0 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 10 -1.60202825202231338e+01 +0 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 11 -1.09414939752629845e+01 +0 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 12 -8.30239207080231978e+00 +0 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 13 -8.30215829002932892e+00 +0 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 14 -3.66976589192071501e+00 +0 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 15 -8.55786405164857933e-01 +0 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 16 3.42868827642931340e+00 +0 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 17 3.42922953412008580e+00 +0 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 0 -1.79077515426584660e+03 +0 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 1 -1.79077515426523041e+03 +0 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 2 -1.40536113640861799e+02 +0 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 3 -1.40534147419924722e+02 +0 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 4 -9.65671950340748708e+01 +0 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 5 -9.65664541641771592e+01 +0 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 6 -9.65589602603281776e+01 +0 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 7 -9.65531481498000375e+01 +0 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 8 -9.65456658890988564e+01 +0 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 9 -9.65448759060874266e+01 +0 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 10 -1.47191757824906801e+01 +0 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 11 -1.27256475803191567e+01 +0 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 12 -1.01366074434949827e+01 +0 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 13 -8.00580361203718205e+00 +0 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 14 -2.24812845390956140e+00 +0 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 15 2.24689703981350242e+00 +0 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 16 2.66038532303092223e+00 +0 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 17 4.04506764784448691e+00 +0 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 0 -1.79077515426597870e+03 +0 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 1 -1.79077515426466903e+03 +0 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 2 -1.40537096465036399e+02 +0 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 3 -1.40533164374878936e+02 +0 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 4 -9.65665109306643643e+01 +0 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 5 -9.65664880982950820e+01 +0 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 6 -9.65603168638611180e+01 +0 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 7 -9.65517921406844550e+01 +0 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 8 -9.65456098473798505e+01 +0 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 9 -9.65455870853770506e+01 +0 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 10 -1.60202825202232582e+01 +0 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 11 -1.09414939752631319e+01 +0 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 12 -8.30239207091153908e+00 +0 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 13 -8.30215828992023219e+00 +0 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 14 -3.66976589192134872e+00 +0 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 15 -8.55786405164800312e-01 +0 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 16 3.42868827640491958e+00 +0 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 17 3.42922953414369536e+00 +0 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 0 -1.79077515426629884e+03 +0 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 1 -1.79077515426440982e+03 +0 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 2 -1.40537730872973924e+02 +0 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 3 -1.40532529254301522e+02 +0 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 4 -9.65655387726086047e+01 +0 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 5 -9.65655128457974570e+01 +0 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 6 -9.65632902590108699e+01 +0 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 7 -9.65488153199762280e+01 +0 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 8 -9.65465864121902513e+01 +0 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 9 -9.65465604710438328e+01 +0 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 10 -1.65770426463677971e+01 +0 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 11 -1.11614713920623849e+01 +0 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 12 -6.83446538687402327e+00 +0 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 13 -6.83429150157976117e+00 +0 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 14 -3.25766509525125914e+00 +0 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 15 -4.54437384668740008e-01 +0 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 16 -4.54226208679499976e-01 +0 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 17 4.22326177805050307e+00 +0 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 0 -1.79077515426584773e+03 +0 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 1 -1.79077515426523200e+03 +0 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 2 -1.40536113640862226e+02 +0 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 3 -1.40534147419924381e+02 +0 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 4 -9.65671950340739755e+01 +0 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 5 -9.65664541641793477e+01 +0 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 6 -9.65589602603264865e+01 +0 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 7 -9.65531481498005490e+01 +0 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 8 -9.65456658890988564e+01 +0 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 9 -9.65448759060870856e+01 +0 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 10 -1.47191757826371958e+01 +0 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 11 -1.27256475802042974e+01 +0 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 12 -1.01366074434940501e+01 +0 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 13 -8.00580361194632140e+00 +0 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 14 -2.24812845370333347e+00 +0 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 15 2.24689703950412278e+00 +0 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 16 2.66038532306650088e+00 +0 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 17 4.04506764794831586e+00 +0 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 0 -1.79077515426584773e+03 +0 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 1 -1.79077515426522768e+03 +0 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 2 -1.40536113656792168e+02 +0 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 3 -1.40534147425193538e+02 +0 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 4 -9.65672094959985969e+01 +0 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 5 -9.65664315264972402e+01 +0 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 6 -9.65589683454524703e+01 +0 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 7 -9.65531561999573853e+01 +0 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 8 -9.65456432671638680e+01 +0 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 9 -9.65448903723045220e+01 +0 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 10 -1.47193042036142856e+01 +0 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 11 -1.27257140894192613e+01 +0 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 12 -1.01360519806400475e+01 +0 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 13 -8.00595877249585541e+00 +0 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 14 -2.24866502719746597e+00 +0 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 15 2.24714396370276726e+00 +0 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 16 2.66081542428018958e+00 +0 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 17 4.04476639108061153e+00 +0 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 0 -1.79077515426584773e+03 +0 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 1 -1.79077515426522814e+03 +0 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 2 -1.40536113656792594e+02 +0 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 3 -1.40534147425193055e+02 +0 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 4 -9.65672094959980996e+01 +0 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 5 -9.65664315264997555e+01 +0 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 6 -9.65589683454514613e+01 +0 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 7 -9.65531561999586359e+01 +0 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 8 -9.65456432671640385e+01 +0 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 9 -9.65448903723044083e+01 +0 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 10 -1.47193042037604584e+01 +0 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 11 -1.27257140893046241e+01 +0 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 12 -1.01360519806391700e+01 +0 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 13 -8.00595877240435527e+00 +0 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 14 -2.24866502699064119e+00 +0 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 15 2.24714396339368383e+00 +0 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 16 2.66081542431645790e+00 +0 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 17 4.04476639118494763e+00 +0 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 0 -1.79077515426584569e+03 +0 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 1 -1.79077515426523155e+03 +0 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 2 -1.40536113649174268e+02 +0 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 3 -1.40534147439107187e+02 +0 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 4 -9.65672014841272244e+01 +0 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 5 -9.65664415502332645e+01 +0 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 6 -9.65589663131753895e+01 +0 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 7 -9.65531541588122906e+01 +0 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 8 -9.65456535186512355e+01 +0 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 9 -9.65448821866860811e+01 +0 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 10 -1.47192668727217999e+01 +0 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 11 -1.27259236528808568e+01 +0 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 12 -1.01358749264003212e+01 +0 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 13 -8.00581630851818815e+00 +0 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 14 -2.24852984005740808e+00 +0 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 15 2.24698216455045729e+00 +0 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 16 2.66067334408116052e+00 +0 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 17 4.04521853677901166e+00 +0 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 0 -1.79077515426629702e+03 +0 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 1 -1.79077515426440914e+03 +0 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 2 -1.40537730872974066e+02 +0 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 3 -1.40532529254302148e+02 +0 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 4 -9.65655387726073258e+01 +0 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 5 -9.65655128457962348e+01 +0 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 6 -9.65632902590142237e+01 +0 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 7 -9.65488153199760717e+01 +0 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 8 -9.65465864121903081e+01 +0 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 9 -9.65465604710440033e+01 +0 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 10 -1.65770426462765741e+01 +0 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 11 -1.11614713920289432e+01 +0 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 12 -6.83446538699579964e+00 +0 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 13 -6.83429150168896804e+00 +0 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 14 -3.25766509552004013e+00 +0 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 15 -4.54437384614415463e-01 +0 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 16 -4.54226208776004614e-01 +0 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 17 4.22326177825135041e+00 +0 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 0 -1.79077515426584614e+03 +0 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 1 -1.79077515426522973e+03 +0 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 2 -1.40536113640862482e+02 +0 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 3 -1.40534147419925176e+02 +0 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 4 -9.65671950340752971e+01 +0 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 5 -9.65664541641776424e+01 +0 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 6 -9.65589602603278365e+01 +0 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 7 -9.65531481497997817e+01 +0 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 8 -9.65456658890989132e+01 +0 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 9 -9.65448759060873272e+01 +0 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 10 -1.47191757824905807e+01 +0 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 11 -1.27256475803190785e+01 +0 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 12 -1.01366074434946984e+01 +0 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 13 -8.00580361203706303e+00 +0 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 14 -2.24812845390952099e+00 +0 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 15 2.24689703981377775e+00 +0 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 16 2.66038532303093023e+00 +0 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 17 4.04506764784454820e+00 +0 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 0 -1.79077515426597961e+03 +0 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 1 -1.79077515426466903e+03 +0 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 2 -1.40537096465036427e+02 +0 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 3 -1.40533164374878965e+02 +0 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 4 -9.65665109306625595e+01 +0 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 5 -9.65664880982970573e+01 +0 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 6 -9.65603168638614591e+01 +0 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 7 -9.65517921406842987e+01 +0 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 8 -9.65456098473794952e+01 +0 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 9 -9.65455870853770790e+01 +0 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 10 -1.60202825202231978e+01 +0 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 11 -1.09414939752630964e+01 +0 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 12 -8.30239207080219721e+00 +0 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 13 -8.30215829002925965e+00 +0 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 14 -3.66976589192072566e+00 +0 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 15 -8.55786405164933761e-01 +0 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 16 3.42868827642931251e+00 +0 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 17 3.42922953412007958e+00 +0 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 0 -1.79077515426584728e+03 +0 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 1 -1.79077515426522882e+03 +0 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 2 -1.40536113656791912e+02 +0 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 3 -1.40534147425193424e+02 +0 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 4 -9.65672094959989096e+01 +0 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 5 -9.65664315264973254e+01 +0 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 6 -9.65589683454529819e+01 +0 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 7 -9.65531561999579111e+01 +0 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 8 -9.65456432671641807e+01 +0 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 9 -9.65448903723046925e+01 +0 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 10 -1.47193042036143709e+01 +0 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 11 -1.27257140894196201e+01 +0 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 12 -1.01360519806401221e+01 +0 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 13 -8.00595877249589449e+00 +0 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 14 -2.24866502719734518e+00 +0 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 15 2.24714396370290403e+00 +0 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 16 2.66081542428038054e+00 +0 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 17 4.04476639108082470e+00 +0 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 0 -1.79077515426584500e+03 +0 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 1 -1.79077515426523087e+03 +0 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 2 -1.40536113649175093e+02 +0 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 3 -1.40534147439107215e+02 +0 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 4 -9.65672014841273949e+01 +0 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 5 -9.65664415502329661e+01 +0 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 6 -9.65589663131755600e+01 +0 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 7 -9.65531541588121769e+01 +0 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 8 -9.65456535186508376e+01 +0 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 9 -9.65448821866862090e+01 +0 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 10 -1.47192668727217644e+01 +0 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 11 -1.27259236528805602e+01 +0 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 12 -1.01358749264003407e+01 +0 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 13 -8.00581630851844750e+00 +0 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 14 -2.24852984005749423e+00 +0 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 15 2.24698216455040400e+00 +0 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 16 2.66067334408186307e+00 +0 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 17 4.04521853677896726e+00 +0 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 0 -1.79077515426584728e+03 +0 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 1 -1.79077515426522814e+03 +0 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 2 -1.40536113656792224e+02 +0 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 3 -1.40534147425192856e+02 +0 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 4 -9.65672094959974174e+01 +0 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 5 -9.65664315264992581e+01 +0 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 6 -9.65589683454512056e+01 +0 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 7 -9.65531561999584085e+01 +0 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 8 -9.65456432671638680e+01 +0 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 9 -9.65448903723042378e+01 +0 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 10 -1.47193042037606308e+01 +0 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 11 -1.27257140893048071e+01 +0 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 12 -1.01360519806394418e+01 +0 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 13 -8.00595877240449738e+00 +0 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 14 -2.24866502699066606e+00 +0 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 15 2.24714396339396805e+00 +0 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 16 2.66081542431635309e+00 +0 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 17 4.04476639118489878e+00 +0 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 0 -1.79077515426597824e+03 +0 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 1 -1.79077515426466903e+03 +0 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 2 -1.40537096465036342e+02 +0 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 3 -1.40533164374878851e+02 +0 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 4 -9.65665109306643501e+01 +0 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 5 -9.65664880982949683e+01 +0 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 6 -9.65603168638610185e+01 +0 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 7 -9.65517921406844692e+01 +0 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 8 -9.65456098473799216e+01 +0 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 9 -9.65455870853770222e+01 +0 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 10 -1.60202825202232901e+01 +0 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 11 -1.09414939752631231e+01 +0 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 12 -8.30239207091156750e+00 +0 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 13 -8.30215828992025351e+00 +0 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 14 -3.66976589192145974e+00 +0 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 15 -8.55786405165177122e-01 +0 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 16 3.42868827640515672e+00 +0 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 17 3.42922953414390363e+00 +0 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 0 -1.79077515426584500e+03 +0 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 1 -1.79077515426522814e+03 +0 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 2 -1.40536113640862482e+02 +0 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 3 -1.40534147419925119e+02 +0 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 4 -9.65671950340738761e+01 +0 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 5 -9.65664541641794045e+01 +0 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 6 -9.65589602603269412e+01 +0 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 7 -9.65531481498010606e+01 +0 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 8 -9.65456658890989559e+01 +0 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 9 -9.65448759060872135e+01 +0 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 10 -1.47191757826368903e+01 +0 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 11 -1.27256475802041269e+01 +0 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 12 -1.01366074434939311e+01 +0 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 13 -8.00580361194626455e+00 +0 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 14 -2.24812845370330816e+00 +0 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 15 2.24689703950405173e+00 +0 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 16 2.66038532306626996e+00 +0 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 17 4.04506764794828921e+00 +0 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 0 -1.79077515426629861e+03 +0 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 1 -1.79077515426440982e+03 +0 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 2 -1.40537730872974265e+02 +0 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 3 -1.40532529254301835e+02 +0 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 4 -9.65655387726086900e+01 +0 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 5 -9.65655128457975991e+01 +0 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 6 -9.65632902590106852e+01 +0 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 7 -9.65488153199762280e+01 +0 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 8 -9.65465864121902229e+01 +0 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 9 -9.65465604710439038e+01 +0 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 10 -1.65770426463677047e+01 +0 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 11 -1.11614713920623796e+01 +0 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 12 -6.83446538687401883e+00 +0 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 13 -6.83429150157966792e+00 +0 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 14 -3.25766509525131065e+00 +0 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 15 -4.54437384668407551e-01 +0 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 16 -4.54226208679350707e-01 +0 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 17 4.22326177805050751e+00 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 0 -1.88271137622335186e+03 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 1 -1.88271135846460629e+03 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 2 -1.65270227324449479e+02 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 3 -1.65251715504972594e+02 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 4 -1.12359428510191336e+02 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 5 -1.12359416293860846e+02 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 6 -1.12359400773580774e+02 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 7 -1.12305172220018974e+02 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 8 -1.12305162208992783e+02 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 9 -1.12305146092538493e+02 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 10 -1.96854701306733268e+01 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 11 -4.30302711669224536e+00 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 12 -4.30145071986783645e+00 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 13 -4.30077879204474200e+00 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 14 -2.97209942424417450e-01 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 15 2.72435263961760488e-01 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 16 2.73452130537953253e-01 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 17 2.74671260889596036e-01 +1 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 0 -1.88271144946777622e+03 +1 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 1 -1.88271135563607663e+03 +1 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 2 -1.65270013946016292e+02 +1 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 3 -1.65253380795704885e+02 +1 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 4 -1.12355686834306354e+02 +1 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 5 -1.12355672823846078e+02 +1 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 6 -1.12344311582733283e+02 +1 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 7 -1.12318444685069352e+02 +1 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 8 -1.12307668005146539e+02 +1 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 9 -1.12307652199531645e+02 +1 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 10 -1.77144158237180953e+01 +1 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 11 -1.10556060479475011e+01 +1 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 12 -5.82344521209776733e+00 +1 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 13 -5.82106321771144319e+00 +1 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 14 -9.06302852918271173e-01 +1 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 15 2.53604706058816154e+00 +1 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 16 2.54070803146322088e+00 +1 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 17 6.95846999725694015e+00 +1 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 0 -1.88271144947905395e+03 +1 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 1 -1.88271135562478935e+03 +1 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 2 -1.65270013946016206e+02 +1 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 3 -1.65253380795704913e+02 +1 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 4 -1.12355686834307733e+02 +1 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 5 -1.12355672823846120e+02 +1 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 6 -1.12344311582733496e+02 +1 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 7 -1.12318444685069593e+02 +1 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 8 -1.12307668005146851e+02 +1 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 9 -1.12307652199533820e+02 +1 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 10 -1.77144158237194702e+01 +1 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 11 -1.10556060479476592e+01 +1 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 12 -5.82344521209755417e+00 +1 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 13 -5.82106321771179314e+00 +1 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 14 -9.06302852919422031e-01 +1 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 15 2.53604706058877793e+00 +1 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 16 2.54070803146385771e+00 +1 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 17 6.95846999725810633e+00 +1 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 0 -1.88271144937742633e+03 +1 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 1 -1.88271135573002971e+03 +1 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 2 -1.65270011589780211e+02 +1 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 3 -1.65253380018709606e+02 +1 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 4 -1.12355681922658462e+02 +1 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 5 -1.12355663796917909e+02 +1 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 6 -1.12344324940697476e+02 +1 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 7 -1.12318459229800297e+02 +1 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 8 -1.12307661310422759e+02 +1 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 9 -1.12307646784728249e+02 +1 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 10 -1.77144395755090152e+01 +1 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 11 -1.10554952705312246e+01 +1 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 12 -5.82376905156687830e+00 +1 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 13 -5.82131623647948970e+00 +1 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 14 -9.06607409253183838e-01 +1 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 15 2.53824574799481129e+00 +1 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 16 2.53930540750872513e+00 +1 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 17 6.95860883285321652e+00 +1 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 0 -1.88271146810116102e+03 +1 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 1 -1.88271133330534099e+03 +1 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 2 -1.65269620734219217e+02 +1 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 3 -1.65253727049855087e+02 +1 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 4 -1.12352337175618587e+02 +1 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 5 -1.12352306371416375e+02 +1 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 6 -1.12344912832434787e+02 +1 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 7 -1.12316188823601621e+02 +1 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 8 -1.12310401082634186e+02 +1 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 9 -1.12310372715702997e+02 +1 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 10 -1.68895164761088523e+01 +1 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 11 -1.06474355132913434e+01 +1 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 12 -7.68907100381660591e+00 +1 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 13 -7.68812834513908427e+00 +1 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 14 -1.18272712858925799e+00 +1 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 15 1.84421305956359549e+00 +1 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 16 6.35622396831113967e+00 +1 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 17 6.35674629233433741e+00 +1 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 0 -1.88271142614359292e+03 +1 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 1 -1.88271140303304151e+03 +1 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 2 -1.65264735365234571e+02 +1 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 3 -1.65258122883097826e+02 +1 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 4 -1.12352920588127134e+02 +1 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 5 -1.12350841506175414e+02 +1 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 6 -1.12335795588351658e+02 +1 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 7 -1.12326770731288903e+02 +1 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 8 -1.12309698717290445e+02 +1 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 9 -1.12309307089998640e+02 +1 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 10 -1.48319458147047953e+01 +1 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 11 -1.26870298569535969e+01 +1 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 12 -9.94154082948582918e+00 +1 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 13 -7.27565009512137095e+00 +1 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 14 3.93423322765209471e-01 +1 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 15 4.99843294995169174e+00 +1 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 16 5.65231570944436434e+00 +1 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 17 6.84346349528886133e+00 +1 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 0 -1.88271144937278768e+03 +1 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 1 -1.88271135573467132e+03 +1 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 2 -1.65270011589780154e+02 +1 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 3 -1.65253380018709692e+02 +1 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 4 -1.12355681922657908e+02 +1 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 5 -1.12355663796917071e+02 +1 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 6 -1.12344324940696197e+02 +1 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 7 -1.12318459229800496e+02 +1 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 8 -1.12307661310422702e+02 +1 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 9 -1.12307646784727211e+02 +1 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 10 -1.77144395755073489e+01 +1 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 11 -1.10554952705329512e+01 +1 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 12 -5.82376905156778246e+00 +1 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 13 -5.82131623648016383e+00 +1 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 14 -9.06607409253161522e-01 +1 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 15 2.53824574799557379e+00 +1 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 16 2.53930540750932465e+00 +1 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 17 6.95860883285567233e+00 +1 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 0 -1.88271142614374025e+03 +1 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 1 -1.88271140303289030e+03 +1 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 2 -1.65264735365235254e+02 +1 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 3 -1.65258122883097855e+02 +1 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 4 -1.12352920588127446e+02 +1 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 5 -1.12350841506176522e+02 +1 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 6 -1.12335795588351758e+02 +1 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 7 -1.12326770731289216e+02 +1 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 8 -1.12309698717292434e+02 +1 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 9 -1.12309307089999180e+02 +1 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 10 -1.48319458147042038e+01 +1 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 11 -1.26870298569541511e+01 +1 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 12 -9.94154082948582740e+00 +1 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 13 -7.27565009512257177e+00 +1 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 14 3.93423322765505679e-01 +1 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 15 4.99843294995079646e+00 +1 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 16 5.65231570944499229e+00 +1 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 17 6.84346349528961806e+00 +1 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 0 -1.88271146810142272e+03 +1 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 1 -1.88271133330507882e+03 +1 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 2 -1.65269620734219501e+02 +1 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 3 -1.65253727049855740e+02 +1 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 4 -1.12352337175618374e+02 +1 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 5 -1.12352306371416788e+02 +1 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 6 -1.12344912832435270e+02 +1 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 7 -1.12316188823602147e+02 +1 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 8 -1.12310401082634570e+02 +1 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 9 -1.12310372715702997e+02 +1 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 10 -1.68895164761060279e+01 +1 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 11 -1.06474355132936047e+01 +1 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 12 -7.68907100381669473e+00 +1 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 13 -7.68812834513978949e+00 +1 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 14 -1.18272712858908058e+00 +1 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 15 1.84421305956434889e+00 +1 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 16 6.35622396831275882e+00 +1 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 17 6.35674629233685895e+00 +1 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 0 -1.88271144949870427e+03 +1 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 1 -1.88271135560572066e+03 +1 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 2 -1.65270011975630155e+02 +1 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 3 -1.65253380136131341e+02 +1 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 4 -1.12355674830009136e+02 +1 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 5 -1.12355657509965980e+02 +1 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 6 -1.12344339858756882e+02 +1 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 7 -1.12318472978235690e+02 +1 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 8 -1.12307657562103529e+02 +1 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 9 -1.12307638931476689e+02 +1 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 10 -1.77143558528857632e+01 +1 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 11 -1.10551768637750172e+01 +1 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 12 -5.82238193816819205e+00 +1 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 13 -5.82231865148521344e+00 +1 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 14 -9.07235550272573743e-01 +1 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 15 2.53915790743479608e+00 +1 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 16 2.53952549595993338e+00 +1 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 17 6.95797085968033624e+00 +1 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 0 -1.88271146809771085e+03 +1 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 1 -1.88271133330149610e+03 +1 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 2 -1.65269622080539335e+02 +1 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 3 -1.65253727301746608e+02 +1 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 4 -1.12352330488603542e+02 +1 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 5 -1.12352320670509130e+02 +1 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 6 -1.12344902279233906e+02 +1 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 7 -1.12316178398071116e+02 +1 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 8 -1.12310394784435218e+02 +1 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 9 -1.12310386644580035e+02 +1 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 10 -1.68894816921991513e+01 +1 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 11 -1.06471575616582435e+01 +1 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 12 -7.68895246013025258e+00 +1 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 13 -7.68819737931031177e+00 +1 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 14 -1.18278953808827669e+00 +1 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 15 1.84453876659576110e+00 +1 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 16 6.35665142554591167e+00 +1 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 17 6.35713814725248572e+00 +1 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 0 -1.88271142615284816e+03 +1 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 1 -1.88271140303442871e+03 +1 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 2 -1.65264736487595400e+02 +1 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 3 -1.65258122151402233e+02 +1 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 4 -1.12352913952535985e+02 +1 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 5 -1.12350848782093550e+02 +1 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 6 -1.12335795621220640e+02 +1 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 7 -1.12326767592365314e+02 +1 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 8 -1.12309694043135806e+02 +1 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 9 -1.12309313703335192e+02 +1 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 10 -1.48317963524649432e+01 +1 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 11 -1.26867842000881232e+01 +1 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 12 -9.94155110940450193e+00 +1 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 13 -7.27575048490049081e+00 +1 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 14 3.93187307808588848e-01 +1 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 15 4.99776331705903853e+00 +1 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 16 5.65213817442565780e+00 +1 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 17 6.84364572085917722e+00 +1 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 0 -1.88271146809764628e+03 +1 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 1 -1.88271133330177372e+03 +1 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 2 -1.65269622080665130e+02 +1 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 3 -1.65253727301849523e+02 +1 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 4 -1.12352330488691948e+02 +1 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 5 -1.12352320670587289e+02 +1 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 6 -1.12344902279315349e+02 +1 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 7 -1.12316178398152772e+02 +1 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 8 -1.12310394784517996e+02 +1 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 9 -1.12310386644659943e+02 +1 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 10 -1.68894816922955791e+01 +1 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 11 -1.06471575617214143e+01 +1 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 12 -7.68895246033643076e+00 +1 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 13 -7.68819737923082602e+00 +1 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 14 -1.18278953813936250e+00 +1 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 15 1.84453876654761051e+00 +1 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 16 6.35665142547308637e+00 +1 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 17 6.35713814724185067e+00 +1 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 0 -1.88271144955043474e+03 +1 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 1 -1.88271135555427531e+03 +1 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 2 -1.65270011975821660e+02 +1 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 3 -1.65253380136260461e+02 +1 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 4 -1.12355674830131377e+02 +1 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 5 -1.12355657510078245e+02 +1 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 6 -1.12344339858865581e+02 +1 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 7 -1.12318472978346989e+02 +1 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 8 -1.12307657562218722e+02 +1 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 9 -1.12307638931587817e+02 +1 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 10 -1.77143558531613010e+01 +1 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 11 -1.10551768639468904e+01 +1 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 12 -5.82238193811899851e+00 +1 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 13 -5.82231865142826788e+00 +1 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 14 -9.07235550041862626e-01 +1 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 15 2.53915790731837498e+00 +1 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 16 2.53952549601444977e+00 +1 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 17 6.95797085943879257e+00 +1 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 0 -1.88271142615290273e+03 +1 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 1 -1.88271140303458378e+03 +1 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 2 -1.65264736487739043e+02 +1 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 3 -1.65258122151485253e+02 +1 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 4 -1.12352913952621989e+02 +1 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 5 -1.12350848782171170e+02 +1 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 6 -1.12335795621295972e+02 +1 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 7 -1.12326767592444156e+02 +1 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 8 -1.12309694043215856e+02 +1 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 9 -1.12309313703412244e+02 +1 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 10 -1.48317963527507555e+01 +1 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 11 -1.26867842000467945e+01 +1 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 12 -9.94155110951311727e+00 +1 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 13 -7.27575048485654907e+00 +1 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 14 3.93187308000209679e-01 +1 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 15 4.99776331670671592e+00 +1 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 16 5.65213817441206157e+00 +1 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 17 6.84364572092026169e+00 +1 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 0 -1.88271142614635482e+03 +1 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 1 -1.88271140303987363e+03 +1 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 2 -1.65264735579528974e+02 +1 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 3 -1.65258121638268960e+02 +1 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 4 -1.12352903979349549e+02 +1 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 5 -1.12350851872405926e+02 +1 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 6 -1.12335802662517239e+02 +1 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 7 -1.12326776282142177e+02 +1 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 8 -1.12309684594672731e+02 +1 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 9 -1.12309315848842175e+02 +1 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 10 -1.48318031031075801e+01 +1 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 11 -1.26869915654116170e+01 +1 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 12 -9.94084003532266713e+00 +1 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 13 -7.27629172552369763e+00 +1 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 14 3.92816167475411260e-01 +1 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 15 4.99804747662464965e+00 +1 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 16 5.65250902949700507e+00 +1 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 17 6.84366501827694051e+00 +1 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 0 -1.88271142614638984e+03 +1 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 1 -1.88271140303992911e+03 +1 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 2 -1.65264735579605741e+02 +1 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 3 -1.65258121638293119e+02 +1 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 4 -1.12352903979388017e+02 +1 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 5 -1.12350851872438554e+02 +1 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 6 -1.12335802662547906e+02 +1 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 7 -1.12326776282175246e+02 +1 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 8 -1.12309684594705871e+02 +1 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 9 -1.12309315848874689e+02 +1 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 10 -1.48318031033564903e+01 +1 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 11 -1.26869915653163812e+01 +1 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 12 -9.94084003537843763e+00 +1 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 13 -7.27629172543227565e+00 +1 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 14 3.92816167692053020e-01 +1 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 15 4.99804747630923263e+00 +1 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 16 5.65250902949859579e+00 +1 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 17 6.84366501835097818e+00 +1 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 0 -1.88271142614534347e+03 +1 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 1 -1.88271140302748313e+03 +1 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 2 -1.65264735468701616e+02 +1 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 3 -1.65258121304040401e+02 +1 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 4 -1.12352888959406030e+02 +1 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 5 -1.12350862972653474e+02 +1 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 6 -1.12335808008780788e+02 +1 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 7 -1.12326780994008573e+02 +1 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 8 -1.12309672614508258e+02 +1 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 9 -1.12309325126580632e+02 +1 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 10 -1.48317577222304635e+01 +1 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 11 -1.26868980688976905e+01 +1 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 12 -9.94124867010967961e+00 +1 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 13 -7.27605008717437141e+00 +1 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 14 3.92880610241729655e-01 +1 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 15 4.99779681654104291e+00 +1 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 16 5.65237762602873506e+00 +1 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 17 6.84390513975646630e+00 +1 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 0 -1.88271144956944045e+03 +1 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 1 -1.88271135553497288e+03 +1 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 2 -1.65270011975629757e+02 +1 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 3 -1.65253380136132392e+02 +1 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 4 -1.12355674830010102e+02 +1 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 5 -1.12355657509966250e+02 +1 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 6 -1.12344339858757607e+02 +1 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 7 -1.12318472978235349e+02 +1 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 8 -1.12307657562103216e+02 +1 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 9 -1.12307638931477584e+02 +1 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 10 -1.77143558528950571e+01 +1 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 11 -1.10551768637740189e+01 +1 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 12 -5.82238193816858285e+00 +1 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 13 -5.82231865148567351e+00 +1 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 14 -9.07235550276664249e-01 +1 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 15 2.53915790743469749e+00 +1 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 16 2.53952549596022958e+00 +1 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 17 6.95797085968579232e+00 +1 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 0 -1.88271142615273175e+03 +1 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 1 -1.88271140303454536e+03 +1 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 2 -1.65264736487596224e+02 +1 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 3 -1.65258122151402517e+02 +1 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 4 -1.12352913952537378e+02 +1 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 5 -1.12350848782094417e+02 +1 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 6 -1.12335795621220200e+02 +1 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 7 -1.12326767592364490e+02 +1 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 8 -1.12309694043136119e+02 +1 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 9 -1.12309313703334922e+02 +1 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 10 -1.48317963524667054e+01 +1 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 11 -1.26867842000870681e+01 +1 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 12 -9.94155110940419284e+00 +1 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 13 -7.27575048489856968e+00 +1 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 14 3.93187307808883668e-01 +1 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 15 4.99776331705970822e+00 +1 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 16 5.65213817442613653e+00 +1 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 17 6.84364572085804745e+00 +1 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 0 -1.88271146809790912e+03 +1 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 1 -1.88271133330129464e+03 +1 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 2 -1.65269622080538682e+02 +1 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 3 -1.65253727301746181e+02 +1 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 4 -1.12352330488603741e+02 +1 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 5 -1.12352320670509201e+02 +1 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 6 -1.12344902279234432e+02 +1 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 7 -1.12316178398071770e+02 +1 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 8 -1.12310394784435189e+02 +1 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 9 -1.12310386644579793e+02 +1 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 10 -1.68894816921980926e+01 +1 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 11 -1.06471575616595864e+01 +1 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 12 -7.68895246013076683e+00 +1 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 13 -7.68819737931135450e+00 +1 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 14 -1.18278953808794207e+00 +1 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 15 1.84453876659560168e+00 +1 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 16 6.35665142554753793e+00 +1 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 17 6.35713814725400628e+00 +1 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 0 -1.88271142614653718e+03 +1 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 1 -1.88271140303969082e+03 +1 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 2 -1.65264735579530935e+02 +1 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 3 -1.65258121638266829e+02 +1 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 4 -1.12352903979349321e+02 +1 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 5 -1.12350851872406267e+02 +1 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 6 -1.12335802662518176e+02 +1 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 7 -1.12326776282142276e+02 +1 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 8 -1.12309684594673314e+02 +1 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 9 -1.12309315848842246e+02 +1 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 10 -1.48318031029678767e+01 +1 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 11 -1.26869915655298762e+01 +1 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 12 -9.94084003534023530e+00 +1 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 13 -7.27629172564557347e+00 +1 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 14 3.92816167434389407e-01 +1 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 15 4.99804747660045567e+00 +1 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 16 5.65250902957898305e+00 +1 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 17 6.84366501842990971e+00 +1 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 0 -1.88271142614527616e+03 +1 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 1 -1.88271140302754884e+03 +1 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 2 -1.65264735468702440e+02 +1 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 3 -1.65258121304040799e+02 +1 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 4 -1.12352888959405931e+02 +1 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 5 -1.12350862972653246e+02 +1 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 6 -1.12335808008781029e+02 +1 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 7 -1.12326780994008942e+02 +1 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 8 -1.12309672614508059e+02 +1 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 9 -1.12309325126581001e+02 +1 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 10 -1.48317577222315808e+01 +1 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 11 -1.26868980688969710e+01 +1 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 12 -9.94124867010973645e+00 +1 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 13 -7.27605008717420532e+00 +1 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 14 3.92880610241452544e-01 +1 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 15 4.99779681654217978e+00 +1 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 16 5.65237762602887006e+00 +1 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 17 6.84390513975554615e+00 +1 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 0 -1.88271142614656515e+03 +1 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 1 -1.88271140303975108e+03 +1 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 2 -1.65264735579602956e+02 +1 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 3 -1.65258121638294853e+02 +1 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 4 -1.12352903979388330e+02 +1 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 5 -1.12350851872438199e+02 +1 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 6 -1.12335802662547181e+02 +1 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 7 -1.12326776282175885e+02 +1 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 8 -1.12309684594705359e+02 +1 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 9 -1.12309315848874547e+02 +1 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 10 -1.48318031034965827e+01 +1 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 11 -1.26869915651992375e+01 +1 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 12 -9.94084003536196192e+00 +1 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 13 -7.27629172531154289e+00 +1 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 14 3.92816167733401056e-01 +1 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 15 4.99804747633386182e+00 +1 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 16 5.65250902941647304e+00 +1 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 17 6.84366501819901085e+00 +1 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 0 -1.88271146809798415e+03 +1 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 1 -1.88271133330143493e+03 +1 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 2 -1.65269622080664959e+02 +1 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 3 -1.65253727301849409e+02 +1 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 4 -1.12352330488692559e+02 +1 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 5 -1.12352320670586934e+02 +1 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 6 -1.12344902279316273e+02 +1 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 7 -1.12316178398153909e+02 +1 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 8 -1.12310394784518863e+02 +1 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 9 -1.12310386644660014e+02 +1 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 10 -1.68894816922947335e+01 +1 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 11 -1.06471575617228851e+01 +1 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 12 -7.68895246033699120e+00 +1 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 13 -7.68819737923120350e+00 +1 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 14 -1.18278953813904297e+00 +1 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 15 1.84453876654725124e+00 +1 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 16 6.35665142547464956e+00 +1 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 17 6.35713814724243242e+00 +1 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 0 -1.88271142615285089e+03 +1 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 1 -1.88271140303462676e+03 +1 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 2 -1.65264736487738872e+02 +1 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 3 -1.65258122151486305e+02 +1 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 4 -1.12352913952621847e+02 +1 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 5 -1.12350848782171411e+02 +1 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 6 -1.12335795621296242e+02 +1 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 7 -1.12326767592444924e+02 +1 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 8 -1.12309694043215686e+02 +1 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 9 -1.12309313703412442e+02 +1 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 10 -1.48317963527509793e+01 +1 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 11 -1.26867842000472049e+01 +1 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 12 -9.94155110951319543e+00 +1 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 13 -7.27575048485706244e+00 +1 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 14 3.93187308000081337e-01 +1 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 15 4.99776331670615104e+00 +1 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 16 5.65213817441178268e+00 +1 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 17 6.84364572092105572e+00 +1 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 0 -1.88271144953874705e+03 +1 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 1 -1.88271135556596346e+03 +1 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 2 -1.65270011975822200e+02 +1 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 3 -1.65253380136260887e+02 +1 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 4 -1.12355674830131107e+02 +1 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 5 -1.12355657510078927e+02 +1 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 6 -1.12344339858865666e+02 +1 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 7 -1.12318472978347302e+02 +1 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 8 -1.12307657562218893e+02 +1 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 9 -1.12307638931588642e+02 +1 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 10 -1.77143558531588923e+01 +1 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 11 -1.10551768639473451e+01 +1 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 12 -5.82238193811786164e+00 +1 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 13 -5.82231865142818350e+00 +1 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 14 -9.07235550040826455e-01 +1 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 15 2.53915790731763824e+00 +1 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 16 2.53952549601498401e+00 +1 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 17 6.95797085943841509e+00 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 0 -1.88281943961626507e+03 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 1 -1.88281943718410002e+03 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 2 -1.65302825468573843e+02 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 3 -1.65292923598443025e+02 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 4 -1.13212541400676116e+02 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 5 -1.13212532640861440e+02 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 6 -1.13212510529657067e+02 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 7 -1.13173632715239521e+02 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 8 -1.13173624351149698e+02 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 9 -1.13173602653494569e+02 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 10 -1.99042298910824016e+01 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 11 -4.56529737383152145e+00 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 12 -4.56365735022373453e+00 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 13 -4.56297275172692096e+00 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 14 -3.88716619728882787e-02 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 15 4.42673692126531904e-01 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 16 4.43557163122234033e-01 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 17 4.44838887878505507e-01 +2 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 0 -1.88281952145900505e+03 +2 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 1 -1.88281942873881212e+03 +2 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 2 -1.65298963344079368e+02 +2 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 3 -1.65296536818127919e+02 +2 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 4 -1.13210120698102315e+02 +2 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 5 -1.13210103858359261e+02 +2 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 6 -1.13202608588226099e+02 +2 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 7 -1.13181587426813039e+02 +2 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 8 -1.13174856698553285e+02 +2 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 9 -1.13174837942798078e+02 +2 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 10 -1.83347970320921760e+01 +2 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 11 -1.17017171054915003e+01 +2 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 12 -6.12092246005405194e+00 +2 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 13 -6.11840451308519206e+00 +2 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 14 -7.06557081337085169e-01 +2 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 15 2.77211211985006889e+00 +2 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 16 2.77695736746766730e+00 +2 0 0 1 0.00000000000000000e+00 0.00000000000000000e+00 3.33333333333297177e-01 17 7.59013548903934510e+00 +2 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 0 -1.88281952146767162e+03 +2 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 1 -1.88281942873014418e+03 +2 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 2 -1.65298963344078970e+02 +2 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 3 -1.65296536818128544e+02 +2 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 4 -1.13210120698101861e+02 +2 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 5 -1.13210103858357911e+02 +2 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 6 -1.13202608588226241e+02 +2 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 7 -1.13181587426813167e+02 +2 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 8 -1.13174856698553000e+02 +2 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 9 -1.13174837942798661e+02 +2 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 10 -1.83347970320927196e+01 +2 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 11 -1.17017171054918325e+01 +2 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 12 -6.12092246005394713e+00 +2 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 13 -6.11840451308527200e+00 +2 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 14 -7.06557081337833237e-01 +2 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 15 2.77211211984956929e+00 +2 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 16 2.77695736746772814e+00 +2 0 0 2 0.00000000000000000e+00 0.00000000000000000e+00 6.66666666666688390e-01 17 7.59013548904008495e+00 +2 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 0 -1.88281952139290706e+03 +2 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 1 -1.88281942881020336e+03 +2 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 2 -1.65298961025691597e+02 +2 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 3 -1.65296534754775138e+02 +2 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 4 -1.13210115281182837e+02 +2 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 5 -1.13210097825170692e+02 +2 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 6 -1.13202619816029326e+02 +2 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 7 -1.13181598825937215e+02 +2 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 8 -1.13174850472751473e+02 +2 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 9 -1.13174834869908281e+02 +2 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 10 -1.83347889926419576e+01 +2 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 11 -1.17015315561977324e+01 +2 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 12 -6.12131277614568692e+00 +2 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 13 -6.11868992958260893e+00 +2 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 14 -7.06797322048129062e-01 +2 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 15 2.77439402265041535e+00 +2 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 16 2.77547089849135276e+00 +2 0 0 3 0.00000000000000000e+00 3.33333333333287019e-01 0.00000000000000000e+00 17 7.59023730432492982e+00 +2 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 0 -1.88281953948972296e+03 +2 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 1 -1.88281940740054188e+03 +2 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 2 -1.65298455035912411e+02 +2 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 3 -1.65295956079775863e+02 +2 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 4 -1.13208060336833839e+02 +2 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 5 -1.13208029893277939e+02 +2 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 6 -1.13202788824611105e+02 +2 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 7 -1.13179526769288273e+02 +2 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 8 -1.13176682692057483e+02 +2 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 9 -1.13176654562826187e+02 +2 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 10 -1.77504342778565736e+01 +2 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 11 -1.12898860837116981e+01 +2 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 12 -8.04851417153267512e+00 +2 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 13 -8.04751453513995685e+00 +2 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 14 -1.02255075881548430e+00 +2 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 15 2.10147033343576650e+00 +2 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 16 6.87997085852748214e+00 +2 0 0 4 0.00000000000000000e+00 3.33333333333287019e-01 3.33333333333298731e-01 17 6.88063633820098541e+00 +2 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 0 -1.88281949976222381e+03 +2 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 1 -1.88281947530813113e+03 +2 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 2 -1.65297189027766166e+02 +2 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 3 -1.65296726812408366e+02 +2 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 4 -1.13208488323081838e+02 +2 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 5 -1.13207371460944287e+02 +2 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 6 -1.13195870215833992e+02 +2 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 7 -1.13188465268400037e+02 +2 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 8 -1.13176128278764423e+02 +2 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 9 -1.13174779769007316e+02 +2 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 10 -1.61887831527780399e+01 +2 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 11 -1.36848929332847487e+01 +2 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 12 -1.04069338519655208e+01 +2 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 13 -7.62471404954659526e+00 +2 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 14 5.91330162211054389e-01 +2 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 15 5.42190696834345331e+00 +2 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 16 6.10843031852087837e+00 +2 0 0 5 0.00000000000000000e+00 3.33333333333287019e-01 6.66666666666689944e-01 17 7.49153707416817483e+00 +2 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 0 -1.88281952138934116e+03 +2 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 1 -1.88281942881377131e+03 +2 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 2 -1.65298961025691682e+02 +2 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 3 -1.65296534754774740e+02 +2 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 4 -1.13210115281183477e+02 +2 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 5 -1.13210097825169726e+02 +2 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 6 -1.13202619816028090e+02 +2 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 7 -1.13181598825937414e+02 +2 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 8 -1.13174850472751913e+02 +2 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 9 -1.13174834869907215e+02 +2 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 10 -1.83347889926409913e+01 +2 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 11 -1.17015315561991748e+01 +2 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 12 -6.12131277614535119e+00 +2 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 13 -6.11868992958266489e+00 +2 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 14 -7.06797322048297039e-01 +2 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 15 2.77439402265050816e+00 +2 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 16 2.77547089849156858e+00 +2 0 0 6 0.00000000000000000e+00 6.66666666666673957e-01 0.00000000000000000e+00 17 7.59023730432660848e+00 +2 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 0 -1.88281949976224519e+03 +2 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 1 -1.88281947530810908e+03 +2 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 2 -1.65297189027765995e+02 +2 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 3 -1.65296726812408252e+02 +2 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 4 -1.13208488323081951e+02 +2 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 5 -1.13207371460944543e+02 +2 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 6 -1.13195870215834077e+02 +2 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 7 -1.13188465268400179e+02 +2 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 8 -1.13176128278764637e+02 +2 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 9 -1.13174779769007827e+02 +2 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 10 -1.61887831527768569e+01 +2 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 11 -1.36848929332846971e+01 +2 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 12 -1.04069338519646024e+01 +2 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 13 -7.62471404954763621e+00 +2 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 14 5.91330162210833454e-01 +2 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 15 5.42190696834374286e+00 +2 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 16 6.10843031852140861e+00 +2 0 0 7 0.00000000000000000e+00 6.66666666666673957e-01 3.33333333333333648e-01 17 7.49153707416860559e+00 +2 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 0 -1.88281953948997966e+03 +2 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 1 -1.88281940740028904e+03 +2 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 2 -1.65298455035912070e+02 +2 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 3 -1.65295956079775550e+02 +2 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 4 -1.13208060336834464e+02 +2 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 5 -1.13208029893277299e+02 +2 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 6 -1.13202788824610479e+02 +2 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 7 -1.13179526769288032e+02 +2 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 8 -1.13176682692057724e+02 +2 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 9 -1.13176654562826087e+02 +2 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 10 -1.77504342778555291e+01 +2 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 11 -1.12898860837135100e+01 +2 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 12 -8.04851417153258986e+00 +2 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 13 -8.04751453514093029e+00 +2 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 14 -1.02255075881517921e+00 +2 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 15 2.10147033343603562e+00 +2 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 16 6.87997085852788270e+00 +2 0 0 8 0.00000000000000000e+00 6.66666666666673957e-01 6.66666666666630769e-01 17 6.88063633820290033e+00 +2 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 0 -1.88281952148342452e+03 +2 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 1 -1.88281942871368960e+03 +2 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 2 -1.65298962288300288e+02 +2 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 3 -1.65296535377870725e+02 +2 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 4 -1.13210113680958600e+02 +2 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 5 -1.13210087688011058e+02 +2 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 6 -1.13202633115231379e+02 +2 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 7 -1.13181611813466318e+02 +2 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 8 -1.13174849629610250e+02 +2 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 9 -1.13174824073856087e+02 +2 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 10 -1.83347664076528787e+01 +2 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 11 -1.17011790612860835e+01 +2 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 12 -6.11979164774926687e+00 +2 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 13 -6.11973223382639642e+00 +2 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 14 -7.07519270965071057e-01 +2 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 15 2.77537637911244239e+00 +2 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 16 2.77574753427258303e+00 +2 0 0 9 3.33333333333316884e-01 0.00000000000000000e+00 0.00000000000000000e+00 17 7.58952326752006368e+00 +2 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 0 -1.88281953948800697e+03 +2 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 1 -1.88281940739947072e+03 +2 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 2 -1.65298456205631652e+02 +2 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 3 -1.65295956278714101e+02 +2 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 4 -1.13208050624201249e+02 +2 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 5 -1.13208042796269424e+02 +2 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 6 -1.13202783302655277e+02 +2 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 7 -1.13179521692381300e+02 +2 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 8 -1.13176673009762297e+02 +2 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 9 -1.13176666470080917e+02 +2 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 10 -1.77504278608324810e+01 +2 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 11 -1.12895443721485318e+01 +2 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 12 -8.04842821397359387e+00 +2 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 13 -8.04758192322033850e+00 +2 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 14 -1.02260188058298507e+00 +2 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 15 2.10180654145140888e+00 +2 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 16 6.88050387272667141e+00 +2 0 0 10 3.33333333333316884e-01 0.00000000000000000e+00 3.33333333333325710e-01 17 6.88099133044807409e+00 +2 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 0 -1.88281949976781698e+03 +2 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 1 -1.88281947530957746e+03 +2 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 2 -1.65297188818331705e+02 +2 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 3 -1.65296727972800539e+02 +2 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 4 -1.13208479420694843e+02 +2 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 5 -1.13207378554933285e+02 +2 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 6 -1.13195873037940885e+02 +2 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 7 -1.13188465864739953e+02 +2 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 8 -1.13176120670931809e+02 +2 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 9 -1.13174785028388001e+02 +2 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 10 -1.61886677217683683e+01 +2 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 11 -1.36845554396689142e+01 +2 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 12 -1.04070168008617259e+01 +2 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 13 -7.62480900571912734e+00 +2 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 14 5.91027408719718705e-01 +2 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 15 5.42116869215835262e+00 +2 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 16 6.10827062294399159e+00 +2 0 0 11 3.33333333333316884e-01 0.00000000000000000e+00 6.66666666666622998e-01 17 7.49185901150457578e+00 +2 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 0 -1.88281953948794762e+03 +2 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 1 -1.88281940739974061e+03 +2 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 2 -1.65298456205742696e+02 +2 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 3 -1.65295956278809541e+02 +2 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 4 -1.13208050624291346e+02 +2 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 5 -1.13208042796346987e+02 +2 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 6 -1.13202783302739434e+02 +2 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 7 -1.13179521692465642e+02 +2 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 8 -1.13176673009848628e+02 +2 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 9 -1.13176666470160669e+02 +2 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 10 -1.77504278609383839e+01 +2 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 11 -1.12895443722115907e+01 +2 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 12 -8.04842821418707111e+00 +2 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 13 -8.04758192312984555e+00 +2 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 14 -1.02260188061801127e+00 +2 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 15 2.10180654141453838e+00 +2 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 16 6.88050387267088137e+00 +2 0 0 12 3.33333333333316884e-01 3.33333333333363624e-01 0.00000000000000000e+00 17 6.88099133045285516e+00 +2 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 0 -1.88281952152318399e+03 +2 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 1 -1.88281942867422094e+03 +2 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 2 -1.65298962288418750e+02 +2 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 3 -1.65296535378037959e+02 +2 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 4 -1.13210113681081566e+02 +2 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 5 -1.13210087688125128e+02 +2 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 6 -1.13202633115337406e+02 +2 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 7 -1.13181611813575174e+02 +2 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 8 -1.13174849629728769e+02 +2 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 9 -1.13174824073969262e+02 +2 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 10 -1.83347664078660735e+01 +2 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 11 -1.17011790614659610e+01 +2 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 12 -6.11979164768425576e+00 +2 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 13 -6.11973223376941977e+00 +2 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 14 -7.07519270713907411e-01 +2 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 15 2.77537637903220036e+00 +2 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 16 2.77574753432215227e+00 +2 0 0 13 3.33333333333316884e-01 3.33333333333363624e-01 3.33333333333360571e-01 17 7.58952326726621251e+00 +2 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 0 -1.88281949976791770e+03 +2 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 1 -1.88281947530969001e+03 +2 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 2 -1.65297188818410518e+02 +2 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 3 -1.65296727972923350e+02 +2 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 4 -1.13208479420783974e+02 +2 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 5 -1.13207378555010237e+02 +2 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 6 -1.13195873038017496e+02 +2 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 7 -1.13188465864820117e+02 +2 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 8 -1.13176120671015013e+02 +2 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 9 -1.13174785028465791e+02 +2 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 10 -1.61886677220381578e+01 +2 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 11 -1.36845554396072000e+01 +2 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 12 -1.04070168009734463e+01 +2 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 13 -7.62480900566191178e+00 +2 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 14 5.91027408933062715e-01 +2 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 15 5.42116869178995398e+00 +2 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 16 6.10827062294454226e+00 +2 0 0 14 3.33333333333316884e-01 3.33333333333363624e-01 6.66666666666657748e-01 17 7.49185901159383238e+00 +2 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 0 -1.88281949976282476e+03 +2 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 1 -1.88281947531394326e+03 +2 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 2 -1.65297188194009408e+02 +2 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 3 -1.65296727478044119e+02 +2 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 4 -1.13208470846485795e+02 +2 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 5 -1.13207381859830960e+02 +2 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 6 -1.13195878656203689e+02 +2 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 7 -1.13188471829779417e+02 +2 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 8 -1.13176113602471389e+02 +2 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 9 -1.13174786941406367e+02 +2 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 10 -1.61886930172135308e+01 +2 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 11 -1.36848230974039708e+01 +2 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 12 -1.04061515807837655e+01 +2 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 13 -7.62542763550907399e+00 +2 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 14 5.90677112109611047e-01 +2 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 15 5.42143067251496280e+00 +2 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 16 6.10863101802821173e+00 +2 0 0 15 3.33333333333316884e-01 6.66666666666650531e-01 0.00000000000000000e+00 17 7.49177788373232900e+00 +2 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 0 -1.88281949976286842e+03 +2 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 1 -1.88281947531399305e+03 +2 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 2 -1.65297188194033822e+02 +2 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 3 -1.65296727478107670e+02 +2 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 4 -1.13208470846527334e+02 +2 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 5 -1.13207381859863247e+02 +2 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 6 -1.13195878656234939e+02 +2 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 7 -1.13188471829813807e+02 +2 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 8 -1.13176113602508877e+02 +2 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 9 -1.13174786941438853e+02 +2 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 10 -1.61886930174468056e+01 +2 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 11 -1.36848230972730196e+01 +2 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 12 -1.04061515808379479e+01 +2 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 13 -7.62542763539541379e+00 +2 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 14 5.90677112339595189e-01 +2 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 15 5.42143067218094377e+00 +2 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 16 6.10863101803242081e+00 +2 0 0 16 3.33333333333316884e-01 6.66666666666650531e-01 3.33333333331198856e-01 17 7.49177788381368526e+00 +2 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 0 -1.88281949976323790e+03 +2 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 1 -1.88281947530431808e+03 +2 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 2 -1.65297188044209321e+02 +2 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 3 -1.65296727444646763e+02 +2 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 4 -1.13208457724566500e+02 +2 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 5 -1.13207392659819746e+02 +2 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 6 -1.13195882381318754e+02 +2 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 7 -1.13188475644737849e+02 +2 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 8 -1.13176103725858155e+02 +2 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 9 -1.13174795106804652e+02 +2 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 10 -1.61886997222978373e+01 +2 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 11 -1.36846825804582650e+01 +2 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 12 -1.04066488701695157e+01 +2 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 13 -7.62511501294281313e+00 +2 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 14 5.90691974009327492e-01 +2 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 15 5.42120787582597341e+00 +2 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 16 6.10855290238401416e+00 +2 0 0 17 3.33333333333316884e-01 6.66666666666650531e-01 6.66666666666659413e-01 17 7.49211619514369787e+00 +2 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 0 -1.88281952153772932e+03 +2 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 1 -1.88281942865937685e+03 +2 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 2 -1.65298962288298185e+02 +2 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 3 -1.65296535377873624e+02 +2 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 4 -1.13210113680958841e+02 +2 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 5 -1.13210087688010518e+02 +2 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 6 -1.13202633115231137e+02 +2 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 7 -1.13181611813465807e+02 +2 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 8 -1.13174849629609383e+02 +2 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 9 -1.13174824073856570e+02 +2 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 10 -1.83347664076565557e+01 +2 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 11 -1.17011790612852682e+01 +2 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 12 -6.11979164775067019e+00 +2 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 13 -6.11973223382690978e+00 +2 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 14 -7.07519270967902902e-01 +2 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 15 2.77537637911238866e+00 +2 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 16 2.77574753427275533e+00 +2 0 0 18 6.66666666666633767e-01 0.00000000000000000e+00 0.00000000000000000e+00 17 7.58952326752442463e+00 +2 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 0 -1.88281949976780265e+03 +2 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 1 -1.88281947530959519e+03 +2 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 2 -1.65297188818332245e+02 +2 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 3 -1.65296727972800397e+02 +2 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 4 -1.13208479420695738e+02 +2 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 5 -1.13207378554933740e+02 +2 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 6 -1.13195873037940473e+02 +2 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 7 -1.13188465864739854e+02 +2 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 8 -1.13176120670931510e+02 +2 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 9 -1.13174785028387845e+02 +2 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 10 -1.61886677217679669e+01 +2 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 11 -1.36845554396683156e+01 +2 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 12 -1.04070168008595765e+01 +2 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 13 -7.62480900571718667e+00 +2 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 14 5.91027408719734915e-01 +2 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 15 5.42116869215865016e+00 +2 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 16 6.10827062294332279e+00 +2 0 0 19 6.66666666666633767e-01 0.00000000000000000e+00 3.33333333333260207e-01 17 7.49185901150374356e+00 +2 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 0 -1.88281953948819660e+03 +2 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 1 -1.88281940739927677e+03 +2 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 2 -1.65298456205631481e+02 +2 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 3 -1.65295956278713987e+02 +2 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 4 -1.13208050624200695e+02 +2 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 5 -1.13208042796269424e+02 +2 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 6 -1.13202783302656627e+02 +2 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 7 -1.13179521692382124e+02 +2 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 8 -1.13176673009760918e+02 +2 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 9 -1.13176666470080605e+02 +2 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 10 -1.77504278608314436e+01 +2 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 11 -1.12895443721498694e+01 +2 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 12 -8.04842821397399710e+00 +2 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 13 -8.04758192322028698e+00 +2 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 14 -1.02260188058158219e+00 +2 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 15 2.10180654145173573e+00 +2 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 16 6.88050387272700537e+00 +2 0 0 20 6.66666666666633767e-01 0.00000000000000000e+00 6.66666666666651420e-01 17 6.88099133044917988e+00 +2 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 0 -1.88281949976284295e+03 +2 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 1 -1.88281947531392711e+03 +2 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 2 -1.65297188194007077e+02 +2 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 3 -1.65296727478045909e+02 +2 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 4 -1.13208470846484943e+02 +2 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 5 -1.13207381859830491e+02 +2 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 6 -1.13195878656204826e+02 +2 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 7 -1.13188471829778905e+02 +2 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 8 -1.13176113602471418e+02 +2 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 9 -1.13174786941406396e+02 +2 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 10 -1.61886930171006327e+01 +2 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 11 -1.36848230975129770e+01 +2 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 12 -1.04061515807992926e+01 +2 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 13 -7.62542763561822046e+00 +2 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 14 5.90677112076195998e-01 +2 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 15 5.42143067249377442e+00 +2 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 16 6.10863101808940812e+00 +2 0 0 21 6.66666666666633767e-01 3.33333333342019755e-01 0.00000000000000000e+00 17 7.49177788384848675e+00 +2 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 0 -1.88281949976322881e+03 +2 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 1 -1.88281947530432831e+03 +2 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 2 -1.65297188044209520e+02 +2 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 3 -1.65296727444648354e+02 +2 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 4 -1.13208457724566287e+02 +2 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 5 -1.13207392659820258e+02 +2 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 6 -1.13195882381318697e+02 +2 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 7 -1.13188475644737096e+02 +2 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 8 -1.13176103725858113e+02 +2 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 9 -1.13174795106804723e+02 +2 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 10 -1.61886997222986864e+01 +2 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 11 -1.36846825804573786e+01 +2 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 12 -1.04066488701687767e+01 +2 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 13 -7.62511501294258665e+00 +2 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 14 5.90691974009580845e-01 +2 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 15 5.42120787582537034e+00 +2 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 16 6.10855290238477444e+00 +2 0 0 22 6.66666666666633767e-01 3.33333333342019755e-01 3.33333333342019755e-01 17 7.49211619514303528e+00 +2 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 0 -1.88281949976288888e+03 +2 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 1 -1.88281947531397418e+03 +2 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 2 -1.65297188194035698e+02 +2 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 3 -1.65296727478105595e+02 +2 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 4 -1.13208470846527632e+02 +2 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 5 -1.13207381859862224e+02 +2 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 6 -1.13195878656234413e+02 +2 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 7 -1.13188471829815157e+02 +2 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 8 -1.13176113602508579e+02 +2 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 9 -1.13174786941438896e+02 +2 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 10 -1.61886930175584389e+01 +2 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 11 -1.36848230971651788e+01 +2 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 12 -1.04061515808236891e+01 +2 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 13 -7.62542763528763956e+00 +2 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 14 5.90677112373810709e-01 +2 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 15 5.42143067220201580e+00 +2 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 16 6.10863101797131414e+00 +2 0 0 23 6.66666666666633767e-01 3.33333333342019755e-01 6.66666666669579522e-01 17 7.49177788369830289e+00 +2 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 0 -1.88281953948827277e+03 +2 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 1 -1.88281940739941479e+03 +2 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 2 -1.65298456205742411e+02 +2 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 3 -1.65295956278809314e+02 +2 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 4 -1.13208050624291857e+02 +2 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 5 -1.13208042796347158e+02 +2 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 6 -1.13202783302739519e+02 +2 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 7 -1.13179521692465386e+02 +2 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 8 -1.13176673009848699e+02 +2 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 9 -1.13176666470160825e+02 +2 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 10 -1.77504278609383341e+01 +2 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 11 -1.12895443722130295e+01 +2 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 12 -8.04842821418762000e+00 +2 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 13 -8.04758192313106413e+00 +2 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 14 -1.02260188061780211e+00 +2 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 15 2.10180654141409207e+00 +2 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 16 6.88050387267267549e+00 +2 0 0 24 6.66666666666633767e-01 6.66666666666627439e-01 0.00000000000000000e+00 17 6.88099133045339162e+00 +2 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 0 -1.88281949976790793e+03 +2 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 1 -1.88281947530969683e+03 +2 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 2 -1.65297188818411428e+02 +2 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 3 -1.65296727972924060e+02 +2 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 4 -1.13208479420783220e+02 +2 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 5 -1.13207378555010322e+02 +2 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 6 -1.13195873038017822e+02 +2 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 7 -1.13188465864820031e+02 +2 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 8 -1.13176120671015283e+02 +2 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 9 -1.13174785028465919e+02 +2 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 10 -1.61886677220367581e+01 +2 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 11 -1.36845554396074558e+01 +2 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 12 -1.04070168009733273e+01 +2 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 13 -7.62480900566216668e+00 +2 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 14 5.91027408932700227e-01 +2 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 15 5.42116869178981187e+00 +2 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 16 6.10827062294475009e+00 +2 0 0 25 6.66666666666633767e-01 6.66666666666627439e-01 3.33333333333296733e-01 17 7.49185901159440792e+00 +2 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 0 -1.88281952151420637e+03 +2 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 1 -1.88281942868319607e+03 +2 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 2 -1.65298962288419489e+02 +2 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 3 -1.65296535378037788e+02 +2 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 4 -1.13210113681081239e+02 +2 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 5 -1.13210087688125512e+02 +2 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 6 -1.13202633115337449e+02 +2 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 7 -1.13181611813575415e+02 +2 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 8 -1.13174849629728740e+02 +2 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 9 -1.13174824073969575e+02 +2 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 10 -1.83347664078655015e+01 +2 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 11 -1.17011790614666182e+01 +2 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 12 -6.11979164768425310e+00 +2 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 13 -6.11973223377049358e+00 +2 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 14 -7.07519270714146109e-01 +2 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 15 2.77537637903162304e+00 +2 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 16 2.77574753432296006e+00 +2 0 0 26 6.66666666666633767e-01 6.66666666666627439e-01 6.66666666666687946e-01 17 7.58952326726588566e+00 diff --git a/regression_tests/refs/qsgw_aims_Si_k333_headonly_libri/librpa/librpa.d/qsgw_iterations.dat b/regression_tests/refs/qsgw_aims_Si_k333_headonly_libri/librpa/librpa.d/qsgw_iterations.dat new file mode 100644 index 00000000..3937c599 --- /dev/null +++ b/regression_tests/refs/qsgw_aims_Si_k333_headonly_libri/librpa/librpa.d/qsgw_iterations.dat @@ -0,0 +1,18 @@ +# qsgw_contract_version 6 +# fixed_basis immutable_mf0 +# live_update eigenvalues_wfc +# velocity fixed_reference +# head scf_grid_analytic_live +# wing disabled_stage1 +# symmetry exx_off_gw_off_rpa_off +# hartree disabled +# band disabled_stage1 +# h_qsgw_cut disabled_non_band +# qsgw_input_contract ../dataset/qsgw_input.head-only.contract +# qsgw_input_contract_sha256 789a44049ff4f45caabbf7155cc3a2f9c20a40dbc400c130628a5fa36bb9c3b7 +# qsgw_mixer none +# qsgw_mixing_beta 0.20000000000000001 +# iter max_delta_eV residual_l2_Ha residual_max_Ha efermi_eV gap_eV electron_count requested_mode applied_mode beta fallback rcond coefficient_l1 coefficient_count converged coefficients fallback_reason +0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 -4.62473536096244420e+00 1.90984419787082427e+00 2.79999999999995985e+01 -1 -1 2.00000000000000011e-01 0 1.00000000000000000e+00 0.00000000000000000e+00 0 0 none none +1 9.19363138354428315e+01 2.67809404645147211e+01 3.37858834028470767e+00 -2.74178416509205247e+00 3.11798925390537995e+00 2.79999999999996128e+01 -1 -1 2.00000000000000011e-01 0 1.00000000000000000e+00 0.00000000000000000e+00 0 0 none none +2 1.35694200006737264e+00 5.18793966136549378e-01 4.98894375332743500e-02 -2.79278731617246612e+00 3.54037087110890925e+00 2.79999999999996199e+01 -1 -1 2.00000000000000011e-01 0 1.00000000000000000e+00 0.00000000000000000e+00 0 0 none none diff --git a/regression_tests/refs/qsgw_aims_mole_H2O_libri/librpa/librpa.d/qsgw_eigenvalues.dat b/regression_tests/refs/qsgw_aims_mole_H2O_libri/librpa/librpa.d/qsgw_eigenvalues.dat new file mode 100644 index 00000000..99bd0623 --- /dev/null +++ b/regression_tests/refs/qsgw_aims_mole_H2O_libri/librpa/librpa.d/qsgw_eigenvalues.dat @@ -0,0 +1,87 @@ +# qsgw_contract_version 6 +# fixed_basis immutable_mf0 +# live_update eigenvalues_wfc +# velocity disabled_stage1 +# head disabled_stage1 +# wing disabled_stage1 +# symmetry exx_off_gw_off_rpa_off +# hartree disabled +# band disabled_stage1 +# h_qsgw_cut disabled_non_band +# qsgw_input_contract ../dataset/qsgw_input.contract +# qsgw_input_contract_sha256 ba2f60c27a7f746eadd640eba97cf2e6dcb715c41dfc6c72fedacacfb9a74cbc +# qsgw_mixer linear +# qsgw_mixing_beta 0.20000000000000001 +# iter channel spin kpoint kx ky kz band energy_eV +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 0 -5.11264291568801241e+02 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 1 -2.52317549277505080e+01 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 2 -1.30094411178103844e+01 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 3 -9.25981540448206708e+00 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 4 -7.08583317901462451e+00 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 5 8.21963939891753592e-02 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 6 2.76392217425596787e+00 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 7 8.08357104506078983e+00 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 8 9.08548696909238096e+00 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 9 1.00057639603652273e+01 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 10 1.58135427543746037e+01 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 11 2.11055716853985018e+01 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 12 2.39569435673092137e+01 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 13 2.63207145322299354e+01 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 14 3.02929952716714794e+01 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 15 3.13067800450341700e+01 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 16 4.15367363968503298e+01 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 17 5.29696222537144621e+01 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 18 5.54656484918151378e+01 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 19 6.24882871155976574e+01 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 20 6.51914583988309744e+01 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 21 6.59039881564698931e+01 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 22 7.86661524955675731e+01 +0 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 23 8.78821215916043883e+01 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 0 -5.20301052889794846e+02 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 1 -2.65940664886547999e+01 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 2 -1.40980692677330612e+01 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 3 -1.02765296706094702e+01 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 4 -8.05009346701610440e+00 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 5 7.84914855868157346e-01 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 6 3.59836028121052420e+00 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 7 8.97267182548722708e+00 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 8 1.00980970103055370e+01 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 9 1.08679205626465336e+01 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 10 1.69176847370507986e+01 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 11 2.21955091289811541e+01 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 12 2.51719594826342323e+01 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 13 2.75811526309474182e+01 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 14 3.15787741182861019e+01 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 15 3.26132289410355725e+01 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 16 4.27565361532245731e+01 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 17 5.44418258951966720e+01 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 18 5.68534029477951819e+01 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 19 6.39269978677396651e+01 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 20 6.66505706978662147e+01 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 21 6.74641497604935552e+01 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 22 8.05271618394183264e+01 +1 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 23 8.96918099386045924e+01 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 0 -5.27356834454978980e+02 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 1 -2.76332633730737776e+01 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 2 -1.49703520528607541e+01 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 3 -1.10901699283755040e+01 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 4 -8.81916046535808107e+00 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 5 1.33514524554955005e+00 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 6 4.25546699350178059e+00 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 7 9.69254690399988661e+00 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 8 1.09137175617558082e+01 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 9 1.15589253865707846e+01 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 10 1.78223999136687432e+01 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 11 2.31135732877460995e+01 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 12 2.61455858333374422e+01 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 13 2.85878517073257754e+01 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 14 3.26056848965873414e+01 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 15 3.36606568607649237e+01 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 16 4.37201002480100200e+01 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 17 5.56454420984518663e+01 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 18 5.79921004470786343e+01 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 19 6.51249182120017025e+01 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 20 6.78476948612600097e+01 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 21 6.87097091594189635e+01 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 22 8.18452679972980945e+01 +2 0 0 0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 23 9.10404534094078599e+01 diff --git a/regression_tests/refs/qsgw_aims_mole_H2O_libri/librpa/librpa.d/qsgw_iterations.dat b/regression_tests/refs/qsgw_aims_mole_H2O_libri/librpa/librpa.d/qsgw_iterations.dat new file mode 100644 index 00000000..f55b6dca --- /dev/null +++ b/regression_tests/refs/qsgw_aims_mole_H2O_libri/librpa/librpa.d/qsgw_iterations.dat @@ -0,0 +1,18 @@ +# qsgw_contract_version 6 +# fixed_basis immutable_mf0 +# live_update eigenvalues_wfc +# velocity disabled_stage1 +# head disabled_stage1 +# wing disabled_stage1 +# symmetry exx_off_gw_off_rpa_off +# hartree disabled +# band disabled_stage1 +# h_qsgw_cut disabled_non_band +# qsgw_input_contract ../dataset/qsgw_input.contract +# qsgw_input_contract_sha256 ba2f60c27a7f746eadd640eba97cf2e6dcb715c41dfc6c72fedacacfb9a74cbc +# qsgw_mixer linear +# qsgw_mixing_beta 0.20000000000000001 +# iter max_delta_eV residual_l2_Ha residual_max_Ha efermi_eV gap_eV electron_count requested_mode applied_mode beta fallback rcond coefficient_l1 coefficient_count converged coefficients fallback_reason +0 0.00000000000000000e+00 0.00000000000000000e+00 0.00000000000000000e+00 -3.50181839251272420e+00 7.16802957300379973e+00 1.00000000000000000e+01 -1 -1 2.00000000000000011e-01 0 1.00000000000000000e+00 0.00000000000000000e+00 0 0 none none +1 9.03676132099355023e+00 2.01986814950554106e+00 1.66041930876287935e+00 -3.63258930557397353e+00 8.83500832288426352e+00 1.00000000000000000e+01 0 0 2.00000000000000011e-01 0 1.00000000000000000e+00 1.00000000000000000e+00 1 0 1.00000000000000000e+00 none +2 7.05578156518423150e+00 1.58487151937529358e+00 1.29635109828567963e+00 -3.74200760990426584e+00 1.01543057109076322e+01 1.00000000000000000e+01 0 0 2.00000000000000011e-01 0 1.00000000000000000e+00 1.00000000000000000e+00 1 0 1.00000000000000000e+00 none diff --git a/regression_tests/testcases/qsgw_aims_Si_k333_headonly_libri/dataset.tar.gz b/regression_tests/testcases/qsgw_aims_Si_k333_headonly_libri/dataset.tar.gz new file mode 100644 index 00000000..6078cc38 Binary files /dev/null and b/regression_tests/testcases/qsgw_aims_Si_k333_headonly_libri/dataset.tar.gz differ diff --git a/regression_tests/testcases/qsgw_aims_Si_k333_headonly_libri/librpa/librpa.in b/regression_tests/testcases/qsgw_aims_Si_k333_headonly_libri/librpa/librpa.in new file mode 100644 index 00000000..3a73d952 --- /dev/null +++ b/regression_tests/testcases/qsgw_aims_Si_k333_headonly_libri/librpa/librpa.in @@ -0,0 +1,35 @@ +task = qsgw +input_dir = ../dataset +output_level = info +constants_choice = aims +version_coul_reader = 1 +prefix_coul_full = coulomb_full_iq +prefix_coul_cut = coulomb_cut_iq +nfreq = 6 +tfgrid_type = minimax +n_params_anacon = -1 +n_params_anacon_resample = -1 +anacon_nfreq = -1 +replace_w_head = true +option_dielect_func = 4 +parallel_routing = libri +vq_threshold = 0 +sqrt_coulomb_threshold = 0 +use_scalapack_gw_wc = true +use_shrink_abfs = false +use_shrink_chi = false +use_pyatb = false +use_fullcoul_exx = false +use_fullcoul_eps = true +use_fullcoul_wc = false +use_symmetry_exx = false +use_symmetry_gw = false +use_symmetry_rpa = false +use_kpara_scf_eigvec = false +output_energy_qp = false +qsgw_input_contract = qsgw_input.head-only.contract +qsgw_mixer = none +qsgw_min_iter = 2 +qsgw_max_iter = 2 +qsgw_band0_cut_mode = 0 +qsgw_write_iteration_matrices = false diff --git a/regression_tests/testcases/qsgw_aims_mole_H2O_libri/dataset.tar.gz b/regression_tests/testcases/qsgw_aims_mole_H2O_libri/dataset.tar.gz new file mode 100644 index 00000000..98d9a0e2 Binary files /dev/null and b/regression_tests/testcases/qsgw_aims_mole_H2O_libri/dataset.tar.gz differ diff --git a/regression_tests/testcases/qsgw_aims_mole_H2O_libri/librpa/librpa.in b/regression_tests/testcases/qsgw_aims_mole_H2O_libri/librpa/librpa.in new file mode 100644 index 00000000..33bb97d9 --- /dev/null +++ b/regression_tests/testcases/qsgw_aims_mole_H2O_libri/librpa/librpa.in @@ -0,0 +1,31 @@ +# Driver parameters +task = qsgw +input_dir = ../dataset +constants_choice = aims + +# G0W0 kernel +nfreq = 6 +tfgrid_type = minimax +n_params_anacon = -1 +n_params_anacon_resample = -1 +anacon_nfreq = -1 +parallel_routing = libri +vq_threshold = 0 +sqrt_coulomb_threshold = 0 +use_scalapack_gw_wc = true +use_shrink_abfs = false +use_shrink_chi = false +replace_w_head = false +use_symmetry_exx = false +use_symmetry_gw = false +use_symmetry_rpa = false +use_kpara_scf_eigvec = false + +# QSGW fixed-basis iteration +qsgw_input_contract = qsgw_input.contract +qsgw_mixer = linear +qsgw_mixing_beta = 0.2 +qsgw_min_iter = 2 +qsgw_max_iter = 2 +qsgw_convergence_tolerance_ev = 1e-4 +qsgw_write_iteration_matrices = false diff --git a/regression_tests/testsuite.xml b/regression_tests/testsuite.xml index c966fe22..dd954409 100644 --- a/regression_tests/testsuite.xml +++ b/regression_tests/testsuite.xml @@ -385,6 +385,32 @@ /> + + + + + + + + + + + + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5a31d9d0..331966f4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -123,9 +123,6 @@ list(APPEND lib_sources mpi/two_level_parallel_context.cpp mpi/kpoint_blacs_parallel_context.cpp - # qsgw/fermi_energy_occupation.cpp - # qsgw/Hamiltonian.cpp - utils/constants.cpp utils/dev_options.cpp utils/profiler.cpp @@ -165,6 +162,7 @@ endif() # source must be PRIVATE to avoid recompilation when linking driver to this library target_sources(rpa_lib PRIVATE ${lib_sources}) +add_subdirectory(qsgw) target_link_libraries(rpa_lib PRIVATE ${math_libs} ${parallel_libs}) install(TARGETS rpa_lib diff --git a/src/core/pbc.h b/src/core/pbc.h index de0b3ff9..3db1290d 100644 --- a/src/core/pbc.h +++ b/src/core/pbc.h @@ -200,6 +200,14 @@ class AtomPairBvKRemap return &it_R->second; } const std::map &at(const atom_pair_type &atom_pair) const { return remap_.at(atom_pair); } + bool erase_mapping(const atom_pair_type &atom_pair, const R_type &R) + { + const auto atom_pair_it = remap_.find(atom_pair); + if (atom_pair_it == remap_.end()) return false; + const bool erased = atom_pair_it->second.erase(R) != 0; + if (atom_pair_it->second.empty()) remap_.erase(atom_pair_it); + return erased; + } std::size_t size() const { return remap_.size(); } bool empty() const { return remap_.empty(); } bool is_empty() const { return remap_.empty(); } diff --git a/src/qsgw/CMakeLists.txt b/src/qsgw/CMakeLists.txt new file mode 100644 index 00000000..dc2259bf --- /dev/null +++ b/src/qsgw/CMakeLists.txt @@ -0,0 +1,18 @@ +target_sources(rpa_lib PRIVATE + band_bvk_remap.cpp + band_output.cpp + correlation_potential.cpp + convergence.cpp + distributed_matrix.cpp + effective_hamiltonian.cpp + fixed_basis.cpp + hamiltonian_cut.cpp + hamiltonian_mixing.cpp + input_contract.cpp + iteration_trace.cpp + mixing.cpp + occupation.cpp + projection_target.cpp + sha256.cpp + vxc_io.cpp +) diff --git a/src/qsgw/Hamiltonian.cpp b/src/qsgw/Hamiltonian.cpp deleted file mode 100644 index 1ed8e1bd..00000000 --- a/src/qsgw/Hamiltonian.cpp +++ /dev/null @@ -1,586 +0,0 @@ -#include "Hamiltonian.h" -#include "fermi_energy_occupation.h" -#include "fermi_energy_occupation.h" -#include -#include "pbc.h" -#include "constants.h" - - -// 定义复数类型 -using cplxdb = std::complex; - -//G0, for scRPA -std::vector> build_G0( - MeanField& meanfield, - const std::vector& freq_nodes, - int ispin, - int ikpt, - int n_bands) { - // 获取虚频点列表和能量列表 - std::vector eigenvals(n_bands); - for (int n = 0; n < n_bands; ++n) { - eigenvals[n] = meanfield.get_eigenvals()[ispin](ikpt, n); - } - - // 创建 G0 矩阵,大小为 n_bands x freq_nodes.size() - std::vector> G0(n_bands, std::vector(2*freq_nodes.size())); - - for (int n = 0; n < n_bands; ++n) { - for (size_t w = 0; w < freq_nodes.size(); ++w) { - // 计算 G_n^0(iω) = 1 / (iω - ε_n) - cplxdb iw(0.0, freq_nodes[w]); // 虚频 iω - G0[n][w] = 1.0 / (iw - eigenvals[n]); - - G0[n][w+freq_nodes.size()] = 1.0 / (-iw - eigenvals[n]); - - } - } - return G0; -} -// mode A -Matz build_correlation_potential_spin_k_modeA( - const std::vector>>& sigc_spin_k, - int n_bands) { - // std::cout << "QSGW: mode A" << std::endl; - Matz Vc_spin_k(n_bands, n_bands, MAJOR::COL); - std::map Re_sigma; - std::map sigma; - // std::cout << "check112 " << std::endl; - for (int k = 0 ; k < n_bands; ++k) - { - Re_sigma[k] = Matz(n_bands, n_bands, MAJOR::COL); - sigma[k] = Matz(n_bands, n_bands, MAJOR::COL); - for (int i = 0; i < n_bands; ++i) - { - for (int j = 0; j < n_bands; ++j) - { - sigma[k](i,j) = sigc_spin_k[i][j][k] ; - } - } - Re_sigma[k] = 0.5 * (sigma[k] + transpose(sigma[k],true)); - } - // std::cout << "check113 " << std::endl; - for (int i = 0; i < n_bands; ++i) - { - for (int j = 0; j < n_bands; ++j) - { - - // 构建关联势矩阵 - std::complex Vc_ij = 0.5 * (Re_sigma[i](i,j) + Re_sigma[j](i,j)); - Vc_spin_k(i, j) = Vc_ij; - } - } - - return Vc_spin_k; -} - -//mode B -Matz build_correlation_potential_spin_k( - const std::vector>>& sigc_spin_k, - int n_bands) { - // std::cout << "QSGW: mode B" << std::endl; - Matz Vc_spin_k(n_bands, n_bands, MAJOR::COL); - std::map Re_sigma; - std::map sigma; - for (int k = 0 ; k < n_bands+1; ++k) - { - Re_sigma[k] = Matz(n_bands, n_bands, MAJOR::COL); - sigma[k] = Matz(n_bands, n_bands, MAJOR::COL); - for (int i = 0; i < n_bands; ++i) - { - for (int j = 0; j < n_bands; ++j) - { - sigma[k](i,j) = sigc_spin_k[i][j][k] ; - } - } - Re_sigma[k] = 0.5 * (sigma[k] + transpose(sigma[k],true)); - } - - for (int i = 0; i < n_bands; ++i) - { - - std::complex Vc_ii = Re_sigma[i](i,i); - Vc_spin_k(i, i) = Vc_ii; - for (int j = 0; j < n_bands; ++j) - { - if(i!=j){ - - std::complex Vc_ij = Re_sigma[n_bands](i,j); - Vc_spin_k(i, j) = Vc_ij; - } - } - } - return Vc_spin_k; -} - - -// 计算scRPA交换-关联能量矩阵 -Matz calculate_scRPA_exchange_correlation( - MeanField& meanfield, - const std::vector& freq_nodes, - const std::vector& freq_weights, - const std::map& sigc_spin_k, - const std::vector>>& sigc_sk_mat, - const std::vector>& G0, - int ispin, - int ikpt, - int n_bands, - double temperature) -{ - Matz V_rpa_ks(n_bands, n_bands, MAJOR::COL); // 交换-关联能量矩阵,列优先存储 - const double K_B = 3.16681e-6; // Hartree/K - std::map Re_sigma; - std::map sigma; - double mu = meanfield.get_efermi(); - // std::cout << "scrpa00" << std::endl; - for (int k = 0 ; k < n_bands; ++k) - { - Re_sigma[k] = Matz(n_bands, n_bands, MAJOR::COL); - sigma[k] = Matz(n_bands, n_bands, MAJOR::COL); - // std::cout << "scrpa01" << std::endl; - for (int i = 0; i < n_bands; ++i) - { - for (int j = 0; j < n_bands; ++j) - { - // std::cout << "scrpa02" << std::endl; - sigma[k](i,j) = sigc_sk_mat[i][j][k] ; - // std::cout << "scrpa1" << std::endl; - } - } - Re_sigma[k] = 0.5 * (sigma[k] + transpose(sigma[k],true)); - } - // std::cout << "scrpa2" << std::endl; - for (int n = 0; n < n_bands; ++n) { - // 获取填充因子 fn - double energy_n = meanfield.get_eigenvals()[ispin](ikpt, n); - double f_n = fermi_dirac(energy_n, mu, temperature) * 2.0 / meanfield.get_n_spins(); - - for (int m = 0; m < n_bands; ++m) { - cplxdb V_nm = 0.0; - // 获取填充因子 fm - double energy_m = meanfield.get_eigenvals()[ispin](ikpt, m); - double f_m = fermi_dirac(energy_m, mu, temperature) * 2.0 / meanfield.get_n_spins(); - - // 构建关联势矩阵 - std::complex Vc_nm = 0.5 * (Re_sigma[n](n,m) + Re_sigma[m](n,m)); - - // std::cout << "scrpa3" << std::endl; - if ( (energy_n - mu) * (energy_m - mu) < 0){ - // double delta_nm = (f_n - f_m)/(energy_n - energy_m); - double delta_nm = f_n - f_m; - // // 如果 f_n - f_m 太小,则跳过该计算 - // if (std::abs(delta_nm) < threshold) { - // V_rpa_ks(n, m) = 0.0; - // continue; // 跳过这个循环的进一步计算 - // } - // std::cout << "scrpa4" << std::endl; - // 累加交换-关联项 - for (size_t w = 0; w < freq_weights.size(); ++w) { - cplxdb sigc_nm_iw = sigc_spin_k.at(freq_nodes[w])(n, m); - cplxdb sigc_nm_minus_iw = sigc_spin_k.at(-freq_nodes[w])(n, m); - V_nm += freq_weights[w] * (sigc_nm_iw * (G0[n][w] - G0[m][w]) + sigc_nm_minus_iw * (G0[n][w+freq_weights.size()] - G0[m][w+freq_weights.size()])); - } - - // 归一化并存储 - V_rpa_ks(n, m) = V_nm / delta_nm; - } - else{ - V_rpa_ks(n, m) = Vc_nm; - } - - - - } - } - - return V_rpa_ks; -} - - - -std::map> construct_H0_GW( - MeanField& meanfield, - const std::map> & H_KS_all, - const std::map> & vxc_all, - const std::map> & Hexx_all, - const std::map> & Vc_all, - int n_spins, int n_kpoints, int n_bands) { - - // 初始化 GW 哈密顿量矩阵 - std::map> H0_GW_all; - - double efermi = meanfield.get_efermi(); - for (int ispin = 0; ispin < n_spins; ++ispin) - { - for (int ikpt = 0; ikpt < n_kpoints; ++ikpt) - { - Matz Hexx_ispin_ik = Hexx_all.at(ispin).at(ikpt); - Matz Vxc_construct_ispin_ik = Hexx_ispin_ik + Vc_all.at(ispin).at(ikpt); - // // realize in k-space - // for (int i = 0; i < n_bands; ++i){ - // for (int j = 0; j < n_bands; ++j){ - // Vxc_construct_ispin_ik(i,j) = std::real(Vxc_construct_ispin_ik(i,j)); - // // Vxc_construct_ispin_ik(i,j) = std::real(Vc_all.at(ispin).at(ikpt)(i,j)) + Hexx_ispin_ik(i,j); - // } - // } - - // cut if possible - // Matz Vxc_diff_spin_k = Hexx_ispin_ik + Vc_all.at(ispin).at(ikpt) - vxc_all.at(ispin).at(ikpt); - // for (int i = 0; i < n_bands; ++i){ - // double energy_i = meanfield.get_eigenvals()[ispin](ikpt, i); - // for (int j = 0; j < n_bands; ++j){ - // double energy_j = meanfield.get_eigenvals()[ispin](ikpt, j); - // if(energy_i > efermi+1.75||energy_j > efermi+1.75){ - // Vxc_diff_spin_k(i, j)= 0.0 ; - // } - // } - // } - // 构建 GW 哈密顿量矩阵 - Matz H0_GW_spin_k = H_KS_all.at(ispin).at(ikpt) - vxc_all.at(ispin).at(ikpt) + Vxc_construct_ispin_ik; - - H0_GW_all[ispin][ikpt] = H0_GW_spin_k; - } - } - - return H0_GW_all; -} - -std::map> construct_H0_HF( - MeanField& meanfield, - const std::map> & H_KS_all, - const std::map> & vxc_all, - const std::map> & Hexx_all, - int n_spins, int n_kpoints, int n_bands) { - - // 初始化 HF 哈密顿量矩阵 - std::map> H0_HF_all; - for (int ispin = 0; ispin < n_spins; ++ispin) - { - for (int ikpt = 0; ikpt < n_kpoints; ++ikpt) - { - Matz Hexx_ispin_ik = Hexx_all.at(ispin).at(ikpt); - Matz H0_HF_spin_k = H_KS_all.at(ispin).at(ikpt) - vxc_all.at(ispin).at(ikpt) + Hexx_ispin_ik ; - H0_HF_all[ispin][ikpt] = H0_HF_spin_k; - } - } - - return H0_HF_all; -} - -void diagonalize_and_store(MeanField& meanfield, const std::map>& H0_GW_all, - int n_spins, int n_kpoints, int dimension) -{ - int n_bands = meanfield.get_n_bands(); - int n_soc = meanfield.get_n_soc(); - int nao = meanfield.get_n_aos(); - for (int ispin = 0; ispin < n_spins; ++ispin) - { - for (int ikpt = 0; ikpt < n_kpoints; ++ikpt) - { - // 取出相应的哈密顿量矩阵 - const auto &h = H0_GW_all.at(ispin).at(ikpt).copy(); - const std::string final_banner(90, '-'); - // 对角化哈密顿量,得到 QP 波函数在 KS 表象下的表示 - std::vector w; - Matz eigvec_KS; - - // // 打印哈密顿量矩阵 - // printf("%77s\n", final_banner.c_str()); - // printf("Hamiltonian matrix (h):\n"); - // for (int i = 0; i < dimension; ++i) { - // for (int j = 0; j < n_bands; ++j) { - // printf("%20.16f ", h(i, j).real()); - // } - // printf("\n"); // 换行 - // } - // printf("%77s\n", final_banner.c_str()); - - eigsh(h, w, eigvec_KS); - - // // 打印本征值 w - // printf("Eigenvalues (w):\n"); - // for (int i = 0; i < dimension; ++i) { - // printf("%20.16f\n", w[i]); - // } - // printf("%77s\n", final_banner.c_str()); - - // // 打印本征向量矩阵 eigvec_KS - // printf("Eigenvectors (eigvec_KS):\n"); - // for (int i = 0; i < dimension; ++i) { - // for (int j = 0; j < n_bands; ++j) { - // printf("%20.16f ", eigvec_KS(i, j).real()); - // } - // printf("\n"); // 换行 - // } - // printf("%77s\n", final_banner.c_str()); - - - // 将本征值存储到 MeanField 的 eskb 矩阵 - for (int ib = 0; ib < dimension; ++ib) - { - meanfield.get_eigenvals()[ispin](ikpt, ib) = w[ib]; - } - - - // 将本征向量存储到 MeanField 的 wfc 矩阵 - Matz wfc(dimension, nao * n_soc, MAJOR::COL); - for (int ib1 = 0; ib1 < dimension; ++ib1) - { - for (int isoc = 0; isoc < n_soc; isoc++) - { - for (int iao = 0; iao < nao; iao++) - { - int ib2 = iao * n_soc + isoc; - wfc(ib1, ib2) = meanfield.get_eigenvectors0()[ispin][isoc][ikpt](ib1, iao); - } - } - } - - - auto eigvec_NAO = transpose(eigvec_KS) * wfc; - - // for (int isoc = 0; isoc < n_soc; isoc++) - - // printf("%77s\n", final_banner.c_str()); - // printf("Eigenvectors2:\n"); - // for (int i = 0; i < meanfield.get_n_bands(); i++) { - // for (int j = 0; j < meanfield.get_n_bands(); j++) { - // const auto &eigenvectors = meanfield.get_eigenvectors()[ispin][ikpt](i, j) ; - // printf("%20.16f ", eigenvectors.real()); - // } - // printf("\n"); // 换行 - // } - // printf("%77s\n", final_banner.c_str()); - // printf("\n"); - // 将 KS 表示旋转到 NAO 表示 - - for (int ib1 = 0; ib1 < dimension; ++ib1) - { - for (int isoc = 0; isoc < n_soc; isoc++) - { - for (int iao = 0; iao < nao; iao++) - { - int ib2 = iao * n_soc + isoc; - meanfield.get_eigenvectors()[ispin][isoc][ikpt](ib1, iao) = eigvec_NAO(ib1, ib2); - } - } - } - // printf("%77s\n", final_banner.c_str()); - // printf("Eigenvectors3:\n"); - // for (int i = 0; i < meanfield.get_n_bands(); i++) { - // for (int j = 0; j < meanfield.get_n_bands(); j++) { - // const auto &eigenvectors = meanfield.get_eigenvectors()[ispin][ikpt](i, j) ; - // printf("%20.16f ", eigenvectors.real()); - // } - // printf("\n"); // 换行 - // } - // printf("%77s\n", final_banner.c_str()); - // printf("\n"); - } - - - } - std::cout << "所有本征值已存储到 MeanField 对象。" << std::endl; -} - - -// -// std::map FT_K_TO_R(MeanField& meanfield, const std::map& Vk_ispin, const std::vector>& Rlist) -// { -// std::map Vr_ispin; -// const auto n_aos = meanfield.get_n_aos(); -// // 遍历实空间格点向量列表 -// for (auto R : Rlist) -// { -// auto iteR = std::find(Rlist.cbegin(), Rlist.cend(), R); -// auto iR = std::distance(Rlist.cbegin(), iteR); -// // std::cout << "iR 的值是: " << iR << std::endl; -// Vr_ispin[iR] = Matz(n_aos, n_aos, MAJOR::COL); -// Vr_ispin[iR].zero_out(); -// for (int i = 0; i < n_aos; i++){ -// for (int j = 0; j < n_aos; j++){ -// complex element = 0.0; -// for (int i_kpoint = 0; i_kpoint < meanfield.get_n_kpoints(); i_kpoint++) -// { -// auto Vk_element = Vk_ispin.at(i_kpoint)(i,j); -// auto k = kfrac_list[i_kpoint]; -// double ang = - k * R * TWO_PI ; -// // double ang_check = - k * R ; -// // std::cout << "ang 的值是: " << ang_check << std::endl; -// complex kphase = complex(cos(ang), sin(ang)); -// element += Vk_element * kphase; -// // element += 1.0 * kphase; - -// } -// Vr_ispin[iR](i,j)=element; -// // for (int i = 0; i < n_aos; ++i){ -// // for (int j = 0; j < n_aos; ++j){ -// // Vr_ispin[iR](i,j)=std::real(Vr_ispin[iR](i,j)); -// // } -// // } -// } -// } -// } -// return Vr_ispin; -// } - - - - -// std::map FT_R_TO_K(MeanField& meanfield, const std::map& Vr_ispin, const std::vector>& Rlist) -// { -// std::map Vk_ispin; -// const auto n_aos = meanfield.get_n_aos(); -// // 遍历实空间格点向量列表 -// for (int i_kpoint = 0; i_kpoint < meanfield.get_n_kpoints(); i_kpoint++) -// { -// Vk_ispin[i_kpoint] = Matz(n_aos, n_aos, MAJOR::COL); -// Vk_ispin[i_kpoint].zero_out(); -// auto k = kfrac_list[i_kpoint]; -// for (int i = 0; i < n_aos; i++){ -// for (int j = 0; j < n_aos; j++){ -// complex element = 0.0; -// for (auto R : Rlist) -// { -// auto iteR = std::find(Rlist.cbegin(), Rlist.cend(), R); -// auto iR = std::distance(Rlist.cbegin(), iteR); -// auto Vr_element = Vr_ispin.at(iR)(i,j); -// double ang = k * R * TWO_PI ; -// complex kphase = complex(cos(ang), sin(ang)); -// element += Vr_element * kphase / static_cast(meanfield.get_n_kpoints()); -// // element += 1.0 * kphase; -// } -// Vk_ispin[i_kpoint](i,j)=element; -// } -// } -// } -// return Vk_ispin; -// } - -//interation diag - -// Matz calculate_scRPA_xc_g_matrix( -// const std::vector& freq_nodes, -// const std::vector& freq_weights, -// const Matz& sigx_spin_k, -// const std::map& sigc_spin_k, -// const std::vector>& G0, -// int n_bands ) -// { -// Matz V_rpa_ks(n_bands, n_bands, MAJOR::COL); -// for (int n = 0; n < n_bands; ++n) { -// for (int m = 0; m < n_bands; ++m) { -// cplxdb V_nm = 0.0; -// for (size_t w = 0; w < freq_weights.size(); ++w) { -// cplxdb sig_nm_iw = sigc_spin_k.at(freq_nodes[w])(n, m)+sigx_spin_k(n, m); -// V_nm += freq_weights[w] * sig_nm_iw * G0[n][w] ; -// } -// V_rpa_ks(n, m) = V_nm ; -// } -// } -// return V_rpa_ks; -// } - -// Matz calculate_scRPA_lambda_matrix( -// MeanField& meanfield, -// const Matz& H_KS0, -// const Matz& vxc0, -// const Matz& gxc_matrix_diger, -// double mu, -// double temperature, -// int ispin, -// int ikpt, -// int n_bands) -// { -// Matz lambda_matrix(n_bands, n_bands, MAJOR::COL); -// for (int i = 0; i < n_bands; ++i) -// { -// for (int j = 0; j < n_bands; ++j) -// { -// double energy_n = meanfield.get_eigenvals()[ispin](ikpt, j); -// double f_n = fermi_dirac(energy_n, mu, temperature) * 2.0 / meanfield.get_n_spins(); -// std::complex Vc_ij = f_n *(H_KS0(i, j) - vxc0(i, j)) + gxc_matrix_diger(i, j); -// lambda_matrix(i, j) = Vc_ij; -// } -// } -// return lambda_matrix; -// } - -// Matz construct_F_matrix(const Matz& lambda_matrix,std::vector& F_eigenvalues,int n_bands,int iteration) -// { -// Matz F_matrix(n_bands, n_bands, MAJOR::COL); -// Matz lambda_matrix_diger = transpose(lambda_matrix,true); -// std::vector w; -// if(iteration==1){ -// Matz Re_lambda_matrix(n_bands, n_bands, MAJOR::COL); -// Matz eigvec; -// Re_lambda_matrix = 0.5 * (lambda_matrix + lambda_matrix_diger); -// eigsh(Re_lambda_matrix, w, eigvec); -// } -// else{ -// w=F_eigenvalues; -// } - - -// for (int i = 0; i < n_bands; ++i) -// { -// F_matrix(i, i) = w[i]; -// for (int j = 0; j < n_bands; ++j) -// { -// if(i F_ij = lambda_matrix(i, j) - lambda_matrix_diger(i, j); -// F_matrix(i, j) = F_ij; -// } -// if(i>j){ -// std::complex F_ij = lambda_matrix_diger(i, j) - lambda_matrix(i, j); -// F_matrix(i, j) = F_ij; -// } -// } -// } - -// if(F_matrix==transpose(F_matrix,true)){ -// std::cout << "F_matrix is hermitian" << std::endl; -// } -// else{ -// std::cout << "F_matrix is not hermitian" << std::endl; -// } -// return F_matrix; -// } - -// std::vector solve_update_F_matrix(MeanField& meanfield,const Matz& F_matrix,int ispin,int ikpt,int n_bands) -// { -// std::vector w; -// Matz eigvec_KS; -// eigsh(F_matrix, w, eigvec_KS); -// std::vector F_eigenvalues; -// for (int i = 0; i < n_bands; ++i) -// { -// F_eigenvalues.push_back(w[i]); -// } - -// Matz wfc(n_bands, n_bands, MAJOR::COL); - -// for (int ib = 0; ib < n_bands; ++ib) -// { -// for (int iao = 0; iao < n_bands; iao++) -// { -// wfc(ib, iao) = meanfield.get_eigenvectors0()[ispin][ikpt](ib, iao); -// } -// } -// auto eigvec_NAO = transpose(eigvec_KS) * wfc; - -// for (int ib = 0; ib < n_bands; ++ib) -// { -// for (int iao = 0; iao < n_bands; iao++) -// { -// meanfield.get_eigenvectors()[ispin][ikpt](ib, iao) = eigvec_NAO(ib, iao); -// } -// } - - - - -// return F_eigenvalues; -// } - - diff --git a/src/qsgw/Hamiltonian.h b/src/qsgw/Hamiltonian.h deleted file mode 100644 index 4096a4f4..00000000 --- a/src/qsgw/Hamiltonian.h +++ /dev/null @@ -1,90 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "matrix_m.h" -#include "meanfield.h" - -//G0, for scRPA -std::vector> build_G0( - MeanField& meanfield, - const std::vector& freq_nodes, - int ispin, - int ikpt, - int n_states); - -// 构建关联势函数 -Matz build_correlation_potential_spin_k( - const std::vector>>& sigc_spin_k, - int n_states); - -Matz build_correlation_potential_spin_k_modeA( - const std::vector>>& sigc_spin_k, - int n_states); - -Matz calculate_scRPA_exchange_correlation( - MeanField& meanfield, - const std::vector& freq_nodes, - const std::vector& freq_weights, - const std::map& sigc_spin_k, - const std::vector>>& sigc_sk_mat, - const std::vector>& G0, - int ispin, - int ikpt, - int n_states, - double temperature) ; - -// 构建哈密顿量函数 -std::map> construct_H0_GW( - MeanField& meanfield, - const std::map> & H_KS_all, - const std::map> & vxc_all, - const std::map> & Hexx_all, - const std::map> & Vc_all, - int n_spins, int n_kpoints, int n_states); - -std::map> construct_H0_HF( - MeanField& meanfield, - const std::map> & H_KS_all, - const std::map> & vxc_all, - const std::map> & Hexx_all, - int n_spins, int n_kpoints, int n_states); - -// 对 Hamiltonian 进行对角化并存储本征值和本征矢量 -void diagonalize_and_store(MeanField& meanfield, const std::map>& H0_GW_all, - int n_spins, int n_kpoints, int dimension); - -std::map FT_R_TO_K( - MeanField& meanfield, - const std::map& Vr_ispin, - const std::vector>& Rlist); - -std::map FT_K_TO_R( - MeanField& meanfield, - const std::map& Vk_ispin, - const std::vector>& Rlist); - -Matz calculate_scRPA_xc_g_matrix( - const std::vector& freq_nodes, - const std::vector& freq_weights, - const Matz& sigx_spin_k, - const std::map& sigc_spin_k, - const std::vector>& G0, - int n_states) ; - -Matz calculate_scRPA_lambda_matrix( - MeanField& meanfield, - const Matz& H_KS0, - const Matz& vxc0, - const Matz& gxc_matrix, - double mu, - double temperature, - int ispin, - int ikpt, - int n_states); - -Matz construct_F_matrix(const Matz& lambda_matrix,std::vector& F_eigenvalues,int n_states,int iteration); - -std::vector solve_update_F_matrix(MeanField& meanfield,const Matz& F_matrix,int ispin,int ikpt,int n_states); \ No newline at end of file diff --git a/src/qsgw/band_bvk_remap.cpp b/src/qsgw/band_bvk_remap.cpp new file mode 100644 index 00000000..2097749b --- /dev/null +++ b/src/qsgw/band_bvk_remap.cpp @@ -0,0 +1,50 @@ +#include "band_bvk_remap.h" + +#include "../api/compute_helper.h" +#include "../io/global_io.h" + +#include + +namespace librpa_int +{ +namespace qsgw +{ + +AtomPairBvKRemap build_legacy_band_bvk_remap( + const Atoms& atoms, + const PeriodicBoundaryData& pbc, + const int remap_convention) +{ + auto remap = + api::build_band_bvk_remap(atoms, pbc, remap_convention); + if (remap_convention != 0 || pbc.Rlist.empty()) + { + return remap; + } + + const Vector3_Order positive_corner{ + (pbc.period.x - 1) / 2, + (pbc.period.y - 1) / 2, + (pbc.period.z - 1) / 2}; + int legacy_overrides = 0; + for (const auto& atom_coord : atoms.coords_frac) + { + const auto atom = atom_coord.first; + const auto& coord = atom_coord.second; + const bool displaced = + std::abs(coord.x) + std::abs(coord.y) + std::abs(coord.z) > + 1.0e-12; + if (displaced && + remap.erase_mapping({atom, atom}, positive_corner)) + { + ++legacy_overrides; + } + } + global::ofs_myid + << "QSGW legacy band BvK identity overrides: " + << legacy_overrides << "\n"; + return remap; +} + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/band_bvk_remap.h b/src/qsgw/band_bvk_remap.h new file mode 100644 index 00000000..e77d4649 --- /dev/null +++ b/src/qsgw/band_bvk_remap.h @@ -0,0 +1,18 @@ +#pragma once + +#include "../core/atom.h" +#include "../core/geometry.h" +#include "../core/pbc.h" + +namespace librpa_int +{ +namespace qsgw +{ + +AtomPairBvKRemap build_legacy_band_bvk_remap( + const Atoms& atoms, + const PeriodicBoundaryData& pbc, + int remap_convention); + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/band_output.cpp b/src/qsgw/band_output.cpp new file mode 100644 index 00000000..94e1442f --- /dev/null +++ b/src/qsgw/band_output.cpp @@ -0,0 +1,172 @@ +#include "band_output.h" + +#include "../utils/constants.h" + +#include +#include +#include +#include +#include +#include + +namespace librpa_int +{ +namespace qsgw +{ +namespace +{ + +void validate_layout( + const MeanField& live, + const MeanField& reference, + const std::vector>& kpoints, + const int spin, + const double chemical_potential) +{ + if (!live.initialized() || !reference.initialized()) + throw std::invalid_argument( + "QSGW band output mean fields must be initialized"); + if (live.get_n_spins() != reference.get_n_spins() || + live.get_n_kpoints() != reference.get_n_kpoints() || + live.get_n_bands() != reference.get_n_bands() || + live.get_n_spinor() != reference.get_n_spinor()) + throw std::invalid_argument( + "QSGW band output mean-field layouts differ"); + if (spin < 0 || spin >= live.get_n_spins()) + throw std::invalid_argument( + "QSGW band output spin index is out of range"); + if (kpoints.size() != + static_cast(live.get_n_kpoints())) + throw std::invalid_argument( + "QSGW band output k-point count differs"); + if (!std::isfinite(chemical_potential)) + throw std::invalid_argument( + "QSGW band output chemical potential is non-finite"); +} + +const Matz& matrix_at(const SpinKMatrixMap& matrices, + const int spin, + const int kpoint, + const int dimension, + const char* label) +{ + const auto spin_it = matrices.find(spin); + if (spin_it == matrices.end()) + throw std::invalid_argument( + std::string("QSGW band output is missing ") + label + + " spin data"); + const auto kpoint_it = spin_it->second.find(kpoint); + if (kpoint_it == spin_it->second.end() || + kpoint_it->second.nr() != dimension || + kpoint_it->second.nc() != dimension) + throw std::invalid_argument( + std::string("QSGW band output has malformed ") + label + + " k-point data"); + return kpoint_it->second; +} + +double checked_real(const cplxdb value, const char* label) +{ + if (!std::isfinite(value.real()) || !std::isfinite(value.imag())) + throw std::invalid_argument( + std::string("QSGW band output has invalid ") + label + + " diagonal data"); + return value.real(); +} + +void write_kpoint_prefix(std::ostream& output, + const int kpoint, + const Vector3_Order& coordinate) +{ + output << std::setw(5) << kpoint + 1 + << std::setw(15) << std::setprecision(7) << coordinate.x + << std::setw(15) << std::setprecision(7) << coordinate.y + << std::setw(15) << std::setprecision(7) << coordinate.z; +} + +} // namespace + +void write_qsgw_band_spin_tables( + std::ostream& ks_output, + std::ostream& exx_output, + std::ostream& qsgw_output, + const MeanField& live_band, + const MeanField& reference_band, + const std::vector>& kpoints, + const SpinKMatrixMap& reference_hamiltonian, + const SpinKMatrixMap& dft_vxc, + const SpinKMatrixMap& exchange, + const int spin, + const double chemical_potential_ha) +{ + validate_layout( + live_band, reference_band, kpoints, spin, + chemical_potential_ha); + if (!ks_output.good() || !exx_output.good() || !qsgw_output.good()) + throw std::invalid_argument( + "QSGW band output stream is not writable"); + + ks_output << std::fixed; + exx_output << std::fixed; + qsgw_output << std::fixed; + const double occupation_scale = static_cast( + live_band.get_n_kpoints() * live_band.get_n_spins()); + for (int kpoint = 0; kpoint < live_band.get_n_kpoints(); ++kpoint) + { + const Matz& h_ks = matrix_at( + reference_hamiltonian, spin, kpoint, + live_band.get_n_bands(), "KS Hamiltonian"); + const Matz& vxc = matrix_at( + dft_vxc, spin, kpoint, live_band.get_n_bands(), "Vxc"); + const Matz& exx = matrix_at( + exchange, spin, kpoint, live_band.get_n_bands(), "EXX"); + write_kpoint_prefix(ks_output, kpoint, kpoints[kpoint]); + write_kpoint_prefix(exx_output, kpoint, kpoints[kpoint]); + write_kpoint_prefix(qsgw_output, kpoint, kpoints[kpoint]); + + for (int band = 0; band < live_band.get_n_bands(); ++band) + { + const double reference_occupation = + reference_band.get_weight()[spin](kpoint, band) * + occupation_scale; + if (!std::isfinite(reference_occupation) || + reference_occupation < 0.0) + { + throw std::invalid_argument( + "QSGW band output reference occupation is invalid"); + } + const double qsgw_energy = + live_band.get_eigenvals()[spin](kpoint, band); + if (!std::isfinite(qsgw_energy)) + throw std::invalid_argument( + "QSGW band output eigenvalue is non-finite"); + const double ks_energy = checked_real( + h_ks(band, band), "KS Hamiltonian"); + const double exx_energy = checked_real( + h_ks(band, band) - vxc(band, band) + + exx(band, band), + "EXX Hamiltonian"); + + ks_output << std::setw(15) << std::setprecision(5) + << reference_occupation + << std::setw(15) << std::setprecision(5) + << ks_energy * HA2EV; + exx_output << std::setw(15) << std::setprecision(5) + << reference_occupation + << std::setw(15) << std::setprecision(5) + << exx_energy * HA2EV; + qsgw_output << std::setw(15) << std::setprecision(5) + << reference_occupation + << std::setw(15) << std::setprecision(5) + << qsgw_energy * HA2EV; + } + ks_output << '\n'; + exx_output << '\n'; + qsgw_output << '\n'; + } + if (!ks_output.good() || !exx_output.good() || !qsgw_output.good()) + throw std::runtime_error("Failed to write QSGW band output tables"); +} + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/band_output.h b/src/qsgw/band_output.h new file mode 100644 index 00000000..6bb3020a --- /dev/null +++ b/src/qsgw/band_output.h @@ -0,0 +1,32 @@ +#pragma once + +#include "matrix_map.h" + +#include "../core/meanfield.h" +#include "../math/vector3_order.h" + +#include +#include + +namespace librpa_int +{ +namespace qsgw +{ + +// Write one spin channel using the historical QSGW/KS/EXX band-table layout. +// Matrix inputs and MeanField eigenvalues are in Hartree; table energies are eV. +void write_qsgw_band_spin_tables( + std::ostream& ks_output, + std::ostream& exx_output, + std::ostream& qsgw_output, + const MeanField& live_band, + const MeanField& reference_band, + const std::vector>& kpoints, + const SpinKMatrixMap& reference_hamiltonian, + const SpinKMatrixMap& dft_vxc, + const SpinKMatrixMap& exchange, + int spin, + double chemical_potential_ha); + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/convergence.cpp b/src/qsgw/convergence.cpp new file mode 100644 index 00000000..da33bb6f --- /dev/null +++ b/src/qsgw/convergence.cpp @@ -0,0 +1,90 @@ +#include "convergence.h" + +#include +#include +#include + +namespace librpa_int +{ +namespace qsgw +{ + +EigenvalueSnapshot eigenvalue_snapshot(const MeanField& meanfield) +{ + if (!meanfield.initialized()) + { + throw std::invalid_argument( + "QSGW convergence mean field is not initialized"); + } + EigenvalueSnapshot result; + result.reserve(static_cast(meanfield.get_n_spins())); + for (const matrix& eigenvalues : meanfield.get_eigenvals()) + { + matrix copy(eigenvalues.nr, eigenvalues.nc, false); + for (int index = 0; index < eigenvalues.size; ++index) + { + if (!std::isfinite(eigenvalues.c[index])) + { + throw std::invalid_argument( + "QSGW convergence eigenvalue is non-finite"); + } + copy.c[index] = eigenvalues.c[index]; + } + result.push_back(std::move(copy)); + } + return result; +} + +double max_eigenvalue_change( + const MeanField& meanfield, + const EigenvalueSnapshot& previous) +{ + if (!meanfield.initialized() || + previous.size() != + static_cast(meanfield.get_n_spins())) + { + throw std::invalid_argument( + "QSGW convergence snapshot has an incompatible spin layout"); + } + double result = 0.0; + for (int spin = 0; spin < meanfield.get_n_spins(); ++spin) + { + const matrix& current = meanfield.get_eigenvals()[spin]; + const matrix& old = previous[static_cast(spin)]; + if (current.nr != old.nr || current.nc != old.nc) + { + throw std::invalid_argument( + "QSGW convergence snapshot has incompatible dimensions"); + } + for (int index = 0; index < current.size; ++index) + { + if (!std::isfinite(current.c[index]) || + !std::isfinite(old.c[index])) + { + throw std::invalid_argument( + "QSGW convergence eigenvalue is non-finite"); + } + result = std::max( + result, std::abs(current.c[index] - old.c[index])); + } + } + return result; +} + +bool qsgw_iteration_converged(const int iteration, + const int minimum_iterations, + const double maximum_change, + const double tolerance) +{ + if (iteration < 1 || minimum_iterations < 1 || + maximum_change < 0.0 || !std::isfinite(maximum_change) || + !(tolerance > 0.0) || !std::isfinite(tolerance)) + { + throw std::invalid_argument( + "QSGW convergence inputs are invalid"); + } + return iteration >= minimum_iterations && maximum_change < tolerance; +} + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/convergence.h b/src/qsgw/convergence.h new file mode 100644 index 00000000..2ff15c6c --- /dev/null +++ b/src/qsgw/convergence.h @@ -0,0 +1,26 @@ +#pragma once + +#include "../core/meanfield.h" + +#include + +namespace librpa_int +{ +namespace qsgw +{ + +using EigenvalueSnapshot = std::vector; + +EigenvalueSnapshot eigenvalue_snapshot(const MeanField& meanfield); + +double max_eigenvalue_change( + const MeanField& meanfield, + const EigenvalueSnapshot& previous); + +bool qsgw_iteration_converged(int iteration, + int minimum_iterations, + double maximum_change, + double tolerance); + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/correlation_potential.cpp b/src/qsgw/correlation_potential.cpp new file mode 100644 index 00000000..7db81787 --- /dev/null +++ b/src/qsgw/correlation_potential.cpp @@ -0,0 +1,279 @@ +#include "correlation_potential.h" + +#include "../core/analycont.h" + +#include +#include +#include + +namespace librpa_int +{ +namespace qsgw +{ +namespace +{ + +bool finite_complex(const cplxdb value) +{ + return std::isfinite(value.real()) && std::isfinite(value.imag()); +} + +void validate_parameter_count(const int count, + const std::size_t data_size, + const char* label) +{ + if (count < -1 || count == 0 || (count == 1 && data_size > 1)) + { + throw std::invalid_argument( + std::string(label) + + " must be -1 or at least 2 for a multi-point continuation"); + } +} + +void validate_contract( + const MeanField& meanfield, + const std::vector& source_frequencies, + const std::map& sigma, + const int spin, + const int kpoint, + const CorrelationPotentialSettings& settings) +{ + if (!meanfield.initialized() || spin < 0 || + spin >= meanfield.get_n_spins() || kpoint < 0 || + kpoint >= meanfield.get_n_kpoints()) + { + throw std::invalid_argument( + "QSGW correlation potential received an invalid mean-field index"); + } + if (!std::isfinite(meanfield.get_efermi())) + { + throw std::invalid_argument( + "QSGW correlation potential Fermi energy is non-finite"); + } + for (int band = 0; band < meanfield.get_n_bands(); ++band) + { + if (!std::isfinite(meanfield.get_eigenvals()[spin](kpoint, band))) + { + throw std::invalid_argument( + "QSGW correlation potential eigenvalue is non-finite"); + } + } + + if (source_frequencies.size() < 2 || + sigma.size() != source_frequencies.size()) + { + throw std::invalid_argument( + "QSGW correlation potential requires matching multi-point frequency data"); + } + double previous = -1.0; + for (const double frequency : source_frequencies) + { + if (!std::isfinite(frequency) || frequency <= 0.0 || + frequency <= previous) + { + throw std::invalid_argument( + "QSGW imaginary frequencies must be finite, positive, and strictly increasing"); + } + previous = frequency; + const auto sigma_it = sigma.find(frequency); + if (sigma_it == sigma.end()) + { + throw std::invalid_argument( + "QSGW self-energy map is missing a requested frequency"); + } + const Matz& matrix = sigma_it->second; + if (matrix.nr() != meanfield.get_n_bands() || + matrix.nc() != meanfield.get_n_bands()) + { + throw std::invalid_argument( + "QSGW self-energy matrix has an invalid shape"); + } + for (int row = 0; row < matrix.nr(); ++row) + { + for (int column = 0; column < matrix.nc(); ++column) + { + if (!finite_complex(matrix(row, column))) + { + throw std::invalid_argument( + "QSGW self-energy matrix contains non-finite data"); + } + } + } + } + + validate_parameter_count(settings.n_params_anacon, + settings.resample_imagfreqs.empty() + ? source_frequencies.size() + : settings.resample_imagfreqs.size(), + "n_params_anacon"); + if (!settings.resample_imagfreqs.empty()) + { + validate_parameter_count(settings.n_params_anacon_resample, + source_frequencies.size(), + "n_params_anacon_resample"); + double previous_imaginary = -1.0; + for (const cplxdb frequency : settings.resample_imagfreqs) + { + if (!finite_complex(frequency) || frequency.real() != 0.0 || + frequency.imag() <= 0.0 || + frequency.imag() <= previous_imaginary) + { + throw std::invalid_argument( + "QSGW resampling points must be strictly increasing positive imaginary frequencies"); + } + previous_imaginary = frequency.imag(); + } + } + + if (settings.mode != CorrelationPotentialMode::ModeA && + settings.mode != CorrelationPotentialMode::ModeB) + { + throw std::invalid_argument( + "QSGW correlation potential mode is invalid"); + } +} + +cplxdb continue_element( + const std::vector& source_points, + const std::vector& source_data, + const cplxdb target, + const CorrelationPotentialSettings& settings) +{ + cplxdb result; + if (settings.resample_imagfreqs.empty()) + { + result = AnalyContPade(settings.n_params_anacon, + source_points, source_data) + .get(target); + } + else + { + const AnalyContPade source_pade( + settings.n_params_anacon_resample, + source_points, source_data); + std::vector resampled_data; + resampled_data.reserve(settings.resample_imagfreqs.size()); + for (const cplxdb frequency : settings.resample_imagfreqs) + { + const cplxdb value = source_pade.get(frequency); + if (!finite_complex(value)) + { + throw std::runtime_error( + "QSGW first-stage analytic continuation produced non-finite data"); + } + resampled_data.push_back(value); + } + result = AnalyContPade(settings.n_params_anacon, + settings.resample_imagfreqs, + resampled_data) + .get(target); + } + if (!finite_complex(result)) + { + throw std::runtime_error( + "QSGW analytic continuation produced non-finite data"); + } + return result; +} + +Matz evaluate_hermitian_sigma( + const std::vector& source_frequencies, + const std::map& sigma, + const double target_energy, + const CorrelationPotentialSettings& settings) +{ + std::vector source_points; + source_points.reserve(source_frequencies.size()); + for (const double frequency : source_frequencies) + { + source_points.emplace_back(0.0, frequency); + } + + const int dimension = sigma.begin()->second.nr(); + Matz continued(dimension, dimension, MAJOR::ROW); + for (int row = 0; row < dimension; ++row) + { + for (int column = 0; column < dimension; ++column) + { + std::vector source_data; + source_data.reserve(source_frequencies.size()); + for (const double frequency : source_frequencies) + { + source_data.push_back(sigma.at(frequency)(row, column)); + } + continued(row, column) = continue_element( + source_points, source_data, cplxdb(target_energy, 0.0), + settings); + } + } + + Matz hermitian(dimension, dimension, MAJOR::ROW); + for (int row = 0; row < dimension; ++row) + { + for (int column = 0; column < dimension; ++column) + { + hermitian(row, column) = + 0.5 * (continued(row, column) + + std::conj(continued(column, row))); + } + } + return hermitian; +} + +} // namespace + +Matz build_qsgw_correlation_potential( + const MeanField& meanfield, + const std::vector& source_frequencies, + const std::map& sigma_imaginary_axis, + const int spin, + const int kpoint, + const CorrelationPotentialSettings& settings) +{ + validate_contract(meanfield, source_frequencies, sigma_imaginary_axis, + spin, kpoint, settings); + const int dimension = meanfield.get_n_bands(); + const double fermi_energy = meanfield.get_efermi(); + + std::vector sigma_at_state_energy; + sigma_at_state_energy.reserve(static_cast(dimension)); + for (int band = 0; band < dimension; ++band) + { + sigma_at_state_energy.push_back(evaluate_hermitian_sigma( + source_frequencies, sigma_imaginary_axis, + meanfield.get_eigenvals()[spin](kpoint, band) - fermi_energy, + settings)); + } + + Matz result(dimension, dimension, MAJOR::ROW); + if (settings.mode == CorrelationPotentialMode::ModeB) + { + const Matz sigma_at_fermi = evaluate_hermitian_sigma( + source_frequencies, sigma_imaginary_axis, 0.0, settings); + for (int row = 0; row < dimension; ++row) + { + for (int column = 0; column < dimension; ++column) + { + result(row, column) = + row == column + ? sigma_at_state_energy[row](row, row) + : sigma_at_fermi(row, column); + } + } + return result; + } + + for (int row = 0; row < dimension; ++row) + { + for (int column = 0; column < dimension; ++column) + { + result(row, column) = + 0.5 * (sigma_at_state_energy[row](row, column) + + sigma_at_state_energy[column](row, column)); + } + } + return result; +} + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/correlation_potential.h b/src/qsgw/correlation_potential.h new file mode 100644 index 00000000..72340d47 --- /dev/null +++ b/src/qsgw/correlation_potential.h @@ -0,0 +1,37 @@ +#pragma once + +#include "../core/meanfield.h" +#include "../math/matrix_m.h" + +#include +#include + +namespace librpa_int +{ +namespace qsgw +{ + +enum class CorrelationPotentialMode +{ + ModeA, + ModeB, +}; + +struct CorrelationPotentialSettings +{ + CorrelationPotentialMode mode = CorrelationPotentialMode::ModeB; + int n_params_anacon = -1; + int n_params_anacon_resample = -1; + std::vector resample_imagfreqs; +}; + +Matz build_qsgw_correlation_potential( + const MeanField& meanfield, + const std::vector& source_frequencies, + const std::map& sigma_imaginary_axis, + int spin, + int kpoint, + const CorrelationPotentialSettings& settings = {}); + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/distributed_matrix.cpp b/src/qsgw/distributed_matrix.cpp new file mode 100644 index 00000000..06405b3d --- /dev/null +++ b/src/qsgw/distributed_matrix.cpp @@ -0,0 +1,169 @@ +#include "distributed_matrix.h" + +#include "../math/scalapack_connector.h" + +#include +#include +#include +#include + +namespace librpa_int +{ +namespace qsgw +{ + +Matz collect_blacs_matrix_root(const Matz& local, + const ArrayDesc& distributed_descriptor) +{ + if (!distributed_descriptor.is_initialized()) + { + throw std::invalid_argument( + "QSGW distributed matrix descriptor is not initialized"); + } + if (distributed_descriptor.m() <= 0 || + distributed_descriptor.n() <= 0) + { + throw std::invalid_argument( + "QSGW distributed matrix must have positive global dimensions"); + } + if (local.major() != MAJOR::COL) + { + throw std::invalid_argument( + "QSGW distributed matrix must use column-major local storage"); + } + if (local.nr() != distributed_descriptor.m_loc() || + local.nc() != distributed_descriptor.n_loc()) + { + throw std::invalid_argument( + "QSGW local matrix shape does not match its BLACS descriptor"); + } + + ArrayDesc root_descriptor(distributed_descriptor.ictxt()); + root_descriptor.init(distributed_descriptor.m(), + distributed_descriptor.n(), + distributed_descriptor.m(), + distributed_descriptor.n(), + distributed_descriptor.irsrc(), + distributed_descriptor.icsrc()); + + Matz source_dummy(1, 1, MAJOR::COL); + const cplxdb* source = local.nr() > 0 && local.nc() > 0 + ? local.ptr() + : source_dummy.ptr(); + Matz transfer_buffer = root_descriptor.is_src() + ? Matz(distributed_descriptor.m(), + distributed_descriptor.n(), MAJOR::COL) + : Matz(1, 1, MAJOR::COL); + ScalapackConnector::pgemr2d_f( + distributed_descriptor.m(), distributed_descriptor.n(), + source, 1, 1, distributed_descriptor.desc, + transfer_buffer.ptr(), 1, 1, root_descriptor.desc, + distributed_descriptor.ictxt()); + + if (root_descriptor.is_src()) + { + return transfer_buffer; + } + return Matz(0, 0, MAJOR::COL); +} + +void broadcast_spin_k_matrix_map(SpinKMatrixMap& values, + const int root, + const MpiCommHandler& communicator) +{ + communicator.check_initialized(); + if (root < 0 || root >= communicator.nprocs) + { + throw std::invalid_argument("QSGW matrix broadcast root is invalid"); + } + + int valid = 1; + int block_count = 0; + std::vector metadata; + if (communicator.myid == root) + { + if (values.empty()) valid = 0; + for (const auto& [spin, by_kpoint] : values) + { + if (by_kpoint.empty()) valid = 0; + for (const auto& [kpoint, matrix] : by_kpoint) + { + if (matrix.nr() <= 0 || matrix.nc() <= 0 || + matrix.nr() > std::numeric_limits::max() / + matrix.nc()) + { + valid = 0; + continue; + } + for (int index = 0; index < matrix.nr() * matrix.nc(); ++index) + { + const cplxdb value = matrix.ptr()[index]; + if (!std::isfinite(value.real()) || + !std::isfinite(value.imag())) + { + valid = 0; + } + } + metadata.insert(metadata.end(), { + spin, kpoint, matrix.nr(), matrix.nc(), + static_cast(matrix.major())}); + ++block_count; + } + } + if (block_count <= 0) valid = 0; + } + + communicator.bcast(&valid, 1, root); + if (!valid) + { + throw std::invalid_argument( + "QSGW root matrix map is empty, malformed, or non-finite"); + } + communicator.bcast(&block_count, 1, root); + if (communicator.myid != root) + { + metadata.resize(static_cast(5 * block_count)); + } + communicator.bcast(metadata.data(), 5 * block_count, root); + + SpinKMatrixMap received; + for (int block = 0; block < block_count; ++block) + { + const int offset = 5 * block; + const int spin = metadata[offset]; + const int kpoint = metadata[offset + 1]; + const int rows = metadata[offset + 2]; + const int columns = metadata[offset + 3]; + const int major_raw = metadata[offset + 4]; + if (rows <= 0 || columns <= 0 || + (major_raw != static_cast(MAJOR::ROW) && + major_raw != static_cast(MAJOR::COL))) + { + throw std::runtime_error( + "QSGW matrix broadcast metadata is invalid"); + } + Matz matrix(rows, columns, static_cast(major_raw)); + if (communicator.myid == root) + { + const auto spin_it = values.find(spin); + if (spin_it == values.end()) + throw std::runtime_error( + "QSGW root matrix map changed during broadcast"); + const auto kpoint_it = spin_it->second.find(kpoint); + if (kpoint_it == spin_it->second.end()) + throw std::runtime_error( + "QSGW root matrix map changed during broadcast"); + matrix = kpoint_it->second.copy(); + } + communicator.bcast(matrix.ptr(), rows * columns, root); + if (!received[spin].emplace(kpoint, std::move(matrix)).second) + { + throw std::runtime_error( + "QSGW matrix broadcast contains duplicate keys"); + } + } + values = std::move(received); +} + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/distributed_matrix.h b/src/qsgw/distributed_matrix.h new file mode 100644 index 00000000..fc07fb9d --- /dev/null +++ b/src/qsgw/distributed_matrix.h @@ -0,0 +1,22 @@ +#pragma once + +#include "matrix_map.h" + +#include "../math/matrix_m.h" +#include "../mpi/base_blacs.h" +#include "../mpi/base_mpi.h" + +namespace librpa_int +{ +namespace qsgw +{ + +Matz collect_blacs_matrix_root(const Matz& local, + const ArrayDesc& distributed_descriptor); + +void broadcast_spin_k_matrix_map(SpinKMatrixMap& values, + int root, + const MpiCommHandler& communicator); + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/effective_hamiltonian.cpp b/src/qsgw/effective_hamiltonian.cpp new file mode 100644 index 00000000..8987b289 --- /dev/null +++ b/src/qsgw/effective_hamiltonian.cpp @@ -0,0 +1,172 @@ +#include "effective_hamiltonian.h" + +#include +#include +#include +#include + +namespace librpa_int +{ +namespace qsgw +{ +namespace +{ + +void validate_map(const SpinKMatrixMap& values, + const SpinKMatrixMap* layout, + const std::string& label) +{ + if (values.empty()) + { + throw std::invalid_argument( + "QSGW " + label + " Hamiltonian map is empty"); + } + if (layout != nullptr && values.size() != layout->size()) + { + throw std::invalid_argument( + "QSGW " + label + " Hamiltonian spin layout differs"); + } + + for (const auto& [spin, by_kpoint] : values) + { + if (by_kpoint.empty()) + { + throw std::invalid_argument( + "QSGW " + label + " Hamiltonian spin map is empty"); + } + const auto layout_spin = layout == nullptr + ? SpinKMatrixMap::const_iterator{} + : layout->find(spin); + if (layout != nullptr && + (layout_spin == layout->end() || + by_kpoint.size() != layout_spin->second.size())) + { + throw std::invalid_argument( + "QSGW " + label + " Hamiltonian k-point layout differs"); + } + + for (const auto& [kpoint, matrix] : by_kpoint) + { + if (matrix.nr() <= 0 || matrix.nr() != matrix.nc()) + { + throw std::invalid_argument( + "QSGW " + label + + " Hamiltonian matrix must be non-empty and square"); + } + if (layout != nullptr) + { + const auto layout_kpoint = + layout_spin->second.find(kpoint); + if (layout_kpoint == layout_spin->second.end() || + layout_kpoint->second.nr() != matrix.nr() || + layout_kpoint->second.nc() != matrix.nc()) + { + throw std::invalid_argument( + "QSGW " + label + + " Hamiltonian keys or dimensions differ"); + } + } + + for (int row = 0; row < matrix.nr(); ++row) + { + for (int column = 0; column < matrix.nc(); ++column) + { + const cplxdb value = matrix(row, column); + if (!std::isfinite(value.real()) || + !std::isfinite(value.imag())) + { + throw std::invalid_argument( + "QSGW " + label + + " Hamiltonian contains non-finite data"); + } + } + } + } + } +} + +} // namespace + +SpinKMatrixMap build_reference_hamiltonian( + const MeanField& reference) +{ + if (!reference.initialized()) + { + throw std::invalid_argument( + "QSGW reference mean field is not initialized"); + } + SpinKMatrixMap result; + for (int spin = 0; spin < reference.get_n_spins(); ++spin) + { + for (int kpoint = 0; kpoint < reference.get_n_kpoints(); ++kpoint) + { + Matz hamiltonian(reference.get_n_bands(), + reference.get_n_bands(), MAJOR::ROW); + for (int band = 0; band < reference.get_n_bands(); ++band) + { + const double eigenvalue = + reference.get_eigenvals()[spin](kpoint, band); + if (!std::isfinite(eigenvalue)) + { + throw std::invalid_argument( + "QSGW reference eigenvalue is non-finite"); + } + hamiltonian(band, band) = eigenvalue; + } + result[spin][kpoint] = std::move(hamiltonian); + } + } + return result; +} + +SpinKMatrixMap assemble_effective_hamiltonian( + const SpinKMatrixMap& reference_hamiltonian, + const SpinKMatrixMap& dft_vxc, + const SpinKMatrixMap& exchange, + const SpinKMatrixMap& correlation) +{ + validate_map(reference_hamiltonian, nullptr, "reference"); + validate_map(dft_vxc, &reference_hamiltonian, "DFT Vxc"); + validate_map(exchange, &reference_hamiltonian, "exchange"); + validate_map(correlation, &reference_hamiltonian, "correlation"); + SpinKMatrixMap result; + for (const auto& [spin, by_kpoint] : reference_hamiltonian) + { + for (const auto& [kpoint, reference] : by_kpoint) + { + const Matz& vxc = dft_vxc.at(spin).at(kpoint); + const Matz& exx = exchange.at(spin).at(kpoint); + const Matz& vc = correlation.at(spin).at(kpoint); + Matz assembled(reference.nr(), reference.nc(), + reference.major()); + for (int row = 0; row < reference.nr(); ++row) + { + for (int column = 0; column < reference.nc(); ++column) + { + assembled(row, column) = + reference(row, column) - vxc(row, column) + + exx(row, column) + vc(row, column); + } + } + for (int row = 0; row < assembled.nr(); ++row) + { + // Legacy Scheme A diagonalizes with eigsh(UPLO='U'). Keep + // that upper triangle authoritative while materializing the + // same operator as an explicitly Hermitian matrix. + assembled(row, row) = + cplxdb(assembled(row, row).real(), 0.0); + for (int column = row + 1; + column < assembled.nc(); ++column) + { + assembled(column, row) = + std::conj(assembled(row, column)); + } + } + result[spin][kpoint] = std::move(assembled); + } + } + return result; +} + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/effective_hamiltonian.h b/src/qsgw/effective_hamiltonian.h new file mode 100644 index 00000000..3cdfa38c --- /dev/null +++ b/src/qsgw/effective_hamiltonian.h @@ -0,0 +1,22 @@ +#pragma once + +#include "matrix_map.h" + +#include "../core/meanfield.h" + +namespace librpa_int +{ +namespace qsgw +{ + +SpinKMatrixMap build_reference_hamiltonian( + const MeanField& reference); + +SpinKMatrixMap assemble_effective_hamiltonian( + const SpinKMatrixMap& reference_hamiltonian, + const SpinKMatrixMap& dft_vxc, + const SpinKMatrixMap& exchange, + const SpinKMatrixMap& correlation); + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/fermi_energy_occupation.cpp b/src/qsgw/fermi_energy_occupation.cpp deleted file mode 100644 index 77927a7a..00000000 --- a/src/qsgw/fermi_energy_occupation.cpp +++ /dev/null @@ -1,278 +0,0 @@ -#include "fermi_energy_occupation.h" - -#include -#include -#include "constants.h" - -// 费米分布 -double fermi_dirac(double energy, double mu, double temperature) -{ - // const double K_B = 3.16681e-6; // Hartree/K - // return 1.0 / (1.0 + exp((energy - mu) / (K_B * temperature))); - if (energy <= mu) { - return 1.0; - } else { - return 0.0; - } -} - -// 计算给定化学势下的总占据态 -double calculate_total_occupation(const MeanField &mf, double mu, double temperature) { - double total_occupation = 0.0; - - for (int ispin = 0; ispin < mf.get_n_spins(); ++ispin) { - for (int ikpt = 0; ikpt < mf.get_n_kpoints(); ++ikpt) { - for (int ib = 0; ib < mf.get_n_bands(); ++ib) { - double energy = mf.get_eigenvals()[ispin](ikpt, ib); - double occupation = fermi_dirac(energy, mu, temperature) * 2.0 / (mf.get_n_kpoints() * mf.get_n_spins()); - total_occupation += occupation; - } - } - } - - return total_occupation; -} -//calculate_local_occupation_for each ispin-ikpoint_0-temperature -static double calculate_local_occupation(const MeanField &mf, double mu, double temperature, int ispin, int ikpt) { - double local_occupation = 0.0; - for (int ib = 0; ib < mf.get_n_bands(); ++ib) { - double energy = mf.get_eigenvals()[ispin](ikpt, ib); - double occupation = fermi_dirac(energy, mu, temperature) * 2.0 / mf.get_n_spins(); - local_occupation += occupation; - } - return local_occupation; -} - -// //1 -// double calculate_fermi_energy(const MeanField &mf, double temperature, double total_electrons) { -// double tolerance = 1e-4; -// double total_occupation = 0.0; -// double mu = 0.0; -// double gap = 0.0; -// double vbm = -10000.0; // 比mu小的最大值 -// double cbm = 10000.0; // 比mu大的最小值 - -// for (int ispin = 0; ispin < mf.get_n_spins(); ++ispin) { -// for (int ikpt = 0; ikpt < mf.get_n_kpoints(); ++ikpt) { - - -// for (int ib = 0; ib < mf.get_n_bands(); ++ib) { -// double energy = mf.get_eigenvals()[ispin](ikpt, ib); -// mu = energy; -// total_occupation = calculate_total_occupation(mf, mu, temperature); -// // 如果能量比mu小,更新vbm -// if (total_occupation < total_electrons + tolerance) { -// if (energy > vbm) { -// vbm = energy; -// std::cout << "ikpt1: " << ikpt << std::endl; -// std::cout << "ib1: " << ib << std::endl; -// } -// } -// // 如果能量比mu大,更新cbm -// else{ -// if (energy < cbm) { -// cbm = energy; -// std::cout << "ikpt2: " << ikpt << std::endl; -// std::cout << "ib2: " << ib << std::endl; -// } -// } -// } -// } -// } -// // 最终费米能级取 vbm 和 cbm 的中间值 -// mu = (vbm + cbm) * 0.5; -// gap = cbm - vbm ; -// std::cout << "Final VBM: " << vbm * HA2EV<< ", CBM: " << cbm * HA2EV << ", Final Fermi level: " << mu * HA2EV << std::endl; -// std::cout << "Hamiltonian_gap: " << gap * HA2EV << " eV, "<< std::endl; -// return mu; -// } - -//calculate_semiconductor_gap -double calculate_fermi_energy(const MeanField &mf, double temperature, double total_electrons) { - double tolerance = 1e-5; - double mu = 0.0; - double gap = 0.0; - double vbm = -10000.0; // 比mu小的最大值 - double cbm = 10000.0; // 比mu大的最小值 - - for (int ispin = 0; ispin < mf.get_n_spins(); ++ispin) { - for (int ikpt = 0; ikpt < mf.get_n_kpoints(); ++ikpt) { - double local_vbm = -10000.0; - double local_cbm = 10000.0; - double local_occupation = 0.0; - for (int ib = 0; ib < mf.get_n_bands(); ++ib) { - double local_mu = mf.get_eigenvals()[ispin](ikpt, ib); - local_occupation = calculate_local_occupation(mf, local_mu, temperature, ispin, ikpt); - - // update vbm,cbm; - if (local_occupation <= total_electrons + tolerance ) { - local_vbm = local_mu; - } - if (local_occupation > total_electrons + tolerance ) { - if (local_mu < local_cbm) { - local_cbm = local_mu; - } - } - } - if (local_vbm > vbm) { - vbm = local_vbm; - } - if (local_cbm < cbm){ - cbm = local_cbm; - } - } - } - mu = (vbm + cbm) * 0.5; - gap = cbm - vbm ; - std::cout << "Final VBM: " << vbm * HA2EV<< ", CBM: " << cbm * HA2EV << ", Final Fermi level: " << mu * HA2EV << std::endl; - std::cout << "Hamiltonian_gap: " << gap * HA2EV << " eV, "<< std::endl; - return mu; -} - - -//calculate_local_occupation_for each ispin-ikpoint_0-temperature -static double calculate_eqp_local_occupation(const MeanField &mf, std::map>> e_qp_all, double mu, double temperature, int ispin, int ikpt) { - double local_occupation = 0.0; - for (int ib = 0; ib < mf.get_n_bands(); ++ib) { - double energy = e_qp_all[ispin][ikpt][ib]; - double occupation = fermi_dirac(energy, mu, temperature) * 2.0 / mf.get_n_spins(); - local_occupation += occupation; - } - return local_occupation; -} - -static double calculate_eqp_total_occupation(const MeanField &mf, std::map>> e_qp_all, double mu, double temperature) { - double total_occupation = 0.0; - - for (int ispin = 0; ispin < mf.get_n_spins(); ++ispin) { - for (int ikpt = 0; ikpt < mf.get_n_kpoints(); ++ikpt) { - for (int ib = 0; ib < mf.get_n_bands(); ++ib) { - double energy = e_qp_all[ispin][ikpt][ib]; - double occupation = fermi_dirac(energy, mu, temperature) * 2.0 / (mf.get_n_kpoints() * mf.get_n_spins()); - total_occupation += occupation; - } - } - } - - return total_occupation; -} -// //2 -// double calculate_eqp_fermi_energy(const MeanField &mf, -// std::map>> e_qp_all, -// double temperature, -// double total_electrons) { -// double tolerance = 1e-4; -// double total_occupation = 0.0; -// double mu = 0.0; -// double gap = 0.0; -// double vbm = -10000.0; // 比mu小的最大值 -// double cbm = 10000.0; // 比mu大的最小值 - -// for (int ispin = 0; ispin < mf.get_n_spins(); ++ispin) { -// for (int ikpt = 0; ikpt < mf.get_n_kpoints(); ++ikpt) { - - -// for (int ib = 0; ib < mf.get_n_bands(); ++ib) { -// double energy = e_qp_all[ispin][ikpt][ib]; -// mu = energy; -// total_occupation = calculate_eqp_total_occupation(mf, e_qp_all, mu, temperature); -// // 如果能量比mu小,更新vbm -// if (total_occupation < total_electrons + tolerance) { -// if (energy > vbm) { -// vbm = energy; -// std::cout << "ikpt3: " << ikpt << std::endl; -// std::cout << "ib3: " << ib << std::endl; -// } -// } -// // 如果能量比mu大,更新cbm -// else{ -// if (energy < cbm) { -// cbm = energy; -// std::cout << "ikpt4: " << ikpt << std::endl; -// std::cout << "ib4: " << ib << std::endl; -// } -// } -// } -// } -// } - -// // 最终费米能级取 vbm 和 cbm 的中间值 -// mu = (vbm + cbm) * 0.5; -// gap = cbm - vbm ; -// std::cout << "Final eqp_VBM: " << vbm* HA2EV << ", eqp_CBM: " << cbm* HA2EV << ", Final eqp_Fermi level: " << mu * HA2EV<< std::endl; -// std::cout << "eqp_gap: " << gap * HA2EV << " eV, "<< std::endl; -// return gap; -// } -//22 -double calculate_eqp_fermi_energy(const MeanField &mf, - std::map>> e_qp_all, - double temperature, - double total_electrons) { - double tolerance = 1e-5; - double mu = 0.0; - double gap = 0.0; - double vbm = -10000.0; // 比mu小的最大值 - double cbm = 10000.0; // 比mu大的最小值 - - for (int ispin = 0; ispin < mf.get_n_spins(); ++ispin) { - for (int ikpt = 0; ikpt < mf.get_n_kpoints(); ++ikpt) { - double local_vbm = -10000.0; - double local_cbm = 10000.0; - double local_occupation = 0.0; - for (int ib = 0; ib < mf.get_n_bands(); ++ib) { - double local_mu = e_qp_all[ispin][ikpt][ib]; - local_occupation = calculate_eqp_local_occupation(mf,e_qp_all ,local_mu, temperature, ispin, ikpt); - std::cout << "local_occupation: " << local_occupation << std::endl; - // update vbm,cbm; - if (local_occupation <= total_electrons + tolerance ) { - local_vbm = local_mu; - } - if (local_occupation > total_electrons + tolerance) { - if (local_mu < local_cbm) { - local_cbm = local_mu; - } - } - } - if (local_vbm > vbm) { - vbm = local_vbm; - } - if (local_cbm < cbm){ - cbm = local_cbm; - } - } - } - - // 最终费米能级取 vbm 和 cbm 的中间值 - mu = (vbm + cbm) * 0.5; - gap = cbm - vbm ; - std::cout << "Final eqp_VBM: " << vbm* HA2EV << ", eqp_CBM: " << cbm* HA2EV << ", Final eqp_Fermi level: " << mu * HA2EV<< std::endl; - std::cout << "eqp_gap: " << gap * HA2EV << " eV, "<< std::endl; - return gap; -} - - - - - -void update_fermi_energy_and_occupations(MeanField &mf, const double temperature, const double efermi) -{ - double total_electrons1 = 0.0; - // 更新占据数 - for (int ispin = 0; ispin < mf.get_n_spins(); ++ispin) - { - for (int ikpt = 0; ikpt < mf.get_n_kpoints(); ++ikpt) - { - for (int ib = 0; ib < mf.get_n_bands(); ++ib) - { - const double energy = mf.get_eigenvals()[ispin](ikpt, ib); - mf.get_weight()[ispin](ikpt, ib) = fermi_dirac(energy, efermi, temperature) * 2.0 / (mf.get_n_kpoints() * mf.get_n_spins()); - total_electrons1 += (mf.get_weight()[ispin](ikpt, ib)*mf.get_n_kpoints()); // 计算总占据数 - } - } - } - total_electrons1 = total_electrons1 / mf.get_n_kpoints(); - // 输出 total_electrons - std::cout << "Total electrons: " << total_electrons1 << std::endl; - std::cout << "efermi: " << efermi << std::endl; - mf.get_efermi() = efermi; -} diff --git a/src/qsgw/fermi_energy_occupation.h b/src/qsgw/fermi_energy_occupation.h deleted file mode 100644 index fabb9d3d..00000000 --- a/src/qsgw/fermi_energy_occupation.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once -#include "meanfield.h" - -double calculate_total_occupation(const MeanField &mf, double mu, double temperature); -double calculate_fermi_energy(const MeanField &mf, double temperature, double total_electrons); -double calculate_eqp_fermi_energy(const MeanField &mf, - std::map>> e_qp_all, - double temperature, - double total_electrons) ; -double fermi_dirac(double energy, double mu, double temperature); -void update_fermi_energy_and_occupations(MeanField &meanfield, const double temperature, const double efermi); diff --git a/src/qsgw/fixed_basis.cpp b/src/qsgw/fixed_basis.cpp new file mode 100644 index 00000000..8b44ce1e --- /dev/null +++ b/src/qsgw/fixed_basis.cpp @@ -0,0 +1,848 @@ +#include "fixed_basis.h" + +#include "../math/utils_matrix_mpi.h" + +#include +#include +#include +#include +#include +#include + +namespace librpa_int +{ +namespace qsgw +{ +namespace +{ + +constexpr double hermitian_tolerance = 1.0e-10; + +void require_same_meanfield_shape(const MeanField& live, + const MeanField& reference) +{ + if (live.get_n_spins() != reference.get_n_spins() || + live.get_n_kpoints() != reference.get_n_kpoints() || + live.get_n_bands() != reference.get_n_bands() || + live.get_n_aos() != reference.get_n_aos() || + live.get_n_spinor() != reference.get_n_spinor()) + { + throw std::invalid_argument( + "QSGW live and reference mean fields have different shapes"); + } +} + +void require_same_wfc_distribution(const MeanField& live, + const MeanField& reference) +{ + const auto& live_wfc = live.get_eigenvectors(); + const auto& reference_wfc = reference.get_eigenvectors(); + if (live_wfc.size() != reference_wfc.size()) + { + throw std::invalid_argument( + "QSGW live and reference WFC distributions differ"); + } + for (const auto& [spin, reference_spin] : reference_wfc) + { + const auto live_spin_it = live_wfc.find(spin); + if (live_spin_it == live_wfc.end() || + live_spin_it->second.size() != reference_spin.size()) + { + throw std::invalid_argument( + "QSGW live and reference WFC distributions differ"); + } + for (const auto& [spinor, reference_spinor] : reference_spin) + { + const auto live_spinor_it = live_spin_it->second.find(spinor); + if (live_spinor_it == live_spin_it->second.end() || + live_spinor_it->second.size() != reference_spinor.size()) + { + throw std::invalid_argument( + "QSGW live and reference WFC distributions differ"); + } + for (const auto& [kpoint, reference_block] : reference_spinor) + { + const auto live_block_it = + live_spinor_it->second.find(kpoint); + if (live_block_it == live_spinor_it->second.end() || + live_block_it->second.nr != reference_block.nr || + live_block_it->second.nc != reference_block.nc) + { + throw std::invalid_argument( + "QSGW live and reference WFC distributions differ"); + } + } + } + } +} + +void require_valid_wfc(const MeanField& meanfield, const char* label) +{ + for (int spin = 0; spin < meanfield.get_n_spins(); ++spin) + { + for (int spinor = 0; spinor < meanfield.get_n_spinor(); ++spinor) + { + for (int kpoint = 0; kpoint < meanfield.get_n_kpoints(); ++kpoint) + { + const ComplexMatrix* block = + meanfield.find_wfc(spin, spinor, kpoint); + if (block == nullptr || + block->nr != meanfield.get_n_bands() || + block->nc != meanfield.get_n_aos()) + { + throw std::invalid_argument( + std::string("QSGW ") + label + + " wavefunction map is incomplete or has an invalid shape"); + } + for (int index = 0; index < block->size; ++index) + { + if (!std::isfinite(block->c[index].real()) || + !std::isfinite(block->c[index].imag())) + { + throw std::invalid_argument( + std::string("QSGW ") + label + + " wavefunction contains non-finite data"); + } + } + } + } + } +} + +void require_valid_hamiltonian(const SpinKMatrixMap& hamiltonian, + const MeanField& reference) +{ + if (static_cast(hamiltonian.size()) != reference.get_n_spins()) + { + throw std::invalid_argument("QSGW Hamiltonian spin map is incomplete"); + } + const int dimension = reference.get_n_bands(); + for (int spin = 0; spin < reference.get_n_spins(); ++spin) + { + const auto spin_it = hamiltonian.find(spin); + if (spin_it == hamiltonian.end() || + static_cast(spin_it->second.size()) != + reference.get_n_kpoints()) + { + throw std::invalid_argument("QSGW Hamiltonian k-point map is incomplete"); + } + for (int kpoint = 0; kpoint < reference.get_n_kpoints(); ++kpoint) + { + const auto kpoint_it = spin_it->second.find(kpoint); + if (kpoint_it == spin_it->second.end()) + { + throw std::invalid_argument( + "QSGW Hamiltonian k-point map is incomplete"); + } + const Matz& matrix = kpoint_it->second; + if (matrix.nr() != dimension || matrix.nc() != dimension) + { + throw std::invalid_argument( + "QSGW Hamiltonian matrix has an invalid shape"); + } + for (int row = 0; row < dimension; ++row) + { + for (int column = 0; column < dimension; ++column) + { + const cplxdb value = matrix(row, column); + if (!std::isfinite(value.real()) || + !std::isfinite(value.imag())) + { + throw std::invalid_argument( + "QSGW Hamiltonian contains non-finite data"); + } + if (std::abs(value - std::conj(matrix(column, row))) > + hermitian_tolerance) + { + throw std::invalid_argument( + "QSGW Hamiltonian is not Hermitian"); + } + } + } + } + } +} + +void require_valid_velocity(const VelocityMatrix& velocity, + const MeanField& reference, + const char* label) +{ + if (static_cast(velocity.size()) != reference.get_n_spins()) + { + throw std::invalid_argument( + std::string("QSGW ") + label + " velocity spin map is incomplete"); + } + const int dimension = reference.get_n_bands(); + for (int spin = 0; spin < reference.get_n_spins(); ++spin) + { + if (static_cast(velocity[spin].size()) != + reference.get_n_kpoints()) + { + throw std::invalid_argument( + std::string("QSGW ") + label + + " velocity k-point map is incomplete"); + } + for (int kpoint = 0; kpoint < reference.get_n_kpoints(); ++kpoint) + { + if (velocity[spin][kpoint].size() != 3) + { + throw std::invalid_argument( + std::string("QSGW ") + label + + " velocity must contain three Cartesian components"); + } + for (const ComplexMatrix& component : velocity[spin][kpoint]) + { + if (component.nr != dimension || component.nc != dimension) + { + throw std::invalid_argument( + std::string("QSGW ") + label + + " velocity matrix has an invalid shape"); + } + for (int index = 0; index < component.size; ++index) + { + if (!std::isfinite(component.c[index].real()) || + !std::isfinite(component.c[index].imag())) + { + throw std::invalid_argument( + std::string("QSGW ") + label + + " velocity contains non-finite data"); + } + } + } + } + } +} + +Matz collect_reference_wfc_rows(const MeanField& reference, + const int spin, + const int kpoint, + const MAJOR major) +{ + const int dimension = reference.get_n_bands(); + const int spinor_count = reference.get_n_spinor(); + const int ao_count = reference.get_n_aos(); + Matz rows(dimension, ao_count * spinor_count, major); + for (int band = 0; band < dimension; ++band) + { + for (int spinor = 0; spinor < spinor_count; ++spinor) + { + const ComplexMatrix& block = + reference.get_eigenvectors().at(spin).at(spinor).at(kpoint); + for (int ao = 0; ao < ao_count; ++ao) + { + rows(band, ao * spinor_count + spinor) = block(band, ao); + } + } + } + return rows; +} + +void store_wfc_rows(MeanField& target, + const int spin, + const int kpoint, + const Matz& rows) +{ + for (int band = 0; band < target.get_n_bands(); ++band) + { + for (int spinor = 0; spinor < target.get_n_spinor(); ++spinor) + { + ComplexMatrix& block = + target.get_eigenvectors().at(spin).at(spinor).at(kpoint); + for (int ao = 0; ao < target.get_n_aos(); ++ao) + { + block(band, ao) = + rows(band, ao * target.get_n_spinor() + spinor); + } + } + } +} + +ComplexMatrix to_complex_matrix(const Matz& input) +{ + ComplexMatrix output(input.nr(), input.nc()); + for (int row = 0; row < input.nr(); ++row) + { + for (int column = 0; column < input.nc(); ++column) + { + output(row, column) = input(row, column); + } + } + return output; +} + +double frobenius_norm(const Matz& matrix) +{ + double norm_squared = 0.0; + for (int row = 0; row < matrix.nr(); ++row) + { + for (int column = 0; column < matrix.nc(); ++column) + { + norm_squared += std::norm(matrix(row, column)); + } + } + return std::sqrt(norm_squared); +} + +void require_finite_matrix(const Matz& matrix, const char* label) +{ + for (int row = 0; row < matrix.nr(); ++row) + { + for (int column = 0; column < matrix.nc(); ++column) + { + const cplxdb value = matrix(row, column); + if (!std::isfinite(value.real()) || + !std::isfinite(value.imag())) + { + throw std::invalid_argument( + std::string("QSGW ") + label + + " contains non-finite data"); + } + } + } +} + +Matz invert_complete_wfc_basis( + const Matz& matrix, + const double relative_tolerance, + VelocityBasisAlignmentResult& diagnostics) +{ + if (matrix.nr() != matrix.nc()) + { + throw std::invalid_argument( + "QSGW velocity alignment requires a complete square WFC basis"); + } + const int dimension = matrix.nr(); + Matz result = matrix.copy(); + std::vector pivots(static_cast(dimension)); + std::vector work( + static_cast(std::max(1, dimension))); + int info = 0; + if (result.is_row_major()) + { + LapackConnector::getrf( + dimension, dimension, result.ptr(), dimension, + pivots.data(), info); + } + else + { + LapackConnector::getrf_f( + dimension, dimension, result.ptr(), dimension, + pivots.data(), info); + } + if (info != 0) + { + throw std::invalid_argument( + "QSGW velocity reference WFC basis is singular"); + } + if (result.is_row_major()) + { + LapackConnector::getri( + dimension, result.ptr(), dimension, pivots.data(), + work.data(), static_cast(work.size()), info); + } + else + { + LapackConnector::getri_f( + dimension, result.ptr(), dimension, pivots.data(), + work.data(), static_cast(work.size()), info); + } + if (info != 0) + { + throw std::invalid_argument( + "QSGW velocity reference WFC basis inversion failed"); + } + require_finite_matrix(result, "velocity reference WFC inverse"); + + const Matz product = matrix * result; + double residual_squared = 0.0; + for (int row = 0; row < dimension; ++row) + { + for (int column = 0; column < dimension; ++column) + { + const cplxdb expected = row == column ? 1.0 : 0.0; + residual_squared += + std::norm(product(row, column) - expected); + } + } + const double inverse_residual = std::sqrt( + residual_squared / std::max(1, dimension)); + const double condition_estimate = + frobenius_norm(matrix) * frobenius_norm(result); + diagnostics.maximum_basis_inverse_residual = std::max( + diagnostics.maximum_basis_inverse_residual, + inverse_residual); + diagnostics.maximum_basis_condition_estimate = std::max( + diagnostics.maximum_basis_condition_estimate, + condition_estimate); + if (!std::isfinite(inverse_residual) || + inverse_residual > relative_tolerance || + !std::isfinite(condition_estimate) || + condition_estimate > 1.0 / relative_tolerance) + { + throw std::invalid_argument( + "QSGW velocity reference WFC basis is ill-conditioned"); + } + return result; +} + +void diagonalize_hermitian(const Matz& matrix, + std::vector& eigenvalues, + Matz& unitary) +{ + const int dimension = matrix.nr(); + const int block_size = LapackConnector::ilaenv( + 1, "zheev", "VU", dimension, -1, -1, -1); + const int work_size = std::max(1, dimension * (block_size + 1)); + const int real_work_size = std::max(1, 3 * dimension - 2); + + unitary = matrix.copy(); + eigenvalues.assign(dimension, 0.0); + std::vector work(work_size); + std::vector real_work(real_work_size); + int info = 0; + if (unitary.is_row_major()) + { + LapackConnector::heev( + 'V', 'U', dimension, unitary.ptr(), dimension, + eigenvalues.data(), work.data(), work_size, + real_work.data(), info); + } + else + { + LapackConnector::heev_f( + 'V', 'U', dimension, unitary.ptr(), dimension, + eigenvalues.data(), work.data(), work_size, + real_work.data(), info); + } + if (info != 0) + { + throw std::runtime_error( + "QSGW fixed-basis Hamiltonian diagonalization failed"); + } +} + +double relative_reconstruction_residual(const Matz& source, + const Matz& transform, + const Matz& reference) +{ + const Matz reconstructed = transform * reference; + double residual_squared = 0.0; + double source_norm_squared = 0.0; + double reconstructed_norm_squared = 0.0; + for (int row = 0; row < source.nr(); ++row) + { + for (int column = 0; column < source.nc(); ++column) + { + residual_squared += std::norm( + source(row, column) - reconstructed(row, column)); + source_norm_squared += std::norm(source(row, column)); + reconstructed_norm_squared += + std::norm(reconstructed(row, column)); + } + } + return std::sqrt( + residual_squared / + std::max(source_norm_squared, reconstructed_norm_squared)); +} + +double maximum_unitarity_residual(const Matz& transform) +{ + const Matz product = transform * transpose(transform, true); + double result = 0.0; + for (int row = 0; row < transform.nr(); ++row) + { + for (int column = 0; column < transform.nc(); ++column) + { + const cplxdb expected = row == column ? 1.0 : 0.0; + result = std::max( + result, std::abs(product(row, column) - expected)); + } + } + return result; +} + +Matz project_to_nearest_unitary(const Matz& transform) +{ + const int dimension = transform.nr(); + const Matz gram = transpose(transform, true) * transform; + std::vector eigenvalues; + Matz eigenvectors; + diagonalize_hermitian(gram, eigenvalues, eigenvectors); + for (const double eigenvalue : eigenvalues) + { + if (!(eigenvalue > 0.0) || !std::isfinite(eigenvalue)) + { + throw std::invalid_argument( + "QSGW velocity WFC basis transform has a non-positive singular value"); + } + } + + Matz inverse_sqrt(dimension, dimension, transform.major()); + for (int row = 0; row < dimension; ++row) + { + for (int column = 0; column < dimension; ++column) + { + cplxdb value = 0.0; + for (int state = 0; state < dimension; ++state) + { + value += eigenvectors(row, state) * + (1.0 / std::sqrt(eigenvalues[state])) * + std::conj(eigenvectors(column, state)); + } + inverse_sqrt(row, column) = value; + } + } + Matz result = transform * inverse_sqrt; + require_finite_matrix(result, "projected velocity WFC basis transform"); + return result; +} + +} // namespace + +ScopedReferenceEigenvectors::ScopedReferenceEigenvectors( + MeanField& live, + const MeanField& reference) + : live_(live), + live_eigenvectors_(reference.get_eigenvectors()) +{ + if (&live == &reference) + { + throw std::invalid_argument( + "QSGW live and reference mean fields must be distinct"); + } + require_same_meanfield_shape(live, reference); + require_same_wfc_distribution(live, reference); + live_.get_eigenvectors().swap(live_eigenvectors_); +} + +ScopedReferenceEigenvectors::~ScopedReferenceEigenvectors() noexcept +{ + live_.get_eigenvectors().swap(live_eigenvectors_); +} + +void prepare_fhi_aims_interband_velocity( + VelocityMatrix& velocity, + const MeanField& reference) +{ + require_valid_velocity(velocity, reference, "FHI-aims source"); + VelocityMatrix prepared = velocity; + for (auto& spin : prepared) + { + for (auto& kpoint : spin) + { + for (ComplexMatrix& component : kpoint) + { + for (int row = 0; row < component.nr; ++row) + { + for (int column = row + 1; + column < component.nc; ++column) + { + if (std::abs(component(row, column) - + std::conj(component(column, row))) > + hermitian_tolerance) + { + throw std::invalid_argument( + "QSGW FHI-aims interband velocity is not Hermitian"); + } + } + component(row, row) = 0.0; + } + } + } + } + velocity = std::move(prepared); +} + +VelocityBasisAlignmentResult align_velocity_to_reference_wfc( + const MeanField& velocity_basis, + const MeanField& reference, + VelocityMatrix& velocity, + const double relative_tolerance) +{ + if (!(relative_tolerance > 0.0) || + !std::isfinite(relative_tolerance)) + { + throw std::invalid_argument( + "QSGW velocity-basis tolerance must be finite and positive"); + } + require_same_meanfield_shape(velocity_basis, reference); + require_valid_wfc(velocity_basis, "velocity-basis"); + require_valid_wfc(reference, "reference"); + require_valid_velocity(velocity, reference, "source"); + + VelocityMatrix aligned = velocity; + VelocityBasisAlignmentResult result; + const int dimension = reference.get_n_bands(); + const int coefficient_dimension = + reference.get_n_aos() * reference.get_n_spinor(); + if (dimension != coefficient_dimension) + { + throw std::invalid_argument( + "QSGW velocity alignment requires a complete square WFC basis"); + } + for (int spin = 0; spin < reference.get_n_spins(); ++spin) + { + for (int kpoint = 0; kpoint < reference.get_n_kpoints(); ++kpoint) + { + const Matz source_rows = collect_reference_wfc_rows( + velocity_basis, spin, kpoint, MAJOR::ROW); + const Matz reference_rows = collect_reference_wfc_rows( + reference, spin, kpoint, MAJOR::ROW); + const Matz reference_inverse = invert_complete_wfc_basis( + reference_rows, relative_tolerance, result); + const Matz raw_transform = source_rows * reference_inverse; + require_finite_matrix( + raw_transform, "raw velocity WFC basis transform"); + + const double raw_relative_residual = + relative_reconstruction_residual( + source_rows, raw_transform, reference_rows); + result.maximum_raw_relative_wfc_residual = std::max( + result.maximum_raw_relative_wfc_residual, + raw_relative_residual); + if (!std::isfinite(raw_relative_residual) || + raw_relative_residual > relative_tolerance) + { + throw std::invalid_argument( + "QSGW raw velocity WFC basis transform does not reconstruct the source basis"); + } + + const double raw_unitarity_residual = + maximum_unitarity_residual(raw_transform); + result.maximum_raw_unitarity_residual = std::max( + result.maximum_raw_unitarity_residual, + raw_unitarity_residual); + if (!std::isfinite(raw_unitarity_residual) || + raw_unitarity_residual > relative_tolerance) + { + throw std::invalid_argument( + "QSGW raw velocity WFC basis transform is not unitary within input precision"); + } + + const Matz transform = + project_to_nearest_unitary(raw_transform); + const double relative_residual = + relative_reconstruction_residual( + source_rows, transform, reference_rows); + result.maximum_relative_wfc_residual = std::max( + result.maximum_relative_wfc_residual, + relative_residual); + if (!std::isfinite(relative_residual) || + relative_residual > relative_tolerance) + { + throw std::invalid_argument( + "QSGW projected velocity WFC basis transform does not reconstruct the source basis"); + } + + const double unitary_residual = + maximum_unitarity_residual(transform); + result.maximum_unitarity_residual = std::max( + result.maximum_unitarity_residual, + unitary_residual); + if (!std::isfinite(unitary_residual) || + unitary_residual > relative_tolerance) + { + throw std::invalid_argument( + "QSGW projected velocity WFC basis transform is not unitary"); + } + + for (int row = 0; row < dimension; ++row) + { + for (int column = 0; column < dimension; ++column) + { + const cplxdb expected = row == column ? 1.0 : 0.0; + result.maximum_unitary_projection_correction = + std::max( + result.maximum_unitary_projection_correction, + std::abs( + transform(row, column) - + raw_transform(row, column))); + result.maximum_transform_deviation_from_identity = + std::max( + result.maximum_transform_deviation_from_identity, + std::abs(transform(row, column) - expected)); + } + } + const ComplexMatrix transform_matrix = + to_complex_matrix(transform); + const ComplexMatrix transform_transpose = + transpose(transform_matrix, false); + const ComplexMatrix transform_conjugate = + librpa_int::conj(transform_matrix); + for (int direction = 0; direction < 3; ++direction) + { + aligned[spin][kpoint][direction] = + transform_transpose * + velocity[spin][kpoint][direction] * + transform_conjugate; + } + } + } + velocity = std::move(aligned); + return result; +} + +VelocityBasisAlignmentResult align_distributed_velocity_to_reference_wfc( + const MeanField& velocity_basis, + const MeanField& reference, + VelocityMatrix& velocity, + const MpiCommHandler& communicator, + const double relative_tolerance) +{ + if (!communicator.is_initialized()) + { + throw std::invalid_argument( + "QSGW velocity-basis communicator is not initialized"); + } + if (!(relative_tolerance > 0.0) || + !std::isfinite(relative_tolerance)) + { + throw std::invalid_argument( + "QSGW velocity-basis tolerance must be finite and positive"); + } + require_same_meanfield_shape(velocity_basis, reference); + + MeanField replicated_basis = velocity_basis; + const int expected_rows = reference.get_n_bands(); + const int expected_columns = reference.get_n_aos(); + for (int spin = 0; spin < reference.get_n_spins(); ++spin) + { + for (int spinor = 0; + spinor < reference.get_n_spinor(); ++spinor) + { + for (int kpoint = 0; + kpoint < reference.get_n_kpoints(); ++kpoint) + { + const ComplexMatrix* local = + velocity_basis.find_wfc(spin, spinor, kpoint); + const int owns_local = local == nullptr ? 0 : 1; + const int bad_shape_local = + local != nullptr && + (local->nr != expected_rows || + local->nc != expected_columns) + ? 1 + : 0; + int bad_shape_global = 0; + communicator.allreduce( + &bad_shape_local, &bad_shape_global, 1, MPI_MAX); + if (bad_shape_global != 0) + { + throw std::invalid_argument( + "QSGW distributed velocity-basis wavefunction has an invalid shape"); + } + + const int owner = find_mpi_owner_rank( + owns_local != 0, communicator.comm); + if (owner < 0) + { + throw std::invalid_argument( + "QSGW distributed velocity-basis wavefunction is missing on every MPI rank"); + } + + ComplexMatrix canonical; + if (communicator.myid == owner) + { + canonical = *local; + } + broadcast_ComplexMatrix( + canonical, owner, communicator.comm); + + int nonfinite_local = 0; + double difference_local = 0.0; + double scale_local = 0.0; + if (local != nullptr) + { + for (int index = 0; index < local->size; ++index) + { + const cplxdb value = local->c[index]; + const cplxdb reference_value = canonical.c[index]; + if (!std::isfinite(value.real()) || + !std::isfinite(value.imag()) || + !std::isfinite(reference_value.real()) || + !std::isfinite(reference_value.imag())) + { + nonfinite_local = 1; + continue; + } + difference_local = std::max( + difference_local, + std::abs(value - reference_value)); + scale_local = std::max( + scale_local, std::abs(reference_value)); + } + } + int nonfinite_global = 0; + double difference_global = 0.0; + double scale_global = 0.0; + communicator.allreduce( + &nonfinite_local, &nonfinite_global, 1, MPI_MAX); + communicator.allreduce( + &difference_local, &difference_global, 1, MPI_MAX); + communicator.allreduce( + &scale_local, &scale_global, 1, MPI_MAX); + if (nonfinite_global != 0 || + difference_global > + relative_tolerance * std::max(1.0, scale_global)) + { + throw std::invalid_argument( + "QSGW distributed velocity-basis wavefunction copies disagree across MPI ranks"); + } + + replicated_basis.get_eigenvectors()[spin][spinor][kpoint] = + std::move(canonical); + } + } + } + + return align_velocity_to_reference_wfc( + replicated_basis, reference, velocity, relative_tolerance); +} + +FixedBasisDiagonalizationResult diagonalize_in_reference_basis( + MeanField& live, + const MeanField& reference, + const SpinKMatrixMap& hamiltonian) +{ + if (&live == &reference) + { + throw std::invalid_argument( + "QSGW live and reference mean fields must be distinct objects"); + } + require_same_meanfield_shape(live, reference); + require_valid_wfc(reference, "reference"); + require_valid_wfc(live, "live"); + require_valid_hamiltonian(hamiltonian, reference); + + MeanField next_live = live; + + FixedBasisDiagonalizationResult result; + for (int spin = 0; spin < reference.get_n_spins(); ++spin) + { + for (int kpoint = 0; kpoint < reference.get_n_kpoints(); ++kpoint) + { + const Matz& matrix = hamiltonian.at(spin).at(kpoint); + std::vector eigenvalues; + Matz unitary; + diagonalize_hermitian(matrix, eigenvalues, unitary); + result.unitary[spin][kpoint] = unitary; + + for (int band = 0; band < reference.get_n_bands(); ++band) + { + next_live.get_eigenvals()[spin](kpoint, band) = + eigenvalues.at(band); + } + + const Matz reference_rows = collect_reference_wfc_rows( + reference, spin, kpoint, unitary.major()); + const Matz rotated_rows = + transpose(unitary, false) * reference_rows; + store_wfc_rows(next_live, spin, kpoint, rotated_rows); + } + } + + live = std::move(next_live); + return result; +} + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/fixed_basis.h b/src/qsgw/fixed_basis.h new file mode 100644 index 00000000..634e7f76 --- /dev/null +++ b/src/qsgw/fixed_basis.h @@ -0,0 +1,85 @@ +#pragma once + +#include "matrix_map.h" + +#include "../core/meanfield.h" +#include "../math/complexmatrix.h" +#include "../mpi/base_mpi.h" + +#include +#include + +namespace librpa_int +{ +namespace qsgw +{ + +using VelocityMatrix = + std::vector>>; + +using MeanFieldEigenvectorMap = + std::map>>; + +// Temporarily expose the immutable reference WFC through a live MeanField so +// the unmodified upstream k-grid projection routines can be reused. All other +// live mean-field state remains untouched and the live WFC is restored on exit. +class ScopedReferenceEigenvectors +{ +public: + ScopedReferenceEigenvectors(MeanField& live, + const MeanField& reference); + ~ScopedReferenceEigenvectors() noexcept; + + ScopedReferenceEigenvectors(const ScopedReferenceEigenvectors&) = delete; + ScopedReferenceEigenvectors& operator=( + const ScopedReferenceEigenvectors&) = delete; + ScopedReferenceEigenvectors(ScopedReferenceEigenvectors&&) = delete; + ScopedReferenceEigenvectors& operator=( + ScopedReferenceEigenvectors&&) = delete; + +private: + MeanField& live_; + MeanFieldEigenvectorMap live_eigenvectors_; +}; + +struct FixedBasisDiagonalizationResult +{ + SpinKMatrixMap unitary; +}; + +struct VelocityBasisAlignmentResult +{ + double maximum_relative_wfc_residual = 0.0; + double maximum_unitarity_residual = 0.0; + double maximum_raw_relative_wfc_residual = 0.0; + double maximum_raw_unitarity_residual = 0.0; + double maximum_unitary_projection_correction = 0.0; + double maximum_basis_inverse_residual = 0.0; + double maximum_basis_condition_estimate = 0.0; + double maximum_transform_deviation_from_identity = 0.0; +}; + +void prepare_fhi_aims_interband_velocity( + VelocityMatrix& velocity, + const MeanField& reference); + +VelocityBasisAlignmentResult align_velocity_to_reference_wfc( + const MeanField& velocity_basis, + const MeanField& reference, + VelocityMatrix& velocity, + double relative_tolerance = 1.0e-8); + +VelocityBasisAlignmentResult align_distributed_velocity_to_reference_wfc( + const MeanField& velocity_basis, + const MeanField& reference, + VelocityMatrix& velocity, + const MpiCommHandler& communicator, + double relative_tolerance = 1.0e-8); + +FixedBasisDiagonalizationResult diagonalize_in_reference_basis( + MeanField& live, + const MeanField& reference, + const SpinKMatrixMap& hamiltonian); + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/hamiltonian_cut.cpp b/src/qsgw/hamiltonian_cut.cpp new file mode 100644 index 00000000..fb00489e --- /dev/null +++ b/src/qsgw/hamiltonian_cut.cpp @@ -0,0 +1,151 @@ +#include "hamiltonian_cut.h" + +#include +#include +#include +#include +#include + +namespace librpa_int +{ +namespace qsgw +{ +namespace +{ + +void validate_options(const HamiltonianCutOptions& options) +{ + if (options.unoccupied_keep < 0) + throw std::invalid_argument( + "QSGW Hamiltonian cut requires non-negative unoccupied_keep"); + if (!std::isfinite(options.shift_ha)) + throw std::invalid_argument( + "QSGW Hamiltonian cut shift must be finite"); + switch (options.mode) + { + case HamiltonianCutMode::Uncut: + case HamiltonianCutMode::ReferenceDiagonal: + case HamiltonianCutMode::ShiftedReferenceDiagonal: + return; + } + throw std::invalid_argument("Unsupported QSGW Hamiltonian cut mode"); +} + +void validate_matrix(const Matz& matrix, const int dimension, + const std::string& label) +{ + if (matrix.nr() != dimension || matrix.nc() != dimension) + throw std::invalid_argument( + "QSGW Hamiltonian cut " + label + " dimension differs"); + for (int row = 0; row < dimension; ++row) + { + for (int column = 0; column < dimension; ++column) + { + const cplxdb value = matrix(row, column); + if (!std::isfinite(value.real()) || !std::isfinite(value.imag())) + throw std::invalid_argument( + "QSGW Hamiltonian cut " + label + + " contains non-finite data"); + } + } +} + +} // namespace + +HamiltonianCutMode hamiltonian_cut_mode_from_int(const int mode) +{ + switch (mode) + { + case 0: return HamiltonianCutMode::Uncut; + case 1: return HamiltonianCutMode::ReferenceDiagonal; + case 2: return HamiltonianCutMode::ShiftedReferenceDiagonal; + default: + throw std::invalid_argument( + "QSGW Hamiltonian cut mode must be 0, 1, or 2"); + } +} + +SpinKMatrixMap apply_hamiltonian_cut( + const SpinKMatrixMap& hamiltonian, + const SpinKMatrixMap& reference_hamiltonian, + const MeanField& live_meanfield, + const HamiltonianCutOptions& options) +{ + validate_options(options); + if (!live_meanfield.initialized()) + throw std::invalid_argument( + "QSGW Hamiltonian cut mean field is not initialized"); + if (options.mode != HamiltonianCutMode::Uncut && + !std::isfinite(live_meanfield.get_efermi())) + throw std::invalid_argument( + "QSGW Hamiltonian cut Fermi energy is non-finite"); + if (hamiltonian.size() != reference_hamiltonian.size() || + static_cast(hamiltonian.size()) != live_meanfield.get_n_spins()) + throw std::invalid_argument( + "QSGW Hamiltonian cut spin layout differs"); + + const int dimension = live_meanfield.get_n_bands(); + SpinKMatrixMap result; + for (const auto& [spin, by_kpoint] : hamiltonian) + { + const auto reference_spin = reference_hamiltonian.find(spin); + if (spin < 0 || spin >= live_meanfield.get_n_spins() || + reference_spin == reference_hamiltonian.end() || + by_kpoint.size() != reference_spin->second.size() || + static_cast(by_kpoint.size()) != + live_meanfield.get_n_kpoints()) + throw std::invalid_argument( + "QSGW Hamiltonian cut k-point layout differs"); + + for (const auto& [kpoint, matrix] : by_kpoint) + { + const auto reference_kpoint = + reference_spin->second.find(kpoint); + if (kpoint < 0 || kpoint >= live_meanfield.get_n_kpoints() || + reference_kpoint == reference_spin->second.end()) + throw std::invalid_argument( + "QSGW Hamiltonian cut k-point keys differ"); + validate_matrix(matrix, dimension, "input"); + validate_matrix(reference_kpoint->second, dimension, + "reference"); + + Matz cut = matrix.copy(); + if (options.mode != HamiltonianCutMode::Uncut) + { + int occupied = 0; + for (int band = 0; band < dimension; ++band) + { + const double energy = + live_meanfield.get_eigenvals()[spin](kpoint, band); + if (!std::isfinite(energy)) + throw std::invalid_argument( + "QSGW Hamiltonian cut eigenvalue is non-finite"); + if (energy < live_meanfield.get_efermi()) ++occupied; + } + const int active_limit = std::min( + dimension, occupied + options.unoccupied_keep); + const double shift = + options.mode == + HamiltonianCutMode::ShiftedReferenceDiagonal + ? options.shift_ha + : 0.0; + for (int row = 0; row < dimension; ++row) + { + for (int column = 0; column < dimension; ++column) + { + if (row < active_limit && column < active_limit) + continue; + cut(row, column) = row == column + ? reference_kpoint->second(row, row) + shift + : cplxdb(0.0, 0.0); + } + } + } + result[spin][kpoint] = std::move(cut); + } + } + return result; +} + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/hamiltonian_cut.h b/src/qsgw/hamiltonian_cut.h new file mode 100644 index 00000000..9970640c --- /dev/null +++ b/src/qsgw/hamiltonian_cut.h @@ -0,0 +1,36 @@ +#pragma once + +#include "matrix_map.h" + +#include "../core/meanfield.h" + +namespace librpa_int +{ +namespace qsgw +{ + +enum class HamiltonianCutMode +{ + Uncut = 0, + ReferenceDiagonal = 1, + ShiftedReferenceDiagonal = 2, +}; + +struct HamiltonianCutOptions +{ + int unoccupied_keep = 10; + HamiltonianCutMode mode = + HamiltonianCutMode::ShiftedReferenceDiagonal; + double shift_ha = 20.0; +}; + +HamiltonianCutMode hamiltonian_cut_mode_from_int(int mode); + +SpinKMatrixMap apply_hamiltonian_cut( + const SpinKMatrixMap& hamiltonian, + const SpinKMatrixMap& reference_hamiltonian, + const MeanField& live_meanfield, + const HamiltonianCutOptions& options); + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/hamiltonian_mixing.cpp b/src/qsgw/hamiltonian_mixing.cpp new file mode 100644 index 00000000..8aaad025 --- /dev/null +++ b/src/qsgw/hamiltonian_mixing.cpp @@ -0,0 +1,274 @@ +#include "hamiltonian_mixing.h" + +#include +#include +#include +#include + +namespace librpa_int +{ +namespace qsgw +{ +namespace +{ + +constexpr double hermitian_tolerance = 1.0e-10; + +using MatrixBlockLayout = detail::HamiltonianMatrixBlockLayout; +using MapLayout = detail::HamiltonianMapLayout; + +MapLayout build_layout(const SpinKMatrixMap& values, const char* channel) +{ + if (values.empty()) + { + throw std::invalid_argument( + std::string("QSGW ") + channel + " Hamiltonian map is empty"); + } + + MapLayout layout; + for (const auto& [spin, by_kpoint] : values) + { + if (by_kpoint.empty()) + { + throw std::invalid_argument( + std::string("QSGW ") + channel + + " Hamiltonian contains an empty spin map"); + } + for (const auto& [kpoint, value] : by_kpoint) + { + if (value.nr() <= 0 || value.nr() != value.nc()) + { + throw std::invalid_argument( + std::string("QSGW ") + channel + + " Hamiltonian matrix must be non-empty and square"); + } + for (int row = 0; row < value.nr(); ++row) + { + for (int column = 0; column < value.nc(); ++column) + { + const cplxdb element = value(row, column); + if (!std::isfinite(element.real()) || + !std::isfinite(element.imag())) + { + throw std::invalid_argument( + std::string("QSGW ") + channel + + " Hamiltonian contains non-finite data"); + } + if (std::abs(element - std::conj(value(column, row))) > + hermitian_tolerance) + { + throw std::invalid_argument( + std::string("QSGW ") + channel + + " Hamiltonian is not Hermitian"); + } + } + } + + MatrixBlockLayout block; + block.spin = spin; + block.kpoint = kpoint; + block.rows = value.nr(); + block.columns = value.nc(); + block.major = value.major(); + block.packed_offset = layout.packed_size; + layout.packed_size += 2 * value.nr() * value.nc(); + layout.blocks.push_back(block); + } + } + return layout; +} + +void require_matching_layout(const SpinKMatrixMap& values, + const MapLayout& expected, + const char* channel) +{ + const MapLayout actual = build_layout(values, channel); + if (actual.blocks.size() != expected.blocks.size() || + actual.packed_size != expected.packed_size) + { + throw std::invalid_argument( + std::string("QSGW ") + channel + + " Hamiltonian layout changed during mixing"); + } + for (std::size_t index = 0; index < expected.blocks.size(); ++index) + { + const MatrixBlockLayout& lhs = actual.blocks[index]; + const MatrixBlockLayout& rhs = expected.blocks[index]; + if (lhs.spin != rhs.spin || lhs.kpoint != rhs.kpoint || + lhs.rows != rhs.rows || lhs.columns != rhs.columns) + { + throw std::invalid_argument( + std::string("QSGW ") + channel + + " Hamiltonian keys or dimensions changed during mixing"); + } + } +} + +matrix pack_map(const SpinKMatrixMap& values, const MapLayout& layout) +{ + matrix packed(layout.packed_size, 1, true); + for (const MatrixBlockLayout& block : layout.blocks) + { + const Matz& value = values.at(block.spin).at(block.kpoint); + int offset = block.packed_offset; + for (int row = 0; row < block.rows; ++row) + { + for (int column = 0; column < block.columns; ++column) + { + packed(offset++, 0) = value(row, column).real(); + packed(offset++, 0) = value(row, column).imag(); + } + } + } + return packed; +} + +SpinKMatrixMap unpack_map(const matrix& packed, const MapLayout& layout) +{ + if (packed.nr != layout.packed_size || packed.nc != 1) + { + throw std::runtime_error( + "QSGW packed Hamiltonian has an unexpected shape"); + } + + SpinKMatrixMap result; + for (const MatrixBlockLayout& block : layout.blocks) + { + Matz value(block.rows, block.columns, block.major); + int offset = block.packed_offset; + for (int row = 0; row < block.rows; ++row) + { + for (int column = 0; column < block.columns; ++column) + { + value(row, column) = + cplxdb(packed(offset, 0), packed(offset + 1, 0)); + offset += 2; + } + } + result[block.spin][block.kpoint] = std::move(value); + } + return result; +} + +} // namespace + +SpinKHamiltonianResidual measure_spin_k_hamiltonian_residual( + const SpinKMatrixMap& output, + const SpinKMatrixMap& input) +{ + const MapLayout layout = build_layout(input, "input"); + require_matching_layout(output, layout, "output"); + double square_sum = 0.0; + double maximum = 0.0; + for (const MatrixBlockLayout& block : layout.blocks) + { + const Matz& lhs = output.at(block.spin).at(block.kpoint); + const Matz& rhs = input.at(block.spin).at(block.kpoint); + for (int row = 0; row < block.rows; ++row) + { + for (int column = 0; column < block.columns; ++column) + { + const double magnitude = std::abs( + lhs(row, column) - rhs(row, column)); + square_sum += magnitude * magnitude; + maximum = std::max(maximum, magnitude); + } + } + } + return {std::sqrt(square_sum), maximum}; +} + +SpinKHamiltonianMixer::SpinKHamiltonianMixer(MixingOptions options) + : mixer_(std::move(options)) +{ +} + +void SpinKHamiltonianMixer::initialize(const SpinKMatrixMap& grid_input) +{ + const MapLayout grid_layout = build_layout(grid_input, "grid"); + HamiltonianMixer next_mixer = mixer_; + next_mixer.initialize(pack_map(grid_input, grid_layout)); + + mixer_ = std::move(next_mixer); + grid_layout_ = grid_layout; + band_layout_.reset(); + initialized_ = true; +} + +void SpinKHamiltonianMixer::initialize(const SpinKMatrixMap& grid_input, + const SpinKMatrixMap& band_input) +{ + const MapLayout grid_layout = build_layout(grid_input, "grid"); + const MapLayout band_layout = build_layout(band_input, "band"); + HamiltonianMixer next_mixer = mixer_; + next_mixer.initialize(pack_map(grid_input, grid_layout), + pack_map(band_input, band_layout)); + + mixer_ = std::move(next_mixer); + grid_layout_ = grid_layout; + band_layout_ = band_layout; + initialized_ = true; +} + +SpinKHamiltonianMixResult SpinKHamiltonianMixer::mix( + const SpinKMatrixMap& grid_output) +{ + return mix_impl(grid_output, std::nullopt); +} + +SpinKHamiltonianMixResult SpinKHamiltonianMixer::mix( + const SpinKMatrixMap& grid_output, + const SpinKMatrixMap& band_output) +{ + return mix_impl(grid_output, band_output); +} + +SpinKHamiltonianMixResult SpinKHamiltonianMixer::mix_impl( + const SpinKMatrixMap& grid_output, + const std::optional& band_output) +{ + if (!initialized_) + { + throw std::logic_error( + "QSGW spin/k Hamiltonian mixer must be initialized before use"); + } + require_matching_layout(grid_output, grid_layout_, "grid"); + if (band_layout_.has_value() != band_output.has_value()) + { + throw std::invalid_argument( + "QSGW band Hamiltonian presence changed during mixing"); + } + if (band_output.has_value()) + { + require_matching_layout(*band_output, *band_layout_, "band"); + } + + HamiltonianMixer next_mixer = mixer_; + HamiltonianMixResult packed_result; + if (band_output.has_value()) + { + packed_result = next_mixer.mix( + pack_map(grid_output, grid_layout_), + pack_map(*band_output, *band_layout_)); + } + else + { + packed_result = next_mixer.mix(pack_map(grid_output, grid_layout_)); + } + + SpinKHamiltonianMixResult result; + result.grid = unpack_map(packed_result.grid, grid_layout_); + if (packed_result.band.has_value()) + { + result.band = unpack_map(*packed_result.band, *band_layout_); + } + result.decision = std::move(packed_result.decision); + result.residual_l2 = packed_result.residual_l2; + result.residual_max = packed_result.residual_max; + + mixer_ = std::move(next_mixer); + return result; +} + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/hamiltonian_mixing.h b/src/qsgw/hamiltonian_mixing.h new file mode 100644 index 00000000..78fdf564 --- /dev/null +++ b/src/qsgw/hamiltonian_mixing.h @@ -0,0 +1,79 @@ +#pragma once + +#include "matrix_map.h" +#include "mixing.h" + +#include +#include + +namespace librpa_int +{ +namespace qsgw +{ + +struct SpinKHamiltonianMixResult +{ + SpinKMatrixMap grid; + std::optional band; + MixingDecision decision; + double residual_l2 = 0.0; + double residual_max = 0.0; +}; + +struct SpinKHamiltonianResidual +{ + double l2 = 0.0; + double maximum = 0.0; +}; + +SpinKHamiltonianResidual measure_spin_k_hamiltonian_residual( + const SpinKMatrixMap& output, + const SpinKMatrixMap& input); + +namespace detail +{ + +struct HamiltonianMatrixBlockLayout +{ + int spin = 0; + int kpoint = 0; + int rows = 0; + int columns = 0; + MAJOR major = MAJOR::ROW; + int packed_offset = 0; +}; + +struct HamiltonianMapLayout +{ + std::vector blocks; + int packed_size = 0; +}; + +} // namespace detail + +class SpinKHamiltonianMixer +{ +public: + explicit SpinKHamiltonianMixer(MixingOptions options = {}); + + void initialize(const SpinKMatrixMap& grid_input); + void initialize(const SpinKMatrixMap& grid_input, + const SpinKMatrixMap& band_input); + + SpinKHamiltonianMixResult mix(const SpinKMatrixMap& grid_output); + SpinKHamiltonianMixResult mix(const SpinKMatrixMap& grid_output, + const SpinKMatrixMap& band_output); + +private: + HamiltonianMixer mixer_; + bool initialized_ = false; + detail::HamiltonianMapLayout grid_layout_; + std::optional band_layout_; + + SpinKHamiltonianMixResult mix_impl( + const SpinKMatrixMap& grid_output, + const std::optional& band_output); +}; + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/input_contract.cpp b/src/qsgw/input_contract.cpp new file mode 100644 index 00000000..0cfb708e --- /dev/null +++ b/src/qsgw/input_contract.cpp @@ -0,0 +1,641 @@ +#include "input_contract.h" +#include "sha256.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace librpa_int +{ +namespace qsgw +{ +namespace +{ + +std::string trim(std::string value) +{ + value.erase( + value.begin(), + std::find_if(value.begin(), value.end(), [](const unsigned char ch) { + return !std::isspace(ch); + })); + value.erase( + std::find_if(value.rbegin(), value.rend(), [](const unsigned char ch) { + return !std::isspace(ch); + }).base(), + value.end()); + return value; +} + +std::string lowercase(std::string value) +{ + std::transform(value.begin(), value.end(), value.begin(), + [](const unsigned char ch) { + return static_cast(std::tolower(ch)); + }); + return value; +} + +int parse_integer(const std::string& value, + const std::string& key, + const std::string& source_name) +{ + try + { + std::size_t consumed = 0; + const int result = std::stoi(value, &consumed); + if (consumed != value.size()) throw std::invalid_argument("suffix"); + return result; + } + catch (const std::exception&) + { + throw std::invalid_argument( + "Invalid QSGW input-contract integer " + key + " in " + + source_name); + } +} + +int read_pyatb_kpoint_count(const std::filesystem::path& kpoint_file, + const int active_kpoints) +{ + std::ifstream input(kpoint_file); + int n_basis = 0; + int n_states = 0; + int n_spins = 0; + int source_kpoints = 0; + if (!(input >> n_basis >> n_states >> n_spins >> source_kpoints) || + n_basis <= 0 || n_states <= 0 || n_spins <= 0 || + source_kpoints < active_kpoints) + { + throw std::invalid_argument( + "QSGW cannot determine complete PyATB head-only k-point coverage from " + + kpoint_file.string()); + } + return source_kpoints; +} + +bool safe_relative_path(const std::string& value) +{ + if (value.empty() || value.find('\\') != std::string::npos) return false; + const std::filesystem::path path(value); + if (path.is_absolute()) return false; + for (const auto& component : path) + { + if (component == "..") return false; + } + return true; +} + +const std::set& allowed_roles() +{ + static const std::set roles{ + "mf0_eigenvalues", + "mf0_wavefunctions", + "scf_kpoints", + "vxc_scf_manifest", + "reader_static", + "velocity_mf0", + "band_mf0_eigenvalues", + "band_mf0_wavefunctions", + "band_kpoints", + "vxc_band_manifest", + }; + return roles; +} + +bool has_role(const QsgwInputContract& contract, const std::string& role) +{ + return !contract.files(role).empty(); +} + +void require_roles(const QsgwInputContract& contract, + const std::initializer_list roles, + const std::string& source_name) +{ + for (const char* role : roles) + { + if (!has_role(contract, role)) + { + throw std::invalid_argument( + "Missing QSGW input-contract role " + std::string(role) + + " in " + source_name); + } + } +} + +void reject_roles(const QsgwInputContract& contract, + const std::initializer_list roles, + const std::string& source_name) +{ + for (const char* role : roles) + { + if (has_role(contract, role)) + { + throw std::invalid_argument( + "Unexpected QSGW input-contract role " + std::string(role) + + " in " + source_name); + } + } +} + +} // namespace + +BandReferencePaths resolve_band_reference_paths( + const std::string& input_directory, + const std::string& band_kpath_filename, + const int n_kpoints) +{ + if (input_directory.empty() || band_kpath_filename.empty() || + n_kpoints <= 0) + { + throw std::invalid_argument( + "QSGW band reference input needs a directory, k-path file, and positive k-point count"); + } + + const std::filesystem::path input(input_directory); + BandReferencePaths paths; + paths.kpoints = std::filesystem::absolute( + input / band_kpath_filename) + .lexically_normal(); + paths.eigenvalues.reserve(static_cast(n_kpoints)); + paths.wavefunctions.reserve(static_cast(n_kpoints)); + for (int kpoint = 1; kpoint <= n_kpoints; ++kpoint) + { + std::ostringstream index; + index << std::setw(5) << std::setfill('0') << kpoint; + paths.eigenvalues.push_back( + std::filesystem::absolute( + input / + ("band_KS_eigenvalue_k_" + index.str() + ".txt")) + .lexically_normal()); + paths.wavefunctions.push_back( + std::filesystem::absolute( + input / + ("band_KS_eigenvector_k_" + index.str() + ".txt")) + .lexically_normal()); + } + return paths; +} + +std::vector resolve_same_grid_velocity_paths( + const QsgwProducer producer, + const std::string& input_directory, + const int n_kpoints) +{ + if (input_directory.empty() || n_kpoints <= 0) + { + throw std::invalid_argument( + "QSGW same-grid velocity input needs a directory and positive k-point count"); + } + + const std::filesystem::path input(input_directory); + std::vector paths; + if (producer == QsgwProducer::FhiAims) + { + paths.reserve(static_cast(n_kpoints)); + for (int kpoint = 1; kpoint <= n_kpoints; ++kpoint) + { + std::ostringstream filename; + filename << "mommat_ks_kpt_" << std::setw(6) + << std::setfill('0') << kpoint << ".dat"; + paths.push_back( + std::filesystem::absolute(input / filename.str()) + .lexically_normal()); + } + } + else + { + const std::filesystem::path pyatb_velocity = + input / "pyatb_librpa_df" / "velocity_matrix"; + if (std::filesystem::exists(pyatb_velocity)) + { + const std::filesystem::path pyatb = + input / "pyatb_librpa_df"; + const std::filesystem::path pyatb_kpoints = + pyatb / "k_path_info"; + const int source_kpoints = + read_pyatb_kpoint_count(pyatb_kpoints, n_kpoints); + paths.reserve(static_cast(source_kpoints) + 3); + paths.push_back(pyatb_kpoints); + paths.push_back(pyatb / "band_out"); + for (int kpoint = 0; kpoint < source_kpoints; ++kpoint) + { + paths.push_back( + pyatb / + ("KS_eigenvector_" + std::to_string(kpoint) + ".dat")); + } + paths.push_back(pyatb_velocity); + for (std::filesystem::path& path : paths) + { + path = std::filesystem::absolute(path).lexically_normal(); + } + } + else + { + paths.push_back( + std::filesystem::absolute(input / "velocity_matrix") + .lexically_normal()); + } + } + return paths; +} + +QsgwInputContract QsgwInputContract::parse( + std::istream& input, + const std::string& source_name) +{ + QsgwInputContract result; + std::map metadata; + std::set> role_files; + bool saw_magic = false; + bool saw_table_header = false; + std::string line; + while (std::getline(input, line)) + { + const std::string content = trim(line); + if (content.empty()) continue; + if (!saw_magic) + { + if (content != "# librpa-qsgw-input-contract-v1") + { + throw std::invalid_argument( + "Invalid QSGW input-contract header in " + source_name); + } + saw_magic = true; + continue; + } + if (!saw_table_header) + { + std::istringstream fields(content); + std::string key; + std::string value; + fields >> key; + if (lowercase(key) == "role") + { + std::string sha256; + std::string file; + std::string extra; + if (!(fields >> sha256 >> file) || fields >> extra || + lowercase(sha256) != "sha256" || + lowercase(file) != "file") + { + throw std::invalid_argument( + "Invalid QSGW input-contract table header in " + + source_name); + } + saw_table_header = true; + continue; + } + std::string extra; + if (key.empty() || !(fields >> value) || fields >> extra) + { + throw std::invalid_argument( + "Malformed QSGW input-contract metadata in " + + source_name); + } + key = lowercase(key); + if (!metadata.emplace(key, value).second) + { + throw std::invalid_argument( + "Duplicate QSGW input-contract metadata in " + + source_name); + } + continue; + } + + QsgwInputFile file; + std::istringstream fields(content); + std::string extra; + if (!(fields >> file.role >> file.sha256 >> file.file) || + fields >> extra) + { + throw std::invalid_argument( + "Malformed QSGW input-contract file entry in " + + source_name); + } + file.role = lowercase(file.role); + if (allowed_roles().count(file.role) == 0 || + !is_sha256_hex(file.sha256) || !safe_relative_path(file.file) || + !role_files.emplace(file.role, file.file).second) + { + throw std::invalid_argument( + "Invalid QSGW input-contract file entry in " + source_name); + } + result.files_[file.role].push_back(std::move(file)); + } + + const std::set required_metadata{ + "producer", "internal_energy_units", "mf0_basis", "mf0_gauge", + "n_spins", "n_bands", "n_aos", "n_scf_kpoints", + "n_headwing_kpoints", "n_band_kpoints", "headwing_grid", + "headwing_update", "hartree_update", "band_update", + }; + if (!saw_magic || !saw_table_header || result.files_.empty() || + metadata.size() != required_metadata.size()) + { + throw std::invalid_argument( + "Incomplete QSGW input contract " + source_name); + } + for (const std::string& key : required_metadata) + { + if (metadata.count(key) == 0) + { + throw std::invalid_argument( + "Missing QSGW input-contract metadata " + key + " in " + + source_name); + } + } + + const std::string producer = lowercase(metadata.at("producer")); + if (producer == "abacus") + result.producer_ = QsgwProducer::Abacus; + else if (producer == "fhi-aims") + result.producer_ = QsgwProducer::FhiAims; + else + throw std::invalid_argument( + "Unsupported QSGW input-contract producer in " + source_name); + + if (lowercase(metadata.at("internal_energy_units")) != "hartree" || + lowercase(metadata.at("mf0_basis")) != + "state_coefficients_in_nao" || + lowercase(metadata.at("mf0_gauge")) != "producer_state") + { + throw std::invalid_argument( + "Invalid QSGW mf0 units, basis, or gauge in " + source_name); + } + + result.n_spins_ = parse_integer( + metadata.at("n_spins"), "n_spins", source_name); + result.n_bands_ = parse_integer( + metadata.at("n_bands"), "n_bands", source_name); + result.n_aos_ = parse_integer( + metadata.at("n_aos"), "n_aos", source_name); + result.n_scf_kpoints_ = parse_integer( + metadata.at("n_scf_kpoints"), "n_scf_kpoints", source_name); + result.n_headwing_kpoints_ = parse_integer( + metadata.at("n_headwing_kpoints"), "n_headwing_kpoints", source_name); + result.n_band_kpoints_ = parse_integer( + metadata.at("n_band_kpoints"), "n_band_kpoints", source_name); + if (result.n_spins_ <= 0 || result.n_bands_ <= 0 || + result.n_aos_ <= 0 || result.n_scf_kpoints_ <= 0 || + result.n_headwing_kpoints_ < 0 || result.n_band_kpoints_ < 0) + { + throw std::invalid_argument( + "Invalid QSGW input-contract dimensions in " + source_name); + } + + const std::string headwing_grid = + lowercase(metadata.at("headwing_grid")); + if (headwing_grid == "disabled") + result.headwing_grid_ = HeadwingGridMode::Disabled; + else if (headwing_grid == "scf") + result.headwing_grid_ = HeadwingGridMode::ScfGrid; + else + throw std::invalid_argument( + "QSGW supports only disabled or same-grid head-only input in " + + source_name); + + const std::string headwing_update = + lowercase(metadata.at("headwing_update")); + if (headwing_update == "none") + result.headwing_update_ = HeadwingUpdateMode::None; + else if (headwing_update == "fixed_reference") + result.headwing_update_ = HeadwingUpdateMode::FixedReference; + else + throw std::invalid_argument( + "QSGW supports only fixed-basis same-grid head updates in " + + source_name); + + const std::string hartree = lowercase(metadata.at("hartree_update")); + if (hartree != "off") + throw std::invalid_argument( + "QSGW Hartree updates are not enabled in " + source_name); + + const std::string band = lowercase(metadata.at("band_update")); + if (band == "off") + result.band_update_ = BandUpdateMode::Off; + else if (band == "fixed_basis_rotation") + result.band_update_ = BandUpdateMode::FixedBasisRotation; + else + throw std::invalid_argument( + "Unsupported QSGW band update mode in " + source_name); + + require_roles(result, + {"mf0_eigenvalues", "mf0_wavefunctions", "scf_kpoints", + "vxc_scf_manifest", "reader_static"}, + source_name); + + if (result.headwing_grid_ == HeadwingGridMode::Disabled) + { + if (result.n_headwing_kpoints_ != 0 || + result.headwing_update_ != HeadwingUpdateMode::None) + { + throw std::invalid_argument( + "Disabled QSGW head-wing has inconsistent dimensions or update mode in " + + source_name); + } + reject_roles(result, {"velocity_mf0"}, source_name); + } + else if (result.headwing_grid_ == HeadwingGridMode::ScfGrid) + { + if (result.n_headwing_kpoints_ != result.n_scf_kpoints_ || + result.headwing_update_ != + HeadwingUpdateMode::FixedReference) + { + throw std::invalid_argument( + "Same-grid QSGW head must retain velocity in the immutable mf0 basis in " + + source_name); + } + require_roles(result, {"velocity_mf0"}, source_name); + } + + const std::initializer_list band_roles{ + "band_mf0_eigenvalues", "band_mf0_wavefunctions", "band_kpoints", + "vxc_band_manifest"}; + if (result.band_update_ == BandUpdateMode::Off) + { + if (result.n_band_kpoints_ != 0) + { + throw std::invalid_argument( + "Disabled QSGW band update has nonzero band k-points in " + + source_name); + } + reject_roles(result, band_roles, source_name); + } + else + { + if (result.n_band_kpoints_ <= 0) + { + throw std::invalid_argument( + "QSGW fixed-basis band update needs band k-points in " + + source_name); + } + require_roles(result, band_roles, source_name); + } + return result; +} + +void QsgwInputContract::validate_file_hashes( + const std::string& base_directory) const +{ + if (base_directory.empty()) + { + throw std::invalid_argument( + "QSGW input-contract base directory must not be empty"); + } + const std::filesystem::path base(base_directory); + for (const auto& role : files_) + { + for (const QsgwInputFile& file : role.second) + { + const std::filesystem::path path = base / file.file; + if (sha256_file(path.string()) != file.sha256) + { + throw std::invalid_argument( + "QSGW input SHA256 mismatch for role " + file.role + + ": " + path.string()); + } + } + } +} + +const std::vector& QsgwInputContract::files( + const std::string& role) const +{ + static const std::vector empty; + const auto found = files_.find(lowercase(role)); + return found == files_.end() ? empty : found->second; +} + +void validate_scf_input_binding( + const QsgwInputContract& contract, + const std::string& contract_base_directory, + const std::filesystem::path& eigenvalue_file, + const std::vector& wavefunction_files, + const std::filesystem::path& kpoint_file, + const std::vector& reader_static_files) +{ + if (contract_base_directory.empty() || eigenvalue_file.empty() || + wavefunction_files.empty() || kpoint_file.empty() || + reader_static_files.empty()) + { + throw std::invalid_argument( + "QSGW SCF binding requires complete reader file sets and a contract base directory"); + } + + const auto declared_paths = [&](const std::string& role) { + std::vector paths; + for (const QsgwInputFile& file : contract.files(role)) + { + paths.push_back( + std::filesystem::absolute( + std::filesystem::path(contract_base_directory) / + file.file) + .lexically_normal()); + } + std::sort(paths.begin(), paths.end()); + return paths; + }; + const auto require_exact = [&](const std::string& role, + std::vector paths) { + for (std::filesystem::path& path : paths) + path = std::filesystem::absolute(path).lexically_normal(); + std::sort(paths.begin(), paths.end()); + if (declared_paths(role) != paths) + { + throw std::invalid_argument( + "QSGW " + role + + " contract files do not exactly match the SCF files read by the driver"); + } + }; + + require_exact("mf0_eigenvalues", {eigenvalue_file}); + require_exact("mf0_wavefunctions", wavefunction_files); + require_exact("scf_kpoints", {kpoint_file}); + require_exact("reader_static", reader_static_files); +} + +void validate_band_reference_binding( + const QsgwInputContract& contract, + const std::string& contract_base_directory, + const std::string& input_directory, + const std::string& band_kpath_filename) +{ + if (contract.band_update() != BandUpdateMode::FixedBasisRotation || + contract_base_directory.empty()) + { + throw std::invalid_argument( + "QSGW band reference binding requires a fixed-basis rotation contract and contract base directory"); + } + const BandReferencePaths expected = resolve_band_reference_paths( + input_directory, band_kpath_filename, contract.n_band_kpoints()); + + const auto declared_paths = [&](const std::string& role) { + std::vector paths; + for (const QsgwInputFile& file : contract.files(role)) + { + paths.push_back( + std::filesystem::absolute( + std::filesystem::path(contract_base_directory) / + file.file) + .lexically_normal()); + } + std::sort(paths.begin(), paths.end()); + return paths; + }; + const auto require_exact = [&](const std::string& role, + std::vector paths) { + std::sort(paths.begin(), paths.end()); + if (declared_paths(role) != paths) + { + throw std::invalid_argument( + "QSGW " + role + + " contract files do not exactly match the band files read by the driver"); + } + }; + + require_exact("band_kpoints", {expected.kpoints}); + require_exact("band_mf0_eigenvalues", expected.eigenvalues); + require_exact("band_mf0_wavefunctions", expected.wavefunctions); + if (contract.files("vxc_band_manifest").size() != 1) + { + throw std::invalid_argument( + "QSGW band reference contract requires exactly one Vxc manifest"); + } +} + +void validate_qsgw_execution_modes(const QsgwInputContract& contract, + const HeadwingGridMode headwing_grid, + const bool update_band) +{ + HeadwingUpdateMode expected_update = HeadwingUpdateMode::None; + if (headwing_grid == HeadwingGridMode::ScfGrid) + expected_update = HeadwingUpdateMode::FixedReference; + const bool headwing_matches = + contract.headwing_grid() == headwing_grid && + contract.headwing_update() == expected_update; + if (!headwing_matches) + { + throw std::invalid_argument( + "QSGW execution mode and input contract disagree about the head/wing grid or live-update mode"); + } + + const bool band_matches = update_band + ? contract.band_update() == BandUpdateMode::FixedBasisRotation + : contract.band_update() == BandUpdateMode::Off; + if (!band_matches) + { + throw std::invalid_argument( + "QSGW execution mode and input contract disagree about the band update"); + } +} + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/input_contract.h b/src/qsgw/input_contract.h new file mode 100644 index 00000000..e4ff2ae9 --- /dev/null +++ b/src/qsgw/input_contract.h @@ -0,0 +1,118 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace librpa_int +{ +namespace qsgw +{ + +enum class QsgwProducer +{ + Abacus, + FhiAims, +}; + +enum class HeadwingGridMode +{ + Disabled, + ScfGrid, +}; + +enum class HeadwingUpdateMode +{ + None, + FixedReference, +}; + +enum class BandUpdateMode +{ + Off, + FixedBasisRotation, +}; + +struct QsgwInputFile +{ + std::string role; + std::string sha256; + std::string file; +}; + +struct BandReferencePaths +{ + std::filesystem::path kpoints; + std::vector eigenvalues; + std::vector wavefunctions; +}; + +BandReferencePaths resolve_band_reference_paths( + const std::string& input_directory, + const std::string& band_kpath_filename, + int n_kpoints); + +std::vector resolve_same_grid_velocity_paths( + QsgwProducer producer, + const std::string& input_directory, + int n_kpoints); + +class QsgwInputContract +{ +public: + static QsgwInputContract parse(std::istream& input, + const std::string& source_name); + + void validate_file_hashes(const std::string& base_directory) const; + const std::vector& files(const std::string& role) const; + + QsgwProducer producer() const noexcept { return producer_; } + HeadwingGridMode headwing_grid() const noexcept { return headwing_grid_; } + HeadwingUpdateMode headwing_update() const noexcept + { + return headwing_update_; + } + BandUpdateMode band_update() const noexcept { return band_update_; } + int n_spins() const noexcept { return n_spins_; } + int n_bands() const noexcept { return n_bands_; } + int n_aos() const noexcept { return n_aos_; } + int n_scf_kpoints() const noexcept { return n_scf_kpoints_; } + int n_headwing_kpoints() const noexcept { return n_headwing_kpoints_; } + int n_band_kpoints() const noexcept { return n_band_kpoints_; } + +private: + QsgwProducer producer_ = QsgwProducer::Abacus; + HeadwingGridMode headwing_grid_ = HeadwingGridMode::Disabled; + HeadwingUpdateMode headwing_update_ = HeadwingUpdateMode::None; + BandUpdateMode band_update_ = BandUpdateMode::Off; + int n_spins_ = 0; + int n_bands_ = 0; + int n_aos_ = 0; + int n_scf_kpoints_ = 0; + int n_headwing_kpoints_ = 0; + int n_band_kpoints_ = 0; + std::map> files_; +}; + +void validate_scf_input_binding( + const QsgwInputContract& contract, + const std::string& contract_base_directory, + const std::filesystem::path& eigenvalue_file, + const std::vector& wavefunction_files, + const std::filesystem::path& kpoint_file, + const std::vector& reader_static_files); + +void validate_band_reference_binding( + const QsgwInputContract& contract, + const std::string& contract_base_directory, + const std::string& input_directory, + const std::string& band_kpath_filename); + +void validate_qsgw_execution_modes(const QsgwInputContract& contract, + HeadwingGridMode headwing_grid, + bool update_band); + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/iteration_trace.cpp b/src/qsgw/iteration_trace.cpp new file mode 100644 index 00000000..36fece60 --- /dev/null +++ b/src/qsgw/iteration_trace.cpp @@ -0,0 +1,514 @@ +#include "iteration_trace.h" + +#include "../utils/constants.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace librpa_int +{ +namespace qsgw +{ +namespace +{ + +constexpr int trace_precision = std::numeric_limits::max_digits10; + +int mode_code(const MixingMode mode) +{ + (void)mode; + return 0; +} + +std::string machine_token(const std::string& value) +{ + if (value.empty()) return "none"; + std::string result = value; + for (char& character : result) + { + const unsigned char byte = static_cast(character); + if (!std::isalnum(byte) && character != '-' && character != '.') + { + character = '_'; + } + } + return result; +} + +std::string coefficient_token(const std::vector& coefficients) +{ + if (coefficients.empty()) return "none"; + std::ostringstream output; + output << std::scientific << std::setprecision(trace_precision); + for (std::size_t index = 0; index < coefficients.size(); ++index) + { + if (index != 0) output << ','; + output << coefficients[index]; + } + return output.str(); +} + +void require_finite_nonnegative(const double value, const char* label) +{ + if (!std::isfinite(value) || value < 0.0) + { + throw std::invalid_argument( + std::string("QSGW iteration trace ") + label + + " must be finite and nonnegative"); + } +} + +bool finite_complex(const cplxdb value) +{ + return std::isfinite(value.real()) && std::isfinite(value.imag()); +} + +void validate_matrix_component_label(const std::string& component) +{ + if (component.empty()) + { + throw std::invalid_argument( + "QSGW matrix trace component must not be empty"); + } + for (const char character : component) + { + const unsigned char byte = static_cast(character); + if (!std::isalnum(byte) && character != '_' && + character != '-' && character != '.') + { + throw std::invalid_argument( + "QSGW matrix trace component is not a machine token"); + } + } +} + +void write_matrix_rows(std::ostream& output, + const int iteration, + const IterationChannel channel, + const std::string& component, + const int spin, + const int kpoint, + const int frequency_index, + const double frequency, + const Matz& matrix) +{ + if (spin < 0 || kpoint < 0 || frequency_index < -1 || + !std::isfinite(frequency) || frequency < 0.0 || + matrix.nr() <= 0 || matrix.nc() <= 0) + { + throw std::invalid_argument( + "QSGW matrix trace layout is invalid"); + } + for (int row = 0; row < matrix.nr(); ++row) + { + for (int column = 0; column < matrix.nc(); ++column) + { + const cplxdb value = matrix(row, column); + if (!finite_complex(value)) + { + throw std::invalid_argument( + "QSGW matrix trace contains non-finite data"); + } + output << iteration << " " + << static_cast(channel) << " " + << component << " " << spin << " " << kpoint << " " + << frequency_index << " " << frequency << " " + << row << " " << column << " " + << value.real() << " " << value.imag() << "\n"; + } + } +} + +class StreamFormatGuard +{ +public: + explicit StreamFormatGuard(std::ostream& output) + : output_(output), flags_(output.flags()), precision_(output.precision()) + { + } + + ~StreamFormatGuard() + { + output_.flags(flags_); + output_.precision(precision_); + } + +private: + std::ostream& output_; + std::ios::fmtflags flags_; + std::streamsize precision_; +}; + +} // namespace + +void write_iteration_summary_header(std::ostream& output) +{ + output << "# iter max_delta_eV residual_l2_Ha residual_max_Ha " + "efermi_eV gap_eV electron_count requested_mode applied_mode beta fallback " + "rcond coefficient_l1 coefficient_count converged coefficients " + "fallback_reason\n"; +} + +void write_iteration_summary(std::ostream& output, + const IterationSummary& summary) +{ + if (summary.iteration < 0) + { + throw std::invalid_argument( + "QSGW iteration trace index must be nonnegative"); + } + require_finite_nonnegative( + summary.maximum_eigenvalue_change_ev, "maximum eigenvalue change"); + require_finite_nonnegative(summary.residual_l2_ha, "residual L2 norm"); + require_finite_nonnegative(summary.residual_max_ha, "residual max norm"); + require_finite_nonnegative(summary.gap_ev, "gap"); + require_finite_nonnegative(summary.electron_count, "electron count"); + if (!std::isfinite(summary.fermi_energy_ev) || + !(summary.beta > 0.0) || !std::isfinite(summary.beta) || + !std::isfinite(summary.reciprocal_condition) || + summary.reciprocal_condition < 0.0) + { + throw std::invalid_argument( + "QSGW iteration trace contains invalid scalar data"); + } + + double coefficient_l1 = 0.0; + for (const double coefficient : summary.coefficients) + { + if (!std::isfinite(coefficient)) + { + throw std::invalid_argument( + "QSGW iteration trace coefficient is non-finite"); + } + coefficient_l1 += std::abs(coefficient); + } + + const int requested = summary.has_mixing_decision + ? mode_code(summary.requested_mode) + : -1; + const int applied = summary.has_mixing_decision + ? mode_code(summary.applied_mode) + : -1; + StreamFormatGuard guard(output); + output << std::scientific << std::setprecision(trace_precision) + << summary.iteration << " " + << summary.maximum_eigenvalue_change_ev << " " + << summary.residual_l2_ha << " " + << summary.residual_max_ha << " " + << summary.fermi_energy_ev << " " + << summary.gap_ev << " " + << summary.electron_count << " " + << requested << " " << applied << " " + << summary.beta << " " + << (summary.fell_back ? 1 : 0) << " " + << summary.reciprocal_condition << " " + << coefficient_l1 << " " + << summary.coefficients.size() << " " + << (summary.converged ? 1 : 0) << " " + << coefficient_token(summary.coefficients) << " " + << machine_token(summary.fallback_reason) << "\n"; +} + +void write_eigenvalue_trace_header(std::ostream& output) +{ + output << "# iter channel spin kpoint kx ky kz band energy_eV\n"; +} + +void write_eigenvalue_trace( + std::ostream& output, + const int iteration, + const IterationChannel channel, + const MeanField& meanfield, + const std::vector>& kpoints) +{ + if (iteration < 0 || !meanfield.initialized() || + kpoints.size() != + static_cast(meanfield.get_n_kpoints())) + { + throw std::invalid_argument( + "QSGW eigenvalue trace layout is invalid"); + } + + StreamFormatGuard guard(output); + output << std::scientific << std::setprecision(trace_precision); + for (int spin = 0; spin < meanfield.get_n_spins(); ++spin) + { + for (int kpoint = 0; kpoint < meanfield.get_n_kpoints(); ++kpoint) + { + const auto& coordinate = + kpoints[static_cast(kpoint)]; + if (!std::isfinite(coordinate.x) || + !std::isfinite(coordinate.y) || + !std::isfinite(coordinate.z)) + { + throw std::invalid_argument( + "QSGW eigenvalue trace k point is non-finite"); + } + for (int band = 0; band < meanfield.get_n_bands(); ++band) + { + const double eigenvalue = + meanfield.get_eigenvals()[spin](kpoint, band); + if (!std::isfinite(eigenvalue)) + { + throw std::invalid_argument( + "QSGW eigenvalue trace value is non-finite"); + } + output << iteration << " " + << static_cast(channel) << " " + << spin << " " << kpoint << " " + << coordinate.x << " " << coordinate.y << " " + << coordinate.z << " " << band << " " + << eigenvalue * HA2EV << "\n"; + } + } + } +} + +void write_matrix_trace_header(std::ostream& output) +{ + output << "# iter channel component spin kpoint frequency_index " + "frequency_Ha row column real_value imag_value\n"; +} + +void write_matrix_component_trace( + std::ostream& output, + const int iteration, + const IterationChannel channel, + const std::string& component, + const SpinKMatrixMap& matrices) +{ + if (iteration < 0 || matrices.empty()) + { + throw std::invalid_argument( + "QSGW matrix component trace input is empty or invalid"); + } + validate_matrix_component_label(component); + StreamFormatGuard guard(output); + output << std::scientific << std::setprecision(trace_precision); + for (const auto& [spin, by_kpoint] : matrices) + { + if (by_kpoint.empty()) + { + throw std::invalid_argument( + "QSGW matrix component trace has an empty spin channel"); + } + for (const auto& [kpoint, matrix] : by_kpoint) + { + write_matrix_rows( + output, iteration, channel, component, spin, kpoint, + -1, 0.0, matrix); + } + } +} + +void write_frequency_matrix_component_trace( + std::ostream& output, + const int iteration, + const IterationChannel channel, + const std::string& component, + const SpinKFrequencyMatrixMap& matrices) +{ + if (iteration < 0 || matrices.empty()) + { + throw std::invalid_argument( + "QSGW frequency matrix trace input is empty or invalid"); + } + validate_matrix_component_label(component); + StreamFormatGuard guard(output); + output << std::scientific << std::setprecision(trace_precision); + for (const auto& [spin, by_kpoint] : matrices) + { + if (by_kpoint.empty()) + { + throw std::invalid_argument( + "QSGW frequency matrix trace has an empty spin channel"); + } + for (const auto& [kpoint, by_frequency] : by_kpoint) + { + if (by_frequency.empty()) + { + throw std::invalid_argument( + "QSGW frequency matrix trace has an empty k point"); + } + int frequency_index = 0; + for (const auto& [frequency, matrix] : by_frequency) + { + write_matrix_rows( + output, iteration, channel, component, spin, kpoint, + frequency_index, frequency, matrix); + ++frequency_index; + } + } + } +} + +void write_occupation_trace( + std::ostream& output, + const int iteration, + const IterationChannel channel, + const MeanField& meanfield) +{ + if (!meanfield.initialized()) + { + throw std::invalid_argument( + "QSGW occupation trace mean field is not initialized"); + } + SpinKMatrixMap matrices; + for (int spin = 0; spin < meanfield.get_n_spins(); ++spin) + { + for (int kpoint = 0; kpoint < meanfield.get_n_kpoints(); ++kpoint) + { + Matz occupations(1, meanfield.get_n_bands(), MAJOR::ROW); + for (int band = 0; band < meanfield.get_n_bands(); ++band) + { + occupations(0, band) = + meanfield.get_weight()[spin](kpoint, band); + } + matrices[spin][kpoint] = std::move(occupations); + } + } + write_matrix_component_trace( + output, iteration, channel, "occupation", matrices); +} + +void write_scalar_component_trace( + std::ostream& output, + const int iteration, + const IterationChannel channel, + const std::string& component, + const double value) +{ + if (!std::isfinite(value)) + { + throw std::invalid_argument( + "QSGW scalar matrix trace value is non-finite"); + } + SpinKMatrixMap matrices; + matrices[0][0] = Matz(1, 1, MAJOR::ROW); + matrices[0][0](0, 0) = value; + write_matrix_component_trace( + output, iteration, channel, component, matrices); +} + +void write_wavefunction_trace( + std::ostream& output, + const int iteration, + const IterationChannel channel, + const MeanField& meanfield, + const std::string& component_prefix) +{ + if (component_prefix.empty()) + { + throw std::invalid_argument( + "QSGW wavefunction trace component prefix is empty"); + } + if (!meanfield.initialized()) + { + throw std::invalid_argument( + "QSGW wavefunction trace mean field is not initialized"); + } + for (int spinor = 0; spinor < meanfield.get_n_spinor(); ++spinor) + { + SpinKMatrixMap matrices; + for (int spin = 0; spin < meanfield.get_n_spins(); ++spin) + { + for (int kpoint = 0; kpoint < meanfield.get_n_kpoints(); ++kpoint) + { + const ComplexMatrix* block = + meanfield.find_wfc(spin, spinor, kpoint); + if (block == nullptr || + block->nr != meanfield.get_n_bands() || + block->nc != meanfield.get_n_aos()) + { + throw std::invalid_argument( + "QSGW wavefunction trace map is incomplete or has an invalid shape"); + } + Matz matrix(block->nr, block->nc, MAJOR::ROW); + for (int row = 0; row < block->nr; ++row) + { + for (int column = 0; column < block->nc; ++column) + { + matrix(row, column) = (*block)(row, column); + } + } + matrices[spin][kpoint] = std::move(matrix); + } + } + write_matrix_component_trace( + output, iteration, channel, + component_prefix + "_spinor" + std::to_string(spinor), matrices); + } +} + +void write_velocity_trace( + std::ostream& output, + const int iteration, + const IterationChannel channel, + const std::vector>>& velocity, + const std::string& component_prefix) +{ + static const char* component_suffixes[3] = {"_x", "_y", "_z"}; + if (component_prefix.empty()) + { + throw std::invalid_argument( + "QSGW velocity trace component prefix is empty"); + } + if (velocity.empty()) + { + throw std::invalid_argument("QSGW velocity trace input is empty"); + } + const std::size_t kpoint_count = velocity.front().size(); + if (kpoint_count == 0) + { + throw std::invalid_argument( + "QSGW velocity trace contains no k points"); + } + for (int direction = 0; direction < 3; ++direction) + { + SpinKMatrixMap matrices; + for (std::size_t spin = 0; spin < velocity.size(); ++spin) + { + if (velocity[spin].size() != kpoint_count) + { + throw std::invalid_argument( + "QSGW velocity trace spin channels have different k-point counts"); + } + for (std::size_t kpoint = 0; kpoint < kpoint_count; ++kpoint) + { + if (velocity[spin][kpoint].size() != 3) + { + throw std::invalid_argument( + "QSGW velocity trace requires three Cartesian components"); + } + const ComplexMatrix& block = + velocity[spin][kpoint][direction]; + if (block.nr <= 0 || block.nr != block.nc) + { + throw std::invalid_argument( + "QSGW velocity trace matrix has an invalid shape"); + } + Matz matrix(block.nr, block.nc, MAJOR::ROW); + for (int row = 0; row < block.nr; ++row) + { + for (int column = 0; column < block.nc; ++column) + { + matrix(row, column) = block(row, column); + } + } + matrices[static_cast(spin)][static_cast(kpoint)] = + std::move(matrix); + } + } + write_matrix_component_trace( + output, iteration, channel, + component_prefix + component_suffixes[direction], matrices); + } +} + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/iteration_trace.h b/src/qsgw/iteration_trace.h new file mode 100644 index 00000000..3f4e3f95 --- /dev/null +++ b/src/qsgw/iteration_trace.h @@ -0,0 +1,94 @@ +#pragma once + +#include "matrix_map.h" +#include "mixing.h" + +#include "../core/meanfield.h" + +#include +#include +#include + +namespace librpa_int +{ +namespace qsgw +{ + +enum class IterationChannel +{ + Grid = 0, + Band = 1, + Headwing = 2, +}; + +struct IterationSummary +{ + int iteration = 0; + double maximum_eigenvalue_change_ev = 0.0; + double residual_l2_ha = 0.0; + double residual_max_ha = 0.0; + double fermi_energy_ev = 0.0; + double gap_ev = 0.0; + double electron_count = 0.0; + bool has_mixing_decision = true; + MixingMode requested_mode = MixingMode::Linear; + MixingMode applied_mode = MixingMode::Linear; + double beta = 0.2; + bool fell_back = false; + double reciprocal_condition = 1.0; + std::vector coefficients; + bool converged = false; + std::string fallback_reason; +}; + +void write_iteration_summary_header(std::ostream& output); +void write_iteration_summary(std::ostream& output, + const IterationSummary& summary); + +void write_eigenvalue_trace_header(std::ostream& output); +void write_eigenvalue_trace( + std::ostream& output, + int iteration, + IterationChannel channel, + const MeanField& meanfield, + const std::vector>& kpoints); + +void write_matrix_trace_header(std::ostream& output); +void write_matrix_component_trace( + std::ostream& output, + int iteration, + IterationChannel channel, + const std::string& component, + const SpinKMatrixMap& matrices); +void write_frequency_matrix_component_trace( + std::ostream& output, + int iteration, + IterationChannel channel, + const std::string& component, + const SpinKFrequencyMatrixMap& matrices); +void write_occupation_trace( + std::ostream& output, + int iteration, + IterationChannel channel, + const MeanField& meanfield); +void write_scalar_component_trace( + std::ostream& output, + int iteration, + IterationChannel channel, + const std::string& component, + double value); +void write_wavefunction_trace( + std::ostream& output, + int iteration, + IterationChannel channel, + const MeanField& meanfield, + const std::string& component_prefix = "wfc"); +void write_velocity_trace( + std::ostream& output, + int iteration, + IterationChannel channel, + const std::vector>>& velocity, + const std::string& component_prefix = "velocity"); + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/matrix_map.h b/src/qsgw/matrix_map.h new file mode 100644 index 00000000..3523bf28 --- /dev/null +++ b/src/qsgw/matrix_map.h @@ -0,0 +1,20 @@ +#pragma once + +#include "../math/matrix_m.h" +#include "../math/vector3_order.h" + +#include + +namespace librpa_int +{ +namespace qsgw +{ + +using SpinKMatrixMap = std::map>; +using SpinKFrequencyMatrixMap = + std::map>>; +using RealSpaceMatrixMap = std::map, Matz>; +using SpinRMatrixMap = std::map; + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/mixing.cpp b/src/qsgw/mixing.cpp new file mode 100644 index 00000000..e8bae83a --- /dev/null +++ b/src/qsgw/mixing.cpp @@ -0,0 +1,175 @@ +#include "mixing.h" + +#include +#include +#include + +namespace librpa_int +{ +namespace qsgw +{ +namespace +{ + +void require_same_shape(const matrix& lhs, + const matrix& rhs, + const char* channel) +{ + if (lhs.nr != rhs.nr || lhs.nc != rhs.nc) + { + throw std::invalid_argument( + std::string("QSGW ") + channel + + " mixing matrix dimensions do not match"); + } +} + +void require_finite_matrix(const matrix& value, const char* channel) +{ + if (value.nr <= 0 || value.nc <= 0) + { + throw std::invalid_argument( + std::string("QSGW ") + channel + + " mixing matrix must be non-empty"); + } + for (int index = 0; index < value.size; ++index) + { + if (!std::isfinite(value.c[index])) + { + throw std::invalid_argument( + std::string("QSGW ") + channel + + " mixing matrix contains non-finite data"); + } + } +} + +double residual_l2_norm(const matrix& residual) +{ + double squared_norm = 0.0; + for (int index = 0; index < residual.size; ++index) + { + squared_norm += residual.c[index] * residual.c[index]; + } + return std::sqrt(squared_norm); +} + +double residual_max_norm(const matrix& residual) +{ + double result = 0.0; + for (int index = 0; index < residual.size; ++index) + { + result = std::max(result, std::abs(residual.c[index])); + } + return result; +} + +matrix linear_mix(const matrix& input, + const matrix& output, + const double beta) +{ + matrix residual = output - input; + residual *= beta; + return input + residual; +} + +} // namespace + +HamiltonianMixer::HamiltonianMixer(MixingOptions options) + : options_(options) +{ + if (options_.mode != MixingMode::Linear) + { + throw std::invalid_argument( + "QSGW supports only linear Hamiltonian mixing"); + } + if (!(options_.beta > 0.0 && options_.beta <= 1.0) || + !std::isfinite(options_.beta)) + { + throw std::invalid_argument( + "QSGW mixing beta must be finite and in (0, 1]"); + } +} + +void HamiltonianMixer::initialize(const matrix& grid_input) +{ + require_finite_matrix(grid_input, "grid input"); + grid_input_ = grid_input; + band_input_.reset(); +} + +void HamiltonianMixer::initialize(const matrix& grid_input, + const matrix& band_input) +{ + require_finite_matrix(grid_input, "grid input"); + require_finite_matrix(band_input, "band input"); + grid_input_ = grid_input; + band_input_ = band_input; +} + +HamiltonianMixResult HamiltonianMixer::mix(const matrix& grid_output) +{ + return mix_impl(grid_output, std::nullopt); +} + +HamiltonianMixResult HamiltonianMixer::mix( + const matrix& grid_output, + const matrix& band_output) +{ + return mix_impl(grid_output, band_output); +} + +HamiltonianMixResult HamiltonianMixer::mix_impl( + const matrix& grid_output, + const std::optional& band_output) +{ + if (!grid_input_) + { + throw std::logic_error( + "QSGW Hamiltonian mixer must be initialized before use"); + } + require_same_shape(*grid_input_, grid_output, "grid"); + require_finite_matrix(grid_output, "grid output"); + if (band_input_.has_value() != band_output.has_value()) + { + throw std::invalid_argument( + "QSGW band mixing input/output presence does not match"); + } + if (band_input_) + { + require_same_shape(*band_input_, *band_output, "band"); + require_finite_matrix(*band_output, "band output"); + } + + const matrix grid_residual = grid_output - *grid_input_; + require_finite_matrix(grid_residual, "grid residual"); + const double residual_l2 = residual_l2_norm(grid_residual); + const double residual_max = residual_max_norm(grid_residual); + if (!std::isfinite(residual_l2) || !std::isfinite(residual_max)) + { + throw std::invalid_argument( + "QSGW grid residual norm is not finite"); + } + + matrix mixed_grid = + linear_mix(*grid_input_, grid_output, options_.beta); + require_finite_matrix(mixed_grid, "mixed grid"); + std::optional mixed_band; + if (band_input_) + { + mixed_band = + linear_mix(*band_input_, *band_output, options_.beta); + require_finite_matrix(*mixed_band, "mixed band"); + } + + MixingDecision decision; + decision.requested_mode = MixingMode::Linear; + decision.applied_mode = MixingMode::Linear; + decision.beta = options_.beta; + decision.coefficients = {1.0}; + + grid_input_ = mixed_grid; + band_input_ = mixed_band; + return {mixed_grid, mixed_band, decision, residual_l2, residual_max}; +} + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/mixing.h b/src/qsgw/mixing.h new file mode 100644 index 00000000..8203131d --- /dev/null +++ b/src/qsgw/mixing.h @@ -0,0 +1,68 @@ +#pragma once + +#include "../math/matrix.h" + +#include +#include +#include + +namespace librpa_int +{ +namespace qsgw +{ + +enum class MixingMode +{ + Linear, +}; + +struct MixingOptions +{ + MixingMode mode = MixingMode::Linear; + double beta = 0.2; +}; + +struct MixingDecision +{ + MixingMode requested_mode = MixingMode::Linear; + MixingMode applied_mode = MixingMode::Linear; + double beta = 0.2; + bool fell_back = false; + std::string fallback_reason; + double reciprocal_condition = 1.0; + std::vector coefficients; +}; + +struct HamiltonianMixResult +{ + matrix grid; + std::optional band; + MixingDecision decision; + double residual_l2 = 0.0; + double residual_max = 0.0; +}; + +class HamiltonianMixer +{ +public: + explicit HamiltonianMixer(MixingOptions options = {}); + + void initialize(const matrix& grid_input); + void initialize(const matrix& grid_input, const matrix& band_input); + + HamiltonianMixResult mix(const matrix& grid_output); + HamiltonianMixResult mix(const matrix& grid_output, + const matrix& band_output); + +private: + MixingOptions options_; + std::optional grid_input_; + std::optional band_input_; + + HamiltonianMixResult mix_impl( + const matrix& grid_output, + const std::optional& band_output); +}; + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/occupation.cpp b/src/qsgw/occupation.cpp new file mode 100644 index 00000000..2d31db60 --- /dev/null +++ b/src/qsgw/occupation.cpp @@ -0,0 +1,353 @@ +#include "occupation.h" + +#include +#include +#include +#include +#include +#include + +namespace librpa_int +{ +namespace qsgw +{ + +namespace +{ + +struct State +{ + double energy; + double capacity; + int spin; + int kpoint; + int band; +}; + +void validate_same_layout(const MeanField& live, const MeanField& reference) +{ + if (!live.initialized() || !reference.initialized()) + { + throw std::invalid_argument("QSGW occupation mean fields must be initialized"); + } + if (live.get_n_spins() != reference.get_n_spins() || + live.get_n_kpoints() != reference.get_n_kpoints() || + live.get_n_bands() != reference.get_n_bands() || + live.get_n_spinor() != reference.get_n_spinor()) + { + throw std::invalid_argument("QSGW live and reference mean-field layouts differ"); + } +} + +double state_capacity_factor(const MeanField& meanfield) +{ + return 2.0 / + static_cast(meanfield.get_n_spins() * meanfield.get_n_spinor()); +} + +void validate_kpoint_weights(const MeanField& meanfield, + const std::vector& kpoint_weights, + const double tolerance) +{ + if (!meanfield.initialized()) + { + throw std::invalid_argument("QSGW occupation mean field must be initialized"); + } + if (!std::isfinite(tolerance) || !(tolerance > 0.0)) + { + throw std::invalid_argument("QSGW occupation tolerance must be finite and positive"); + } + if (kpoint_weights.size() != + static_cast(meanfield.get_n_kpoints())) + { + throw std::invalid_argument("QSGW k-point weight count does not match mean field"); + } + + double weight_sum = 0.0; + for (const double weight: kpoint_weights) + { + if (!std::isfinite(weight) || weight < 0.0) + { + throw std::invalid_argument( + "QSGW k-point weights must be finite and nonnegative"); + } + weight_sum += weight; + } + if (!std::isfinite(weight_sum) || + std::abs(weight_sum - 1.0) > tolerance) + { + throw std::invalid_argument("QSGW k-point weights must sum to one"); + } +} + +} // namespace + +double physical_electron_count( + const MeanField& meanfield, + const std::vector& kpoint_weights, + const double tolerance) +{ + validate_kpoint_weights(meanfield, kpoint_weights, tolerance); + const double capacity_factor = state_capacity_factor(meanfield); + + double result = 0.0; + for (int spin = 0; spin < meanfield.get_n_spins(); ++spin) + { + for (int kpoint = 0; kpoint < meanfield.get_n_kpoints(); ++kpoint) + { + const double state_capacity = + capacity_factor * kpoint_weights[kpoint]; + for (int band = 0; band < meanfield.get_n_bands(); ++band) + { + const double stored_weight = + meanfield.get_weight()[spin](kpoint, band); + if (!std::isfinite(stored_weight) || + stored_weight < -tolerance || + stored_weight > state_capacity + tolerance) + { + throw std::invalid_argument( + "QSGW occupation exceeds the MeanField k-weighted state capacity"); + } + result += stored_weight; + } + } + } + if (!std::isfinite(result)) + { + throw std::invalid_argument("QSGW physical electron count is not finite"); + } + return result; +} + +OccupationResult analyze_qsgw_occupations( + const MeanField& meanfield, + const std::vector& kpoint_weights, + const double total_electrons, + const OccupationSettings& settings) +{ + MeanField probe = meanfield; + return update_qsgw_occupations( + probe, meanfield, kpoint_weights, total_electrons, settings); +} + +OccupationResult update_qsgw_occupations( + MeanField& live_meanfield, + const MeanField& reference_meanfield, + const std::vector& kpoint_weights, + const double total_electrons, + const OccupationSettings& settings) +{ + validate_same_layout(live_meanfield, reference_meanfield); + if (&live_meanfield == &reference_meanfield) + { + throw std::invalid_argument( + "QSGW live and immutable reference mean fields must not alias"); + } + if (settings.temperature_kelvin != 0.0) + { + throw std::invalid_argument("Finite-temperature QSGW occupations are not implemented"); + } + if (!std::isfinite(settings.degeneracy_tolerance_ha) || + !std::isfinite(settings.electron_tolerance) || + !(settings.degeneracy_tolerance_ha > 0.0) || + !(settings.electron_tolerance > 0.0)) + { + throw std::invalid_argument("QSGW occupation tolerances must be finite and positive"); + } + validate_kpoint_weights( + live_meanfield, kpoint_weights, settings.electron_tolerance); + + const double capacity_factor = state_capacity_factor(reference_meanfield); + const double reference_electrons = physical_electron_count( + reference_meanfield, kpoint_weights, settings.electron_tolerance); + double total_capacity = 0.0; + std::vector states; + states.reserve(static_cast(live_meanfield.get_n_spins()) * + live_meanfield.get_n_kpoints() * live_meanfield.get_n_bands()); + + for (int spin = 0; spin < live_meanfield.get_n_spins(); ++spin) + { + for (int kpoint = 0; kpoint < live_meanfield.get_n_kpoints(); ++kpoint) + { + const double state_capacity = + capacity_factor * kpoint_weights[kpoint]; + for (int band = 0; band < live_meanfield.get_n_bands(); ++band) + { + const double energy = + live_meanfield.get_eigenvals()[spin](kpoint, band); + if (!std::isfinite(energy)) + { + throw std::invalid_argument( + "QSGW eigenvalues must be finite before occupation filling"); + } + total_capacity += state_capacity; + states.push_back( + {energy, state_capacity, spin, kpoint, band}); + } + } + } + + if (!std::isfinite(total_electrons) || total_electrons < 0.0 || + total_electrons > total_capacity + settings.electron_tolerance) + { + throw std::invalid_argument("QSGW target electron count is outside state capacity"); + } + if (!std::isfinite(reference_electrons) || !std::isfinite(total_capacity)) + { + throw std::invalid_argument( + "QSGW reference electron count or state capacity is not finite"); + } + if (std::abs(reference_electrons - total_electrons) > settings.electron_tolerance) + { + throw std::invalid_argument( + "QSGW target electron count differs from immutable reference occupations"); + } + + std::sort(states.begin(), states.end(), [](const State& lhs, const State& rhs) { + if (lhs.energy != rhs.energy) + { + return lhs.energy < rhs.energy; + } + if (lhs.spin != rhs.spin) + { + return lhs.spin < rhs.spin; + } + if (lhs.kpoint != rhs.kpoint) + { + return lhs.kpoint < rhs.kpoint; + } + return lhs.band < rhs.band; + }); + + auto updated_weights = live_meanfield.get_weight(); + for (auto& spin_weights: updated_weights) + { + spin_weights.zero_out(); + } + double remaining = total_electrons; + for (std::size_t begin = 0; begin < states.size();) + { + std::size_t end = begin + 1; + while (end < states.size() && + std::abs(states[end].energy - states[begin].energy) <= + settings.degeneracy_tolerance_ha) + { + ++end; + } + + double group_capacity = 0.0; + for (std::size_t index = begin; index < end; ++index) + { + group_capacity += states[index].capacity; + } + const double fraction = group_capacity > 0.0 + ? std::clamp(remaining / group_capacity, 0.0, 1.0) + : 0.0; + for (std::size_t index = begin; index < end; ++index) + { + const State& state = states[index]; + updated_weights[state.spin](state.kpoint, state.band) = + fraction * state.capacity; + } + remaining -= fraction * group_capacity; + if (remaining < settings.electron_tolerance) + { + remaining = 0.0; + } + begin = end; + } + if (remaining > settings.electron_tolerance) + { + throw std::runtime_error("QSGW global occupation filling did not conserve charge"); + } + + OccupationResult result; + double filling_vbm = -std::numeric_limits::infinity(); + double filling_cbm = std::numeric_limits::infinity(); + double lowest_active_energy = std::numeric_limits::infinity(); + double highest_active_energy = -std::numeric_limits::infinity(); + for (const State& state: states) + { + const double occupation = + updated_weights[state.spin](state.kpoint, state.band); + result.electron_count += occupation; + if (state.capacity <= settings.electron_tolerance) + { + continue; + } + lowest_active_energy = std::min(lowest_active_energy, state.energy); + highest_active_energy = std::max(highest_active_energy, state.energy); + if (occupation > settings.electron_tolerance) + { + filling_vbm = std::max(filling_vbm, state.energy); + } + if (occupation < state.capacity - settings.electron_tolerance) + { + filling_cbm = std::min(filling_cbm, state.energy); + } + if (occupation > settings.electron_tolerance && + occupation < state.capacity - settings.electron_tolerance) + { + result.metallic = true; + } + } + + if (!std::isfinite(filling_vbm)) + { + filling_vbm = std::isfinite(lowest_active_energy) + ? lowest_active_energy + : states.front().energy; + } + if (!std::isfinite(filling_cbm)) + { + filling_cbm = std::isfinite(highest_active_energy) + ? highest_active_energy + : states.back().energy; + } + result.chemical_potential = + 0.5 * (filling_vbm + filling_cbm); + + result.vbm = -std::numeric_limits::infinity(); + result.cbm = std::numeric_limits::infinity(); + for (const State& state: states) + { + const double reference_occupation = + reference_meanfield.get_weight()[state.spin]( + state.kpoint, state.band); + if (reference_occupation > settings.electron_tolerance) + { + result.vbm = std::max(result.vbm, state.energy); + } + if (reference_occupation < + state.capacity - settings.electron_tolerance) + { + result.cbm = std::min(result.cbm, state.energy); + } + if (reference_occupation > settings.electron_tolerance && + reference_occupation < + state.capacity - settings.electron_tolerance) + { + result.metallic = true; + } + } + if (!std::isfinite(result.vbm)) result.vbm = filling_vbm; + if (!std::isfinite(result.cbm)) result.cbm = filling_cbm; + const double manifold_gap = result.cbm - result.vbm; + if (manifold_gap <= settings.degeneracy_tolerance_ha) + { + result.metallic = true; + } + result.gap = result.metallic ? 0.0 : manifold_gap; + + if (std::abs(result.electron_count - total_electrons) > + settings.electron_tolerance) + { + throw std::runtime_error("QSGW updated occupations do not conserve charge"); + } + live_meanfield.get_weight() = std::move(updated_weights); + live_meanfield.get_efermi() = result.chemical_potential; + return result; +} + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/occupation.h b/src/qsgw/occupation.h new file mode 100644 index 00000000..a46f975b --- /dev/null +++ b/src/qsgw/occupation.h @@ -0,0 +1,48 @@ +#pragma once + +#include "../core/meanfield.h" + +#include + +namespace librpa_int +{ +namespace qsgw +{ + +struct OccupationSettings +{ + double temperature_kelvin = 0.0; + double degeneracy_tolerance_ha = 1.0e-10; + double electron_tolerance = 1.0e-12; +}; + +struct OccupationResult +{ + double chemical_potential = 0.0; + double electron_count = 0.0; + double vbm = 0.0; + double cbm = 0.0; + double gap = 0.0; + bool metallic = false; +}; + +double physical_electron_count( + const MeanField& meanfield, + const std::vector& kpoint_weights, + double tolerance = 1.0e-12); + +OccupationResult analyze_qsgw_occupations( + const MeanField& meanfield, + const std::vector& kpoint_weights, + double total_electrons, + const OccupationSettings& settings = {}); + +OccupationResult update_qsgw_occupations( + MeanField& live_meanfield, + const MeanField& reference_meanfield, + const std::vector& kpoint_weights, + double total_electrons, + const OccupationSettings& settings = {}); + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/projection_target.cpp b/src/qsgw/projection_target.cpp new file mode 100644 index 00000000..76701086 --- /dev/null +++ b/src/qsgw/projection_target.cpp @@ -0,0 +1,88 @@ +#include "projection_target.h" + +#include +#include + +namespace librpa_int +{ +namespace qsgw +{ + +ProjectionTargetShape validate_projection_target( + const MeanField& reference, + const std::vector>& kpoints, + const int expected_n_spins, + const int expected_n_spinors, + const int expected_n_aos, + const std::string& label) +{ + if (!reference.initialized()) + { + throw std::invalid_argument( + "QSGW " + label + " projection mean field is not initialized"); + } + if (reference.get_n_spins() != expected_n_spins || + reference.get_n_spinor() != expected_n_spinors || + reference.get_n_aos() != expected_n_aos) + { + throw std::invalid_argument( + "QSGW " + label + + " projection target is incompatible with the source basis"); + } + if (reference.get_n_kpoints() != static_cast(kpoints.size())) + { + throw std::invalid_argument( + "QSGW " + label + + " projection k-point coordinates do not match the mean field"); + } + + for (const auto& kpoint : kpoints) + { + if (!std::isfinite(kpoint.x) || !std::isfinite(kpoint.y) || + !std::isfinite(kpoint.z)) + { + throw std::invalid_argument( + "QSGW " + label + + " projection contains a non-finite k-point"); + } + } + + for (int spin = 0; spin < reference.get_n_spins(); ++spin) + { + for (int spinor = 0; spinor < reference.get_n_spinor(); ++spinor) + { + for (int kpoint = 0; kpoint < reference.get_n_kpoints(); ++kpoint) + { + const ComplexMatrix* wavefunction = + reference.find_wfc(spin, spinor, kpoint); + if (wavefunction == nullptr || + wavefunction->nr != reference.get_n_bands() || + wavefunction->nc != reference.get_n_aos()) + { + throw std::invalid_argument( + "QSGW " + label + + " projection wavefunction map is incomplete or malformed"); + } + for (int index = 0; index < wavefunction->size; ++index) + { + const cplxdb value = wavefunction->c[index]; + if (!std::isfinite(value.real()) || + !std::isfinite(value.imag())) + { + throw std::invalid_argument( + "QSGW " + label + + " projection wavefunction contains non-finite data"); + } + } + } + } + } + + return { + reference.get_n_spins(), reference.get_n_spinor(), + reference.get_n_kpoints(), reference.get_n_bands(), + reference.get_n_aos()}; +} + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/projection_target.h b/src/qsgw/projection_target.h new file mode 100644 index 00000000..cd7b5310 --- /dev/null +++ b/src/qsgw/projection_target.h @@ -0,0 +1,32 @@ +#pragma once + +#include "../core/meanfield.h" +#include "../math/vector3_order.h" + +#include +#include + +namespace librpa_int +{ +namespace qsgw +{ + +struct ProjectionTargetShape +{ + int n_spins = 0; + int n_spinors = 0; + int n_kpoints = 0; + int n_bands = 0; + int n_aos = 0; +}; + +ProjectionTargetShape validate_projection_target( + const MeanField& reference, + const std::vector>& kpoints, + int expected_n_spins, + int expected_n_spinors, + int expected_n_aos, + const std::string& label); + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/sha256.cpp b/src/qsgw/sha256.cpp new file mode 100644 index 00000000..06518b01 --- /dev/null +++ b/src/qsgw/sha256.cpp @@ -0,0 +1,215 @@ +#include "sha256.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace librpa_int +{ +namespace qsgw +{ +namespace +{ + +constexpr std::array round_constants{ + 0x428a2f98U, 0x71374491U, 0xb5c0fbcfU, 0xe9b5dba5U, + 0x3956c25bU, 0x59f111f1U, 0x923f82a4U, 0xab1c5ed5U, + 0xd807aa98U, 0x12835b01U, 0x243185beU, 0x550c7dc3U, + 0x72be5d74U, 0x80deb1feU, 0x9bdc06a7U, 0xc19bf174U, + 0xe49b69c1U, 0xefbe4786U, 0x0fc19dc6U, 0x240ca1ccU, + 0x2de92c6fU, 0x4a7484aaU, 0x5cb0a9dcU, 0x76f988daU, + 0x983e5152U, 0xa831c66dU, 0xb00327c8U, 0xbf597fc7U, + 0xc6e00bf3U, 0xd5a79147U, 0x06ca6351U, 0x14292967U, + 0x27b70a85U, 0x2e1b2138U, 0x4d2c6dfcU, 0x53380d13U, + 0x650a7354U, 0x766a0abbU, 0x81c2c92eU, 0x92722c85U, + 0xa2bfe8a1U, 0xa81a664bU, 0xc24b8b70U, 0xc76c51a3U, + 0xd192e819U, 0xd6990624U, 0xf40e3585U, 0x106aa070U, + 0x19a4c116U, 0x1e376c08U, 0x2748774cU, 0x34b0bcb5U, + 0x391c0cb3U, 0x4ed8aa4aU, 0x5b9cca4fU, 0x682e6ff3U, + 0x748f82eeU, 0x78a5636fU, 0x84c87814U, 0x8cc70208U, + 0x90befffaU, 0xa4506cebU, 0xbef9a3f7U, 0xc67178f2U, +}; + +std::uint32_t rotate_right(const std::uint32_t value, + const unsigned int shift) +{ + return (value >> shift) | (value << (32U - shift)); +} + +class Sha256 +{ +public: + void update(const unsigned char* data, const std::size_t size) + { + total_bytes_ += static_cast(size); + for (std::size_t index = 0; index < size; ++index) + { + buffer_[buffer_size_++] = data[index]; + if (buffer_size_ == buffer_.size()) + { + transform(buffer_.data()); + buffer_size_ = 0; + } + } + } + + std::string finish() + { + const std::uint64_t bit_length = total_bytes_ * 8U; + buffer_[buffer_size_++] = 0x80U; + if (buffer_size_ > 56) + { + while (buffer_size_ < buffer_.size()) buffer_[buffer_size_++] = 0; + transform(buffer_.data()); + buffer_size_ = 0; + } + while (buffer_size_ < 56) buffer_[buffer_size_++] = 0; + for (int shift = 56; shift >= 0; shift -= 8) + { + buffer_[buffer_size_++] = + static_cast((bit_length >> shift) & 0xffU); + } + transform(buffer_.data()); + + std::ostringstream output; + output << std::hex << std::setfill('0'); + for (const std::uint32_t word : state_) + { + output << std::setw(8) << word; + } + return output.str(); + } + +private: + void transform(const unsigned char* block) + { + std::array words{}; + for (std::size_t index = 0; index < 16; ++index) + { + const std::size_t offset = index * 4; + words[index] = + (static_cast(block[offset]) << 24U) | + (static_cast(block[offset + 1]) << 16U) | + (static_cast(block[offset + 2]) << 8U) | + static_cast(block[offset + 3]); + } + for (std::size_t index = 16; index < words.size(); ++index) + { + const std::uint32_t sigma0 = + rotate_right(words[index - 15], 7) ^ + rotate_right(words[index - 15], 18) ^ + (words[index - 15] >> 3U); + const std::uint32_t sigma1 = + rotate_right(words[index - 2], 17) ^ + rotate_right(words[index - 2], 19) ^ + (words[index - 2] >> 10U); + words[index] = words[index - 16] + sigma0 + + words[index - 7] + sigma1; + } + + std::uint32_t a = state_[0]; + std::uint32_t b = state_[1]; + std::uint32_t c = state_[2]; + std::uint32_t d = state_[3]; + std::uint32_t e = state_[4]; + std::uint32_t f = state_[5]; + std::uint32_t g = state_[6]; + std::uint32_t h = state_[7]; + for (std::size_t index = 0; index < words.size(); ++index) + { + const std::uint32_t sum1 = rotate_right(e, 6) ^ + rotate_right(e, 11) ^ + rotate_right(e, 25); + const std::uint32_t choice = (e & f) ^ ((~e) & g); + const std::uint32_t temp1 = h + sum1 + choice + + round_constants[index] + words[index]; + const std::uint32_t sum0 = rotate_right(a, 2) ^ + rotate_right(a, 13) ^ + rotate_right(a, 22); + const std::uint32_t majority = (a & b) ^ (a & c) ^ (b & c); + const std::uint32_t temp2 = sum0 + majority; + h = g; + g = f; + f = e; + e = d + temp1; + d = c; + c = b; + b = a; + a = temp1 + temp2; + } + state_[0] += a; + state_[1] += b; + state_[2] += c; + state_[3] += d; + state_[4] += e; + state_[5] += f; + state_[6] += g; + state_[7] += h; + } + + std::array state_{ + 0x6a09e667U, 0xbb67ae85U, 0x3c6ef372U, 0xa54ff53aU, + 0x510e527fU, 0x9b05688cU, 0x1f83d9abU, 0x5be0cd19U, + }; + std::array buffer_{}; + std::size_t buffer_size_ = 0; + std::uint64_t total_bytes_ = 0; +}; + +} // namespace + +std::string sha256_string(const std::string_view input) +{ + Sha256 digest; + digest.update(reinterpret_cast(input.data()), + input.size()); + return digest.finish(); +} + +std::string sha256_file(const std::string& path) +{ + std::ifstream input(path, std::ios::binary); + if (!input) + { + throw std::runtime_error("Cannot open QSGW input for SHA256: " + path); + } + Sha256 digest; + std::vector buffer(1024 * 1024); + while (input) + { + input.read(buffer.data(), static_cast(buffer.size())); + const std::streamsize count = input.gcount(); + if (count > 0) + { + digest.update( + reinterpret_cast(buffer.data()), + static_cast(count)); + } + } + if (!input.eof()) + { + throw std::runtime_error("Cannot read QSGW input for SHA256: " + path); + } + return digest.finish(); +} + +bool is_sha256_hex(const std::string_view digest) noexcept +{ + if (digest.size() != 64) return false; + for (const char value : digest) + { + if (!((value >= '0' && value <= '9') || + (value >= 'a' && value <= 'f'))) + { + return false; + } + } + return true; +} + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/sha256.h b/src/qsgw/sha256.h new file mode 100644 index 00000000..8c5e1842 --- /dev/null +++ b/src/qsgw/sha256.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +namespace librpa_int +{ +namespace qsgw +{ + +std::string sha256_string(std::string_view input); +std::string sha256_file(const std::string& path); +bool is_sha256_hex(std::string_view digest) noexcept; + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/vxc_io.cpp b/src/qsgw/vxc_io.cpp new file mode 100644 index 00000000..15dd7460 --- /dev/null +++ b/src/qsgw/vxc_io.cpp @@ -0,0 +1,656 @@ +#include "vxc_io.h" +#include "sha256.h" + +#include +#include +#include +#include +#include +#include + +namespace librpa_int +{ +namespace qsgw +{ +namespace +{ + +constexpr double hermitian_tolerance = 1.0e-10; + +std::string trim(std::string value) +{ + value.erase( + value.begin(), + std::find_if(value.begin(), value.end(), [](const unsigned char ch) { + return !std::isspace(ch); + })); + value.erase( + std::find_if(value.rbegin(), value.rend(), [](const unsigned char ch) { + return !std::isspace(ch); + }).base(), + value.end()); + return value; +} + +std::string lowercase(std::string value) +{ + std::transform(value.begin(), value.end(), value.begin(), + [](const unsigned char ch) { + return static_cast(std::tolower(ch)); + }); + return value; +} + +bool finite_complex(const cplxdb value) +{ + return std::isfinite(value.real()) && std::isfinite(value.imag()); +} + +void validate_hermitian_matrix(const Matz& matrix, + const int expected_dimension, + const std::string& label) +{ + if (matrix.nr() != expected_dimension || + matrix.nc() != expected_dimension) + { + throw std::invalid_argument(label + " has an invalid shape"); + } + for (int row = 0; row < matrix.nr(); ++row) + { + for (int column = 0; column < matrix.nc(); ++column) + { + if (!finite_complex(matrix(row, column))) + { + throw std::invalid_argument(label + " contains non-finite data"); + } + if (std::abs(matrix(row, column) - + std::conj(matrix(column, row))) > + hermitian_tolerance) + { + throw std::invalid_argument(label + " is not Hermitian"); + } + } + } +} + +std::vector parse_complex_values(const std::string& line, + const std::string& source_name) +{ + std::vector result; + std::size_t position = 0; + while (true) + { + const std::size_t left = line.find('(', position); + if (left == std::string::npos) break; + const std::size_t comma = line.find(',', left + 1); + const std::size_t right = + line.find(')', comma == std::string::npos ? left + 1 : comma + 1); + if (comma == std::string::npos || right == std::string::npos) + { + throw std::invalid_argument( + "Malformed ABACUS complex value in " + source_name); + } + try + { + const double real = std::stod( + trim(line.substr(left + 1, comma - left - 1))); + const double imaginary = std::stod( + trim(line.substr(comma + 1, right - comma - 1))); + result.emplace_back(real, imaginary); + } + catch (const std::exception&) + { + throw std::invalid_argument( + "Invalid ABACUS complex value in " + source_name); + } + position = right + 1; + } + return result; +} + +void validate_reference_wfc(const MeanField& reference, + const int spin, + const int kpoint) +{ + if (!reference.initialized() || spin < 0 || + spin >= reference.get_n_spins() || kpoint < 0 || + kpoint >= reference.get_n_kpoints()) + { + throw std::invalid_argument( + "QSGW Vxc projection received an invalid mean-field index"); + } + for (int spinor = 0; spinor < reference.get_n_spinor(); ++spinor) + { + const ComplexMatrix* wfc = reference.find_wfc(spin, spinor, kpoint); + if (wfc == nullptr || wfc->nr != reference.get_n_bands() || + wfc->nc != reference.get_n_aos()) + { + throw std::invalid_argument( + "QSGW Vxc projection reference wavefunction is incomplete"); + } + for (int index = 0; index < wfc->size; ++index) + { + if (!finite_complex(wfc->c[index])) + { + throw std::invalid_argument( + "QSGW Vxc projection wavefunction contains non-finite data"); + } + } + } +} + +double periodic_distance(const double lhs, const double rhs) +{ + const double difference = lhs - rhs; + return std::abs(difference - std::round(difference)); +} + +} // namespace + +VxcManifest VxcManifest::parse(std::istream& input, + const std::string& source_name) +{ + VxcManifest result; + std::map metadata; + bool saw_magic = false; + bool saw_table_header = false; + std::string line; + while (std::getline(input, line)) + { + const std::string content = trim(line); + if (content.empty()) continue; + if (!saw_magic) + { + if (content != "# librpa-qsgw-vxc-manifest-v2") + { + throw std::invalid_argument( + "Invalid QSGW Vxc manifest header in " + source_name); + } + saw_magic = true; + continue; + } + if (!saw_table_header) + { + std::istringstream fields(content); + std::string first; + fields >> first; + if (lowercase(first) == "spin") + { + std::vector header; + header.push_back(first); + std::string field; + while (fields >> field) header.push_back(field); + const std::vector expected{ + "spin", "k_index", "kx", "ky", "kz", "rows", + "columns", "sha256", "file"}; + if (header.size() != expected.size()) + { + throw std::invalid_argument( + "Invalid QSGW Vxc manifest table header in " + + source_name); + } + for (std::size_t index = 0; index < expected.size(); ++index) + { + if (lowercase(header[index]) != expected[index]) + { + throw std::invalid_argument( + "Invalid QSGW Vxc manifest table header in " + + source_name); + } + } + saw_table_header = true; + continue; + } + + std::string value; + fields >> value; + std::string extra; + if (first.empty() || value.empty() || fields >> extra) + { + throw std::invalid_argument( + "Malformed QSGW Vxc manifest metadata in " + source_name); + } + first = lowercase(first); + if (!metadata.emplace(first, value).second) + { + throw std::invalid_argument( + "Duplicate QSGW Vxc manifest metadata in " + source_name); + } + continue; + } + + VxcManifestEntry entry; + std::istringstream fields(content); + int spin_one_based = 0; + int kpoint_one_based = 0; + if (!(fields >> spin_one_based >> kpoint_one_based >> entry.kpoint.x >> + entry.kpoint.y >> entry.kpoint.z >> entry.rows >> + entry.columns >> entry.sha256 >> entry.file)) + { + throw std::invalid_argument( + "Malformed QSGW Vxc manifest entry in " + source_name); + } + std::string extra; + if (fields >> extra || spin_one_based <= 0 || kpoint_one_based <= 0 || + entry.rows <= 0 || entry.columns <= 0 || + !is_sha256_hex(entry.sha256) || entry.file.empty() || + std::filesystem::path(entry.file).is_absolute() || + entry.file.find('\\') != std::string::npos || + !std::isfinite(entry.kpoint.x) || + !std::isfinite(entry.kpoint.y) || + !std::isfinite(entry.kpoint.z)) + { + throw std::invalid_argument( + "Invalid QSGW Vxc manifest entry in " + source_name); + } + for (const auto& component : std::filesystem::path(entry.file)) + { + if (component == "..") + { + throw std::invalid_argument( + "QSGW Vxc manifest file escapes its base directory in " + + source_name); + } + } + entry.spin = spin_one_based - 1; + entry.k_index = kpoint_one_based - 1; + if (!result.entries_ + .emplace(std::make_pair(entry.spin, entry.k_index), entry) + .second) + { + throw std::invalid_argument( + "Duplicate spin/k entry in QSGW Vxc manifest " + source_name); + } + } + + if (!saw_magic || !saw_table_header || result.entries_.empty()) + { + throw std::invalid_argument( + "Incomplete QSGW Vxc manifest " + source_name); + } + for (const char* key : {"kind", "producer", "units", "basis", "gauge"}) + { + if (metadata.count(key) == 0) + { + throw std::invalid_argument( + "Missing QSGW Vxc manifest metadata in " + source_name); + } + } + if (metadata.size() != 5) + { + throw std::invalid_argument( + "Unknown QSGW Vxc manifest metadata in " + source_name); + } + + const std::string kind = lowercase(metadata.at("kind")); + if (kind == "scf") + result.kind_ = VxcDatasetKind::ScfGrid; + else if (kind == "band") + result.kind_ = VxcDatasetKind::BandPath; + else + throw std::invalid_argument( + "Unsupported QSGW Vxc manifest kind in " + source_name); + + result.producer_ = lowercase(metadata.at("producer")); + const std::string units = lowercase(metadata.at("units")); + const std::string basis = lowercase(metadata.at("basis")); + const std::string gauge = lowercase(metadata.at("gauge")); + if (units == "ha" || units == "hartree") + result.units_ = VxcUnits::Hartree; + else if (units == "ry" || units == "rydberg") + result.units_ = VxcUnits::Rydberg; + else + throw std::invalid_argument( + "Unsupported QSGW Vxc units in " + source_name); + if (basis == "nao") + result.basis_ = VxcBasis::Nao; + else if (basis == "state") + result.basis_ = VxcBasis::State; + else + throw std::invalid_argument( + "Unsupported QSGW Vxc basis in " + source_name); + if (gauge == "ao_bloch") + result.gauge_ = VxcGauge::AoBloch; + else if (gauge == "mf0_state") + result.gauge_ = VxcGauge::Mf0State; + else + throw std::invalid_argument( + "Unsupported QSGW Vxc gauge in " + source_name); + + const bool valid_abacus_state = + result.producer_ == "abacus" && + result.units_ == VxcUnits::Rydberg && + result.basis_ == VxcBasis::State && + result.gauge_ == VxcGauge::Mf0State; + const bool valid_abacus_nao = + result.producer_ == "abacus" && + result.units_ == VxcUnits::Rydberg && + result.basis_ == VxcBasis::Nao && + result.gauge_ == VxcGauge::AoBloch; + const bool valid_aims = + result.producer_ == "fhi-aims" && + result.units_ == VxcUnits::Hartree && + result.basis_ == VxcBasis::State && + result.gauge_ == VxcGauge::Mf0State; + if (!valid_abacus_state && !valid_abacus_nao && !valid_aims) + { + throw std::invalid_argument( + "Incompatible QSGW Vxc producer, units, basis, or gauge in " + + source_name); + } + return result; +} + +void VxcManifest::validate( + const VxcDatasetKind expected_kind, + const int spin_count, + const std::vector>& expected_kpoints, + const int expected_rows, + const int expected_columns, + const double tolerance) const +{ + if (kind_ != expected_kind || spin_count <= 0 || + expected_kpoints.empty() || expected_rows <= 0 || + expected_columns <= 0 || !(tolerance > 0.0) || + !std::isfinite(tolerance) || + entries_.size() != + static_cast(spin_count) * expected_kpoints.size()) + { + throw std::invalid_argument( + "QSGW Vxc manifest does not match the requested dataset"); + } + for (int spin = 0; spin < spin_count; ++spin) + { + for (std::size_t kpoint = 0; kpoint < expected_kpoints.size(); ++kpoint) + { + const VxcManifestEntry& entry = + at(spin, static_cast(kpoint)); + const Vector3_Order& expected = expected_kpoints[kpoint]; + if (entry.rows != expected_rows || + entry.columns != expected_columns || + periodic_distance(entry.kpoint.x, expected.x) > tolerance || + periodic_distance(entry.kpoint.y, expected.y) > tolerance || + periodic_distance(entry.kpoint.z, expected.z) > tolerance) + { + throw std::invalid_argument( + "QSGW Vxc manifest k coordinate does not match its dataset index"); + } + } + } +} + +void VxcManifest::validate_file_hashes( + const std::string& base_directory) const +{ + if (base_directory.empty()) + { + throw std::invalid_argument( + "QSGW Vxc manifest base directory must not be empty"); + } + const std::filesystem::path base(base_directory); + for (const auto& item : entries_) + { + const VxcManifestEntry& entry = item.second; + const std::filesystem::path path = base / entry.file; + if (sha256_file(path.string()) != entry.sha256) + { + throw std::invalid_argument( + "QSGW Vxc input SHA256 mismatch: " + path.string()); + } + } +} + +const VxcManifestEntry& VxcManifest::at(const int spin, + const int k_index) const +{ + const auto entry = entries_.find({spin, k_index}); + if (entry == entries_.end()) + { + throw std::out_of_range( + "QSGW Vxc manifest does not contain the requested spin/k entry"); + } + return entry->second; +} + +Matz read_abacus_vxc_ha(std::istream& input, + const std::string& source_name) +{ + int rows = -1; + int columns = -1; + int current_row = -1; + bool saw_row_marker = false; + bool legacy_dimension_header = false; + std::map> triangular_rows; + std::vector dense_values; + std::string line; + while (std::getline(input, line)) + { + const std::string content = trim(line); + if (content.empty()) continue; + const std::string lowered = lowercase(content); + if (lowered.rfind("# rows", 0) == 0) + { + if (legacy_dimension_header) + throw std::invalid_argument( + "Mixed ABACUS Vxc matrix headers in " + source_name); + std::istringstream fields(content); + std::string hash; + std::string key; + if (!(fields >> hash >> key >> rows)) + throw std::invalid_argument( + "Malformed ABACUS Vxc row header in " + source_name); + continue; + } + if (lowered.rfind("# columns", 0) == 0) + { + if (legacy_dimension_header) + throw std::invalid_argument( + "Mixed ABACUS Vxc matrix headers in " + source_name); + std::istringstream fields(content); + std::string hash; + std::string key; + if (!(fields >> hash >> key >> columns)) + throw std::invalid_argument( + "Malformed ABACUS Vxc column header in " + source_name); + continue; + } + if (content.front() == '#') continue; + if (rows < 0 && columns < 0 && dense_values.empty() && + triangular_rows.empty() && content.find('(') == std::string::npos) + { + std::istringstream fields(content); + int dimension = -1; + if ((fields >> dimension) && dimension > 0 && + (fields >> std::ws).eof()) + { + rows = dimension; + columns = dimension; + legacy_dimension_header = true; + continue; + } + } + if (lowered.rfind("row ", 0) == 0) + { + if (legacy_dimension_header) + throw std::invalid_argument( + "Mixed ABACUS Vxc triangular layouts in " + source_name); + std::istringstream fields(content); + std::string label; + int row_one_based = 0; + if (!(fields >> label >> row_one_based) || row_one_based <= 0) + { + throw std::invalid_argument( + "Malformed ABACUS Vxc row marker in " + source_name); + } + current_row = row_one_based - 1; + if (!triangular_rows.emplace(current_row, + std::vector{}).second) + { + throw std::invalid_argument( + "Duplicate ABACUS Vxc row marker in " + source_name); + } + saw_row_marker = true; + } + + const std::vector values = + parse_complex_values(content, source_name); + if (values.empty()) continue; + if (saw_row_marker) + { + if (current_row < 0 || !dense_values.empty()) + { + throw std::invalid_argument( + "Mixed ABACUS Vxc dense and triangular layouts in " + + source_name); + } + auto& row = triangular_rows.at(current_row); + row.insert(row.end(), values.begin(), values.end()); + } + else + { + dense_values.insert(dense_values.end(), values.begin(), values.end()); + } + } + + if (rows <= 0 || columns <= 0 || rows != columns) + { + throw std::invalid_argument( + "ABACUS Vxc matrix header is incomplete or non-square in " + + source_name); + } + Matz result(rows, columns, MAJOR::ROW); + if (legacy_dimension_header) + { + const std::size_t expected = + static_cast(rows) * (rows + 1) / 2; + if (saw_row_marker || dense_values.size() != expected) + { + throw std::invalid_argument( + "Legacy ABACUS triangular Vxc entry count mismatch in " + + source_name); + } + std::size_t index = 0; + for (int row = 0; row < rows; ++row) + { + for (int column = row; column < columns; ++column) + { + const cplxdb value = 0.5 * dense_values[index++]; + result(row, column) = value; + if (row != column) + result(column, row) = std::conj(value); + } + } + } + else if (saw_row_marker) + { + if (!dense_values.empty() || + triangular_rows.size() != static_cast(rows)) + { + throw std::invalid_argument( + "Incomplete ABACUS triangular Vxc matrix in " + source_name); + } + for (int row = 0; row < rows; ++row) + { + const auto row_it = triangular_rows.find(row); + const int expected = columns - row; + if (row_it == triangular_rows.end() || + static_cast(row_it->second.size()) != expected) + { + throw std::invalid_argument( + "ABACUS triangular Vxc row length mismatch in " + + source_name); + } + for (int offset = 0; offset < expected; ++offset) + { + const int column = row + offset; + const cplxdb value = 0.5 * row_it->second[offset]; + result(row, column) = value; + if (row != column) + result(column, row) = std::conj(value); + } + } + } + else + { + if (dense_values.size() != + static_cast(rows) * columns) + { + throw std::invalid_argument( + "ABACUS dense Vxc entry count mismatch in " + source_name); + } + for (int row = 0; row < rows; ++row) + { + for (int column = 0; column < columns; ++column) + { + result(row, column) = + 0.5 * dense_values[static_cast(row) * + columns + column]; + } + } + } + validate_hermitian_matrix(result, rows, "ABACUS Vxc matrix"); + return result; +} + +Matz project_vxc_nao_to_fixed_basis(const Matz& vxc_nao, + const MeanField& reference, + const int spin, + const int kpoint) +{ + validate_reference_wfc(reference, spin, kpoint); + validate_hermitian_matrix(vxc_nao, reference.get_n_aos(), + "QSGW NAO Vxc matrix"); + Matz result(reference.get_n_bands(), reference.get_n_bands(), + MAJOR::ROW); + for (int bra = 0; bra < reference.get_n_bands(); ++bra) + { + for (int ket = 0; ket < reference.get_n_bands(); ++ket) + { + for (int spinor = 0; spinor < reference.get_n_spinor(); ++spinor) + { + const ComplexMatrix& wfc = + reference.get_eigenvectors() + .at(spin) + .at(spinor) + .at(kpoint); + for (int row = 0; row < reference.get_n_aos(); ++row) + { + for (int column = 0; column < reference.get_n_aos(); + ++column) + { + result(bra, ket) += + std::conj(wfc(bra, row)) * vxc_nao(row, column) * + wfc(ket, column); + } + } + } + } + } + validate_hermitian_matrix(result, reference.get_n_bands(), + "QSGW projected Vxc matrix"); + return result; +} + +Matz prepare_vxc_in_fixed_state_basis(const Matz& input, + const VxcBasis basis, + const MeanField& reference, + const int spin, + const int kpoint) +{ + validate_reference_wfc(reference, spin, kpoint); + if (basis == VxcBasis::Nao) + { + return project_vxc_nao_to_fixed_basis( + input, reference, spin, kpoint); + } + if (basis == VxcBasis::State) + { + validate_hermitian_matrix(input, reference.get_n_bands(), + "QSGW state-basis Vxc matrix"); + return input.copy(); + } + throw std::invalid_argument("QSGW Vxc basis is invalid"); +} + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/qsgw/vxc_io.h b/src/qsgw/vxc_io.h new file mode 100644 index 00000000..30b481da --- /dev/null +++ b/src/qsgw/vxc_io.h @@ -0,0 +1,98 @@ +#pragma once + +#include "../core/meanfield.h" +#include "../math/matrix_m.h" + +#include +#include +#include +#include +#include + +namespace librpa_int +{ +namespace qsgw +{ + +enum class VxcDatasetKind +{ + ScfGrid, + BandPath, +}; + +enum class VxcUnits +{ + Hartree, + Rydberg, +}; + +enum class VxcBasis +{ + Nao, + State, +}; + +enum class VxcGauge +{ + AoBloch, + Mf0State, +}; + +struct VxcManifestEntry +{ + int spin = -1; + int k_index = -1; + Vector3_Order kpoint; + int rows = -1; + int columns = -1; + std::string sha256; + std::string file; +}; + +class VxcManifest +{ +public: + static VxcManifest parse(std::istream& input, + const std::string& source_name); + + void validate(VxcDatasetKind expected_kind, + int spin_count, + const std::vector>& expected_kpoints, + int expected_rows, + int expected_columns, + double tolerance) const; + + void validate_file_hashes(const std::string& base_directory) const; + + const std::string& producer() const noexcept { return producer_; } + VxcUnits units() const noexcept { return units_; } + VxcBasis basis() const noexcept { return basis_; } + VxcGauge gauge() const noexcept { return gauge_; } + VxcDatasetKind kind() const noexcept { return kind_; } + const VxcManifestEntry& at(int spin, int k_index) const; + +private: + VxcDatasetKind kind_ = VxcDatasetKind::ScfGrid; + std::string producer_; + VxcUnits units_ = VxcUnits::Hartree; + VxcBasis basis_ = VxcBasis::State; + VxcGauge gauge_ = VxcGauge::Mf0State; + std::map, VxcManifestEntry> entries_; +}; + +Matz read_abacus_vxc_ha(std::istream& input, + const std::string& source_name); + +Matz project_vxc_nao_to_fixed_basis(const Matz& vxc_nao, + const MeanField& reference, + int spin, + int kpoint); + +Matz prepare_vxc_in_fixed_state_basis(const Matz& input, + VxcBasis basis, + const MeanField& reference, + int spin, + int kpoint); + +} // namespace qsgw +} // namespace librpa_int diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index e6d820e1..a6783a5c 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -42,6 +42,9 @@ foreach(target test_base_utility test_blas_lapack test_complexmatrix + test_qsgw_correlation_potential + test_qsgw_convergence + test_qsgw_effective_hamiltonian test_fitting test_io test_symmetry_context @@ -55,6 +58,18 @@ foreach(target test_meanfield test_pbc test_profiler + test_qsgw_band_output + test_qsgw_band_bvk_remap + test_qsgw_fixed_basis + test_qsgw_hamiltonian_cut + test_qsgw_hamiltonian_mixing + test_qsgw_input_contract + test_qsgw_iteration_trace + test_qsgw_mixing + test_qsgw_occupation + test_qsgw_projection_target + test_qsgw_sha256 + test_qsgw_vxc_io test_qpe_solver test_rsh test_symmetry @@ -72,6 +87,8 @@ foreach(target test_blacs test_dataset test_epsilon + test_qsgw_distributed_matrix + test_qsgw_fixed_basis_mpi test_matrix_m_mpi test_meanfield_mpi test_kpoint_blacs_parallel_context diff --git a/src/test/test_qsgw_band_bvk_remap.cpp b/src/test/test_qsgw_band_bvk_remap.cpp new file mode 100644 index 00000000..d2608801 --- /dev/null +++ b/src/test/test_qsgw_band_bvk_remap.cpp @@ -0,0 +1,74 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "../api/compute_helper.h" +#include "../qsgw/band_bvk_remap.h" + +#include +#include +#include + +using librpa_int::Atoms; +using librpa_int::Matrix3; +using librpa_int::PeriodicBoundaryData; +using librpa_int::Vector3; +using librpa_int::Vector3_Order; +using librpa_int::atom_t; +using librpa_int::qsgw::build_legacy_band_bvk_remap; + +namespace +{ + +void test_displaced_same_atom_positive_corner_matches_legacy_mapping() +{ + constexpr double a = 5.1315533365962613; + const Matrix3 lattice( + 0.0, a, a, + a, 0.0, a, + a, a, 0.0); + + PeriodicBoundaryData pbc; + pbc.set_latvec({ + 0.0, a, a, + a, 0.0, a, + a, a, 0.0}); + pbc.set_period(4, 4, 4); + + Atoms atoms; + atoms.set( + {14, 14}, + {Vector3{0.0, 0.0, 0.0}, + Vector3{0.5 * a, 0.5 * a, 0.5 * a}}, + lattice); + + const Vector3_Order corner{1, 1, 1}; + const auto upstream = + librpa_int::api::build_band_bvk_remap(atoms, pbc, 0); + const auto* upstream_origin = + upstream.find_R_bvk({atom_t{0}, atom_t{0}}, corner); + const auto* upstream_displaced = + upstream.find_R_bvk({atom_t{1}, atom_t{1}}, corner); + assert(upstream_origin != nullptr); + assert(upstream_displaced != nullptr); + assert(upstream_origin->front() == Vector3_Order(-3, 1, 1)); + assert(upstream_displaced->front() == Vector3_Order(-3, 1, 1)); + + const auto legacy = build_legacy_band_bvk_remap(atoms, pbc, 0); + const auto* legacy_origin = + legacy.find_R_bvk({atom_t{0}, atom_t{0}}, corner); + const auto* legacy_displaced = + legacy.find_R_bvk({atom_t{1}, atom_t{1}}, corner); + assert(legacy_origin != nullptr); + assert(legacy_origin->front() == Vector3_Order(-3, 1, 1)); + assert(legacy_displaced == nullptr); +} + +} // namespace + +int main() +{ + test_displaced_same_atom_positive_corner_matches_legacy_mapping(); + std::cout << "test_qsgw_band_bvk_remap: all tests passed\n"; + return 0; +} diff --git a/src/test/test_qsgw_band_output.cpp b/src/test/test_qsgw_band_output.cpp new file mode 100644 index 00000000..eaf605ae --- /dev/null +++ b/src/test/test_qsgw_band_output.cpp @@ -0,0 +1,211 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "../qsgw/band_output.h" + +#include "../utils/constants.h" + +#include +#include +#include +#include +#include +#include +#include + +using librpa_int::HA2EV; +using librpa_int::MeanField; +using librpa_int::Matz; +using librpa_int::Vector3_Order; +using librpa_int::qsgw::SpinKMatrixMap; +using librpa_int::qsgw::write_qsgw_band_spin_tables; + +namespace +{ + +template +void assert_throws(Function&& function) +{ + bool threw = false; + try + { + function(); + } + catch (const std::exception&) + { + threw = true; + } + assert(threw); +} + +Matz diagonal(const double first, const double second) +{ + Matz result(2, 2); + result(0, 0) = first; + result(1, 1) = second; + return result; +} + +void test_legacy_band_columns_and_energy_components() +{ + MeanField reference(1, 2, 2, 2, 1); + MeanField live = reference; + for (int kpoint = 0; kpoint < 2; ++kpoint) + { + reference.get_weight()[0](kpoint, 0) = 1.0; + reference.get_weight()[0](kpoint, 1) = 0.0; + live.get_eigenvals()[0](kpoint, 0) = 0.1 + 0.1 * kpoint; + live.get_eigenvals()[0](kpoint, 1) = 1.0 + 0.1 * kpoint; + } + + SpinKMatrixMap h_ks; + SpinKMatrixMap vxc; + SpinKMatrixMap exx; + h_ks[0][0] = diagonal(0.0, 0.8); + h_ks[0][1] = diagonal(0.1, 0.9); + vxc[0][0] = diagonal(0.2, 0.3); + vxc[0][1] = diagonal(0.2, 0.3); + exx[0][0] = diagonal(-0.1, -0.05); + exx[0][1] = diagonal(-0.1, -0.05); + exx[0][1](0, 0) += librpa_int::cplxdb(0.0, 0.25); + const std::vector> kpoints{ + {0.0, 0.0, 0.0}, {0.5, 0.0, 0.0}}; + + std::ostringstream ks; + std::ostringstream hf; + std::ostringstream qsgw; + write_qsgw_band_spin_tables( + ks, hf, qsgw, live, reference, kpoints, h_ks, vxc, exx, + 0, 2.0); + + int index = 0; + double kx = 0.0; + double ky = 0.0; + double kz = 0.0; + double occ0 = 0.0; + double energy0 = 0.0; + double occ1 = 0.0; + double energy1 = 0.0; + std::istringstream ks_input(ks.str()); + ks_input >> index >> kx >> ky >> kz + >> occ0 >> energy0 >> occ1 >> energy1; + assert(index == 1); + assert(std::abs(occ0 - 2.0) < 1.0e-12); + assert(std::abs(occ1) < 1.0e-12); + assert(std::abs(energy0) < 1.0e-12); + assert(std::abs(energy1 - 0.8 * HA2EV) < 1.0e-4); + + std::istringstream hf_input(hf.str()); + hf_input >> index >> kx >> ky >> kz + >> occ0 >> energy0 >> occ1 >> energy1; + assert(std::abs(energy0 - (-0.3 * HA2EV)) < 1.0e-4); + assert(std::abs(energy1 - (0.45 * HA2EV)) < 1.0e-4); + + std::istringstream qsgw_input(qsgw.str()); + qsgw_input >> index >> kx >> ky >> kz + >> occ0 >> energy0 >> occ1 >> energy1; + assert(std::abs(occ0 - 2.0) < 1.0e-12); + assert(std::abs(occ1) < 1.0e-12); + assert(std::abs(energy0 - 0.1 * HA2EV) < 1.0e-4); + assert(std::abs(energy1 - 1.0 * HA2EV) < 1.0e-4); + + ks_input >> index >> kx >> ky >> kz + >> occ0 >> energy0 >> occ1 >> energy1; + assert(index == 2); + assert(std::abs(kx - 0.5) < 1.0e-12); + assert(std::abs(ky) < 1.0e-12); + assert(std::abs(kz) < 1.0e-12); + assert(std::abs(occ0 - 2.0) < 1.0e-12); + assert(std::abs(occ1) < 1.0e-12); + assert(std::abs(energy0 - 0.1 * HA2EV) < 1.0e-4); + assert(std::abs(energy1 - 0.9 * HA2EV) < 1.0e-4); + ks_input >> std::ws; + assert(ks_input.peek() == std::char_traits::eof()); + + hf_input >> index >> kx >> ky >> kz + >> occ0 >> energy0 >> occ1 >> energy1; + assert(index == 2); + assert(std::abs(energy0 - (-0.2 * HA2EV)) < 1.0e-4); + assert(std::abs(energy1 - (0.55 * HA2EV)) < 1.0e-4); + hf_input >> std::ws; + assert(hf_input.peek() == std::char_traits::eof()); + + qsgw_input >> index >> kx >> ky >> kz + >> occ0 >> energy0 >> occ1 >> energy1; + assert(index == 2); + assert(std::abs(occ0 - 2.0) < 1.0e-12); + assert(std::abs(occ1) < 1.0e-12); + assert(std::abs(energy0 - 0.2 * HA2EV) < 1.0e-4); + assert(std::abs(energy1 - 1.1 * HA2EV) < 1.0e-4); + qsgw_input >> std::ws; + assert(qsgw_input.peek() == std::char_traits::eof()); +} + +void test_invalid_band_output_contracts_are_rejected() +{ + MeanField reference(1, 1, 1, 1, 1); + MeanField live = reference; + reference.get_weight()[0](0, 0) = 2.0; + live.get_eigenvals()[0](0, 0) = 0.0; + SpinKMatrixMap matrices; + matrices[0][0] = Matz(1, 1); + const std::vector> kpoints{{0.0, 0.0, 0.0}}; + + assert_throws([&] { + std::ostringstream a; + std::ostringstream b; + std::ostringstream c; + write_qsgw_band_spin_tables( + a, b, c, live, reference, {}, matrices, matrices, + matrices, 0, 0.0); + }); + assert_throws([&] { + std::ostringstream a; + std::ostringstream b; + std::ostringstream c; + write_qsgw_band_spin_tables( + a, b, c, live, reference, kpoints, matrices, matrices, + matrices, 1, 0.0); + }); + assert_throws([&] { + std::ostringstream a; + std::ostringstream b; + std::ostringstream c; + write_qsgw_band_spin_tables( + a, b, c, live, reference, kpoints, matrices, matrices, + matrices, 0, std::numeric_limits::quiet_NaN()); + }); + + SpinKMatrixMap nonfinite = matrices; + nonfinite.at(0).at(0)(0, 0) = + std::numeric_limits::infinity(); + assert_throws([&] { + std::ostringstream a; + std::ostringstream b; + std::ostringstream c; + write_qsgw_band_spin_tables( + a, b, c, live, reference, kpoints, nonfinite, matrices, + matrices, 0, 0.0); + }); + + nonfinite.at(0).at(0)(0, 0) = librpa_int::cplxdb( + 0.0, std::numeric_limits::infinity()); + assert_throws([&] { + std::ostringstream a; + std::ostringstream b; + std::ostringstream c; + write_qsgw_band_spin_tables( + a, b, c, live, reference, kpoints, nonfinite, matrices, + matrices, 0, 0.0); + }); +} + +} // namespace + +int main() +{ + test_legacy_band_columns_and_energy_components(); + test_invalid_band_output_contracts_are_rejected(); + return 0; +} diff --git a/src/test/test_qsgw_convergence.cpp b/src/test/test_qsgw_convergence.cpp new file mode 100644 index 00000000..66ee7d08 --- /dev/null +++ b/src/test/test_qsgw_convergence.cpp @@ -0,0 +1,79 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "../qsgw/convergence.h" + +#include +#include +#include +#include +#include + +using librpa_int::MeanField; +using librpa_int::qsgw::eigenvalue_snapshot; +using librpa_int::qsgw::max_eigenvalue_change; +using librpa_int::qsgw::qsgw_iteration_converged; + +namespace +{ + +template +void assert_throws(Function&& function) +{ + bool threw = false; + try + { + function(); + } + catch (const std::exception&) + { + threw = true; + } + assert(threw); +} + +void test_maximum_change_and_minimum_iteration_gate() +{ + MeanField meanfield(2, 2, 3, 1, 1); + for (int spin = 0; spin < 2; ++spin) + { + for (int kpoint = 0; kpoint < 2; ++kpoint) + { + for (int band = 0; band < 3; ++band) + { + meanfield.get_eigenvals()[spin](kpoint, band) = + 100.0 * spin + 10.0 * kpoint + band; + } + } + } + const auto previous = eigenvalue_snapshot(meanfield); + meanfield.get_eigenvals()[1](1, 2) += 0.003; + meanfield.get_eigenvals()[0](0, 0) -= 0.002; + assert(std::abs(max_eigenvalue_change(meanfield, previous) - 0.003) < + 1.0e-14); + assert(!qsgw_iteration_converged(4, 5, 1.0e-5, 1.0e-4)); + assert(qsgw_iteration_converged(5, 5, 1.0e-5, 1.0e-4)); +} + +void test_invalid_snapshot_or_tolerance_is_rejected() +{ + MeanField meanfield(1, 1, 1, 1, 1); + auto snapshot = eigenvalue_snapshot(meanfield); + snapshot.clear(); + assert_throws([&] { max_eigenvalue_change(meanfield, snapshot); }); + assert_throws([&] { + qsgw_iteration_converged(1, 1, 0.0, + std::numeric_limits::quiet_NaN()); + }); +} + +} // namespace + +int main() +{ + test_maximum_change_and_minimum_iteration_gate(); + test_invalid_snapshot_or_tolerance_is_rejected(); + std::cout << "test_qsgw_convergence: all tests passed\n"; + return 0; +} diff --git a/src/test/test_qsgw_correlation_potential.cpp b/src/test/test_qsgw_correlation_potential.cpp new file mode 100644 index 00000000..e85955de --- /dev/null +++ b/src/test/test_qsgw_correlation_potential.cpp @@ -0,0 +1,353 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "../qsgw/correlation_potential.h" + +#include "../core/analycont.h" + +#include +#include +#include +#include +#include +#include +#include + +using librpa_int::AnalyContPade; +using librpa_int::Matz; +using librpa_int::MeanField; +using librpa_int::cplxdb; +using librpa_int::qsgw::CorrelationPotentialMode; +using librpa_int::qsgw::CorrelationPotentialSettings; +using librpa_int::qsgw::build_qsgw_correlation_potential; + +namespace +{ + +void assert_close(const cplxdb actual, const cplxdb expected, + const double tolerance = 1.0e-13) +{ + assert(std::abs(actual - expected) < tolerance); +} + +template +void assert_throws(Function&& function) +{ + bool threw = false; + try + { + function(); + } + catch (const std::exception&) + { + threw = true; + } + assert(threw); +} + +std::vector make_imagfreqs(const std::vector& frequencies) +{ + std::vector result; + for (const double frequency: frequencies) + { + result.emplace_back(0.0, frequency); + } + return result; +} + +std::map make_sigma(const std::vector& frequencies) +{ + Matz constant(2, 2); + constant(0, 0) = 2.123456789e-7; + constant(0, 1) = cplxdb(1.234567891e-7, 0.456789123e-7); + constant(1, 0) = std::conj(constant(0, 1)); + constant(1, 1) = 4.234567891e-7; + + Matz slope(2, 2); + slope(0, 0) = 0.712345678e-7; + slope(0, 1) = cplxdb(-0.312345678e-7, 0.212345678e-7); + slope(1, 0) = std::conj(slope(0, 1)); + slope(1, 1) = -0.423456789e-7; + + std::map sigma; + for (const double frequency: frequencies) + { + sigma[frequency] = constant + cplxdb(0.0, frequency) * slope; + } + return sigma; +} + +cplxdb evaluate_element_with_upstream_pade( + const std::vector& source_frequencies, + const std::map& sigma, + const int row, + const int column, + const cplxdb target, + const std::vector& resample_frequencies, + const int n_params_anacon, + const int n_params_anacon_resample) +{ + const auto source_imagfreqs = make_imagfreqs(source_frequencies); + std::vector source_data; + for (const double frequency: source_frequencies) + { + source_data.push_back(sigma.at(frequency)(row, column)); + } + + if (resample_frequencies.empty()) + { + return AnalyContPade( + n_params_anacon, source_imagfreqs, source_data).get(target); + } + + const AnalyContPade source_pade( + n_params_anacon_resample, source_imagfreqs, source_data); + std::vector resampled_data; + for (const cplxdb frequency: resample_frequencies) + { + resampled_data.push_back(source_pade.get(frequency)); + } + return AnalyContPade( + n_params_anacon, resample_frequencies, resampled_data).get(target); +} + +Matz evaluate_matrix( + const std::vector& source_frequencies, + const std::map& sigma, + const double energy, + const std::vector& resample_frequencies, + const int n_params_anacon, + const int n_params_anacon_resample) +{ + Matz result(2, 2); + for (int row = 0; row < 2; ++row) + { + for (int column = 0; column < 2; ++column) + { + result(row, column) = evaluate_element_with_upstream_pade( + source_frequencies, sigma, row, column, + cplxdb(energy, 0.0), resample_frequencies, + n_params_anacon, n_params_anacon_resample); + } + } + return 0.5 * (result + transpose(result, true)); +} + +Matz expected_potential( + const MeanField& meanfield, + const std::vector& source_frequencies, + const std::map& sigma, + const CorrelationPotentialMode mode, + const std::vector& resample_frequencies, + const int n_params_anacon, + const int n_params_anacon_resample) +{ + const double efermi = meanfield.get_efermi(); + const double e0 = meanfield.get_eigenvals()[0](0, 0) - efermi; + const double e1 = meanfield.get_eigenvals()[0](0, 1) - efermi; + const Matz sigma0 = evaluate_matrix( + source_frequencies, sigma, e0, resample_frequencies, + n_params_anacon, n_params_anacon_resample); + const Matz sigma1 = evaluate_matrix( + source_frequencies, sigma, e1, resample_frequencies, + n_params_anacon, n_params_anacon_resample); + const Matz sigma_fermi = evaluate_matrix( + source_frequencies, sigma, 0.0, resample_frequencies, + n_params_anacon, n_params_anacon_resample); + + Matz expected(2, 2); + if (mode == CorrelationPotentialMode::ModeB) + { + expected(0, 0) = sigma0(0, 0); + expected(1, 1) = sigma1(1, 1); + expected(0, 1) = sigma_fermi(0, 1); + expected(1, 0) = sigma_fermi(1, 0); + return expected; + } + + expected(0, 0) = sigma0(0, 0); + expected(1, 1) = sigma1(1, 1); + expected(0, 1) = 0.5 * (sigma0(0, 1) + sigma1(0, 1)); + expected(1, 0) = 0.5 * (sigma1(1, 0) + sigma0(1, 0)); + return expected; +} + +void run_mode_test( + const CorrelationPotentialMode mode, + const std::vector& resample_frequencies, + const int n_params_anacon = -1, + const int n_params_anacon_resample = -1) +{ + MeanField meanfield(1, 1, 2, 2, 1); + meanfield.get_eigenvals()[0](0, 0) = -0.4; + meanfield.get_eigenvals()[0](0, 1) = 0.6; + meanfield.get_efermi() = 0.1; + const std::vector source_frequencies{0.2, 1.0}; + const auto sigma = make_sigma(source_frequencies); + + CorrelationPotentialSettings settings; + settings.mode = mode; + settings.n_params_anacon = n_params_anacon; + settings.n_params_anacon_resample = n_params_anacon_resample; + settings.resample_imagfreqs = resample_frequencies; + const Matz actual = build_qsgw_correlation_potential( + meanfield, source_frequencies, sigma, 0, 0, settings); + const Matz expected = expected_potential( + meanfield, source_frequencies, sigma, mode, resample_frequencies, + n_params_anacon, n_params_anacon_resample); + + for (int row = 0; row < 2; ++row) + { + for (int column = 0; column < 2; ++column) + { + assert_close(actual(row, column), expected(row, column)); + } + } + assert(std::abs(actual(0, 0)) > 1.0e-8); +} + +void test_mode_a_and_b_match_upstream_pade_without_qsgw_rounding() +{ + run_mode_test(CorrelationPotentialMode::ModeA, {}); + run_mode_test(CorrelationPotentialMode::ModeB, {}); +} + +void test_optional_resampling_matches_the_upstream_two_stage_pade() +{ + run_mode_test( + CorrelationPotentialMode::ModeB, + {cplxdb(0.0, 0.3), cplxdb(0.0, 0.8)}); +} + +void test_two_stage_parameter_counts_follow_upstream_order() +{ + const std::vector resample_frequencies{ + cplxdb(0.0, 0.3), cplxdb(0.0, 0.65), + cplxdb(0.0, 0.9)}; + + MeanField meanfield(1, 1, 2, 2, 1); + meanfield.get_eigenvals()[0](0, 0) = -0.4; + meanfield.get_eigenvals()[0](0, 1) = 0.6; + meanfield.get_efermi() = 0.1; + const std::vector source_frequencies{0.2, 0.6, 1.0}; + auto sigma = make_sigma(source_frequencies); + sigma.at(0.6)(0, 0) += 1.1e-7; + sigma.at(0.6)(0, 1) += cplxdb(0.4e-7, -0.2e-7); + sigma.at(0.6)(1, 0) += cplxdb(-0.3e-7, 0.1e-7); + sigma.at(0.6)(1, 1) -= 0.7e-7; + + CorrelationPotentialSettings settings; + settings.mode = CorrelationPotentialMode::ModeB; + settings.n_params_anacon = 2; + settings.n_params_anacon_resample = 3; + settings.resample_imagfreqs = resample_frequencies; + const Matz actual = build_qsgw_correlation_potential( + meanfield, source_frequencies, sigma, 0, 0, settings); + const Matz correct = expected_potential( + meanfield, source_frequencies, sigma, CorrelationPotentialMode::ModeB, + resample_frequencies, 2, 3); + const Matz swapped = expected_potential( + meanfield, source_frequencies, sigma, CorrelationPotentialMode::ModeB, + resample_frequencies, 3, 2); + for (int row = 0; row < 2; ++row) + { + for (int column = 0; column < 2; ++column) + { + assert_close(actual(row, column), correct(row, column)); + } + } + assert(std::abs(correct(0, 0) - swapped(0, 0)) > 1.0e-12); +} + +void test_nonfinite_matrix_data_is_rejected_instead_of_zeroed() +{ + MeanField meanfield(1, 1, 2, 2, 1); + meanfield.get_eigenvals()[0](0, 0) = -0.4; + meanfield.get_eigenvals()[0](0, 1) = 0.6; + meanfield.get_efermi() = 0.1; + const std::vector frequencies{0.2, 1.0}; + auto sigma = make_sigma(frequencies); + sigma.at(0.2)(0, 0) = + std::numeric_limits::quiet_NaN(); + assert_throws([&] { + (void)build_qsgw_correlation_potential( + meanfield, frequencies, sigma, 0, 0, {}); + }); +} + +void test_invalid_settings_and_frequency_contracts_are_rejected() +{ + MeanField meanfield(1, 1, 2, 2, 1); + meanfield.get_eigenvals()[0](0, 0) = -0.4; + meanfield.get_eigenvals()[0](0, 1) = 0.6; + meanfield.get_efermi() = 0.1; + const std::vector frequencies{0.2, 1.0}; + const auto sigma = make_sigma(frequencies); + + CorrelationPotentialSettings settings; + settings.n_params_anacon = 0; + assert_throws([&] { + (void)build_qsgw_correlation_potential( + meanfield, frequencies, sigma, 0, 0, settings); + }); + + settings = {}; + settings.resample_imagfreqs = {cplxdb(0.0, 0.3), cplxdb(0.0, 0.8)}; + settings.n_params_anacon_resample = 0; + assert_throws([&] { + (void)build_qsgw_correlation_potential( + meanfield, frequencies, sigma, 0, 0, settings); + }); + + settings = {}; + settings.resample_imagfreqs = {cplxdb(0.1, 0.3)}; + assert_throws([&] { + (void)build_qsgw_correlation_potential( + meanfield, frequencies, sigma, 0, 0, settings); + }); + + assert_throws([&] { + (void)build_qsgw_correlation_potential( + meanfield, {1.0, 0.2}, sigma, 0, 0, {}); + }); + assert_throws([&] { + (void)build_qsgw_correlation_potential( + meanfield, {0.2, 0.2}, sigma, 0, 0, {}); + }); + + auto missing_frequency = sigma; + missing_frequency.erase(1.0); + assert_throws([&] { + (void)build_qsgw_correlation_potential( + meanfield, frequencies, missing_frequency, 0, 0, {}); + }); + + auto wrong_shape = sigma; + wrong_shape.at(1.0) = Matz(1, 1); + wrong_shape.at(1.0)(0, 0) = 1.0; + assert_throws([&] { + (void)build_qsgw_correlation_potential( + meanfield, frequencies, wrong_shape, 0, 0, {}); + }); + + MeanField nonfinite_meanfield = meanfield; + nonfinite_meanfield.get_eigenvals()[0](0, 1) = + std::numeric_limits::infinity(); + assert_throws([&] { + (void)build_qsgw_correlation_potential( + nonfinite_meanfield, frequencies, sigma, 0, 0, {}); + }); +} + +} // namespace + +int main() +{ + test_mode_a_and_b_match_upstream_pade_without_qsgw_rounding(); + test_optional_resampling_matches_the_upstream_two_stage_pade(); + test_two_stage_parameter_counts_follow_upstream_order(); + test_nonfinite_matrix_data_is_rejected_instead_of_zeroed(); + test_invalid_settings_and_frequency_contracts_are_rejected(); + return 0; +} diff --git a/src/test/test_qsgw_distributed_matrix.cpp b/src/test/test_qsgw_distributed_matrix.cpp new file mode 100644 index 00000000..90959eac --- /dev/null +++ b/src/test/test_qsgw_distributed_matrix.cpp @@ -0,0 +1,167 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "../qsgw/distributed_matrix.h" + +#include "../io/global_io.h" +#include "../math/lapack_connector.h" +#include "../math/utils_matrix_m_mpi.h" +#include "../mpi/global_mpi.h" +#include "../mpi/utils_blacs.h" + +#include +#include +#include +#include + +using librpa_int::ArrayDesc; +using librpa_int::BlacsCtxtHandler; +using librpa_int::MAJOR; +using librpa_int::Matz; +using librpa_int::ScalapackConnector; +using librpa_int::cplxdb; +using librpa_int::init_local_mat; +using librpa_int::qsgw::collect_blacs_matrix_root; +using librpa_int::qsgw::broadcast_spin_k_matrix_map; +using librpa_int::qsgw::SpinKMatrixMap; + +namespace +{ + +void assert_close(const cplxdb actual, const cplxdb expected) +{ + assert(std::abs(actual - expected) < 1.0e-13); +} + +Matz make_global_matrix(const int rows, const int columns) +{ + Matz result(rows, columns, MAJOR::COL); + for (int row = 0; row < rows; ++row) + { + for (int column = 0; column < columns; ++column) + { + result(row, column) = + cplxdb(10.0 * row + column + 0.25, + row - 2.0 * column + 0.5); + } + } + return result; +} + +void test_distributed_complex_matrix_is_collected_exactly_once_on_root( + BlacsCtxtHandler& blacs) +{ + blacs.set_square_grid(); + assert(blacs.nprocs == 4); + assert(blacs.nprows == 2); + assert(blacs.npcols == 2); + + constexpr int rows = 5; + constexpr int columns = 4; + ArrayDesc distributed_desc(blacs); + distributed_desc.init(rows, columns, 2, 2, 0, 0); + ArrayDesc root_desc(blacs); + root_desc.init(rows, columns, rows, columns, 0, 0); + + Matz global(1, 1, MAJOR::COL); + if (root_desc.is_src()) + { + global = make_global_matrix(rows, columns); + } + auto local = init_local_mat(distributed_desc, MAJOR::COL); + ScalapackConnector::pgemr2d_f( + rows, columns, + global.ptr(), 1, 1, root_desc.desc, + local.ptr(), 1, 1, distributed_desc.desc, + distributed_desc.ictxt()); + + const auto collected = collect_blacs_matrix_root(local, distributed_desc); + if (root_desc.is_src()) + { + assert(collected.nr() == rows); + assert(collected.nc() == columns); + const auto expected = make_global_matrix(rows, columns); + for (int row = 0; row < rows; ++row) + { + for (int column = 0; column < columns; ++column) + { + assert_close(collected(row, column), expected(row, column)); + } + } + } + else + { + assert(collected.nr() == 0); + assert(collected.nc() == 0); + } + distributed_desc.barrier(); +} + +void test_spin_k_matrix_map_is_broadcast_with_layout( + const librpa_int::MpiCommHandler& communicator) +{ + SpinKMatrixMap values; + if (communicator.myid == 0) + { + Matz first(2, 2, MAJOR::ROW); + first(0, 0) = {1.0, 0.0}; + first(0, 1) = {2.0, 3.0}; + first(1, 0) = {4.0, 5.0}; + first(1, 1) = {6.0, 0.0}; + values[0][1] = std::move(first); + + Matz second(1, 3, MAJOR::COL); + second(0, 0) = {-1.0, 0.5}; + second(0, 1) = {-2.0, 1.5}; + second(0, 2) = {-3.0, 2.5}; + values[2][4] = std::move(second); + } + else + { + Matz poison(1, 1, MAJOR::ROW); + poison(0, 0) = 99.0; + values[9][9] = std::move(poison); + } + + broadcast_spin_k_matrix_map(values, 0, communicator); + assert(values.size() == 2); + assert(values.count(9) == 0); + const auto& first = values.at(0).at(1); + assert(first.nr() == 2 && first.nc() == 2); + assert(first.major() == MAJOR::ROW); + assert_close(first(0, 1), {2.0, 3.0}); + assert_close(first(1, 0), {4.0, 5.0}); + const auto& second = values.at(2).at(4); + assert(second.nr() == 1 && second.nc() == 3); + assert(second.major() == MAJOR::COL); + assert_close(second(0, 2), {-3.0, 2.5}); +} + +} // namespace + +int main(int argc, char** argv) +{ + int provided = 0; + MPI_Init_thread(&argc, &argv, MPI_THREAD_MULTIPLE, &provided); + assert(provided >= MPI_THREAD_MULTIPLE); + librpa_int::global::init_global_mpi(MPI_COMM_WORLD); + librpa_int::global::init_global_io(); + + BlacsCtxtHandler blacs(librpa_int::global::mpi_comm_global); + blacs.init(); + test_distributed_complex_matrix_is_collected_exactly_once_on_root(blacs); + test_spin_k_matrix_map_is_broadcast_with_layout( + librpa_int::global::mpi_comm_global_h); + blacs.exit(); + + const int rank = librpa_int::global::myid_global; + librpa_int::global::finalize_global_io(); + librpa_int::global::finalize_global_mpi(); + MPI_Finalize(); + if (rank == 0) + { + std::cout << "test_qsgw_distributed_matrix: all tests passed\n"; + } + return 0; +} diff --git a/src/test/test_qsgw_effective_hamiltonian.cpp b/src/test/test_qsgw_effective_hamiltonian.cpp new file mode 100644 index 00000000..1435a210 --- /dev/null +++ b/src/test/test_qsgw_effective_hamiltonian.cpp @@ -0,0 +1,198 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "../qsgw/effective_hamiltonian.h" + +#include +#include +#include +#include +#include +#include + +using librpa_int::Matz; +using librpa_int::cplxdb; +using librpa_int::qsgw::SpinKMatrixMap; +using librpa_int::qsgw::assemble_effective_hamiltonian; +using librpa_int::qsgw::build_reference_hamiltonian; + +namespace +{ + +template +void assert_throws(Function&& function) +{ + bool threw = false; + try + { + function(); + } + catch (const std::exception&) + { + threw = true; + } + assert(threw); +} + +template +std::string exception_message(Function&& function) +{ + try + { + function(); + } + catch (const std::exception& error) + { + return error.what(); + } + assert(false); + return {}; +} + +Matz hermitian(const double diagonal0, const double diagonal1, + const cplxdb off_diagonal, + const librpa_int::MAJOR major = librpa_int::MAJOR::ROW) +{ + Matz result(2, 2, major); + result(0, 0) = diagonal0; + result(1, 1) = diagonal1; + result(0, 1) = off_diagonal; + result(1, 0) = std::conj(off_diagonal); + return result; +} + +SpinKMatrixMap one_block(const Matz& value) +{ + SpinKMatrixMap result; + result[0][0] = value.copy(); + return result; +} + +void assert_close(const cplxdb actual, const cplxdb expected, + const double tolerance = 1.0e-13) +{ + assert(std::abs(actual - expected) < tolerance); +} + +void test_qsgw_formula() +{ + const auto hks = one_block(hermitian( + 1.0, 2.0, {0.1, -0.2}, librpa_int::MAJOR::ROW)); + const auto vxc = one_block(hermitian( + 0.3, 0.4, {-0.2, 0.1}, librpa_int::MAJOR::COL)); + const auto exchange = one_block(hermitian( + -0.5, -0.6, {0.05, 0.3}, librpa_int::MAJOR::COL)); + const auto correlation = one_block(hermitian( + 0.2, 0.1, {0.15, -0.1}, librpa_int::MAJOR::ROW)); + + const Matz hks_before = hks.at(0).at(0).copy(); + const Matz vxc_before = vxc.at(0).at(0).copy(); + const Matz exchange_before = exchange.at(0).at(0).copy(); + const Matz correlation_before = correlation.at(0).at(0).copy(); + Matz expected(2, 2); + for (int row = 0; row < 2; ++row) + { + for (int column = 0; column < 2; ++column) + { + expected(row, column) = + hks.at(0).at(0)(row, column) - + vxc.at(0).at(0)(row, column) + + exchange.at(0).at(0)(row, column) + + correlation.at(0).at(0)(row, column); + } + } + + const auto result = assemble_effective_hamiltonian( + hks, vxc, exchange, correlation); + const auto& matrix = result.at(0).at(0); + for (int row = 0; row < 2; ++row) + { + for (int column = 0; column < 2; ++column) + { + assert_close(matrix(row, column), expected(row, column)); + assert_close(hks.at(0).at(0)(row, column), + hks_before(row, column)); + assert_close(vxc.at(0).at(0)(row, column), + vxc_before(row, column)); + assert_close(exchange.at(0).at(0)(row, column), + exchange_before(row, column)); + assert_close(correlation.at(0).at(0)(row, column), + correlation_before(row, column)); + } + } +} + +void test_layout_and_finiteness_mismatches_are_rejected() +{ + const auto valid = one_block(hermitian(1.0, 2.0, {0.0, 0.0})); + auto missing = valid; + missing.at(0).erase(0); + assert_throws([&] { + assemble_effective_hamiltonian( + valid, missing, valid, valid); + }); + + auto nonfinite = one_block(valid.at(0).at(0)); + nonfinite.at(0).at(0)(0, 0) = + std::numeric_limits::quiet_NaN(); + assert(std::isfinite(valid.at(0).at(0)(0, 0).real())); + assert_throws([&] { + assemble_effective_hamiltonian( + valid, valid, valid, nonfinite); + }); +} + +void test_nonhermitian_components_use_the_legacy_upper_triangle() +{ + const auto valid = one_block(hermitian(1.0, 2.0, {0.0, 0.0})); + auto exchange = one_block(hermitian(-0.5, -0.6, {0.05, 0.3})); + exchange.at(0).at(0)(0, 0) += cplxdb(0.0, 4.0e-7); + exchange.at(0).at(0)(1, 0) += cplxdb(0.0, 2.5e-7); + + const auto result = assemble_effective_hamiltonian( + valid, valid, exchange, valid); + const auto& matrix = result.at(0).at(0); + assert_close(matrix(0, 0), cplxdb(0.5, 0.0)); + assert_close(matrix(0, 1), cplxdb(0.05, 0.3)); + assert_close(matrix(1, 0), std::conj(matrix(0, 1))); +} + +void test_reference_hamiltonian_is_the_initial_eigenvalue_diagonal() +{ + librpa_int::MeanField reference(1, 2, 3, 1, 1); + for (int kpoint = 0; kpoint < 2; ++kpoint) + { + for (int band = 0; band < 3; ++band) + { + reference.get_eigenvals()[0](kpoint, band) = + 10.0 * kpoint + band + 0.25; + } + } + const auto result = build_reference_hamiltonian(reference); + for (int kpoint = 0; kpoint < 2; ++kpoint) + { + for (int row = 0; row < 3; ++row) + { + for (int column = 0; column < 3; ++column) + { + const cplxdb expected = row == column + ? cplxdb(10.0 * kpoint + row + 0.25, 0.0) + : cplxdb(0.0, 0.0); + assert_close(result.at(0).at(kpoint)(row, column), expected); + } + } + } +} + +} // namespace + +int main() +{ + test_qsgw_formula(); + test_layout_and_finiteness_mismatches_are_rejected(); + test_nonhermitian_components_use_the_legacy_upper_triangle(); + test_reference_hamiltonian_is_the_initial_eigenvalue_diagonal(); + std::cout << "test_qsgw_effective_hamiltonian: all tests passed\n"; + return 0; +} diff --git a/src/test/test_qsgw_fixed_basis.cpp b/src/test/test_qsgw_fixed_basis.cpp new file mode 100644 index 00000000..0d1ca0ba --- /dev/null +++ b/src/test/test_qsgw_fixed_basis.cpp @@ -0,0 +1,549 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "../qsgw/fixed_basis.h" + +#include +#include +#include +#include +#include +#include + +using librpa_int::ComplexMatrix; +using librpa_int::MeanField; +using librpa_int::Matz; +using librpa_int::cplxdb; +using librpa_int::qsgw::SpinKMatrixMap; +using librpa_int::qsgw::ScopedReferenceEigenvectors; +using librpa_int::qsgw::VelocityMatrix; +using librpa_int::qsgw::align_velocity_to_reference_wfc; +using librpa_int::qsgw::diagonalize_in_reference_basis; +using librpa_int::qsgw::prepare_fhi_aims_interband_velocity; + +namespace +{ + +void assert_close(const cplxdb actual, const cplxdb expected, + const double tolerance = 1.0e-12) +{ + assert(std::abs(actual - expected) < tolerance); +} + +void assert_meanfield_equal(const MeanField& actual, const MeanField& expected) +{ + assert(actual.get_n_spins() == expected.get_n_spins()); + assert(actual.get_n_kpoints() == expected.get_n_kpoints()); + assert(actual.get_n_bands() == expected.get_n_bands()); + assert(actual.get_n_aos() == expected.get_n_aos()); + assert(actual.get_n_spinor() == expected.get_n_spinor()); + assert_close(actual.get_efermi(), expected.get_efermi()); + + for (int spin = 0; spin < actual.get_n_spins(); ++spin) + { + for (int kpoint = 0; kpoint < actual.get_n_kpoints(); ++kpoint) + { + for (int band = 0; band < actual.get_n_bands(); ++band) + { + assert_close(actual.get_eigenvals()[spin](kpoint, band), + expected.get_eigenvals()[spin](kpoint, band)); + assert_close(actual.get_weight()[spin](kpoint, band), + expected.get_weight()[spin](kpoint, band)); + } + for (int spinor = 0; spinor < actual.get_n_spinor(); ++spinor) + { + const auto& actual_wfc = + actual.get_eigenvectors().at(spin).at(spinor).at(kpoint); + const auto& expected_wfc = + expected.get_eigenvectors().at(spin).at(spinor).at(kpoint); + assert(actual_wfc.nr == expected_wfc.nr); + assert(actual_wfc.nc == expected_wfc.nc); + for (int row = 0; row < actual_wfc.nr; ++row) + { + for (int column = 0; column < actual_wfc.nc; ++column) + { + assert_close(actual_wfc(row, column), + expected_wfc(row, column)); + } + } + } + } + } +} + +void assert_velocity_equal(const VelocityMatrix& actual, + const VelocityMatrix& expected) +{ + assert(actual.size() == expected.size()); + for (std::size_t spin = 0; spin < actual.size(); ++spin) + { + assert(actual[spin].size() == expected[spin].size()); + for (std::size_t kpoint = 0; kpoint < actual[spin].size(); ++kpoint) + { + assert(actual[spin][kpoint].size() == expected[spin][kpoint].size()); + for (std::size_t direction = 0; + direction < actual[spin][kpoint].size(); ++direction) + { + const auto& actual_matrix = actual[spin][kpoint][direction]; + const auto& expected_matrix = expected[spin][kpoint][direction]; + assert(actual_matrix.nr == expected_matrix.nr); + assert(actual_matrix.nc == expected_matrix.nc); + for (int row = 0; row < actual_matrix.nr; ++row) + { + for (int column = 0; column < actual_matrix.nc; ++column) + { + assert_close(actual_matrix(row, column), + expected_matrix(row, column)); + } + } + } + } + } +} + +void initialize_velocity_matrix(VelocityMatrix& velocity, + const int spins, + const int kpoints, + const int states) +{ + velocity.assign(spins, {}); + for (int spin = 0; spin < spins; ++spin) + { + velocity[spin].assign(kpoints, {}); + for (int kpoint = 0; kpoint < kpoints; ++kpoint) + { + velocity[spin][kpoint].assign(3, ComplexMatrix(states, states)); + } + } +} + +template +void assert_throws(Function&& function) +{ + bool threw = false; + try + { + function(); + } + catch (const std::exception&) + { + threw = true; + } + assert(threw); +} + +Matz collect_wfc_rows(const MeanField& meanfield) +{ + Matz output(meanfield.get_n_bands(), + meanfield.get_n_aos() * meanfield.get_n_spinor()); + for (int band = 0; band < meanfield.get_n_bands(); ++band) + { + for (int spinor = 0; spinor < meanfield.get_n_spinor(); ++spinor) + { + const auto& block = meanfield.get_eigenvectors().at(0).at(spinor).at(0); + for (int ao = 0; ao < meanfield.get_n_aos(); ++ao) + { + output(band, ao * meanfield.get_n_spinor() + spinor) = + block(band, ao); + } + } + } + return output; +} + +void initialize_identity_wfc(MeanField& meanfield, const int kpoint = 0) +{ + ComplexMatrix identity(2, 2); + identity(0, 0) = 1.0; + identity(1, 1) = 1.0; + meanfield.get_eigenvectors()[0][0][kpoint] = identity; +} + +void test_scoped_reference_eigenvectors_restores_live_state() +{ + MeanField reference(1, 1, 2, 2, 1); + initialize_identity_wfc(reference); + reference.get_eigenvals()[0](0, 0) = -0.5; + reference.get_eigenvals()[0](0, 1) = 0.5; + const MeanField reference_before = reference; + + MeanField live = reference; + live.get_eigenvals()[0](0, 0) = -0.25; + live.get_eigenvals()[0](0, 1) = 0.75; + live.get_weight()[0](0, 0) = 2.0; + live.get_efermi() = 0.125; + auto& live_wfc = live.get_eigenvectors()[0][0][0]; + live_wfc(0, 0) = 0.0; + live_wfc(0, 1) = 1.0; + live_wfc(1, 0) = -1.0; + live_wfc(1, 1) = 0.0; + const MeanField live_before = live; + + bool threw = false; + try + { + ScopedReferenceEigenvectors scope(live, reference); + const Matz projected_wfc = collect_wfc_rows(live); + const Matz reference_wfc = collect_wfc_rows(reference); + for (int row = 0; row < 2; ++row) + { + for (int column = 0; column < 2; ++column) + { + assert_close(projected_wfc(row, column), + reference_wfc(row, column)); + } + } + assert_close(live.get_eigenvals()[0](0, 0), -0.25); + assert_close(live.get_eigenvals()[0](0, 1), 0.75); + assert_close(live.get_weight()[0](0, 0), 2.0); + assert_close(live.get_efermi(), 0.125); + throw std::runtime_error("exercise exception restoration"); + } + catch (const std::runtime_error&) + { + threw = true; + } + assert(threw); + assert_meanfield_equal(live, live_before); + assert_meanfield_equal(reference, reference_before); + + assert_throws([&] { + ScopedReferenceEigenvectors invalid(reference, reference); + }); + + MeanField mismatched(1, 1, 1, 1, 1); + mismatched.get_eigenvectors()[0][0][0] = ComplexMatrix(1, 1); + const MeanField live_before_mismatch = live; + assert_throws([&] { + ScopedReferenceEigenvectors invalid(live, mismatched); + }); + assert_meanfield_equal(live, live_before_mismatch); +} + +void test_fixed_reference_updates_live_wfc_from_mf0_each_iteration() +{ + MeanField reference(1, 1, 2, 2, 1); + initialize_identity_wfc(reference); + reference.get_eigenvals()[0](0, 0) = -0.5; + reference.get_eigenvals()[0](0, 1) = 0.5; + reference.get_weight()[0](0, 0) = 2.0; + reference.get_efermi() = 0.25; + const MeanField reference_before = reference; + + MeanField live = reference; + SpinKMatrixMap hamiltonian; + hamiltonian[0][0] = Matz(2, 2); + hamiltonian[0][0](0, 0) = 1.0; + hamiltonian[0][0](0, 1) = cplxdb(0.0, 0.4); + hamiltonian[0][0](1, 0) = cplxdb(0.0, -0.4); + hamiltonian[0][0](1, 1) = 2.0; + + const auto result = diagonalize_in_reference_basis( + live, reference, hamiltonian); + const Matz& unitary = result.unitary.at(0).at(0); + const Matz unitary_transpose = transpose(unitary, false); + const Matz unitary_dagger = transpose(unitary, true); + + // MeanField stores ket coefficients as band-by-AO rows, so a basis + // rotation whose eigenvectors are columns of U is C_live = U^T C_0. + const Matz expected_wfc = + unitary_transpose * collect_wfc_rows(reference); + const Matz actual_wfc = collect_wfc_rows(live); + for (int row = 0; row < 2; ++row) + { + for (int column = 0; column < 2; ++column) + { + assert_close(actual_wfc(row, column), expected_wfc(row, column)); + } + } + + assert_meanfield_equal(reference, reference_before); + + // The immutable reference remains the producer basis after live updates. + const Matz reference_after_wfc = collect_wfc_rows(reference); + const Matz reference_before_wfc = collect_wfc_rows(reference_before); + for (int row = 0; row < 2; ++row) + { + for (int column = 0; column < 2; ++column) + { + assert_close(reference_after_wfc(row, column), + reference_before_wfc(row, column)); + } + } + + const Matz diagonal = unitary_dagger * hamiltonian.at(0).at(0) * unitary; + assert_close(diagonal(0, 1), 0.0); + assert_close(diagonal(1, 0), 0.0); + assert_close(live.get_eigenvals()[0](0, 0), diagonal(0, 0).real()); + assert_close(live.get_eigenvals()[0](0, 1), diagonal(1, 1).real()); + + // A second iteration must rotate directly from the immutable reference, + // not compound the first iteration's live wavefunction. + hamiltonian[0][0](0, 0) = 2.0; + hamiltonian[0][0](0, 1) = cplxdb(0.3, 0.2); + hamiltonian[0][0](1, 0) = cplxdb(0.3, -0.2); + hamiltonian[0][0](1, 1) = 0.5; + + const auto second_result = diagonalize_in_reference_basis( + live, reference, hamiltonian); + const Matz& second_unitary = second_result.unitary.at(0).at(0); + const Matz second_unitary_transpose = + transpose(second_unitary, false); + const Matz second_unitary_dagger = transpose(second_unitary, true); + const Matz second_expected_wfc = + second_unitary_transpose * collect_wfc_rows(reference); + const Matz second_actual_wfc = collect_wfc_rows(live); + for (int row = 0; row < 2; ++row) + { + for (int column = 0; column < 2; ++column) + { + assert_close(second_actual_wfc(row, column), + second_expected_wfc(row, column)); + } + } + + const Matz second_diagonal = + second_unitary_dagger * hamiltonian.at(0).at(0) * second_unitary; + assert_close(second_diagonal(0, 1), 0.0); + assert_close(second_diagonal(1, 0), 0.0); + assert_close(live.get_eigenvals()[0](0, 0), + second_diagonal(0, 0).real()); + assert_close(live.get_eigenvals()[0](0, 1), + second_diagonal(1, 1).real()); + + const Matz reference_after_second = collect_wfc_rows(reference); + for (int row = 0; row < 2; ++row) + { + for (int column = 0; column < 2; ++column) + { + assert_close(reference_after_second(row, column), + reference_before_wfc(row, column)); + } + } + assert_meanfield_equal(reference, reference_before); + assert_close(live.get_weight()[0](0, 0), 2.0); + assert_close(live.get_weight()[0](0, 1), 0.0); + assert_close(live.get_efermi(), 0.25); +} + +void test_invalid_late_kpoint_does_not_partially_update_live_state() +{ + MeanField reference(1, 2, 2, 2, 1); + initialize_identity_wfc(reference, 0); + initialize_identity_wfc(reference, 1); + MeanField live = reference; + live.get_eigenvals()[0](0, 0) = -1.0; + live.get_eigenvals()[0](0, 1) = 1.0; + live.get_eigenvals()[0](1, 0) = -2.0; + live.get_eigenvals()[0](1, 1) = 2.0; + const MeanField live_before = live; + + SpinKMatrixMap hamiltonian; + hamiltonian[0][0] = Matz(2, 2); + hamiltonian[0][0](0, 0) = 0.5; + hamiltonian[0][0](1, 1) = 1.5; + hamiltonian[0][1] = Matz(1, 1); + hamiltonian[0][1](0, 0) = 3.0; + + assert_throws([&] { + (void)diagonalize_in_reference_basis( + live, reference, hamiltonian); + }); + + assert_meanfield_equal(live, live_before); + + SpinKMatrixMap valid_hamiltonian = hamiltonian; + valid_hamiltonian[0][1] = Matz(2, 2); + valid_hamiltonian[0][1](0, 0) = 2.0; + valid_hamiltonian[0][1](1, 1) = 3.0; + assert_throws([&] { + (void)diagonalize_in_reference_basis( + reference, reference, valid_hamiltonian); + }); +} + +void test_invalid_matrix_data_does_not_mutate_live_state() +{ + MeanField reference(1, 1, 2, 2, 1); + initialize_identity_wfc(reference); + reference.get_eigenvals()[0](0, 0) = -0.5; + reference.get_eigenvals()[0](0, 1) = 0.5; + MeanField live = reference; + + const auto assert_rejected_without_mutation = [&](const Matz& matrix) { + SpinKMatrixMap hamiltonian; + hamiltonian[0][0] = matrix; + const MeanField live_before = live; + assert_throws([&] { + (void)diagonalize_in_reference_basis( + live, reference, hamiltonian); + }); + assert_meanfield_equal(live, live_before); + }; + + Matz nonhermitian(2, 2); + nonhermitian(0, 0) = 1.0; + nonhermitian(0, 1) = cplxdb(0.0, 0.4); + nonhermitian(1, 0) = cplxdb(0.0, 0.4); + nonhermitian(1, 1) = 2.0; + assert_rejected_without_mutation(nonhermitian); + + Matz nonfinite(2, 2); + nonfinite(0, 0) = std::numeric_limits::quiet_NaN(); + nonfinite(1, 1) = 2.0; + assert_rejected_without_mutation(nonfinite); +} + +void test_velocity_basis_unitary_is_aligned_to_fixed_reference() +{ + MeanField reference(1, 1, 2, 2, 1); + initialize_identity_wfc(reference); + MeanField source_basis = reference; + const std::vector phases{ + cplxdb(0.0, 1.0), cplxdb(0.6, -0.8)}; + for (int band = 0; band < 2; ++band) + { + for (int ao = 0; ao < 2; ++ao) + { + source_basis.get_eigenvectors()[0][0][0](band, ao) *= + phases[static_cast(band)]; + } + } + + VelocityMatrix reference_velocity; + initialize_velocity_matrix(reference_velocity, 1, 1, 2); + reference_velocity[0][0][0](0, 0) = 1.0; + reference_velocity[0][0][0](0, 1) = cplxdb(0.2, 0.3); + reference_velocity[0][0][0](1, 0) = cplxdb(0.2, -0.3); + reference_velocity[0][0][0](1, 1) = -0.5; + reference_velocity[0][0][1](0, 1) = cplxdb(-0.4, 0.1); + reference_velocity[0][0][1](1, 0) = cplxdb(-0.4, -0.1); + reference_velocity[0][0][2](0, 0) = 0.75; + reference_velocity[0][0][2](1, 1) = 1.25; + + VelocityMatrix source_velocity = reference_velocity; + for (int direction = 0; direction < 3; ++direction) + { + for (int row = 0; row < 2; ++row) + { + for (int column = 0; column < 2; ++column) + { + source_velocity[0][0][direction](row, column) = + std::conj(phases[static_cast(row)]) * + reference_velocity[0][0][direction](row, column) * + phases[static_cast(column)]; + } + } + } + + const auto alignment = align_velocity_to_reference_wfc( + source_basis, reference, source_velocity); + assert(alignment.maximum_relative_wfc_residual < 1.0e-14); + assert(alignment.maximum_unitarity_residual < 1.0e-14); + assert(alignment.maximum_basis_inverse_residual < 1.0e-14); + assert(alignment.maximum_transform_deviation_from_identity > 1.0); + assert_velocity_equal(source_velocity, reference_velocity); + + MeanField mixed_basis = reference; + const double inverse_sqrt_two = 1.0 / std::sqrt(2.0); + mixed_basis.get_eigenvectors()[0][0][0](0, 0) = inverse_sqrt_two; + mixed_basis.get_eigenvectors()[0][0][0](0, 1) = inverse_sqrt_two; + mixed_basis.get_eigenvectors()[0][0][0](1, 0) = -inverse_sqrt_two; + mixed_basis.get_eigenvectors()[0][0][0](1, 1) = inverse_sqrt_two; + + ComplexMatrix transform(2, 2); + transform(0, 0) = inverse_sqrt_two; + transform(0, 1) = inverse_sqrt_two; + transform(1, 0) = -inverse_sqrt_two; + transform(1, 1) = inverse_sqrt_two; + VelocityMatrix mixed_velocity = reference_velocity; + for (int direction = 0; direction < 3; ++direction) + { + mixed_velocity[0][0][direction] = + librpa_int::conj(transform) * + reference_velocity[0][0][direction] * + transpose(transform, false); + } + const auto mixed_alignment = align_velocity_to_reference_wfc( + mixed_basis, reference, mixed_velocity); + assert(mixed_alignment.maximum_relative_wfc_residual < 1.0e-14); + assert(mixed_alignment.maximum_unitarity_residual < 1.0e-14); + assert(mixed_alignment.maximum_transform_deviation_from_identity > 0.7); + assert_velocity_equal(mixed_velocity, reference_velocity); + + MeanField rounded_basis = reference; + rounded_basis.get_eigenvectors()[0][0][0](0, 0) = 1.0 + 2.0e-10; + rounded_basis.get_eigenvectors()[0][0][0](1, 1) = 1.0 - 1.0e-10; + VelocityMatrix rounded_velocity = reference_velocity; + const auto rounded_alignment = align_velocity_to_reference_wfc( + rounded_basis, reference, rounded_velocity); + assert(rounded_alignment.maximum_relative_wfc_residual > 1.0e-11); + assert(rounded_alignment.maximum_relative_wfc_residual < 1.0e-8); + assert(rounded_alignment.maximum_unitarity_residual < 1.0e-14); + assert(rounded_alignment.maximum_raw_unitarity_residual > 1.0e-10); + assert(rounded_alignment.maximum_unitary_projection_correction > 1.0e-11); + assert_velocity_equal(rounded_velocity, reference_velocity); + + MeanField nonunitary_basis = reference; + nonunitary_basis.get_eigenvectors()[0][0][0](0, 0) = 2.0; + VelocityMatrix rejected_velocity = reference_velocity; + const VelocityMatrix before_rejection = rejected_velocity; + assert_throws([&] { + (void)align_velocity_to_reference_wfc( + nonunitary_basis, reference, rejected_velocity); + }); + assert_velocity_equal(rejected_velocity, before_rejection); +} + +void test_fhi_aims_interband_velocity_is_prepared_in_qsgw_only() +{ + MeanField reference(1, 1, 2, 2, 1); + VelocityMatrix velocity; + initialize_velocity_matrix(velocity, 1, 1, 2); + for (int direction = 0; direction < 3; ++direction) + { + const cplxdb off_diagonal( + 0.25 + direction, -0.5 + 0.1 * direction); + velocity[0][0][direction](0, 0) = + cplxdb(1.0 + direction, 0.4); + velocity[0][0][direction](0, 1) = off_diagonal; + velocity[0][0][direction](1, 0) = std::conj(off_diagonal); + velocity[0][0][direction](1, 1) = + cplxdb(-2.0 - direction, -0.7); + } + const VelocityMatrix before = velocity; + + prepare_fhi_aims_interband_velocity(velocity, reference); + for (int direction = 0; direction < 3; ++direction) + { + assert_close(velocity[0][0][direction](0, 0), 0.0); + assert_close(velocity[0][0][direction](1, 1), 0.0); + assert_close(velocity[0][0][direction](0, 1), + before[0][0][direction](0, 1)); + assert_close(velocity[0][0][direction](1, 0), + before[0][0][direction](1, 0)); + } + + VelocityMatrix invalid = before; + invalid[0][0][2](1, 0) += cplxdb(0.0, 0.25); + const VelocityMatrix invalid_before = invalid; + assert_throws([&] { + prepare_fhi_aims_interband_velocity(invalid, reference); + }); + assert_velocity_equal(invalid, invalid_before); +} + +} // namespace + +int main() +{ + test_scoped_reference_eigenvectors_restores_live_state(); + test_fixed_reference_updates_live_wfc_from_mf0_each_iteration(); + test_invalid_late_kpoint_does_not_partially_update_live_state(); + test_invalid_matrix_data_does_not_mutate_live_state(); + test_velocity_basis_unitary_is_aligned_to_fixed_reference(); + test_fhi_aims_interband_velocity_is_prepared_in_qsgw_only(); + std::cout << "test_qsgw_fixed_basis: all tests passed\n"; + return 0; +} diff --git a/src/test/test_qsgw_fixed_basis_mpi.cpp b/src/test/test_qsgw_fixed_basis_mpi.cpp new file mode 100644 index 00000000..8b3a05b4 --- /dev/null +++ b/src/test/test_qsgw_fixed_basis_mpi.cpp @@ -0,0 +1,196 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "../qsgw/fixed_basis.h" + +#include +#include +#include +#include +#include +#include + +#include "../io/global_io.h" +#include "../mpi/global_mpi.h" + +using librpa_int::ComplexMatrix; +using librpa_int::MeanField; +using librpa_int::cplxdb; +using librpa_int::global::finalize_global_io; +using librpa_int::global::finalize_global_mpi; +using librpa_int::global::init_global_io; +using librpa_int::global::init_global_mpi; +using librpa_int::global::mpi_comm_global_h; +using librpa_int::global::myid_global; +using librpa_int::global::size_global; +using librpa_int::qsgw::VelocityMatrix; +using librpa_int::qsgw::align_distributed_velocity_to_reference_wfc; + +namespace +{ + +void initialize_velocity(VelocityMatrix& velocity, + const int kpoints, + const int states) +{ + velocity.assign(1, {}); + velocity[0].assign(kpoints, {}); + for (int kpoint = 0; kpoint < kpoints; ++kpoint) + { + velocity[0][kpoint].assign( + 3, ComplexMatrix(states, states)); + } +} + +void assert_close(const cplxdb actual, + const cplxdb expected, + const double tolerance = 1.0e-12) +{ + assert(std::abs(actual - expected) < tolerance); +} + +void assert_velocity_equal(const VelocityMatrix& actual, + const VelocityMatrix& expected) +{ + assert(actual.size() == expected.size()); + for (std::size_t spin = 0; spin < actual.size(); ++spin) + { + assert(actual[spin].size() == expected[spin].size()); + for (std::size_t kpoint = 0; + kpoint < actual[spin].size(); ++kpoint) + { + assert(actual[spin][kpoint].size() == + expected[spin][kpoint].size()); + for (std::size_t direction = 0; + direction < actual[spin][kpoint].size(); ++direction) + { + const auto& lhs = actual[spin][kpoint][direction]; + const auto& rhs = expected[spin][kpoint][direction]; + assert(lhs.nr == rhs.nr); + assert(lhs.nc == rhs.nc); + for (int row = 0; row < lhs.nr; ++row) + { + for (int column = 0; column < lhs.nc; ++column) + { + assert_close(lhs(row, column), + rhs(row, column)); + } + } + } + } + } +} + +void test_distributed_velocity_basis_alignment() +{ + assert(size_global == 4); + constexpr int states = 2; + const int kpoints = size_global; + MeanField reference(1, kpoints, states, states, 1); + MeanField distributed_basis(1, kpoints, states, states, 1); + VelocityMatrix expected_velocity; + VelocityMatrix source_velocity; + initialize_velocity(expected_velocity, kpoints, states); + + for (int kpoint = 0; kpoint < kpoints; ++kpoint) + { + ComplexMatrix identity(states, states); + identity.set_as_identity_matrix(); + reference.get_eigenvectors()[0][0][kpoint] = identity; + + const std::vector phases{ + std::polar(1.0, 0.2 * (kpoint + 1)), + std::polar(1.0, -0.3 * (kpoint + 1))}; + if (kpoint % size_global == myid_global) + { + ComplexMatrix phased = identity; + for (int band = 0; band < states; ++band) + { + for (int ao = 0; ao < states; ++ao) + { + phased(band, ao) *= + phases[static_cast(band)]; + } + } + distributed_basis.get_eigenvectors()[0][0][kpoint] = + std::move(phased); + } + + for (int direction = 0; direction < 3; ++direction) + { + auto& matrix = expected_velocity[0][kpoint][direction]; + matrix(0, 0) = 0.5 + kpoint + direction; + matrix(1, 1) = -0.25 - 0.1 * kpoint; + matrix(0, 1) = cplxdb( + 0.2 + 0.01 * kpoint, 0.3 + 0.02 * direction); + matrix(1, 0) = std::conj(matrix(0, 1)); + } + } + + source_velocity = expected_velocity; + for (int kpoint = 0; kpoint < kpoints; ++kpoint) + { + const std::vector phases{ + std::polar(1.0, 0.2 * (kpoint + 1)), + std::polar(1.0, -0.3 * (kpoint + 1))}; + for (int direction = 0; direction < 3; ++direction) + { + for (int row = 0; row < states; ++row) + { + for (int column = 0; column < states; ++column) + { + source_velocity[0][kpoint][direction](row, column) = + std::conj(phases[static_cast(row)]) * + expected_velocity[0][kpoint][direction](row, column) * + phases[static_cast(column)]; + } + } + } + } + + const auto alignment = align_distributed_velocity_to_reference_wfc( + distributed_basis, reference, source_velocity, + mpi_comm_global_h); + assert(alignment.maximum_relative_wfc_residual < 1.0e-14); + assert(alignment.maximum_unitarity_residual < 1.0e-14); + assert(alignment.maximum_transform_deviation_from_identity > 0.1); + assert_velocity_equal(source_velocity, expected_velocity); + + MeanField missing_basis = distributed_basis; + if (myid_global == 0) + { + missing_basis.get_eigenvectors()[0][0].erase(0); + } + VelocityMatrix untouched = expected_velocity; + bool threw = false; + try + { + (void)align_distributed_velocity_to_reference_wfc( + missing_basis, reference, untouched, + mpi_comm_global_h); + } + catch (const std::invalid_argument&) + { + threw = true; + } + assert(threw); + assert_velocity_equal(untouched, expected_velocity); +} + +} // namespace + +int main(int argc, char* argv[]) +{ + int provided = 0; + MPI_Init_thread(&argc, &argv, MPI_THREAD_MULTIPLE, &provided); + init_global_mpi(MPI_COMM_WORLD); + init_global_io(); + + test_distributed_velocity_basis_alignment(); + + finalize_global_io(); + finalize_global_mpi(); + MPI_Finalize(); + return 0; +} diff --git a/src/test/test_qsgw_hamiltonian_cut.cpp b/src/test/test_qsgw_hamiltonian_cut.cpp new file mode 100644 index 00000000..fe597437 --- /dev/null +++ b/src/test/test_qsgw_hamiltonian_cut.cpp @@ -0,0 +1,247 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "../qsgw/hamiltonian_cut.h" + +#include +#include +#include +#include +#include +#include + +using librpa_int::MAJOR; +using librpa_int::Matz; +using librpa_int::MeanField; +using librpa_int::cplxdb; +using librpa_int::qsgw::HamiltonianCutMode; +using librpa_int::qsgw::HamiltonianCutOptions; +using librpa_int::qsgw::SpinKMatrixMap; +using librpa_int::qsgw::apply_hamiltonian_cut; +using librpa_int::qsgw::hamiltonian_cut_mode_from_int; + +namespace +{ + +template +void assert_throws(Function&& function) +{ + bool threw = false; + try + { + function(); + } + catch (const std::exception&) + { + threw = true; + } + assert(threw); +} + +void assert_close(const cplxdb actual, const cplxdb expected, + const double tolerance = 1.0e-13) +{ + assert(std::abs(actual - expected) < tolerance); +} + +Matz make_matrix(const int dimension, const double diagonal_offset) +{ + Matz result(dimension, dimension, MAJOR::ROW); + for (int row = 0; row < dimension; ++row) + { + result(row, row) = diagonal_offset + row; + for (int column = row + 1; column < dimension; ++column) + { + const cplxdb value(0.1 * (row + 1), 0.05 * (column + 1)); + result(row, column) = value; + result(column, row) = std::conj(value); + } + } + return result; +} + +MeanField make_live_meanfield() +{ + MeanField live(1, 1, 5, 1, 1); + live.get_efermi() = 0.0; + live.get_eigenvals()[0](0, 0) = -2.0; + live.get_eigenvals()[0](0, 1) = -0.5; + live.get_eigenvals()[0](0, 2) = 0.0; + live.get_eigenvals()[0](0, 3) = 0.4; + live.get_eigenvals()[0](0, 4) = 0.8; + return live; +} + +void test_mode_zero_is_an_exact_uncut_copy() +{ + MeanField live = make_live_meanfield(); + live.get_efermi() = std::numeric_limits::quiet_NaN(); + live.get_eigenvals()[0](0, 0) = + std::numeric_limits::quiet_NaN(); + SpinKMatrixMap raw; + SpinKMatrixMap reference; + raw[0][0] = make_matrix(5, 10.0); + reference[0][0] = make_matrix(5, -3.0); + const Matz raw_before = raw.at(0).at(0).copy(); + + HamiltonianCutOptions options; + options.mode = HamiltonianCutMode::Uncut; + options.unoccupied_keep = 0; + options.shift_ha = 123.0; + const auto result = apply_hamiltonian_cut(raw, reference, live, options); + + for (int row = 0; row < 5; ++row) + { + for (int column = 0; column < 5; ++column) + { + assert_close(result.at(0).at(0)(row, column), + raw_before(row, column)); + assert_close(raw.at(0).at(0)(row, column), + raw_before(row, column)); + } + } +} + +void test_cut_modes_require_a_finite_live_spectrum() +{ + MeanField live = make_live_meanfield(); + SpinKMatrixMap raw; + SpinKMatrixMap reference; + raw[0][0] = make_matrix(5, 10.0); + reference[0][0] = make_matrix(5, -3.0); + + HamiltonianCutOptions options; + options.mode = HamiltonianCutMode::ReferenceDiagonal; + live.get_efermi() = std::numeric_limits::quiet_NaN(); + assert_throws([&] { + (void)apply_hamiltonian_cut(raw, reference, live, options); + }); + + live = make_live_meanfield(); + live.get_eigenvals()[0](0, 2) = + std::numeric_limits::quiet_NaN(); + assert_throws([&] { + (void)apply_hamiltonian_cut(raw, reference, live, options); + }); +} + +void test_mode_one_restores_reference_above_legacy_active_limit() +{ + const MeanField live = make_live_meanfield(); + SpinKMatrixMap raw; + SpinKMatrixMap reference; + raw[0][0] = make_matrix(5, 10.0); + reference[0][0] = make_matrix(5, -3.0); + + HamiltonianCutOptions options; + options.mode = HamiltonianCutMode::ReferenceDiagonal; + options.unoccupied_keep = 1; + const auto result = apply_hamiltonian_cut(raw, reference, live, options); + const Matz& cut = result.at(0).at(0); + + // Strict energy < efermi gives Nocc=2. Therefore bands 0,1,2 survive + // and the cut starts at index 3, matching the frozen 8476213 source. + for (int row = 0; row < 3; ++row) + for (int column = 0; column < 3; ++column) + assert_close(cut(row, column), raw.at(0).at(0)(row, column)); + for (int row = 0; row < 5; ++row) + { + for (int column = 0; column < 5; ++column) + { + if (row < 3 && column < 3) continue; + const cplxdb expected = row == column + ? reference.at(0).at(0)(row, row) + : cplxdb(0.0, 0.0); + assert_close(cut(row, column), expected); + } + } +} + +void test_mode_two_adds_hartree_shift_to_reference_diagonal() +{ + const MeanField live = make_live_meanfield(); + SpinKMatrixMap raw; + SpinKMatrixMap reference; + raw[0][0] = make_matrix(5, 10.0); + reference[0][0] = make_matrix(5, -3.0); + + HamiltonianCutOptions options; + options.mode = HamiltonianCutMode::ShiftedReferenceDiagonal; + options.unoccupied_keep = 1; + options.shift_ha = 20.0; + const auto result = apply_hamiltonian_cut(raw, reference, live, options); + const Matz& cut = result.at(0).at(0); + + assert_close(cut(2, 2), raw.at(0).at(0)(2, 2)); + assert_close(cut(3, 3), reference.at(0).at(0)(3, 3) + 20.0); + assert_close(cut(4, 4), reference.at(0).at(0)(4, 4) + 20.0); + assert_close(cut(2, 3), 0.0); + assert_close(cut(3, 2), 0.0); +} + +void test_keep_covering_all_bands_is_uncut() +{ + const MeanField live = make_live_meanfield(); + SpinKMatrixMap raw; + SpinKMatrixMap reference; + raw[0][0] = make_matrix(5, 10.0); + reference[0][0] = make_matrix(5, -3.0); + + HamiltonianCutOptions options; + options.mode = HamiltonianCutMode::ShiftedReferenceDiagonal; + options.unoccupied_keep = 5; + const auto result = apply_hamiltonian_cut(raw, reference, live, options); + for (int row = 0; row < 5; ++row) + for (int column = 0; column < 5; ++column) + assert_close(result.at(0).at(0)(row, column), + raw.at(0).at(0)(row, column)); +} + +void test_invalid_options_and_layouts_fail_closed() +{ + const MeanField live = make_live_meanfield(); + SpinKMatrixMap raw; + SpinKMatrixMap reference; + raw[0][0] = make_matrix(5, 10.0); + reference[0][0] = make_matrix(5, -3.0); + + assert(hamiltonian_cut_mode_from_int(0) == HamiltonianCutMode::Uncut); + assert(hamiltonian_cut_mode_from_int(1) == + HamiltonianCutMode::ReferenceDiagonal); + assert(hamiltonian_cut_mode_from_int(2) == + HamiltonianCutMode::ShiftedReferenceDiagonal); + assert_throws([&] { (void)hamiltonian_cut_mode_from_int(-1); }); + assert_throws([&] { (void)hamiltonian_cut_mode_from_int(3); }); + + HamiltonianCutOptions options; + options.unoccupied_keep = -1; + assert_throws([&] { + (void)apply_hamiltonian_cut(raw, reference, live, options); + }); + options.unoccupied_keep = 1; + options.shift_ha = std::numeric_limits::infinity(); + assert_throws([&] { + (void)apply_hamiltonian_cut(raw, reference, live, options); + }); + options.shift_ha = 20.0; + SpinKMatrixMap wrong_reference = reference; + wrong_reference.at(0).at(0) = make_matrix(4, -3.0); + assert_throws([&] { + (void)apply_hamiltonian_cut(raw, wrong_reference, live, options); + }); +} + +} // namespace + +int main() +{ + test_mode_zero_is_an_exact_uncut_copy(); + test_cut_modes_require_a_finite_live_spectrum(); + test_mode_one_restores_reference_above_legacy_active_limit(); + test_mode_two_adds_hartree_shift_to_reference_diagonal(); + test_keep_covering_all_bands_is_uncut(); + test_invalid_options_and_layouts_fail_closed(); + std::cout << "test_qsgw_hamiltonian_cut: all tests passed\n"; + return 0; +} diff --git a/src/test/test_qsgw_hamiltonian_mixing.cpp b/src/test/test_qsgw_hamiltonian_mixing.cpp new file mode 100644 index 00000000..835e0fd5 --- /dev/null +++ b/src/test/test_qsgw_hamiltonian_mixing.cpp @@ -0,0 +1,394 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "../qsgw/hamiltonian_mixing.h" +#include "../qsgw/hamiltonian_cut.h" + +#include +#include +#include +#include +#include +#include +#include + +using librpa_int::Matz; +using librpa_int::MeanField; +using librpa_int::cplxdb; +using librpa_int::qsgw::HamiltonianCutMode; +using librpa_int::qsgw::HamiltonianCutOptions; +using librpa_int::qsgw::MixingMode; +using librpa_int::qsgw::MixingOptions; +using librpa_int::qsgw::SpinKHamiltonianMixer; +using librpa_int::qsgw::SpinKMatrixMap; +using librpa_int::qsgw::apply_hamiltonian_cut; +using librpa_int::qsgw::measure_spin_k_hamiltonian_residual; + +namespace +{ + +void assert_close(const cplxdb actual, const cplxdb expected, + const double tolerance = 1.0e-13) +{ + assert(std::abs(actual - expected) < tolerance); +} + +template +void assert_throws(Function&& function) +{ + bool threw = false; + try + { + function(); + } + catch (const std::exception&) + { + threw = true; + } + assert(threw); +} + +Matz hermitian_2x2(const double d0, const double d1, + const cplxdb off_diagonal, + const librpa_int::MAJOR major = librpa_int::MAJOR::ROW) +{ + Matz result(2, 2, major); + result(0, 0) = d0; + result(1, 1) = d1; + result(0, 1) = off_diagonal; + result(1, 0) = std::conj(off_diagonal); + return result; +} + +SpinKMatrixMap deep_copy_map(const SpinKMatrixMap& input) +{ + SpinKMatrixMap result; + for (const auto& [spin, by_kpoint] : input) + { + for (const auto& [kpoint, value] : by_kpoint) + { + result[spin][kpoint] = value.copy(); + } + } + return result; +} + +SpinKMatrixMap make_grid(const double shift) +{ + SpinKMatrixMap result; + result[0][0] = hermitian_2x2( + 1.0 + shift, 2.0 + shift, {0.25 + shift, -0.5}); + result[0][1] = hermitian_2x2( + 3.0 + shift, 4.0 + shift, {-0.75, 0.2 + shift}); + result[1][0] = hermitian_2x2( + 5.0 + shift, 6.0 + shift, {0.1, 0.3 + shift}); + result[1][1] = hermitian_2x2( + 7.0 + shift, 8.0 + shift, {-0.4 + shift, -0.2}); + return result; +} + +SpinKMatrixMap make_band(const double shift) +{ + SpinKMatrixMap result; + for (int spin = 0; spin < 2; ++spin) + { + for (int k = 0; k < 3; ++k) + { + Matz value(1, 1); + value(0, 0) = 10.0 * spin + k + shift; + result[spin][k] = value; + } + } + return result; +} + +void assert_linear_map(const SpinKMatrixMap& mixed, + const SpinKMatrixMap& input, + const SpinKMatrixMap& output, + const double beta) +{ + assert(mixed.size() == input.size()); + for (const auto& [spin, by_k] : input) + { + for (const auto& [k, matrix] : by_k) + { + const auto& got = mixed.at(spin).at(k); + const auto& target = output.at(spin).at(k); + assert(got.nr() == matrix.nr()); + assert(got.nc() == matrix.nc()); + for (int row = 0; row < matrix.nr(); ++row) + { + for (int column = 0; column < matrix.nc(); ++column) + { + assert_close( + got(row, column), + (1.0 - beta) * matrix(row, column) + + beta * target(row, column)); + } + } + } + } +} + +void assert_maps_equal(const SpinKMatrixMap& actual, + const SpinKMatrixMap& expected) +{ + assert(actual.size() == expected.size()); + for (const auto& [spin, by_k] : expected) + { + assert(actual.count(spin) == 1); + assert(actual.at(spin).size() == by_k.size()); + for (const auto& [kpoint, matrix] : by_k) + { + const auto& got = actual.at(spin).at(kpoint); + assert(got.nr() == matrix.nr()); + assert(got.nc() == matrix.nc()); + assert(got.major() == matrix.major()); + for (int row = 0; row < matrix.nr(); ++row) + { + for (int column = 0; column < matrix.nc(); ++column) + { + assert_close(got(row, column), matrix(row, column)); + } + } + } + } +} + +void test_grid_and_band_storage_major_are_independent() +{ + MixingOptions options; + options.beta = 0.25; + SpinKHamiltonianMixer mixer(options); + + SpinKMatrixMap grid0; + grid0[0][0] = hermitian_2x2( + 1.0, 2.0, {0.25, -0.5}, librpa_int::MAJOR::COL); + SpinKMatrixMap band0; + band0[0][0] = hermitian_2x2( + 3.0, 4.0, {-0.75, 0.2}, librpa_int::MAJOR::ROW); + SpinKMatrixMap grid_target; + grid_target[0][0] = hermitian_2x2( + 5.0, 6.0, {0.5, 0.25}, librpa_int::MAJOR::ROW); + SpinKMatrixMap band_target; + band_target[0][0] = hermitian_2x2( + 7.0, 8.0, {-0.1, -0.4}, librpa_int::MAJOR::COL); + + mixer.initialize(grid0, band0); + const auto result = mixer.mix(grid_target, band_target); + + assert(result.grid.at(0).at(0).major() == librpa_int::MAJOR::COL); + assert(result.band->at(0).at(0).major() == librpa_int::MAJOR::ROW); + assert_linear_map(result.grid, grid0, grid_target, 0.25); + assert_linear_map(*result.band, band0, band_target, 0.25); +} + +void test_complex_grid_and_different_band_layout_mix_together() +{ + MixingOptions options; + options.beta = 0.2; + SpinKHamiltonianMixer mixer(options); + + const auto grid0 = make_grid(0.0); + const auto band0 = make_band(0.0); + const auto grid_target = make_grid(1.5); + const auto band_target = make_band(5.0); + mixer.initialize(grid0, band0); + + const auto result = mixer.mix(grid_target, band_target); + assert(result.decision.applied_mode == MixingMode::Linear); + assert(result.band.has_value()); + assert_linear_map(result.grid, grid0, grid_target, 0.2); + assert_linear_map(*result.band, band0, band_target, 0.2); + + for (const auto& [spin, by_k] : result.grid) + { + (void)spin; + for (const auto& [k, matrix] : by_k) + { + (void)k; + assert_close(matrix(0, 1), std::conj(matrix(1, 0))); + assert_close(matrix(0, 0).imag(), 0.0); + assert_close(matrix(1, 1).imag(), 0.0); + } + } +} + +void test_band_channel_never_changes_grid_mixing_history_or_decision() +{ + MixingOptions options; + options.mode = MixingMode::Linear; + options.beta = 0.2; + SpinKHamiltonianMixer grid_only(options); + SpinKHamiltonianMixer grid_and_band(options); + const auto grid0 = make_grid(0.0); + const auto band0 = make_band(0.0); + grid_only.initialize(grid0); + grid_and_band.initialize(grid0, band0); + + std::vector grid_targets{ + make_grid(0.7), make_grid(-0.2), make_grid(1.1), + }; + grid_targets[1].at(0).at(0)(0, 1) += cplxdb(0.15, -0.05); + grid_targets[1].at(0).at(0)(1, 0) = + std::conj(grid_targets[1].at(0).at(0)(0, 1)); + grid_targets[2].at(1).at(1)(0, 0) += 0.35; + + for (std::size_t step = 0; step < grid_targets.size(); ++step) + { + const auto grid_result = grid_only.mix(grid_targets[step]); + const auto combined_result = grid_and_band.mix( + grid_targets[step], make_band(2.0 + 3.0 * step)); + assert_maps_equal(combined_result.grid, grid_result.grid); + assert_close(combined_result.residual_l2, + grid_result.residual_l2); + assert_close(combined_result.residual_max, + grid_result.residual_max); + assert(combined_result.decision.requested_mode == + grid_result.decision.requested_mode); + assert(combined_result.decision.applied_mode == + grid_result.decision.applied_mode); + assert(combined_result.decision.fell_back == + grid_result.decision.fell_back); + assert_close(combined_result.decision.reciprocal_condition, + grid_result.decision.reciprocal_condition); + assert(combined_result.decision.coefficients.size() == + grid_result.decision.coefficients.size()); + for (std::size_t index = 0; + index < grid_result.decision.coefficients.size(); ++index) + { + assert_close(combined_result.decision.coefficients[index], + grid_result.decision.coefficients[index]); + } + } +} + +void test_invalid_band_call_is_transactional() +{ + MixingOptions options; + options.beta = 0.2; + const auto grid0 = make_grid(0.0); + const auto band0 = make_band(0.0); + const auto grid_target = make_grid(1.0); + const auto band_target = make_band(2.0); + + SpinKHamiltonianMixer tested(options); + SpinKHamiltonianMixer fresh(options); + tested.initialize(grid0, band0); + fresh.initialize(grid0, band0); + + auto missing_band_k = band_target; + missing_band_k.at(0).erase(1); + assert_throws([&] { tested.mix(grid_target, missing_band_k); }); + + const auto after_rejection = tested.mix(grid_target, band_target); + const auto expected = fresh.mix(grid_target, band_target); + assert_linear_map(after_rejection.grid, grid0, grid_target, 0.2); + assert_linear_map(*after_rejection.band, band0, band_target, 0.2); + assert_close(after_rejection.residual_l2, expected.residual_l2); + assert_close(after_rejection.residual_max, expected.residual_max); +} + +void test_nonhermitian_and_nonfinite_maps_are_rejected() +{ + SpinKHamiltonianMixer mixer; + const auto grid0 = make_grid(0.0); + mixer.initialize(grid0); + + auto nonhermitian = make_grid(1.0); + nonhermitian.at(0).at(0)(1, 0) += cplxdb(0.0, 0.25); + assert_throws([&] { mixer.mix(nonhermitian); }); + + auto nonfinite = make_grid(1.0); + nonfinite.at(0).at(0)(0, 0) = + std::numeric_limits::quiet_NaN(); + assert_throws([&] { mixer.mix(nonfinite); }); +} + +void test_residual_uses_complex_frobenius_norm() +{ + SpinKMatrixMap input; + input[0][0] = Matz(2, 2); + SpinKMatrixMap output = deep_copy_map(input); + output[0][0](0, 0) = 3.0; + output[0][0](0, 1) = {0.0, 4.0}; + output[0][0](1, 0) = {0.0, -4.0}; + const auto residual = measure_spin_k_hamiltonian_residual(output, input); + assert_close(residual.l2, std::sqrt(41.0)); + assert_close(residual.maximum, 4.0); +} + +void test_exact_cut_region_does_not_enter_linear_mixing_residual() +{ + MeanField live(1, 1, 5, 1, 1); + live.get_efermi() = 0.0; + live.get_eigenvals()[0](0, 0) = -2.0; + live.get_eigenvals()[0](0, 1) = -0.5; + live.get_eigenvals()[0](0, 2) = 0.0; + live.get_eigenvals()[0](0, 3) = 0.4; + live.get_eigenvals()[0](0, 4) = 0.8; + + SpinKMatrixMap reference; + reference[0][0] = Matz(5, 5); + SpinKMatrixMap raw = deep_copy_map(reference); + for (int band = 0; band < 5; ++band) + { + reference[0][0](band, band) = -2.0 + band; + raw[0][0](band, band) = 10.0 + band; + } + raw[0][0](0, 1) = cplxdb(1.0, 2.0); + raw[0][0](1, 0) = std::conj(raw[0][0](0, 1)); + raw[0][0](3, 4) = cplxdb(100.0, -50.0); + raw[0][0](4, 3) = std::conj(raw[0][0](3, 4)); + + HamiltonianCutOptions cut_options; + cut_options.mode = HamiltonianCutMode::ShiftedReferenceDiagonal; + cut_options.unoccupied_keep = 1; + cut_options.shift_ha = 20.0; + const auto cut_reference = apply_hamiltonian_cut( + reference, reference, live, cut_options); + const auto cut_raw = apply_hamiltonian_cut( + raw, reference, live, cut_options); + + MixingOptions mixing_options; + mixing_options.mode = MixingMode::Linear; + mixing_options.beta = 0.2; + SpinKHamiltonianMixer mixer(mixing_options); + mixer.initialize(cut_reference); + const auto mixed = mixer.mix(cut_raw); + const Matz& matrix = mixed.grid.at(0).at(0); + + assert_close(matrix(0, 0), 0.8 * reference.at(0).at(0)(0, 0) + + 0.2 * raw.at(0).at(0)(0, 0)); + assert_close(matrix(0, 1), 0.2 * raw.at(0).at(0)(0, 1)); + for (int band = 3; band < 5; ++band) + { + assert_close(matrix(band, band), + reference.at(0).at(0)(band, band) + 20.0); + } + assert_close(matrix(3, 4), 0.0); + assert_close(matrix(4, 3), 0.0); + + const auto projected = apply_hamiltonian_cut( + mixed.grid, reference, live, cut_options); + mixer.initialize(projected); + const auto repeated = mixer.mix(projected); + assert_close(repeated.residual_l2, 0.0); + assert_close(repeated.residual_max, 0.0); +} + +} // namespace + +int main() +{ + test_complex_grid_and_different_band_layout_mix_together(); + test_grid_and_band_storage_major_are_independent(); + test_band_channel_never_changes_grid_mixing_history_or_decision(); + test_invalid_band_call_is_transactional(); + test_nonhermitian_and_nonfinite_maps_are_rejected(); + test_residual_uses_complex_frobenius_norm(); + test_exact_cut_region_does_not_enter_linear_mixing_residual(); + std::cout << "test_qsgw_hamiltonian_mixing: all tests passed\n"; + return 0; +} diff --git a/src/test/test_qsgw_input_contract.cpp b/src/test/test_qsgw_input_contract.cpp new file mode 100644 index 00000000..e9dd0407 --- /dev/null +++ b/src/test/test_qsgw_input_contract.cpp @@ -0,0 +1,265 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "../qsgw/input_contract.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using librpa_int::qsgw::BandUpdateMode; +using librpa_int::qsgw::HeadwingGridMode; +using librpa_int::qsgw::HeadwingUpdateMode; +using librpa_int::qsgw::QsgwInputContract; +using librpa_int::qsgw::QsgwProducer; +using librpa_int::qsgw::resolve_band_reference_paths; +using librpa_int::qsgw::resolve_same_grid_velocity_paths; +using librpa_int::qsgw::validate_band_reference_binding; +using librpa_int::qsgw::validate_qsgw_execution_modes; +using librpa_int::qsgw::validate_scf_input_binding; + +namespace +{ + +constexpr const char* abc_sha256 = + "ba7816bf8f01cfea414140de5dae2223" + "b00361a396177a9cb410ff61f20015ad"; + +template +void assert_throws(Function&& function) +{ + bool threw = false; + try + { + function(); + } + catch (const std::exception&) + { + threw = true; + } + assert(threw); +} + +std::string supported_contract(const bool head, const bool band) +{ + std::ostringstream text; + text << "# librpa-qsgw-input-contract-v1\n" + << "producer\tfhi-aims\n" + << "internal_energy_units\thartree\n" + << "mf0_basis\tstate_coefficients_in_nao\n" + << "mf0_gauge\tproducer_state\n" + << "n_spins\t1\n" + << "n_bands\t4\n" + << "n_aos\t4\n" + << "n_scf_kpoints\t2\n" + << "n_headwing_kpoints\t" << (head ? 2 : 0) << "\n" + << "n_band_kpoints\t" << (band ? 2 : 0) << "\n" + << "headwing_grid\t" << (head ? "scf" : "disabled") << "\n" + << "headwing_update\t" + << (head ? "fixed_reference" : "none") << "\n" + << "hartree_update\toff\n" + << "band_update\t" + << (band ? "fixed_basis_rotation" : "off") << "\n" + << "role\tsha256\tfile\n" + << "mf0_eigenvalues\t" << abc_sha256 + << "\tmf0_eigenvalues.dat\n" + << "mf0_wavefunctions\t" << abc_sha256 + << "\tmf0_wavefunctions.dat\n" + << "scf_kpoints\t" << abc_sha256 + << "\tscf_kpoints.dat\n" + << "vxc_scf_manifest\t" << abc_sha256 + << "\tvxc_scf.manifest\n" + << "reader_static\t" << abc_sha256 + << "\treader_static.dat\n"; + if (head) + { + text << "velocity_mf0\t" << abc_sha256 + << "\tmommat_ks_kpt_000001.dat\n" + << "velocity_mf0\t" << abc_sha256 + << "\tmommat_ks_kpt_000002.dat\n"; + } + if (band) + { + text << "band_kpoints\t" << abc_sha256 + << "\tband_kpath_info\n" + << "vxc_band_manifest\t" << abc_sha256 + << "\tvxc_band.manifest\n"; + for (int kpoint = 1; kpoint <= 2; ++kpoint) + { + std::ostringstream index; + index << std::setw(5) << std::setfill('0') << kpoint; + text << "band_mf0_eigenvalues\t" << abc_sha256 + << "\tband_KS_eigenvalue_k_" << index.str() << ".txt\n" + << "band_mf0_wavefunctions\t" << abc_sha256 + << "\tband_KS_eigenvector_k_" << index.str() << ".txt\n"; + } + } + return text.str(); +} + +QsgwInputContract parse(const std::string& text) +{ + std::istringstream input(text); + return QsgwInputContract::parse(input, "test-contract"); +} + +void test_supported_head_only_band_contract() +{ + const QsgwInputContract contract = parse(supported_contract(true, true)); + assert(contract.producer() == QsgwProducer::FhiAims); + assert(contract.headwing_grid() == HeadwingGridMode::ScfGrid); + assert(contract.headwing_update() == + HeadwingUpdateMode::FixedReference); + assert(contract.band_update() == BandUpdateMode::FixedBasisRotation); + assert(contract.n_spins() == 1); + assert(contract.n_bands() == 4); + assert(contract.n_aos() == 4); + assert(contract.n_scf_kpoints() == 2); + assert(contract.n_headwing_kpoints() == 2); + assert(contract.n_band_kpoints() == 2); + assert(contract.files("velocity_mf0").size() == 2); + + validate_qsgw_execution_modes( + contract, HeadwingGridMode::ScfGrid, true); + assert_throws([&] { + validate_qsgw_execution_modes( + contract, HeadwingGridMode::Disabled, true); + }); + assert_throws([&] { + validate_qsgw_execution_modes( + contract, HeadwingGridMode::ScfGrid, false); + }); +} + +void test_disabled_head_and_band_contract() +{ + const QsgwInputContract contract = parse(supported_contract(false, false)); + assert(contract.headwing_grid() == HeadwingGridMode::Disabled); + assert(contract.headwing_update() == HeadwingUpdateMode::None); + assert(contract.band_update() == BandUpdateMode::Off); + validate_qsgw_execution_modes( + contract, HeadwingGridMode::Disabled, false); +} + +void test_unsupported_modes_are_rejected() +{ + std::string independent = supported_contract(true, false); + const auto grid = independent.find("headwing_grid\tscf"); + independent.replace(grid, std::string("headwing_grid\tscf").size(), + "headwing_grid\tindependent_full"); + assert_throws([&] { (void)parse(independent); }); + + std::string live_fourier = supported_contract(true, false); + const auto update = + live_fourier.find("headwing_update\tfixed_reference"); + live_fourier.replace( + update, + std::string("headwing_update\tfixed_reference").size(), + "headwing_update\tlive_ao_fourier"); + assert_throws([&] { (void)parse(live_fourier); }); + + std::string hartree = supported_contract(true, false); + const auto mode = hartree.find("hartree_update\toff"); + hartree.replace(mode, std::string("hartree_update\toff").size(), + "hartree_update\tdelta_density"); + assert_throws([&] { (void)parse(hartree); }); +} + +void test_reader_bindings_match_exact_files() +{ + const std::filesystem::path root = + std::filesystem::absolute("test_qsgw_contract_binding.tmp") + .lexically_normal(); + const QsgwInputContract contract = parse(supported_contract(true, true)); + + validate_scf_input_binding( + contract, root.string(), root / "mf0_eigenvalues.dat", + {root / "mf0_wavefunctions.dat"}, root / "scf_kpoints.dat", + {root / "reader_static.dat"}); + assert_throws([&] { + validate_scf_input_binding( + contract, root.string(), root / "mf0_eigenvalues.dat", + {root / "wrong_wavefunctions.dat"}, root / "scf_kpoints.dat", + {root / "reader_static.dat"}); + }); + + validate_band_reference_binding( + contract, root.string(), root.string(), "band_kpath_info"); + assert_throws([&] { + validate_band_reference_binding( + contract, root.string(), root.string(), "wrong_kpath_info"); + }); +} + +void test_reader_path_conventions() +{ + const std::filesystem::path root = + "test_qsgw_reader_paths.tmp"; + const auto band = resolve_band_reference_paths( + root.string(), "band_kpath_info", 2); + const std::filesystem::path absolute_root = + std::filesystem::absolute(root).lexically_normal(); + assert(band.kpoints == absolute_root / "band_kpath_info"); + assert(band.eigenvalues.front() == + absolute_root / "band_KS_eigenvalue_k_00001.txt"); + assert(band.wavefunctions.back() == + absolute_root / "band_KS_eigenvector_k_00002.txt"); + + const auto aims = resolve_same_grid_velocity_paths( + QsgwProducer::FhiAims, root.string(), 2); + assert(aims.size() == 2); + assert(aims.front() == + absolute_root / "mommat_ks_kpt_000001.dat"); + assert(aims.back() == + absolute_root / "mommat_ks_kpt_000002.dat"); +} + +void test_contract_hashes_are_checked() +{ + const std::filesystem::path root = + "test_qsgw_contract_hashes.tmp"; + std::filesystem::remove_all(root); + std::filesystem::create_directories(root); + const QsgwInputContract contract = parse(supported_contract(true, true)); + for (const std::string& role : { + "mf0_eigenvalues", "mf0_wavefunctions", "scf_kpoints", + "vxc_scf_manifest", "reader_static", "velocity_mf0", + "band_kpoints", "vxc_band_manifest", + "band_mf0_eigenvalues", "band_mf0_wavefunctions"}) + { + for (const auto& file : contract.files(role)) + { + std::ofstream output(root / file.file, std::ios::binary); + output << "abc"; + } + } + contract.validate_file_hashes(root.string()); + { + std::ofstream output(root / "vxc_scf.manifest", + std::ios::binary | std::ios::trunc); + output << "changed"; + } + assert_throws([&] { contract.validate_file_hashes(root.string()); }); + std::filesystem::remove_all(root); +} + +} // namespace + +int main() +{ + test_supported_head_only_band_contract(); + test_disabled_head_and_band_contract(); + test_unsupported_modes_are_rejected(); + test_reader_bindings_match_exact_files(); + test_reader_path_conventions(); + test_contract_hashes_are_checked(); + std::cout << "test_qsgw_input_contract: all tests passed\n"; + return 0; +} diff --git a/src/test/test_qsgw_iteration_trace.cpp b/src/test/test_qsgw_iteration_trace.cpp new file mode 100644 index 00000000..2555fb07 --- /dev/null +++ b/src/test/test_qsgw_iteration_trace.cpp @@ -0,0 +1,481 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "../qsgw/iteration_trace.h" + +#include +#include +#include +#include +#include +#include +#include + +using librpa_int::MeanField; +using librpa_int::Vector3_Order; +using librpa_int::qsgw::IterationChannel; +using librpa_int::qsgw::IterationSummary; +using librpa_int::qsgw::MixingMode; +using librpa_int::qsgw::SpinKFrequencyMatrixMap; +using librpa_int::qsgw::SpinKMatrixMap; +using librpa_int::qsgw::write_eigenvalue_trace; +using librpa_int::qsgw::write_eigenvalue_trace_header; +using librpa_int::qsgw::write_frequency_matrix_component_trace; +using librpa_int::qsgw::write_iteration_summary; +using librpa_int::qsgw::write_iteration_summary_header; +using librpa_int::qsgw::write_matrix_component_trace; +using librpa_int::qsgw::write_matrix_trace_header; +using librpa_int::qsgw::write_occupation_trace; +using librpa_int::qsgw::write_scalar_component_trace; +using librpa_int::qsgw::write_velocity_trace; +using librpa_int::qsgw::write_wavefunction_trace; + +namespace +{ + +template +void assert_throws(Function&& function) +{ + bool threw = false; + try + { + function(); + } + catch (const std::exception&) + { + threw = true; + } + assert(threw); +} + +void test_summary_is_numeric_through_the_convergence_column() +{ + IterationSummary summary; + summary.iteration = 3; + summary.maximum_eigenvalue_change_ev = 0.125; + summary.residual_l2_ha = 0.25; + summary.residual_max_ha = 0.5; + summary.fermi_energy_ev = 1.5; + summary.gap_ev = 2.5; + summary.electron_count = 8.0; + summary.requested_mode = MixingMode::Linear; + summary.applied_mode = MixingMode::Linear; + summary.beta = 0.2; + summary.fell_back = true; + summary.reciprocal_condition = 1.0e-14; + summary.coefficients = {0.25, -0.75}; + summary.converged = false; + summary.fallback_reason = "ill conditioned residual system"; + + std::ostringstream output; + write_iteration_summary_header(output); + write_iteration_summary(output, summary); + + std::istringstream lines(output.str()); + std::string header; + std::string row; + std::getline(lines, header); + std::getline(lines, row); + assert(header.find("requested_mode") != std::string::npos); + assert(header.find("fallback_reason") != std::string::npos); + + std::istringstream values(row); + int iteration = -1; + double max_delta = 0.0; + double residual_l2 = 0.0; + double residual_max = 0.0; + double efermi = 0.0; + double gap = 0.0; + double electron_count = 0.0; + int requested = -9; + int applied = -9; + double beta = 0.0; + int fallback = 0; + double rcond = 0.0; + double coefficient_l1 = 0.0; + int coefficient_count = 0; + int converged = 1; + std::string coefficients; + std::string reason; + values >> iteration >> max_delta >> residual_l2 >> residual_max >> + efermi >> gap >> electron_count >> requested >> applied >> beta >> fallback >> + rcond >> coefficient_l1 >> coefficient_count >> converged >> + coefficients >> reason; + assert(values); + assert(iteration == 3); + assert(std::abs(max_delta - 0.125) < 1.0e-14); + assert(std::abs(electron_count - 8.0) < 1.0e-14); + assert(requested == 0); + assert(applied == 0); + assert(std::abs(beta - 0.2) < 1.0e-14); + assert(fallback == 1); + assert(std::abs(rcond - 1.0e-14) < 1.0e-25); + assert(std::abs(coefficient_l1 - 1.0) < 1.0e-14); + assert(coefficient_count == 2); + assert(converged == 0); + assert(coefficients.find(',') != std::string::npos); + assert(reason == "ill_conditioned_residual_system"); +} + +void test_iteration_zero_and_band_channel_eigenvalues_are_explicit() +{ + MeanField meanfield(1, 2, 2, 1, 1); + meanfield.get_eigenvals()[0](0, 0) = -0.5; + meanfield.get_eigenvals()[0](0, 1) = 0.25; + meanfield.get_eigenvals()[0](1, 0) = -0.4; + meanfield.get_eigenvals()[0](1, 1) = 0.35; + const std::vector> kpoints{ + {0.0, 0.0, 0.0}, + {0.5, 0.0, 0.0}, + }; + + std::ostringstream output; + write_eigenvalue_trace_header(output); + write_eigenvalue_trace( + output, 0, IterationChannel::Band, meanfield, kpoints); + + std::istringstream lines(output.str()); + std::string header; + std::getline(lines, header); + assert(header.find("energy_eV") != std::string::npos); + + int row_count = 0; + std::string row; + while (std::getline(lines, row)) + { + if (row.empty()) continue; + std::istringstream values(row); + int iteration = -1; + int channel = -1; + int spin = -1; + int kpoint = -1; + double kx = 0.0; + double ky = 0.0; + double kz = 0.0; + int band = -1; + double energy_ev = 0.0; + values >> iteration >> channel >> spin >> kpoint >> + kx >> ky >> kz >> band >> energy_ev; + assert(values); + assert(iteration == 0); + assert(channel == 1); + assert(spin == 0); + assert(kpoint == row_count / 2); + assert(band == row_count % 2); + ++row_count; + } + assert(row_count == 4); + + std::ostringstream headwing_output; + write_eigenvalue_trace( + headwing_output, 3, IterationChannel::Headwing, meanfield, kpoints); + std::istringstream headwing_rows(headwing_output.str()); + int iteration = -1; + int channel = -1; + headwing_rows >> iteration >> channel; + assert(headwing_rows); + assert(iteration == 3); + assert(channel == 2); +} + +void test_invalid_trace_inputs_are_rejected() +{ + MeanField meanfield(1, 1, 1, 1, 1); + std::ostringstream output; + assert_throws([&] { + write_eigenvalue_trace( + output, -1, IterationChannel::Grid, meanfield, + {{0.0, 0.0, 0.0}}); + }); + assert_throws([&] { + write_eigenvalue_trace( + output, 0, IterationChannel::Grid, meanfield, {}); + }); +} + +void test_matrix_components_include_full_complex_data_and_frequency_index() +{ + SpinKMatrixMap exchange; + exchange[0][2] = librpa_int::Matz(2, 2, librpa_int::MAJOR::ROW); + const double roundtrip_value = std::nextafter(1.0, 2.0); + exchange[0][2](0, 0) = {roundtrip_value, 0.0}; + exchange[0][2](0, 1) = {2.0, 3.0}; + exchange[0][2](1, 0) = {2.0, -3.0}; + exchange[0][2](1, 1) = {4.0, 0.0}; + + SpinKFrequencyMatrixMap sigma; + sigma[0][2][0.25] = exchange[0][2].copy(); + sigma[0][2][1.50] = exchange[0][2].copy(); + + std::ostringstream output; + write_matrix_trace_header(output); + write_matrix_component_trace( + output, 1, IterationChannel::Grid, "exx", exchange); + write_frequency_matrix_component_trace( + output, 1, IterationChannel::Band, "sigma_c_iw", sigma); + + std::istringstream lines(output.str()); + std::string header; + std::getline(lines, header); + assert(header.find("frequency_index") != std::string::npos); + assert(header.find("imag_value") != std::string::npos); + + int rows = 0; + std::string row; + while (std::getline(lines, row)) + { + if (row.empty()) continue; + std::istringstream values(row); + int iteration = -1; + int channel = -1; + std::string component; + int spin = -1; + int kpoint = -1; + int frequency_index = -2; + double frequency = 0.0; + int matrix_row = -1; + int matrix_column = -1; + double real = 0.0; + double imaginary = 0.0; + values >> iteration >> channel >> component >> spin >> kpoint >> + frequency_index >> frequency >> matrix_row >> matrix_column >> + real >> imaginary; + assert(values); + assert(iteration == 1); + assert(spin == 0); + assert(kpoint == 2); + if (rows < 4) + { + assert(channel == 0); + assert(component == "exx"); + assert(frequency_index == -1); + assert(frequency == 0.0); + } + else + { + assert(channel == 1); + assert(component == "sigma_c_iw"); + assert(frequency_index == (rows - 4) / 4); + assert(std::abs(frequency - + (frequency_index == 0 ? 0.25 : 1.50)) < 1.0e-14); + } + if (matrix_row == 0 && matrix_column == 1) + { + assert(real == 2.0); + assert(imaginary == 3.0); + } + if (matrix_row == 0 && matrix_column == 0) + { + assert(real == roundtrip_value); + } + ++rows; + } + assert(rows == 12); +} + +void test_invalid_matrix_component_trace_is_rejected() +{ + SpinKMatrixMap valid; + valid[0][0] = librpa_int::Matz(1, 1, librpa_int::MAJOR::ROW); + valid[0][0](0, 0) = 1.0; + std::ostringstream output; + assert_throws([&] { + write_matrix_component_trace( + output, -1, IterationChannel::Grid, "exx", valid); + }); + assert_throws([&] { + write_matrix_component_trace( + output, 1, IterationChannel::Grid, "bad component", valid); + }); + assert_throws([&] { + write_matrix_component_trace( + output, 1, IterationChannel::Grid, "empty", {}); + }); + valid[0][0](0, 0) = { + std::numeric_limits::quiet_NaN(), 0.0}; + assert_throws([&] { + write_matrix_component_trace( + output, 1, IterationChannel::Grid, "nonfinite", valid); + }); +} + +void test_occupation_and_scalar_components_are_explicit() +{ + MeanField meanfield(1, 2, 2, 1, 1); + meanfield.get_weight()[0](0, 0) = 0.5; + meanfield.get_weight()[0](0, 1) = 0.0; + meanfield.get_weight()[0](1, 0) = 0.25; + meanfield.get_weight()[0](1, 1) = 0.25; + + std::ostringstream output; + write_occupation_trace( + output, 4, IterationChannel::Grid, meanfield); + write_scalar_component_trace( + output, 4, IterationChannel::Grid, "fermi_energy_ha", -0.125); + write_scalar_component_trace( + output, 4, IterationChannel::Grid, "electron_count", 2.0); + write_scalar_component_trace( + output, 4, IterationChannel::Grid, "gap_ha", 0.25); + + int occupation_rows = 0; + int scalar_rows = 0; + std::istringstream lines(output.str()); + std::string row; + while (std::getline(lines, row)) + { + if (row.empty()) continue; + std::istringstream values(row); + int iteration = -1; + int channel = -1; + std::string component; + int spin = -1; + int kpoint = -1; + int frequency_index = -2; + double frequency = -1.0; + int matrix_row = -1; + int matrix_column = -1; + double real = 0.0; + double imaginary = 0.0; + values >> iteration >> channel >> component >> spin >> kpoint >> + frequency_index >> frequency >> matrix_row >> matrix_column >> + real >> imaginary; + assert(values); + assert(iteration == 4); + assert(channel == 0); + assert(frequency_index == -1); + assert(frequency == 0.0); + assert(imaginary == 0.0); + if (component == "occupation") + { + assert(spin == 0); + assert(kpoint == occupation_rows / 2); + assert(matrix_row == 0); + assert(matrix_column == occupation_rows % 2); + ++occupation_rows; + } + else + { + assert(component == "fermi_energy_ha" || + component == "electron_count" || component == "gap_ha"); + assert(spin == 0); + assert(kpoint == 0); + assert(matrix_row == 0); + assert(matrix_column == 0); + ++scalar_rows; + } + } + assert(occupation_rows == 4); + assert(scalar_rows == 3); + + assert_throws([&] { + write_scalar_component_trace( + output, 4, IterationChannel::Grid, "bad", + std::numeric_limits::quiet_NaN()); + }); +} + +void test_wavefunction_and_velocity_components_are_explicit() +{ + MeanField meanfield(1, 1, 2, 3, 1); + auto& wfc = meanfield.get_eigenvectors()[0][0][0]; + wfc.create(2, 3); + for (int row = 0; row < wfc.nr; ++row) + { + for (int column = 0; column < wfc.nc; ++column) + { + wfc(row, column) = { + static_cast(10 * row + column), + static_cast(row - column)}; + } + } + + std::vector>> velocity(1); + velocity[0].resize(1); + velocity[0][0].resize(3); + for (int direction = 0; direction < 3; ++direction) + { + velocity[0][0][direction].create(2, 2); + for (int row = 0; row < 2; ++row) + { + for (int column = 0; column < 2; ++column) + { + velocity[0][0][direction](row, column) = { + static_cast(direction + row + column), + static_cast(row - column)}; + } + } + } + + std::ostringstream output; + write_wavefunction_trace( + output, 2, IterationChannel::Grid, meanfield); + write_velocity_trace( + output, 2, IterationChannel::Grid, velocity); + + int wfc_rows = 0; + int velocity_rows = 0; + std::istringstream lines(output.str()); + std::string row; + while (std::getline(lines, row)) + { + if (row.empty()) continue; + std::istringstream values(row); + int iteration = -1; + int channel = -1; + std::string component; + values >> iteration >> channel >> component; + assert(values); + assert(iteration == 2); + assert(channel == 0); + if (component == "wfc_spinor0") + { + ++wfc_rows; + } + else + { + assert(component == "velocity_x" || + component == "velocity_y" || + component == "velocity_z"); + ++velocity_rows; + } + } + assert(wfc_rows == 6); + assert(velocity_rows == 12); + + std::ostringstream provenance_output; + write_wavefunction_trace( + provenance_output, 0, IterationChannel::Headwing, meanfield, + "headwing_reader_wfc"); + write_velocity_trace( + provenance_output, 0, IterationChannel::Headwing, velocity, + "headwing_reader_velocity"); + assert(provenance_output.str().find( + "headwing_reader_wfc_spinor0") != std::string::npos); + assert(provenance_output.str().find( + "headwing_reader_velocity_x") != std::string::npos); + assert(provenance_output.str().find( + "headwing_reader_velocity_y") != std::string::npos); + assert(provenance_output.str().find( + "headwing_reader_velocity_z") != std::string::npos); + + velocity[0][0].pop_back(); + assert_throws([&] { + write_velocity_trace( + output, 2, IterationChannel::Grid, velocity); + }); +} + +} // namespace + +int main() +{ + test_summary_is_numeric_through_the_convergence_column(); + test_iteration_zero_and_band_channel_eigenvalues_are_explicit(); + test_invalid_trace_inputs_are_rejected(); + test_matrix_components_include_full_complex_data_and_frequency_index(); + test_invalid_matrix_component_trace_is_rejected(); + test_occupation_and_scalar_components_are_explicit(); + test_wavefunction_and_velocity_components_are_explicit(); + return 0; +} diff --git a/src/test/test_qsgw_mixing.cpp b/src/test/test_qsgw_mixing.cpp new file mode 100644 index 00000000..7b4467c0 --- /dev/null +++ b/src/test/test_qsgw_mixing.cpp @@ -0,0 +1,176 @@ +// These tests intentionally use assert; keep it active in Release test builds. +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "../qsgw/mixing.h" + +#include +#include +#include +#include +#include + +using librpa_int::matrix; +using librpa_int::qsgw::HamiltonianMixer; +using librpa_int::qsgw::MixingMode; +using librpa_int::qsgw::MixingOptions; + +namespace +{ + +void assert_close(const double actual, const double expected) +{ + assert(std::abs(actual - expected) < 1.0e-14); +} + +template +void assert_throws(Function&& function) +{ + bool threw = false; + try + { + function(); + } + catch (const std::exception&) + { + threw = true; + } + assert(threw); +} + +void test_default_linear_mixing_updates_grid_and_band_together() +{ + HamiltonianMixer mixer; + + matrix grid0(2, 1, true); + grid0(0, 0) = 1.0; + grid0(1, 0) = 2.0; + matrix band0(1, 1, true); + band0(0, 0) = 10.0; + mixer.initialize(grid0, band0); + + matrix grid_output(2, 1, true); + grid_output(0, 0) = 3.0; + grid_output(1, 0) = 6.0; + matrix band_output(1, 1, true); + band_output(0, 0) = 20.0; + + const auto result = mixer.mix(grid_output, band_output); + assert(result.decision.requested_mode == MixingMode::Linear); + assert(result.decision.applied_mode == MixingMode::Linear); + assert_close(result.decision.beta, 0.2); + assert(!result.decision.fell_back); + assert(result.decision.coefficients.size() == 1); + assert_close(result.decision.coefficients.front(), 1.0); + assert(result.band.has_value()); + assert_close(result.grid(0, 0), 1.4); + assert_close(result.grid(1, 0), 2.8); + assert_close(result.band->operator()(0, 0), 12.0); +} + +void test_reported_residual_is_unmixed_even_when_beta_is_one() +{ + MixingOptions options; + options.beta = 1.0; + HamiltonianMixer mixer(options); + + matrix grid0(2, 1, true); + mixer.initialize(grid0); + matrix output(2, 1, true); + output(0, 0) = 3.0; + output(1, 0) = 4.0; + + const auto result = mixer.mix(output); + assert_close(result.residual_l2, 5.0); + assert_close(result.residual_max, 4.0); + assert_close(result.grid(0, 0), 3.0); + assert_close(result.grid(1, 0), 4.0); +} + +void test_rejected_band_shape_does_not_change_linear_state() +{ + HamiltonianMixer tested; + HamiltonianMixer fresh; + + matrix grid0(2, 1, true); + matrix band0(1, 1, true); + tested.initialize(grid0, band0); + fresh.initialize(grid0, band0); + + matrix rejected_grid(2, 1, true); + rejected_grid(0, 0) = 1.0; + matrix rejected_band(2, 1, true); + assert_throws([&] { tested.mix(rejected_grid, rejected_band); }); + + matrix accepted_grid(2, 1, true); + accepted_grid(1, 0) = 1.0; + matrix accepted_band(1, 1, true); + accepted_band(0, 0) = 2.0; + const auto actual = tested.mix(accepted_grid, accepted_band); + const auto expected = fresh.mix(accepted_grid, accepted_band); + + assert_close(actual.grid(0, 0), expected.grid(0, 0)); + assert_close(actual.grid(1, 0), expected.grid(1, 0)); + assert_close(actual.band->operator()(0, 0), + expected.band->operator()(0, 0)); +} + +void test_nonfinite_input_or_output_is_rejected_before_state_mutation() +{ + HamiltonianMixer tested; + HamiltonianMixer fresh; + + matrix grid0(2, 1, true); + matrix band0(1, 1, true); + tested.initialize(grid0, band0); + fresh.initialize(grid0, band0); + + matrix nonfinite_grid(2, 1, true); + nonfinite_grid(0, 0) = std::numeric_limits::quiet_NaN(); + assert_throws([&] { tested.mix(nonfinite_grid, band0); }); + + matrix accepted_grid(2, 1, true); + accepted_grid(0, 0) = 1.0; + matrix accepted_band(1, 1, true); + accepted_band(0, 0) = 2.0; + const auto actual = tested.mix(accepted_grid, accepted_band); + const auto expected = fresh.mix(accepted_grid, accepted_band); + assert_close(actual.grid(0, 0), expected.grid(0, 0)); + assert_close(actual.band->operator()(0, 0), + expected.band->operator()(0, 0)); +} + +void test_overflowing_residual_is_rejected_before_state_mutation() +{ + HamiltonianMixer tested; + HamiltonianMixer fresh; + + matrix grid0(1, 1, true); + grid0(0, 0) = std::numeric_limits::max(); + tested.initialize(grid0); + fresh.initialize(grid0); + + matrix overflowing_output(1, 1, true); + overflowing_output(0, 0) = -std::numeric_limits::max(); + assert_throws([&] { tested.mix(overflowing_output); }); + + const auto actual = tested.mix(grid0); + const auto expected = fresh.mix(grid0); + assert_close(actual.grid(0, 0), expected.grid(0, 0)); + assert_close(actual.residual_l2, expected.residual_l2); + assert_close(actual.residual_max, expected.residual_max); +} + +} // namespace + +int main() +{ + test_default_linear_mixing_updates_grid_and_band_together(); + test_reported_residual_is_unmixed_even_when_beta_is_one(); + test_rejected_band_shape_does_not_change_linear_state(); + test_nonfinite_input_or_output_is_rejected_before_state_mutation(); + test_overflowing_residual_is_rejected_before_state_mutation(); + std::cout << "test_qsgw_mixing: all tests passed\n"; + return 0; +} diff --git a/src/test/test_qsgw_occupation.cpp b/src/test/test_qsgw_occupation.cpp new file mode 100644 index 00000000..e5350b19 --- /dev/null +++ b/src/test/test_qsgw_occupation.cpp @@ -0,0 +1,326 @@ +// These tests intentionally use assert; keep it active in Release test builds. +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "../qsgw/occupation.h" + +#include +#include +#include +#include +#include +#include + +using librpa_int::MeanField; +using librpa_int::qsgw::OccupationSettings; +using librpa_int::qsgw::analyze_qsgw_occupations; +using librpa_int::qsgw::physical_electron_count; +using librpa_int::qsgw::update_qsgw_occupations; + +namespace +{ + +void assert_close(const double actual, const double expected, const double tolerance = 1.0e-12) +{ + assert(std::abs(actual - expected) < tolerance); +} + +template +void assert_invalid_argument(Function&& function) +{ + bool threw = false; + try + { + function(); + } + catch (const std::invalid_argument&) + { + threw = true; + } + assert(threw); +} + +double stored_total_weight(const MeanField& meanfield) +{ + double result = 0.0; + for (int spin = 0; spin < meanfield.get_n_spins(); ++spin) + { + for (int kpoint = 0; kpoint < meanfield.get_n_kpoints(); ++kpoint) + { + for (int band = 0; band < meanfield.get_n_bands(); ++band) + { + result += meanfield.get_weight()[spin](kpoint, band); + } + } + } + return result; +} + +void test_analysis_preserves_input_meanfield() +{ + MeanField meanfield(1, 1, 2, 2, 1); + meanfield.get_weight()[0].zero_out(); + meanfield.get_weight()[0](0, 0) = 2.0; + meanfield.get_eigenvals()[0](0, 0) = -1.0; + meanfield.get_eigenvals()[0](0, 1) = 2.0; + meanfield.get_efermi() = 0.25; + + const auto result = analyze_qsgw_occupations( + meanfield, {1.0}, 2.0, OccupationSettings{}); + + assert_close(meanfield.get_weight()[0](0, 0), 2.0); + assert_close(meanfield.get_weight()[0](0, 1), 0.0); + assert_close(meanfield.get_efermi(), 0.25); + assert_close(result.chemical_potential, 0.5); + assert_close(result.electron_count, 2.0); + assert_close(result.gap, 3.0); +} + +void test_global_filling_preserves_nonuniform_kpoint_weights() +{ + MeanField reference(1, 2, 2, 2, 1); + reference.get_weight()[0].zero_out(); + reference.get_weight()[0](0, 0) = 1.5; + reference.get_weight()[0](1, 0) = 0.5; + + MeanField live = reference; + live.get_eigenvals()[0](0, 0) = -1.0; + live.get_eigenvals()[0](0, 1) = 2.0; + live.get_eigenvals()[0](1, 0) = -0.5; + live.get_eigenvals()[0](1, 1) = 3.0; + + const std::vector kpoint_weights{0.75, 0.25}; + const auto result = update_qsgw_occupations( + live, reference, kpoint_weights, 2.0, OccupationSettings{}); + + assert_close(live.get_weight()[0](0, 0), 1.5); + assert_close(live.get_weight()[0](1, 0), 0.5); + assert_close(live.get_weight()[0](0, 1), 0.0); + assert_close(live.get_weight()[0](1, 1), 0.0); + assert_close(stored_total_weight(live), 2.0); + assert_close(physical_electron_count(live, kpoint_weights), 2.0); + assert_close(result.electron_count, 2.0); + assert(result.chemical_potential > -0.5); + assert(result.chemical_potential < 2.0); + assert(!result.metallic); +} + +void test_physical_electron_count_uses_stored_geometric_weights_once() +{ + MeanField reference(1, 2, 1, 1, 1); + reference.get_weight()[0].zero_out(); + reference.get_weight()[0](0, 0) = 1.5; + + const std::vector kpoint_weights{0.75, 0.25}; + assert_close(stored_total_weight(reference), 1.5); + assert_close(physical_electron_count(reference, kpoint_weights), 1.5); +} + +void test_global_spin_filling_does_not_fill_one_electron_per_spin() +{ + MeanField reference(2, 1, 2, 2, 1); + reference.get_weight()[0].zero_out(); + reference.get_weight()[1].zero_out(); + reference.get_weight()[0](0, 0) = 1.0; + + MeanField live = reference; + live.get_eigenvals()[0](0, 0) = -1.0; + live.get_eigenvals()[0](0, 1) = 10.0; + live.get_eigenvals()[1](0, 0) = 0.0; + live.get_eigenvals()[1](0, 1) = 20.0; + + const auto result = update_qsgw_occupations( + live, reference, {1.0}, 1.0, OccupationSettings{}); + + assert_close(live.get_weight()[0](0, 0), 1.0); + assert_close(live.get_weight()[0](0, 1), 0.0); + assert_close(live.get_weight()[1](0, 0), 0.0); + assert_close(live.get_weight()[1](0, 1), 0.0); + assert_close(stored_total_weight(live), 1.0); + assert_close(result.electron_count, 1.0); + assert(result.chemical_potential > -1.0); + assert(result.chemical_potential < 0.0); +} + +void test_degenerate_frontier_uses_one_capacity_fraction() +{ + MeanField reference(1, 2, 1, 1, 1); + reference.get_weight()[0].zero_out(); + reference.get_weight()[0](0, 0) = 0.5; + reference.get_weight()[0](1, 0) = 0.5; + + MeanField live = reference; + live.get_eigenvals()[0](0, 0) = 0.0; + live.get_eigenvals()[0](1, 0) = 0.0; + + const auto result = update_qsgw_occupations( + live, reference, {0.75, 0.25}, 1.0, OccupationSettings{}); + + assert_close(live.get_weight()[0](0, 0), 0.75); + assert_close(live.get_weight()[0](1, 0), 0.25); + assert_close(result.electron_count, 1.0); + assert_close(result.chemical_potential, 0.0); + assert_close(result.gap, 0.0); + assert(result.metallic); +} + +void test_overlapping_reference_manifolds_report_zero_gap() +{ + MeanField reference(1, 2, 2, 2, 1); + reference.get_weight()[0].zero_out(); + reference.get_weight()[0](0, 0) = 1.0; + reference.get_weight()[0](1, 0) = 1.0; + + MeanField live = reference; + live.get_eigenvals()[0](0, 0) = 1.0; + live.get_eigenvals()[0](0, 1) = 2.0; + live.get_eigenvals()[0](1, 0) = 0.0; + live.get_eigenvals()[0](1, 1) = 0.5; + + const auto result = update_qsgw_occupations( + live, reference, {0.5, 0.5}, 2.0, OccupationSettings{}); + + assert_close(result.electron_count, 2.0); + assert_close(result.vbm, 1.0); + assert_close(result.cbm, 0.5); + assert_close(result.gap, 0.0); + assert_close(result.chemical_potential, 0.75); + assert(result.metallic); +} + +void test_spinor_capacity_is_one_electron_per_state() +{ + MeanField reference(1, 1, 2, 2, 2); + reference.get_weight()[0].zero_out(); + reference.get_weight()[0](0, 0) = 1.0; + + MeanField live = reference; + live.get_eigenvals()[0](0, 0) = -1.0; + live.get_eigenvals()[0](0, 1) = 1.0; + + const auto result = update_qsgw_occupations( + live, reference, {1.0}, 1.0, OccupationSettings{}); + + assert_close(live.get_weight()[0](0, 0), 1.0); + assert_close(live.get_weight()[0](0, 1), 0.0); + assert_close(result.electron_count, 1.0); + assert_close(result.chemical_potential, 0.0); + assert(!result.metallic); +} + +void test_zero_weight_kpoint_has_zero_storage_capacity() +{ + MeanField reference(1, 2, 1, 1, 1); + reference.get_weight()[0].zero_out(); + reference.get_weight()[0](0, 0) = 2.0; + + MeanField live = reference; + live.get_eigenvals()[0](0, 0) = 0.0; + live.get_eigenvals()[0](1, 0) = 0.0; + + const auto result = update_qsgw_occupations( + live, reference, {1.0, 0.0}, 2.0, OccupationSettings{}); + + assert_close(live.get_weight()[0](0, 0), 2.0); + assert_close(live.get_weight()[0](1, 0), 0.0); + assert_close(result.electron_count, 2.0); +} + +void test_nonfinite_energy_rejection_preserves_live_state() +{ + MeanField reference(1, 1, 2, 2, 1); + reference.get_weight()[0].zero_out(); + reference.get_weight()[0](0, 0) = 2.0; + + MeanField live = reference; + live.get_weight()[0](0, 0) = 0.25; + live.get_weight()[0](0, 1) = 0.75; + live.get_efermi() = 0.125; + live.get_eigenvals()[0](0, 0) = + std::numeric_limits::quiet_NaN(); + live.get_eigenvals()[0](0, 1) = 1.0; + + assert_invalid_argument([&]() { + update_qsgw_occupations( + live, reference, {1.0}, 2.0, OccupationSettings{}); + }); + assert_close(live.get_weight()[0](0, 0), 0.25); + assert_close(live.get_weight()[0](0, 1), 0.75); + assert_close(live.get_efermi(), 0.125); +} + +void test_invalid_reference_rejection_preserves_live_state() +{ + MeanField reference(1, 1, 2, 2, 1); + reference.get_weight()[0].zero_out(); + reference.get_weight()[0](0, 0) = 2.5; + + MeanField live = reference; + live.get_weight()[0](0, 0) = 0.5; + live.get_weight()[0](0, 1) = 0.25; + live.get_efermi() = -0.25; + + assert_invalid_argument([&]() { + update_qsgw_occupations( + live, reference, {1.0}, 2.5, OccupationSettings{}); + }); + assert_close(live.get_weight()[0](0, 0), 0.5); + assert_close(live.get_weight()[0](0, 1), 0.25); + assert_close(live.get_efermi(), -0.25); +} + +void test_live_reference_alias_is_rejected_without_mutation() +{ + MeanField live(1, 1, 2, 2, 1); + live.get_weight()[0].zero_out(); + live.get_weight()[0](0, 0) = 2.0; + live.get_eigenvals()[0](0, 0) = -1.0; + live.get_eigenvals()[0](0, 1) = 1.0; + live.get_efermi() = 0.25; + + assert_invalid_argument([&]() { + update_qsgw_occupations( + live, live, {1.0}, 2.0, OccupationSettings{}); + }); + assert_close(live.get_weight()[0](0, 0), 2.0); + assert_close(live.get_weight()[0](0, 1), 0.0); + assert_close(live.get_efermi(), 0.25); +} + +void test_finite_temperature_rejection_preserves_live_state() +{ + MeanField reference(1, 1, 1, 1, 1); + reference.get_weight()[0](0, 0) = 2.0; + MeanField live = reference; + live.get_weight()[0](0, 0) = 0.5; + live.get_efermi() = -0.5; + + OccupationSettings settings; + settings.temperature_kelvin = 300.0; + assert_invalid_argument([&]() { + update_qsgw_occupations(live, reference, {1.0}, 2.0, settings); + }); + assert_close(live.get_weight()[0](0, 0), 0.5); + assert_close(live.get_efermi(), -0.5); +} + +} // namespace + +int main() +{ + test_analysis_preserves_input_meanfield(); + test_global_filling_preserves_nonuniform_kpoint_weights(); + test_physical_electron_count_uses_stored_geometric_weights_once(); + test_global_spin_filling_does_not_fill_one_electron_per_spin(); + test_degenerate_frontier_uses_one_capacity_fraction(); + test_overlapping_reference_manifolds_report_zero_gap(); + test_spinor_capacity_is_one_electron_per_state(); + test_zero_weight_kpoint_has_zero_storage_capacity(); + test_nonfinite_energy_rejection_preserves_live_state(); + test_invalid_reference_rejection_preserves_live_state(); + test_live_reference_alias_is_rejected_without_mutation(); + test_finite_temperature_rejection_preserves_live_state(); + std::cout << "test_qsgw_occupation: all tests passed\n"; + return 0; +} diff --git a/src/test/test_qsgw_projection_target.cpp b/src/test/test_qsgw_projection_target.cpp new file mode 100644 index 00000000..69e8aa9b --- /dev/null +++ b/src/test/test_qsgw_projection_target.cpp @@ -0,0 +1,136 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "../qsgw/projection_target.h" + +#include +#include +#include +#include +#include +#include + +using librpa_int::MeanField; +using librpa_int::Vector3_Order; +using librpa_int::qsgw::validate_projection_target; + +namespace +{ + +template +void assert_throws(Function&& function) +{ + bool threw = false; + try + { + function(); + } + catch (const std::exception&) + { + threw = true; + } + assert(threw); +} + +MeanField make_meanfield(const int n_kpoints, const int n_bands, + const int n_aos = 4, const int n_spinors = 1) +{ + MeanField result(1, n_kpoints, n_bands, n_aos, n_spinors); + for (int spinor = 0; spinor < n_spinors; ++spinor) + { + for (int kpoint = 0; kpoint < n_kpoints; ++kpoint) + { + auto& wfc = result.get_eigenvectors()[0][spinor][kpoint]; + wfc.create(n_bands, n_aos); + for (int band = 0; band < n_bands; ++band) + { + for (int ao = 0; ao < n_aos; ++ao) + { + wfc(band, ao) = std::complex( + 100.0 * spinor + 10.0 * kpoint + band + 0.1 * ao, + -0.01 * (spinor + band + ao)); + } + } + } + } + return result; +} + +std::vector> make_kpoints(const int count) +{ + std::vector> result; + for (int index = 0; index < count; ++index) + { + result.push_back({static_cast(index) / count, 0.0, 0.0}); + } + return result; +} + +void test_grid_and_band_targets_may_have_independent_layouts() +{ + const MeanField grid = make_meanfield(2, 3); + const MeanField band = make_meanfield(5, 6); + + const auto grid_shape = validate_projection_target( + grid, make_kpoints(2), 1, 1, 4, "grid"); + const auto band_shape = validate_projection_target( + band, make_kpoints(5), 1, 1, 4, "band"); + + assert(grid_shape.n_kpoints == 2); + assert(grid_shape.n_bands == 3); + assert(band_shape.n_kpoints == 5); + assert(band_shape.n_bands == 6); +} + +void test_missing_or_mismatched_target_data_is_rejected() +{ + MeanField target = make_meanfield(2, 3); + target.get_eigenvectors()[0][0].erase(1); + assert_throws([&] { + validate_projection_target( + target, make_kpoints(2), 1, 1, 4, "grid"); + }); + + const MeanField valid = make_meanfield(2, 3); + assert_throws([&] { + validate_projection_target( + valid, make_kpoints(1), 1, 1, 4, "grid"); + }); + assert_throws([&] { + validate_projection_target( + valid, make_kpoints(2), 2, 1, 4, "grid"); + }); + assert_throws([&] { + validate_projection_target( + valid, make_kpoints(2), 1, 1, 5, "grid"); + }); + + const MeanField spinor_target = make_meanfield(2, 3, 4, 2); + assert_throws([&] { + validate_projection_target( + spinor_target, make_kpoints(2), 1, 1, 4, "band"); + }); +} + +void test_nonfinite_wavefunction_is_rejected() +{ + MeanField target = make_meanfield(1, 2); + target.get_eigenvectors()[0][0][0](0, 0) = + std::complex(std::numeric_limits::quiet_NaN(), 0.0); + assert_throws([&] { + validate_projection_target( + target, make_kpoints(1), 1, 1, 4, "grid"); + }); +} + +} // namespace + +int main() +{ + test_grid_and_band_targets_may_have_independent_layouts(); + test_missing_or_mismatched_target_data_is_rejected(); + test_nonfinite_wavefunction_is_rejected(); + std::cout << "test_qsgw_projection_target: all tests passed\n"; + return 0; +} diff --git a/src/test/test_qsgw_sha256.cpp b/src/test/test_qsgw_sha256.cpp new file mode 100644 index 00000000..a3e4f8e7 --- /dev/null +++ b/src/test/test_qsgw_sha256.cpp @@ -0,0 +1,73 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "../qsgw/sha256.h" + +#include +#include +#include +#include +#include +#include + +using librpa_int::qsgw::is_sha256_hex; +using librpa_int::qsgw::sha256_file; +using librpa_int::qsgw::sha256_string; + +namespace +{ + +template +void assert_throws(Function&& function) +{ + bool threw = false; + try + { + function(); + } + catch (const std::exception&) + { + threw = true; + } + assert(threw); +} + +void test_known_vectors() +{ + assert(sha256_string("") == + "e3b0c44298fc1c149afbf4c8996fb924" + "27ae41e4649b934ca495991b7852b855"); + assert(sha256_string("abc") == + "ba7816bf8f01cfea414140de5dae2223" + "b00361a396177a9cb410ff61f20015ad"); +} + +void test_file_hash_and_validation() +{ + const std::string path = "test_qsgw_sha256_input.tmp"; + { + std::ofstream output(path, std::ios::binary); + output << "abc"; + } + assert(sha256_file(path) == sha256_string("abc")); + std::remove(path.c_str()); + + assert(is_sha256_hex( + "ba7816bf8f01cfea414140de5dae2223" + "b00361a396177a9cb410ff61f20015ad")); + assert(!is_sha256_hex("BA7816BF8F01CFEA414140DE5DAE2223" + "B00361A396177A9CB410FF61F20015AD")); + assert(!is_sha256_hex("abc")); + assert_throws([&] { (void)sha256_file("missing-qsgw-input.dat"); }); +} + +} // namespace + +int main() +{ + test_known_vectors(); + test_file_hash_and_validation(); + std::cout << "test_qsgw_sha256: all tests passed\n"; + return 0; +} diff --git a/src/test/test_qsgw_vxc_io.cpp b/src/test/test_qsgw_vxc_io.cpp new file mode 100644 index 00000000..47d8f15d --- /dev/null +++ b/src/test/test_qsgw_vxc_io.cpp @@ -0,0 +1,529 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "../qsgw/vxc_io.h" +#include "../qsgw/sha256.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using librpa_int::Vector3_Order; +using librpa_int::ComplexMatrix; +using librpa_int::MeanField; +using librpa_int::Matz; +using librpa_int::cplxdb; +using librpa_int::qsgw::VxcDatasetKind; +using librpa_int::qsgw::VxcBasis; +using librpa_int::qsgw::VxcManifest; +using librpa_int::qsgw::VxcGauge; +using librpa_int::qsgw::VxcUnits; +using librpa_int::qsgw::prepare_vxc_in_fixed_state_basis; +using librpa_int::qsgw::project_vxc_nao_to_fixed_basis; +using librpa_int::qsgw::read_abacus_vxc_ha; + +namespace +{ + +void assert_close(const cplxdb actual, const cplxdb expected) +{ + assert(std::abs(actual - expected) < 1.0e-14); +} + +template +void assert_throws(Function&& function) +{ + bool threw = false; + try + { + function(); + } + catch (const std::exception&) + { + threw = true; + } + assert(threw); +} + +void test_abacus_vxc_uses_fixed_rydberg_to_hartree_conversion() +{ + const std::string text = + "# rows 2\n" + "# columns 2\n" + "Row 1\n" + " (2.0,0.0) (1.0,2.0)\n" + "Row 2\n" + " (4.0,0.0)\n"; + + for (const int nspins: {1, 2}) + { + // nspins is intentionally not an input to the parser. ABACUS Vxc is + // in Ry for both scalar and spin-polarized calculations. + (void)nspins; + std::istringstream input(text); + const auto matrix = read_abacus_vxc_ha(input, "synthetic-vxck"); + assert_close(matrix(0, 0), 1.0); + assert_close(matrix(0, 1), cplxdb(0.5, 1.0)); + assert_close(matrix(1, 0), cplxdb(0.5, -1.0)); + assert_close(matrix(1, 1), 2.0); + } +} + +void test_abacus_vxc_accepts_legacy_dimension_first_triangular_layout() +{ + const std::string text = + "3\n" + " (2.0,0.0) (1.0,2.0) (3.0,-1.0)\n" + " (4.0,0.0) (5.0,6.0)\n" + " (8.0,0.0)\n"; + std::istringstream input(text); + const auto matrix = read_abacus_vxc_ha(input, "legacy-vxcs"); + + assert_close(matrix(0, 0), 1.0); + assert_close(matrix(0, 1), cplxdb(0.5, 1.0)); + assert_close(matrix(1, 0), cplxdb(0.5, -1.0)); + assert_close(matrix(0, 2), cplxdb(1.5, -0.5)); + assert_close(matrix(2, 0), cplxdb(1.5, 0.5)); + assert_close(matrix(1, 1), 2.0); + assert_close(matrix(1, 2), cplxdb(2.5, 3.0)); + assert_close(matrix(2, 1), cplxdb(2.5, -3.0)); + assert_close(matrix(2, 2), 4.0); + + std::istringstream incomplete( + "2\n" + " (2.0,0.0) (1.0,0.0)\n"); + assert_throws([&] { + (void)read_abacus_vxc_ha(incomplete, "incomplete-legacy-vxcs"); + }); +} + +void test_abacus_nao_vxc_is_projected_to_the_fixed_state_basis() +{ + MeanField reference(1, 1, 2, 2, 1); + const double inv_sqrt_two = 1.0 / std::sqrt(2.0); + ComplexMatrix wfc(2, 2); + wfc(0, 0) = inv_sqrt_two; + wfc(0, 1) = cplxdb(0.0, inv_sqrt_two); + wfc(1, 0) = cplxdb(0.0, inv_sqrt_two); + wfc(1, 1) = inv_sqrt_two; + reference.get_eigenvectors()[0][0][0] = wfc; + + Matz vxc_nao(2, 2); + vxc_nao(0, 0) = 2.0; + vxc_nao(0, 1) = cplxdb(1.0, 0.5); + vxc_nao(1, 0) = cplxdb(1.0, -0.5); + vxc_nao(1, 1) = 4.0; + + Matz coefficients(2, 2); + for (int row = 0; row < 2; ++row) + { + for (int column = 0; column < 2; ++column) + { + coefficients(row, column) = wfc(row, column); + } + } + const Matz expected = + conj(coefficients) * vxc_nao * transpose(coefficients, false); + const Matz actual = + project_vxc_nao_to_fixed_basis(vxc_nao, reference, 0, 0); + const Matz selected = prepare_vxc_in_fixed_state_basis( + vxc_nao, VxcBasis::Nao, reference, 0, 0); + + for (int row = 0; row < 2; ++row) + { + for (int column = 0; column < 2; ++column) + { + assert_close(actual(row, column), expected(row, column)); + assert_close(selected(row, column), expected(row, column)); + } + } + assert(std::abs(actual(0, 1) - vxc_nao(0, 1)) > 1.0e-6); + + Matz vxc_state(2, 2); + vxc_state(0, 0) = -1.0; + vxc_state(0, 1) = cplxdb(0.2, 0.1); + vxc_state(1, 0) = cplxdb(0.2, -0.1); + vxc_state(1, 1) = 0.5; + const Matz state_selected = prepare_vxc_in_fixed_state_basis( + vxc_state, VxcBasis::State, reference, 0, 0); + for (int row = 0; row < 2; ++row) + { + for (int column = 0; column < 2; ++column) + { + assert_close(state_selected(row, column), vxc_state(row, column)); + } + } +} + +void test_vxc_matrix_validation_rejects_invalid_numeric_data_and_layout() +{ + MeanField reference(1, 1, 2, 2, 1); + ComplexMatrix identity(2, 2); + identity(0, 0) = 1.0; + identity(1, 1) = 1.0; + reference.get_eigenvectors()[0][0][0] = identity; + + Matz wrong_shape(1, 1); + wrong_shape(0, 0) = 1.0; + assert_throws([&] { + (void)prepare_vxc_in_fixed_state_basis( + wrong_shape, VxcBasis::Nao, reference, 0, 0); + }); + assert_throws([&] { + (void)prepare_vxc_in_fixed_state_basis( + wrong_shape, VxcBasis::State, reference, 0, 0); + }); + + Matz nonhermitian(2, 2); + nonhermitian(0, 0) = 1.0; + nonhermitian(0, 1) = cplxdb(0.0, 0.5); + nonhermitian(1, 0) = cplxdb(0.0, 0.5); + nonhermitian(1, 1) = 2.0; + assert_throws([&] { + (void)prepare_vxc_in_fixed_state_basis( + nonhermitian, VxcBasis::State, reference, 0, 0); + }); + + Matz nonfinite(2, 2); + nonfinite(0, 0) = std::numeric_limits::quiet_NaN(); + nonfinite(1, 1) = 2.0; + assert_throws([&] { + (void)prepare_vxc_in_fixed_state_basis( + nonfinite, VxcBasis::Nao, reference, 0, 0); + }); + + const std::string imaginary_diagonal = + "# rows 2\n" + "# columns 2\n" + "Row 1\n" + " (2.0,0.1) (1.0,0.0)\n" + "Row 2\n" + " (4.0,0.0)\n"; + std::istringstream triangular_input(imaginary_diagonal); + assert_throws([&] { + (void)read_abacus_vxc_ha(triangular_input, "imaginary-diagonal"); + }); + + const std::string nonhermitian_dense = + "# rows 2\n" + "# columns 2\n" + " (2.0,0.0) (1.0,1.0)\n" + " (1.0,1.0) (4.0,0.0)\n"; + std::istringstream dense_input(nonhermitian_dense); + assert_throws([&] { + (void)read_abacus_vxc_ha(dense_input, "nonhermitian-dense"); + }); + + const std::string nonfinite_text = + "# rows 1\n" + "# columns 1\n" + "Row 1\n" + " (nan,0.0)\n"; + std::istringstream nonfinite_input(nonfinite_text); + assert_throws([&] { + (void)read_abacus_vxc_ha(nonfinite_input, "nonfinite-vxck"); + }); +} + +std::string valid_grid_manifest() +{ + return + "# librpa-qsgw-vxc-manifest-v2\n" + "kind\tscf\n" + "producer\tabacus\n" + "units\tRy\n" + "basis\tnao\n" + "gauge\tao_bloch\n" + "spin\tk_index\tkx\tky\tkz\trows\tcolumns\tsha256\tfile\n" + "1\t1\t0.0\t0.0\t0.0\t2\t2\tba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad\tvxck1s1_nao.txt\n" + "1\t2\t0.5\t0.5\t0.0\t2\t2\tba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad\tvxck2s1_nao.txt\n"; +} + +std::string valid_abacus_state_grid_manifest() +{ + return + "# librpa-qsgw-vxc-manifest-v2\n" + "kind\tscf\n" + "producer\tabacus\n" + "units\tRy\n" + "basis\tstate\n" + "gauge\tmf0_state\n" + "spin\tk_index\tkx\tky\tkz\trows\tcolumns\tsha256\tfile\n" + "1\t1\t0.0\t0.0\t0.0\t2\t2\tba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad\tvxck1s1_nao.txt\n"; +} + +std::string valid_band_manifest() +{ + return + "# librpa-qsgw-vxc-manifest-v2\n" + "kind\tband\n" + "producer\tabacus\n" + "units\tRy\n" + "basis\tnao\n" + "gauge\tao_bloch\n" + "spin\tk_index\tkx\tky\tkz\trows\tcolumns\tsha256\tfile\n" + "1\t1\t0.0\t0.0\t0.0\t2\t2\tba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad\tvxck1s1_nao.txt\n" + "1\t2\t0.25\t0.25\t0.25\t2\t2\tba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad\tvxck2s1_nao.txt\n"; +} + +void test_manifest_binds_each_matrix_to_spin_index_and_k_coordinate() +{ + std::istringstream input(valid_grid_manifest()); + const VxcManifest manifest = VxcManifest::parse(input, "grid-manifest"); + const std::vector> expected_kpoints{ + {0.0, 0.0, 0.0}, + {0.5, 0.5, 0.0}, + }; + manifest.validate( + VxcDatasetKind::ScfGrid, 1, expected_kpoints, 2, 2, 1.0e-10); + assert(manifest.producer() == "abacus"); + assert(manifest.units() == VxcUnits::Rydberg); + assert(manifest.basis() == VxcBasis::Nao); + assert(manifest.gauge() == VxcGauge::AoBloch); + assert(manifest.at(0, 0).file == "vxck1s1_nao.txt"); + assert(manifest.at(0, 1).file == "vxck2s1_nao.txt"); +} + +void test_abacus_out_mat_xc_manifest_is_state_basis_without_projection() +{ + std::istringstream input(valid_abacus_state_grid_manifest()); + const VxcManifest manifest = + VxcManifest::parse(input, "abacus-out-mat-xc-manifest"); + manifest.validate( + VxcDatasetKind::ScfGrid, 1, {{0.0, 0.0, 0.0}}, 2, 2, 1.0e-10); + assert(manifest.producer() == "abacus"); + assert(manifest.units() == VxcUnits::Rydberg); + assert(manifest.basis() == VxcBasis::State); + assert(manifest.gauge() == VxcGauge::Mf0State); + + MeanField reference(1, 1, 2, 2, 1); + const double inv_sqrt_two = 1.0 / std::sqrt(2.0); + ComplexMatrix wfc(2, 2); + wfc(0, 0) = inv_sqrt_two; + wfc(0, 1) = cplxdb(0.0, inv_sqrt_two); + wfc(1, 0) = cplxdb(0.0, inv_sqrt_two); + wfc(1, 1) = inv_sqrt_two; + reference.get_eigenvectors()[0][0][0] = wfc; + + Matz vxc_state(2, 2); + vxc_state(0, 0) = -1.0; + vxc_state(0, 1) = cplxdb(0.2, 0.1); + vxc_state(1, 0) = cplxdb(0.2, -0.1); + vxc_state(1, 1) = 0.5; + const Matz selected = prepare_vxc_in_fixed_state_basis( + vxc_state, manifest.basis(), reference, 0, 0); + for (int row = 0; row < 2; ++row) + { + for (int column = 0; column < 2; ++column) + { + assert_close(selected(row, column), vxc_state(row, column)); + } + } +} + +void test_fhi_aims_manifest_marks_hartree_state_basis_without_projection() +{ + const std::string text = + "# librpa-qsgw-vxc-manifest-v2\n" + "kind\tscf\n" + "producer\tfhi-aims\n" + "units\tHa\n" + "basis\tstate\n" + "gauge\tmf0_state\n" + "spin\tk_index\tkx\tky\tkz\trows\tcolumns\tsha256\tfile\n" + "1\t1\t0.0\t0.0\t0.0\t2\t2\tba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad\txc_matr_spin_1_kpt_000001.csc\n"; + std::istringstream input(text); + const VxcManifest manifest = VxcManifest::parse(input, "aims-manifest"); + manifest.validate( + VxcDatasetKind::ScfGrid, 1, {{0.0, 0.0, 0.0}}, 2, 2, 1.0e-10); + assert(manifest.producer() == "fhi-aims"); + assert(manifest.units() == VxcUnits::Hartree); + assert(manifest.basis() == VxcBasis::State); + assert(manifest.gauge() == VxcGauge::Mf0State); +} + +void test_manifest_rejects_nscf_files_presented_as_scf_grid() +{ + std::string text = valid_grid_manifest(); + const auto position = text.find("0.5\t0.5\t0.0"); + text.replace(position, std::string("0.5\t0.5\t0.0").size(), + "0.05\t0.0\t0.0"); + std::istringstream input(text); + const VxcManifest manifest = VxcManifest::parse(input, "wrong-grid-manifest"); + const std::vector> expected_kpoints{ + {0.0, 0.0, 0.0}, + {0.5, 0.5, 0.0}, + }; + assert_throws([&] { + manifest.validate( + VxcDatasetKind::ScfGrid, 1, expected_kpoints, 2, 2, 1.0e-10); + }); +} + +void test_manifest_rejects_duplicate_spin_k_entries() +{ + std::string text = valid_grid_manifest(); + text += "1\t2\t0.5\t0.5\t0.0\t2\t2\tba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad\tduplicate.txt\n"; + std::istringstream input(text); + assert_throws([&] { (void)VxcManifest::parse(input, "duplicate-manifest"); }); +} + +void test_band_manifest_is_distinct_from_scf_grid() +{ + std::istringstream input(valid_band_manifest()); + const VxcManifest manifest = VxcManifest::parse(input, "band-manifest"); + const std::vector> band_kpoints{ + {0.0, 0.0, 0.0}, + {0.25, 0.25, 0.25}, + }; + manifest.validate( + VxcDatasetKind::BandPath, 1, band_kpoints, 2, 2, 1.0e-10); + assert_throws([&] { + manifest.validate( + VxcDatasetKind::ScfGrid, 1, band_kpoints, 2, 2, 1.0e-10); + }); +} + +void test_manifest_rejects_missing_spin_or_kpoint_entries() +{ + std::istringstream input(valid_grid_manifest()); + const VxcManifest manifest = VxcManifest::parse(input, "incomplete-spin-manifest"); + const std::vector> expected_kpoints{ + {0.0, 0.0, 0.0}, + {0.5, 0.5, 0.0}, + }; + assert_throws([&] { + manifest.validate( + VxcDatasetKind::ScfGrid, 2, expected_kpoints, 2, 2, 1.0e-10); + }); + + std::string missing_kpoint = valid_grid_manifest(); + const auto last_entry = missing_kpoint.rfind("1\t2\t"); + missing_kpoint.erase(last_entry); + std::istringstream missing_input(missing_kpoint); + const VxcManifest missing = + VxcManifest::parse(missing_input, "incomplete-kpoint-manifest"); + assert_throws([&] { + missing.validate( + VxcDatasetKind::ScfGrid, 1, expected_kpoints, 2, 2, 1.0e-10); + }); +} + +void test_manifest_rejects_unsupported_units() +{ + std::string text = valid_grid_manifest(); + const auto units = text.find("units\tRy"); + text.replace(units, std::string("units\tRy").size(), "units\teV"); + std::istringstream input(text); + assert_throws([&] { + (void)VxcManifest::parse(input, "wrong-units-manifest"); + }); +} + +void test_manifest_rejects_incompatible_producer_units_or_basis() +{ + const auto assert_bad_manifest = [](std::string text, + const std::string& needle, + const std::string& replacement) { + const auto position = text.find(needle); + assert(position != std::string::npos); + text.replace(position, needle.size(), replacement); + std::istringstream input(text); + assert_throws([&] { + (void)VxcManifest::parse(input, "incompatible-vxc-manifest"); + }); + }; + + assert_bad_manifest(valid_grid_manifest(), "units\tRy", "units\tHa"); + assert_bad_manifest(valid_grid_manifest(), "basis\tnao", "basis\tstate"); + assert_bad_manifest(valid_grid_manifest(), "gauge\tao_bloch", "gauge\tmf0_state"); + + const std::string aims_manifest = + "# librpa-qsgw-vxc-manifest-v2\n" + "kind\tscf\n" + "producer\tfhi-aims\n" + "units\tHa\n" + "basis\tstate\n" + "gauge\tmf0_state\n" + "spin\tk_index\tkx\tky\tkz\trows\tcolumns\tsha256\tfile\n" + "1\t1\t0.0\t0.0\t0.0\t2\t2\tba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad\txc_matr_spin_1_kpt_000001.csc\n"; + assert_bad_manifest(aims_manifest, "units\tHa", "units\tRy"); + assert_bad_manifest(aims_manifest, "basis\tstate", "basis\tnao"); +} + +void test_manifest_rejects_shape_hash_gauge_and_changed_files() +{ + const auto assert_bad = [](std::string text, + const std::string& needle, + const std::string& replacement) { + const auto position = text.find(needle); + assert(position != std::string::npos); + text.replace(position, needle.size(), replacement); + std::istringstream input(text); + assert_throws([&] { (void)VxcManifest::parse(input, "bad-v2"); }); + }; + assert_bad(valid_grid_manifest(), "gauge\tao_bloch", "gauge\tmf0_state"); + assert_bad(valid_grid_manifest(), + "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", + "not-a-sha256"); + + std::istringstream shape_input(valid_grid_manifest()); + const VxcManifest shape_manifest = + VxcManifest::parse(shape_input, "shape-v2"); + assert_throws([&] { + shape_manifest.validate( + VxcDatasetKind::ScfGrid, 1, + {{0.0, 0.0, 0.0}, {0.5, 0.5, 0.0}}, 3, 3, 1.0e-10); + }); + + const std::string path = "test_qsgw_vxc_hash_input.tmp"; + { + std::ofstream output(path, std::ios::binary); + output << "abc"; + } + std::string one_file = valid_grid_manifest(); + one_file.erase(one_file.rfind("1\t2\t")); + const auto file_position = one_file.find("vxck1s1_nao.txt"); + one_file.replace(file_position, std::string("vxck1s1_nao.txt").size(), path); + std::istringstream file_input(one_file); + const VxcManifest file_manifest = + VxcManifest::parse(file_input, "hash-v2"); + file_manifest.validate_file_hashes("."); + { + std::ofstream output(path, std::ios::binary | std::ios::app); + output << "changed"; + } + assert_throws([&] { file_manifest.validate_file_hashes("."); }); + std::remove(path.c_str()); +} + +} // namespace + +int main() +{ + test_abacus_vxc_uses_fixed_rydberg_to_hartree_conversion(); + test_abacus_vxc_accepts_legacy_dimension_first_triangular_layout(); + test_abacus_nao_vxc_is_projected_to_the_fixed_state_basis(); + test_vxc_matrix_validation_rejects_invalid_numeric_data_and_layout(); + test_manifest_binds_each_matrix_to_spin_index_and_k_coordinate(); + test_abacus_out_mat_xc_manifest_is_state_basis_without_projection(); + test_fhi_aims_manifest_marks_hartree_state_basis_without_projection(); + test_manifest_rejects_nscf_files_presented_as_scf_grid(); + test_manifest_rejects_duplicate_spin_k_entries(); + test_band_manifest_is_distinct_from_scf_grid(); + test_manifest_rejects_missing_spin_or_kpoint_entries(); + test_manifest_rejects_unsupported_units(); + test_manifest_rejects_incompatible_producer_units_or_basis(); + test_manifest_rejects_shape_hash_gauge_and_changed_files(); + std::cout << "test_qsgw_vxc_io: all tests passed\n"; + return 0; +}