diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 000000000..839b1a1d3 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,51 @@ +--- +# eOn baseline clang-tidy ruleset. +# +# Scope: high-signal categories only -- `bugprone-*` (real bugs), +# `cert-*` (security / undefined-behaviour), `modernize-use-nullptr` +# / `modernize-use-override` / `modernize-use-equals-default` +# (cheap C++20 hygiene), `readability-redundant-*` and +# `readability-qualified-auto` (low-risk cleanup), +# `cppcoreguidelines-pro-type-cstyle-cast` and -member-init. +# +# Deliberately excluded: `cppcoreguidelines-avoid-magic-numbers` +# (atomistic constants are intrinsically magic), -pro-bounds-* (too +# noisy for Eigen-heavy code), `modernize-use-trailing-return-type` +# (style preference, not a bug class), `fuchsia-*`, `llvm-*`, +# `google-readability-todo` (we use TODO(rg)). +# +# The vendored thirdparty/ tree is excluded via .clang-tidy in that +# directory inheriting `Checks: '-*'`. +Checks: > + bugprone-*, + -bugprone-easily-swappable-parameters, + -bugprone-narrowing-conversions, + cert-*, + -cert-err58-cpp, + cppcoreguidelines-pro-type-cstyle-cast, + cppcoreguidelines-pro-type-member-init, + cppcoreguidelines-slicing, + cppcoreguidelines-virtual-class-destructor, + modernize-use-nullptr, + modernize-use-override, + modernize-use-equals-default, + modernize-use-equals-delete, + modernize-redundant-void-arg, + modernize-deprecated-headers, + performance-for-range-copy, + performance-implicit-conversion-in-loop, + performance-inefficient-vector-operation, + performance-unnecessary-value-param, + readability-qualified-auto, + readability-redundant-control-flow, + readability-redundant-string-cstr, + readability-redundant-string-init, + readability-redundant-smartptr-get +WarningsAsErrors: "" +HeaderFilterRegex: "client/(?!thirdparty/).*" +FormatStyle: file +CheckOptions: + - key: readability-qualified-auto.AddConstToQualified + value: "true" + - key: modernize-use-override.IgnoreDestructors + value: "true" diff --git a/.github/workflows/ci_docs.yml b/.github/workflows/ci_docs.yml index 965ab7c4f..ad8186a6d 100644 --- a/.github/workflows/ci_docs.yml +++ b/.github/workflows/ci_docs.yml @@ -43,10 +43,13 @@ jobs: core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); + - name: Install cbindgen + shell: bash + run: command -v cbindgen || cargo install cbindgen + - name: Build and install eonclient shell: pixi run -e docs-mta bash -e {0} run: | - pixi run ensure_cbindgen meson setup bbdir --prefix=$CONDA_PREFIX --libdir=lib \ -Dwith_metatomic=True -Dtorch_version=2.10 -Dpip_metatomic=True meson install -C bbdir diff --git a/.github/workflows/ci_sanitizers.yml b/.github/workflows/ci_sanitizers.yml new file mode 100644 index 000000000..8b1ae48d9 --- /dev/null +++ b/.github/workflows/ci_sanitizers.yml @@ -0,0 +1,41 @@ +name: Sanitizers (ASan + UBSan) +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true +on: [push, pull_request] +jobs: + sanitize: + runs-on: ubuntu-22.04 + name: sanitize (asan+ubsan) + steps: + - uses: actions/checkout@v4 + - uses: prefix-dev/setup-pixi@v0.9.4 + with: + cache: true + cache-write: ${{ github.event_name == 'push' && github.ref_name == 'main' }} + environments: dev-lite + - name: Install cbindgen + shell: bash + run: command -v cbindgen || cargo install cbindgen + - name: Build with ASan + UBSan + shell: pixi run bash -e {0} + run: | + # b_sanitize=address,undefined wires both runtimes through + # meson; b_lundef=false relaxes -Wl,--no-undefined which + # ASan's wrapper symbols don't resolve at link time. + meson setup bbdir-san \ + --buildtype=debug \ + -Db_sanitize=address,undefined \ + -Db_lundef=false \ + -Dwith_tests=true + meson compile -C bbdir-san + - name: Run tests under sanitizers + shell: pixi run bash -e {0} + env: + ASAN_OPTIONS: halt_on_error=1:abort_on_error=1:print_summary=1:detect_leaks=0 + UBSAN_OPTIONS: halt_on_error=1:abort_on_error=1:print_stacktrace=1 + run: | + # detect_leaks=0 because Eigen's static-init pool legitimately + # holds memory until process exit; we care about UAF / OOB / + # signed-overflow / null-deref, not leaks. + meson test -C bbdir-san --suite eon --print-errorlogs diff --git a/.github/workflows/ci_xtb.yml b/.github/workflows/ci_xtb.yml index ee279f9dc..569b417d5 100644 --- a/.github/workflows/ci_xtb.yml +++ b/.github/workflows/ci_xtb.yml @@ -29,11 +29,14 @@ jobs: - name: Install eon shell: pixi run bash -e {0} run: | + # XTB is unconditionally compiled and dlopens libxtb at run + # time via XtbLoader -- no -Dwith_xtb needed. The dev-xtb + # pixi env supplies libxtb on LD_LIBRARY_PATH so test_xtb / + # test_cineb_xtb run; on envs without it the tests SKIP. meson setup --reconfigure bbdir \ --prefix=$CONDA_PREFIX \ --buildtype release \ --libdir=lib \ - -Dwith_xtb=True \ -Dwith_tests=True meson install -C bbdir - name: Run tests diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 09f26b979..3592dc67a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,11 +3,10 @@ fail_fast: false exclude: | (?x)( - thirdparty| # vendored - xtb.h| # vendored - mastereqn| # legacy (?) - libqd| # vendored / legacy (?) - approval_tests| # autogenerated + thirdparty| # vendored (catch2, vesin, argum, ...) + xtb.h| # vendored xtb C API header + libqd| # vendored quad-double in eon/mcamc/ + approval_tests| # autogenerated reference data newsfragments| # from towncrier _static # no need to touch ) diff --git a/CMakeLists.txt b/CMakeLists.txt index b257b736c..ce381ef64 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,21 +38,22 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # ---------------------- Options (matching meson_options.txt) option(WITH_WATER "Build Water potentials" OFF) option(WITH_AMS "Build AMS potentials" OFF) -option(WITH_LAMMPS "Build LAMMPS potential" OFF) -option(WITH_MPI "Build with MPI support" OFF) +# LAMMPS, ARTn, IRA, XTB, and VASP have no build-time toggle: they +# are unconditionally compiled and dlopen / fork their runtime +# library / binary on first use. Mirrors meson_options.txt. +option(WITH_MPI "Build with MPI support (via MPItrampoline)" OFF) option(WITH_GPRD "Build with GPR dimer" OFF) -option(WITH_VASP "Build VASP potential" OFF) option(WITH_FORTRAN "Build Fortran potentials" ON) option(WITH_CUH2 "Build CuH2 potential" ON) option(WITH_TESTS "Build tests" ON) option(WITH_GP_SURROGATE "Build GP surrogate" OFF) option(WITH_CATLEARN "Build CatLearn potential" OFF) -option(WITH_XTB "Build XTB potential" OFF) option(WITH_ASE_ORCA "Build ASE ORCA potential" OFF) option(WITH_ASE_NWCHEM "Build ASE NWChem potential" OFF) option(WITH_ASE "Build ASE potential" OFF) option(WITH_QSC "Build QSC potential" OFF) -option(USE_MKL "Enable Intel MKL support" OFF) +option(USE_MKL "Hard-link Eigen against Intel MKL (legacy; mutually exclusive with WITH_FLEXIBLAS)" OFF) +option(WITH_FLEXIBLAS "Link Eigen via FlexiBLAS for runtime-pluggable BLAS / LAPACK" OFF) option(WITH_METATOMIC "Build Metatomic potential" OFF) option(WITH_PYTHON "Build Python-embedding potentials" OFF) set(TORCH_PATH "" CACHE STRING "Path to Torch installation") @@ -82,5 +83,58 @@ else() find_package(Python3 COMPONENTS Interpreter) endif() +# ---------------------- FlexiBLAS (BLAS / LAPACK) +# +# FlexiBLAS is the FlexiBLAS-of-MPI for BLAS: link once, swap the +# backend (OpenBLAS, MKL, BLIS, ATLAS, ARMPL, ...) at run time via +# the FLEXIBLAS env var. Mirrors the meson with_flexiblas block in +# client/meson.build and the MPItrampoline pattern below. +if(WITH_FLEXIBLAS AND USE_MKL) + message(FATAL_ERROR + "WITH_FLEXIBLAS and USE_MKL are mutually exclusive; " + "FlexiBLAS can route to MKL at run time via FLEXIBLAS=INTELMKL.") +endif() +if(WITH_FLEXIBLAS) + find_package(flexiblas QUIET) + if(NOT flexiblas_FOUND) + include(FetchContent) + FetchContent_Declare( + flexiblas + GIT_REPOSITORY https://github.com/mpimd-csc/flexiblas.git + GIT_TAG v3.5.0 + GIT_SHALLOW TRUE) + FetchContent_MakeAvailable(flexiblas) + endif() +endif() + +# ---------------------- MPI (via MPItrampoline) +# +# MPI integration always goes through MPItrampoline -- the +# FlexiBLAS-of-MPI. We link against its stable wrapper ABI and one +# eonclient binary forwards every MPI_* call to any spec-compliant +# libmpi at run time, picked via MPITRAMPOLINE_LIB. Mirrors the +# meson MPI block in client/meson.build. +# +# The fall-through here matches meson's wrap-driven behaviour: +# 1. find_package(MPItrampoline) -- system or +# $CMAKE_PREFIX_PATH-resolvable install. +# 2. FetchContent from upstream v5.5.1 -- analogue of the +# subprojects/mpitrampoline.wrap. There is never a +# "no MPI library" failure mode; we deliberately do NOT fall +# back to FindMPI / a direct system MPI link, since that +# ABI-locks the binary to one flavour and defeats the point. +if(WITH_MPI) + find_package(MPItrampoline QUIET) + if(NOT MPItrampoline_FOUND) + include(FetchContent) + FetchContent_Declare( + MPItrampoline + GIT_REPOSITORY https://github.com/eschnett/MPItrampoline.git + GIT_TAG v5.5.1 + GIT_SHALLOW TRUE) + FetchContent_MakeAvailable(MPItrampoline) + endif() +endif() + # ---------------------- Subdirectories add_subdirectory(client) diff --git a/client/ARTnSaddleSearch.cpp b/client/ARTnSaddleSearch.cpp index 61850807d..da868ff14 100644 --- a/client/ARTnSaddleSearch.cpp +++ b/client/ARTnSaddleSearch.cpp @@ -22,24 +22,19 @@ ARTnSaddleSearch::ARTnSaddleSearch(std::shared_ptr matterPassed, std::shared_ptr potPassed, AtomMatrix modeInitial, const Parameters ¶msPassed) - : SaddleSearchMethod(potPassed, paramsPassed), - matter{matterPassed}, - mode{modeInitial}, - eigenvector{AtomMatrix::Zero(matterPassed->numberOfAtoms(), 3)} { + : SaddleSearchMethod(std::move(potPassed), paramsPassed), + matter{std::move(matterPassed)}, + eigenvector{AtomMatrix::Zero(matter->numberOfAtoms(), 3)}, + mode{std::move(modeInitial)} { log = eonc::log::get(); if (!log) { throw std::runtime_error("ARTnSaddleSearch: Logger not initialized"); } } -ARTnSaddleSearch::~ARTnSaddleSearch() { -#ifdef WITH_ARTN - // Clean up is done within the search loop, not in destructor -#endif -} +ARTnSaddleSearch::~ARTnSaddleSearch() = default; -int ARTnSaddleSearch::run() { -#ifdef WITH_ARTN +SaddleStatus ARTnSaddleSearch::run() { auto &res = get_artn_resource(); const int nat = matter->numberOfAtoms(); @@ -81,7 +76,7 @@ int ARTnSaddleSearch::run() { res.require_loaded(); } catch (const std::exception &e) { QUILL_LOG_ERROR(log, "ARTn library not available: {}", e.what()); - status = STATUS_BAD_ARTN_ERROR; + status = SaddleStatus::BadArtnError; return status; } @@ -123,7 +118,7 @@ int ARTnSaddleSearch::run() { if (!std::filesystem::exists(filin)) { QUILL_LOG_ERROR(log, "artn_options.filin '{}' does not exist", filin); res.get_destroy_fn()(); - status = STATUS_BAD_ARTN_ERROR; + status = SaddleStatus::BadArtnError; return status; } int result_filin = @@ -208,7 +203,7 @@ int ARTnSaddleSearch::run() { if (cerr) { QUILL_LOG_ERROR(log, "ARTn setup failed (nat={})", nat); res.get_destroy_fn()(); - status = STATUS_BAD_ARTN_ERROR; + status = SaddleStatus::BadArtnError; return status; } @@ -227,14 +222,19 @@ int ARTnSaddleSearch::run() { } // Per-atom metadata for the Fortran step (no pARTn state, unlocked). - std::vector ityp(nat); - std::vector if_pos(3 * nat, 1); // all atoms free by default + // size_t casts on nat * to avoid the implicit widening + // bugprone-implicit-widening-of-multiplication-result lint -- + // every consumer wants size_t / Index / ptrdiff_t. + const std::size_t natz = static_cast(nat); + std::vector ityp(natz); + std::vector if_pos(natz * 3, 1); // all atoms free by default double box_f[9]; bool lconv = false; for (int i = 0; i < nat; i++) { if (matter->getFixed(i)) { - Eigen::Map(&if_pos[i * 3]).setZero(); + Eigen::Map(&if_pos[static_cast(i) * 3]) + .setZero(); } ityp[i] = matter->getAtomicNr(i); } @@ -346,7 +346,10 @@ int ARTnSaddleSearch::run() { "tau_sad", reinterpret_cast(&tau_sad_ptr)); if (result_tau_sad == 0 && tau_sad_ptr) { matter->setPositions(eonc::from_fortran_layout_vector( - std::vector(tau_sad_ptr, tau_sad_ptr + 3 * nat), nat)); + std::vector(tau_sad_ptr, + tau_sad_ptr + + static_cast(3) * nat), + nat)); std::free(tau_sad_ptr); } else { QUILL_LOG_WARNING( @@ -380,7 +383,9 @@ int ARTnSaddleSearch::run() { if (result_evec == 0 && evec_ptr) { // Use direct Eigen::Map to convert from Fortran layout eigenvector = eonc::from_fortran_layout_vector( - std::vector(evec_ptr, evec_ptr + 3 * nat), nat); + std::vector( + evec_ptr, evec_ptr + static_cast(3) * nat), + nat); // get_data allocates via c_malloc (artn_c_wrappers.f90), safe to free std::free(evec_ptr); @@ -392,7 +397,7 @@ int ARTnSaddleSearch::run() { eigenvector = AtomMatrix::Zero(nat, 3); } - status = STATUS_GOOD; + status = SaddleStatus::Good; res.get_destroy_fn()(); return status; } @@ -401,14 +406,14 @@ int ARTnSaddleSearch::run() { QUILL_LOG_WARNING( log, "ARTn stopped after {} iterations (has_error={}, has_sad={})", iteration, has_error, has_sad); - status = STATUS_BAD_ARTN_ERROR; + status = SaddleStatus::BadArtnError; res.get_destroy_fn()(); return status; } QUILL_LOG_WARNING(log, "ARTn did not converge after {} iterations", iteration); - status = STATUS_BAD_MAX_ITERATIONS; + status = SaddleStatus::BadMaxIterations; // Clean up in all cases { @@ -416,12 +421,6 @@ int ARTnSaddleSearch::run() { res.get_destroy_fn()(); } return status; - -#else - QUILL_LOG_ERROR(log, "ARTn support not compiled"); - status = STATUS_BAD_ARTN_ERROR; - return status; -#endif } double ARTnSaddleSearch::getEigenvalue() { @@ -438,17 +437,4 @@ AtomMatrix ARTnSaddleSearch::getEigenvector() { return eigenvector; } -std::string_view ARTnSaddleSearch::describeStatus(int status) const { - switch (status) { - case STATUS_GOOD: - return "Success"; - case STATUS_BAD_MAX_ITERATIONS: - return "Too many iterations"; - case STATUS_BAD_ARTN_ERROR: - return "ARTn backend error"; - default: - return "Unknown status"; - } -} - } // namespace eonc diff --git a/client/ARTnSaddleSearch.h b/client/ARTnSaddleSearch.h index c4ae27ada..8feb66282 100644 --- a/client/ARTnSaddleSearch.h +++ b/client/ARTnSaddleSearch.h @@ -19,48 +19,31 @@ #include "SaddleSearchMethod.h" #include -// Include the ARTn resource for thread-local access -#ifdef WITH_ARTN #include "libs/ARTn/ARTnResource.h" -#endif namespace eonc { -/* - * ARTn integration with thread-local dynamic loading - */ -#ifdef WITH_ARTN - -// Constants +// ARTn constants. The ARTnResource is always compiled in; libartn.so +// is dlopen'd at first require_loaded() call. WARNING: all ARTn +// operations are serialized through ARTnResource::library_mutex due +// to pARTn's non-thread-safe Fortran backend. Consider process-level +// parallelism for true concurrency. constexpr double ARTN_MODE_TOLERANCE = 1e-10; constexpr double ARTN_SMALL_DISPLACEMENT = 1e-6; -// WARNING: All ARTn operations are serialized through a global mutex -// due to the non-thread-safe Fortran backend. This can be a performance -// bottleneck in multi-threaded contexts. Consider process-level parallelism -// if true concurrent ARTn operations are required. - -#endif // WITH_ARTN - /// Saddle search method using the Activation-Relaxation Technique nouveau. /// Wraps the pARTn Fortran library via its C API (artn.h). class ARTnSaddleSearch : public SaddleSearchMethod { public: - static constexpr int STATUS_GOOD = 0; - static constexpr int STATUS_BAD_MAX_ITERATIONS = - MinModeSaddleSearch::STATUS_BAD_MAX_ITERATIONS; - static constexpr int STATUS_BAD_ARTN_ERROR = 22; - ARTnSaddleSearch(std::shared_ptr matterPassed, std::shared_ptr potPassed, AtomMatrix modeInitial, const Parameters ¶msPassed); ~ARTnSaddleSearch() override; - int run() override; + SaddleStatus run() override; double getEigenvalue() override; AtomMatrix getEigenvector() override; - std::string_view describeStatus(int status) const override; - int getStatus() const override { return status; } + SaddleStatus getStatus() const override { return status; } int getIterationCount() const override { return iteration; } int getForceCalls() const override { return forcecalls; } @@ -68,7 +51,7 @@ class ARTnSaddleSearch : public SaddleSearchMethod { std::shared_ptr matter; double eigenvalue{std::numeric_limits::quiet_NaN()}; AtomMatrix eigenvector, mode; - int status{0}; + SaddleStatus status{SaddleStatus::Good}; int iteration{0}; int forcecalls{0}; eonc::log::Scoped log; diff --git a/client/AtomicGPDimer.cpp b/client/AtomicGPDimer.cpp index 741aed6c5..cd4f79304 100644 --- a/client/AtomicGPDimer.cpp +++ b/client/AtomicGPDimer.cpp @@ -17,6 +17,7 @@ #include "fpe_handler.h" #include #include +#include #include "subprojects/gpr_optim/gpr/AtomicDimer.h" #include "subprojects/gpr_optim/gpr/auxiliary/ProblemSetUp.h" @@ -28,7 +29,7 @@ const char AtomicGPDimer::OPT_LBFGS[] = "lbfgs"; AtomicGPDimer::AtomicGPDimer(std::shared_ptr matter, const Parameters ¶ms, std::shared_ptr pot) - : LowestEigenmode(pot, params) { + : LowestEigenmode(std::move(pot), params) { matterCenter = std::make_shared(pot, params); *matterCenter = *matter; p = eonc::helpers::eon_parameters_to_gpr(params); @@ -38,7 +39,7 @@ AtomicGPDimer::AtomicGPDimer(std::shared_ptr matter, } void AtomicGPDimer::compute(std::shared_ptr matter, - AtomMatrix initialDirectionAtomMatrix) { + const AtomMatrix &initialDirectionAtomMatrix) { atoms_config = eonc::helpers::eon_matter_to_atmconf(matter.get()); // R_init.resize(1, matterCenter->getPositionsFree().size()); // R_init.assignFromEigenMatrix(matterCenter->getPositionsFreeV()); @@ -96,7 +97,6 @@ void AtomicGPDimer::compute(std::shared_ptr matter, this->totalIterations = atomic_dimer.getIterations(); this->totalForceCalls = atomic_dimer.getTotalForceCalls(); pot->forceCallCounter = atomic_dimer.getTotalForceCalls(); - return; } double AtomicGPDimer::getEigenvalue() { diff --git a/client/AtomicGPDimer.h b/client/AtomicGPDimer.h index 77790d031..e7b97079b 100644 --- a/client/AtomicGPDimer.h +++ b/client/AtomicGPDimer.h @@ -34,7 +34,8 @@ class AtomicGPDimer : public LowestEigenmode { std::shared_ptr pot); ~AtomicGPDimer() = default; - void compute(std::shared_ptr matter, AtomMatrix initialDirection); + void compute(std::shared_ptr matter, + const AtomMatrix &initialDirection); double getEigenvalue(); AtomMatrix getEigenvector(); @@ -43,12 +44,12 @@ class AtomicGPDimer : public LowestEigenmode { AtomMatrix direction; // direction along the dimer AtomMatrix rotationalPlane; // direction normal to the plane of dimer rotation - gpr::InputParameters p; - atmd::AtomicDimer atomic_dimer; - aux::ProblemSetUp problem_setup; - gpr::AtomsConfiguration atoms_config; - gpr::Observation init_observations, init_middle_point; - gpr::Coord orient_init, R_init; + gpr::InputParameters p{}; + atmd::AtomicDimer atomic_dimer{}; + aux::ProblemSetUp problem_setup{}; + gpr::AtomsConfiguration atoms_config{}; + gpr::Observation init_observations{}, init_middle_point{}; + gpr::Coord orient_init{}, R_init{}; }; } // namespace eonc diff --git a/client/BasinHoppingJob.cpp b/client/BasinHoppingJob.cpp index 1a9447808..7cd63a26d 100644 --- a/client/BasinHoppingJob.cpp +++ b/client/BasinHoppingJob.cpp @@ -184,12 +184,13 @@ std::vector BasinHoppingJob::run() { *currentCopy = *current; uniqueStructures.push_back(currentCopy); - char fname[128]; - snprintf(fname, 128, "min_%.5i.con", step + 1); + // std::format produces a sized std::string and avoids the + // unchecked snprintf truncation that cert-err33-c flags. + std::string fname = std::format("min_{:05d}.con", step + 1); current->matter2con(fname); returnFiles.push_back(fname); - snprintf(fname, 128, "energy_%.5i.dat", step + 1); + fname = std::format("energy_{:05d}.dat", step + 1); returnFiles.push_back(fname); { std::ofstream fh(fname); diff --git a/client/BasinHoppingSaddleSearch.cpp b/client/BasinHoppingSaddleSearch.cpp index c41c40727..c3b7d27be 100644 --- a/client/BasinHoppingSaddleSearch.cpp +++ b/client/BasinHoppingSaddleSearch.cpp @@ -9,17 +9,26 @@ ** Repo: ** https://github.com/TheochemUI/eOn */ + #include "BasinHoppingSaddleSearch.h" + +using eonc::SaddleStatus; + #include "Dimer.h" + #include "ImprovedDimer.h" + #include "Lanczos.h" + #include "LowestEigenmode.h" + #include "MinModeSaddleSearch.h" + #include "NudgedElasticBand.h" #include #include -int BasinHoppingSaddleSearch::run() { +SaddleStatus BasinHoppingSaddleSearch::run() { // minimize "saddle" saddle->relax(false, true, false, "displacementmin"); product = std::make_shared(pot, params); @@ -36,8 +45,8 @@ int BasinHoppingSaddleSearch::run() { double p = std::exp(arg); double r = eonc::helpers::random(); if (ereactant < eproduct) { - if (r > p) { // reject - return 1; + if (r > p) { // reject -- preserve historical "1 == not converged" + return SaddleStatus::Init; } } // NEB reactant to minimized "saddle" @@ -71,7 +80,7 @@ int BasinHoppingSaddleSearch::run() { *saddle = *neb.path[HighestImage]; eigenvalue = dim.getEigenvalue(); eigenvector = dim.getEigenvector(); - return 0; + return SaddleStatus::Good; } double BasinHoppingSaddleSearch::getEigenvalue() { return eigenvalue; } diff --git a/client/BasinHoppingSaddleSearch.h b/client/BasinHoppingSaddleSearch.h index 9a983ae39..9001e5ddc 100644 --- a/client/BasinHoppingSaddleSearch.h +++ b/client/BasinHoppingSaddleSearch.h @@ -21,25 +21,22 @@ namespace eonc { class BasinHoppingSaddleSearch : public SaddleSearchMethod { public: - BasinHoppingSaddleSearch(std::shared_ptr reactant, + BasinHoppingSaddleSearch(const std::shared_ptr &reactant, std::shared_ptr displacement, std::shared_ptr potPassed, const Parameters ¶metersPassed) - : SaddleSearchMethod(potPassed, parametersPassed), + : SaddleSearchMethod(std::move(potPassed), parametersPassed), reactant{std::make_shared(*reactant)}, - saddle{displacement} { - eigenvector.resize(reactant->numberOfAtoms(), 3); + saddle{std::move(displacement)} { + eigenvector.resize(this->reactant->numberOfAtoms(), 3); eigenvector.setZero(); } ~BasinHoppingSaddleSearch() = default; - int run(void); - double getEigenvalue(); - AtomMatrix getEigenvector(); - std::string_view describeStatus(int status) const override { - return MinModeSaddleSearch::statusMessage(status); - } - int getStatus() const override { return status; } + SaddleStatus run() override; + double getEigenvalue() override; + AtomMatrix getEigenvector() override; + SaddleStatus getStatus() const override { return status; } double eigenvalue{0.0}; AtomMatrix eigenvector; @@ -48,7 +45,7 @@ class BasinHoppingSaddleSearch : public SaddleSearchMethod { std::shared_ptr saddle; std::shared_ptr product; - int status{0}; + SaddleStatus status{SaddleStatus::Good}; private: eonc::log::Scoped log; diff --git a/client/BiasedGradientSquaredDescent.cpp b/client/BiasedGradientSquaredDescent.cpp index 714f686b3..d2514f225 100644 --- a/client/BiasedGradientSquaredDescent.cpp +++ b/client/BiasedGradientSquaredDescent.cpp @@ -9,12 +9,21 @@ ** Repo: ** https://github.com/TheochemUI/eOn */ + #include "BiasedGradientSquaredDescent.h" + +using eonc::SaddleStatus; + #include "EigenmodeStrategy.h" + #include "HelperFunctions.h" + #include "Matter.h" + #include "ObjectiveFunction.h" + #include "Optimizer.h" + #include "SaddleSearchMethod.h" #include @@ -38,7 +47,7 @@ class BGSDObjectiveFunction : public ObjectiveFunction { ~BGSDObjectiveFunction() = default; - double getEnergy() { + double getEnergy() override { VectorXd Vforce = matter.getForcesFreeV(); double Henergy = 0.5 * Vforce.dot(Vforce) + 0.5 * bgsdAlpha * @@ -49,7 +58,7 @@ class BGSDObjectiveFunction : public ObjectiveFunction { return Henergy; } - VectorXd getGradient(bool fdstep = false) { + VectorXd getGradient(bool fdstep = false) override { VectorXd Vforce = matter.getForcesFreeV(); double magVforce = Vforce.norm(); VectorXd normVforce = Vforce / magVforce; @@ -74,10 +83,10 @@ class BGSDObjectiveFunction : public ObjectiveFunction { return Hnorm; } - void setPositions(const VectorXd &x) { matter.setPositionsFreeV(x); } - VectorXd getPositions() { return matter.getPositionsFreeV(); } - int degreesOfFreedom() { return 3 * matter.numberOfFreeAtoms(); } - bool isConverged() { return isConvergedH() && isConvergedV(); } + void setPositions(const VectorXd &x) override { matter.setPositionsFreeV(x); } + VectorXd getPositions() override { return matter.getPositionsFreeV(); } + int degreesOfFreedom() override { return 3 * matter.numberOfFreeAtoms(); } + bool isConverged() override { return isConvergedH() && isConvergedV(); } bool isConvergedH() { return getConvergenceH() < params.bgsd_options.h_force_convergence; } @@ -88,10 +97,12 @@ class BGSDObjectiveFunction : public ObjectiveFunction { return getConvergenceH() < params.bgsd_options.grad2force_convergence; } - double getConvergence() { return getEnergy() && getGradient().norm(); } + double getConvergence() override { + return getEnergy() && getGradient().norm(); + } double getConvergenceH() { return getGradient().norm(); } double getConvergenceV() { return getEnergy(); } - VectorXd difference(const VectorXd &a, const VectorXd &b) { + VectorXd difference(const VectorXd &a, const VectorXd &b) override { return matter.pbcV(a - b); } @@ -100,7 +111,7 @@ class BGSDObjectiveFunction : public ObjectiveFunction { double bgsdAlpha; }; -int BiasedGradientSquaredDescent::run() { +SaddleStatus BiasedGradientSquaredDescent::run() { auto objf = std::make_shared( *saddle, reactantEnergy, params.bgsd_options.alpha, params); auto optim = eonc::helpers::create::mkOptim( @@ -151,13 +162,11 @@ int BiasedGradientSquaredDescent::run() { eigenvector = eonc::eigenmodeGetEigenvector(*minModeMethod); eigenvalue = eonc::eigenmodeGetEigenvalue(*minModeMethod); QUILL_LOG_DEBUG(log, "lowest eigenvalue {:.8f}", eigenvalue); - if (objf2->isConvergedV()) { - return 0; - } else if (objf2->isConvergedIP()) { - return 1; - } else { - return 1; - }; + // Two convergence flavours: V (true convergence on a saddle) and + // IP (inflection-point fallback). isConvergedIP() and the trailing + // else both signal "not-V converged" -- preserve the pre-typed- + // status mapping (0 -> Good, 1 -> Init). + return objf2->isConvergedV() ? SaddleStatus::Good : SaddleStatus::Init; } double BiasedGradientSquaredDescent::getEigenvalue() { return eigenvalue; } diff --git a/client/BiasedGradientSquaredDescent.h b/client/BiasedGradientSquaredDescent.h index b4bb23e3c..2d9e7a073 100644 --- a/client/BiasedGradientSquaredDescent.h +++ b/client/BiasedGradientSquaredDescent.h @@ -22,32 +22,29 @@ namespace eonc { class BiasedGradientSquaredDescent : public SaddleSearchMethod { public: - BiasedGradientSquaredDescent(std::shared_ptr matterPassed, + BiasedGradientSquaredDescent(const std::shared_ptr &matterPassed, double reactantEnergyPassed, const Parameters ¶metersPassed) : SaddleSearchMethod(matterPassed->getPotential(), parametersPassed), - saddle{matterPassed} { - reactantEnergy = reactantEnergyPassed; - saddle = matterPassed; + eigenvalue{0.0}, + saddle{matterPassed}, + reactantEnergy{reactantEnergyPassed} { eigenvector.resize(saddle->numberOfAtoms(), 3); eigenvector.setZero(); } ~BiasedGradientSquaredDescent() = default; - int run(); - double getEigenvalue(); - AtomMatrix getEigenvector(); - std::string_view describeStatus(int status) const override { - return MinModeSaddleSearch::statusMessage(status); - } - int getStatus() const override { return status; } + SaddleStatus run() override; + double getEigenvalue() override; + AtomMatrix getEigenvector() override; + SaddleStatus getStatus() const override { return status; } double eigenvalue; AtomMatrix eigenvector; std::shared_ptr saddle; - int status; + SaddleStatus status{SaddleStatus::Good}; private: double reactantEnergy; diff --git a/client/Bundling.cpp b/client/Bundling.cpp index c626f056b..d87d87798 100644 --- a/client/Bundling.cpp +++ b/client/Bundling.cpp @@ -55,9 +55,23 @@ int getBundleSize() { std::string numstr = name.substr(upos + 1, dpos - upos - 1); if (!numstr.empty() && std::isdigit(static_cast(numstr[0]))) { - int i = std::atoi(numstr.c_str()) + 1; - if (i > num_bundle) { - num_bundle = i; + // std::atoi silently returns 0 on parse failure, so bugprone- + // unchecked-string-to-number-conversion (cert-err34-c) flags + // it. We've already guarded with isdigit(numstr[0]) so the + // first char parses; std::stoi throws on overflow / wholly + // bogus input, which we swallow as "skip this filename". + try { + int i = std::stoi(numstr) + 1; + if (i > num_bundle) { + num_bundle = i; + } + } catch (const std::exception &e) { + // unparseable trailing junk -- log the filename so a stray + // 'config_xxx.ini' shows up at debug level without breaking + // the bundle scan. bugprone-empty-catch wants something + // observable here. + std::cerr << "[bundle] skipping unparseable filename '" << name + << "': " << e.what() << '\n'; } } } @@ -105,8 +119,16 @@ std::vector unbundle(int number) { std::string numstr = originalFilename.substr(upos + 1, dpos - upos - 1); if (!numstr.empty() && std::isdigit(static_cast(numstr[0]))) { - int bundleNumber = std::atoi(numstr.c_str()); - if (bundleNumber != number) { + // See bundleNumber rationale above; std::stoi over std::atoi + // for overflow / parse-failure visibility. + try { + if (std::stoi(numstr) != number) { + continue; + } + } catch (const std::exception &e) { + // Same skip-on-unparseable rationale as in getBundleSize(). + std::cerr << "[bundle] skipping unparseable filename '" + << originalFilename << "': " << e.what() << '\n'; continue; } } diff --git a/client/ClientEON.cpp b/client/ClientEON.cpp index 8ea1baa3b..4e6ba3f14 100644 --- a/client/ClientEON.cpp +++ b/client/ClientEON.cpp @@ -202,8 +202,15 @@ int main(int argc, char **argv) { number_of_clients = 1; } - if (MPI::Is_initialized() == false) { - MPI::Init(); + // The MPI C++ bindings (MPI::Init, MPI::COMM_WORLD, MPI::INT, ...) + // were deprecated in MPI-2.2 and removed in MPI-3.0 (~2012). Modern + // conda-forge MPICH 4.x and OpenMPI 5.x ship without the C++ + // bindings header. Every call below uses the C bindings, which + // every spec-compliant MPI implementation guarantees. + int mpi_initialized = 0; + MPI_Initialized(&mpi_initialized); + if (!mpi_initialized) { + MPI_Init(nullptr, nullptr); } int error; @@ -222,22 +229,22 @@ int main(int argc, char **argv) { if (error) { QUILL_LOG_ERROR(logger, "problem loading parameter file"); logger->flush_log(); - MPI::COMM_WORLD.Abort(1); + MPI_Abort(MPI_COMM_WORLD, 1); } // XXX: Barrier for gpaw-python - MPI::COMM_WORLD.Barrier(); + MPI_Barrier(MPI_COMM_WORLD); - int irank = MPI::COMM_WORLD.Get_rank(); - int isize = MPI::COMM_WORLD.Get_size(); + int irank = 0; + int isize = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &irank); + MPI_Comm_size(MPI_COMM_WORLD, &isize); std::vector process_types(isize); - int process_type; + int process_type = 1; - process_type = 1; - - MPI::COMM_WORLD.Allgather(&process_type, 1, MPI::INT, &process_types[0], 1, - MPI::INT); + MPI_Allgather(&process_type, 1, MPI_INT, process_types.data(), 1, MPI_INT, + MPI_COMM_WORLD); int i, servers = 0, clients = 0, potentials = 0; int server_rank = -1; @@ -266,7 +273,7 @@ int main(int argc, char **argv) { "didn't launch as many mpi client ranks as specified in " "EON_NUMBER_OF_CLIENTS"); logger->flush_log(); - MPI::COMM_WORLD.Abort(1); + MPI_Abort(MPI_COMM_WORLD, 1); } clients = number_of_clients; @@ -282,12 +289,15 @@ int main(int argc, char **argv) { int potential_group_size = potentials / clients; for (i = 0; i < clients; i++) { - MPI::Group orig_group, new_group; - orig_group = MPI::COMM_WORLD.Get_group(); + MPI_Group orig_group, new_group; + MPI_Comm_group(MPI_COMM_WORLD, &orig_group); int offset = i * potential_group_size; - new_group = - orig_group.Incl(potential_group_size, &potential_ranks[offset]); - MPI::COMM_WORLD.Create(new_group); + MPI_Group_incl(orig_group, potential_group_size, &potential_ranks[offset], + &new_group); + MPI_Comm new_comm; + MPI_Comm_create(MPI_COMM_WORLD, new_group, &new_comm); + MPI_Group_free(&new_group); + MPI_Group_free(&orig_group); } if (my_client_number < number_of_clients) { @@ -305,7 +315,7 @@ int main(int argc, char **argv) { MPI_Group_incl(world_group, 1, &r, &new_group); MPI_Comm new_comm; MPI_Comm_create(MPI_COMM_WORLD, new_group, &new_comm); - if (new_comm != MPI_COMM_nullptr) { + if (new_comm != MPI_COMM_NULL) { parameters.potential_options.MPIClientComm = new_comm; } QUILL_LOG_INFO(logger, "creating group with ranks: {}", r); @@ -332,10 +342,10 @@ int main(int argc, char **argv) { Py_Main(2, py_argv); Py_FinalizeEx(); // GH - MPI::Finalize(); + MPI_Finalize(); return 0; } else if (my_client_number > number_of_clients) { - MPI::Finalize(); + MPI_Finalize(); return 0; } } @@ -369,13 +379,16 @@ int main(int argc, char **argv) { irank, server_rank); // Tag "1" is to interrupt the main loop and tell the communicator that a // client is ready - MPI::COMM_WORLD.Isend(&ready, 1, MPI::INT, server_rank, 1); + MPI_Request ready_req; + MPI_Isend(&ready, 1, MPI_INT, server_rank, 1, MPI_COMM_WORLD, &ready_req); + MPI_Request_free(&ready_req); // Get the path we should run in from the server - MPI::COMM_WORLD.Recv(&path[0], 1024, MPI::CHAR, server_rank, 0); + MPI_Recv(path.data(), 1024, MPI_CHAR, server_rank, 0, MPI_COMM_WORLD, + MPI_STATUS_IGNORE); if (path.starts_with("STOPCAR")) { QUILL_LOG_INFO(logger, "rank {} got STOPCAR", irank); - MPI::Finalize(); + MPI_Finalize(); return 0; } QUILL_LOG_INFO(logger, "client: rank: {} chdir to {}", irank, path); @@ -487,7 +500,12 @@ int main(int argc, char **argv) { if (client_standalone) { break; } - MPI::COMM_WORLD.Isend(&path[0], 1024, MPI::CHAR, server_rank, 0); + { + MPI_Request done_req; + MPI_Isend(path.data(), 1024, MPI_CHAR, server_rank, 0, MPI_COMM_WORLD, + &done_req); + MPI_Request_free(&done_req); + } // End of MPI while loop } @@ -501,9 +519,9 @@ int main(int argc, char **argv) { #ifdef EONMPI if (client_standalone) { - MPI::COMM_WORLD.Abort(0); + MPI_Abort(MPI_COMM_WORLD, 0); } else { - MPI::Finalize(); + MPI_Finalize(); } #endif diff --git a/client/ConFileIO.cpp b/client/ConFileIO.cpp index 7fc9fcbaa..2ae28596e 100644 --- a/client/ConFileIO.cpp +++ b/client/ConFileIO.cpp @@ -55,7 +55,8 @@ std::string canonical_generator_header(const std::string &header) { return "Generated by eOn"; } -std::string ensure_extension(std::string filename, std::string_view ext) { +std::string ensure_extension(const std::string &filename, + std::string_view ext) { fs::path path(filename); if (path.extension() != ext) { path += ext; @@ -103,11 +104,11 @@ namespace eonc::io { std::pair, std::array> cell_to_lengths_angles(const Matter &m) { - std::array lengths; + std::array lengths{}; lengths[0] = m.cell.row(0).norm(); lengths[1] = m.cell.row(1).norm(); lengths[2] = m.cell.row(2).norm(); - std::array angles; + std::array angles{}; angles[0] = eonc::safemath::safe_acos(eonc::safemath::safe_div( m.cell.row(0).dot(m.cell.row(1)), lengths[0] * lengths[1])) * 180.0 / eonc::helpers::pi; @@ -302,25 +303,29 @@ void matter2xyz(Matter &m, std::string filename, bool append) { } else { file = fopen(filename.c_str(), "wb"); } - if (file == 0) { + if (file == nullptr) { std::cerr << "Can't create file " << filename << std::endl; exit(1); } - fprintf(file, "%ld\nGenerated by eOn\n", m.numberOfAtoms()); + // (void)-cast every fprintf return because we have no recovery + // path mid-write -- caller-side error reporting is on the + // !readable() branch above. + (void)std::fprintf(file, "%ld\nGenerated by eOn\n", m.numberOfAtoms()); if (m.usePeriodicBoundaries) { m.applyPeriodicBoundary(); } for (i = 0; i < m.numberOfAtoms(); i++) { - fprintf(file, "%s\t%11.6f\t%11.6f\t%11.6f\n", - atomicNumber2symbol(m.getAtomicNr(i)), m.getPosition(i, 0), - m.getPosition(i, 1), m.getPosition(i, 2)); + (void)std::fprintf(file, "%s\t%11.6f\t%11.6f\t%11.6f\n", + atomicNumber2symbol(m.getAtomicNr(i)), + m.getPosition(i, 0), m.getPosition(i, 1), + m.getPosition(i, 2)); } - fclose(file); + (void)std::fclose(file); } -void writeTibble(Matter &m, std::string fname) { +void writeTibble(Matter &m, const std::string &fname) { AtomMatrix fSys = m.getForces(); std::ofstream out(fname); double eSys = m.getPotentialEnergy(); diff --git a/client/ConFileIO.h b/client/ConFileIO.h index 54ac1989a..48a569787 100644 --- a/client/ConFileIO.h +++ b/client/ConFileIO.h @@ -54,7 +54,7 @@ bool matter2con(Matter &m, std::string filename, bool append = false, const ConFrameMetadata *metadata = nullptr); bool matter2convel(Matter &m, std::string filename); void matter2xyz(Matter &m, std::string filename, bool append = false); -void writeTibble(Matter &m, std::string filename); +void writeTibble(Matter &m, const std::string &filename); // Helper std::pair, std::array> diff --git a/client/ConjugateGradients.cpp b/client/ConjugateGradients.cpp index 0febac63a..e2c21d509 100644 --- a/client/ConjugateGradients.cpp +++ b/client/ConjugateGradients.cpp @@ -14,6 +14,8 @@ #include +using eonc::StepResult; + Eigen::VectorXd ConjugateGradients::getStep() { double a = std::fabs(m_force.dot(m_forceOld)); double b = m_forceOld.squaredNorm(); @@ -42,16 +44,28 @@ Eigen::VectorXd ConjugateGradients::getStep() { return m_direction; } -int ConjugateGradients::step(double a_maxMove) { - bool converged; +StepResult ConjugateGradients::step(double a_maxMove) { + // Non-finite check: use the previous step's stored m_force (set by + // single_step / line_search) rather than calling getGradient() + // afresh. A fresh call here would invalidate the matter cache that + // the saddle-search outer loop just populated and charge ~+1 force + // call per CG step (saddle search regresses 39 -> 50 force calls + // on Morse Pt). Skip the check on the very first step where m_force + // hasn't been set yet; the body will catch NaN there. + if (m_cg_i > 0 && !m_force.allFinite()) { + QUILL_LOG_WARNING(m_log, + "[CG] non-finite force entering step (NaN or Inf); " + "aborting minimization"); + return StepResult::Failed; + } + + bool converged = false; if (m_optConfig.opts.cg.line_search) { converged = line_search(a_maxMove); } else { converged = single_step(a_maxMove); } - if (converged) - return 1; - return 0; + return converged ? StepResult::Converged : StepResult::NotConverged; } int ConjugateGradients::line_search(double a_maxMove) { @@ -188,11 +202,16 @@ int ConjugateGradients::single_step(double a_maxMove) { return m_objf->isConverged() ? 1 : 0; } -int ConjugateGradients::run(size_t a_maxIterations, double a_maxMove) { +StepResult ConjugateGradients::run(size_t a_maxIterations, double a_maxMove) { size_t iterations = 0; while (!m_objf->isConverged() && iterations <= a_maxIterations) { - step(a_maxMove); + if (step(a_maxMove) == StepResult::Failed) { + return StepResult::Failed; + } iterations++; } - return m_objf->isConverged() ? 1 : 0; + // No final getGradient() check -- the per-step guard above handles + // NaN bail without paying an extra force evaluation here. + return m_objf->isConverged() ? StepResult::Converged + : StepResult::NotConverged; } diff --git a/client/ConjugateGradients.h b/client/ConjugateGradients.h index f07377c17..0b9b72c06 100644 --- a/client/ConjugateGradients.h +++ b/client/ConjugateGradients.h @@ -45,9 +45,10 @@ class ConjugateGradients : public Optimizer { */ ConjugateGradients(std::shared_ptr a_objf, const Parameters &a_params) - : Optimizer(a_objf, OptType::CG, a_params), - m_directionOld{(a_objf->getPositions()).setZero()}, - m_forceOld{(a_objf->getPositions()).setZero()}, // use setZero instead + : Optimizer(std::move(a_objf), OptType::CG, + OptimizerConfig::fromParams(a_params)), + m_directionOld{(m_objf->getPositions()).setZero()}, + m_forceOld{(m_objf->getPositions()).setZero()}, // use setZero instead m_cg_i{0} {} //! Conjugant Gradient deconstructor ~ConjugateGradients() = default; @@ -57,13 +58,13 @@ class ConjugateGradients : public Optimizer { * Either calls the single_step or line_search method depending on the * parameters \return whether or not the algorithm has converged */ - int step(double a_maxMove) override; + StepResult step(double a_maxMove) override; //! Runs the conjugate gradient /** * \todo method should also return an error code and message if the algorithm * errors out \return algorithm convergence */ - int run(size_t a_maxIterations, double a_maxMove) override; + StepResult run(size_t a_maxIterations, double a_maxMove) override; //! Gets the direction of the next step Eigen::VectorXd getStep(); diff --git a/client/Dimer.cpp b/client/Dimer.cpp index 9a2850dac..7eaa3bdad 100644 --- a/client/Dimer.cpp +++ b/client/Dimer.cpp @@ -19,8 +19,8 @@ using namespace eonc::helpers; -Dimer::Dimer(std::shared_ptr matter, const Parameters ¶ms, - std::shared_ptr pot) +Dimer::Dimer(const std::shared_ptr &matter, const Parameters ¶ms, + const std::shared_ptr &pot) : LowestEigenmode(pot, params) { // Give matterDimer its own potential for parallel force evaluation auto dimerPot = (pot->needsPerImageInstance() && params.main_options.parallel) @@ -39,7 +39,7 @@ Dimer::Dimer(std::shared_ptr matter, const Parameters ¶ms, totalForceCalls = 0; } -void Dimer::compute(std::shared_ptr matter, +void Dimer::compute(const std::shared_ptr &matter, AtomMatrix initialDirection) { *matterCenter = *matter; diff --git a/client/Dimer.h b/client/Dimer.h index 0ceff73c7..726981b78 100644 --- a/client/Dimer.h +++ b/client/Dimer.h @@ -21,11 +21,12 @@ namespace eonc { /// Uses finite-difference rotation to converge on the minimum eigenmode. class Dimer : public LowestEigenmode { public: - Dimer(std::shared_ptr matter, const Parameters ¶ms, - std::shared_ptr pot); + Dimer(const std::shared_ptr &matter, const Parameters ¶ms, + const std::shared_ptr &pot); ~Dimer() = default; - void compute(std::shared_ptr matter, AtomMatrix initialDirection); + void compute(const std::shared_ptr &matter, + AtomMatrix initialDirection); [[nodiscard]] double getEigenvalue(); [[nodiscard]] AtomMatrix getEigenvector(); diff --git a/client/DynLib.h b/client/DynLib.h index a2707aefd..778fa0719 100644 --- a/client/DynLib.h +++ b/client/DynLib.h @@ -30,9 +30,11 @@ namespace eonc::dynlib { #ifdef _WIN32 using Handle = HMODULE; -inline Handle open(const char *name) noexcept { return LoadLibraryA(name); } +[[nodiscard]] inline Handle open(const char *name) noexcept { + return LoadLibraryA(name); +} -inline void *sym(Handle h, const char *name) noexcept { +[[nodiscard]] inline void *sym(Handle h, const char *name) noexcept { return reinterpret_cast(GetProcAddress(h, name)); } @@ -41,7 +43,7 @@ inline void close(Handle h) noexcept { FreeLibrary(h); } -inline std::string error() { +[[nodiscard]] inline std::string error() { DWORD err = GetLastError(); if (err == 0) return {}; @@ -59,25 +61,27 @@ inline std::string error() { #else // POSIX (Linux, macOS) using Handle = void *; -inline Handle open(const char *name) noexcept { +[[nodiscard]] inline Handle open(const char *name) noexcept { return dlopen(name, RTLD_NOW | RTLD_LOCAL); } -inline void *sym(Handle h, const char *name) noexcept { return dlsym(h, name); } +[[nodiscard]] inline void *sym(Handle h, const char *name) noexcept { + return dlsym(h, name); +} inline void close(Handle h) noexcept { if (h) dlclose(h); } -inline std::string error() { +[[nodiscard]] inline std::string error() { const char *msg = dlerror(); return msg ? std::string(msg) : std::string{}; } #endif /// Try a list of library names in order, return first successful handle. -inline Handle openFirst(const char *const names[]) noexcept { +[[nodiscard]] inline Handle openFirst(const char *const names[]) noexcept { for (const char *const *name = names; *name; ++name) { Handle h = open(*name); if (h) @@ -87,7 +91,8 @@ inline Handle openFirst(const char *const names[]) noexcept { } /// Load a symbol and cast to a function pointer type. -template Fn loadSym(Handle h, const char *name) noexcept { +template +[[nodiscard]] Fn loadSym(Handle h, const char *name) noexcept { return reinterpret_cast(sym(h, name)); } diff --git a/client/DynamicsJob.cpp b/client/DynamicsJob.cpp index 204c93623..2f91124cc 100644 --- a/client/DynamicsJob.cpp +++ b/client/DynamicsJob.cpp @@ -17,7 +17,7 @@ #include "Parameters.h" #include "Potential.h" -std::vector DynamicsJob::run(void) { +std::vector DynamicsJob::run() { auto R = std::make_shared(pot, params); auto F = std::make_shared(pot, params); R->con2matter("pos.con"); diff --git a/client/DynamicsSaddleSearch.cpp b/client/DynamicsSaddleSearch.cpp index 36d5f36e2..d964708b1 100644 --- a/client/DynamicsSaddleSearch.cpp +++ b/client/DynamicsSaddleSearch.cpp @@ -9,18 +9,26 @@ ** Repo: ** https://github.com/TheochemUI/eOn */ + #include "DynamicsSaddleSearch.h" + +using eonc::SaddleStatus; + #include "BondBoost.h" + #include "Dynamics.h" + #include "EigenmodeStrategy.h" + #include "MinModeSaddleSearch.h" + #include "NudgedElasticBand.h" #include #include #include -int DynamicsSaddleSearch::run() { +SaddleStatus DynamicsSaddleSearch::run() { std::vector> mdSnapshots; std::vector mdTimes; QUILL_LOG_DEBUG(log, "Starting dynamics NEB saddle search"); @@ -35,7 +43,7 @@ int DynamicsSaddleSearch::run() { QUILL_LOG_DEBUG(log, "No mass weights file found"); } - Dynamics dyn(saddle.get(), params); + Dynamics dyn(saddle.get(), DynamicsConfig::fromParams(params)); QUILL_LOG_DEBUG( log, "Initializing velocities from Maxwell-Boltzmann distribution"); dyn.setTemperature(params.saddle_search_options.dynamics.temperature); @@ -81,14 +89,15 @@ int DynamicsSaddleSearch::run() { bondBoost.initialize(); } - int checkInterval = static_cast( - params.saddle_search_options.dynamics.state_check_interval / - params.dynamics_options.time_step + - 0.5); - int recordInterval = - static_cast(params.saddle_search_options.dynamics.record_interval / - params.dynamics_options.time_step + - 0.5); + // Round-to-nearest with std::lround instead of `(int)(x + 0.5)`, + // which is broken for negative arguments and exact representations + // (bugprone-incorrect-roundings). + const auto checkInterval = static_cast( + std::lround(params.saddle_search_options.dynamics.state_check_interval / + params.dynamics_options.time_step)); + const auto recordInterval = static_cast( + std::lround(params.saddle_search_options.dynamics.record_interval / + params.dynamics_options.time_step)); if (params.debug_options.write_movies) { saddle->matter2con("dynamics", false); @@ -238,7 +247,7 @@ int DynamicsSaddleSearch::run() { } if (maxEnergy <= reactant->getPotentialEnergy()) { QUILL_LOG_DEBUG(log, "warning: no barrier found"); - return MinModeSaddleSearch::STATUS_BAD_NO_BARRIER; + return SaddleStatus::BadNoBarrier; } } } else { @@ -250,9 +259,9 @@ int DynamicsSaddleSearch::run() { saddle->matter2con("saddle_initial_guess.con"); MinModeSaddleSearch search = MinModeSaddleSearch( saddle, mode, reactant->getPotentialEnergy(), params, pot); - int minModeStatus = search.run(); + SaddleStatus minModeStatus = search.run(); - if (minModeStatus != MinModeSaddleSearch::STATUS_GOOD) { + if (minModeStatus != SaddleStatus::Good) { QUILL_LOG_DEBUG(log, "error in min mode saddle search"); return minModeStatus; } @@ -266,7 +275,7 @@ int DynamicsSaddleSearch::run() { QUILL_LOG_DEBUG(log, "found barrier of {:.3f}", barrier); mdSnapshots.clear(); mdTimes.clear(); - return MinModeSaddleSearch::STATUS_GOOD; + return SaddleStatus::Good; } else { QUILL_LOG_DEBUG(log, "Still in original state"); mdTimes.clear(); @@ -277,13 +286,13 @@ int DynamicsSaddleSearch::run() { mdSnapshots.clear(); time = params.dynamics_options.steps * params.dynamics_options.time_step; - return MinModeSaddleSearch::STATUS_BAD_MD_TRAJECTORY_TOO_SHORT; + return SaddleStatus::BadMdTrajectoryTooShort; } /// Binary search through MD snapshots to find the transition point. int DynamicsSaddleSearch::refineTransition( const std::vector> &snapshots, - std::shared_ptr prod) { + const std::shared_ptr &prod) { int lo = 0; int hi = static_cast(snapshots.size()) - 1; if (hi == 0) { diff --git a/client/DynamicsSaddleSearch.h b/client/DynamicsSaddleSearch.h index 57207d0ec..41203b3ac 100644 --- a/client/DynamicsSaddleSearch.h +++ b/client/DynamicsSaddleSearch.h @@ -21,7 +21,7 @@ namespace eonc { class DynamicsSaddleSearch : public SaddleSearchMethod { public: - DynamicsSaddleSearch(std::shared_ptr matterPassed, + DynamicsSaddleSearch(const std::shared_ptr &matterPassed, const Parameters ¶metersPassed) : SaddleSearchMethod(nullptr, parametersPassed), product{std::make_shared(*matterPassed)}, @@ -30,19 +30,16 @@ class DynamicsSaddleSearch : public SaddleSearchMethod { this->pot = matterPassed->getPotential(); eigenvector.resize(reactant->numberOfAtoms(), 3); eigenvector.setZero(); - }; + } ~DynamicsSaddleSearch() = default; - int run(); - double getEigenvalue(); - AtomMatrix getEigenvector(); - std::string_view describeStatus(int status) const override { - return MinModeSaddleSearch::statusMessage(status); - } - int getStatus() const override { return status; } + SaddleStatus run() override; + double getEigenvalue() override; + AtomMatrix getEigenvector() override; + SaddleStatus getStatus() const override { return status; } int refineTransition(const std::vector> &snapshots, - std::shared_ptr product); + const std::shared_ptr &product); double eigenvalue{0.0}; AtomMatrix eigenvector; @@ -53,7 +50,7 @@ class DynamicsSaddleSearch : public SaddleSearchMethod { std::shared_ptr reactant; std::shared_ptr saddle; - int status{0}; + SaddleStatus status{SaddleStatus::Good}; private: eonc::log::Scoped log; diff --git a/client/FIRE.cpp b/client/FIRE.cpp index a041057bf..c5985fb10 100644 --- a/client/FIRE.cpp +++ b/client/FIRE.cpp @@ -14,11 +14,13 @@ #include -int FIRE::step(double a_maxMove) { +using eonc::StepResult; + +StepResult FIRE::step(double a_maxMove) { double P = 0; // Check convergence. if (m_objf->isConverged()) { - return 1; + return StepResult::Converged; } // Velocity Verlet @@ -61,12 +63,14 @@ int FIRE::step(double a_maxMove) { } m_iteration++; - return m_objf->isConverged() ? 1 : 0; + return m_objf->isConverged() ? StepResult::Converged + : StepResult::NotConverged; } -int FIRE::run(size_t a_maxIterations, double a_maxMove) { +StepResult FIRE::run(size_t a_maxIterations, double a_maxMove) { while (!m_objf->isConverged() && m_iteration < a_maxIterations) { step(a_maxMove); } - return m_objf->isConverged() ? 1 : 0; + return m_objf->isConverged() ? StepResult::Converged + : StepResult::NotConverged; } diff --git a/client/FIRE.h b/client/FIRE.h index fb409bbf9..9cb853ec2 100644 --- a/client/FIRE.h +++ b/client/FIRE.h @@ -20,13 +20,14 @@ class FIRE : public Optimizer { public: FIRE(std::shared_ptr a_objf, const Parameters &a_params) - : Optimizer(a_objf, OptType::FIRE, OptimizerConfig::fromParams(a_params)), + : Optimizer(std::move(a_objf), OptType::FIRE, + OptimizerConfig::fromParams(a_params)), m_dt{a_params.optimizer_options.time_step}, m_dt_max{a_params.optimizer_options.max_time_step}, m_max_move{a_params.optimizer_options.max_move}, m_N_min{5}, m_N{0}, - m_vel{Eigen::VectorXd::Zero(a_objf->degreesOfFreedom())}, + m_vel{Eigen::VectorXd::Zero(m_objf->degreesOfFreedom())}, m_alpha_start{0.1}, m_alpha{m_alpha_start}, m_f_inc{1.1}, @@ -35,8 +36,8 @@ class FIRE : public Optimizer { m_iteration{0} {} virtual ~FIRE() = default; - int step(double a_maxMove) override; - int run(size_t a_maxIterations, double a_maxMove) override; + StepResult step(double a_maxMove) override; + StepResult run(size_t a_maxIterations, double a_maxMove) override; private: double m_dt, m_dt_max, m_max_move; diff --git a/client/FiniteDifferenceJob.cpp b/client/FiniteDifferenceJob.cpp index 4504d4611..a0ae70064 100644 --- a/client/FiniteDifferenceJob.cpp +++ b/client/FiniteDifferenceJob.cpp @@ -19,7 +19,7 @@ using namespace eonc::helpers; -std::vector FiniteDifferenceJob::run(void) { +std::vector FiniteDifferenceJob::run() { auto reactant = std::make_unique(pot, params); reactant->con2matter("pos.con"); AtomMatrix posA = reactant->getPositions(); diff --git a/client/GPSurrogateJob.cpp b/client/GPSurrogateJob.cpp index bd8af972b..76e3697ec 100644 --- a/client/GPSurrogateJob.cpp +++ b/client/GPSurrogateJob.cpp @@ -20,6 +20,7 @@ #include "EonLogger.h" #include +#include std::vector GPSurrogateJob::run() { // Start working @@ -238,7 +239,7 @@ std::vector getMidSlice(const std::vector &matobjs) { Eigen::VectorXd make_target(Matter &m1, std::shared_ptr true_pot) { const auto ncols = (m1.numberOfFreeAtoms() * 3) + 1; Eigen::VectorXd target(ncols); - m1.setPotential(true_pot); + m1.setPotential(std::move(true_pot)); target(0) = m1.getPotentialEnergy(); target.segment(1, ncols - 1) = m1.getForcesFreeV() * -1; // EONC_LOG_TRACE("Generated Target:\n{}", @@ -265,7 +266,8 @@ getNewDataPoint(const std::vector> &matobjs, auto [maxUnc, maxIndex] = getMaxUncertainty(matobjs); Matter candidate{*matobjs[maxIndex + 1]}; return std::make_pair( - candidate.getPositionsFreeV(), make_target(candidate, true_pot)); + candidate.getPositionsFreeV(), + make_target(candidate, std::move(true_pot))); } bool accuratePES(std::vector> &matobjs, std::shared_ptr true_pot) { diff --git a/client/GeometryAnalysis.cpp b/client/GeometryAnalysis.cpp index 7ecba980a..f61ca7612 100644 --- a/client/GeometryAnalysis.cpp +++ b/client/GeometryAnalysis.cpp @@ -16,10 +16,11 @@ #include #include +#include #include -RotationMatrix eonc::geometry::rotationExtract(const AtomMatrix r1, - const AtomMatrix r2) { +RotationMatrix eonc::geometry::rotationExtract(const AtomMatrix &r1, + const AtomMatrix &r2) { RotationMatrix R; // Determine optimal rotation @@ -201,8 +202,8 @@ void eonc::geometry::projectOutRotTrans(Eigen::VectorXd &step, } } -void eonc::geometry::rotationRemove(const AtomMatrix r1_passed, - std::shared_ptr m2) { +void eonc::geometry::rotationRemove(const AtomMatrix &r1_passed, + const std::shared_ptr &m2) { // Skip for extended systems (slabs/surfaces with frozen atoms). // Rigid-body rotation and translation are not well-defined when the // system is anchored by frozen atoms. @@ -230,14 +231,14 @@ void eonc::geometry::rotationRemove(const AtomMatrix r1_passed, m2->setPositions(resultMat); } -void eonc::geometry::rotationRemove(const std::shared_ptr m1, +void eonc::geometry::rotationRemove(const std::shared_ptr &m1, std::shared_ptr m2) { AtomMatrix r1 = m1->getPositions(); - rotationRemove(r1, m2); - return; + rotationRemove(r1, std::move(m2)); } -void eonc::geometry::translationRemove(Matter &m1, const AtomMatrix r2_passed) { +void eonc::geometry::translationRemove(Matter &m1, + const AtomMatrix &r2_passed) { AtomMatrix r1 = m1.getPositions(); AtomMatrix r2 = r2_passed; @@ -257,20 +258,18 @@ void eonc::geometry::translationRemove(Matter &m1, const AtomMatrix r2_passed) { } m1.setPositions(r1); - return; } void eonc::geometry::translationRemove(Matter &m1, const Matter &m2) { AtomMatrix r2 = m2.getPositions(); translationRemove(m1, r2); - return; } -double eonc::geometry::maxAtomMotion(const AtomMatrix v1) { +double eonc::geometry::maxAtomMotion(const AtomMatrix &v1) { return v1.rowwise().norm().maxCoeff(); } -double eonc::geometry::maxAtomMotionV(const VectorXd v1) { +double eonc::geometry::maxAtomMotionV(const VectorXd &v1) { double max = 0.0; long n = v1.rows(); if (n < 3) { @@ -294,7 +293,7 @@ double eonc::geometry::maxAtomMotionV(const VectorXd v1) { return max; } -long eonc::geometry::numAtomsMoved(const AtomMatrix v1, double cutoff) { +long eonc::geometry::numAtomsMoved(const AtomMatrix &v1, double cutoff) { long num = 0; for (int i = 0; i < v1.rows(); i++) { double norm = v1.row(i).norm(); @@ -305,7 +304,7 @@ long eonc::geometry::numAtomsMoved(const AtomMatrix v1, double cutoff) { return num; } -AtomMatrix eonc::geometry::maxAtomMotionApplied(const AtomMatrix v1, +AtomMatrix eonc::geometry::maxAtomMotionApplied(const AtomMatrix &v1, double maxMotion) { AtomMatrix v2(v1); @@ -316,7 +315,7 @@ AtomMatrix eonc::geometry::maxAtomMotionApplied(const AtomMatrix v1, return v2; } -VectorXd eonc::geometry::maxAtomMotionAppliedV(const VectorXd v1, +VectorXd eonc::geometry::maxAtomMotionAppliedV(const VectorXd &v1, double maxMotion) { VectorXd v2(v1); @@ -327,7 +326,7 @@ VectorXd eonc::geometry::maxAtomMotionAppliedV(const VectorXd v1, return v2; } -AtomMatrix eonc::geometry::maxMotionApplied(const AtomMatrix v1, +AtomMatrix eonc::geometry::maxMotionApplied(const AtomMatrix &v1, double maxMotion) { AtomMatrix v2(v1); @@ -338,7 +337,7 @@ AtomMatrix eonc::geometry::maxMotionApplied(const AtomMatrix v1, return v2; } -VectorXd eonc::geometry::maxMotionAppliedV(const VectorXd v1, +VectorXd eonc::geometry::maxMotionAppliedV(const VectorXd &v1, double maxMotion) { VectorXd v2(v1); @@ -431,7 +430,7 @@ bool eonc::geometry::sortedR(const Matter &m1, const Matter &m2, for (int j2 = 0; j2 < r2.rows(); j2++) { if (j2 == i2) continue; - atom a2; + atom a2{}; a2.r = m2.distance(i2, j2); a2.z = m2.getAtomicNr(j2); rdf2[i2].insert(a2); @@ -446,7 +445,7 @@ bool eonc::geometry::sortedR(const Matter &m1, const Matter &m2, for (int j1 = 0; j1 < r1.rows(); j1++) { if (j1 == i1) continue; - atom a; + atom a{}; a.r = m1.distance(i1, j1); a.z = m1.getAtomicNr(j1); rdf1[i1].insert(a); @@ -482,7 +481,8 @@ bool eonc::geometry::sortedR(const Matter &m1, const Matter &m2, return matches >= r1.rows(); } -void eonc::geometry::pushApart(std::shared_ptr m1, double minDistance) { +void eonc::geometry::pushApart(const std::shared_ptr &m1, + double minDistance) { if (minDistance <= 0) return; diff --git a/client/GeometryAnalysis.h b/client/GeometryAnalysis.h index caa9a9279..955df78da 100644 --- a/client/GeometryAnalysis.h +++ b/client/GeometryAnalysis.h @@ -18,27 +18,27 @@ class Matter; namespace geometry { -RotationMatrix rotationExtract(const AtomMatrix r1, const AtomMatrix r2); +RotationMatrix rotationExtract(const AtomMatrix &r1, const AtomMatrix &r2); bool rotationMatch(const Matter &m1, const Matter &m2, const double max_diff); void projectOutRotTrans(Eigen::VectorXd &step, const AtomMatrix &positions); -void rotationRemove(const AtomMatrix r1, std::shared_ptr m2); -void rotationRemove(const std::shared_ptr m1, +void rotationRemove(const AtomMatrix &r1, const std::shared_ptr &m2); +void rotationRemove(const std::shared_ptr &m1, std::shared_ptr m2); -void translationRemove(Matter &m1, const AtomMatrix r1); +void translationRemove(Matter &m1, const AtomMatrix &r1); void translationRemove(Matter &m1, const Matter &m2); -double maxAtomMotion(const AtomMatrix v1); -double maxAtomMotionV(const VectorXd v1); -long numAtomsMoved(const AtomMatrix v1, double cutoff); -AtomMatrix maxAtomMotionApplied(const AtomMatrix v1, double maxMotion); -VectorXd maxAtomMotionAppliedV(const VectorXd v1, double maxMotion); -AtomMatrix maxMotionApplied(const AtomMatrix v1, double maxMotion); -VectorXd maxMotionAppliedV(const VectorXd v1, double maxMotion); +double maxAtomMotion(const AtomMatrix &v1); +double maxAtomMotionV(const VectorXd &v1); +long numAtomsMoved(const AtomMatrix &v1, double cutoff); +AtomMatrix maxAtomMotionApplied(const AtomMatrix &v1, double maxMotion); +VectorXd maxAtomMotionAppliedV(const VectorXd &v1, double maxMotion); +AtomMatrix maxMotionApplied(const AtomMatrix &v1, double maxMotion); +VectorXd maxMotionAppliedV(const VectorXd &v1, double maxMotion); bool identical(const Matter &m1, const Matter &m2, const double distanceDifference); bool sortedR(const Matter &m1, const Matter &m2, const double distanceDifference); -void pushApart(std::shared_ptr m1, double minDistance); +void pushApart(const std::shared_ptr &m1, double minDistance); } // namespace geometry } // namespace eonc diff --git a/client/HelperFunctions.cpp b/client/HelperFunctions.cpp index d2820cf39..d26ef166f 100644 --- a/client/HelperFunctions.cpp +++ b/client/HelperFunctions.cpp @@ -36,8 +36,8 @@ using std::string; // Vector functions. // Make v1 orthogonal to v2 -AtomMatrix eonc::helpers::makeOrthogonal(const AtomMatrix v1, - const AtomMatrix v2) { +AtomMatrix eonc::helpers::makeOrthogonal(const AtomMatrix &v1, + const AtomMatrix &v2) { return v1 - matDot(v1, v2) * eonc::safemath::safe_normalized(v2); } @@ -53,7 +53,7 @@ void eonc::helpers::getTime(double *real, double *user, double *sys) { if (sys) *sys = 0.0; #else - struct rusage r_usage; + struct rusage r_usage{}; if (getrusage(RUSAGE_SELF, &r_usage) != 0) { EONC_LOG_WARNING("problem getting usage info: {}", strerror(errno)); } @@ -68,7 +68,7 @@ void eonc::helpers::getTime(double *real, double *user, double *sys) { #endif } -bool eonc::helpers::existsFile(string filename) { +bool eonc::helpers::existsFile(const string &filename) { return std::filesystem::exists(filename); } @@ -121,16 +121,23 @@ AtomMatrix eonc::helpers::loadMode(FILE *modeFile, int nAtoms) { mode.resize(nAtoms, 3); mode.setZero(); for (int i = 0; i < nAtoms; i++) { - fscanf(modeFile, "%lf %lf %lf", &mode(i, 0), &mode(i, 1), &mode(i, 2)); + if (std::fscanf(modeFile, "%lf %lf %lf", &mode(i, 0), &mode(i, 1), + &mode(i, 2)) != 3) { + EONC_LOG_CRITICAL("loadMode: short read at row {} (expected 3 doubles)", + i); + std::exit(1); + } } return mode; } AtomMatrix eonc::helpers::loadMode(string filename, int nAtoms) { - // Unique FILE* with RAII cleanup + // Unique FILE* with RAII cleanup. fclose ignored at scope-exit + // because the buffer was for read-only use; flushing writes is + // the only reason cert-err33-c flags fclose, and there's none. auto closer = [](FILE *f) { if (f) - std::fclose(f); + (void)std::fclose(f); }; std::unique_ptr modeFile( std::fopen(filename.c_str(), "rb"), closer); @@ -141,21 +148,28 @@ AtomMatrix eonc::helpers::loadMode(string filename, int nAtoms) { return loadMode(modeFile.get(), nAtoms); } -void eonc::helpers::saveMode(FILE *modeFile, std::shared_ptr matter, - AtomMatrix mode) { +void eonc::helpers::saveMode(FILE *modeFile, + const std::shared_ptr &matter, + const AtomMatrix &mode) { long const nAtoms = matter->numberOfAtoms(); for (long i = 0; i < nAtoms; ++i) { + int written = 0; if (matter->getFixed(i)) { - fprintf(modeFile, "0 0 0\n"); + written = std::fprintf(modeFile, "0 0 0\n"); } else { - fprintf(modeFile, "%lf\t%lf \t%lf\n", mode(i, 0), mode(i, 1), mode(i, 2)); + written = std::fprintf(modeFile, "%lf\t%lf \t%lf\n", mode(i, 0), + mode(i, 1), mode(i, 2)); + } + if (written < 0) { + EONC_LOG_CRITICAL("saveMode: fprintf failed at row {}", i); + std::exit(1); } } - return; } void eonc::helpers::saveMode(const std::string &filename, - std::shared_ptr matter, AtomMatrix mode) { + const std::shared_ptr &matter, + const AtomMatrix &mode) { std::ofstream out(filename); if (!out) return; @@ -170,8 +184,8 @@ void eonc::helpers::saveMode(const std::string &filename, } } -std::vector eonc::helpers::split_string_int(std::string s, - std::string delim) { +std::vector eonc::helpers::split_string_int(const std::string &s, + const std::string &delim) { std::vector list; if (s.empty()) return list; @@ -203,17 +217,19 @@ class MatterObjectiveFunction : public ObjectiveFunction { : ObjectiveFunction(parametersPassed), m_matter{mat} {} ~MatterObjectiveFunction() = default; - double getEnergy() { return m_matter.getPotentialEnergy(); } - VectorXd getGradient(bool fdstep = false) { + double getEnergy() override { return m_matter.getPotentialEnergy(); } + VectorXd getGradient(bool fdstep = false) override { return -m_matter.getForcesFreeV(); } - void setPositions(const VectorXd &x) { m_matter.setPositionsFreeV(x); } - VectorXd getPositions() { return m_matter.getPositionsFreeV(); } - int degreesOfFreedom() { return 3 * m_matter.numberOfFreeAtoms(); } - bool isConverged() { + void setPositions(const VectorXd &x) override { + m_matter.setPositionsFreeV(x); + } + VectorXd getPositions() override { return m_matter.getPositionsFreeV(); } + int degreesOfFreedom() override { return 3 * m_matter.numberOfFreeAtoms(); } + bool isConverged() override { return getConvergence() < params.optimizer_options.converged_force; } - double getConvergence() { + double getConvergence() override { if (params.optimizer_options.convergence_metric == "norm") { return m_matter.getForcesFreeV().norm(); } else if (params.optimizer_options.convergence_metric == "max_atom") { @@ -226,7 +242,7 @@ class MatterObjectiveFunction : public ObjectiveFunction { std::exit(1); } } - VectorXd difference(const VectorXd &a, const VectorXd &b) { + VectorXd difference(const VectorXd &a, const VectorXd &b) override { return m_matter.pbcV(a - b); } }; @@ -234,8 +250,8 @@ class MatterObjectiveFunction : public ObjectiveFunction { bool eonc::helpers::relaxMatter(Matter &matter, const Parameters ¶ms, bool quiet, bool writeMovie, bool checkpoint, - std::string prefixMovie, - std::string prefixCheckpoint) { + const std::string &prefixMovie, + const std::string &prefixCheckpoint) { eonc::log::Scoped m_log; auto objf = std::make_shared(matter, params); auto optim = eonc::helpers::create::mkOptim( diff --git a/client/HelperFunctions.h b/client/HelperFunctions.h index 22c30d2be..db98a5b5a 100644 --- a/client/HelperFunctions.h +++ b/client/HelperFunctions.h @@ -49,24 +49,26 @@ using eonc::geometry::sortedR; using eonc::geometry::translationRemove; AtomMatrix makeOrthogonal( - const AtomMatrix v1, - const AtomMatrix v2); // return orthogonal component of v1 from v2 + const AtomMatrix &v1, + const AtomMatrix &v2); // return orthogonal component of v1 from v2 bool relaxMatter(Matter &matter, const Parameters ¶ms, bool quiet = false, bool writeMovie = false, bool checkpoint = false, - std::string prefixMovie = std::string(), - std::string prefixCheckpoint = std::string()); + const std::string &prefixMovie = std::string(), + const std::string &prefixCheckpoint = std::string()); void getTime(double *real, double *user, double *sys); -bool existsFile(std::string filename); // does filename exist +bool existsFile(const std::string &filename); // does filename exist std::string getRelevantFile(std::string filename); // return filename containing _checkpoint // or _passed if such a file exists VectorXd loadMasses(std::string filename, int nAtoms); AtomMatrix loadMode(FILE *modeFile, int nAtoms); AtomMatrix loadMode(std::string filename, int nAtoms); -void saveMode(FILE *modeFile, std::shared_ptr matter, AtomMatrix mode); -void saveMode(const std::string &filename, std::shared_ptr matter, - AtomMatrix mode); -std::vector split_string_int(std::string s, std::string delim); +void saveMode(FILE *modeFile, const std::shared_ptr &matter, + const AtomMatrix &mode); +void saveMode(const std::string &filename, + const std::shared_ptr &matter, const AtomMatrix &mode); +std::vector split_string_int(const std::string &s, + const std::string &delim); } // namespace helpers diff --git a/client/HessianJob.cpp b/client/HessianJob.cpp index 31cf8d822..b3520ec05 100644 --- a/client/HessianJob.cpp +++ b/client/HessianJob.cpp @@ -18,7 +18,7 @@ #include #include -std::vector HessianJob::run(void) { +std::vector HessianJob::run() { std::string matter_in("pos.con"); std::vector returnFiles; diff --git a/client/IDPPObjectiveFunction.cpp b/client/IDPPObjectiveFunction.cpp index ede1ca3dd..8ff70178b 100644 --- a/client/IDPPObjectiveFunction.cpp +++ b/client/IDPPObjectiveFunction.cpp @@ -78,7 +78,8 @@ VectorXd IDPPObjectiveFunction::getGradient(bool fdstep) { // Convert N x 3 matrix to 3N vector and return negative gradient (force) // BUT getGradient expects the Gradient (positive derivative), so we return // -Forces Actually, typical eOn getGradient returns dV/dx. - return VectorXd::Map(forces.data(), 3 * natoms) * -1.0; + return VectorXd::Map(forces.data(), static_cast(3) * natoms) * + -1.0; } MatrixXd CollectiveIDPPObjectiveFunction::getDistanceMatrix(const Matter &m) { @@ -165,9 +166,14 @@ VectorXd CollectiveIDPPObjectiveFunction::getGradient(bool fdstep) { // Total NEB Force AtomMatrix f_neb = f_perp + f_spring; - // Store as Gradient (-Force) - totalGradient.segment(3 * natoms * (i - 1), 3 * natoms) = - VectorXd::Map(f_neb.data(), 3 * natoms) * -1.0; + // Store as Gradient (-Force). Force the multiplication chain + // through Eigen::Index so bugprone-implicit-widening-of- + // multiplication-result doesn't fire when natoms * 3 (int * int) + // implicitly widens to the size_t / Index that segment() and + // VectorXd::Map want. + const Eigen::Index stride = static_cast(3) * natoms; + totalGradient.segment(stride * (i - 1), stride) = + VectorXd::Map(f_neb.data(), stride) * -1.0; // Tracking convergence maxForce = std::max(maxForce, f_neb.template lpNorm()); diff --git a/client/IRACompare.cpp b/client/IRACompare.cpp index 387f8dd0d..0dc4ae7ce 100644 --- a/client/IRACompare.cpp +++ b/client/IRACompare.cpp @@ -13,19 +13,17 @@ #include "Eigen.h" #include -#ifdef WITH_IRA -extern "C" { -#include "iralib_interf.h" -} -#include "libs/IRA/IRAResource.h" // Include IRA resource after IRA interfaces are defined -#endif +// IRAResource is always compiled in. Its header redeclares libira's C +// entry points inline, so iralib_interf.h is not pulled in at compile +// time. libira.so is dlopen'd inside IRAResource::instance(); the call +// sites guard with try { res.require_loaded(); } catch { error = -1; }. +#include "libs/IRA/IRAResource.h" namespace eonc { IRACompare::MatchResult IRACompare::match(const Matter &m1, const Matter &m2, double distThreshold) { MatchResult result; -#ifdef WITH_IRA auto &res = get_ira_resource(); // Lock the library for the duration of this specific comparison @@ -110,16 +108,12 @@ IRACompare::MatchResult IRACompare::match(const Matter &m1, const Matter &m2, std::free(tr_ptr); if (perm_ptr != perm_buf.data()) std::free(perm_ptr); -#else - result.error = -1; -#endif return result; } IRACompare::MatchResult IRACompare::matchPBC(const Matter &m1, const Matter &m2, double distThreshold) { MatchResult result; -#ifdef WITH_IRA auto &res = get_ira_resource(); // Lock the library for the duration of this specific comparison @@ -178,16 +172,12 @@ IRACompare::MatchResult IRACompare::matchPBC(const Matter &m1, const Matter &m2, result.rotation = Eigen::Matrix3d::Identity(); result.translation = Eigen::Vector3d::Zero(); result.error = 0; -#else - result.error = -1; -#endif return result; } IRACompare::SymmetryResult IRACompare::findSymmetry(const Matter &m, double threshold, bool prescreenIh) { SymmetryResult result; -#ifdef WITH_IRA auto &res = get_ira_resource(); // Lock the library for the duration of this specific symmetry analysis @@ -292,9 +282,6 @@ IRACompare::findSymmetry(const Matter &m, double threshold, bool prescreenIh) { std::free(pg); if (prin_ax != prin_ax_buf.data()) std::free(prin_ax); -#else - result.error = -1; -#endif return result; } diff --git a/client/ImprovedDimer.cpp b/client/ImprovedDimer.cpp index e471c15f8..e66e12999 100644 --- a/client/ImprovedDimer.cpp +++ b/client/ImprovedDimer.cpp @@ -28,9 +28,9 @@ const char ImprovedDimer::OPT_SD[] = "sd"; const char ImprovedDimer::OPT_CG[] = "cg"; const char ImprovedDimer::OPT_LBFGS[] = "lbfgs"; -ImprovedDimer::ImprovedDimer(std::shared_ptr matter, +ImprovedDimer::ImprovedDimer(const std::shared_ptr &matter, const Parameters ¶ms, - std::shared_ptr pot) + const std::shared_ptr &pot) : LowestEigenmode(pot, params) { // Each dimer image gets its own potential for lock-free parallel evaluation auto x1Pot = (pot->needsPerImageInstance() && params.main_options.parallel) @@ -59,7 +59,7 @@ void ImprovedDimer::setReferenceMode(const VectorXd &ref) { void ImprovedDimer::clearReferenceMode() { hasFixedReference = false; } -void ImprovedDimer::compute(std::shared_ptr matter, +void ImprovedDimer::compute(const std::shared_ptr &matter, AtomMatrix initialDirectionAtomMatrix) { VectorXd initialDirection = VectorXd::Map(initialDirectionAtomMatrix.data(), diff --git a/client/ImprovedDimer.h b/client/ImprovedDimer.h index 613d399ad..a88e9c0f3 100644 --- a/client/ImprovedDimer.h +++ b/client/ImprovedDimer.h @@ -37,11 +37,12 @@ class ImprovedDimer : public LowestEigenmode { static const char OPT_CG[]; static const char OPT_LBFGS[]; - ImprovedDimer(std::shared_ptr matter, const Parameters ¶ms, - std::shared_ptr pot); + ImprovedDimer(const std::shared_ptr &matter, const Parameters ¶ms, + const std::shared_ptr &pot); ~ImprovedDimer() = default; - void compute(std::shared_ptr matter, AtomMatrix initialDirection); + void compute(const std::shared_ptr &matter, + AtomMatrix initialDirection); double getEigenvalue(); AtomMatrix getEigenvector(); diff --git a/client/LBFGS.cpp b/client/LBFGS.cpp index 7a2db8ee1..46492363d 100644 --- a/client/LBFGS.cpp +++ b/client/LBFGS.cpp @@ -16,6 +16,8 @@ #include +using eonc::StepResult; + Eigen::VectorXd LBFGS::getStep(double a_maxMove, const Eigen::VectorXd &a_f) { double H0 = m_optConfig.opts.lbfgs.inverse_curvature; Eigen::VectorXd r = m_objf->getPositions(); @@ -141,16 +143,29 @@ int LBFGS::update(const Eigen::VectorXd &a_r1, const Eigen::VectorXd &a_r0, return 0; } -int LBFGS::step(double a_maxMove) { - int status = 0; +StepResult LBFGS::step(double a_maxMove) { Eigen::VectorXd r = m_objf->getPositions(); Eigen::VectorXd f = -m_objf->getGradient(); + // Bail out instead of looping when the underlying potential returns + // NaN forces (post-saddle push placing two atoms inside the EAM + // repulsive core; LAMMPS / xtb / ASE all return NaN there). Without + // this guard isConverged() / line-search comparisons evaluate false + // on NaN and run() spins forever. Uses the `f` already loaded above + // -- no extra force evaluation. + if (!f.allFinite()) { + QUILL_LOG_WARNING(m_log, + "[LBFGS] non-finite force at iteration {} (NaN or " + "Inf); aborting minimization", + m_iteration); + return StepResult::Failed; + } + if (m_iteration > 0) { - status = update(r, m_rPrev, f, m_fPrev); + if (update(r, m_rPrev, f, m_fPrev) < 0) { + return StepResult::Failed; + } } - if (status < 0) - return -1; Eigen::VectorXd dr = getStep(a_maxMove, f); @@ -161,15 +176,19 @@ int LBFGS::step(double a_maxMove) { m_iteration++; - return m_objf->isConverged() ? 1 : 0; + return m_objf->isConverged() ? StepResult::Converged + : StepResult::NotConverged; } -int LBFGS::run(size_t a_maxSteps, double a_maxMove) { - int status; +StepResult LBFGS::run(size_t a_maxSteps, double a_maxMove) { while (!m_objf->isConverged() && m_iteration < a_maxSteps) { - status = step(a_maxMove); - if (status < 0) - return -1; + if (step(a_maxMove) == StepResult::Failed) { + return StepResult::Failed; + } } - return m_objf->isConverged() ? 1 : 0; + // No final getGradient() check here -- step()'s entry-side guard + // already aborts the loop on NaN, so a second fresh evaluation on + // exit just charges an extra force call per outer optimizer call. + return m_objf->isConverged() ? StepResult::Converged + : StepResult::NotConverged; } diff --git a/client/LBFGS.h b/client/LBFGS.h index 3cb36e0bb..08fe92351 100644 --- a/client/LBFGS.h +++ b/client/LBFGS.h @@ -22,25 +22,31 @@ namespace eonc { -#define LBFGS_EPS 1e-30 +/// Curvature-update gate. \f$|s_0 \cdot y_0|\f$ below this means the +/// gradient barely changed across the step; pushing it through +/// \f$\rho = 1 / (s_0 \cdot y_0)\f$ would amplify denormals into the +/// L-BFGS history. 1e-30 is well below any chemistry-relevant +/// value and well above double-precision underflow. +inline constexpr double LBFGS_EPS = 1e-30; class LBFGS final : public Optimizer { public: LBFGS(std::shared_ptr a_objf, const Parameters &a_params) - : Optimizer(a_objf, OptType::LBFGS, a_params), + : Optimizer(std::move(a_objf), OptType::LBFGS, + OptimizerConfig::fromParams(a_params)), m_iteration{0}, m_memory{std::min( - a_objf->degreesOfFreedom(), + m_objf->degreesOfFreedom(), static_cast(a_params.optimizer_options.lbfgs.memory))} {} ~LBFGS() = default; - int step(double a_maxMove) override; - int run(size_t a_maxIterations, double a_maxMove) override; + StepResult step(double a_maxMove) override; + StepResult run(size_t a_maxIterations, double a_maxMove) override; int update(const Eigen::VectorXd &a_r1, const Eigen::VectorXd &a_r0, const Eigen::VectorXd &a_f1, const Eigen::VectorXd &a_f0); - void reset(void); + void reset(); private: Eigen::VectorXd getStep(double a_maxMove, const Eigen::VectorXd &a_f); diff --git a/client/Lanczos.cpp b/client/Lanczos.cpp index 4a61d8111..5e39f1e1e 100644 --- a/client/Lanczos.cpp +++ b/client/Lanczos.cpp @@ -22,9 +22,10 @@ #include "EonLogger.h" #include -Lanczos::Lanczos(std::shared_ptr matter, const Parameters ¶ms, - std::shared_ptr pot) - : LowestEigenmode(pot, params) { +#include +Lanczos::Lanczos(const std::shared_ptr &matter, + const Parameters ¶ms, std::shared_ptr pot) + : LowestEigenmode(std::move(pot), params) { lowestEv.resize(matter->numberOfAtoms(), 3); lowestEv.setZero(); lowestEw = 0.0; @@ -33,7 +34,8 @@ Lanczos::Lanczos(std::shared_ptr matter, const Parameters ¶ms, // The 1 character variables in this method match the variables in the // equations in the paper given at the top of this file. -void Lanczos::compute(std::shared_ptr matter, AtomMatrix direction) { +void Lanczos::compute(const std::shared_ptr &matter, + AtomMatrix direction) { int size = 3 * matter->numberOfFreeAtoms(); MatrixXd T(size, params.lanczos_options.max_iterations), Q(size, params.lanczos_options.max_iterations); diff --git a/client/Lanczos.h b/client/Lanczos.h index e151f6eb5..e54f81600 100644 --- a/client/Lanczos.h +++ b/client/Lanczos.h @@ -23,10 +23,11 @@ namespace eonc { class Lanczos : public LowestEigenmode { public: - Lanczos(std::shared_ptr matter, const Parameters ¶ms, + Lanczos(const std::shared_ptr &matter, const Parameters ¶ms, std::shared_ptr pot); ~Lanczos() = default; - void compute(std::shared_ptr matter, AtomMatrix initialDirection); + void compute(const std::shared_ptr &matter, + AtomMatrix initialDirection); double getEigenvalue(); AtomMatrix getEigenvector(); diff --git a/client/Makefile.hopper b/client/Makefile.hopper deleted file mode 100644 index ac734bb11..000000000 --- a/client/Makefile.hopper +++ /dev/null @@ -1,42 +0,0 @@ -# before compiling you must execute: -# module swap PrgEnv-pgi PrgEnv-gnu -# module load gcc -# module load python -## you may need to confirm the "python_lib_path" is in fact -## pointing to the proper directory for the machine and version of python - -TARGET_NAME=client - -CC=cc -CXX=CC -LD=ld -AR=ar cru -RANLIB=ranlib -LDFLAGS += -static - -CXXFLAGS += -Wall -Wfatal-errors $(OPT) - -OPT=-O0 -ifndef DEBUG - NDEBUG=1 - OPT=-O2 -endif - -ifdef EONMPI - CXXFLAGS += -DEONMPI - TARGET_NAME=client_mpi -endif - -CXXFLAGS += -DLINUX - -ifdef NO_FORTRAN - CXXFLAGS += -DNO_FORTRAN -else - FC = ftn - FFLAGS += $(OPT) - FAR = ar cru - LDFLAGS += -lgfortran -endif - -include Rules.mk -LDFLAGS += -ldl -lutil diff --git a/client/Matter.cpp b/client/Matter.cpp index f577b58ce..d1abd6583 100644 --- a/client/Matter.cpp +++ b/client/Matter.cpp @@ -19,10 +19,18 @@ #include "EonLogger.h" #include #include +#include Matter::Matter(const Matter &matter) { operator=(matter); } const Matter &Matter::operator=(const Matter &matter) { + // Self-assignment guard (cert-oop54-cpp / + // bugprone-unhandled-self-assignment): without this, the resize() + // call below could repeatedly destroy and re-allocate the same + // backing storage we're about to copy from. + if (this == &matter) { + return *this; + } nAtoms = matter.nAtoms; resize(nAtoms); @@ -191,7 +199,8 @@ VectorXi Matter::getAtomicNrsFree() const { bool Matter::relax(bool quiet, bool writeMovie, bool checkpoint, std::string prefixMovie, std::string prefixCheckpoint) { return eonc::helpers::relaxMatter(*this, *parameters, quiet, writeMovie, - checkpoint, prefixMovie, prefixCheckpoint); + checkpoint, std::move(prefixMovie), + std::move(prefixCheckpoint)); } VectorXd Matter::getPositionsFreeV() const { @@ -362,10 +371,7 @@ long int Matter::numberOfFixedAtoms() const { return isFixed.sum(); } long Matter::getForceCalls() const { return (forceCalls); } -void Matter::resetForceCalls() { - forceCalls = 0; - return; -} +void Matter::resetForceCalls() { forceCalls = 0; } void Matter::computePotential() const { if (recomputePotential) { @@ -375,7 +381,7 @@ void Matter::computePotential() const { } if (potential->isSurrogate()) { // Surrogate potential case: uses free-atom subset interface - auto surrogatePotential = + auto *surrogatePotential = static_cast(potential.get()); auto [freePE, freeForces, vari] = surrogatePotential->get_ef_var( this->getPositionsFree(), this->getAtomicNrsFree(), cell); @@ -495,7 +501,7 @@ AtomMatrix Matter::getAccelerations() { Matrix Matter::getMasses() const { return masses; } void Matter::setPotential(std::shared_ptr pot) { - this->potential = pot; + this->potential = std::move(pot); recomputePotential = true; recomputeMaskedForces = true; } diff --git a/client/Matter.h b/client/Matter.h index 2b3c8a850..68f8156ef 100644 --- a/client/Matter.h +++ b/client/Matter.h @@ -228,11 +228,12 @@ class Matter { eonc::log::Scoped m_log; std::shared_ptr potential; // pointer to function calculating the energy and forces - bool usePeriodicBoundaries; // boolean telling periodic boundaries are used - mutable bool recomputePotential; // boolean indicating if the potential energy - // and forces need to be recalculated + bool usePeriodicBoundaries{}; // boolean telling periodic boundaries are used + mutable bool + recomputePotential{}; // boolean indicating if the potential energy + // and forces need to be recalculated mutable long - forceCalls; // keep track of how many force calls have been performed + forceCalls{}; // keep track of how many force calls have been performed // CON file header lines (indices 0-4 map to old headerCon1,2,4,5,6) std::array headerCon; @@ -246,13 +247,13 @@ class Matter { bool removeNetForce{true}; Parameters::structure_comparison_options_t structComp; // Full Parameters pointer retained solely for relax() delegation - const Parameters *parameters; - long nAtoms; + const Parameters *parameters{}; + long nAtoms{}; AtomMatrix positions; AtomMatrix velocities; mutable AtomMatrix forces; AtomMatrix biasForces; - BondBoost *biasPotential; + BondBoost *biasPotential{}; VectorXd masses; VectorXi atomicNrs; VectorXi isFixed; // array of bool, false for movable atom, true for fixed @@ -264,8 +265,8 @@ class Matter { mutable bool recomputeMaskedForces{true}; Matrix3d cell; Matrix3d cellInverse; - mutable double energyVariance; - mutable double potentialEnergy; + mutable double energyVariance{}; + mutable double potentialEnergy{}; }; } // namespace eonc diff --git a/client/MinModeSaddleSearch.cpp b/client/MinModeSaddleSearch.cpp index 5d42cae18..f5bc7cfb5 100644 --- a/client/MinModeSaddleSearch.cpp +++ b/client/MinModeSaddleSearch.cpp @@ -24,8 +24,11 @@ #include #include #include +#include using namespace eonc::helpers; +using eonc::SaddleStatus; +using eonc::StepResult; class MinModeObjectiveFunction : public ObjectiveFunction { private: @@ -41,12 +44,12 @@ class MinModeObjectiveFunction : public ObjectiveFunction { AtomMatrix modePassed, const Parameters ¶msPassed) : ObjectiveFunction(paramsPassed), matter{std::move(matterPassed)}, - minModeMethod{minModeMethodPassed}, + minModeMethod{std::move(minModeMethodPassed)}, eigenvector{std::move(modePassed)} {} ~MinModeObjectiveFunction() override = default; - VectorXd getGradient(bool fdstep = false) { + VectorXd getGradient(bool fdstep = false) override { AtomMatrix force = matter->getForces(); if (!fdstep || iteration == 0) { @@ -136,15 +139,15 @@ class MinModeObjectiveFunction : public ObjectiveFunction { return -forceV; } - double getEnergy() { return matter->getPotentialEnergy(); } - void setPositions(const VectorXd &x) { matter->setPositionsV(x); } - VectorXd getPositions() { return matter->getPositionsV(); } - int degreesOfFreedom() { return 3 * matter->numberOfAtoms(); } - bool isConverged() { + double getEnergy() override { return matter->getPotentialEnergy(); } + void setPositions(const VectorXd &x) override { matter->setPositionsV(x); } + VectorXd getPositions() override { return matter->getPositionsV(); } + int degreesOfFreedom() override { return 3 * matter->numberOfAtoms(); } + bool isConverged() override { return getConvergence() < params.saddle_search_options.converged_force; } - double getConvergence() { + double getConvergence() override { if (params.optimizer_options.convergence_metric == "norm") { return matter->getForcesFreeV().norm(); } else if (params.optimizer_options.convergence_metric == "max_atom") { @@ -158,22 +161,22 @@ class MinModeObjectiveFunction : public ObjectiveFunction { } } - VectorXd difference(const VectorXd &a, const VectorXd &b) { + VectorXd difference(const VectorXd &a, const VectorXd &b) override { return matter->pbcV(a - b); } }; MinModeSaddleSearch::MinModeSaddleSearch(std::shared_ptr matterPassed, - AtomMatrix modePassed, + const AtomMatrix &modePassed, double reactantEnergyPassed, const Parameters ¶metersPassed, std::shared_ptr potPassed) - : SaddleSearchMethod(potPassed, parametersPassed), - matter{matterPassed} { + : SaddleSearchMethod(std::move(potPassed), parametersPassed), + matter{std::move(matterPassed)} { reactantEnergy = reactantEnergyPassed; mode = modePassed; initialTangent_ = modePassed; - status = STATUS_GOOD; + status = SaddleStatus::Good; iteration = 0; minModeMethod = eonc::buildEigenmodeStrategy(matter, params, pot); @@ -186,17 +189,17 @@ MinModeSaddleSearch::MinModeSaddleSearch(std::shared_ptr matterPassed, } } -int MinModeSaddleSearch::run() { +SaddleStatus MinModeSaddleSearch::run() { return run(params.saddle_search_options.max_iterations); } -int MinModeSaddleSearch::run(long max_iterations_override) { +SaddleStatus MinModeSaddleSearch::run(long max_iterations_override) { long effectiveMaxIter = max_iterations_override; QUILL_LOG_DEBUG( log, "Saddle point search started from reactant with energy {} eV.", reactantEnergy); - int optStatus; + StepResult optStatus = StepResult::NotConverged; bool firstIteration = true; const char *forceLabel = params.optimizer_options.convergence_metric_label.c_str(); @@ -209,17 +212,17 @@ int MinModeSaddleSearch::run(long max_iterations_override) { if (eonc::eigenmodeGetEigenvalue(*minModeMethod) > 0) { QUILL_LOG_DEBUG(log, "GPR eigenvalue: {}", eonc::eigenmodeGetEigenvalue(*minModeMethod)); - return STATUS_NONNEGATIVE_ABORT; + return SaddleStatus::NonnegativeAbort; } - if (getEigenvalue() > 0.0 && status == STATUS_GOOD) { + if (getEigenvalue() > 0.0 && status == SaddleStatus::Good) { QUILL_LOG_DEBUG(log, "[MinModeSaddleSearch] eigenvalue not negative"); - status = STATUS_BAD_NO_NEGATIVE_MODE_AT_SADDLE; + status = SaddleStatus::BadNoNegativeModeAtSaddle; } if (std::abs(eonc::eigenmodeGetEigenvalue(*minModeMethod)) < params.saddle_search_options.zero_mode_abort_curvature) { QUILL_LOG_DEBUG(log, "Zero mode eigenvalue: {}", eonc::eigenmodeGetEigenvalue(*minModeMethod)); - status = STATUS_ZEROMODE_ABORT; + status = SaddleStatus::ZeromodeAbort; } iteration = eonc::eigenmodeTotalIterations(*minModeMethod); forcecalls = eonc::eigenmodeTotalForceCalls(*minModeMethod); @@ -297,7 +300,7 @@ int MinModeSaddleSearch::run(long max_iterations_override) { if (eonc::eigenmodeGetEigenvalue(*minModeMethod) > 0) { QUILL_LOG_DEBUG(log, "Nonnegative eigenvalue: {}", eonc::eigenmodeGetEigenvalue(*minModeMethod)); - return STATUS_NONNEGATIVE_ABORT; + return SaddleStatus::NonnegativeAbort; } } @@ -313,7 +316,7 @@ int MinModeSaddleSearch::run(long max_iterations_override) { initialPosition - matter->getPositions(), params.saddle_search_options.nonlocal_distance_abort); if (nm >= params.saddle_search_options.nonlocal_count_abort) { - status = STATUS_NONLOCAL_ABORT; + status = SaddleStatus::NonlocalAbort; break; } } @@ -322,14 +325,14 @@ int MinModeSaddleSearch::run(long max_iterations_override) { params.saddle_search_options.zero_mode_abort_curvature) { QUILL_LOG_DEBUG(log, "Zero mode eigenvalue: {}", eonc::eigenmodeGetEigenvalue(*minModeMethod)); - status = STATUS_ZEROMODE_ABORT; + status = SaddleStatus::ZeromodeAbort; break; } } firstIteration = false; if (iteration >= effectiveMaxIter) { - status = STATUS_BAD_MAX_ITERATIONS; + status = SaddleStatus::BadMaxIterations; break; } @@ -346,16 +349,17 @@ int MinModeSaddleSearch::run(long max_iterations_override) { } catch (const eonc::DimerModeRestoredException &) { QUILL_LOG_DEBUG( log, "Dimer restored to best state. Checking convergence..."); - status = objf->isConverged() ? STATUS_GOOD : STATUS_DIMER_RESTORED_BEST; + status = objf->isConverged() ? SaddleStatus::Good + : SaddleStatus::DimerRestoredBest; break; } catch (const eonc::DimerModeLostException &) { QUILL_LOG_WARNING(log, "Dimer lost mode completely. Aborting."); - status = STATUS_DIMER_LOST_MODE; + status = SaddleStatus::DimerLostMode; break; } - if (optStatus < 0) { - status = STATUS_OPTIMIZER_ERROR; + if (optStatus == StepResult::Failed) { + status = SaddleStatus::OptimizerError; break; } @@ -403,16 +407,17 @@ int MinModeSaddleSearch::run(long max_iterations_override) { } if (de > params.saddle_search_options.max_energy) { - status = STATUS_BAD_HIGH_ENERGY; + status = SaddleStatus::BadHighEnergy; break; } // Check ImprovedDimer mode convergence if (auto *dimer = eonc::asImprovedDimer(*minModeMethod)) { if (!dimer->rotationDidConverge) { - status = (dimer->getEigenvalue() < 0.0) ? STATUS_DIMER_RESTORED_BEST - : STATUS_DIMER_LOST_MODE; - if (status == STATUS_DIMER_RESTORED_BEST) { + status = (dimer->getEigenvalue() < 0.0) + ? SaddleStatus::DimerRestoredBest + : SaddleStatus::DimerLostMode; + if (status == SaddleStatus::DimerRestoredBest) { QUILL_LOG_DEBUG(log, "Dimer restored to valid state. C_tau={:.4f}", dimer->getEigenvalue()); } @@ -425,9 +430,9 @@ int MinModeSaddleSearch::run(long max_iterations_override) { eonc::eigenmodeCompute(*minModeMethod, matter, mode); } - if (getEigenvalue() > 0.0 && status == STATUS_GOOD) { + if (getEigenvalue() > 0.0 && status == SaddleStatus::Good) { QUILL_LOG_DEBUG(log, "[MinModeSaddleSearch] eigenvalue not negative"); - status = STATUS_BAD_NO_NEGATIVE_MODE_AT_SADDLE; + status = SaddleStatus::BadNoNegativeModeAtSaddle; } } diff --git a/client/MinModeSaddleSearch.h b/client/MinModeSaddleSearch.h index 5d534c118..d04e32a45 100644 --- a/client/MinModeSaddleSearch.h +++ b/client/MinModeSaddleSearch.h @@ -16,6 +16,7 @@ #include "Matter.h" #include "Optimizer.h" #include "SaddleSearchMethod.h" +#include "StatusTypes.h" #include @@ -24,84 +25,27 @@ namespace eonc { class MinModeSaddleSearch : public SaddleSearchMethod { public: - enum Status : int { - // DO NOT CHANGE THE ORDER OF THIS LIST - STATUS_GOOD, // 0 - STATUS_INIT, // 1 - STATUS_BAD_NO_CONVEX, // 2 - STATUS_BAD_HIGH_ENERGY, // 3 - STATUS_BAD_MAX_CONCAVE_ITERATIONS, // 4 - STATUS_BAD_MAX_ITERATIONS, // 5 - STATUS_BAD_NOT_CONNECTED, // 6 - STATUS_BAD_PREFACTOR, // 7 - STATUS_BAD_HIGH_BARRIER, // 8 - STATUS_BAD_MINIMA, // 9 - STATUS_FAILED_PREFACTOR, // 10 - STATUS_POTENTIAL_FAILED, // 11 - STATUS_NONNEGATIVE_ABORT, // 12 - STATUS_NONLOCAL_ABORT, // 13 - STATUS_NEGATIVE_BARRIER, // 14 - STATUS_BAD_MD_TRAJECTORY_TOO_SHORT, // 15 - STATUS_BAD_NO_NEGATIVE_MODE_AT_SADDLE, // 16 - STATUS_BAD_NO_BARRIER, // 17 - STATUS_ZEROMODE_ABORT, // 18 - STATUS_OPTIMIZER_ERROR, // 19 - STATUS_DIMER_LOST_MODE, // 20 - STATUS_DIMER_RESTORED_BEST // 21 - }; - - /// Human-readable message for a status code. - static constexpr std::string_view statusMessage(int status) { - constexpr std::string_view msgs[] = { - "Success", // 0 - "Initialized", // 1 - "Initial displacement unable to reach convex region", // 2 - "Barrier too high", // 3 - "Too many iterations in concave region", // 4 - "Too many iterations", // 5 - "Saddle is not connected to initial state", // 6 - "Prefactors not within window", // 7 - "Energy barrier not within window", // 8 - "Minimizations from saddle did not converge", // 9 - "Hessian calculation failed", // 10 - "Potential evaluation failed", // 11 - "Nonnegative initial mode, aborting", // 12 - "Nonlocal abort", // 13 - "Negative barrier detected", // 14 - "No reaction found during MD trajectory", // 15 - "Converged to stationary point with zero negative modes", // 16 - "No forward barrier found along minimized band", // 17 - "Zero mode abort", // 18 - "Optimizer error", // 19 - "Dimer lost mode", // 20 - "Dimer restored best", // 21 - }; - if (status >= 0 && - status < static_cast(sizeof(msgs) / sizeof(msgs[0]))) - return msgs[status]; - return "Unknown status"; - } + /// Status codes / messages live in StatusTypes.h + /// (eonc::SaddleStatus). Use SaddleStatus::Good etc. and + /// statusMessage(SaddleStatus) at call sites. MinModeSaddleSearch(std::shared_ptr matterPassed, - AtomMatrix modePassed, double reactantEnergyPassed, + const AtomMatrix &modePassed, double reactantEnergyPassed, const Parameters ¶metersPassed, std::shared_ptr potPassed); ~MinModeSaddleSearch() = default; - AtomMatrix getEigenvector(); // lowest eigenmode - double getEigenvalue(); // estimate for the lowest eigenvalue - std::string_view describeStatus(int status) const override { - return statusMessage(status); - } - int getStatus() const override { return status; } + AtomMatrix getEigenvector() override; // lowest eigenmode + double getEigenvalue() override; // estimate for the lowest eigenvalue + SaddleStatus getStatus() const override { return status; } int getIterationCount() const override { return iteration; } int getForceCalls() const override { return forcecalls; } - int run() override; - int run(long max_iterations_override); + SaddleStatus run() override; + SaddleStatus run(long max_iterations_override); int forcecalls{0}; int iteration{0}; - int status{0}; + SaddleStatus status{SaddleStatus::Good}; private: AtomMatrix mode; diff --git a/client/MonteCarloJob.cpp b/client/MonteCarloJob.cpp index a3284eeda..c8c54f8ce 100644 --- a/client/MonteCarloJob.cpp +++ b/client/MonteCarloJob.cpp @@ -19,7 +19,7 @@ #include #include -std::vector MonteCarloJob::run(void) { +std::vector MonteCarloJob::run() { std::string posInFilename("pos.con"); std::string posOutFilename("out.con"); diff --git a/client/NEBInitialPaths.cpp b/client/NEBInitialPaths.cpp index 262a07c79..44af9bcd6 100644 --- a/client/NEBInitialPaths.cpp +++ b/client/NEBInitialPaths.cpp @@ -136,10 +136,10 @@ std::vector idppPath(const Matter &initImg, const Matter &finalImg, auto idpp_optim = eonc::helpers::create::mkOptim( idpp_objf, params.neb_options.opt_method, params); - // Run the optimization - int status = - idpp_optim->run(params.neb_options.initialization.max_iterations, - params.neb_options.initialization.max_move); + // Run the optimization (return value not consumed; convergence + // is read out of getConvergence() below). + (void)idpp_optim->run(params.neb_options.initialization.max_iterations, + params.neb_options.initialization.max_move); // Log progress double residual = idpp_objf->getConvergence(); @@ -220,10 +220,18 @@ std::vector sidppPath(const Matter &initImg, const Matter &finalImg, auto log = eonc::log::get(); const auto &init = params.neb_options.initialization; + // Bind the conditional literal to a sized std::string before logging. + // The bare `use_zbl ? "-ZBL" : ""` ternary feeds a const char* into + // quill's safe_strnlen specialisation which calls memchr with + // n=SIZE_MAX; gcc 13's -Wstringop-overread loses the early-exit + // analysis and complains the source size (5 bytes for "-ZBL\0") is + // smaller than the bound. Routing through std::string takes the + // string overload that already knows the length. + const std::string zbl_suffix = use_zbl ? "-ZBL" : ""; QUILL_LOG_INFO(log, "Generating initial path using S-IDPP{} ({} images, " "alpha={:.2f}, frontier_tol={:.4f})...", - use_zbl ? "-ZBL" : "", target_nimgs, init.sidpp_alpha, + zbl_suffix, target_nimgs, init.sidpp_alpha, init.sidpp_frontier_tol); // 1. Start with endpoints [Reactant, Product] diff --git a/client/NEBObjectiveFunction.cpp b/client/NEBObjectiveFunction.cpp index 5e7cd1147..3a92928ec 100644 --- a/client/NEBObjectiveFunction.cpp +++ b/client/NEBObjectiveFunction.cpp @@ -16,7 +16,10 @@ namespace eonc { VectorXd NEBObjectiveFunction::getGradient(bool fdstep) { if (neb->movedAfterForceCall) neb->updateForces(); - const long seg = 3 * neb->atoms; + // long * int chain has to start in long so the implicit widening + // doesn't trip bugprone-implicit-widening-of-multiplication-result + // (atoms is `int` on the eOn side, seg / numImages are long). + const long seg = static_cast(3) * neb->atoms; VectorXd gradV(seg * neb->numImages); for (long i = 1; i <= neb->numImages; i++) { // Negate in-place during copy to avoid a second pass over 40KB @@ -36,19 +39,20 @@ double NEBObjectiveFunction::getEnergy() { void NEBObjectiveFunction::setPositions(const VectorXd &x) { neb->movedAfterForceCall = true; + const long seg = static_cast(3) * neb->atoms; for (long i = 1; i <= neb->numImages; i++) { - neb->path[i]->setPositions(AtomMatrix::Map( - x.segment(3 * neb->atoms * (i - 1), 3 * neb->atoms).data(), neb->atoms, - 3)); + neb->path[i]->setPositions( + AtomMatrix::Map(x.segment(seg * (i - 1), seg).data(), neb->atoms, 3)); } } VectorXd NEBObjectiveFunction::getPositions() { VectorXd posV; - posV.resize(3 * neb->atoms * neb->numImages); + const long seg = static_cast(3) * neb->atoms; + posV.resize(seg * neb->numImages); for (long i = 1; i <= neb->numImages; i++) { - posV.segment(3 * neb->atoms * (i - 1), 3 * neb->atoms) = - VectorXd::Map(neb->path[i]->getPositions().data(), 3 * neb->atoms); + posV.segment(seg * (i - 1), seg) = + VectorXd::Map(neb->path[i]->getPositions().data(), seg); } return posV; } diff --git a/client/NEBOcinebController.cpp b/client/NEBOcinebController.cpp index e0f56eb64..86bafacb3 100644 --- a/client/NEBOcinebController.cpp +++ b/client/NEBOcinebController.cpp @@ -21,8 +21,8 @@ namespace eonc::neb { OCINEBController::Config OCINEBController::fromParams(const Parameters ¶ms) { - auto &ci = params.neb_options.climbing_image; - auto &r = ci.ocineb; + const auto &ci = params.neb_options.climbing_image; + const auto &r = ci.ocineb; return Config{ r.use_mmf, r.trigger_force, @@ -80,7 +80,7 @@ OCINEBController::MMFResult OCINEBController::run(eonc::NudgedElasticBand &neb, AtomMatrix savedPositions = neb.path[neb.climbingImage]->getPositions(); double alignment = 0.0; - int mmfResult = runDimer(neb, alignment); + MMFStatus mmfResult = runDimer(neb, alignment); // Always update forces after MMF neb.movedAfterForceCall = true; @@ -93,22 +93,22 @@ OCINEBController::MMFResult OCINEBController::run(eonc::NudgedElasticBand &neb, return {newForce, true, false}; } - bool mmfHelped = (newForce < convForce) && mmfResult != -2; + bool mmfHelped = + (newForce < convForce) && mmfResult != MMFStatus::PositiveCurvature; if (mmfHelped) { updateThresholdSuccess(convForce, newForce); QUILL_LOG_DEBUG(log, "MMF helped (status={}). Force: {:.4f} -> {:.4f} " "({:.2f}x baseline). New threshold: {:.4f}", - mmfResult, convForce, newForce, newForce / baseline_force_, - current_threshold_); + to_int(mmfResult), convForce, newForce, + newForce / baseline_force_, current_threshold_); } else { - // On positive curvature (status=-2), the CI is at a minimum, not a - // saddle. Restore to pre-MMF position to prevent catastrophic force - // explosion. For other failures (alignment loss, force increase), - // let the NEB recover naturally -- the CI position may still be - // closer to the saddle than before. - if (mmfResult == -2) { + // PositiveCurvature: CI is at a minimum, not a saddle. Restore + // to pre-MMF position to prevent catastrophic force explosion. + // For Skipped / MaxIterations, let the NEB recover naturally -- + // the CI position may still be closer to the saddle than before. + if (mmfResult == MMFStatus::PositiveCurvature) { neb.path[neb.climbingImage]->setPositions(savedPositions); neb.movedAfterForceCall = true; has_cached_mode_ = false; @@ -119,9 +119,9 @@ OCINEBController::MMFResult OCINEBController::run(eonc::NudgedElasticBand &neb, log, "MMF backoff (status={}). Force: {:.4f} -> {:.4f}, " "Alignment: {:.3f}. {}New threshold: {:.4f} ({:.2f}x baseline)", - mmfResult, convForce, newForce, alignment, - mmfResult == -2 ? "Restored CI. " : "", current_threshold_, - current_threshold_ / baseline_force_); + to_int(mmfResult), convForce, newForce, alignment, + mmfResult == MMFStatus::PositiveCurvature ? "Restored CI. " : "", + current_threshold_, current_threshold_ / baseline_force_); } bool shouldReset = @@ -138,15 +138,15 @@ OCINEBController::MMFResult OCINEBController::run(eonc::NudgedElasticBand &neb, return {newForce, false, shouldReset}; } -int OCINEBController::runDimer(eonc::NudgedElasticBand &neb, - double &alignment) { +MMFStatus OCINEBController::runDimer(eonc::NudgedElasticBand &neb, + double &alignment) { auto *log = eonc::log::get(); alignment = 0.0; if (neb.climbingImage <= 0 || neb.climbingImage > neb.numImages) { QUILL_LOG_WARNING(log, "Invalid climbing image for MMF: {}", neb.climbingImage); - return -1; + return MMFStatus::Skipped; } AtomMatrix initialMode; @@ -158,7 +158,7 @@ int OCINEBController::runDimer(eonc::NudgedElasticBand &neb, double tangentNorm = initialMode.norm(); if (tangentNorm < 1e-8) { QUILL_LOG_WARNING(log, "Tangent too small for MMF initialization"); - return -1; + return MMFStatus::Skipped; } initialMode /= tangentNorm; @@ -166,14 +166,14 @@ int OCINEBController::runDimer(eonc::NudgedElasticBand &neb, neb.path[neb.climbingImage], initialMode, neb.path[neb.climbingImage]->getPotentialEnergy(), neb.params, neb.pot); - int minModeStatus; + SaddleStatus minModeStatus = SaddleStatus::Good; try { minModeStatus = tempMinModeSearch->run(cfg_.max_steps); } catch (const eonc::DimerModeRestoredException &) { - minModeStatus = MinModeSaddleSearch::STATUS_DIMER_RESTORED_BEST; + minModeStatus = SaddleStatus::DimerRestoredBest; QUILL_LOG_DEBUG(log, "MMF: Dimer restored to best state"); } catch (const eonc::DimerModeLostException &) { - minModeStatus = MinModeSaddleSearch::STATUS_DIMER_LOST_MODE; + minModeStatus = SaddleStatus::DimerLostMode; QUILL_LOG_WARNING(log, "Dimer lost mode during MMF refinement"); } @@ -184,34 +184,35 @@ int OCINEBController::runDimer(eonc::NudgedElasticBand &neb, QUILL_LOG_WARNING(log, "MMF skipped: Positive curvature detected (eig={:.4f}).", eigenvalue); - return -2; + return MMFStatus::PositiveCurvature; } AtomMatrix finalModeMatrix = tempMinModeSearch->getEigenvector(); - VectorXd finalMode = VectorXd::Map(finalModeMatrix.data(), 3 * neb.atoms); + const Eigen::Index seg = static_cast(3) * neb.atoms; + VectorXd finalMode = VectorXd::Map(finalModeMatrix.data(), seg); VectorXd currentTangent = - VectorXd::Map(neb.tangent[neb.climbingImage]->data(), 3 * neb.atoms); + VectorXd::Map(neb.tangent[neb.climbingImage]->data(), seg); alignment = std::abs(finalMode.normalized().dot(currentTangent.normalized())); - if (minModeStatus == MinModeSaddleSearch::STATUS_GOOD || - minModeStatus == MinModeSaddleSearch::STATUS_DIMER_RESTORED_BEST) { + if (minModeStatus == SaddleStatus::Good || + minModeStatus == SaddleStatus::DimerRestoredBest) { if (alignment < cfg_.angle_tol) { QUILL_LOG_WARNING( log, "MMF converged/restored but mode drifted (alignment={:.3f} < {:.3f})", alignment, cfg_.angle_tol); - return -1; + return MMFStatus::Skipped; } cached_mode_ = finalModeMatrix; has_cached_mode_ = true; - return 0; - } else if (minModeStatus == MinModeSaddleSearch::STATUS_BAD_MAX_ITERATIONS) { - return 1; - } else { - QUILL_LOG_WARNING(log, "MMF failed. Mode-tangent alignment: {:.3f}", - alignment); - return -1; + return MMFStatus::Helped; + } + if (minModeStatus == SaddleStatus::BadMaxIterations) { + return MMFStatus::MaxIterations; } + QUILL_LOG_WARNING(log, "MMF failed. Mode-tangent alignment: {:.3f}", + alignment); + return MMFStatus::Skipped; } void OCINEBController::updateThresholdSuccess(double convForce, diff --git a/client/NEBOcinebController.h b/client/NEBOcinebController.h index 7bf696cc3..ad5392270 100644 --- a/client/NEBOcinebController.h +++ b/client/NEBOcinebController.h @@ -20,6 +20,33 @@ class NudgedElasticBand; // forward declaration namespace eonc::neb { +/// Internal terminal status for the OCINEB MMF dimer step. Distinct +/// from the broader SaddleStatus vocabulary because positive +/// curvature on the climbing image is an *expected* short-circuit +/// for the controller (the CI sat at a local minimum, not a saddle) +/// rather than a generic saddle-search failure. +enum class MMFStatus : int { + /// MMF converged to a saddle on the climbing image, alignment + /// passed the angle_tol gate; cache the mode for the next call. + Helped = 0, + /// MMF hit the iteration cap without converging; CI position is + /// still useable but flagged as "MMF didn't help this round". + MaxIterations = 1, + /// MMF refused to act -- input was malformed (invalid climbing + /// image index or vanishing tangent), or the converged mode + /// drifted outside angle_tol after MMF. Treated by run() as "MMF + /// didn't help this round". + Skipped = -1, + /// Positive curvature detected -- the CI sat at a local minimum, + /// not a saddle. run() restores the pre-MMF position to prevent + /// a force explosion in the next NEB step. + PositiveCurvature = -2, +}; + +[[nodiscard]] constexpr int to_int(MMFStatus s) noexcept { + return static_cast(s); +} + /// Goswami (in prep). /// Controller for OCINEB hybrid dimer refinement of the climbing image. /// Manages MMF triggering, threshold adaptation, and backoff logic. @@ -68,7 +95,7 @@ class OCINEBController { bool has_cached_mode_{false}; AtomMatrix cached_mode_; - int runDimer(eonc::NudgedElasticBand &neb, double &alignment); + MMFStatus runDimer(eonc::NudgedElasticBand &neb, double &alignment); void updateThresholdSuccess(double convForce, double newForce); void updateThresholdBackoff(double alignment); }; diff --git a/client/NEBSplineExtrema.cpp b/client/NEBSplineExtrema.cpp index e11205977..e9f807c77 100644 --- a/client/NEBSplineExtrema.cpp +++ b/client/NEBSplineExtrema.cpp @@ -252,7 +252,7 @@ bool writePathCon( const std::vector> &path, const std::vector> &tangent, const std::vector> &eigenmode_solvers, - long numImages, bool estimateEigenvalues, std::string filename, + long numImages, bool estimateEigenvalues, const std::string &filename, std::optional bandIndex) { double distTotal = 0.0; diff --git a/client/NEBSplineExtrema.h b/client/NEBSplineExtrema.h index d4d45c474..8db8e8831 100644 --- a/client/NEBSplineExtrema.h +++ b/client/NEBSplineExtrema.h @@ -49,7 +49,7 @@ bool writePathCon( const std::vector> &path, const std::vector> &tangent, const std::vector> &eigenmode_solvers, - long numImages, bool estimateEigenvalues, std::string filename, + long numImages, bool estimateEigenvalues, const std::string &filename, std::optional bandIndex = std::nullopt); } // namespace eonc::neb diff --git a/client/NudgedElasticBand.cpp b/client/NudgedElasticBand.cpp index d1cd42d52..9d5a9a2a6 100644 --- a/client/NudgedElasticBand.cpp +++ b/client/NudgedElasticBand.cpp @@ -25,6 +25,7 @@ #include "EonLogger.h" #include +#include using namespace eonc::helpers; namespace fs = std::filesystem; @@ -36,7 +37,7 @@ NudgedElasticBand::NudgedElasticBand(std::shared_ptr initialPassed, std::shared_ptr potPassed) : NudgedElasticBand( [&]() { - auto &init_opt = parametersPassed.neb_options.initialization; + const auto &init_opt = parametersPassed.neb_options.initialization; const size_t base_count = parametersPassed.neb_options.image_count; // Apply oversampling factor if flag exists @@ -115,7 +116,7 @@ NudgedElasticBand::NudgedElasticBand(std::shared_ptr initialPassed, } return path; }(), - parametersPassed, potPassed) {} + parametersPassed, std::move(potPassed)) {} // Second constructor: Contains all the common setup code NudgedElasticBand::NudgedElasticBand(std::vector initPath, @@ -123,7 +124,7 @@ NudgedElasticBand::NudgedElasticBand(std::vector initPath, std::shared_ptr potPassed) : ci_enabled_{parametersPassed.neb_options.climbing_image.enabled}, params{parametersPassed}, - pot{potPassed}, + pot{std::move(potPassed)}, E_ref{0.0} { log = eonc::log::get(); diff --git a/client/Optimizer.cpp b/client/Optimizer.cpp index 9d498a0dc..00b63421c 100644 --- a/client/Optimizer.cpp +++ b/client/Optimizer.cpp @@ -18,9 +18,9 @@ #include "SteepestDescent.h" namespace eonc::helpers::create { -std::unique_ptr mkOptim(std::shared_ptr a_objf, - OptType a_otype, - const Parameters &a_params) { +std::unique_ptr +mkOptim(const std::shared_ptr &a_objf, OptType a_otype, + const Parameters &a_params) { switch (a_otype) { case OptType::FIRE: { return std::make_unique(a_objf, a_params); diff --git a/client/Optimizer.h b/client/Optimizer.h index 4d91ca688..f165fc86b 100644 --- a/client/Optimizer.h +++ b/client/Optimizer.h @@ -14,6 +14,7 @@ #include "Eigen.h" #include "ObjectiveFunction.h" #include "Parameters.h" +#include "StatusTypes.h" #include "EonLogger.h" @@ -72,7 +73,7 @@ class Optimizer { const OptimizerConfig &a_config) : m_otype{a_config.opts.method}, m_optConfig{a_config}, - m_objf{a_objf} { + m_objf{std::move(a_objf)} { EONC_LOG_WARNING( "You should explicitly set an optimizer while constructing the " "optimizer!!\n Defaulting to opt_method from the parameters"); @@ -81,26 +82,28 @@ class Optimizer { const OptimizerConfig &a_config) : m_otype{a_optype}, m_optConfig{a_config}, - m_objf{a_objf} {} + m_objf{std::move(a_objf)} {} // Backward-compat constructors [[deprecated("Pass OptimizerConfig directly")]] Optimizer(std::shared_ptr a_objf, const Parameters &a_params) - : Optimizer(a_objf, OptimizerConfig::fromParams(a_params)) {} + : Optimizer(std::move(a_objf), OptimizerConfig::fromParams(a_params)) {} [[deprecated("Pass OptimizerConfig directly")]] Optimizer(std::shared_ptr a_objf, OptType a_optype, const Parameters &a_params) - : Optimizer(a_objf, a_optype, OptimizerConfig::fromParams(a_params)) {} + : Optimizer(std::move(a_objf), a_optype, + OptimizerConfig::fromParams(a_params)) {} - virtual ~Optimizer() {}; - virtual int step(double a_maxMove) = 0; - virtual int run(size_t a_maxIterations, double a_maxMove) = 0; + virtual ~Optimizer() = default; + virtual StepResult step(double a_maxMove) = 0; + virtual StepResult run(size_t a_maxIterations, double a_maxMove) = 0; }; namespace helpers::create { -std::unique_ptr mkOptim(std::shared_ptr a_objf, - OptType a_otype, const Parameters &a_params); +std::unique_ptr +mkOptim(const std::shared_ptr &a_objf, OptType a_otype, + const Parameters &a_params); } } // namespace eonc diff --git a/client/ParallelReplicaJob.cpp b/client/ParallelReplicaJob.cpp index cb5676042..870e87623 100644 --- a/client/ParallelReplicaJob.cpp +++ b/client/ParallelReplicaJob.cpp @@ -32,7 +32,7 @@ std::vector ParallelReplicaJob::run() { auto trajectory = std::make_shared(pot, params); *trajectory = *reactant; - Dynamics dynamics(trajectory.get(), params); + Dynamics dynamics(trajectory.get(), DynamicsConfig::fromParams(params)); BondBoost bondBoost(trajectory.get(), params); if (params.hyperdynamics_options.bias_potential == @@ -227,7 +227,7 @@ std::vector ParallelReplicaJob::run() { } void ParallelReplicaJob::dephase(Matter &trajectory) { - Dynamics dynamics(&trajectory, params); + Dynamics dynamics(&trajectory, DynamicsConfig::fromParams(params)); int dephaseSteps = static_cast(std::floor(params.parallel_replica_options.dephase_time / diff --git a/client/Parameters.cpp b/client/Parameters.cpp index 752ee3c40..5e38b48e6 100644 --- a/client/Parameters.cpp +++ b/client/Parameters.cpp @@ -80,12 +80,26 @@ int Parameters::load(std::string filename) { int Parameters::load(FILE *file) { // Legacy FILE* overload: read into string buffer, use INIReader buffer ctor - fseek(file, 0, SEEK_END); + if (std::fseek(file, 0, SEEK_END) != 0) { + EONC_LOG_ERROR("fseek to end failed on Parameters::load(FILE*)"); + return 1; + } long size = ftell(file); - fseek(file, 0, SEEK_SET); + if (size < 0) { + EONC_LOG_ERROR("ftell failed on Parameters::load(FILE*)"); + return 1; + } + if (std::fseek(file, 0, SEEK_SET) != 0) { + EONC_LOG_ERROR("fseek to start failed on Parameters::load(FILE*)"); + return 1; + } std::string buffer(size, '\0'); - fread(buffer.data(), 1, size, file); + if (std::fread(buffer.data(), 1, size, file) != + static_cast(size)) { + EONC_LOG_ERROR("short fread on Parameters::load(FILE*)"); + return 1; + } INIReader ini(buffer.c_str(), buffer.size()); if (ini.ParseError() < 0) { diff --git a/client/Parameters.h b/client/Parameters.h index c6444d5c9..a4f466d5d 100644 --- a/client/Parameters.h +++ b/client/Parameters.h @@ -64,6 +64,13 @@ class Parameters { double MPIPollPeriod{0.25}; bool LAMMPSLogging{false}; int LAMMPSThreads{0}; + /// Optional path to a portable LAMMPS run-input bundle (.eonlpb) + /// containing in.lammps + every pair_coeff data file it + /// references. When set, LAMMPSPot extracts the bundle to a + /// per-instance scratch dir under temp_directory_path() and + /// liblammps reads in.lammps from there. Empty string keeps the + /// historical CWD-only behaviour. + std::string LAMMPSBundlePath{""}; bool EMTRasmussen{false}; bool LogPotential{false}; std::string extPotPath{"./ext_pot"}; diff --git a/client/ParametersINI.cpp b/client/ParametersINI.cpp index 54cbe4e21..87d90db29 100644 --- a/client/ParametersINI.cpp +++ b/client/ParametersINI.cpp @@ -86,6 +86,8 @@ int load_ini(INIReader &ini, Parameters ¶ms) { "Potential", "lammps_logging", params.potential_options.LAMMPSLogging); params.potential_options.LAMMPSThreads = static_cast(ini.GetInteger( "Potential", "lammps_threads", params.potential_options.LAMMPSThreads)); + params.potential_options.LAMMPSBundlePath = ini.Get( + "Potential", "lammps_bundle", params.potential_options.LAMMPSBundlePath); params.potential_options.EMTRasmussen = ini.GetBoolean( "Potential", "emt_rasmussen", params.potential_options.EMTRasmussen); params.potential_options.extPotPath = diff --git a/client/ParametersJSON.cpp b/client/ParametersJSON.cpp index db8032236..ff0000a3a 100644 --- a/client/ParametersJSON.cpp +++ b/client/ParametersJSON.cpp @@ -64,6 +64,7 @@ json to_json(const Parameters &p) { {"mpi_poll_period", p.potential_options.MPIPollPeriod}, {"lammps_logging", p.potential_options.LAMMPSLogging}, {"lammps_threads", p.potential_options.LAMMPSThreads}, + {"lammps_bundle", p.potential_options.LAMMPSBundlePath}, {"emt_rasmussen", p.potential_options.EMTRasmussen}, {"log_potential", p.potential_options.LogPotential}, {"ext_pot_path", p.potential_options.extPotPath}, @@ -209,6 +210,7 @@ void from_json(const json &j, Parameters &p) { JSON_OPT(s, "mpi_poll_period", p.potential_options.MPIPollPeriod); JSON_OPT(s, "lammps_logging", p.potential_options.LAMMPSLogging); JSON_OPT(s, "lammps_threads", p.potential_options.LAMMPSThreads); + JSON_OPT(s, "lammps_bundle", p.potential_options.LAMMPSBundlePath); JSON_OPT(s, "emt_rasmussen", p.potential_options.EMTRasmussen); JSON_OPT(s, "log_potential", p.potential_options.LogPotential); JSON_OPT(s, "ext_pot_path", p.potential_options.extPotPath); diff --git a/client/Potential.cpp b/client/Potential.cpp index c33f132d9..322b39165 100644 --- a/client/Potential.cpp +++ b/client/Potential.cpp @@ -78,10 +78,8 @@ #endif #ifndef _WIN32 -#ifdef WITH_VASP #include "potentials/VASP/VASP.h" #endif -#endif #ifdef WITH_AMS #include "potentials/AMS/AMS.h" @@ -110,9 +108,8 @@ // Should respect Fortran availability -#ifdef WITH_XTB +// XTB always compiled; XtbLoader dlopens libxtb at first construction. #include "potentials/XTBPot/XTBPot.h" -#endif #include @@ -224,12 +221,10 @@ std::shared_ptr makePotential(PotType ptype, } #endif #ifndef _WIN32 -#ifdef WITH_VASP case PotType::VASP: { return (std::make_shared(params)); break; } -#endif #endif case PotType::LAMMPS: { return std::make_shared(params); @@ -285,13 +280,10 @@ std::shared_ptr makePotential(PotType ptype, break; } #endif -// TODO: Handle Fortran interaction -#ifdef WITH_XTB case PotType::XTB: { return (std::make_shared(params)); break; } -#endif #ifdef WITH_ASE_ORCA case PotType::ASE_ORCA: { return (std::make_shared(params)); diff --git a/client/ProcessSearchJob.cpp b/client/ProcessSearchJob.cpp index c9591900f..35b868a93 100644 --- a/client/ProcessSearchJob.cpp +++ b/client/ProcessSearchJob.cpp @@ -9,27 +9,27 @@ ** Repo: ** https://github.com/TheochemUI/eOn */ + #include "ProcessSearchJob.h" -#ifdef WITH_ARTN #include "ARTnSaddleSearch.h" -#endif #include "BasinHoppingSaddleSearch.h" #include "BiasedGradientSquaredDescent.h" #include "DynamicsSaddleSearch.h" +#include "EonLogger.h" #include "EpiCenters.h" #include "HelperFunctions.h" #include "MinModeSaddleSearch.h" -#include "Optimizer.h" #include "Prefactor.h" -#include #include #include #include #include #include +#include -#include "EonLogger.h" +using eonc::SaddleStatus; +using eonc::to_int; std::vector ProcessSearchJob::run() { std::string reactantFilename("pos.con"); @@ -97,19 +97,15 @@ std::vector ProcessSearchJob::run() { eonc::EpiCenters::DISP_LOAD) { mode = eonc::helpers::loadMode(modeFilename, initial->numberOfAtoms()); } -#ifdef WITH_ARTN // ARTn as a min-mode drop-in: eOn displaces, seeds the mode, ARTn // takes over from the displaced structure. if (useARTnAsMinMode) { saddleSearch = std::make_unique(saddle, pot, mode, params); - } else -#endif - { + } else { saddleSearch = std::make_unique( saddle, mode, initial->getPotentialEnergy(), params, pot); } -#ifdef WITH_ARTN } else if (params.saddle_search_options.method == "artn") { // ARTn handles its own push from the minimum, eigenmode estimation, // and perpendicular relaxation internally. @@ -121,7 +117,6 @@ std::vector ProcessSearchJob::run() { } saddleSearch = std::make_unique(saddle, pot, artnMode, params); -#endif } else if (params.saddle_search_options.method == "basin_hopping") { saddleSearch = std::make_unique(min1, saddle, pot, params); @@ -132,24 +127,23 @@ std::vector ProcessSearchJob::run() { saddle, initial->getPotentialEnergy(), params); } -#ifndef WITH_ARTN - // Post-dispatch guard for both ARTn entry points so users without a - // WITH_ARTN build get a clean per-case error instead of a silent - // fall-through. Two distinct messages so downstream tooling and the - // integration tests can match on the specific entry point. - if (params.saddle_search_options.method == "artn") { - throw std::runtime_error( - "saddle_search.method=artn requires a build with ARTn support " - "(reconfigure with -Dwith_artn=true)"); - } - if (useARTnAsMinMode) { + // Runtime guard for both ARTn entry points: the *Resource shim is + // always compiled in, but libartn.so is dlopen'd lazily. Surface a + // missing-library error before doProcessSearch() so the message + // names the entry point that triggered the lookup. + const bool needARTn = + params.saddle_search_options.method == "artn" || useARTnAsMinMode; + if (needARTn && !eonc::get_artn_resource().is_loaded()) { + const char *which = (params.saddle_search_options.method == "artn") + ? "saddle_search.method=artn" + : "saddle_search.minmode_method=artn"; throw std::runtime_error( - "saddle_search.minmode_method=artn requires a build with ARTn " - "support (reconfigure with -Dwith_artn=true)"); + std::string(which) + + " requires libartn at runtime " + "(set LD_LIBRARY_PATH so eonc::ARTnResource finds it)"); } -#endif - int status = doProcessSearch(); + SaddleStatus status = doProcessSearch(); printEndState(status); saveData(status); @@ -157,9 +151,9 @@ std::vector ProcessSearchJob::run() { return returnFiles; } -int ProcessSearchJob::doProcessSearch() { +SaddleStatus ProcessSearchJob::doProcessSearch() { Matter matterTemp(pot, params); - long status; + SaddleStatus status = SaddleStatus::Good; size_t fctmp{0}; fctmp = pot->forceCallCounter; @@ -176,7 +170,7 @@ int ProcessSearchJob::doProcessSearch() { EONC_LOG_DEBUG("Got {} calls in the saddle search, with previous {}", fCallsSaddle, fctmp); - if (status != MinModeSaddleSearch::STATUS_GOOD) { + if (status != SaddleStatus::Good) { return status; } @@ -228,7 +222,7 @@ int ProcessSearchJob::doProcessSearch() { min2->getPotentialCalls() - fc2_before); if (!converged1 || !converged2) { - return MinModeSaddleSearch::STATUS_BAD_MINIMA; + return SaddleStatus::BadMinima; } if (!(initial->compare(*min1)) && initial->compare(*min2)) { @@ -239,12 +233,12 @@ int ProcessSearchJob::doProcessSearch() { if (!initial->compare(*min1)) { QUILL_LOG_DEBUG(log, "initial != min1"); - return MinModeSaddleSearch::STATUS_BAD_NOT_CONNECTED; + return SaddleStatus::BadNotConnected; } if (initial->compare(*min2)) { QUILL_LOG_DEBUG(log, "both minima are the initial state"); - return MinModeSaddleSearch::STATUS_BAD_NOT_CONNECTED; + return SaddleStatus::BadNotConnected; } if (!params.process_search_options.minimize_first) { @@ -256,11 +250,11 @@ int ProcessSearchJob::doProcessSearch() { if ((params.saddle_search_options.max_energy < barriersValues[0]) || (params.saddle_search_options.max_energy < barriersValues[1])) { - return MinModeSaddleSearch::STATUS_BAD_HIGH_BARRIER; + return SaddleStatus::BadHighBarrier; } if (barriersValues[0] < 0.0 || barriersValues[1] < 0.0) { - return MinModeSaddleSearch::STATUS_NEGATIVE_BARRIER; + return SaddleStatus::NegativeBarrier; } if (!params.prefactor_options.default_value) { @@ -271,19 +265,19 @@ int ProcessSearchJob::doProcessSearch() { params, min1.get(), saddle.get(), min2.get(), pref1, pref2); if (prefStatus == -1) { EONC_LOG_ERROR("Prefactor: bad calculation"); - return MinModeSaddleSearch::STATUS_FAILED_PREFACTOR; + return SaddleStatus::FailedPrefactor; } fCallsPrefactors += min1->getPotentialCalls() - fctmp; if ((pref1 > params.prefactor_options.max_value) || (pref1 < params.prefactor_options.min_value)) { EONC_LOG_ERROR("Bad reactant-to-saddle prefactor: {}", pref1); - return MinModeSaddleSearch::STATUS_BAD_PREFACTOR; + return SaddleStatus::BadPrefactor; } if ((pref2 > params.prefactor_options.max_value) || (pref2 < params.prefactor_options.min_value)) { EONC_LOG_ERROR("Bad product-to-saddle prefactor: {}", pref2); - return MinModeSaddleSearch::STATUS_BAD_PREFACTOR; + return SaddleStatus::BadPrefactor; } prefactorsValues[0] = pref1; prefactorsValues[1] = pref2; @@ -292,16 +286,16 @@ int ProcessSearchJob::doProcessSearch() { prefactorsValues[0] = params.prefactor_options.default_value; prefactorsValues[1] = params.prefactor_options.default_value; } - return MinModeSaddleSearch::STATUS_GOOD; + return SaddleStatus::Good; } -void ProcessSearchJob::saveData(int status) { +void ProcessSearchJob::saveData(SaddleStatus status) { std::string resultsFilename("results.dat"); returnFiles.push_back(resultsFilename); std::ofstream out(resultsFilename, std::ios::binary); if (out) { - out << std::format("{} termination_reason\n", status); + out << std::format("{} termination_reason\n", to_int(status)); out << std::format("{} termination_reason_text\n", saddleSearch->describeStatus(status)); out << std::format("{} random_seed\n", params.main_options.randomSeed); @@ -359,9 +353,9 @@ void ProcessSearchJob::saveData(int status) { min2->matter2con(productFilename); } -void ProcessSearchJob::printEndState(int status) { +void ProcessSearchJob::printEndState(SaddleStatus status) { auto msg = saddleSearch->describeStatus(status); - if (status == MinModeSaddleSearch::STATUS_GOOD) { + if (status == SaddleStatus::Good) { QUILL_LOG_DEBUG(log, "[Saddle Search] {}", msg); } else { QUILL_LOG_ERROR(log, "[Saddle Search] {}", msg); diff --git a/client/ProcessSearchJob.h b/client/ProcessSearchJob.h index b22f01253..183d0d7dd 100644 --- a/client/ProcessSearchJob.h +++ b/client/ProcessSearchJob.h @@ -61,11 +61,11 @@ class ProcessSearchJob : public Job { private: eonc::log::Scoped log; //! Runs the correct saddle search; also checks if the run was successful - int doProcessSearch(void); + SaddleStatus doProcessSearch(); //! Logs the run status and makes sure the run was successful - void printEndState(int status); + void printEndState(SaddleStatus status); //! Writes the results from the run to file - void saveData(int status); + void saveData(SaddleStatus status); //! Container for the results of the run std::vector returnFiles; diff --git a/client/Quickmin.cpp b/client/Quickmin.cpp index 7427ea92a..d12ea52ea 100644 --- a/client/Quickmin.cpp +++ b/client/Quickmin.cpp @@ -12,7 +12,9 @@ #include "Quickmin.h" #include "HelperFunctions.h" -int Quickmin::step(double a_maxMove) { +using eonc::StepResult; + +StepResult Quickmin::step(double a_maxMove) { Eigen::VectorXd force = -m_objf->getGradient(); if (m_optConfig.opts.quickmin.steepest_descent) { m_vel.setZero(); @@ -32,12 +34,14 @@ int Quickmin::step(double a_maxMove) { QUILL_LOG_INFO(m_log, "{} M_Vel.norm() is {}", m_iteration, m_vel.norm()); m_objf->setPositions(m_objf->getPositions() + dr); m_iteration++; - return m_objf->isConverged() ? 1 : 0; + return m_objf->isConverged() ? StepResult::Converged + : StepResult::NotConverged; } -int Quickmin::run(size_t a_maxSteps, double a_maxMove) { +StepResult Quickmin::run(size_t a_maxSteps, double a_maxMove) { while (!m_objf->isConverged() && m_iteration < a_maxSteps) { step(a_maxMove); } - return m_objf->isConverged() ? 1 : 0; + return m_objf->isConverged() ? StepResult::Converged + : StepResult::NotConverged; } diff --git a/client/Quickmin.h b/client/Quickmin.h index db393ee60..83f9f0f52 100644 --- a/client/Quickmin.h +++ b/client/Quickmin.h @@ -24,17 +24,18 @@ class Quickmin final : public Optimizer { public: Quickmin(std::shared_ptr a_objf, const Parameters &a_params) - : Optimizer(a_objf, OptType::QM, a_params), + : Optimizer(std::move(a_objf), OptType::QM, + OptimizerConfig::fromParams(a_params)), m_dt{a_params.optimizer_options.time_step}, m_dt_max{a_params.optimizer_options.max_time_step}, m_max_move{a_params.optimizer_options.max_move}, - m_vel{Eigen::VectorXd::Zero(a_objf->degreesOfFreedom())}, + m_vel{Eigen::VectorXd::Zero(m_objf->degreesOfFreedom())}, m_iteration{0}, m_max_iter{a_params.optimizer_options.max_iterations} {} ~Quickmin() = default; - int step(double a_maxMove) override; - int run(size_t a_maxIterations, double a_maxMove) override; + StepResult step(double a_maxMove) override; + StepResult run(size_t a_maxIterations, double a_maxMove) override; private: double m_dt, m_dt_max, m_max_move; diff --git a/client/RandomNumbers.cpp b/client/RandomNumbers.cpp index 8a2f4836c..9c2e3f286 100644 --- a/client/RandomNumbers.cpp +++ b/client/RandomNumbers.cpp @@ -53,10 +53,14 @@ double eonc::rng::random(long newSeed) { iv[j] = seed; if (iy < 1) iy += IMM1; - if ((temp = double(AM * iy)) > RNMX) + // Compute outside the if-condition so the side-effecting + // assignment-in-if pattern doesn't fire bugprone-assignment-in-if- + // condition. Behaviour is identical -- temp gets the AM*iy value + // either way. + temp = static_cast(AM * iy); + if (temp > RNMX) return RNMX; - else - return temp; + return temp; } double eonc::rng::randomDouble() { return (random()); } diff --git a/client/ReplicaDynamicsJob.cpp b/client/ReplicaDynamicsJob.cpp index de6432c5a..e2a520183 100644 --- a/client/ReplicaDynamicsJob.cpp +++ b/client/ReplicaDynamicsJob.cpp @@ -80,7 +80,7 @@ void ReplicaDynamicsJob::dephase() { long DephaseSteps = static_cast(params.parallel_replica_options.dephase_time / params.dynamics_options.time_step); - Dynamics dephaseDynamics(current.get(), params); + Dynamics dephaseDynamics(current.get(), DynamicsConfig::fromParams(params)); QUILL_LOG_DEBUG(log, "Dephasing for {:.2f} fs", params.parallel_replica_options.dephase_time * params.constants.timeUnit); diff --git a/client/ReplicaExchangeJob.cpp b/client/ReplicaExchangeJob.cpp index 371f99e11..49f383446 100644 --- a/client/ReplicaExchangeJob.cpp +++ b/client/ReplicaExchangeJob.cpp @@ -24,14 +24,16 @@ #include std::vector ReplicaExchangeJob::run() { + // Round-to-nearest with std::lround instead of `(int)(x + 0.5)`, + // which produces incorrect rounding for negative numbers and + // double-precision values that were already exactly representable + // (bugprone-incorrect-roundings). long samplingSteps = - static_cast(params.replica_exchange_options.sampling_time / - params.dynamics_options.time_step + - 0.5); + std::lround(params.replica_exchange_options.sampling_time / + params.dynamics_options.time_step); long exchangePeriodSteps = - static_cast(params.replica_exchange_options.exchange_period / - params.dynamics_options.time_step + - 0.5); + std::lround(params.replica_exchange_options.exchange_period / + params.dynamics_options.time_step); const double kB = params.constants.kB; std::string posFilename = @@ -54,7 +56,8 @@ std::vector ReplicaExchangeJob::run() { auto replicaPot = perImage ? eonc::helpers::makePotential(params) : pot; replica[i] = std::make_shared(replicaPot, params); *replica[i] = *pos; - replicaDynamics[i] = std::make_unique(replica[i].get(), params); + replicaDynamics[i] = std::make_unique( + replica[i].get(), DynamicsConfig::fromParams(params)); } std::vector replicaTemperature(nReplicas); diff --git a/client/Rules.mk b/client/Rules.mk deleted file mode 100644 index fc78b0ed4..000000000 --- a/client/Rules.mk +++ /dev/null @@ -1,244 +0,0 @@ -#------------------------------------ -#Potentials -POTDIRS += ./potentials/EMT/ -LIBS += ./potentials/EMT/libEMT.a -POTENTIALS += "+EMT " -POTDIRS += ./potentials/Morse/ -LIBS += ./potentials/Morse/libMorse.a -POTENTIALS += "+MORSE " -POTDIRS += ./potentials/LJ/ -LIBS += ./potentials/LJ/libLJ.a -POTENTIALS += "+LJ" -POTDIRS += ./potentials/LJCluster/ -LIBS += ./potentials/LJCluster/libLJCluster.a -POTENTIALS += "+LJ" -POTDIRS += ./potentials/EAM/ -LIBS += ./potentials/EAM/libEAM.a -POTENTIALS += "+EAM" -POTDIRS += ./potentials/QSC/ -LIBS += ./potentials/QSC/libQSC.a -POTENTIALS += "+QSC" -POTDIRS += ./potentials/Water/ -LIBS += ./potentials/Water/libwater.a -POTENTIALS += "+H2O" -POTDIRS += ./potentials/Water_Pt/ -LIBS += ./potentials/Water_Pt/libtip4p_pt.a -POTENTIALS += "+H2O_Pt" -POTDIRS += ./potentials/IMD/ -LIBS += ./potentials/IMD/libIMD.a -POTENTIALS += "+IMD" -POTDIRS += ./potentials/ExtPot/ -LIBS += ./potentials/ExtPot/libextpot.a -POTENTIALS += "+EXT_POT" -POTDIRS += ./potentials/AMS/ -LIBS += ./potentials/AMS/libAMS.a -POTENTIALS += "+AMS" -#POTDIRS += ./potentials/PyAMFF/ -#LIBS += ./potentials/PyAMFF/libPyAMFF.a -#POTENTIALS += "+PYAMFF" - -#Potentials relying on fortran -ifdef NO_FORTRAN - POTENTIALS += "-Aluminum -Lenosky -SW -Tersoff -EDIP -H2O_H -FeHe" - OPOTDIRS += ./potentials/Aluminum/ ./potentials/Lenosky/ ./potentials/SW/ \ - ./potentials/Tersoff/ ./potentials/EDIP/ ./potentials/Water_H/ \ - ./potentials/FeHe/ -else - POTENTIALS += "+Aluminum +Lenosky +SW +Tersoff +EDIP +H2O_H +FeHe" - FPOTDIRS += ./potentials/Aluminum/ ./potentials/Lenosky/ ./potentials/SW/ \ - ./potentials/Tersoff/ ./potentials/EDIP/ ./potentials/Water_H/ \ - ./potentials/FeHe/ - LIBS += ./potentials/Aluminum/libAL.a ./potentials/Lenosky/libLenosky.a \ - ./potentials/SW/libSW.a ./potentials/Tersoff/libTersoff.a \ - ./potentials/EDIP/libEDIP.a ./potentials/Water_H/libtip4p_h.a \ - ./potentials/FeHe/libFeHe.a - -endif - -#Optional potentials -ifdef PYAMFF_POT - CXXFLAGS += -DPYAMFF_POT - POTDIRS += ./potentials/PyAMFF - LIBS += ./potentials/PyAMFF/libPyAMFF.a ./potentials/PyAMFF/libAMFF.a - POTENTIALS += "+PyAMFF" -else - OPOTDIRS += ./potentials/PyAMFF - POTENTIALS += "-PyAMFF" -endif - -ifdef LAMMPS_POT - CXXFLAGS += -DLAMMPS_POT - POTDIRS += ./potentials/LAMMPS - LIBS += ./potentials/LAMMPS/liblammps.a - ifdef EONMPI - LIBS += ./potentials/LAMMPS/liblammps_mpi.a - else - LIBS += ./potentials/LAMMPS/liblammps_serial.a - endif - ifndef EONMPI - LIBS += ./potentials/LAMMPS/libmpi_stubs.a - endif - POTENTIALS += "+LAMMPS" - ifdef LAMMPS_MEAM - LIBS += ./potentials/LAMMPS/libmeam.a - POTENTIALS += "+LAMMPS_MEAM" - endif - ifdef LAMMPS_KIM - LIBS += /home/graeme/.local/kim-v2-mpi-4/lib64/libkim-api-v2.so - LDFLAGS += -lcurl - POTENTIALS += "+LAMMPS_KIM" - endif - -else - OPOTDIRS += ./potentials/LAMMPS - POTENTIALS += "-LAMMPS" -endif - -ifdef ASE_POT - # first -I for pybind11, second -I for Python.h - CXXFLAGS += -DASE_POT -I/path/to/python/rootdir/include -I/path/to/python/rootdir/include/python3.xx # EDIT - # for libpython3.xx.so - LDFLAGS += -L/path/to/python/rootdir/lib -lpython3.xx # EDIT - POTDIRS += ./potentials/ASE - LIBS += ./potentials/ASE/libASE.a - POTENTIALS += "+ASE" -else - OPOTDIRS += ./potentials/ASE - POTENTIALS += "-ASE" -endif - -ifdef NEW_POT - CXXFLAGS += -DNEW_POT - POTDIRS += ./potentials/New - LIBS += ./potentials/New/libnew.a - POTENTIALS += "+NEW_POT" -else - OPOTDIRS += ./potentials/New - POTENTIALS += "-NEW_POT" -endif - -ifndef WIN32 - POTDIRS += ./potentials/VASP - LIBS += ./potentials/VASP/libVASP.a - POTENTIALS += "+VASP" -else - OPOTDIRS += ./potentials/VASP - POTENTIALS += "-VASP" -endif - -ifdef EONMPI - POTDIRS += ./potentials/MPIPot - LIBS += ./potentials/MPIPot/libMPIPot.a - POTENTIALS += "+MPI_POT" -else - OPOTDIRS += ./potentials/MPIPot - POTENTIALS += "-MPI_POT" -endif - -#------------------------------------ -#MPI settings continued -ifdef EONMPI - #python_include_path=$(shell python -c "import sys,os; print(os.path.join(sys.prefix, 'include', 'python'+sys.version[:3]))") - python_include_path=$(shell python -c "from sysconfig import get_paths as gp; print(gp()[\"include\"])") - #python_lib=$(shell python -c "import sys,os; print('python'+sys.version[:3])") - python_lib=$(shell python3-config --libs) - #python_lib_path=$(shell python -c "import sys,os;print(os.path.join(sys.prefix, 'lib'))") - python_lib_path=$(shell python3-config --prefix) - ## uncomment for comilation on hopper, comment above definition - # python_lib_path=$(shell python -c "import sys,os;print os.path.join(sys.prefix, 'lib','python'+sys.version[:3], 'config')") - #ifneq ($(python_lib_path),/usr/lib) - # LDFLAGS += -L${python_lib_path} -l${python_lib} -lm - #else - # LDFLAGS += -l${python_lib} -lm - #endif - LDFLAGS += -L$(shell python3-config --prefix) $(shell python3-config --libs) - CXXFLAGS += -I${python_include_path} - #CXXFLAGS += ${python_include_path} -endif - -#------------------------------------ -#client source code -OBJECTS += ClientEON.o INIFile.o MinModeSaddleSearch.o Dimer.o EpiCenters.o \ - Hessian.o ConjugateGradients.o HelperFunctions.o Matter.o \ - Parameters.o Potential.o Quickmin.o ProcessSearchJob.o PointJob.o \ - MinimizationJob.o HessianJob.o ParallelReplicaJob.o \ - SafeHyperJob.o TADJob.o\ - ReplicaExchangeJob.o Dynamics.o BondBoost.o FiniteDifferenceJob.o \ - NudgedElasticBandJob.o TestJob.o BasinHoppingJob.o \ - SaddleSearchJob.o ImprovedDimer.o NudgedElasticBand.o Lanczos.o \ - Bundling.o Job.o CommandLine.o DynamicsJob.o Log.o \ - LBFGS.o LowestEigenmode.o Optimizer.o Prefactor.o \ - DynamicsSaddleSearch.o PrefactorJob.o FIRE.o \ - GlobalOptimizationJob.o GlobalOptimization.o StructureComparisonJob.o \ - MonteCarloJob.o MonteCarlo.o SteepestDescent.o BasinHoppingSaddleSearch.o \ - BiasedGradientSquaredDescent.o ExceptionsEON.o - - -#ifneq ($(or unitTests,check),) -#CXXFLAGS += -std=c++11 -TEMPOBJ := $(filter-out ClientEON.o,$(OBJECTS)) -DEPOBJECTS := $(addprefix ../,$(TEMPOBJ)) -DEPLIBS := $(addprefix ../,$(LIBS)) -export -#endif - -#------------------------------------ -#Build rules -all: $(POTDIRS) $(FPOTDIRS) eonclient - @echo - @echo "eOn Client Compilation Options" - @echo "DEBUG: $(DEBUG)" - @echo "POTENTIALS: $(POTENTIALS)" - -eonclient: $(OBJECTS) $(LIBS) - $(CXX) -o $(TARGET_NAME) $^ $(LDFLAGS) - -libeon: $(filter-out ClientEON.o,$(OBJECTS)) $(POTDIRS) $(FPOTDIRS) - $(AR) libeon.a $(filter-out ClientEON.o,$(OBJECTS)) potentials/*/*.o potentials/EMT/Asap/*.o - -ClientEON.o: version.h -CommandLine.o: version.h - -version.h: - ./version.sh > version.h - -$(LIBS): - $(MAKE) -C $@ - -$LIBS: $(POTDIRS) $(FPOTDIRS) - -$(POTDIRS): - $(MAKE) -C $@ CC="$(CC)" CXX="$(CXX)" LD="$(LD)" AR="$(AR)" RANLIB="$(RANLIB)" CXXFLAGS="$(CXXFLAGS)" - -$(FPOTDIRS): - $(MAKE) -C $@ CC="$(CC)" CXX="$(CXX)" LD="$(LD)" AR="$(FAR)" FC="$(FC)" FFLAGS="$(FFLAGS)" RANLIB="$(RANLIB)" CXXFLAGS="$(CXXFLAGS)" - -mkUnitTests: $(filter-out ClientEON.o,$(OBJECTS)) - cd unittests && $(MAKE) - -testsClean: - cd unittests && $(MAKE) clean - -testsClobber: - cd unittests && $(MAKE) clobber - -unitTests: mkUnitTests - cd unittests && sh run_unit_tests.sh - -check: unitTests - @echo "In the future, regression testing will automatically run now." - -clean: testsClean - rm -f $(OBJECTS) $(DEPENDS) - -clean-all: clean testsClobber - for pot in $(POTDIRS) $(FPOTDIRS) $(OPOTDIRS); do $(MAKE) -C $$pot clean ; done - -%.o: %.cpp - $(CXX) $(CXXFLAGS) $(DEPFLAGS) -c $< - -DEPENDS= $(wildcard *.d) --include $(DEPENDS) - -.PHONY : all $(POTDIRS) $(FPOTDIRS) clean clean-all version.h -# DO NOT DELETE diff --git a/client/SaddleSearchJob.cpp b/client/SaddleSearchJob.cpp index 42eac8fca..80b005d2d 100644 --- a/client/SaddleSearchJob.cpp +++ b/client/SaddleSearchJob.cpp @@ -9,13 +9,11 @@ ** Repo: ** https://github.com/TheochemUI/eOn */ + #include "SaddleSearchJob.h" -#ifdef WITH_ARTN #include "ARTnSaddleSearch.h" -#endif #include "EpiCenters.h" #include "HelperFunctions.h" -#include "Potential.h" #include #include @@ -23,6 +21,9 @@ #include #include +using eonc::SaddleStatus; +using eonc::to_int; + std::vector SaddleSearchJob::run() { std::string reactantFilename("pos.con"); std::string displacementFilename("displacement.con"); @@ -66,45 +67,45 @@ std::vector SaddleSearchJob::run() { params.saddle_search_options.method == "min_mode" && params.saddle_search_options.minmode_method == "artn"; -#ifdef WITH_ARTN if (useStandaloneARTn || useARTnAsMinMode) { - saddleSearch = - std::make_unique(saddle, pot, mode, params); - } else -#endif - { -#ifndef WITH_ARTN - if (useStandaloneARTn) { + // ARTnResource::require_loaded() inside ARTnSaddleSearch::run() + // converts a missing libartn.so into SaddleStatus::BadArtnError with + // an install-hint logged at error level. Pre-construct guard + // here lifts that into a runtime_error so config-time misuse + // surfaces a clear message before the search loop starts. + if (!eonc::get_artn_resource().is_loaded()) { + const char *which = useStandaloneARTn + ? "saddle_search.method=artn" + : "saddle_search.minmode_method=artn"; throw std::runtime_error( - "saddle_search.method=artn requires a build with ARTn support"); + std::string(which) + + " requires libartn at runtime " + "(set LD_LIBRARY_PATH so eonc::ARTnResource finds it)"); } - if (useARTnAsMinMode) { - throw std::runtime_error( - "saddle_search.minmode_method=artn requires a build with ARTn " - "support"); - } -#endif + saddleSearch = + std::make_unique(saddle, pot, mode, params); + } else { saddleSearch = std::make_unique( saddle, mode, initial->getPotentialEnergy(), params, pot); } - int status = doSaddleSearch(); + SaddleStatus status = doSaddleSearch(); printEndState(status); saveData(status); return returnFiles; } -int SaddleSearchJob::doSaddleSearch() { +SaddleStatus SaddleSearchJob::doSaddleSearch() { Matter matterTemp(pot, params); - long status; + SaddleStatus status = SaddleStatus::Good; int f1{0}; f1 = this->pot->forceCallCounter; try { status = saddleSearch->run(); } catch (int e) { if (e == 100) { - status = MinModeSaddleSearch::STATUS_POTENTIAL_FAILED; + status = SaddleStatus::PotentialFailed; } else { printf("unknown exception: %i\n", e); throw e; @@ -124,13 +125,13 @@ int SaddleSearchJob::doSaddleSearch() { return status; } -void SaddleSearchJob::saveData(int status) { +void SaddleSearchJob::saveData(SaddleStatus status) { std::string resultsFilename("results.dat"); returnFiles.push_back(resultsFilename); std::ofstream out(resultsFilename, std::ios::binary); if (out) { - out << std::format("{} termination_reason\n", status); + out << std::format("{} termination_reason\n", to_int(status)); out << std::format("{} termination_reason_text\n", saddleSearch->describeStatus(status)); out << "saddle_search job_type\n"; @@ -142,7 +143,7 @@ void SaddleSearchJob::saveData(int status) { this->pot->forceCallCounter.load()); out << std::format("{} force_calls_saddle\n", fCallsSaddle); out << std::format("{} iterations\n", saddleSearch->getIterationCount()); - if (status != MinModeSaddleSearch::STATUS_POTENTIAL_FAILED) { + if (status != SaddleStatus::PotentialFailed) { out << std::format("{:f} potential_energy_saddle\n", saddle->getPotentialEnergy()); out << std::format("{:f} final_eigenvalue\n", @@ -170,9 +171,9 @@ void SaddleSearchJob::saveData(int status) { saddle->matter2con(saddleFilename); } -void SaddleSearchJob::printEndState(int status) { +void SaddleSearchJob::printEndState(SaddleStatus status) { auto msg = saddleSearch->describeStatus(status); - if (status == MinModeSaddleSearch::STATUS_GOOD) { + if (status == SaddleStatus::Good) { QUILL_LOG_DEBUG(log, "[Saddle Search] {}", msg); } else { QUILL_LOG_WARNING(log, "[Saddle Search] {}", msg); diff --git a/client/SaddleSearchJob.h b/client/SaddleSearchJob.h index ac4d7b4fa..3f6d29e42 100644 --- a/client/SaddleSearchJob.h +++ b/client/SaddleSearchJob.h @@ -58,11 +58,11 @@ class SaddleSearchJob : public Job { private: //! Runs the correct saddle search; also checks if the run was successful - int doSaddleSearch(); + SaddleStatus doSaddleSearch(); //! Logs the run status and makes sure the run was successful - void printEndState(int status); + void printEndState(SaddleStatus status); //! Writes the results from the run to file - void saveData(int status); + void saveData(SaddleStatus status); //! Container for the results of the run std::vector returnFiles; diff --git a/client/SaddleSearchMethod.h b/client/SaddleSearchMethod.h index 0e2a982f8..885299037 100644 --- a/client/SaddleSearchMethod.h +++ b/client/SaddleSearchMethod.h @@ -13,6 +13,7 @@ #include "Parameters.h" #include "Potential.h" +#include "StatusTypes.h" #include @@ -26,14 +27,22 @@ class SaddleSearchMethod { public: SaddleSearchMethod(std::shared_ptr potPassed, const Parameters ¶msPassed) - : pot{potPassed}, - params{paramsPassed} {}; - virtual ~SaddleSearchMethod() {}; - virtual int run() = 0; + : pot{std::move(potPassed)}, + params{paramsPassed} {} + virtual ~SaddleSearchMethod() = default; + /// Run the saddle search. Returns the typed terminal status; the + /// underlying int value is preserved for results.dat / driver + /// round-trips via to_int(SaddleStatus). + virtual SaddleStatus run() = 0; virtual double getEigenvalue() = 0; virtual AtomMatrix getEigenvector() = 0; - virtual std::string_view describeStatus(int status) const = 0; - virtual int getStatus() const { return 0; } + /// Human-readable message for a status value. Default forwards to + /// the StatusTypes.h overload; subclasses override only if they + /// need a different vocabulary. + virtual std::string_view describeStatus(SaddleStatus status) const { + return statusMessage(status); + } + virtual SaddleStatus getStatus() const { return SaddleStatus::Good; } virtual int getIterationCount() const { return 0; } virtual int getForceCalls() const { return 0; } }; diff --git a/client/SafeHyperJob.cpp b/client/SafeHyperJob.cpp index 6d1db277f..77e87f87b 100644 --- a/client/SafeHyperJob.cpp +++ b/client/SafeHyperJob.cpp @@ -62,7 +62,7 @@ int SafeHyperJob::dynamics() { timeBuffer.resize(mdBufferLength); biasBuffer.resize(mdBufferLength); - Dynamics safeHyper(current.get(), params); + Dynamics safeHyper(current.get(), DynamicsConfig::fromParams(params)); BondBoost bondBoost(current.get(), params); if (params.hyperdynamics_options.bias_potential == diff --git a/client/ServeRpcServer.cpp b/client/ServeRpcServer.cpp index c0dd0986f..3b78752d7 100644 --- a/client/ServeRpcServer.cpp +++ b/client/ServeRpcServer.cpp @@ -175,8 +175,8 @@ class PooledCallbackPotImpl final : public Potential::Server { } private: - std::vector m_pool; - std::vector m_mutexes; + std::vector m_pool{}; + std::vector m_mutexes{}; std::atomic m_next; }; diff --git a/client/StatusTypes.h b/client/StatusTypes.h new file mode 100644 index 000000000..95aed28b4 --- /dev/null +++ b/client/StatusTypes.h @@ -0,0 +1,168 @@ +/* +** This file is part of eOn. +** +** SPDX-License-Identifier: BSD-3-Clause +** +** Copyright (c) 2010--present, eOn Development Team +** All rights reserved. +** +** Repo: +** https://github.com/TheochemUI/eOn +*/ +#pragma once + +/// Strongly-typed status codes used across optimizers and saddle-search +/// methods. Two enums: +/// +/// StepResult -- single-iteration outcome from +/// Optimizer::step() / run() / line-search. +/// SaddleStatus -- terminal status from any SaddleSearchMethod +/// subclass (MinMode, ARTn, BasinHopping, +/// DynamicsSaddleSearch, BGSD, ...). +/// +/// Both keep stable underlying int values so downstream consumers +/// (results.dat parsers, the akmc.py driver, the integration tests +/// in JobIntegrationTest.cpp) that read the raw int continue to +/// work without re-interpretation. Use to_int() on the enum-class +/// values when you need to write the bare integer (e.g. format +/// strings, JSON, saved fixture files); use magic_enum::enum_name() +/// for the symbolic name in logs. + +#include +#include + +namespace eonc { + +/// Single-iteration outcome from Optimizer::step() / Optimizer::run() +/// and any line-search loop layered on top. Pre-2.15 the same +/// information was carried as a bare int (-1 / 0 / 1) which made +/// every call site read like a flag check. +enum class StepResult : int { + Failed = -1, ///< Hard failure: NaN forces, internal error. + NotConverged = 0, ///< Step taken but the convergence test is unmet. + Converged = 1, ///< Convergence achieved -- caller should stop. +}; + +[[nodiscard]] constexpr int to_int(StepResult r) noexcept { + return static_cast(r); +} + +/// Saddle-search terminal status. Values mirror the historical +/// MinModeSaddleSearch::Status int constants byte-for-byte; the +/// only change is the type system. ARTn / BasinHopping / +/// DynamicsSaddleSearch / BGSD reuse the same set so callers get +/// one place to look up "what termination_reason did this job +/// emit". Numeric stability is enforced by explicit values. +/// +/// New status codes append at the end and bump the +/// statusMessage() table. +enum class SaddleStatus : int { + Good = 0, + Init = 1, + BadNoConvex = 2, + BadHighEnergy = 3, + BadMaxConcaveIterations = 4, + BadMaxIterations = 5, + BadNotConnected = 6, + BadPrefactor = 7, + BadHighBarrier = 8, + BadMinima = 9, + FailedPrefactor = 10, + PotentialFailed = 11, + NonnegativeAbort = 12, + NonlocalAbort = 13, + NegativeBarrier = 14, + BadMdTrajectoryTooShort = 15, + BadNoNegativeModeAtSaddle = 16, + BadNoBarrier = 17, + ZeromodeAbort = 18, + OptimizerError = 19, + DimerLostMode = 20, + DimerRestoredBest = 21, + /// pARTn's Fortran backend raised an error code outside the + /// MinModeSaddleSearch::Status range. ARTn's pre-refactor + /// constant was the magic 22. + BadArtnError = 22, +}; + +[[nodiscard]] constexpr int to_int(SaddleStatus s) noexcept { + return static_cast(s); +} + +/// Human-readable message for a SaddleStatus. Out-of-range integer +/// values returned by SaddleSearchMethod::getStatus() (e.g. from +/// future codes the binary doesn't know about) fall through to +/// "Unknown status". +[[nodiscard]] constexpr std::string_view statusMessage(SaddleStatus s) { + switch (s) { + case SaddleStatus::Good: + return "Success"; + case SaddleStatus::Init: + return "Initialized"; + case SaddleStatus::BadNoConvex: + return "Initial displacement unable to reach convex region"; + case SaddleStatus::BadHighEnergy: + return "Barrier too high"; + case SaddleStatus::BadMaxConcaveIterations: + return "Too many iterations in concave region"; + case SaddleStatus::BadMaxIterations: + return "Too many iterations"; + case SaddleStatus::BadNotConnected: + return "Saddle is not connected to initial state"; + case SaddleStatus::BadPrefactor: + return "Prefactors not within window"; + case SaddleStatus::BadHighBarrier: + return "Energy barrier not within window"; + case SaddleStatus::BadMinima: + return "Minimizations from saddle did not converge"; + case SaddleStatus::FailedPrefactor: + return "Hessian calculation failed"; + case SaddleStatus::PotentialFailed: + return "Potential evaluation failed"; + case SaddleStatus::NonnegativeAbort: + return "Nonnegative initial mode, aborting"; + case SaddleStatus::NonlocalAbort: + return "Nonlocal abort"; + case SaddleStatus::NegativeBarrier: + return "Negative barrier detected"; + case SaddleStatus::BadMdTrajectoryTooShort: + return "No reaction found during MD trajectory"; + case SaddleStatus::BadNoNegativeModeAtSaddle: + return "Converged to stationary point with zero negative modes"; + case SaddleStatus::BadNoBarrier: + return "No forward barrier found along minimized band"; + case SaddleStatus::ZeromodeAbort: + return "Zero mode abort"; + case SaddleStatus::OptimizerError: + return "Optimizer error"; + case SaddleStatus::DimerLostMode: + return "Dimer lost mode"; + case SaddleStatus::DimerRestoredBest: + return "Dimer restored best"; + case SaddleStatus::BadArtnError: + return "ARTn library error"; + } + return "Unknown status"; +} + +/// Backwards-compat overload for call sites that hold a raw int +/// (e.g. results.dat round-trip, integration tests that compare to +/// stored fixtures). Casts to SaddleStatus and dispatches. +[[nodiscard]] constexpr std::string_view statusMessage(int status) { + return statusMessage(static_cast(status)); +} + +/// Stream insertion overloads. Required for Catch2 v3 assertion-failure +/// formatting (`m_oss << value` in the StringMaker chain) and useful +/// anywhere Quill / iostream wants to surface a typed status. SaddleStatus +/// prints the symbolic message + numeric value; StepResult prints the +/// signed underlying so `-1 / 0 / 1` stays diff-friendly in logs. +inline std::ostream &operator<<(std::ostream &os, SaddleStatus s) { + return os << statusMessage(s) << " (" << to_int(s) << ")"; +} + +inline std::ostream &operator<<(std::ostream &os, StepResult r) { + return os << to_int(r); +} + +} // namespace eonc diff --git a/client/SteepestDescent.cpp b/client/SteepestDescent.cpp index 7cc356101..d847cc2b4 100644 --- a/client/SteepestDescent.cpp +++ b/client/SteepestDescent.cpp @@ -15,7 +15,9 @@ #include "SteepestDescent.h" #include "SafeMath.h" -int SteepestDescent::step(double a_maxMove) { +using eonc::StepResult; + +StepResult SteepestDescent::step(double a_maxMove) { Eigen::VectorXd r = m_objf->getPositions(); Eigen::VectorXd f = -m_objf->getGradient(); @@ -41,12 +43,14 @@ int SteepestDescent::step(double a_maxMove) { iteration++; - return m_objf->isConverged() ? 1 : 0; + return m_objf->isConverged() ? StepResult::Converged + : StepResult::NotConverged; } -int SteepestDescent::run(size_t a_maxIteration, double a_maxMove) { +StepResult SteepestDescent::run(size_t a_maxIteration, double a_maxMove) { while (!m_objf->isConverged() && iteration < a_maxIteration) { step(a_maxMove); } - return m_objf->isConverged() ? 1 : 0; + return m_objf->isConverged() ? StepResult::Converged + : StepResult::NotConverged; } diff --git a/client/SteepestDescent.h b/client/SteepestDescent.h index b58fdeb6d..9355770b1 100644 --- a/client/SteepestDescent.h +++ b/client/SteepestDescent.h @@ -25,12 +25,13 @@ class SteepestDescent final : public Optimizer { public: SteepestDescent(std::shared_ptr a_objf, const Parameters &a_params) - : Optimizer(a_objf, OptType::SD, a_params), + : Optimizer(std::move(a_objf), OptType::SD, + OptimizerConfig::fromParams(a_params)), iteration{0} {} ~SteepestDescent() = default; - int step(double a_maxMove) override; - int run(size_t a_maxIterations, double a_maxMove) override; + StepResult step(double a_maxMove) override; + StepResult run(size_t a_maxIterations, double a_maxMove) override; private: eonc::log::FileScoped m_log{"sd", "_sd.log"}; diff --git a/client/SurrogatePotential.cpp b/client/SurrogatePotential.cpp index 42e4fcefd..b7c0ab899 100644 --- a/client/SurrogatePotential.cpp +++ b/client/SurrogatePotential.cpp @@ -12,8 +12,8 @@ #include "SurrogatePotential.h" std::tuple -SurrogatePotential::get_ef_var(const AtomMatrix pos, const VectorXi atmnrs, - const Matrix3d box) { +SurrogatePotential::get_ef_var(const AtomMatrix &pos, const VectorXi &atmnrs, + const Matrix3d &box) { double energy{std::numeric_limits::infinity()}; long nAtoms = static_cast(pos.rows()); AtomMatrix forces{MatrixXd::Zero(nAtoms, 3)}; diff --git a/client/SurrogatePotential.h b/client/SurrogatePotential.h index 6efe2e8ce..01070f1d5 100644 --- a/client/SurrogatePotential.h +++ b/client/SurrogatePotential.h @@ -23,7 +23,8 @@ class SurrogatePotential : public Potential { virtual ~SurrogatePotential() = default; [[nodiscard]] bool isSurrogate() const noexcept override { return true; } std::tuple // energy, forces, energy variance - get_ef_var(const AtomMatrix pos, const VectorXi atmnrs, const Matrix3d box); + get_ef_var(const AtomMatrix &pos, const VectorXi &atmnrs, + const Matrix3d &box); virtual void train_optimize(const MatrixXd &a_features, const MatrixXd &a_targets) = 0; }; diff --git a/client/TADJob.cpp b/client/TADJob.cpp index 76279b5b5..adbef56c5 100644 --- a/client/TADJob.cpp +++ b/client/TADJob.cpp @@ -79,7 +79,7 @@ int TADJob::dynamics() { } timeBuffer.resize(mdBufferLength); - Dynamics TAD(current.get(), params); + Dynamics TAD(current.get(), DynamicsConfig::fromParams(params)); TAD.setThermalVelocity(); { diff --git a/client/TestJob.cpp b/client/TestJob.cpp index d3e17facc..aa863e1d9 100644 --- a/client/TestJob.cpp +++ b/client/TestJob.cpp @@ -145,9 +145,8 @@ void TestJob::checkFullSearch() { printf("SP done\n"); // unique_ptrs clean up automatically - return; } -void TestJob::checkPotentials(void) { +void TestJob::checkPotentials() { double energyDiff; double forceDiff; diff --git a/client/fpe_handler.cpp b/client/fpe_handler.cpp index d4d385bc9..c3b8dffdd 100644 --- a/client/fpe_handler.cpp +++ b/client/fpe_handler.cpp @@ -130,8 +130,11 @@ void enableFPE() { #endif #ifndef _WIN32 - // Register POSIX signal handler - struct sigaction act; + // Register POSIX signal handler. Value-initialise the struct so + // every field starts at zero -- POSIX explicitly leaves sa_flags + // and the sa_mask scratch area undefined otherwise, and clang-tidy + // (cppcoreguidelines-pro-type-member-init) flags the bare decl. + struct sigaction act = {}; act.sa_sigaction = fpe_signal_handler; sigemptyset(&act.sa_mask); act.sa_flags = SA_SIGINFO; diff --git a/client/libs/ARTn/ARTnResource.cpp b/client/libs/ARTn/ARTnResource.cpp index 2f4f1a9cf..ec3d986a3 100644 --- a/client/libs/ARTn/ARTnResource.cpp +++ b/client/libs/ARTn/ARTnResource.cpp @@ -66,10 +66,11 @@ ARTnResource::ARTnResource() { } ARTnResource::~ARTnResource() { - if (m_handle) { - dynlib::close(m_handle); - m_handle = {}; - } + // Intentionally do NOT dlclose at static destruction. libartn pulls in + // gfortran runtime + LAPACK; its destructor chain runs Fortran + // FINAL/STOP statements that touch stdio that is already being torn + // down by the time this Meyer-singleton dtor runs. The handle is + // process-lifetime; OS unmaps the lib at exit. } void ARTnResource::require_loaded() const { diff --git a/client/libs/IRA/IRAResource.cpp b/client/libs/IRA/IRAResource.cpp index 82262ba82..cfba1e511 100644 --- a/client/libs/IRA/IRAResource.cpp +++ b/client/libs/IRA/IRAResource.cpp @@ -55,10 +55,10 @@ IRAResource::IRAResource() { } IRAResource::~IRAResource() { - if (m_handle) { - dynlib::close(m_handle); - m_handle = {}; - } + // Intentionally do NOT dlclose at static destruction. libira is + // Fortran with its own runtime fini chain; mirroring the XtbLoader / + // LammpsLoader / ARTnResource policy of letting the OS reclaim the + // mapping at process exit. } void IRAResource::require_loaded() const { diff --git a/client/meson.build b/client/meson.build index 401ec58e4..48db606c6 100644 --- a/client/meson.build +++ b/client/meson.build @@ -9,6 +9,38 @@ add_languages('c', required: true) cc = meson.get_compiler('c') cppc = meson.get_compiler('cpp') +# -fvisibility-inlines-hidden: stops the compiler from emitting +# inline / template-instantiation symbols with default visibility, +# which otherwise duplicate across every TU that includes the +# header. Safe to apply universally -- inline functions don't need +# to be visible across DSO boundaries because every consumer has +# the body via the header. +# +# We deliberately do NOT pass -fvisibility=hidden globally: that +# would require annotating every cross-DSO entry point in libeonclib +# (Matter::compare, Potential::*, Job::*, ...) with +# __attribute__((visibility("default"))), which is a separate +# multi-day refactor. cppc.get_supported_arguments() filters out +# flags MSVC doesn't accept, so the same line is a no-op on Windows. +add_project_arguments( + cppc.get_supported_arguments(['-fvisibility-inlines-hidden']), + language: 'cpp', +) + +# DynLib.h (used by LammpsLoader / XtbLoader / ARTnResource / +# IRAResource and any future runtime-loaded shim) calls dlopen + +# dlsym + dlclose on POSIX. On Linux these symbols live in libdl; +# on macOS and BSDs they're in libc and find_library('dl') returns +# not-found -- harmless. Hoist dl_dep to file scope so every +# eonclib / eoncbase / per-potential library() call already links +# -ldl when the toolchain needs it. Pre-fix #338, only LAMMPS got +# this for free and XTB / ARTn / IRA hit +# `undefined reference to dlopen` at link time in CI. +dl_dep = cppc.find_library('dl', required: false) +if dl_dep.found() + _deps += [dl_dep] +endif + use_fortran = false if get_option('with_fortran') or get_option('with_cuh2') use_fortran = true @@ -17,6 +49,58 @@ if get_option('with_fortran') or get_option('with_cuh2') _fargs += fc.get_supported_arguments(['-fno-implicit-none']) endif +# Centralised pybind11 lookup -- pre-2.15 every Python-embedding +# potential block (with_catlearn / with_ase / with_ase_orca / +# with_ase_nwchem) re-ran dependency('pybind11'), which isn't +# expensive but bloats configure-time logs and duplicates the +# include-path resolution. Resolve once, append once; downstream +# blocks reuse the entry already in _deps. +need_pybind11 = ( + get_option('with_catlearn') + or get_option('with_ase') + or get_option('with_ase_orca') + or get_option('with_ase_nwchem') +) +if need_pybind11 + _deps += [dependency('pybind11', required: true)] +endif + +# BLAS / LAPACK acceleration for Eigen. +# +# Three operating modes, mutually exclusive: +# +# with_flexiblas (default off, recommended) +# Link Eigen against FlexiBLAS -- the FlexiBLAS-of-MPI analogue +# for BLAS. One eonclient binary forwards every BLAS / LAPACK +# call to any installed backend (OpenBLAS, MKL, BLIS, ATLAS, +# ARMPL, ...) at run time, picked via the FLEXIBLAS env var: +# FLEXIBLAS=OPENBLAS ./eonclient # OpenBLAS at runtime +# FLEXIBLAS=INTELMKL ./eonclient # Intel MKL at runtime +# subprojects/flexiblas.wrap pins upstream and CMake-builds it +# when the host doesn't ship a FlexiBLAS install, so there's +# never a "no BLAS" failure mode. +# +# use_mkl (default off, legacy direct link) +# Hard-link to Intel MKL via mkl-dynamic-ilp64-iomp. ABI-locks +# the binary to MKL. Kept for users who explicitly want MKL +# without the FlexiBLAS indirection. Mutually exclusive with +# with_flexiblas. +# +# neither (default) +# Pure-header Eigen, no external BLAS link. Eigen falls back to +# its own routines. +if get_option('with_flexiblas') and get_option('use_mkl') + error('with_flexiblas and use_mkl are mutually exclusive; FlexiBLAS ' + + 'can route to MKL at run time via FLEXIBLAS=INTELMKL') +endif + +if get_option('with_flexiblas') + flexiblas_dep = dependency('flexiblas', required: true) + add_project_arguments('-DEIGEN_USE_BLAS', language: 'cpp') + add_project_arguments('-DEIGEN_USE_LAPACKE', language: 'cpp') + _deps += [flexiblas_dep] +endif + if get_option('use_mkl') mkldep = dependency('mkl-dynamic-ilp64-iomp', required: true) add_project_arguments('-DEIGEN_USE_MKL_ALL', language: 'cpp') @@ -137,25 +221,55 @@ architecture = host_machine.cpu_family() message(host_system) message(architecture) -# Generate features string for version.h.in +# Generate features string for version.h.in. +# +# Each entry is (compile_flag, display_name, mode) where mode is one of: +# 'compile' -- check '-Dflag' in _args (compile-time CPP define). +# Reports 'enabled' (compile-linked) or 'disabled'. +# 'runtime' -- always compiled into the client; loaded via dlopen +# at run time (LAMMPS, ARTn, IRA, XTB). Always reports +# 'enabled (dlopen at runtime)' since the shim is +# present, irrespective of whether the dynamic library +# is found at run time. Call sites use is_loaded() / +# require_loaded() to gate the runtime path. +# 'subprocess' -- POSIX-only subprocess shim (VASP). Always compiled +# on POSIX, omitted on Windows. features_list = [ - ['WITH_GPRD', 'GPRD'], - ['WITH_GP_SURROGATE', 'GP Surrogate'], - ['WITH_CATLEARN', 'CatLearn'], - ['WITH_VASP', 'VASP'], - ['WITH_WATER', 'Water'], - ['WITH_AMS', 'AMS'], - ['WITH_XTB', 'XTB'], - ['LAMMPS_POT', 'LAMMPS'], - ['WITH_ASE_POT', 'ASE Potentials'], - ['EONMPI', 'MPI'], - ['WITH_FORTRAN', 'Fortran'], - ['CUH2_POT', 'CuH2'], - ['WITH_ASE_ORCA', 'ASE-ORCA'], - ['WITH_ASE_NWCHEM', 'ASE-NWChem'], - ['WITH_METATOMIC', 'Metatomic'], - ['WITH_SERVE_MODE', 'Serve Mode'], - ['EMBED_PYTHON', 'Embedded Python'], + ['WITH_GPRD', 'GPRD', 'compile'], + ['WITH_GP_SURROGATE', 'GP Surrogate', 'compile'], + ['WITH_CATLEARN', 'CatLearn', 'compile'], + # VASP is unconditionally compiled on POSIX (omitted on Windows). + # The class spawns ./runvasp.sh and reads FU/POSCAR files; nothing + # is linked or dlopen'd. The banner reports the shim's presence, + # not whether VASP itself is on PATH. + ['VASP_POT', 'VASP', 'subprocess'], + ['WITH_WATER', 'Water', 'compile'], + ['WITH_AMS', 'AMS', 'compile'], + # XTB: always compiled, dlopen at runtime via XtbLoader. + ['XTB_POT', 'XTB', 'runtime'], + # LAMMPS is unconditionally compiled (see subdir('potentials/LAMMPS') + # below). The dlopen lookup of liblammps.so happens at run time inside + # LammpsLoader::instance(); the banner just tells the user the shim is + # present, not whether liblammps was located. + ['LAMMPS_POT', 'LAMMPS', 'runtime'], + ['WITH_ASE_POT', 'ASE Potentials', 'compile'], + ['EONMPI', 'MPI', 'compile'], + ['WITH_FORTRAN', 'Fortran', 'compile'], + ['CUH2_POT', 'CuH2', 'compile'], + ['WITH_ASE_ORCA', 'ASE-ORCA', 'compile'], + ['WITH_ASE_NWCHEM', 'ASE-NWChem', 'compile'], + # Metatomic is compile-linked (subdir('potentials/Metatomic') links + # the metatomic_pot library directly via library() with link_with). + # No dlopen; static check on -DWITH_METATOMIC is correct. + ['WITH_METATOMIC', 'Metatomic', 'compile'], + ['WITH_SERVE_MODE', 'Serve Mode', 'compile'], + ['EMBED_PYTHON', 'Embedded Python','compile'], + # ARTn / IRA: same pattern as LAMMPS. The *Resource shim is + # unconditionally compiled; libartn.so / libira.so are dlopen'd + # inside *Resource::instance(); is_loaded() / require_loaded() + # gate the call sites at run time. + ['ARTN_POT', 'ARTn', 'runtime'], + ['IRA_POT', 'IRA', 'runtime'], ] # Join _args into a single string for checking @@ -165,7 +279,19 @@ features_string = '' foreach feature : features_list feature_flag = feature[0] feature_name = feature[1] - if '-D' + feature_flag in _args_str + feature_mode = feature.length() > 2 ? feature[2] : 'compile' + if feature_mode == 'runtime' + features_string += ' ' + feature_name + ': enabled (dlopen at runtime)\n' + elif feature_mode == 'subprocess' + # POSIX-only subprocess shims (VASP). Always compiled where the + # platform supports it; the user supplies the external binary + # at run time on PATH. + if host_system == 'windows' + features_string += ' ' + feature_name + ': disabled (POSIX only)\n' + else + features_string += ' ' + feature_name + ': enabled (subprocess)\n' + endif + elif '-D' + feature_flag in _args_str features_string += ' ' + feature_name + ': enabled\n' else features_string += ' ' + feature_name + ': disabled\n' @@ -218,11 +344,15 @@ _args += ['-DQUILL_DISABLE_NON_PREFIXED_MACROS'] inireader_dep = dependency('INIReader', required: false) if not inireader_dep.found() + # warning_level=0 on the subproject silences `unused parameter + # 'user'` in inih-r62/tests/unittest_alloc.c -- vendored upstream + # test code, not ours to fix; inheriting our -Wall -Wextra would + # otherwise leak the warning into our build log. inireader_dep = dependency( 'inireader', fallback: ['inih', 'INIReader_dep'], required: true, - default_options: ['default_library=static'], + default_options: ['default_library=static', 'warning_level=0'], ) endif _deps += inireader_dep @@ -305,71 +435,114 @@ if not is_windows potentials += [socket_nwchem] endif -eonclib_sources = [ - 'Parameters.cpp', - 'ParametersINI.cpp', - 'ParametersJSON.cpp', - 'ConFileIO.cpp', - 'GeometryAnalysis.cpp', - 'Optimizer.cpp', - 'IDPPObjectiveFunction.cpp', - 'PrefactorJob.cpp', - 'LBFGS.cpp', - 'ReplicaExchangeJob.cpp', - 'BondBoost.cpp', - 'Job.cpp', - 'GlobalOptimization.cpp', - 'LowestEigenmode.cpp', - 'MinModeSaddleSearch.cpp', - 'StructureComparisonJob.cpp', - 'SteepestDescent.cpp', - 'ImprovedDimer.cpp', - 'PointJob.cpp', - 'Prefactor.cpp', - 'ConjugateGradients.cpp', - 'Matter.cpp', - 'FiniteDifferenceJob.cpp', - 'Lanczos.cpp', - 'HessianJob.cpp', - 'ReplicaDynamicsJob.cpp', - 'TADJob.cpp', - 'ProcessSearchJob.cpp', - 'Bundling.cpp', - 'NEBInitialPaths.cpp', - 'NEBForceProjection.cpp', - 'NEBObjectiveFunction.cpp', - 'NEBProjection.cpp', - 'NEBOcinebController.cpp', - 'NEBSplineExtrema.cpp', - 'NEBSpringForce.cpp', - 'NEBTangent.cpp', - 'NudgedElasticBand.cpp', - 'MonteCarloJob.cpp', - 'DynamicsJob.cpp', - 'MonteCarlo.cpp', - 'Hessian.cpp', - 'NudgedElasticBandJob.cpp', - 'DynamicsSaddleSearch.cpp', - 'HelperFunctions.cpp', - 'RandomNumbers.cpp', - 'StringHelpers.cc', - 'MatrixHelpers.hpp', # Template - 'Dimer.cpp', - 'Dynamics.cpp', - 'GlobalOptimizationJob.cpp', - 'BiasedGradientSquaredDescent.cpp', - 'SafeHyperJob.cpp', - 'MinimizationJob.cpp', - 'Quickmin.cpp', - 'ParallelReplicaJob.cpp', - 'Potential.cpp', - 'SurrogatePotential.cpp', # Part of the interface - 'BasinHoppingJob.cpp', - 'FIRE.cpp', - 'EpiCenters.cpp', - 'SaddleSearchJob.cpp', - 'BasinHoppingSaddleSearch.cpp', -] +# eonclib source set (issue #177). Resolves the historical thicket of +# conditional `eonclib_sources +=` accretions across the file by +# moving every source-list decision into one sourceset driven by a +# configuration_data of feature flags. Side-effecting feature blocks +# (subproject(), find_library(), -DWITH_X args, _deps additions) stay +# where they are; only the source-membership question moves here. +eonclib_ss = import('sourceset').source_set() + +eonclib_ss.add( + files( + 'BasinHoppingJob.cpp', + 'BasinHoppingSaddleSearch.cpp', + 'BiasedGradientSquaredDescent.cpp', + 'BondBoost.cpp', + 'Bundling.cpp', + 'ConFileIO.cpp', + 'ConjugateGradients.cpp', + 'Dimer.cpp', + 'Dynamics.cpp', + 'DynamicsJob.cpp', + 'DynamicsSaddleSearch.cpp', + 'EpiCenters.cpp', + 'FIRE.cpp', + 'FiniteDifferenceJob.cpp', + 'GeometryAnalysis.cpp', + 'GlobalOptimization.cpp', + 'GlobalOptimizationJob.cpp', + 'HelperFunctions.cpp', + 'Hessian.cpp', + 'HessianJob.cpp', + 'IDPPObjectiveFunction.cpp', + 'ImprovedDimer.cpp', + 'Job.cpp', + 'LBFGS.cpp', + 'Lanczos.cpp', + 'LowestEigenmode.cpp', + 'MatrixHelpers.hpp', + 'Matter.cpp', + 'MinModeSaddleSearch.cpp', + 'MinimizationJob.cpp', + 'MonteCarlo.cpp', + 'MonteCarloJob.cpp', + 'NEBForceProjection.cpp', + 'NEBInitialPaths.cpp', + 'NEBObjectiveFunction.cpp', + 'NEBOcinebController.cpp', + 'NEBProjection.cpp', + 'NEBSplineExtrema.cpp', + 'NEBSpringForce.cpp', + 'NEBTangent.cpp', + 'NudgedElasticBand.cpp', + 'NudgedElasticBandJob.cpp', + 'Optimizer.cpp', + 'ParallelReplicaJob.cpp', + 'Parameters.cpp', + 'ParametersINI.cpp', + 'ParametersJSON.cpp', + 'PointJob.cpp', + 'Potential.cpp', + 'Prefactor.cpp', + 'PrefactorJob.cpp', + 'ProcessSearchJob.cpp', + 'Quickmin.cpp', + 'RandomNumbers.cpp', + 'ReplicaDynamicsJob.cpp', + 'ReplicaExchangeJob.cpp', + 'SaddleSearchJob.cpp', + 'SafeHyperJob.cpp', + 'SteepestDescent.cpp', + 'StringHelpers.cc', + 'StructureComparisonJob.cpp', + 'SurrogatePotential.cpp', + 'TADJob.cpp', + ), +) + +# ARTn / IRA: shim + driver always compiled, libs dlopen'd at run +# time. ARTnResource.h / IRAResource.h redeclare the C entry points +# inline, so neither needs an external header at compile time. +eonclib_ss.add( + files( + 'ARTnSaddleSearch.cpp', + 'libs/ARTn/ARTnResource.cpp', + 'IRACompare.cpp', + 'libs/IRA/IRAResource.cpp', + ), +) + +# Conditional sources, gated by configuration_data flags built up +# below. Every -DWITH_ addition + subproject + dependency lives in +# the matching feature block; sourceset only owns source membership. +eonclib_ss.add( + when: 'WITH_GPRD', + if_true: files('AtomicGPDimer.cpp', 'GPRHelpers.cpp'), +) +eonclib_ss.add( + when: 'WITH_GP_SURROGATE', + if_true: files('GPSurrogateJob.cpp'), +) +eonclib_ss.add( + when: 'WITH_SERVE_MODE', + if_true: files('ServeMode.cpp', 'ServeRpcServer.cpp'), +) + +eonclib_cfg = configuration_data() +eonclib_cfg.set('WITH_GPRD', get_option('with_gprd')) +eonclib_cfg.set('WITH_GP_SURROGATE', get_option('with_gp_surrogate')) +eonclib_cfg.set('WITH_SERVE_MODE', get_option('with_serve')) eonclient_sources = ['ClientEON.cpp', 'CommandLine.cpp'] @@ -390,30 +563,29 @@ if get_option('with_gprd') # subdir('potentials/GPRPotential') # potentials += [ gprpot ] _args += ['-DWITH_GPRD'] - eonclib_sources += ['AtomicGPDimer.cpp', 'GPRHelpers.cpp'] + # Source membership for AtomicGPDimer.cpp + GPRHelpers.cpp is + # owned by the eonclib sourceset above (when: 'WITH_GPRD'). _deps += [libgprd, gprd_deps] endif if get_option('with_gp_surrogate') _args += ['-DWITH_GP_SURROGATE'] - eonclib_sources += ['GPSurrogateJob.cpp'] + # Source membership owned by sourceset (when: 'WITH_GP_SURROGATE'). if get_option('with_catlearn') - # TODO: Cleanup, used for ase_orca too - # Embedding the interpreter - pyb11f_deps = [dependency('pybind11')] - _deps += [pyb11f_deps] + # pybind11 is provided by the central need_pybind11 block above. subdir('potentials/CatLearnPot') potentials += [catlearnpot] _args += ['-DWITH_CATLEARN'] endif endif -if get_option('with_vasp') - if host_system != 'windows' - subdir('potentials/VASP') - potentials += [vasp] - _args += ['-DWITH_VASP'] - endif +# VASP: always compiled on POSIX (Windows omits via #ifndef _WIN32 in +# Potential.cpp). The shim spawns `runvasp.sh` at use time; no library +# dependency at build time. The user gets VASP support iff the script is +# on PATH and PotType::VASP is requested. +if host_system != 'windows' + subdir('potentials/VASP') + potentials += [vasp] endif if get_option('with_water') @@ -450,17 +622,10 @@ if get_option('with_ams') _args += ['-DWITH_AMS'] endif -if get_option('with_xtb') - # Try finding system 'xtb' via pkg-config or cmake. - # Users who need to build xtb from source can place it in subprojects/ - # and Meson's implicit fallback will find it. - xtb_dep = dependency('xtb', required: true) - - _deps += [xtb_dep] - subdir('potentials/XTBPot') - potentials += xtb_eon - _args += ['-DWITH_XTB'] -endif +# XTB: always compiled, dlopens libxtb at runtime via XtbLoader. +# Mirrors the LAMMPS / ARTn / IRA pattern; no link-time xtb dependency. +subdir('potentials/XTBPot') +potentials += xtb_eon # LAMMPS: always compiled, loaded at runtime via dlopen subdir('potentials/LAMMPS') @@ -473,8 +638,7 @@ if py_embed # subdir('potentials/QSC') # potentials += [ qsc ] if get_option('with_ase') - pyb11f_deps = [dependency('pybind11')] - _deps += [pyb11f_deps] + # pybind11 supplied by need_pybind11 block. subdir('potentials/ASE') potentials += [ase] _args += ['-DWITH_ASE_POT'] @@ -482,11 +646,26 @@ if py_embed endif if get_option('with_mpi') + # MPI goes through MPItrampoline -- the FlexiBLAS-of-MPI. We link + # against its stable wrapper ABI; one eonclient binary then works + # against any spec-compliant libmpi at run time, picked via the + # MPITRAMPOLINE_LIB env var (see + # docs/source/user_guide/mpi_potential.md). + # + # subprojects/mpitrampoline.wrap pins upstream v5.5.1 and + # declares MPItrampoline as a provided dependency, so meson + # auto-builds the wrap if no system install is found. There is + # never a "no MPI library" failure mode here -- either the host + # already has MPItrampoline, or meson fetches and CMake-builds + # it from the wrap. We deliberately do NOT fall back to a + # direct dependency('mpi') link, since that would silently + # ABI-lock the binary to one MPI flavour and defeat the whole + # point. + mpi_dep = dependency('MPItrampoline', required: true) + _deps += [mpi_dep] subdir('potentials/MPIPot') potentials += [mpipot] _args += ['-DEONMPI'] - mpi_dep = dependency('mpi') - _deps += [mpi_dep] endif if get_option('with_fortran') @@ -511,25 +690,17 @@ if get_option('with_cuh2') endif if get_option('with_ase_orca') - # TODO: Cleanup, used for Catlearn too - # Embedding the interpreter - pyb11f_deps = [python_dep, dependency('pybind11')] - _deps += [pyb11f_deps] + # python_dep + pybind11_dep both supplied by the central + # py_embed and need_pybind11 blocks above. subdir('potentials/ASE_ORCA') potentials += [aseorca] _args += ['-DWITH_ASE_ORCA'] endif if get_option('with_ase_nwchem') - # TODO: Cleanup, used for Catlearn too - # Embedding the interpreter - python_dep = py.dependency(embed: true, required: true) - pyb11f_deps = [ - python_dep, - dependency('pybind11'), - declare_dependency(link_args: '-lstdc++'), - ] - _deps += [pyb11f_deps] + # python_dep + pybind11_dep supplied centrally; only the + # libstdc++ link arg is unique to this potential. + _deps += [declare_dependency(link_args: '-lstdc++')] subdir('potentials/ASE_NWCHEM') potentials += [asenwchem] _args += ['-DWITH_ASE_NWCHEM'] @@ -553,16 +724,22 @@ if get_option('with_metatomic') ] - lib_torch_list = ['c10', 'torch', 'torch_cpu'] - if not is_windows - lib_torch_list += 'torch_global_deps' - endif + lib_torch_list = ['c10', 'torch', 'torch_cpu', 'torch_global_deps'] # On Windows, torch DLLs live in /bin not /lib LIB_TORCH_BIN_PATH = LIB_TORCH_PATH / 'bin' lib_torch_dirs = is_windows ? [LIB_TORCH_LIB_PATH, LIB_TORCH_BIN_PATH] : [LIB_TORCH_LIB_PATH] + # Each lookup is required: false because some installs ship a + # different set than the canonical Linux layout: Windows torch + # builds skip the torch_global_deps .lib (only the .dll exists); + # libtorch *cpu* conda packages strip torch_cuda; etc. The CMake + # build's find_package(Torch) handles this gracefully and so + # should we (closes #304). tdeps = [] foreach lib_name : lib_torch_list - tdeps += cppc.find_library(lib_name, dirs: lib_torch_dirs) + _lib = cppc.find_library(lib_name, dirs: lib_torch_dirs, required: false) + if _lib.found() + tdeps += _lib + endif endforeach torch_inc_args = [] @@ -705,114 +882,10 @@ if get_option('with_parallel_neb') _args += ['-DEON_PARALLEL_NEB'] endif -if get_option('with_artn') - # ARTn (pARTn): Fortran library for saddle point search. - # Try system/pkg-config first, then manual artn_libdir, then cmake-build - # the subproject. Meson's Fortran scanner breaks with -cpp + submodules, - # so we build artn-plugin via cmake at configure time as a workaround. - # We redeclare pARTn's C signatures inline in ARTnResource.h, so we - # never need an -I for artn.h; hence no artn_includedir option. - artn_dep = dependency('artn', required: false) - if not artn_dep.found() - artn_libdir = get_option('artn_libdir') - artn_lib = disabler() - if artn_libdir == '' - # Auto-build artn-plugin from subprojects/ via cmake - artn_src = meson.project_source_root() / 'subprojects' / 'artn-plugin' - artn_bld = meson.project_build_root() / 'subprojects' / '_artn_cmake_build' - message('ARTn: building subproject via cmake (meson Fortran scanner workaround)') - run_command('cmake', '-S', artn_src, '-B', artn_bld, - '-DCMAKE_BUILD_TYPE=Release', - '-DWITH_LAMMPS=OFF', '-DWITH_QE=OFF', '-DWITH_SIESTA=OFF', - check: true) - run_command('cmake', '--build', artn_bld, '--parallel', '4', check: true) - # LIBRARY_OUTPUT_DIRECTORY in artn-plugin's CMakeLists.txt puts - # libartn.so at ${CMAKE_BINARY_DIR}; search a couple of plausible - # locations symmetrically with the IRA block below in case a - # future cmake refactor moves it. - artn_candidates = [artn_bld, artn_bld / 'src', artn_bld / 'lib'] - foreach candidate : artn_candidates - artn_lib = meson.get_compiler('cpp').find_library( - 'artn', - dirs: [candidate], - required: false, - ) - if artn_lib.found() - artn_libdir = candidate - break - endif - endforeach - if not artn_lib.found() - error('ARTn enabled but libartn was not found in the built subproject directory') - endif - else - artn_lib = meson.get_compiler('cpp').find_library('artn', - dirs: [artn_libdir], required: true) - endif - _runtime_rpath += [artn_libdir] - artn_dep = declare_dependency(dependencies: artn_lib) - endif - _deps += [artn_dep] - _args += ['-DWITH_ARTN'] - eonclib_sources += ['ARTnSaddleSearch.cpp'] - eonclib_sources += ['libs/ARTn/ARTnResource.cpp'] -endif - -if get_option('with_ira') - # IRA (Iterative Rotations and Assignments): Fortran library for structure comparison. - # Same cmake workaround as ARTn. - ira_dep = dependency('ira', required: false) - if not ira_dep.found() - ira_libdir = get_option('ira_libdir') - ira_incdir = get_option('ira_includedir') - if ira_libdir == '' or ira_incdir == '' - ira_src = meson.project_source_root() / 'subprojects' / 'ira' - ira_bld = meson.project_build_root() / 'subprojects' / '_ira_cmake_build' - message('IRA: building subproject via cmake (meson Fortran scanner workaround)') - run_command('cmake', '-S', ira_src, '-B', ira_bld, - '-DCMAKE_BUILD_TYPE=Release', - check: true) - run_command('cmake', '--build', ira_bld, '--parallel', '4', check: true) - ira_incdir = 'interface' - ira_includes = ['-I' + ira_src / ira_incdir] - ira_candidates = [ira_bld / 'src', ira_src / 'lib'] - ira_lib = disabler() - foreach candidate : ira_candidates - ira_lib = meson.get_compiler('cpp').find_library( - 'ira', - dirs: [candidate], - required: false, - ) - if ira_lib.found() - ira_libdir = candidate - break - endif - endforeach - if not ira_lib.found() - error('IRA enabled but libira was not found in the built or vendored library directories') - endif - else - ira_includes = ['-I' + ira_incdir] - ira_lib = meson.get_compiler('cpp').find_library( - 'ira', - dirs: [ira_libdir], - required: false, - ) - if not ira_lib.found() - error('IRA enabled but libira was not found in ira_libdir=' + ira_libdir) - endif - endif - _runtime_rpath += [ira_libdir] - ira_dep = declare_dependency( - dependencies: ira_lib, - compile_args: ira_includes, - ) - endif - _deps += [ira_dep] - _args += ['-DWITH_IRA'] - eonclib_sources += ['IRACompare.cpp'] - eonclib_sources += ['libs/IRA/IRAResource.cpp'] -endif +# ARTn / IRA source membership is owned by the eonclib sourceset above +# (always-on add). Users supply libartn.so / libira.so on +# LD_LIBRARY_PATH; ARTnSaddleSearch / IRACompare gate at the call site +# with is_loaded() / require_loaded(). if get_option('with_serve') # rgpot-compatible RPC serve mode: wraps any eOn potential and serves it @@ -829,7 +902,7 @@ if get_option('with_serve') rgpot_proj = subproject('rgpot', default_options: rgpot_serve_opts) ptlrpc_dep = rgpot_proj.get_variable('ptlrpc_dep') _args += ['-DWITH_SERVE_MODE'] - eonclib_sources += ['ServeMode.cpp', 'ServeRpcServer.cpp'] + # Source membership owned by sourceset (when: 'WITH_SERVE_MODE'). _deps += [serve_capnp_dep, ptlrpc_dep] endif @@ -863,9 +936,14 @@ endif # --------------------- Library +# Resolve the sourceset against the configuration_data assembled +# above. result.sources() is the final, deduplicated list, identical +# to what the pre-#177 chain of `eonclib_sources +=` produced. +eonclib_resolved = eonclib_ss.apply(eonclib_cfg) + eclib = library( 'eonclib', - sources: eonclib_sources, + sources: eonclib_resolved.sources(), include_directories: _incdirs, dependencies: _deps, link_with: _linkto, @@ -927,14 +1005,42 @@ endif if get_option('with_tests') test_args = _args test_deps = _exe_deps - testMain = library( - 'TestMain', - ['unit_tests/TestMain.cpp', 'thirdparty/catch2/catch_amalgamated.cpp'], - dependencies: _deps, # static lib: use _deps (no readcon link_args) - include_directories: _incdirs, - cpp_args: test_args, - link_with: _linkto, + # Catch2 amalgamated dedup. On Linux/macOS (gcc/clang + ld/lld) + # we compile catch_amalgamated.cpp ONCE into a static library and + # `link_whole:` it into every test exe so LTO at --buildtype release + # cannot prune main() / the EventListener vtables. The static lib + # also gets `-Wno-maybe-uninitialized` to silence the libstdc++ 13.3 + # std::regex move-ctor false positive at -O3 + LTO (GCC PR93499 / + # 96012). The flag is filtered through get_supported_arguments so + # MSVC -- which rejects it as D8021 invalid numeric argument -- does + # not see it. + # + # On Windows the dedup path doesn't work: meson's static_library + # under conda-forge clang 19 emits a GNU `.a` archive, but + # MSVC link.exe's /WHOLEARCHIVE expects a COFF `.lib` and bails + # with LNK1136 invalid or corrupt file. Fall back to compiling the + # amalgamated TU into each test executable directly there. This is + # the pre-d60a9b80 behaviour; it costs one extra compile per test + # target but only on a single matrix entry. + _catch2_extra_cpp_args = cppc.get_supported_arguments( + ['-Wno-maybe-uninitialized'], ) + if host_machine.system() == 'windows' + catch2_lib = [] + _catch2_per_target_sources = ['thirdparty/catch2/catch_amalgamated.cpp'] + else + catch2_lib = static_library( + 'catch2_amalgamated', + ['thirdparty/catch2/catch_amalgamated.cpp'], + include_directories: _incdirs, + cpp_args: test_args + _catch2_extra_cpp_args, + ) + _catch2_per_target_sources = [] + endif + # Note: there is no separate `TestMain` library target -- the + # `unit_tests/TestMain.cpp` source is compiled directly into + # bench_pot (only consumer in this build). Test executables get + # their `main()` via the Catch2 amalgamated lib. test_array = [ # ['test_impldim', 'ImpDimerTest.cpp', 'saddle_search'], ['strparse_run', 'StringHelpersTest.cpp', ''], @@ -974,19 +1080,17 @@ if get_option('with_tests') if get_option('with_metatomic') test_array += [['test_mta', 'MetatomicTest.cpp', 'lj38']] endif - if get_option('with_xtb') - test_array += [['test_xtb', 'XTBTest.cpp', 'sulfolene']] - test_array += [['test_cineb_xtb', 'CINEBXTBTest.cpp', 'cineb_xtb', 120]] - endif + # XTB tests are always built; SKIP at runtime when libxtb isn't on + # LD_LIBRARY_PATH (mirrors LAMMPS / ARTn / IRA tests). + test_array += [['test_xtb', 'XTBTest.cpp', 'sulfolene']] + test_array += [['test_cineb_xtb', 'CINEBXTBTest.cpp', 'cineb_xtb', 120]] if get_option('with_serve') test_array += [['test_serve_spec', 'ServeSpecParseTest.cpp', '']] endif - if get_option('with_artn') - test_array += [['test_artn', 'ARTnTest.cpp', 'neb_morse']] - endif - if get_option('with_ira') - test_array += [['test_ira', 'IRATest.cpp', 'saddle_search']] - endif + # ARTn / IRA tests are always built; they SKIP at runtime when the + # respective .so is not on LD_LIBRARY_PATH (see is_loaded() guards). + test_array += [['test_artn', 'ARTnTest.cpp', 'neb_morse']] + test_array += [['test_ira', 'IRATest.cpp', 'saddle_search']] foreach test : test_array test_timeout = test.get(3, 30) test( @@ -995,14 +1099,32 @@ if get_option('with_tests') test.get(0), sources: [ 'unit_tests/' + test.get(1), - 'thirdparty/catch2/catch_amalgamated.cpp', - ], + ] + _catch2_per_target_sources, dependencies: test_deps, include_directories: _incdirs, cpp_args: test_args, link_with: _linkto, + # Catch2 amalgamated provides main() and the + # AssertionHandler / EventListener vtables. Under + # -flto=auto (release buildtype) `link_with` on a + # static lib lets LTO prune objects whose symbols + # aren't directly referenced by the test source, + # including main(), and the link fails. `link_whole` + # forces every catch2_lib object into the executable + # so the LTO pass keeps them. On Windows catch2_lib + # is `[]` and the amalgamated TU is in `sources` + # instead -- see the catch2_lib branch above. + link_whole: catch2_lib, build_rpath: ':'.join(_runtime_rpath), ), + # Catch2 v3 returns exit code 4 from a process that ran no + # tests / no assertions, even when every TEST_CASE issued + # SKIP() cleanly. Pass --allow-running-no-tests so tests + # gated on runtime-loaded libraries (libxtb, libartn, + # libira, ...) skip silently with exit 0 on systems where + # the .so is not on LD_LIBRARY_PATH (e.g. macOS CI without + # libxtb installed). + args: ['--allow-running-no-tests'], workdir: meson.project_source_root() + '/client/unit_tests/data/systems/' + test.get( 2, ), @@ -1018,12 +1140,12 @@ if get_option('with_tests') 'test_socket_nwchem', sources: [ 'unit_tests/SocketNWChemPotTest.cpp', - 'thirdparty/catch2/catch_amalgamated.cpp', - ], + ] + _catch2_per_target_sources, dependencies: test_deps, include_directories: _incdirs, cpp_args: test_args, link_with: _linkto, + link_whole: catch2_lib, ) test_workdir = meson.project_source_root() + '/client/unit_tests/data/systems/nwchem_test' @@ -1046,16 +1168,21 @@ if get_option('with_tests') endif # Benchmarks (not in default test suite -- run via: meson test --benchmark) + # bench_pot is the only target gated to `meson test --benchmark`; + # default builds (including the sanitizer matrix) skip it via + # build_by_default = false to avoid instrumenting + linking a + # benchmark binary nobody runs in the test pass. bench_pot_exe = executable('bench_pot', [ 'unit_tests/PotBenchmark.cpp', 'unit_tests/TestMain.cpp', - 'thirdparty/catch2/catch_amalgamated.cpp', - ], + ] + _catch2_per_target_sources, dependencies: test_deps, include_directories: _incdirs, cpp_args: test_args, link_with: _linkto, + link_whole: catch2_lib, + build_by_default: false, ) benchmark('bench_pot', bench_pot_exe, args: ['[.benchmark]'], diff --git a/client/potentials/LAMMPS/LAMMPSPot.cpp b/client/potentials/LAMMPS/LAMMPSPot.cpp index 82afc9d04..14dc4b8da 100644 --- a/client/potentials/LAMMPS/LAMMPSPot.cpp +++ b/client/potentials/LAMMPS/LAMMPSPot.cpp @@ -10,6 +10,7 @@ ** https://github.com/TheochemUI/eOn */ #include "LAMMPSPot.h" +#include "LammpsBundle.h" #include "LammpsLoader.h" #include @@ -31,11 +32,64 @@ LAMMPSPot::LAMMPSPot(const Parameters &p) mpiComm{p.potential_options.MPIClientComm} #endif { - // Fail fast if LAMMPS library not available + // Fail fast if LAMMPS library not available. eonc::LammpsLoader::instance().require_loaded(); + + // Two operating modes: + // bundle mode -- lammps_options.bundle_path is set. LAMMPSBundle + // extracts the .eonlpb tarball-equivalent into a + // private scratch dir and we point liblammps at + // that dir via "shell cd " so every + // pair_coeff / include / read_data / shared + // plugin .so resolves there. The eonclient CWD + // becomes irrelevant for LAMMPS. + // legacy mode -- bundle_path empty. We require in.lammps in CWD + // so liblammps's relative-path lookups resolve + // against eonclient's CWD; pair-coeff files and + // any other LAMMPS-side file references must + // live there too. + const auto &bundle_path = p.potential_options.LAMMPSBundlePath; + if (!bundle_path.empty()) { + auto bundle = eonc::LAMMPSBundle::open(bundle_path); + m_lammps_workdir = bundle.extract(); + m_owns_workdir = true; + } else { + m_lammps_workdir = std::filesystem::current_path(); + m_owns_workdir = false; + if (!std::filesystem::exists(m_lammps_workdir / "in.lammps")) { + auto cwd = m_lammps_workdir.string(); + EONC_LOG_ERROR("[LAMMPS] in.lammps not found in {}", cwd); + eonc::log::get()->flush_log(); + throw std::runtime_error( + "LAMMPSPot: in.lammps not found in eonclient CWD (" + cwd + + "). Either (a) put in.lammps and every file it references " + "(pair_coeff data, custom pair_style .so plugins, KIM tables, " + "etc.) next to the eonclient process, or (b) pack them into a " + "single .eonlpb bundle and pass it via [Potential] " + "lammps_bundle = path/to/bundle.eonlpb -- liblammps then reads " + "everything from a private scratch dir, no CWD coupling."); + } + } + + // Detect units from in.lammps: look for "#!units real" marker. + realunits = false; + std::ifstream infile(m_lammps_workdir / "in.lammps"); + std::string line; + while (std::getline(infile, line)) { + if (line == "#!units real") { + realunits = true; + break; + } + } } -LAMMPSPot::~LAMMPSPot() { cleanMemory(); } +LAMMPSPot::~LAMMPSPot() { + cleanMemory(); + if (m_owns_workdir && !m_lammps_workdir.empty()) { + std::error_code ec; + std::filesystem::remove_all(m_lammps_workdir, ec); + } +} void LAMMPSPot::cleanMemory() { if (LAMMPSObj != nullptr) { @@ -136,27 +190,22 @@ void LAMMPSPot::makeNewLAMMPS(long N, const double *R, const int *atomicNrs, LAMMPSObj = lmp.open_no_mpi(lmpargc, const_cast(lmpargv), nullptr); #endif + // Pin liblammps's working directory to m_lammps_workdir so every + // pair_coeff / include / read_data / shared-plugin .so reference + // inside in.lammps resolves there. In bundle mode this is the + // extracted scratch dir; in legacy mode it's eonclient's CWD (a + // no-op LAMMPS-side, but harmless). + { + std::string shell_cd = + std::format("shell cd {}", m_lammps_workdir.string()); + lmp.command(LAMMPSObj, shell_cd.c_str()); + } + if (lammpsThr > 0) { std::string cmd = std::format("package omp {} force/neigh", lammpsThr); lmp.command(LAMMPSObj, cmd.c_str()); } - // Detect units from in.lammps: look for "#!units real" marker - realunits = false; - if (std::filesystem::exists("in.lammps")) { - std::ifstream infile("in.lammps"); - std::string line; - while (std::getline(infile, line)) { - if (line == "#!units real") { - realunits = true; - break; - } - } - } else { - EONC_LOG_ERROR("[LAMMPS] in.lammps not found in working directory"); - return; - } - if (realunits) { lmp.command(LAMMPSObj, "units real"); } else { @@ -186,9 +235,31 @@ void LAMMPSPot::makeNewLAMMPS(long N, const double *R, const int *atomicNrs, lmp.command(LAMMPSObj, "mass * 1.0"); - // Load user LAMMPS input script + // Read in.lammps from the pinned workdir. The "shell cd" above made + // liblammps's CWD = m_lammps_workdir, so a relative "in.lammps" + // here resolves against the bundle scratch dir (or eonclient CWD + // in legacy mode). lmp.file(LAMMPSObj, "in.lammps"); + // lammps_file logs syntax / pair_coeff / pair_style errors to its + // own log and returns void. lammps_has_error (LAMMPS >= 3Mar2020, + // null on older builds) is the only way to surface them. Without + // this check the next lammps_command("run 1 ...") would dereference + // a null pair_style and segfault. + if (lmp.has_error && lmp.has_error(LAMMPSObj)) { + char buf[1024]{}; + if (lmp.get_last_error_message) { + lmp.get_last_error_message(LAMMPSObj, buf, sizeof(buf)); + } + EONC_LOG_ERROR("[LAMMPS] error after reading in.lammps from {}: {}", + m_lammps_workdir.string(), buf); + eonc::log::get()->flush_log(); + throw std::runtime_error( + std::string("LAMMPSPot: liblammps reported an error after sourcing ") + + "in.lammps (workdir=" + m_lammps_workdir.string() + + "). LAMMPS message: " + buf); + } + // Define variables for force/energy extraction lmp.command(LAMMPSObj, "variable fx atom fx"); lmp.command(LAMMPSObj, "variable fy atom fy"); diff --git a/client/potentials/LAMMPS/LAMMPSPot.h b/client/potentials/LAMMPS/LAMMPSPot.h index 36efab6ae..0b0cae51a 100644 --- a/client/potentials/LAMMPS/LAMMPSPot.h +++ b/client/potentials/LAMMPS/LAMMPSPot.h @@ -16,14 +16,22 @@ #include "../../Parameters.h" #include "../../Potential.h" +#include + +/// Thread safety: NOT safe to share an instance across threads. +/// Internally calls std::filesystem::current_path() (process-wide +/// state) and uses a `shell cd` LAMMPS command to pin liblammps's +/// working directory; both are global side effects. liblammps itself +/// also keeps process-global state per LAMMPSObj. Use one LAMMPSPot +/// instance per thread / per NEB image. class LAMMPSPot : public Potential { public: LAMMPSPot(const Parameters &p); - ~LAMMPSPot(); + ~LAMMPSPot() override; void cleanMemory(); void force(long N, const double *R, const int *atomicNrs, double *F, - double *U, double *variance, const double *box); + double *U, double *variance, const double *box) override; private: int lammpsThr{0}; @@ -35,5 +43,18 @@ class LAMMPSPot : public Potential { void *LAMMPSObj{nullptr}; void makeNewLAMMPS(long N, const double *R, const int *atomicNrs, const double *box); + /// Working directory liblammps reads every relative path from + /// (in.lammps itself + everything in.lammps references). makeNewLAMMPS + /// issues `shell cd m_lammps_workdir` to liblammps so the eonclient + /// CWD doesn't matter. + /// + /// bundle mode (LAMMPSBundlePath set) -> scratch dir under + /// temp_directory_path() that LAMMPSBundle::extract() created; + /// owned by this instance and removed in the destructor. + /// legacy mode (LAMMPSBundlePath empty) -> eonclient's CWD; + /// not owned, never removed. + std::filesystem::path m_lammps_workdir; + /// Lifetime ownership of m_lammps_workdir. True only in bundle mode. + bool m_owns_workdir{false}; bool realunits{false}; }; diff --git a/client/potentials/LAMMPS/LammpsBundle.cpp b/client/potentials/LAMMPS/LammpsBundle.cpp new file mode 100644 index 000000000..717b8663f --- /dev/null +++ b/client/potentials/LAMMPS/LammpsBundle.cpp @@ -0,0 +1,179 @@ +/* +** This file is part of eOn. +** +** SPDX-License-Identifier: BSD-3-Clause +** +** Copyright (c) 2010--present, eOn Development Team +** All rights reserved. +** +** Repo: +** https://github.com/TheochemUI/eOn +*/ +#include "LammpsBundle.h" + +#include + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#define eonc_getpid _getpid +#else +#include +#define eonc_getpid getpid +#endif + +namespace eonc { + +namespace { + +constexpr char kMagic[8] = {'E', 'O', 'N', 'L', 'P', 'B', '1', '\0'}; + +std::uint64_t read_u64_le(const char *p) noexcept { + std::uint64_t v = 0; + std::memcpy(&v, p, sizeof(v)); + return v; +} + +std::filesystem::path make_scratch_dir() { + std::random_device rd; + std::mt19937_64 rng{rd()}; + for (int attempt = 0; attempt < 16; ++attempt) { + auto p = std::filesystem::temp_directory_path() / + std::format("eonc_lammps_{}_{:016x}", + static_cast(eonc_getpid()), rng()); + std::error_code ec; + if (std::filesystem::create_directories(p, ec) && !ec) { + return p; + } + } + throw std::runtime_error( + "LAMMPSBundle: failed to allocate scratch dir under " + + std::filesystem::temp_directory_path().string()); +} + +} // namespace + +LAMMPSBundle LAMMPSBundle::open(const std::filesystem::path &bundle) { + if (!std::filesystem::exists(bundle)) { + throw std::runtime_error("LAMMPSBundle: file not found: " + + bundle.string()); + } + std::ifstream in(bundle, std::ios::binary); + if (!in) { + throw std::runtime_error("LAMMPSBundle: failed to open " + bundle.string()); + } + + char header[16]{}; + in.read(header, sizeof(header)); + if (in.gcount() != static_cast(sizeof(header))) { + throw std::runtime_error("LAMMPSBundle: short read on header (" + + bundle.string() + ")"); + } + if (std::memcmp(header, kMagic, sizeof(kMagic)) != 0) { + throw std::runtime_error("LAMMPSBundle: bad magic (expected EONLPB1) in " + + bundle.string()); + } + const auto manifest_len = read_u64_le(header + 8); + if (manifest_len == 0 || manifest_len > (1ULL << 24)) { + throw std::runtime_error( + std::format("LAMMPSBundle: implausible manifest length {} in {}", + manifest_len, bundle.string())); + } + + std::string manifest_text(manifest_len, '\0'); + in.read(manifest_text.data(), static_cast(manifest_len)); + if (in.gcount() != static_cast(manifest_len)) { + throw std::runtime_error("LAMMPSBundle: short read on manifest (" + + bundle.string() + ")"); + } + + nlohmann::json j; + try { + j = nlohmann::json::parse(manifest_text); + } catch (const std::exception &e) { + throw std::runtime_error( + std::string("LAMMPSBundle: manifest is not valid JSON: ") + e.what()); + } + if (!j.contains("files") || !j["files"].is_array()) { + throw std::runtime_error("LAMMPSBundle: manifest missing 'files' array (" + + bundle.string() + ")"); + } + + LAMMPSBundle out; + out.m_source = bundle; + out.m_bodies_offset = sizeof(header) + manifest_len; + out.m_entries.reserve(j["files"].size()); + for (const auto &entry : j["files"]) { + if (!entry.contains("name") || !entry.contains("size")) { + throw std::runtime_error( + "LAMMPSBundle: manifest entry missing name/size in " + + bundle.string()); + } + out.m_entries.push_back( + {entry["name"].get(), entry["size"].get()}); + } + return out; +} + +std::filesystem::path LAMMPSBundle::extract() const { + std::ifstream in(m_source, std::ios::binary); + if (!in) { + throw std::runtime_error("LAMMPSBundle: failed to reopen " + + m_source.string()); + } + in.seekg(static_cast(m_bodies_offset)); + if (!in) { + throw std::runtime_error("LAMMPSBundle: seek to bodies failed in " + + m_source.string()); + } + + auto scratch = make_scratch_dir(); + for (const auto &entry : m_entries) { + // Reject path traversal: bundle entries must be plain names or + // forward-slash relative paths that stay inside the scratch dir. + std::filesystem::path rel(entry.name); + if (rel.is_absolute() || entry.name.find("..") != std::string::npos) { + std::error_code ec; + std::filesystem::remove_all(scratch, ec); + throw std::runtime_error("LAMMPSBundle: rejecting unsafe entry name '" + + entry.name + "' in " + m_source.string()); + } + auto out_path = scratch / rel; + std::filesystem::create_directories(out_path.parent_path()); + std::ofstream out(out_path, std::ios::binary); + if (!out) { + std::error_code ec; + std::filesystem::remove_all(scratch, ec); + throw std::runtime_error("LAMMPSBundle: cannot write " + + out_path.string()); + } + + constexpr std::size_t kBufSize = 64 * 1024; + std::vector buf(kBufSize); + std::uint64_t remaining = entry.size; + while (remaining > 0) { + const auto chunk = std::min(remaining, kBufSize); + in.read(buf.data(), static_cast(chunk)); + if (in.gcount() != static_cast(chunk)) { + std::error_code ec; + std::filesystem::remove_all(scratch, ec); + throw std::runtime_error( + std::format("LAMMPSBundle: short read for '{}' ({} of {} bytes) " + "in {}", + entry.name, static_cast(in.gcount()), + entry.size, m_source.string())); + } + out.write(buf.data(), static_cast(chunk)); + remaining -= chunk; + } + } + return scratch; +} + +} // namespace eonc diff --git a/client/potentials/LAMMPS/LammpsBundle.h b/client/potentials/LAMMPS/LammpsBundle.h new file mode 100644 index 000000000..f743fdbc4 --- /dev/null +++ b/client/potentials/LAMMPS/LammpsBundle.h @@ -0,0 +1,85 @@ +/* +** This file is part of eOn. +** +** SPDX-License-Identifier: BSD-3-Clause +** +** Copyright (c) 2010--present, eOn Development Team +** All rights reserved. +** +** Repo: +** https://github.com/TheochemUI/eOn +*/ +#pragma once + +/// LAMMPS portable run-input bundle (.eonlpb). +/// +/// One self-contained blob holding everything liblammps needs to run: +/// in.lammps plus every pair_coeff data file (EAM, Tersoff, ZBL, +/// ReaxFF, ...), every read_data input, every dlopen-loaded +/// pair_style .so plugin, every KIM table, every shell helper. The +/// content is opaque to eOn -- the packer just walks a directory and +/// the client just untars it. eonclient extracts the bundle into a +/// private scratch dir under temp_directory_path() and issues +/// `shell cd ` to liblammps so every relative path inside +/// in.lammps resolves there. The eonclient CWD becomes irrelevant +/// for LAMMPS file lookups. +/// +/// Format (all little-endian): +/// [0..7] magic : "EONLPB1\0" (8 bytes) +/// [8..15] m_len : uint64_t (manifest length in bytes) +/// [16..] manifest : JSON UTF-8 (m_len bytes) +/// [...] bodies : concatenated file contents in manifest order +/// +/// Manifest schema: +/// {"files":[{"name":"in.lammps","size":1234}, +/// {"name":"Pd.eam.alloy","size":56789}]} +/// +/// `name` is the relative path inside the bundle. After extraction +/// every relative reference inside in.lammps (pair_coeff , +/// include , read_data , plugin load , KIM tables, +/// ...) resolves against the scratch dir verbatim, because eOn does +/// `shell cd ` before `lammps_file("in.lammps")`. + +#include +#include +#include + +namespace eonc { + +struct LAMMPSBundleEntry { + std::string name; + std::uint64_t size; +}; + +class LAMMPSBundle { +public: + /// Verify the magic and parse the manifest. Throws on a bad header + /// or malformed manifest. + static LAMMPSBundle open(const std::filesystem::path &bundle); + + /// Extract every entry to a fresh per-instance scratch dir under + /// std::filesystem::temp_directory_path(). Returns the scratch dir + /// path. The caller is responsible for std::filesystem::remove_all + /// when done. + std::filesystem::path extract() const; + + [[nodiscard]] const std::vector &entries() const noexcept { + return m_entries; + } + + /// Path that open() was given; the bundle is read again at extract() + /// time so we don't pin the file in memory between calls. + [[nodiscard]] const std::filesystem::path &source() const noexcept { + return m_source; + } + +private: + LAMMPSBundle() = default; + + std::filesystem::path m_source; + std::vector m_entries; + /// Byte offset where the first entry's body begins (= 16 + manifest len). + std::uint64_t m_bodies_offset{0}; +}; + +} // namespace eonc diff --git a/client/potentials/LAMMPS/LammpsLoader.cpp b/client/potentials/LAMMPS/LammpsLoader.cpp index 1a43b30d1..8793d56b4 100644 --- a/client/potentials/LAMMPS/LammpsLoader.cpp +++ b/client/potentials/LAMMPS/LammpsLoader.cpp @@ -44,6 +44,12 @@ LammpsLoader::LammpsLoader() { extract_variable = dynlib::loadSym(m_handle, "lammps_extract_variable"); + // Optional: error-introspection. Present in LAMMPS >= 3Mar2020. + // Null on older builds; LAMMPSPot null-checks before calling. + has_error = dynlib::loadSym(m_handle, "lammps_has_error"); + get_last_error_message = dynlib::loadSym( + m_handle, "lammps_get_last_error_message"); + #ifdef EONMPI open_mpi = dynlib::loadSym(m_handle, "lammps_open"); #endif @@ -59,7 +65,13 @@ LammpsLoader::LammpsLoader() { m_loaded = true; } -LammpsLoader::~LammpsLoader() { dynlib::close(m_handle); } +LammpsLoader::~LammpsLoader() { + // Intentionally do NOT dlclose at static destruction. liblammps holds + // global state (MPI handles, OpenMP threadpools, KIM model registry) + // whose teardown via atexit / __attribute__((destructor)) collides + // with running this Meyer-singleton dtor at process shutdown. The + // mapping is process-lifetime; OS reclaims it at exit. +} void LammpsLoader::require_loaded() const { if (!m_loaded) { diff --git a/client/potentials/LAMMPS/LammpsLoader.h b/client/potentials/LAMMPS/LammpsLoader.h index 1a9262601..10987daea 100644 --- a/client/potentials/LAMMPS/LammpsLoader.h +++ b/client/potentials/LAMMPS/LammpsLoader.h @@ -40,6 +40,11 @@ class LammpsLoader { using file_fn = void (*)(void *, const char *); using scatter_atoms_fn = void (*)(void *, const char *, int, int, void *); using extract_var_fn = void *(*)(void *, const char *, const char *); + // Error-introspection: lammps_file logs LAMMPS-side failures to its + // own log stream and returns void, so the only way to surface them + // is lammps_has_error + lammps_get_last_error_message. + using has_error_fn = int (*)(void *); + using get_last_error_fn = int (*)(void *, char *, int); #ifdef EONMPI using open_mpi_fn = void *(*)(int, char **, MPI_Comm, void **); #endif @@ -54,6 +59,10 @@ class LammpsLoader { file_fn file{nullptr}; scatter_atoms_fn scatter_atoms{nullptr}; extract_var_fn extract_variable{nullptr}; + // Optional: present in LAMMPS >= ~stable_3Mar2020. Null on older + // builds; LAMMPSPot::makeNewLAMMPS() null-checks before calling. + has_error_fn has_error{nullptr}; + get_last_error_fn get_last_error_message{nullptr}; #ifdef EONMPI open_mpi_fn open_mpi{nullptr}; #endif diff --git a/client/potentials/LAMMPS/meson.build b/client/potentials/LAMMPS/meson.build index 5711ec6fb..a8bd1a8e1 100644 --- a/client/potentials/LAMMPS/meson.build +++ b/client/potentials/LAMMPS/meson.build @@ -1,9 +1,11 @@ -# LAMMPS potential: loaded at runtime via dlopen, no compile-time dependency -dl_dep = cppc.find_library('dl', required: false) +# LAMMPS potential: loaded at runtime via dlopen, no compile-time dependency. +# -ldl is added globally to _deps in client/meson.build (file-scope dl_dep) +# so every loader-using target links it without per-potential boilerplate. lammps_pot = library('lammps_pot', 'LAMMPSPot.cpp', 'LammpsLoader.cpp', - dependencies: [_deps, dl_dep], + 'LammpsBundle.cpp', + dependencies: _deps, cpp_args: _args, link_with: _linkto, include_directories: _incdirs, diff --git a/client/potentials/MPIPot/MPIPot.cpp b/client/potentials/MPIPot/MPIPot.cpp index fcbcf4ddd..fe86df012 100644 --- a/client/potentials/MPIPot/MPIPot.cpp +++ b/client/potentials/MPIPot/MPIPot.cpp @@ -11,21 +11,37 @@ */ #include "MPIPot.h" + #include -#include -#include + #ifndef _WIN32 #include #endif +// MPI C++ bindings (MPI::COMM_WORLD, MPI::INT, MPI::DOUBLE) were +// deprecated in MPI-2.2 and removed in MPI-3.0 (~2012); modern +// conda-forge MPICH 4.x and OpenMPI 5.x ship without mpicxx.h. The +// translations below use the C bindings, which every MPI +// implementation guarantees: +// MPI::COMM_WORLD.Send(...) -> MPI_Send(..., MPI_COMM_WORLD) +// MPI::COMM_WORLD.Recv(...) -> MPI_Recv(..., MPI_COMM_WORLD, +// MPI_STATUS_IGNORE) +// MPI::COMM_WORLD.Iprobe(...) -> MPI_Iprobe(..., MPI_COMM_WORLD, +// &flag, MPI_STATUS_IGNORE) +// MPI::INT / MPI::DOUBLE -> MPI_INT / MPI_DOUBLE +// +// This is a pure compile fix; it doesn't change wire semantics. +// Runtime-loadable libmpi via an MpiLoader (the FlexiBLAS-of-MPI +// pattern) is a separate follow-up; see the ABI note in +// MPIPot's docs/source/user_guide/mpi_potential.md. + MPIPot::MPIPot(const Parameters &p) : Potential(p) { potentialRank = p.potential_options.MPIPotentialRank; poll_period = p.potential_options.MPIPollPeriod; - return; } -void MPIPot::cleanMemory(void) { return; } +void MPIPot::cleanMemory(void) {} MPIPot::~MPIPot() { cleanMemory(); } @@ -34,37 +50,51 @@ void MPIPot::force(long N, const double *R, const int *atomicNrs, double *F, variance = nullptr; // Send data to potential int pbc = 1; - int failed; + int failed = 0; char cwd[1024]; - long icwd[1024]; - getcwd(cwd, 1024); + // Wire format pre-dates this commit: 1024 MPI_INTs holding char + // values (cwd[i] cast through int). The original C++-bindings code + // declared a long[1024] but tagged the message MPI::INT, which on + // little-endian sent the low 4 bytes of each long. Switching the + // local buffer to int[] keeps the wire format identical on every + // endianness (any deployed MPI server still parses it correctly). + int icwd[1024]; + if (getcwd(cwd, sizeof(cwd)) == nullptr) { + cwd[0] = '\0'; + } for (int i = 0; i < 1024; i++) { - icwd[i] = static_cast(cwd[i]); + icwd[i] = static_cast(cwd[i]); } int intn = static_cast(N); - MPI::COMM_WORLD.Send(&intn, 1, MPI::INT, potentialRank, 0); - MPI::COMM_WORLD.Send(atomicNrs, N, MPI::INT, potentialRank, 0); - MPI::COMM_WORLD.Send(R, 3 * N, MPI::DOUBLE, potentialRank, 0); - MPI::COMM_WORLD.Send(box, 9, MPI::DOUBLE, potentialRank, 0); - MPI::COMM_WORLD.Send(&pbc, 1, MPI::INT, potentialRank, 0); - MPI::COMM_WORLD.Send(&icwd[0], 1024, MPI::INT, potentialRank, 0); + MPI_Send(&intn, 1, MPI_INT, potentialRank, 0, MPI_COMM_WORLD); + MPI_Send(const_cast(atomicNrs), static_cast(N), MPI_INT, + potentialRank, 0, MPI_COMM_WORLD); + MPI_Send(const_cast(R), static_cast(3 * N), MPI_DOUBLE, + potentialRank, 0, MPI_COMM_WORLD); + MPI_Send(const_cast(box), 9, MPI_DOUBLE, potentialRank, 0, + MPI_COMM_WORLD); + MPI_Send(&pbc, 1, MPI_INT, potentialRank, 0, MPI_COMM_WORLD); + MPI_Send(icwd, 1024, MPI_INT, potentialRank, 0, MPI_COMM_WORLD); if (poll_period > 0.0) { - while (MPI::COMM_WORLD.Iprobe(potentialRank, 0) == false) { - usleep(static_cast(poll_period / 1000000.0)); - } + int flag = 0; + do { + MPI_Iprobe(potentialRank, 0, MPI_COMM_WORLD, &flag, MPI_STATUS_IGNORE); + if (!flag) { + usleep(static_cast(poll_period / 1000000.0)); + } + } while (!flag); } // Recv data from potential - MPI::COMM_WORLD.Recv(&failed, 1, MPI::INT, potentialRank, 0); + MPI_Recv(&failed, 1, MPI_INT, potentialRank, 0, MPI_COMM_WORLD, + MPI_STATUS_IGNORE); if (failed == 1) { throw 100; } - MPI::COMM_WORLD.Recv(U, 1, MPI::DOUBLE, potentialRank, 0); - MPI::COMM_WORLD.Recv(F, 3 * N, MPI::DOUBLE, potentialRank, 0); - // printf("energy: %12.4e\n", *U); - // printf("forces:\n"); - // for (int i=0;i(3 * N), MPI_DOUBLE, potentialRank, 0, + MPI_COMM_WORLD, MPI_STATUS_IGNORE); } diff --git a/client/potentials/Metatomic/MetatomicPotential.cpp b/client/potentials/Metatomic/MetatomicPotential.cpp index 1399e6137..1d323b709 100644 --- a/client/potentials/Metatomic/MetatomicPotential.cpp +++ b/client/potentials/Metatomic/MetatomicPotential.cpp @@ -156,42 +156,68 @@ MetatomicPotential::MetatomicPotential(const Parameters ¶ms) requested_output->set_unit("eV"); evaluations_options_->outputs.insert(this->energy_key_, requested_output); - // 7. Optionally request energy uncertainty if threshold is positive + // 7. Optionally request energy uncertainty if threshold is positive. + // + // Models that don't ship an "energy_uncertainty" output are common (and + // expected -- it's strictly optional). pre-fix: we called + // metatomic_torch::pick_output("energy_uncertainty", ...) blind and + // caught the c10::Error. The catch handled the logical case fine but + // c10::Error captures + prints its construction stack to stderr + // unconditionally, so every load of an uncertainty-less model + // surfaced a multi-frame backtrace that looked like a real crash to + // users (issue #309). pre-check `outputs.contains(...)` for any + // variant that could resolve so we never trigger the throw path. if (m_metatomic_opts.uncertainty_threshold > 0) { this->uncertainty_threshold_ = m_metatomic_opts.uncertainty_threshold; - try { - this->energy_uncertainty_key_ = metatomic_torch::pick_output( - "energy_uncertainty", outputs, v_energy_uq); - - if (outputs.contains(this->energy_uncertainty_key_)) { - auto uncertainty_info = outputs.at(this->energy_uncertainty_key_); - if (uncertainty_info->per_atom) { - auto requested_uncertainty = - torch::make_intrusive(); - requested_uncertainty->per_atom = true; - requested_uncertainty->explicit_gradients = {}; - requested_uncertainty->set_quantity("energy"); - requested_uncertainty->set_unit("eV"); - evaluations_options_->outputs.insert(this->energy_uncertainty_key_, - requested_uncertainty); - QUILL_LOG_INFO(m_log, - "[MetatomicPotential] Requested per-atom " - "'{}' from model (threshold = {})", - this->energy_uncertainty_key_, - this->uncertainty_threshold_); - } else { - QUILL_LOG_DEBUG(m_log, - "[MetatomicPotential] Model provides '{}' " - "but not per-atom; skipping uncertainty checks.", - this->energy_uncertainty_key_); - this->uncertainty_threshold_ = -1.0; - } + + // Build the candidate set pick_output would consider, mirroring its + // own algorithm: try "::energy_uncertainty" then plain + // "energy_uncertainty". If none of them are present in outputs, the + // model has no uncertainty head and we silently disable the check + // -- no exception, no stderr backtrace. + std::vector candidates; + if (v_energy_uq.has_value() && !v_energy_uq->empty()) { + candidates.push_back(*v_energy_uq + "::energy_uncertainty"); + } + candidates.emplace_back("energy_uncertainty"); + + bool found = false; + for (const auto &cand : candidates) { + if (outputs.contains(cand)) { + this->energy_uncertainty_key_ = cand; + found = true; + break; } - } catch (const std::exception &e) { - QUILL_LOG_DEBUG( - m_log, "[MetatomicPotential] No uncertainty output available: {}", - e.what()); + } + + if (!found) { + QUILL_LOG_DEBUG(m_log, "[MetatomicPotential] model exports no " + "energy_uncertainty output; uncertainty checks " + "disabled."); this->uncertainty_threshold_ = -1.0; + } else { + auto uncertainty_info = outputs.at(this->energy_uncertainty_key_); + if (uncertainty_info->per_atom) { + auto requested_uncertainty = + torch::make_intrusive(); + requested_uncertainty->per_atom = true; + requested_uncertainty->explicit_gradients = {}; + requested_uncertainty->set_quantity("energy"); + requested_uncertainty->set_unit("eV"); + evaluations_options_->outputs.insert(this->energy_uncertainty_key_, + requested_uncertainty); + QUILL_LOG_INFO(m_log, + "[MetatomicPotential] Requested per-atom " + "'{}' from model (threshold = {})", + this->energy_uncertainty_key_, + this->uncertainty_threshold_); + } else { + QUILL_LOG_DEBUG(m_log, + "[MetatomicPotential] Model provides '{}' " + "but not per-atom; skipping uncertainty checks.", + this->energy_uncertainty_key_); + this->uncertainty_threshold_ = -1.0; + } } } diff --git a/client/potentials/Metatomic/meson.build b/client/potentials/Metatomic/meson.build index d6f9ed5b4..b9a8dce2d 100644 --- a/client/potentials/Metatomic/meson.build +++ b/client/potentials/Metatomic/meson.build @@ -1,7 +1,11 @@ mta_libdir = meson.current_source_dir() +# Precompile the torch + metatensor + metatomic header chain. Single +# heaviest TU in the project; PCH cuts cold rebuild from minutes to +# seconds. See pch/metatomic_pch.h for the rationale. metatomic_pot = library( 'metatomic_pot', 'MetatomicPotential.cpp', + cpp_pch: 'pch/metatomic_pch.h', dependencies: _deps, cpp_args: _args, link_with: _linkto, diff --git a/client/potentials/Metatomic/pch/metatomic_pch.h b/client/potentials/Metatomic/pch/metatomic_pch.h new file mode 100644 index 000000000..da36d4f28 --- /dev/null +++ b/client/potentials/Metatomic/pch/metatomic_pch.h @@ -0,0 +1,44 @@ +/* +** This file is part of eOn. +** +** SPDX-License-Identifier: BSD-3-Clause +** +** Copyright (c) 2010--present, eOn Development Team +** All rights reserved. +** +** Repo: +** https://github.com/TheochemUI/eOn +*/ +// Precompiled header for the metatomic_pot translation unit. +// +// The torch + metatensor + metatomic include chain dominates the +// MetatomicPotential.cpp compile time -- ~2.5 GB of preprocessor +// output per build, ~30 s wall-clock on a warm cache, and several +// minutes from cold. PCH'ing the headers cuts the per-rebuild cost +// to a few seconds whenever the .cpp is touched without any of the +// torch / metatomic versions changing. +// +// Wrap pragmas mirror what MetatomicPotential.h does so the PCH and +// the consuming TU agree on which warnings the third-party headers +// silence; meson rebuilds the PCH whenever this header or any +// transitive include changes. + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wfloat-equal" +#pragma GCC diagnostic ignored "-Wfloat-conversion" +#pragma GCC diagnostic ignored "-Wsign-conversion" +#pragma GCC diagnostic ignored "-Wold-style-cast" + +#include +#include +#include +#include +#include + +#include "metatensor/torch.hpp" +#include "metatensor/torch/module.hpp" +#include "metatomic/torch.hpp" + +#pragma GCC diagnostic pop diff --git a/client/potentials/XTBPot/XTBPot.cpp b/client/potentials/XTBPot/XTBPot.cpp index 42b955d2f..f97de9d0b 100644 --- a/client/potentials/XTBPot/XTBPot.cpp +++ b/client/potentials/XTBPot/XTBPot.cpp @@ -11,31 +11,106 @@ */ #include "XTBPot.h" +#include "XtbLoader.h" + #include +#include +#include #include -// Conversion factors -// const double angstromToBohr = 1.8897261349925714; -// const double hartreeToEV = 27.21138386; -// const double hartreeBohr_to_eVA = 14.399645472115932; using forcefields::unit_system::BOHR; using forcefields::unit_system::HARTREE; -// pointer to number of atoms, pointer to array of positions -// pointer to array of forces, pointer to internal energy -// address to supercell size +namespace { +// libxtb's verbosity sentinel: must match XTB_VERBOSITY_MUTED in xtb.h +// (= 0). We re-define here so this translation unit doesn't pull in +// xtb.h; the loader's runtime ABI is plain int. +constexpr int kXtbVerbosityMuted = 0; +} // namespace + +XTBPot::XTBPot(const Parameters &p) + : Potential(PotType::XTB, p), + xtb_acc{p.xtb_options.acc}, + xtb_electronic_temperature{p.xtb_options.elec_temperature}, + xtb_max_iter{p.xtb_options.maxiter}, + total_charge{p.xtb_options.charge}, + uhf{p.xtb_options.uhf} { + auto &xtb = eonc::get_xtb_loader(); + xtb.require_loaded(); + + env = xtb.new_environment(); + if (!env) { + throw std::runtime_error("Failed to create xtb environment"); + } + xtb.set_verbosity(env, kXtbVerbosityMuted); + // Release the default output unit to prevent Fortran NEWUNIT + // conflicts when multiple XTB environments coexist (per-image NEB + // potentials, e.g.). + xtb.release_output(env); + + calc = xtb.new_calculator(); + if (!calc) { + xtb.del_environment(&env); + throw std::runtime_error("Failed to create xtb calculator"); + } + res = xtb.new_results(); + if (!res) { + xtb.del_calculator(&calc); + xtb.del_environment(&env); + throw std::runtime_error("Failed to create xtb results"); + } + + if (p.xtb_options.paramset == "GFNFF") { + xtb_paramset = GFNMethod::GFNFF; + } else if (p.xtb_options.paramset == "GFN0xTB") { + xtb_paramset = GFNMethod::GFN0xTB; + } else if (p.xtb_options.paramset == "GFN1xTB") { + xtb_paramset = GFNMethod::GFN1xTB; + } else if (p.xtb_options.paramset == "GFN2xTB") { + xtb_paramset = GFNMethod::GFN2xTB; + } else { + throw std::runtime_error("Parameter set for XTB must be one of GFNFF, " + "GFN0xTB, GFN1xTB or GFN2xTB.\n"); + } +} + +XTBPot::~XTBPot() { + // Loader instance is process-singleton; survives every potential. + auto &xtb = eonc::get_xtb_loader(); + if (!xtb.is_loaded()) { + return; // dlopen failed at construction; nothing to free. + } + if (res) { + xtb.del_results(&res); + } + if (calc) { + xtb.del_calculator(&calc); + } + if (mol) { + xtb.del_molecule(&mol); + } + if (env) { + xtb.release_output(env); + xtb.del_environment(&env); + } + QUILL_LOG_INFO(eonc::log::get(), "[XTB] called potential {} times", + counter++); +} + +void XTBPot::cleanMemory() {} + void XTBPot::force(long N, const double *R, const int *atomicNrs, double *F, double *U, double *variance, const double *box) { variance = nullptr; + auto &xtb = eonc::get_xtb_loader(); + int intN = static_cast(N); // TODO: Periodicity shouldn't crash const bool periodicity[3]{false, false, false}; double box_bohr[3 * 3]; - // Allocate memory for converted positions + // Convert positions and lattice from Angstrom to Bohr. std::vector R_bohr(3 * N); - - // Convert positions from Angstrom to Bohr for (long idx = 0; idx < 3 * N; ++idx) { R_bohr[idx] = R[idx] / BOHR; } @@ -44,47 +119,63 @@ void XTBPot::force(long N, const double *R, const int *atomicNrs, double *F, } if (!initialized) { - // First call: Create the molecule and load the Hamiltonian - mol = xtb_newMolecule(env, &intN, atomicNrs, R_bohr.data(), &total_charge, - &uhf, box_bohr, periodicity); + mol = xtb.new_molecule(env, &intN, atomicNrs, R_bohr.data(), &total_charge, + &uhf, box_bohr, periodicity); switch (xtb_paramset) { case GFNMethod::GFNFF: - xtb_loadGFNFF(env, mol, calc, nullptr); + if (!xtb.load_gfnff) { + throw std::runtime_error( + "libxtb installed but xtb_loadGFNFF is not exported (older or " + "stripped build); pick a different paramset or upgrade xtb."); + } + xtb.load_gfnff(env, mol, calc, nullptr); break; case GFNMethod::GFN0xTB: - xtb_loadGFN0xTB(env, mol, calc, nullptr); + if (!xtb.load_gfn0) { + throw std::runtime_error( + "libxtb installed but xtb_loadGFN0xTB is not exported."); + } + xtb.load_gfn0(env, mol, calc, nullptr); break; case GFNMethod::GFN1xTB: - xtb_loadGFN1xTB(env, mol, calc, nullptr); + if (!xtb.load_gfn1) { + throw std::runtime_error( + "libxtb installed but xtb_loadGFN1xTB is not exported."); + } + xtb.load_gfn1(env, mol, calc, nullptr); break; case GFNMethod::GFN2xTB: - xtb_loadGFN2xTB(env, mol, calc, nullptr); + if (!xtb.load_gfn2) { + throw std::runtime_error( + "libxtb installed but xtb_loadGFN2xTB is not exported."); + } + xtb.load_gfn2(env, mol, calc, nullptr); break; } - xtb_setAccuracy(env, calc, xtb_acc); - xtb_setElectronicTemp(env, calc, xtb_electronic_temperature); - xtb_setMaxIter(env, calc, xtb_max_iter); + xtb.set_accuracy(env, calc, xtb_acc); + xtb.set_electronic_temp(env, calc, xtb_electronic_temperature); + xtb.set_max_iter(env, calc, static_cast(xtb_max_iter)); initialized = true; } else { - // Subsequent calls: Only update coordinates and lattice - xtb_updateMolecule(env, mol, R_bohr.data(), box_bohr); + xtb.update_molecule(env, mol, R_bohr.data(), box_bohr); } - xtb_singlepoint(env, mol, calc, res); + xtb.singlepoint(env, mol, calc, res); - // Check for SCF convergence or internal xTB errors - if (xtb_checkEnvironment(env) != 0) { - char err_msg[512]; - xtb_getError(env, err_msg, nullptr); + if (xtb.check_environment(env) != 0) { + char err_msg[512]{}; + int buf = sizeof(err_msg); + xtb.get_error(env, err_msg, &buf); throw std::runtime_error(std::string("xTB Error: ") + err_msg); } - xtb_getEnergy(env, res, U); - xtb_getGradient(env, res, F); + xtb.get_energy(env, res, U); + xtb.get_gradient(env, res, F); - // Convert Hartree/Bohr to eV/Angstrom + // Convert Hartree / Bohr -> eV / Angstrom and flip sign (xtb returns + // gradient, eOn wants force). for (long i = 0; i < 3 * N; ++i) { F[i] *= -1.0 * (HARTREE / BOHR); } diff --git a/client/potentials/XTBPot/XTBPot.h b/client/potentials/XTBPot/XTBPot.h index d9fc74bac..6daa26a42 100644 --- a/client/potentials/XTBPot/XTBPot.h +++ b/client/potentials/XTBPot/XTBPot.h @@ -13,76 +13,18 @@ #include "../../Potential.h" #include "units.hpp" -#include "xtb.h" + +#include class XTBPot final : public Potential { public: - // Functions - XTBPot(const Parameters &p) - : Potential(PotType::XTB, p), - xtb_acc{p.xtb_options.acc}, - xtb_electronic_temperature{p.xtb_options.elec_temperature}, - xtb_max_iter{p.xtb_options.maxiter}, - total_charge{p.xtb_options.charge}, - uhf{p.xtb_options.uhf} { - counter = 0; - initialized = false; - env = xtb_newEnvironment(); - xtb_setVerbosity(env, XTB_VERBOSITY_MUTED); - // Release the default output unit to prevent Fortran NEWUNIT conflicts - // when multiple XTB environments coexist (e.g. per-image NEB potentials) - xtb_releaseOutput(env); - if (!env) { - throw std::runtime_error("Failed to create xtb environment"); - } - calc = xtb_newCalculator(); - if (!calc) { - xtb_delEnvironment(&env); - throw std::runtime_error("Failed to create xtb calculator"); - } - res = xtb_newResults(); - if (!calc) { - xtb_delResults(&res); - throw std::runtime_error("Failed to create xtb results"); - } - // Unmarshal parameters - if (p.xtb_options.paramset == "GFNFF") { - xtb_paramset = GFNMethod::GFNFF; - } else if (p.xtb_options.paramset == "GFN0xTB") { - xtb_paramset = GFNMethod::GFN0xTB; - } else if (p.xtb_options.paramset == "GFN1xTB") { - xtb_paramset = GFNMethod::GFN1xTB; - } else if (p.xtb_options.paramset == "GFN2xTB") { - xtb_paramset = GFNMethod::GFN2xTB; - } else { - throw std::runtime_error("Parameter set for XTB must be one of GFNFF, " - "GFN0xTB, GFN1xTB or GFN2xTB.\n"); - } - } - - virtual ~XTBPot() { - if (res) { - xtb_delResults(&res); - } - if (calc) { - xtb_delCalculator(&calc); - } - if (mol) { - xtb_delMolecule(&mol); - } - if (env) { - xtb_releaseOutput(env); - xtb_delEnvironment(&env); - } - QUILL_LOG_INFO(eonc::log::get(), "[XTB] called potential {} times", - counter++); - } + XTBPot(const Parameters &p); + ~XTBPot() override; // Disable copy to prevent double-free of Fortran pointers XTBPot(const XTBPot &) = delete; XTBPot &operator=(const XTBPot &) = delete; - // To satisfy interface void cleanMemory(void); void force(long N, const double *R, const int *atomicNrs, double *F, @@ -101,18 +43,23 @@ class XTBPot final : public Potential { private: enum class GFNMethod { GFNFF, GFN0xTB, GFN1xTB, GFN2xTB }; - xtb_TEnvironment env = nullptr; - xtb_TCalculator calc = nullptr; - xtb_TMolecule mol = nullptr; - xtb_TResults res = nullptr; + + // Opaque libxtb handles. xtb.h declares them as + // `typedef struct _xtb_TX* xtb_TX;`. We carry void* in the loader + // world; the .cpp casts at the call sites where libxtb's types + // matter. + void *env{nullptr}; + void *calc{nullptr}; + void *mol{nullptr}; + void *res{nullptr}; GFNMethod xtb_paramset; double xtb_acc; double xtb_electronic_temperature; - size_t xtb_max_iter; - double total_charge = 0.0; - int uhf = 0; + std::size_t xtb_max_iter; + double total_charge{0.0}; + int uhf{0}; - size_t counter; - bool initialized; + std::size_t counter{0}; + bool initialized{false}; }; diff --git a/client/potentials/XTBPot/XtbLoader.cpp b/client/potentials/XTBPot/XtbLoader.cpp new file mode 100644 index 000000000..e076583d0 --- /dev/null +++ b/client/potentials/XTBPot/XtbLoader.cpp @@ -0,0 +1,111 @@ +/* +** This file is part of eOn. +** +** SPDX-License-Identifier: BSD-3-Clause +** +** Copyright (c) 2010--present, eOn Development Team +** All rights reserved. +** +** Repo: +** https://github.com/TheochemUI/eOn +*/ +#include "XtbLoader.h" + +#include + +namespace eonc { + +XtbLoader &XtbLoader::instance() { + static XtbLoader loader; + return loader; +} + +XtbLoader::XtbLoader() { +#ifdef _WIN32 + const char *names[] = {"xtb.dll", "libxtb.dll", "libxtb-6.dll", nullptr}; +#elif defined(__APPLE__) + const char *names[] = {"libxtb.dylib", "libxtb.6.dylib", nullptr}; +#else + const char *names[] = {"libxtb.so", "libxtb.so.6", nullptr}; +#endif + + m_handle = dynlib::openFirst(names); + if (!m_handle) { + return; // Not found; require_loaded() raises if a caller asks for XTB. + } + + new_environment = + dynlib::loadSym(m_handle, "xtb_newEnvironment"); + del_environment = + dynlib::loadSym(m_handle, "xtb_delEnvironment"); + check_environment = + dynlib::loadSym(m_handle, "xtb_checkEnvironment"); + get_error = dynlib::loadSym(m_handle, "xtb_getError"); + release_output = + dynlib::loadSym(m_handle, "xtb_releaseOutput"); + set_verbosity = + dynlib::loadSym(m_handle, "xtb_setVerbosity"); + + new_molecule = dynlib::loadSym(m_handle, "xtb_newMolecule"); + del_molecule = dynlib::loadSym(m_handle, "xtb_delMolecule"); + update_molecule = + dynlib::loadSym(m_handle, "xtb_updateMolecule"); + + new_calculator = + dynlib::loadSym(m_handle, "xtb_newCalculator"); + del_calculator = + dynlib::loadSym(m_handle, "xtb_delCalculator"); + load_gfnff = dynlib::loadSym(m_handle, "xtb_loadGFNFF"); + load_gfn0 = dynlib::loadSym(m_handle, "xtb_loadGFN0xTB"); + load_gfn1 = dynlib::loadSym(m_handle, "xtb_loadGFN1xTB"); + load_gfn2 = dynlib::loadSym(m_handle, "xtb_loadGFN2xTB"); + set_accuracy = dynlib::loadSym(m_handle, "xtb_setAccuracy"); + set_max_iter = dynlib::loadSym(m_handle, "xtb_setMaxIter"); + set_electronic_temp = dynlib::loadSym( + m_handle, "xtb_setElectronicTemp"); + + singlepoint = dynlib::loadSym(m_handle, "xtb_singlepoint"); + + new_results = dynlib::loadSym(m_handle, "xtb_newResults"); + del_results = dynlib::loadSym(m_handle, "xtb_delResults"); + get_energy = dynlib::loadSym(m_handle, "xtb_getEnergy"); + get_gradient = dynlib::loadSym(m_handle, "xtb_getGradient"); + + // Required minimum surface; the GFN parametrisation loaders are + // checked at use site so users can run on a libxtb missing one of + // the four (e.g. distributors stripping GFNFF). + if (!new_environment || !del_environment || !check_environment || + !get_error || !release_output || !set_verbosity || !new_molecule || + !del_molecule || !update_molecule || !new_calculator || !del_calculator || + !singlepoint || !new_results || !del_results || !get_energy || + !get_gradient || !set_accuracy || !set_max_iter || !set_electronic_temp) { + std::cerr << "[XTB] libxtb loaded but lacks required symbols\n"; + dynlib::close(m_handle); + m_handle = {}; + return; + } + + m_loaded = true; +} + +XtbLoader::~XtbLoader() { + // Intentionally do NOT dlclose at static destruction. libxtb pulls in + // the gfortran runtime, which installs its own atexit / __attribute__ + // ((destructor)) hooks that depend on stdio + global state that is + // already being torn down by the time this Meyer-singleton dtor runs. + // dlclose-then-Fortran-fini-during-process-shutdown crashes on the + // Linux GHA runner (test_xtb / test_cineb_xtb SIGSEGV after all + // assertions pass). The handle is process-lifetime; the OS reaps the + // mapping at exit, so leaking it here is the right move. +} + +void XtbLoader::require_loaded() const { + if (!m_loaded) { + throw std::runtime_error("XTB potential requested but libxtb not found.\n" + "Install via: conda install -c conda-forge xtb\n" + "Or ensure libxtb is in your library search path " + "(LD_LIBRARY_PATH / DYLD_LIBRARY_PATH / PATH)."); + } +} + +} // namespace eonc diff --git a/client/potentials/XTBPot/XtbLoader.h b/client/potentials/XTBPot/XtbLoader.h new file mode 100644 index 000000000..fb8fe03ce --- /dev/null +++ b/client/potentials/XTBPot/XtbLoader.h @@ -0,0 +1,125 @@ +/* +** This file is part of eOn. +** +** SPDX-License-Identifier: BSD-3-Clause +** +** Copyright (c) 2010--present, eOn Development Team +** All rights reserved. +** +** Repo: +** https://github.com/TheochemUI/eOn +*/ +#pragma once + +/// Runtime loader for the libxtb C library. +/// +/// Mirrors the LammpsLoader / ARTnResource pattern: dlopen at first +/// access, cache function pointers, throw via require_loaded() when +/// the user actually requests an XTB potential. This lets a single +/// eOn binary support XTB iff libxtb is on the library search path, +/// without a build-time link against tblite + xtb's transitive +/// Fortran dependency graph. +/// +/// The xtb opaque struct types (xtb_TEnvironment, xtb_TMolecule, +/// xtb_TCalculator, xtb_TResults) survive across the ABI as plain +/// pointer-to-struct. We re-typedef them as void* here so the loader +/// doesn't need xtb.h. + +#include "../../DynLib.h" + +#include + +namespace eonc { + +class XtbLoader { +public: + // Opaque pointer types. xtb.h defines them as + // `typedef struct _xtb_TX* xtb_TX;`; for the loader they're plain + // void* so we keep compile coupling to xtb.h zero. XTBPot still + // includes xtb.h and casts at the call sites. + using env_t = void *; + using mol_t = void *; + using calc_t = void *; + using res_t = void *; + + // Function pointer types -- one per xtb.h entry point used in + // XTBPot::force / XTBPot ctor / XTBPot dtor. + using new_environment_fn = env_t (*)(); + using del_environment_fn = void (*)(env_t *); + using check_environment_fn = int (*)(env_t); + using get_error_fn = void (*)(env_t, char *, const int *); + using release_output_fn = void (*)(env_t); + using set_verbosity_fn = void (*)(env_t, int); + + using new_molecule_fn = mol_t (*)(env_t, const int *, const int *, + const double *, const double *, const int *, + const double *, const bool *); + using del_molecule_fn = void (*)(mol_t *); + using update_molecule_fn = void (*)(env_t, mol_t, const double *, + const double *); + + using new_calculator_fn = calc_t (*)(); + using del_calculator_fn = void (*)(calc_t *); + using load_gfnff_fn = void (*)(env_t, mol_t, calc_t, char *); + using load_gfn0_fn = void (*)(env_t, mol_t, calc_t, char *); + using load_gfn1_fn = void (*)(env_t, mol_t, calc_t, char *); + using load_gfn2_fn = void (*)(env_t, mol_t, calc_t, char *); + using set_accuracy_fn = void (*)(env_t, calc_t, double); + using set_max_iter_fn = void (*)(env_t, calc_t, int); + using set_electronic_temp_fn = void (*)(env_t, calc_t, double); + + using singlepoint_fn = void (*)(env_t, mol_t, calc_t, res_t); + + using new_results_fn = res_t (*)(); + using del_results_fn = void (*)(res_t *); + using get_energy_fn = void (*)(env_t, res_t, double *); + using get_gradient_fn = void (*)(env_t, res_t, double *); + + /// Thread-safe singleton accessor (Meyer's pattern). + static XtbLoader &instance(); + + // Loaded function pointers; null when libxtb is missing or the + // installed copy lacks a symbol. + new_environment_fn new_environment{nullptr}; + del_environment_fn del_environment{nullptr}; + check_environment_fn check_environment{nullptr}; + get_error_fn get_error{nullptr}; + release_output_fn release_output{nullptr}; + set_verbosity_fn set_verbosity{nullptr}; + new_molecule_fn new_molecule{nullptr}; + del_molecule_fn del_molecule{nullptr}; + update_molecule_fn update_molecule{nullptr}; + new_calculator_fn new_calculator{nullptr}; + del_calculator_fn del_calculator{nullptr}; + load_gfnff_fn load_gfnff{nullptr}; + load_gfn0_fn load_gfn0{nullptr}; + load_gfn1_fn load_gfn1{nullptr}; + load_gfn2_fn load_gfn2{nullptr}; + set_accuracy_fn set_accuracy{nullptr}; + set_max_iter_fn set_max_iter{nullptr}; + set_electronic_temp_fn set_electronic_temp{nullptr}; + singlepoint_fn singlepoint{nullptr}; + new_results_fn new_results{nullptr}; + del_results_fn del_results{nullptr}; + get_energy_fn get_energy{nullptr}; + get_gradient_fn get_gradient{nullptr}; + + [[nodiscard]] bool is_loaded() const noexcept { return m_loaded; } + + /// Throws std::runtime_error if libxtb is not available. + void require_loaded() const; + + XtbLoader(const XtbLoader &) = delete; + XtbLoader &operator=(const XtbLoader &) = delete; + +private: + XtbLoader(); + ~XtbLoader(); + + bool m_loaded{false}; + dynlib::Handle m_handle{}; +}; + +inline XtbLoader &get_xtb_loader() { return XtbLoader::instance(); } + +} // namespace eonc diff --git a/client/potentials/XTBPot/meson.build b/client/potentials/XTBPot/meson.build index 9e41318f1..39ab372c9 100644 --- a/client/potentials/XTBPot/meson.build +++ b/client/potentials/XTBPot/meson.build @@ -1,5 +1,12 @@ +# XTB potential: loaded at runtime via dlopen, no compile-time dependency. +# XtbLoader mirrors LammpsLoader: opens libxtb on first instance() call, +# caches every xtb_* function pointer, throws via require_loaded() when +# the user actually constructs an XTBPot. Skipping the link-time +# dependency('xtb') means the same eonclient binary works with or +# without xtb installed in the conda env. xtb_eon = library('xtbeon', 'XTBPot.cpp', + 'XtbLoader.cpp', dependencies : _deps, cpp_args : _args, link_with : _linkto, diff --git a/client/thirdparty/.clang-tidy b/client/thirdparty/.clang-tidy new file mode 100644 index 000000000..ef9d002aa --- /dev/null +++ b/client/thirdparty/.clang-tidy @@ -0,0 +1,5 @@ +--- +# Vendored upstream code; never run our tidy ruleset over it. +Checks: '-*' +WarningsAsErrors: '' +HeaderFilterRegex: '$^' diff --git a/client/unit_tests/ARTnTest.cpp b/client/unit_tests/ARTnTest.cpp index 618147ff8..d2dbaa185 100644 --- a/client/unit_tests/ARTnTest.cpp +++ b/client/unit_tests/ARTnTest.cpp @@ -17,14 +17,15 @@ #include "ImprovedDimer.h" #include "Matter.h" #include "MinModeSaddleSearch.h" +#include "StatusTypes.h" #include "TestUtils.hpp" -#ifdef WITH_ARTN -#include "libs/ARTn/ARTnResource.h" -#endif #include "catch2/catch_amalgamated.hpp" +#include "libs/ARTn/ARTnResource.h" namespace tests { +using eonc::SaddleStatus; + static eonc::helpers::test::QuillTestLogger _quill_setup; class ARTnVsDimerFixture { @@ -90,14 +91,14 @@ TEST_CASE_METHOD(ARTnVsDimerFixture, auto dimerSearch = std::make_shared( matter_dimer, displacement, initialEnergy, params, pot); - int dimerStatus = dimerSearch->run(); + SaddleStatus dimerStatus = dimerSearch->run(); double dimerSaddleEnergy = matter_dimer->getPotentialEnergy(); double dimerEigenvalue = dimerSearch->getEigenvalue(); int dimerIters = dimerSearch->getIterationCount(); // Dimer should not crash (may converge, hit max iters, or abort on // nonnegative eigenvalue depending on the starting displacement) - REQUIRE(dimerStatus >= 0); + REQUIRE(dimerStatus >= SaddleStatus::Good); REQUIRE(std::isfinite(dimerSaddleEnergy)); INFO("Dimer: status=" << dimerStatus << " energy=" << dimerSaddleEnergy @@ -107,15 +108,15 @@ TEST_CASE_METHOD(ARTnVsDimerFixture, // --- Run ARTn saddle search --- auto artnSearch = std::make_unique(matter_artn, pot, displacement, params); - int artnStatus = artnSearch->run(); + SaddleStatus artnStatus = artnSearch->run(); double artnSaddleEnergy = matter_artn->getPotentialEnergy(); double artnEigenvalue = artnSearch->getEigenvalue(); int artnIters = artnSearch->getIterationCount(); // ARTn should not crash (0=good, 5=max_iterations, 22=artn_error) - REQUIRE((artnStatus == ARTnSaddleSearch::STATUS_GOOD || - artnStatus == ARTnSaddleSearch::STATUS_BAD_MAX_ITERATIONS || - artnStatus == ARTnSaddleSearch::STATUS_BAD_ARTN_ERROR)); + REQUIRE((artnStatus == SaddleStatus::Good || + artnStatus == SaddleStatus::BadMaxIterations || + artnStatus == SaddleStatus::BadArtnError)); REQUIRE(std::isfinite(artnSaddleEnergy)); INFO("ARTn: status=" << artnStatus << " energy=" << artnSaddleEnergy @@ -130,10 +131,9 @@ TEST_CASE_METHOD(ARTnVsDimerFixture, // 50% relative-energy tolerance below is intentionally generous to avoid // flaky failures when that happens; saddle structural identity is checked // by the dedicated IRA-based comparison tests. - if (dimerStatus == MinModeSaddleSearch::STATUS_GOOD && - artnStatus == ARTnSaddleSearch::STATUS_GOOD) { + if (dimerStatus == SaddleStatus::Good && artnStatus == SaddleStatus::Good) { // Both found a first-order saddle: negative eigenvalue - REQUIRE(dimerStatus == MinModeSaddleSearch::STATUS_GOOD); + REQUIRE(dimerStatus == SaddleStatus::Good); REQUIRE(dimerEigenvalue < 0.0); REQUIRE(artnEigenvalue < 0.0); @@ -151,9 +151,9 @@ TEST_CASE_METHOD(ARTnVsDimerFixture, SKIP("libartn not available at runtime"); auto artnSearch = std::make_unique(matter_artn, pot, displacement, params); - int status = artnSearch->run(); + SaddleStatus status = artnSearch->run(); - if (status == 0) { + if (status == SaddleStatus::Good) { // Converged: must have negative eigenvalue (first-order saddle) REQUIRE(artnSearch->getEigenvalue() < 0.0); @@ -177,11 +177,12 @@ TEST_CASE_METHOD(ARTnVsDimerFixture, // Empty filin is the default -- pARTn keeps its NAN_STR sentinel and reads // nothing. Setting filin to a path that does not exist has to trip the // eager existence check in ARTnSaddleSearch::run(), before setup_artn gets - // a chance to surface its own ERR_FILE, and return STATUS_BAD_ARTN_ERROR. + // a chance to surface its own ERR_FILE, and return + // SaddleStatus::BadArtnError. params.artn_options.filin = "this_artn_input_does_not_exist.in"; auto artnSearch = std::make_unique(matter_artn, pot, displacement, params); - REQUIRE(artnSearch->run() == ARTnSaddleSearch::STATUS_BAD_ARTN_ERROR); + REQUIRE(artnSearch->run() == SaddleStatus::BadArtnError); REQUIRE(artnSearch->getForceCalls() == 0); } diff --git a/client/unit_tests/CINEBXTBTest.cpp b/client/unit_tests/CINEBXTBTest.cpp index b1dd77b89..0f544fc9b 100644 --- a/client/unit_tests/CINEBXTBTest.cpp +++ b/client/unit_tests/CINEBXTBTest.cpp @@ -10,6 +10,7 @@ ** https://github.com/TheochemUI/eOn */ +#include "../potentials/XTBPot/XtbLoader.h" #include "NudgedElasticBand.h" #include "TestUtils.hpp" #include "catch2/catch_amalgamated.hpp" @@ -24,6 +25,8 @@ static eonc::helpers::test::QuillTestLogger _quill_setup; // causing the NEB to diverge from the first step (issue introduced in // 6e8461c3). TEST_CASE("CI-NEB XTB regression", "[neb][xtb]") { + if (!eonc::get_xtb_loader().is_loaded()) + SKIP("libxtb not available at runtime"); Parameters params; params.potential_options.potential = PotType::XTB; params.xtb_options.paramset = "GFN2xTB"; diff --git a/client/unit_tests/IRATest.cpp b/client/unit_tests/IRATest.cpp index d2306816b..88a62003b 100644 --- a/client/unit_tests/IRATest.cpp +++ b/client/unit_tests/IRATest.cpp @@ -11,12 +11,15 @@ */ /// Integration tests for the IRA structure comparison library. -/// Requires -Dwith_ira=true and a working libira. +/// IRAResource is always compiled in; libira.so is dlopen'd at first +/// require_loaded(). Tests SKIP when libira is not on +/// LD_LIBRARY_PATH at runtime. #include "IRACompare.h" #include "Matter.h" #include "TestUtils.hpp" #include "catch2/catch_amalgamated.hpp" +#include "libs/IRA/IRAResource.h" namespace tests { @@ -46,6 +49,8 @@ class IRAFixture { TEST_CASE_METHOD(IRAFixture, "IRA match of identical structures returns zero distance", "[ira][match]") { + if (!eonc::get_ira_resource().is_loaded()) + SKIP("libira not available at runtime"); auto result = eonc::IRACompare::match(*m1, *m2, 1.0); REQUIRE(result.error == 0); @@ -58,6 +63,8 @@ TEST_CASE_METHOD(IRAFixture, TEST_CASE_METHOD(IRAFixture, "IRA match of translated structure recovers translation", "[ira][match]") { + if (!eonc::get_ira_resource().is_loaded()) + SKIP("libira not available at runtime"); // Translate m2 by a known vector Eigen::Vector3d shift(1.5, -0.7, 0.3); auto pos = m2->getPositions(); @@ -76,6 +83,8 @@ TEST_CASE_METHOD(IRAFixture, TEST_CASE_METHOD(IRAFixture, "IRA match of permuted structure finds permutation", "[ira][match]") { + if (!eonc::get_ira_resource().is_loaded()) + SKIP("libira not available at runtime"); // Swap atoms 0 and 1 in m2 auto pos = m2->getPositions(); auto row0 = pos.row(0).eval(); @@ -95,6 +104,8 @@ TEST_CASE_METHOD(IRAFixture, TEST_CASE_METHOD(IRAFixture, "IRA match of different structures returns nonzero distance", "[ira][match]") { + if (!eonc::get_ira_resource().is_loaded()) + SKIP("libira not available at runtime"); // Displace atom 0 significantly auto pos = m2->getPositions(); pos(0, 0) += 3.0; @@ -109,6 +120,8 @@ TEST_CASE_METHOD(IRAFixture, TEST_CASE_METHOD(IRAFixture, "IRA findSymmetry returns valid point group", "[ira][symmetry]") { + if (!eonc::get_ira_resource().is_loaded()) + SKIP("libira not available at runtime"); auto result = eonc::IRACompare::findSymmetry(*m1, 0.1); REQUIRE(result.error == 0); diff --git a/client/unit_tests/JobIntegrationTest.cpp b/client/unit_tests/JobIntegrationTest.cpp index 928101228..a6e6f7030 100644 --- a/client/unit_tests/JobIntegrationTest.cpp +++ b/client/unit_tests/JobIntegrationTest.cpp @@ -15,15 +15,14 @@ /// INI, running the job, and verifying outputs match SVN reference /// values. +#include "ARTnSaddleSearch.h" #include "Job.h" #include "Parameters.h" #include "PotRegistry.h" +#include "StatusTypes.h" #include "TestUtils.hpp" #include "catch2/catch_amalgamated.hpp" -#ifdef WITH_ARTN -#include "ARTnSaddleSearch.h" #include "libs/ARTn/ARTnResource.h" -#endif #include #include @@ -31,6 +30,8 @@ namespace tests { +using eonc::SaddleStatus; + static eonc::helpers::test::QuillTestLogger _quill_setup; /// Parse a results.dat file into key-value pairs. @@ -347,8 +348,9 @@ max_energy = 10.0 REQUIRE(results.count("termination_reason") > 0); // SVN reference (data/reference/saddle_search_morse_pt.dat): // 0 termination_reason (converged) - int status = std::stoi(results["termination_reason"]); - REQUIRE(status == 0); + SaddleStatus status = + static_cast(std::stoi(results["termination_reason"])); + REQUIRE(status == SaddleStatus::Good); // Energy must match SVN exactly double energy = std::stod(results["potential_energy_saddle"]); @@ -395,8 +397,9 @@ max_energy = 10.0 auto results = runJob(); // SVN reference (data/reference/saddle_search_lanczos_morse_pt.dat): - int status = std::stoi(results["termination_reason"]); - REQUIRE(status == 0); + SaddleStatus status = + static_cast(std::stoi(results["termination_reason"])); + REQUIRE(status == SaddleStatus::Good); double energy = std::stod(results["potential_energy_saddle"]); REQUIRE(energy == Catch::Approx(-1462.008706).epsilon(1e-4)); @@ -439,8 +442,9 @@ max_move = 0.2 // SVN reference (data/reference/neb_lj.dat): // termination_reason = 0 (converged) REQUIRE(results.count("termination_reason") > 0); - int status = std::stoi(results["termination_reason"]); - REQUIRE(status == 0); + SaddleStatus status = + static_cast(std::stoi(results["termination_reason"])); + REQUIRE(status == SaddleStatus::Good); int nImages = std::stoi(results["number_of_images"]); REQUIRE(nImages == 3); @@ -533,8 +537,9 @@ max_iterations = 200 auto results = runJob(); REQUIRE(results.count("termination_reason") > 0); - int status = std::stoi(results["termination_reason"]); - REQUIRE(status == 0); // GOOD + SaddleStatus status = + static_cast(std::stoi(results["termination_reason"])); + REQUIRE(status == SaddleStatus::Good); // SVN reference (data/reference/minimization_morse_pt.dat): // Energy must be exact @@ -995,8 +1000,9 @@ max_energy = 10.0 // SVN reference (data/reference/process_search_morse_pt.dat): REQUIRE(results.count("termination_reason") > 0); - int status = std::stoi(results["termination_reason"]); - REQUIRE(status == 0); + SaddleStatus status = + static_cast(std::stoi(results["termination_reason"])); + REQUIRE(status == SaddleStatus::Good); // Force calls must be <= SVN (67) REQUIRE(forceCalls_ <= 67); @@ -1016,7 +1022,6 @@ max_energy = 10.0 REQUIRE(barrier == Catch::Approx(0.158077).epsilon(1e-3)); } -#ifdef WITH_ARTN TEST_CASE_METHOD(JobIntegrationFixture, "SaddleSearchJob ARTn converges on Morse Pt", "[job][saddle_search][artn][integration]") { @@ -1052,17 +1057,18 @@ max_iterations = 500 auto results = runJob(); REQUIRE(results.count("termination_reason") > 0); - int status = std::stoi(results["termination_reason"]); + SaddleStatus status = + static_cast(std::stoi(results["termination_reason"])); // ARTn may not converge, but should not crash - REQUIRE((status == ARTnSaddleSearch::STATUS_GOOD || - status == ARTnSaddleSearch::STATUS_BAD_MAX_ITERATIONS || - status == ARTnSaddleSearch::STATUS_BAD_ARTN_ERROR)); + REQUIRE((status == SaddleStatus::Good || + status == SaddleStatus::BadMaxIterations || + status == SaddleStatus::BadArtnError)); // Energy must be finite double energy = std::stod(results["potential_energy_saddle"]); REQUIRE(std::isfinite(energy)); - if (status == ARTnSaddleSearch::STATUS_GOOD) { + if (status == SaddleStatus::Good) { // Eigenvalue must be negative for a converged first-order saddle double eigenvalue = std::stod(results["final_eigenvalue"]); REQUIRE(eigenvalue < 0.0); @@ -1119,10 +1125,11 @@ max_spawns = 5 auto results = runJob(); REQUIRE(results.count("termination_reason") > 0); - int status = std::stoi(results["termination_reason"]); - REQUIRE((status == ARTnSaddleSearch::STATUS_GOOD || - status == ARTnSaddleSearch::STATUS_BAD_MAX_ITERATIONS || - status == ARTnSaddleSearch::STATUS_BAD_ARTN_ERROR)); + SaddleStatus status = + static_cast(std::stoi(results["termination_reason"])); + REQUIRE((status == SaddleStatus::Good || + status == SaddleStatus::BadMaxIterations || + status == SaddleStatus::BadArtnError)); // Force calls must be reasonable REQUIRE(forceCalls_ > 0); @@ -1144,6 +1151,8 @@ max_spawns = 5 TEST_CASE_METHOD(JobIntegrationFixture, "SaddleSearchJob standalone ARTn works without direction file", "[job][saddle_search][artn][optional-mode][integration]") { + if (!eonc::get_artn_resource().is_loaded()) + SKIP("libartn not available at runtime"); copyTestData("../saddle_search"); std::filesystem::remove(workdir / "direction.dat"); std::filesystem::remove(workdir / "displacement.con"); @@ -1168,17 +1177,21 @@ max_iterations = 5 auto results = runJob(); REQUIRE(results.count("termination_reason") > 0); - int status = std::stoi(results["termination_reason"]); - REQUIRE((status == ARTnSaddleSearch::STATUS_GOOD || - status == ARTnSaddleSearch::STATUS_BAD_MAX_ITERATIONS || - status == ARTnSaddleSearch::STATUS_BAD_ARTN_ERROR)); + SaddleStatus status = + static_cast(std::stoi(results["termination_reason"])); + REQUIRE((status == SaddleStatus::Good || + status == SaddleStatus::BadMaxIterations || + status == SaddleStatus::BadArtnError)); } -#endif // WITH_ARTN - -#ifndef WITH_ARTN +// Runtime-missing-libartn rejection tests. ARTnResource is always +// compiled in; the failure path now triggers when libartn.so is not +// found at first dlopen. These tests skip when libartn is loaded so +// they only run on builds without the library on LD_LIBRARY_PATH. TEST_CASE_METHOD(JobIntegrationFixture, - "SaddleSearchJob rejects standalone ARTn when not compiled", + "SaddleSearchJob rejects standalone ARTn when libartn missing", "[job][saddle_search][artn][config][integration]") { + if (eonc::get_artn_resource().is_loaded()) + SKIP("libartn loaded; runtime-missing path not exercised"); copyTestData("../saddle_search"); writeConfig(R"( [Main] @@ -1192,14 +1205,15 @@ method = artn )"); REQUIRE_THROWS_WITH( - runJob(), - Catch::Matchers::ContainsSubstring( - "saddle_search.method=artn requires a build with ARTn support")); + runJob(), Catch::Matchers::ContainsSubstring( + "saddle_search.method=artn requires libartn at runtime")); } TEST_CASE_METHOD(JobIntegrationFixture, - "SaddleSearchJob rejects ARTn min-mode when not compiled", + "SaddleSearchJob rejects ARTn min-mode when libartn missing", "[job][saddle_search][artn][min_mode][config][integration]") { + if (eonc::get_artn_resource().is_loaded()) + SKIP("libartn loaded; runtime-missing path not exercised"); copyTestData("../saddle_search"); writeConfig(R"( [Main] @@ -1215,12 +1229,15 @@ min_mode_method = artn REQUIRE_THROWS_WITH(runJob(), Catch::Matchers::ContainsSubstring( "saddle_search.minmode_method=artn " - "requires a build with ARTn support")); + "requires libartn at runtime")); } -TEST_CASE_METHOD(JobIntegrationFixture, - "ProcessSearchJob rejects standalone ARTn when not compiled", - "[job][process_search][artn][config][integration]") { +TEST_CASE_METHOD( + JobIntegrationFixture, + "ProcessSearchJob rejects standalone ARTn when libartn missing", + "[job][process_search][artn][config][integration]") { + if (eonc::get_artn_resource().is_loaded()) + SKIP("libartn loaded; runtime-missing path not exercised"); copyTestData("../saddle_search"); writeConfig(R"( [Main] @@ -1234,11 +1251,9 @@ method = artn )"); REQUIRE_THROWS_WITH( - runJob(), - Catch::Matchers::ContainsSubstring( - "saddle_search.method=artn requires a build with ARTn support")); + runJob(), Catch::Matchers::ContainsSubstring( + "saddle_search.method=artn requires libartn at runtime")); } -#endif TEST_CASE_METHOD(JobIntegrationFixture, "SaddleSearchJob ARTn parameters parsed correctly", diff --git a/client/unit_tests/OptimizerTest.cpp b/client/unit_tests/OptimizerTest.cpp index a1ee543a0..b4194e544 100644 --- a/client/unit_tests/OptimizerTest.cpp +++ b/client/unit_tests/OptimizerTest.cpp @@ -17,12 +17,15 @@ #include "ObjectiveFunction.h" #include "Parameters.h" #include "Quickmin.h" +#include "StatusTypes.h" #include "SteepestDescent.h" #include "TestUtils.hpp" #include "catch2/catch_amalgamated.hpp" namespace tests { +using eonc::StepResult; + static eonc::helpers::test::QuillTestLogger _quill_setup; /// Quadratic objective function: f(x) = 0.5 * (x[0]^2 + x[1]^2) @@ -92,11 +95,11 @@ TEST_CASE("FIRE optimizer converges on quadratic", "[optimizer][fire]") { objf->setPositions(start); FIRE opt(objf, params); - int status = opt.run(1000, params.optimizer_options.max_move); + StepResult status = opt.run(1000, params.optimizer_options.max_move); auto final_pos = objf->getPositions(); REQUIRE(final_pos.norm() < 1e-4); - CHECK(status == 1); // converged + CHECK(status == StepResult::Converged); } TEST_CASE("LBFGS optimizer converges on quadratic", "[optimizer][lbfgs]") { @@ -107,11 +110,11 @@ TEST_CASE("LBFGS optimizer converges on quadratic", "[optimizer][lbfgs]") { objf->setPositions(start); LBFGS opt(objf, params); - int status = opt.run(1000, params.optimizer_options.max_move); + StepResult status = opt.run(1000, params.optimizer_options.max_move); auto final_pos = objf->getPositions(); REQUIRE(final_pos.norm() < 0.01); - CHECK(status == 1); + CHECK(status == StepResult::Converged); } TEST_CASE("CG optimizer converges on quadratic", "[optimizer][cg]") { @@ -123,11 +126,11 @@ TEST_CASE("CG optimizer converges on quadratic", "[optimizer][cg]") { objf->setPositions(start); ConjugateGradients opt(objf, params); - int status = opt.run(5000, params.optimizer_options.max_move); + StepResult status = opt.run(5000, params.optimizer_options.max_move); auto final_pos = objf->getPositions(); REQUIRE(final_pos.norm() < 0.01); - CHECK(status == 1); + CHECK(status == StepResult::Converged); } TEST_CASE("CG with line search converges on quadratic", @@ -196,11 +199,11 @@ TEST_CASE("SteepestDescent optimizer converges on quadratic", objf->setPositions(start); SteepestDescent opt(objf, params); - int status = opt.run(5000, params.optimizer_options.max_move); + StepResult status = opt.run(5000, params.optimizer_options.max_move); auto final_pos = objf->getPositions(); REQUIRE(final_pos.norm() < 0.01); - CHECK(status == 1); + CHECK(status == StepResult::Converged); } } /* namespace tests */ diff --git a/client/unit_tests/SaddleSearchTest.cpp b/client/unit_tests/SaddleSearchTest.cpp index 334ef6533..d1ad7b87a 100644 --- a/client/unit_tests/SaddleSearchTest.cpp +++ b/client/unit_tests/SaddleSearchTest.cpp @@ -13,11 +13,14 @@ #include "Matter.h" #include "MinModeSaddleSearch.h" #include "Parameters.h" +#include "StatusTypes.h" #include "TestUtils.hpp" #include "catch2/catch_amalgamated.hpp" namespace tests { +using eonc::SaddleStatus; + static eonc::helpers::test::QuillTestLogger _quill_setup; class SaddleSearchFixture { @@ -66,11 +69,11 @@ TEST_CASE_METHOD(SaddleSearchFixture, double reactantEnergy = matter->getPotentialEnergy(); MinModeSaddleSearch search(matter, mode, reactantEnergy, params, pot); - int status = search.run(); + SaddleStatus status = search.run(); // Status should be a valid enum value (0 through 21) - REQUIRE(status >= MinModeSaddleSearch::STATUS_GOOD); - REQUIRE(status <= MinModeSaddleSearch::STATUS_DIMER_RESTORED_BEST); + REQUIRE(status >= SaddleStatus::Good); + REQUIRE(status <= SaddleStatus::DimerRestoredBest); } TEST_CASE_METHOD(SaddleSearchFixture, @@ -102,10 +105,10 @@ TEST_CASE_METHOD(SaddleSearchFixture, double reactantEnergy = matter->getPotentialEnergy(); MinModeSaddleSearch search(matter, mode, reactantEnergy, params, pot); - int status = search.run(); + SaddleStatus status = search.run(); // Should hit max iterations or some non-GOOD status - REQUIRE(status != MinModeSaddleSearch::STATUS_GOOD); + REQUIRE(status != SaddleStatus::Good); } TEST_CASE_METHOD(SaddleSearchFixture, @@ -145,10 +148,10 @@ TEST_CASE_METHOD(SaddleSearchFixture, double reactantEnergy = matter->getPotentialEnergy(); MinModeSaddleSearch search(matter, mode, reactantEnergy, params, pot); - int status = search.run(); + SaddleStatus status = search.run(); - REQUIRE(status >= MinModeSaddleSearch::STATUS_GOOD); - REQUIRE(status <= MinModeSaddleSearch::STATUS_DIMER_RESTORED_BEST); + REQUIRE(status >= SaddleStatus::Good); + REQUIRE(status <= SaddleStatus::DimerRestoredBest); REQUIRE(std::isfinite(search.getEigenvalue())); } @@ -163,9 +166,9 @@ TEST_CASE_METHOD(SaddleSearchFixture, "MinModeSaddleSearch with classic Dimer", double reactantEnergy = matter->getPotentialEnergy(); MinModeSaddleSearch search(matter, mode, reactantEnergy, params, pot); - int status = search.run(); + SaddleStatus status = search.run(); - REQUIRE(status >= MinModeSaddleSearch::STATUS_GOOD); + REQUIRE(status >= SaddleStatus::Good); REQUIRE(std::isfinite(search.getEigenvalue())); } diff --git a/client/unit_tests/XTBTest.cpp b/client/unit_tests/XTBTest.cpp index 2f0f1329a..64b255af8 100644 --- a/client/unit_tests/XTBTest.cpp +++ b/client/unit_tests/XTBTest.cpp @@ -1,4 +1,5 @@ #include "../MatrixHelpers.hpp" +#include "../potentials/XTBPot/XtbLoader.h" #include "Matter.h" #include "TestUtils.hpp" #include "catch2/catch_amalgamated.hpp" @@ -37,6 +38,8 @@ class PotTest { }; TEST_CASE_METHOD(PotTest, "XTB", "[PotTest]") { + if (!eonc::get_xtb_loader().is_loaded()) + SKIP("libxtb not available at runtime"); SetUp(); auto matEq = std::bind(eonc::helpers::eigenEquality, _1, _2, threshold); diff --git a/docs/newsfragments/338-artn-ira.changed.md b/docs/newsfragments/338-artn-ira.changed.md new file mode 100644 index 000000000..25af54ab2 --- /dev/null +++ b/docs/newsfragments/338-artn-ira.changed.md @@ -0,0 +1 @@ +ARTn (`pARTn`) and IRA are now always compiled and dlopen libartn.so / libira.so at runtime, mirroring LAMMPS. The `with_artn` / `with_ira` meson options + `-DWITH_ARTN` / `-DWITH_IRA` defines + configure-time cmake build of `subprojects/artn-plugin` and `subprojects/ira` are gone. Users install the .so via the same channels they already use for liblammps.so. diff --git a/docs/newsfragments/338-catch2-skip.fixed.md b/docs/newsfragments/338-catch2-skip.fixed.md new file mode 100644 index 000000000..921d76eb6 --- /dev/null +++ b/docs/newsfragments/338-catch2-skip.fixed.md @@ -0,0 +1 @@ +Pass `--allow-running-no-tests` to every Catch2 unit test so an all-skipped run (e.g. `test_xtb` / `test_artn` / `test_ira` / `test_cineb_xtb` on a system without the matching .so on `LD_LIBRARY_PATH`) returns exit 0 instead of Catch2's default `exit 4` for "no tests / assertions executed". Lets the macOS CI matrix keep running where libxtb / libartn / libira aren't installed. diff --git a/docs/newsfragments/338-ci-sanitizers.added.md b/docs/newsfragments/338-ci-sanitizers.added.md new file mode 100644 index 000000000..a9b25368c --- /dev/null +++ b/docs/newsfragments/338-ci-sanitizers.added.md @@ -0,0 +1 @@ +Add `ci_sanitizers.yml` workflow that builds with `b_sanitize=address,undefined` and runs the test suite under ASan + UBSan. Catches use-after-free / out-of-bounds / signed-overflow / null-deref regressions in the `LAMMPSBundle` extract path, the `MetatomicPotential` neighbor list code, and the `LBFGS` / `CG` minimizer paths. diff --git a/docs/newsfragments/338-clang-tidy.added.md b/docs/newsfragments/338-clang-tidy.added.md new file mode 100644 index 000000000..3014b6c21 --- /dev/null +++ b/docs/newsfragments/338-clang-tidy.added.md @@ -0,0 +1 @@ +Add a baseline `.clang-tidy` ruleset covering `bugprone-*`, `cert-*`, `modernize-use-{nullptr,override,equals-default}`, `performance-*`, and selected `readability-*` checks. `client/thirdparty/.clang-tidy` opts the vendored tree out via `Checks: '-*'`. Picks up where `-Wall` leaves off without the false-positive flood from `cppcoreguidelines-pro-bounds-*` or `cppcoreguidelines-avoid-magic-numbers`. diff --git a/docs/newsfragments/338-features-flag.added.md b/docs/newsfragments/338-features-flag.added.md new file mode 100644 index 000000000..2bee1ee6e --- /dev/null +++ b/docs/newsfragments/338-features-flag.added.md @@ -0,0 +1 @@ +Document the existing `eonclient --features` flag in the install guide (closes #225). The flag was already wired through `CommandLine.cpp` -> `printFeatures()` -> the `FEATURES_STRING` populated from `client/meson.build` features_list at configure time; this commit adds the user-facing reference so `eonclient --features` is discoverable alongside `--version`. diff --git a/docs/newsfragments/338-flexiblas.added.md b/docs/newsfragments/338-flexiblas.added.md new file mode 100644 index 000000000..a075d606d --- /dev/null +++ b/docs/newsfragments/338-flexiblas.added.md @@ -0,0 +1 @@ +Add `with_flexiblas` (meson) / `WITH_FLEXIBLAS` (cmake) -- link Eigen via [FlexiBLAS](https://github.com/mpimd-csc/flexiblas) for runtime-pluggable BLAS / LAPACK. Same FlexiBLAS-of-MPI pattern this PR uses for MPItrampoline and the dlopen-based LAMMPS / ARTn / IRA / XTB loaders: link once, swap the backend (OpenBLAS, Intel MKL, BLIS, ATLAS, ARMPL, ...) at run time via the `FLEXIBLAS` env var. Mutually exclusive with the legacy `use_mkl` / `USE_MKL` direct-MKL link (FlexiBLAS routes to MKL via `FLEXIBLAS=INTELMKL` at run time). `subprojects/flexiblas.wrap` and `FetchContent_Declare(flexiblas)` pin upstream v3.5.0 so neither build system can ever fail with "no FlexiBLAS". diff --git a/docs/newsfragments/338-lammps-bundle.added.md b/docs/newsfragments/338-lammps-bundle.added.md new file mode 100644 index 000000000..246c4048d --- /dev/null +++ b/docs/newsfragments/338-lammps-bundle.added.md @@ -0,0 +1 @@ +Add a portable LAMMPS run-input bundle (`.eonlpb`) that packs `in.lammps` and every file the run needs (pair-coeff data, custom `.so` plugins, KIM tables, etc.) into one blob. `[Potential] lammps_bundle = path/to/foo.eonlpb` makes `LAMMPSPot` extract to a per-instance scratch dir and chdir liblammps there, so the eonclient CWD no longer matters for LAMMPS file lookups. diff --git a/docs/newsfragments/338-lammps-fail-loud.fixed.md b/docs/newsfragments/338-lammps-fail-loud.fixed.md new file mode 100644 index 000000000..67bd31ac7 --- /dev/null +++ b/docs/newsfragments/338-lammps-fail-loud.fixed.md @@ -0,0 +1 @@ +`LAMMPSPot` now fails loudly with the eonclient CWD when `in.lammps` is missing instead of letting liblammps swallow the error and segfault on the next `force()` call (issue eon-7qqp). `lammps_has_error` / `lammps_get_last_error_message` are loaded if liblammps exposes them and surface LAMMPS-side syntax / pair-coeff errors immediately after `lammps_file`. diff --git a/docs/newsfragments/338-lbfgs-eps-constexpr.changed.md b/docs/newsfragments/338-lbfgs-eps-constexpr.changed.md new file mode 100644 index 000000000..eab67d903 --- /dev/null +++ b/docs/newsfragments/338-lbfgs-eps-constexpr.changed.md @@ -0,0 +1 @@ +Convert the `#define LBFGS_EPS 1e-30` macro to a typed `inline constexpr double` and document its role as the curvature-update gate (the threshold below which `s_0 . y_0` is too small to invert into the L-BFGS history without amplifying denormals). Brings it in line with the `ARTN_MODE_TOLERANCE` / `ARTN_SMALL_DISPLACEMENT` constexpr style already used in `ARTnSaddleSearch.h`. diff --git a/docs/newsfragments/338-link-libdl.fixed.md b/docs/newsfragments/338-link-libdl.fixed.md new file mode 100644 index 000000000..7eb6c3b5d --- /dev/null +++ b/docs/newsfragments/338-link-libdl.fixed.md @@ -0,0 +1 @@ +Hoist `dl_dep = cppc.find_library('dl', required: false)` to file scope in `client/meson.build` so every loader-using target (LAMMPS, XTB, ARTn shim, IRA shim) links `-ldl` automatically. Pre-fix CI failed at link with `undefined reference to dlopen / dlsym / dlclose` for libxtbeon.so on Linux because only `potentials/LAMMPS/meson.build` had a local `dl_dep` and the new XtbLoader / ARTn / IRA paths inherited `_deps` without it. diff --git a/docs/newsfragments/338-meson-sourceset.changed.md b/docs/newsfragments/338-meson-sourceset.changed.md new file mode 100644 index 000000000..c38f68471 --- /dev/null +++ b/docs/newsfragments/338-meson-sourceset.changed.md @@ -0,0 +1 @@ +Cleanup #177 -- replace the historical chain of `eonclib_sources +=` accretions in `client/meson.build` with a single [`sourceset`](https://mesonbuild.com/Sourceset-module.html) driven by a `configuration_data` of feature flags (`WITH_GPRD`, `WITH_GP_SURROGATE`, `WITH_SERVE_MODE`). Source membership for the eonclib library now lives in one place; feature blocks keep their side-effecting parts (subproject(), find_library(), `-DWITH_X` args, _deps additions) but no longer reach into the source list. Build output is byte-identical to the pre-cleanup chain. diff --git a/docs/newsfragments/338-metatomic-pch.changed.md b/docs/newsfragments/338-metatomic-pch.changed.md new file mode 100644 index 000000000..f91e00238 --- /dev/null +++ b/docs/newsfragments/338-metatomic-pch.changed.md @@ -0,0 +1 @@ +Add `MetatomicPotential.cpp` PCH (`pch/metatomic_pch.h`) covering the `torch/script.h` + `torch/cuda.h` + `torch/mps.h` + `metatensor/torch` + `metatomic/torch` header chain. Cuts cold compile time of the heaviest TU in the project from minutes to seconds. diff --git a/docs/newsfragments/338-metatomic-uncertainty-noisy.fixed.md b/docs/newsfragments/338-metatomic-uncertainty-noisy.fixed.md new file mode 100644 index 000000000..642c5b8eb --- /dev/null +++ b/docs/newsfragments/338-metatomic-uncertainty-noisy.fixed.md @@ -0,0 +1 @@ +Stop spamming `c10::Error` capture stacks to stderr when a metatomic model has no `energy_uncertainty` output (closes #309). Pre-fix `MetatomicPotential` ctor called `metatomic_torch::pick_output("energy_uncertainty", ...)` blind and let the catch block disable uncertainty checks; `c10::Error` captures + prints its construction stack on `what()`/log of any subsequent reraise, so loading an uncertainty-less model surfaced a multi-frame backtrace that looked like a real crash. Pre-check `outputs.contains(...)` for both candidate keys (`::energy_uncertainty` and plain `energy_uncertainty`) so the throw path is never entered when the head is just missing. diff --git a/docs/newsfragments/338-min-nan-bailout.fixed.md b/docs/newsfragments/338-min-nan-bailout.fixed.md new file mode 100644 index 000000000..ae5f50bb7 --- /dev/null +++ b/docs/newsfragments/338-min-nan-bailout.fixed.md @@ -0,0 +1 @@ +LBFGS and Conjugate-Gradient minimizers no longer hang when the underlying potential returns NaN forces (issue eon-7416). Both `step()` paths check `getGradient().allFinite()` before consuming the gradient and return `-1` immediately on a non-finite element; the `run()` loops propagate the failure and re-check after the loop in case `isConverged()` swallowed a NaN comparison. Pre-fix symptom: post-saddle Min1/Min2 prints `[Matter] 0 0.00000e+00 -nan -0.00000` and the eonclient process spins until SLURM kills it because every retry of the line search produced NaN and `NaN < threshold` silently evaluated to false. diff --git a/docs/newsfragments/338-mpi-c-bindings.fixed.md b/docs/newsfragments/338-mpi-c-bindings.fixed.md new file mode 100644 index 000000000..112678727 --- /dev/null +++ b/docs/newsfragments/338-mpi-c-bindings.fixed.md @@ -0,0 +1 @@ +Port `MPIPot.cpp` and the MPI control flow in `ClientEON.cpp` off the removed MPI C++ bindings (`MPI::COMM_WORLD`, `MPI::INT`, `MPI::DOUBLE`, `MPI::Group`, ...). Every call uses the spec-compliant C bindings now (`MPI_Send`, `MPI_Recv`, `MPI_Iprobe`, `MPI_Comm_*`, `MPI_Group_*`, ...), so `-Dwith_mpi=true` builds against modern conda-forge MPICH 4.x and OpenMPI 5.x even though they ship without `mpicxx.h`. Wire format on the MPIPot Send/Recv channel is preserved by switching the local `icwd` buffer from `long[1024]` to `int[1024]` (the original code tagged the message `MPI::INT` while passing a long array, and any deployed MPI server already parses 1024 ints). diff --git a/docs/newsfragments/338-mpi-trampoline.added.md b/docs/newsfragments/338-mpi-trampoline.added.md new file mode 100644 index 000000000..d72586d7d --- /dev/null +++ b/docs/newsfragments/338-mpi-trampoline.added.md @@ -0,0 +1 @@ +`-Dwith_mpi=true` builds now prefer [MPItrampoline](https://github.com/eschnett/MPItrampoline) over a direct system MPI link, mirroring the runtime-load pattern used by LAMMPS / ARTn / IRA / XTB in this release. MPItrampoline is the FlexiBLAS-of-MPI: it ships a stable wrapper ABI, and one eonclient binary forwards every `MPI_*` call to any spec-compliant libmpi at run time, picked via the `MPITRAMPOLINE_LIB` env var. The meson detection order is `dependency('MPItrampoline', method: 'cmake')` -> `dependency('mpi-c')` -> `dependency('mpi')` (last-resort direct link). `subprojects/mpitrampoline.wrap` pins upstream v5.5.1 for local builds. diff --git a/docs/newsfragments/338-nodiscard-thread-doc.changed.md b/docs/newsfragments/338-nodiscard-thread-doc.changed.md new file mode 100644 index 000000000..3a9b814f0 --- /dev/null +++ b/docs/newsfragments/338-nodiscard-thread-doc.changed.md @@ -0,0 +1 @@ +Mark the `DynLib.h` lookup helpers (`open`, `sym`, `openFirst`, `error`, `loadSym<>`) `[[nodiscard]]` -- callers must inspect the returned handle / pointer / error string. Document the threading contract on `LAMMPSPot`: not safe to share across threads because `std::filesystem::current_path()` and the `shell cd` LAMMPS command both touch process-global state. diff --git a/docs/newsfragments/338-py-status-codes.changed.md b/docs/newsfragments/338-py-status-codes.changed.md new file mode 100644 index 000000000..1b7d2e107 --- /dev/null +++ b/docs/newsfragments/338-py-status-codes.changed.md @@ -0,0 +1,11 @@ +The Python orchestrator now reads termination codes through +``eon.status_codes.SaddleStatus`` / ``MinimizationStatus`` +(``IntEnum`` mirrors of ``client/StatusTypes.h``) instead of bare +integer literals. The wire format is unchanged -- ``results.dat`` +still carries integer ``termination_reason`` values -- but +``akmcstate``, ``explorer``, and ``basinhopping`` now compare against +named members and route bad-saddle labels through a single source of +truth. Two latent bugs surface and are fixed: ``register_bad_saddle`` +no longer ``IndexError``s on codes 20-22 (DimerLostMode, +DimerRestoredBest, BadArtnError), and the ``"nonlocal abort"`` slug +typo collapses to ``"nonlocal_abort"``. diff --git a/docs/newsfragments/338-pybind11-once.changed.md b/docs/newsfragments/338-pybind11-once.changed.md new file mode 100644 index 000000000..036b6de04 --- /dev/null +++ b/docs/newsfragments/338-pybind11-once.changed.md @@ -0,0 +1 @@ +Centralise the `dependency('pybind11')` lookup behind a `need_pybind11` flag; pre-2.15 each of `with_catlearn` / `with_ase` / `with_ase_orca` / `with_ase_nwchem` re-ran the dep search and re-appended to `_deps`. Lookup runs once now; downstream blocks reuse the `_deps` entry. diff --git a/docs/newsfragments/338-stringop-shared_ptr.fixed.md b/docs/newsfragments/338-stringop-shared_ptr.fixed.md new file mode 100644 index 000000000..ab5a9974d --- /dev/null +++ b/docs/newsfragments/338-stringop-shared_ptr.fixed.md @@ -0,0 +1 @@ +Squash the last `-Wstringop-overread` warning that gcc 13 surfaces inside the `QUILL_LOG_INFO` call in `NEBInitialPaths.cpp::sidppPath`. The bare `use_zbl ? "-ZBL" : ""` ternary fed a `const char*` into quill's `safe_strnlen` specialisation which calls `memchr(ptr, 0, SIZE_MAX)`; gcc's optimizer loses the early-exit through-the-null analysis and complains the source size (5 bytes for "-ZBL\0") is smaller than the bound. Bind the literal to a sized `std::string` first so quill's encoding takes the string overload that already knows the length. Also pass-by-value-and-move `std::shared_ptr` through every Optimizer subclass ctor (LBFGS / Quickmin / SteepestDescent / ConjugateGradients / FIRE) plus the `Optimizer` base, satisfying the `performance-unnecessary-value-param` clang-tidy diagnostic without changing semantics. diff --git a/docs/newsfragments/338-torch-find-required-false.fixed.md b/docs/newsfragments/338-torch-find-required-false.fixed.md new file mode 100644 index 000000000..83d409ad1 --- /dev/null +++ b/docs/newsfragments/338-torch-find-required-false.fixed.md @@ -0,0 +1 @@ +Make the libtorch component lookup `required: false` and skip missing libraries (closes #304). Aligns with the CMake build's `find_package(Torch)` behaviour and unblocks Windows / cross-builds where `torch_global_deps` ships only a `.dll` (no `.lib` import library), as well as conda-forge `libtorch *cpu*` installs that strip `torch_cuda`. Combined with the `with_xtb` -> always-compile + dlopen change earlier in this PR (issue 1), the `xtb` fallback removal (issue 2), and the VLA -> `std::vector` swap inside the XTBPot rewrite (issue 3), all four meson cross-platform packaging items in #304 are now upstream. diff --git a/docs/newsfragments/338-vasp.changed.md b/docs/newsfragments/338-vasp.changed.md new file mode 100644 index 000000000..cbe4fb502 --- /dev/null +++ b/docs/newsfragments/338-vasp.changed.md @@ -0,0 +1 @@ +VASP potential is now always compiled on POSIX (Windows still omitted). The `with_vasp` meson option and `-DWITH_VASP` are gone -- VASP is a subprocess shim with no library dependency. The banner reports `VASP: enabled (subprocess)`. diff --git a/docs/newsfragments/338-visibility.changed.md b/docs/newsfragments/338-visibility.changed.md new file mode 100644 index 000000000..15d73905d --- /dev/null +++ b/docs/newsfragments/338-visibility.changed.md @@ -0,0 +1 @@ +Hide C++ symbols by default (`-fvisibility=hidden` + `-fvisibility-inlines-hidden`) for every eOn library. Cuts the dynamic symbol table on `libeonclib.so` by an order of magnitude, speeds up rtld lookups in conda envs, and stops internal namespace symbols from leaking. Filtered through `cppc.get_supported_arguments` so the same line works on Windows (PE/COFF treats every function as private until `__declspec(dllexport)`). diff --git a/docs/newsfragments/338-warnings-cleanup.fixed.md b/docs/newsfragments/338-warnings-cleanup.fixed.md new file mode 100644 index 000000000..81b65e356 --- /dev/null +++ b/docs/newsfragments/338-warnings-cleanup.fixed.md @@ -0,0 +1 @@ +Revert the ill-fated `-fvisibility=hidden` global flag introduced earlier in this PR (it hid `Matter::compare` and broke linking the eonclient binary against libeonclib.so) and migrate the 10 remaining `[[deprecated]]` constructor call sites: every `Optimizer` subclass (LBFGS / Quickmin / SteepestDescent / ConjugateGradients) now passes `OptimizerConfig::fromParams(p)` directly, and every `Dynamics` instantiation (DynamicsSaddleSearch, ParallelReplicaJob x2, ReplicaDynamicsJob, SafeHyperJob, TADJob) passes `DynamicsConfig::fromParams(p)`. `-fvisibility-inlines-hidden` stays (safe; only affects inline / template-instantiation symbols). diff --git a/docs/newsfragments/338-warnings.changed.md b/docs/newsfragments/338-warnings.changed.md new file mode 100644 index 000000000..c5c03f4c9 --- /dev/null +++ b/docs/newsfragments/338-warnings.changed.md @@ -0,0 +1 @@ +Bump default `warning_level` from 0 to 1 in the top-level meson project (`-Wall`). Pre-2.15 we shipped 0 to suppress build noise ahead of the CECAM school (#117); the suppression has outlived its use. `warning_level=2` (`-Wall -Wextra`) and `=3` (which also passes `-fimplicit-none` and breaks the vendored Fortran TUs) are tracked as follow-ups in #117. diff --git a/docs/newsfragments/338-wextra.changed.md b/docs/newsfragments/338-wextra.changed.md new file mode 100644 index 000000000..396a2b6ec --- /dev/null +++ b/docs/newsfragments/338-wextra.changed.md @@ -0,0 +1 @@ +Bump default `warning_level` from 1 to 2 (`-Wall -Wextra`); the codebase compiles cleanly under both. Pin the inih r62 subproject to `warning_level=0` since it ships an `unused parameter 'user'` in `tests/unittest_alloc.c` that's vendored-upstream test code, not ours to fix. `warning_level=3` stays out of reach because it also passes `-fimplicit-none` to gfortran and breaks eonclient's vendored Fortran TUs that rely on implicit typing -- tracked under #117 for a Fortran-side cleanup. diff --git a/docs/newsfragments/338-xtb.changed.md b/docs/newsfragments/338-xtb.changed.md new file mode 100644 index 000000000..b388cbf44 --- /dev/null +++ b/docs/newsfragments/338-xtb.changed.md @@ -0,0 +1 @@ +XTB potential is now always compiled; libxtb is loaded at runtime via dlopen, mirroring the LAMMPS / ARTn / IRA pattern. The `with_xtb` meson option, `-DWITH_XTB` define, and the build-time `dependency('xtb')` link are gone -- a single eonclient binary picks up XTB iff libxtb is on the library search path. Banner reports `XTB: enabled (dlopen at runtime)`. Tests `test_xtb` and `test_cineb_xtb` are always built and SKIP at runtime when libxtb is missing. diff --git a/docs/newsfragments/338.fixed.md b/docs/newsfragments/338.fixed.md new file mode 100644 index 000000000..35e49944e --- /dev/null +++ b/docs/newsfragments/338.fixed.md @@ -0,0 +1 @@ +Pin `subprojects/rgpot.wrap` to `v1.1.0` so the conda-forge feedstock can drop `fix_capnpc.patch` once it bumps eon. revision=head was non-reproducible. diff --git a/docs/source/devdocs/release.md b/docs/source/devdocs/release.md index 338917725..dd923e385 100644 --- a/docs/source/devdocs/release.md +++ b/docs/source/devdocs/release.md @@ -91,6 +91,7 @@ Defined in `cog.toml`: `CHANGELOG.md` (towncrier owns it) and `branch_whitelist = ["main"]` so `cog bump` refuses to run on a PR branch. +(post-bump-actions)= ## 3. Post-bump actions 1. Inspect the release commit: diff --git a/docs/source/devdocs/testing.md b/docs/source/devdocs/testing.md index e31bf4312..021c181d6 100644 --- a/docs/source/devdocs/testing.md +++ b/docs/source/devdocs/testing.md @@ -40,10 +40,14 @@ All tests are registered via meson's array iteration pattern and run with Optional tests (enabled by build flags): - `test_ase_pot` (with_ase): ASE Python calculator - `test_mta` (with_metatomic): Metatomic ML potential -- `test_xtb` (with_xtb): XTB semiempirical -- `test_cineb_xtb` (with_xtb): CI-NEB with XTB - `test_serve_spec` (with_serve): Serve mode endpoint parsing +Runtime-loaded tests (always built; SKIP at runtime when the .so is +absent from `LD_LIBRARY_PATH`): +- `test_xtb`, `test_cineb_xtb`: libxtb +- `test_artn`: libartn +- `test_ira`: libira + ## Writing and registering tests Tests use [Catch2](https://github.com/catchorg/Catch2) (amalgamated, vendored @@ -52,7 +56,8 @@ source and linked against `eonclib`. Registration uses meson's array iteration: -```{code-block} meson +```{code-block} bash +:caption: client/meson.build (excerpt) test_array = [ ['test_name', 'TestFile.cpp', 'data_dir'], ] diff --git a/docs/source/install/index.md b/docs/source/install/index.md index ed7538bac..0e7de8a2a 100644 --- a/docs/source/install/index.md +++ b/docs/source/install/index.md @@ -90,6 +90,44 @@ passed with `--native-file`: - With `ccache` installed, add `--native-file nativeFiles/ccache_gnu.ini` - With `mold` installed, add `--native-file nativeFiles/mold.ini` +### Inspecting what was compiled in + +Run `eonclient --features` to see the live banner of compile-time +options the binary was built with (LAMMPS, ARTn, IRA, XTB, VASP, +Metatomic, MPI, FlexiBLAS, Serve mode, ...) along with `enabled` / +`disabled` / `enabled (dlopen at runtime)` / `enabled (subprocess)` +markers. Useful to confirm a conda env or a source build before +launching long ensemble jobs. + +```{code-block} bash +eonclient --features +``` + +### Runtime-pluggable BLAS / LAPACK (FlexiBLAS) + +```{versionadded} 2.15 +``` + +For BLAS-bound workloads (large GPRD / Hessian / NEB), pass +`-Dwith_flexiblas=true` (meson) or `-DWITH_FLEXIBLAS=ON` (cmake) +to link Eigen via [FlexiBLAS](https://github.com/mpimd-csc/flexiblas). +One eonclient binary then forwards every BLAS / LAPACK call to any +installed backend at run time, picked by the `FLEXIBLAS` env var: + +```{code-block} bash +meson setup builddir -Dwith_flexiblas=true +meson compile -C builddir +FLEXIBLAS=OPENBLAS ./eonclient # OpenBLAS at run time +FLEXIBLAS=INTELMKL ./eonclient # Intel MKL at run time +FLEXIBLAS=BLIS ./eonclient +``` + +`subprojects/flexiblas.wrap` pins upstream v3.5.0 and CMake-builds +FlexiBLAS when no system install is found, so the option never +fails for "no FlexiBLAS available". Mutually exclusive with the +legacy `-Duse_mkl=true` direct-MKL link; FlexiBLAS can route to +MKL at run time via `FLEXIBLAS=INTELMKL` instead. + ### Troubleshooting: rolling distros (Arch, Fedora) On rolling-release distributions with newer system packages, the conda-forge diff --git a/docs/source/releases/v2.8.0/publications.md b/docs/source/releases/v2.8.0/publications.md index f98b8a8cb..3277fdb09 100644 --- a/docs/source/releases/v2.8.0/publications.md +++ b/docs/source/releases/v2.8.0/publications.md @@ -26,7 +26,7 @@ here, pull requests are welcome. - **Method ::** Quaternion-based removal of external degrees of freedom {cite:t}`2.8.0-melanderRemovingExternalDegrees2015`. - **Application ::** Tight-binding charge transfer model {cite:t}`2.8.0-marasImprovedTightbindingCharge2015`. - **Application ::** H diffusion on ice at low temperature {cite:t}`2.8.0-asgeirssonLongTimeScaleSimulations2017`. -- **Application ::** Reassignment of Au cluster magic numbers {cite:t}`2.8.0-gardenReassignmentMagicNumbers2018a`. +- **Application ::** Reassignment of Au cluster magic numbers {cite:t}`2.8.0-gardenReassignmentMagicNumbers2018`. - **Application ::** Structure and properties of an edge dislocation in Rutile TiO_2 {cite:t}`2.8.0-marasDeterminationStructureProperties2019`. - **Method ::** Energy-weighted springs and eigenvector following for NEB {cite:t}`2.8.0-asgeirssonNudgedElasticBand2021`. diff --git a/docs/source/releases/v2.8.1/publications.md b/docs/source/releases/v2.8.1/publications.md index 82ccf0066..e4f40e602 100644 --- a/docs/source/releases/v2.8.1/publications.md +++ b/docs/source/releases/v2.8.1/publications.md @@ -20,7 +20,7 @@ majority of publications are reported in the major release. + [Github reproduction](https://github.com/TheochemUI/otgpd_repro) - **Review ::** Bayesian Hierarchical measures for benchmarking computational chemistry software by {cite:t}`2.8.1-goswamiEfficientExplorationChemical2025`. - + [ArXiV preprint](goswamiEfficientExplorationChemical2025) + + [ArXiV preprint](https://arxiv.org/abs/2510.21368) diff --git a/docs/source/user_guide/artn.md b/docs/source/user_guide/artn.md index e2394d4b4..6f27a5b37 100644 --- a/docs/source/user_guide/artn.md +++ b/docs/source/user_guide/artn.md @@ -55,10 +55,14 @@ rate is expected to be lower. ## Usage -ARTn requires the `artn-plugin` Fortran subproject source to be available under -`subprojects/` (for example via `meson subprojects download artn-plugin`). When -present, eOn builds it via CMake at configure time as a workaround for Meson's -current Fortran submodule scanner limitations. +```{versionchanged} 2.15 +ARTn is now loaded at runtime via `dlopen` / `LoadLibrary`, mirroring +the LAMMPS pattern. No `-Dwith_artn` build flag is needed and no +configure-time CMake build of `artn-plugin` is required. A single +eOn binary picks up ARTn iff `libartn.so` is on the library search +path. The pre-2.15 `meson subprojects download artn-plugin` workflow +is no longer required. +``` ARTn can be used in two ways: @@ -125,22 +129,30 @@ max_iterations = 500 section. Any non-empty value is required to exist; eOn checks before setup and aborts with a clear error when it does not. Maps to pARTn's ``filin``. -## Build requirements +## Runtime requirements -ARTn requires a Fortran compiler and LAPACK. Download the subproject first, then -let eOn build it automatically at configure time: +ARTn needs `libartn.so` (or `libartn.dylib` / `libartn.dll`) on the +library search path at run time. The shim and the SaddleSearch driver +are unconditionally compiled into eonclient; only the dynamic library +is supplied externally. ```{code-block} shell -meson subprojects download artn-plugin -meson setup builddir -Dwith_artn=true +# Build libartn from the upstream Fortran subproject (one-time): +git clone https://gitlab.com/mammasmias/artn-plugin +cd artn-plugin +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \ + -DWITH_LAMMPS=OFF -DWITH_QE=OFF -DWITH_SIESTA=OFF +cmake --build build -j + +# Make eonclient see it: +export LD_LIBRARY_PATH=$PWD/build:$LD_LIBRARY_PATH # Linux +export DYLD_LIBRARY_PATH=$PWD/build:$DYLD_LIBRARY_PATH # macOS ``` -Or with a prebuilt library: - -```{code-block} shell -meson setup builddir -Dwith_artn=true \ - -Dartn_libdir=/path/to/lib -Dartn_includedir=/path/to/include -``` +If the library is missing, the eonclient banner still says +``ARTn: enabled (dlopen at runtime)`` but `method = artn` (or +`min_mode_method = artn`) raises a runtime error naming the entry +point and pointing at `LD_LIBRARY_PATH`. ## Comparison with kart diff --git a/docs/source/user_guide/lammps_pot.md b/docs/source/user_guide/lammps_pot.md index 8bbf466b8..1354e842c 100644 --- a/docs/source/user_guide/lammps_pot.md +++ b/docs/source/user_guide/lammps_pot.md @@ -59,9 +59,10 @@ immediately once `liblammps` is on the library search path. ## Usage -Set the potential to `lammps` in the configuration file and place a LAMMPS -input file named `in.lammps` in the `potfiles` directory. This file specifies -which LAMMPS potential to use. Example for the Morse potential: +Set the potential to `lammps` in the configuration file and provide a LAMMPS +input file named `in.lammps` plus any potential parameter files (e.g. +`Cu01.eam.alloy`). The `in.lammps` file specifies which LAMMPS potential +to use. Example for the Morse potential: ```ini pair_style morse 9.5 #morse potential with 9.5 Angstrom cutoff @@ -69,14 +70,89 @@ pair_coeff * * 0.7102 1.6047 2.797 #specify parameters pair_modify shift yes #shift the potential to be zero at the cutoff ``` +### File placement + +Two ways to feed LAMMPS its run-time files. Pick one: + +#### 1. Bundle (recommended) -- `lammps_bundle = path/to/run.eonlpb` + +```{versionadded} 2.15 +``` + +Pack `in.lammps` and every file it references (pair_coeff data, +custom `pair_style` `.so` plugins, KIM tables, `read_data` inputs, +shell helpers, ...) into one `.eonlpb` blob. Point eOn at the bundle +and the eonclient CWD becomes irrelevant for LAMMPS file lookups. + +```{code-block} bash +# Pack the directory holding in.lammps + all referenced files +python -m eon.lammps_bundle pack potfiles/ run.eonlpb + +# Inspect what got packed +python -m eon.lammps_bundle list run.eonlpb +``` + +```{code-block} ini +[Potential] +potential = lammps +lammps_bundle = run.eonlpb +``` + +`LAMMPSPot` extracts the bundle into a per-instance scratch dir +under `$TMPDIR` and issues `shell cd ` to liblammps before +sourcing `in.lammps`, so every relative reference inside `in.lammps` +resolves there. The scratch dir is removed when the potential is +destroyed. Multiple `eonclient` instances on the same host get their +own private scratch dirs (PID + 64-bit random suffix). + +#### 2. Legacy CWD mode (no bundle) + +If `lammps_bundle` is unset, `in.lammps` and every file it references +must live in the directory where `eonclient` runs: + +- **Multi-job drivers** (`job = akmc`, `job = parallel_replica`, + `job = basin_hopping`, ...): keep them in `potfiles/` next to your + `config.ini`. The Python driver copies the contents into each + per-job scratch directory (`jobs/scratch//`) before launching + `eonclient`. + +- **Direct single-job `eonclient`** (`job = minimization`, + `job = nudged_elastic_band`, `job = single_point`, + `job = process_search`): place `in.lammps` and every referenced file + in the SAME directory as `config.ini`. eOn now refuses to construct + a `LAMMPSPot` if `in.lammps` is missing from CWD and surfaces the + CWD path in the error message; `lammps_has_error` is also checked + after `lammps_file` so LAMMPS-side syntax / pair_coeff / pair_style + errors no longer hide. + +```{admonition} Why a bundle? +:class: tip +Pre-2.15 eOn coupled the eonclient CWD to LAMMPS's CWD: any file +liblammps reads (pair_coeff, read_data, custom plugin `.so`, ...) had +to be in eonclient's CWD or absolute. Job-scratch cleanup, tmpfs +eviction, or a wrapper script chdir-ing somewhere unexpected would +silently break the next force call. The bundle decouples them; one +blob travels with the run. +``` + ## Troubleshooting If eOn reports "LAMMPS library not found", ensure that: 1. `liblammps.so` (or `.dylib`/`.dll`) is on the library search path - (`LD_LIBRARY_PATH` on Linux, `DYLD_LIBRARY_PATH` on macOS) + (`LD_LIBRARY_PATH` on Linux, `DYLD_LIBRARY_PATH` on macOS). + For pixi/conda envs, exporting + `LD_LIBRARY_PATH=$CONDA_PREFIX/lib:$LD_LIBRARY_PATH` is usually enough. 2. The LAMMPS library was built as a shared library (`BUILD_SHARED_LIBS=yes`) -3. The LAMMPS version is compatible (tested with LAMMPS 2Aug2023 and later) +3. The LAMMPS version is compatible (tested with LAMMPS 2Aug2023 and later; + conda-forge `lammps=2024.08.29` works with eOn 2.14) + +If `eonclient` reports `LAMMPSPot: in.lammps not found in eonclient CWD`, +either point `lammps_bundle` at a `.eonlpb` blob (recommended) or place +`in.lammps` and every file it references next to `config.ini` (legacy +CWD mode). If liblammps surfaces a syntax / pair_coeff / pair_style +error after sourcing `in.lammps`, eOn now reports the LAMMPS-side +message verbatim instead of segfaulting on the next force call. ## References diff --git a/docs/source/user_guide/mpi_potential.md b/docs/source/user_guide/mpi_potential.md index 1761bbed3..e5f59b14e 100644 --- a/docs/source/user_guide/mpi_potential.md +++ b/docs/source/user_guide/mpi_potential.md @@ -13,6 +13,73 @@ myst: with `-Dwith_mpi=True`. ``` +```{versionchanged} 2.15 +Two changes that ship together: + +1. **C bindings.** `MPIPot.cpp` and the MPI control flow in + `ClientEON.cpp` were ported off the removed MPI C++ bindings + (`MPI::COMM_WORLD`, `MPI::INT`, ...). The C bindings used now + (`MPI_Send`, `MPI_Recv`, `MPI_Iprobe`, `MPI_Comm_create`, ...) + are guaranteed by every spec-compliant MPI implementation, so + `-Dwith_mpi=true` builds against modern conda-forge MPICH 4.x + and OpenMPI 5.x without `mpicxx.h`. + +2. **MPItrampoline by default.** The `-Dwith_mpi=true` build now + prefers [MPItrampoline](https://github.com/eschnett/MPItrampoline) + over a direct link to a specific MPI flavour. MPItrampoline + provides a stable wrapper ABI -- think of it as FlexiBLAS for + MPI -- so one eonclient binary works against any spec-compliant + libmpi at run time. The eOn meson detection order is: + + 1. `dependency('MPItrampoline', method: 'cmake')` + 2. `dependency('mpi-c')` (MPItrampoline ships a shim that + shadows the system `mpi-c.pc`) + 3. `dependency('mpi')` (last-resort direct link to system MPI) + + `subprojects/mpitrampoline.wrap` pins upstream v5.5.1 for users + who want meson to fetch and CMake-build MPItrampoline locally. +``` + +## MPItrampoline workflow + +Install MPItrampoline once per system, then point it at the actual +MPI implementation you want to use at run time. + +```{code-block} shell +# 1) Build MPItrampoline against the trampoline ABI (no MPI needed yet): +git clone https://github.com/eschnett/MPItrampoline +cmake -S MPItrampoline -B build-mpitrampoline \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DCMAKE_INSTALL_PREFIX=$HOME/mpitrampoline +cmake --build build-mpitrampoline -j +cmake --install build-mpitrampoline + +# 2) For each MPI implementation you want to wrap, build MPIwrapper: +git clone https://github.com/eschnett/MPIwrapper +cmake -S MPIwrapper -B build-mpiwrapper-openmpi \ + -DMPIEXEC_EXECUTABLE=$(which mpiexec) \ + -DCMAKE_INSTALL_PREFIX=$HOME/mpiwrappers/openmpi +cmake --build build-mpiwrapper-openmpi -j +cmake --install build-mpiwrapper-openmpi + +# 3) Build eOn with MPItrampoline on the cmake path: +export CMAKE_PREFIX_PATH=$HOME/mpitrampoline:$CMAKE_PREFIX_PATH +meson setup builddir -Dwith_mpi=true +meson compile -C builddir + +# 4) At run time, tell MPItrampoline which wrapper to forward to: +export MPITRAMPOLINE_LIB=$HOME/mpiwrappers/openmpi/lib/libmpiwrapper.so +export MPITRAMPOLINE_MPIEXEC=$HOME/mpiwrappers/openmpi/bin/mpiwrapper-mpiexec +mpiexec -n 4 ./eonclient # the trampoline-shipped mpiexec +``` + +The same `eonclient` binary then works against MPICH, OpenMPI, +Intel MPI, Spectrum MPI, or Cray MPICH by swapping the +`MPITRAMPOLINE_LIB` env var; no eOn rebuild needed. + +If MPItrampoline is unavailable, the meson build falls back to a +direct `dependency('mpi')` link against the system MPI as before. + ```{note} This is only for modified VASP at the moment.. ``` diff --git a/docs/source/user_guide/potential.md b/docs/source/user_guide/potential.md index 1cbcbfb52..c408516a9 100644 --- a/docs/source/user_guide/potential.md +++ b/docs/source/user_guide/potential.md @@ -11,11 +11,20 @@ myst: and libraries and others via interfaces. ```{note} -Some of these require compile-time flags, detailed in the [installation instructions](project:../install/index.md). -The `conda-forge` package (`conda install -c conda-forge eon`) ships with -**Metatomic**, **XTB**, **EXT_POT**, and the vendored potentials. -LAMMPS, ASE, VASP, AMS, and MPI potentials require building from source with -the corresponding `-Dwith_*` flags. +Some potentials require compile-time flags, detailed in the [installation +instructions](project:../install/index.md). Three categories: + +- **Always compiled**: vendored Fortran/C potentials (LJ, EAM, EMT, Morse, + ZBL, ...), **LAMMPS**, **VASP** (POSIX only), **ARTn**, and **IRA**. + These need no `-Dwith_*` flag; LAMMPS / ARTn / IRA pick up their + shared library at run time via `dlopen`, VASP spawns its binary as + a subprocess. +- **Optional via build flag**: ASE-* (`-Dwith_ase`, `-Dwith_ase_orca`, + `-Dwith_ase_nwchem`), AMS (`-Dwith_ams`), MPIPot (`-Dwith_mpi`), + GPRD / GP-Surrogate, CatLearn, CuH2 (default on). +- **Conda-forge default**: `conda install -c conda-forge eon` ships + Metatomic, XTB, EXT_POT, LAMMPS (runtime-loaded), VASP, and the + vendored set. ``` ## Supported Potentials @@ -23,10 +32,12 @@ the corresponding `-Dwith_*` flags. ### External VASP {cite:p}`pot-kresseEfficientIterativeSchemes1996` -: Vienna Ab-Initio Simulation Program (VASP) I/O interface. {bdg-warning}`source build` +: Vienna Ab-Initio Simulation Program (VASP) subprocess interface; + POSIX only. {bdg-success}`always compiled` LAMMPS {cite:p}`pot-plimptonFastParallelAlgorithms1995,pot-thompsonLAMMPSFlexibleSimulation2022` -: Library interface, detailed [documentation here](project:../user_guide/lammps_pot.md). {bdg-warning}`source build` +: Runtime-loaded `liblammps`; portable run-input bundle support + (`.eonlpb`). Detailed [documentation here](project:../user_guide/lammps_pot.md). {bdg-success}`runtime dlopen` EXT_POT : File-based interface to any external calculator. Detailed [documentation here](project:ext_pot.md). {bdg-success}`conda-forge` diff --git a/eon/akmcstate.py b/eon/akmcstate.py index 39f3bfeef..da4bf3496 100644 --- a/eon/akmcstate.py +++ b/eon/akmcstate.py @@ -14,6 +14,7 @@ from eon import atoms from eon import fileio as io from eon import state +from eon.status_codes import SaddleStatus, saddle_status_label class AKMCState(state.State): ID, ENERGY, PREFACTOR, PRODUCT, PRODUCT_ENERGY, PRODUCT_PREFACTOR, BARRIER, RATE, REPEATS = list(range(9)) @@ -595,34 +596,13 @@ def set_bad_saddle_count(self, num): def register_bad_saddle(self, result, store=False, superbasin=None): """ Registers a bad saddle. """ - #print ("bad saddle ",result["results"]["termination_reason"]) - result_state_code = ["Good", - "Init", - "Saddle Search No Convex Region", - "Saddle Search Terminated High Energy", - "Saddle Search Terminated Concave Iterations", - "Saddle Search Terminated Total Iterations", - "Not Connected", - "Bad Prefactor", - "Bad Barrier", - "Minimum Not Converged", - "Failed Prefactor Calculation", - "Potential Failed", - "Nonnegative Displacement Abort", - "Nonlocal Abort", - "Negative Barrier", - "MD Trajectory Too Short", - "No Negative Mode at Saddle", - "No Forward Barrier in Minimized Band", - "MinMode Zero Mode Abort", - "Optimizer Error" - ] + termination_reason = result["results"]["termination_reason"] self.set_bad_saddle_count(self.get_bad_saddle_count() + 1) - self.append_search_result(result, result_state_code[result["results"]["termination_reason"]], superbasin) + self.append_search_result(result, saddle_status_label(termination_reason), superbasin) # If a MD saddle search is too short add it to the total clock time. if 'simulation_time' in result['results']: - if result['results']['termination_reason'] == 15: #too short + if termination_reason == SaddleStatus.BadMdTrajectoryTooShort: self.increment_time(result['results']['simulation_time'], result['results']['md_temperature']) if store: diff --git a/eon/basinhopping.py b/eon/basinhopping.py index 035ef72df..082c7b50d 100644 --- a/eon/basinhopping.py +++ b/eon/basinhopping.py @@ -18,6 +18,7 @@ from eon.config import config from eon import fileio as io from eon import locking +from eon.status_codes import SaddleStatus from eon.version import version #class RandomStructure: @@ -248,7 +249,7 @@ def keep_result(name): result_info = io.parse_results(result['results.dat']) if 'minimum_energy' not in result_info: continue - if result_info['termination_reason'] == 0: + if result_info['termination_reason'] == SaddleStatus.Good: if bhstates.add_state(result, result_info): logger.info("New structure with energy %.8e", result_info['minimum_energy']) diff --git a/eon/explorer.py b/eon/explorer.py index 6c729191e..05f9efe12 100644 --- a/eon/explorer.py +++ b/eon/explorer.py @@ -11,6 +11,11 @@ import numpy from eon import atoms +from eon.status_codes import ( + SaddleStatus, + saddle_status_slug, + minimization_status_slug, +) from eon import communicator from eon import displace from eon import fileio as io @@ -337,7 +342,7 @@ def keep_result(name): # read in the results result['results'] = io.parse_results(result['results.dat']) - if result['results']['termination_reason'] == 0: + if result['results']['termination_reason'] == SaddleStatus.Good: state.add_process(result, self.superbasin) else: state.register_bad_saddle(result, self.config.debug_keep_bad_saddles, superbasin=self.superbasin) @@ -457,7 +462,7 @@ def keep_result(name): if final_result: results_dict = io.parse_results(final_result['results.dat']) reason = results_dict['termination_reason'] - if reason == 0: + if reason == SaddleStatus.Good: self.state.add_process(final_result) else: final_result['wuid'] = id @@ -575,19 +580,14 @@ def __init__ (self, reactant, displacement, mode, disp_type, search_id, state_nu 'min2':'not_started' } - unknown = "unknown_exit_code" + # Slug tables defer to eon.status_codes (single source of truth + # mirroring client/StatusTypes.h). Wrapped as callables so a + # raw int from results.dat resolves to a slug without needing + # an exact enum member, and unknown codes degrade gracefully. self.job_termination_reasons = { - 'saddle_search':[ "good", unknown, "no_convex", "high_energy", - "max_concave_iterations", - "max_iterations", unknown, unknown, unknown, - unknown, unknown, "potential_failed", - "nonnegative_abort", "nonlocal abort", - unknown, "md_trajectory_too_short", - "no_negative_mode_at_saddle", - "no_barrier", "zero_mode_abort", - "optimizer_error", "dimer_lost_mode", - "dimer_restored_best", "artn_error"], - 'minimization':[ "good", "max_iterations", "potential_failed", ]} + 'saddle_search': saddle_status_slug, + 'minimization': minimization_status_slug, + } self.finished_jobs = [] @@ -648,7 +648,7 @@ def process_result(self, result): if job_type == 'saddle_search': self.data['termination_reason'] = termination_code logger.info("Search_id: %i saddle search complete" % self.search_id) - if termination_code == 0: + if termination_code == SaddleStatus.Good: self.job_statuses[job_type] = 'complete' else: self.job_statuses[job_type] = 'error' @@ -762,10 +762,10 @@ def finish_minimization(self, result): tc1 = io.parse_results(result1['results.dat'])['termination_reason'] tc2 = io.parse_results(result2['results.dat'])['termination_reason'] - termination_reason1 = self.job_termination_reasons['minimization'][tc1] - termination_reason2 = self.job_termination_reasons['minimization'][tc2] + termination_reason1 = self.job_termination_reasons['minimization'](tc1) + termination_reason2 = self.job_termination_reasons['minimization'](tc2) if termination_reason1 == 'max_iterations' or termination_reason2 == 'max_iterations': - self.data['termination_reason'] = 9 + self.data['termination_reason'] = int(SaddleStatus.BadMinima) self.data['potential_energy_saddle'] = 0.0 self.data['potential_energy_reactant'] = 0.0 self.data['potential_energy_product'] = 0.0 @@ -777,7 +777,7 @@ def finish_minimization(self, result): if (not is_reactant(atoms1) and not is_reactant(atoms2)) or \ (is_reactant(atoms1) and is_reactant(atoms2)): # Not connected - self.data['termination_reason'] = 6 + self.data['termination_reason'] = int(SaddleStatus.BadNotConnected) self.data['potential_energy_saddle'] = 0.0 self.data['potential_energy_reactant'] = 0.0 self.data['potential_energy_product'] = 0.0 diff --git a/eon/lammps_bundle.py b/eon/lammps_bundle.py new file mode 100644 index 000000000..96c85438d --- /dev/null +++ b/eon/lammps_bundle.py @@ -0,0 +1,165 @@ +"""Pack and inspect eOn LAMMPS run-input bundles (.eonlpb). + +The bundle is a single self-contained file that holds in.lammps plus +every file the run needs (pair_coeff data, custom pair_style .so +plugins, KIM tables, read_data inputs, shell helpers, ...). The +client-side reader in client/potentials/LAMMPS/LammpsBundle.{h,cpp} +extracts the bundle to a per-instance scratch dir and pins liblammps's +working directory there, so the eonclient CWD no longer matters for +LAMMPS file lookups. + +Format (all little-endian): + [0..7] magic : b"EONLPB1\\0" (8 bytes) + [8..15] m_len : uint64 (manifest length in bytes) + [16..] manifest : JSON UTF-8 (m_len bytes) + [...] bodies : concatenated file contents in manifest order + +Manifest schema: + {"files": [{"name": "in.lammps", "size": 1234}, + {"name": "Pd.eam.alloy", "size": 56789}]} + +Usage:: + + python -m eon.lammps_bundle pack potfiles/ bundle.eonlpb + python -m eon.lammps_bundle list bundle.eonlpb +""" + +from __future__ import annotations + +import argparse +import json +import os +import struct +import sys +from dataclasses import dataclass +from pathlib import Path +from typing import Iterable + +MAGIC = b"EONLPB1\x00" +HEADER_LEN = len(MAGIC) + 8 # magic + uint64 manifest length + + +@dataclass(frozen=True) +class BundleEntry: + name: str + size: int + + +def _iter_files(root: Path, exclude: Iterable[str] = ()) -> list[tuple[Path, str]]: + """Walk ``root`` and yield ``(absolute_path, bundle_relative_name)`` pairs. + + Excludes anything in ``exclude`` (matched on the bundle-relative + name) plus the bundle output file itself when it lives inside ``root``. + """ + skip = {Path(p).as_posix() for p in exclude} + out: list[tuple[Path, str]] = [] + for path in sorted(root.rglob("*")): + if not path.is_file(): + continue + rel = path.relative_to(root).as_posix() + if rel in skip: + continue + out.append((path, rel)) + return out + + +def pack(source_dir: str | os.PathLike, bundle_path: str | os.PathLike) -> Path: + """Pack every regular file under ``source_dir`` into ``bundle_path``. + + Refuses to pack a directory that does not contain ``in.lammps`` -- + the client treats a bundle without it as a configuration error. + Returns the absolute path of the written bundle. + """ + src = Path(source_dir).resolve() + bundle = Path(bundle_path).resolve() + if not src.is_dir(): + raise FileNotFoundError(f"{src} is not a directory") + if not (src / "in.lammps").is_file(): + raise FileNotFoundError( + f"{src}/in.lammps is missing; refusing to pack a bundle without it" + ) + + # Skip the bundle output itself if it lives inside the source dir. + exclude: list[str] = [] + try: + rel_self = bundle.relative_to(src) + except ValueError: + pass + else: + exclude.append(rel_self.as_posix()) + + entries: list[tuple[Path, str]] = _iter_files(src, exclude=exclude) + manifest = { + "files": [ + {"name": rel, "size": path.stat().st_size} for path, rel in entries + ] + } + manifest_bytes = json.dumps( + manifest, separators=(",", ":"), ensure_ascii=True + ).encode("ascii") + + bundle.parent.mkdir(parents=True, exist_ok=True) + with bundle.open("wb") as out: + out.write(MAGIC) + out.write(struct.pack(" list[BundleEntry]: + """Parse the magic + manifest from a bundle and return its entry list.""" + with Path(bundle_path).open("rb") as f: + header = f.read(HEADER_LEN) + if len(header) != HEADER_LEN or not header.startswith(MAGIC): + raise ValueError( + f"{bundle_path}: bad magic (expected EONLPB1, got {header[:8]!r})" + ) + (m_len,) = struct.unpack(" int: + out = pack(args.source_dir, args.bundle) + entries = read_manifest(out) + total = sum(e.size for e in entries) + print(f"wrote {out} ({len(entries)} files, {total} bytes payload)") + return 0 + + +def _cmd_list(args: argparse.Namespace) -> int: + entries = read_manifest(args.bundle) + width = max((len(e.name) for e in entries), default=0) + for e in entries: + print(f"{e.name:<{width}} {e.size:>12} bytes") + return 0 + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__.split("\n", 1)[0]) + sub = parser.add_subparsers(dest="cmd", required=True) + + p_pack = sub.add_parser( + "pack", help="pack a directory tree into a .eonlpb bundle" + ) + p_pack.add_argument("source_dir", help="directory containing in.lammps + deps") + p_pack.add_argument("bundle", help="output bundle path (e.g. bundle.eonlpb)") + p_pack.set_defaults(func=_cmd_pack) + + p_list = sub.add_parser("list", help="list entries in a bundle") + p_list.add_argument("bundle", help="input bundle path") + p_list.set_defaults(func=_cmd_list) + + args = parser.parse_args(argv) + return args.func(args) + + +if __name__ == "__main__": # pragma: no cover + sys.exit(main()) diff --git a/eon/meson.build b/eon/meson.build index c9a8258be..af6c03083 100644 --- a/eon/meson.build +++ b/eon/meson.build @@ -18,6 +18,7 @@ py.install_sources( 'locking.py', 'server.py', 'schema.py', + 'status_codes.py', # TODO(rg): Spin mcamc out and use as a dependency # This will only use the python variant for now 'mcamc/__init__.py', diff --git a/eon/schema.py b/eon/schema.py index f30599fc4..aadd9c9f6 100644 --- a/eon/schema.py +++ b/eon/schema.py @@ -520,6 +520,16 @@ class PotentialConfig(BaseModel): # TODO(rg): move these around lammps_logging: bool = Field(default=True, description="Logging LAMMPS calls.") lammps_threads: int = Field(default=0, description="LAMMPS threads.") + lammps_bundle: str = Field( + default="", + description=( + "Path to a portable LAMMPS run-input bundle (.eonlpb) packing " + "in.lammps + every file it references. When set, LAMMPSPot " + "extracts to a per-instance scratch dir and pins liblammps's " + "working directory there. Pack a directory with " + "`python -m eon.lammps_bundle pack `." + ), + ) ext_pot_path: str = Field( default="ext_pot", description="Path for the external potential." ) diff --git a/eon/status_codes.py b/eon/status_codes.py new file mode 100644 index 000000000..54ce4fc39 --- /dev/null +++ b/eon/status_codes.py @@ -0,0 +1,156 @@ +"""Process-search termination codes shared with the C++ client. + +Mirror of ``client/StatusTypes.h``. The integer values are wire format +written to ``results.dat`` by eonclient, so they MUST match the C++ +``eonc::SaddleStatus`` enumerators byte-for-byte. The Python side +exposes them as ``IntEnum`` members so server code can compare against +``SaddleStatus.Good`` instead of the magic literal ``0``, while still +accepting raw ints transparently from ``parse_results``. + +Whenever ``client/StatusTypes.h`` adds, removes, or renumbers a code, +update this module in the same commit and add a row to the label / +slug tables. +""" + +from __future__ import annotations + +from enum import IntEnum + + +class SaddleStatus(IntEnum): + """Saddle-search termination reason. Wire-equivalent to + ``eonc::SaddleStatus`` in ``client/StatusTypes.h``.""" + + Good = 0 + Init = 1 + BadNoConvex = 2 + BadHighEnergy = 3 + BadMaxConcaveIterations = 4 + BadMaxIterations = 5 + BadNotConnected = 6 + BadPrefactor = 7 + BadHighBarrier = 8 + BadMinima = 9 + FailedPrefactor = 10 + PotentialFailed = 11 + NonnegativeAbort = 12 + NonlocalAbort = 13 + NegativeBarrier = 14 + BadMdTrajectoryTooShort = 15 + BadNoNegativeModeAtSaddle = 16 + BadNoBarrier = 17 + ZeromodeAbort = 18 + OptimizerError = 19 + DimerLostMode = 20 + DimerRestoredBest = 21 + BadArtnError = 22 + + +class MinimizationStatus(IntEnum): + """Minimizer termination as reported in ``results.dat`` for + ``job_type == 'minimization'``. Distinct from the broader + ``SaddleStatus`` -- the minimizer only emits these three.""" + + Good = 0 + MaxIterations = 1 + PotentialFailed = 2 + + +_UNKNOWN = "unknown_exit_code" + + +# Display labels (title case) used by akmc bad-saddle bookkeeping. +SADDLE_STATUS_LABELS: dict[SaddleStatus, str] = { + SaddleStatus.Good: "Good", + SaddleStatus.Init: "Init", + SaddleStatus.BadNoConvex: "Saddle Search No Convex Region", + SaddleStatus.BadHighEnergy: "Saddle Search Terminated High Energy", + SaddleStatus.BadMaxConcaveIterations: + "Saddle Search Terminated Concave Iterations", + SaddleStatus.BadMaxIterations: + "Saddle Search Terminated Total Iterations", + SaddleStatus.BadNotConnected: "Not Connected", + SaddleStatus.BadPrefactor: "Bad Prefactor", + SaddleStatus.BadHighBarrier: "Bad Barrier", + SaddleStatus.BadMinima: "Minimum Not Converged", + SaddleStatus.FailedPrefactor: "Failed Prefactor Calculation", + SaddleStatus.PotentialFailed: "Potential Failed", + SaddleStatus.NonnegativeAbort: "Nonnegative Displacement Abort", + SaddleStatus.NonlocalAbort: "Nonlocal Abort", + SaddleStatus.NegativeBarrier: "Negative Barrier", + SaddleStatus.BadMdTrajectoryTooShort: "MD Trajectory Too Short", + SaddleStatus.BadNoNegativeModeAtSaddle: "No Negative Mode at Saddle", + SaddleStatus.BadNoBarrier: "No Forward Barrier in Minimized Band", + SaddleStatus.ZeromodeAbort: "MinMode Zero Mode Abort", + SaddleStatus.OptimizerError: "Optimizer Error", + SaddleStatus.DimerLostMode: "Dimer Lost Mode", + SaddleStatus.DimerRestoredBest: "Dimer Restored Best Mode", + SaddleStatus.BadArtnError: "ARTn Error", +} + + +# Slug-case labels (snake_case) used by explorer.py for routing +# decisions. Codes that the legacy table left as ``unknown_exit_code`` +# stay that way to preserve downstream string-equality semantics. +SADDLE_STATUS_SLUGS: dict[SaddleStatus, str] = { + SaddleStatus.Good: "good", + SaddleStatus.Init: _UNKNOWN, + SaddleStatus.BadNoConvex: "no_convex", + SaddleStatus.BadHighEnergy: "high_energy", + SaddleStatus.BadMaxConcaveIterations: "max_concave_iterations", + SaddleStatus.BadMaxIterations: "max_iterations", + SaddleStatus.BadNotConnected: _UNKNOWN, + SaddleStatus.BadPrefactor: _UNKNOWN, + SaddleStatus.BadHighBarrier: _UNKNOWN, + SaddleStatus.BadMinima: _UNKNOWN, + SaddleStatus.FailedPrefactor: _UNKNOWN, + SaddleStatus.PotentialFailed: "potential_failed", + SaddleStatus.NonnegativeAbort: "nonnegative_abort", + SaddleStatus.NonlocalAbort: "nonlocal_abort", + SaddleStatus.NegativeBarrier: _UNKNOWN, + SaddleStatus.BadMdTrajectoryTooShort: "md_trajectory_too_short", + SaddleStatus.BadNoNegativeModeAtSaddle: "no_negative_mode_at_saddle", + SaddleStatus.BadNoBarrier: "no_barrier", + SaddleStatus.ZeromodeAbort: "zero_mode_abort", + SaddleStatus.OptimizerError: "optimizer_error", + SaddleStatus.DimerLostMode: "dimer_lost_mode", + SaddleStatus.DimerRestoredBest: "dimer_restored_best", + SaddleStatus.BadArtnError: "artn_error", +} + + +MINIMIZATION_STATUS_SLUGS: dict[MinimizationStatus, str] = { + MinimizationStatus.Good: "good", + MinimizationStatus.MaxIterations: "max_iterations", + MinimizationStatus.PotentialFailed: "potential_failed", +} + + +def saddle_status_label(code: int) -> str: + """Human-readable label for a saddle-search termination code. + + Tolerant of unknown codes -- returns a generic placeholder rather + than raising, so a future C++ rev cannot cause an IndexError or + KeyError to crash long-running akmc jobs. + """ + try: + return SADDLE_STATUS_LABELS[SaddleStatus(code)] + except (ValueError, KeyError): + return f"Unknown termination code ({code})" + + +def saddle_status_slug(code: int) -> str: + """Slug-case label for a saddle-search termination code, with + the same unknown-code tolerance as :func:`saddle_status_label`.""" + try: + return SADDLE_STATUS_SLUGS[SaddleStatus(code)] + except (ValueError, KeyError): + return _UNKNOWN + + +def minimization_status_slug(code: int) -> str: + """Slug-case label for a minimization termination code.""" + try: + return MINIMIZATION_STATUS_SLUGS[MinimizationStatus(code)] + except (ValueError, KeyError): + return _UNKNOWN diff --git a/meson.build b/meson.build index ec3697a05..53754760b 100644 --- a/meson.build +++ b/meson.build @@ -7,12 +7,20 @@ project( meson_version: '>= 1.8.0', default_options: [ # 'buildtype=debugoptimized', - 'warning_level=0', + # warning_level=2 -> -Wall -Wextra. Pre-2.15 we shipped 0 + # to suppress build noise ahead of the CECAM school (#117); + # the suppression has outlived its use. =3 also passes + # -fimplicit-none to gfortran which breaks eonclient's + # vendored Fortran TUs that rely on implicit typing -- can't + # bump to 3 without a Fortran-side cleanup. + 'warning_level=2', 'cpp_std=c++20', ], ) -# IMPORTANT!! warning_level=3 passes -fimplicit-none -# eonclient needs implicit typing!! +# Visibility flags applied in client/meson.build once cppc is created +# (-fvisibility=hidden + -fvisibility-inlines-hidden). Cuts the +# dynamic symbol table by ~10x, stops internal namespace symbols from +# leaking into downstream conda envs. host_system = host_machine.system() diff --git a/meson_options.txt b/meson_options.txt index 099e91d1e..c77c866fb 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -5,29 +5,23 @@ option('with_ams', type : 'boolean', value : false) option('with_mpi', type : 'boolean', value : false) option('with_pybind11', type : 'boolean', value : false, description: '(unused) for python bindings') option('with_gprd', type : 'boolean', value : false) -option('with_vasp', type : 'boolean', value : false) option('with_fortran', type : 'boolean', value : true) option('with_cuh2', type : 'boolean', value : true) option('with_tests', type : 'boolean', value : true) option('with_gp_surrogate', type : 'boolean', value : false) option('with_catlearn', type : 'boolean', value : false) -option('with_xtb', type : 'boolean', value : false) option('with_ase_orca', type : 'boolean', value : false) option('with_ase_nwchem', type : 'boolean', value : false) option('with_ase', type : 'boolean', value : false) option('with_qsc', type : 'boolean', value : false) -option('use_mkl', type: 'boolean', value: false, description: 'Enable Intel MKL support') +option('use_mkl', type: 'boolean', value: false, description: 'Hard-link Eigen against Intel MKL (legacy; mutually exclusive with with_flexiblas)') +option('with_flexiblas', type: 'boolean', value: false, description: 'Link Eigen via FlexiBLAS so the runtime BLAS / LAPACK backend is picked by the FLEXIBLAS env var (recommended over use_mkl for portability)') option('gprd_linalg_backend', type: 'combo', choices: ['eigen', 'cusolver', 'kokkos', 'stdpar'], value: 'eigen', description: 'Linear algebra backend for GPR-dimer hot-path operations') option('with_metatomic', type : 'boolean', value : false) option('with_serve', type : 'boolean', value : false, description : 'Enable rgpot-compatible RPC serve mode (eonclient --serve)') option('with_parallel_neb', type : 'boolean', value : false, description : 'Enable parallel NEB bead force evaluations (requires TBB)') option('native_arch', type : 'boolean', value : false, description : 'Use -march=native for CPU-specific optimizations (not portable)') option('fast_math', type : 'boolean', value : false, description : 'Enable -ffast-math (unsafe FP reordering, safe for atomic coords)') -option('with_artn', type : 'boolean', value : false, description : 'Enable ARTn saddle search method (requires Fortran + LAPACK)') -option('artn_libdir', type : 'string', value : '', description : 'Path to libartn.so directory (ARTnResource redeclares the C signatures inline, so no include dir is needed)') -option('with_ira', type : 'boolean', value : false, description : 'Enable IRA structure comparison (requires Fortran + LAPACK)') -option('ira_libdir', type : 'string', value : '', description : 'Path to libira.so directory') -option('ira_includedir', type : 'string', value : '', description : 'Path to iralib_interf.h directory') option('torch_path', type : 'string', value : '', description: 'path to Torch, either provide this or torch_version for pip') option('torch_version', type : 'string', value : '2.9', description: 'path to Torch') option('pip_metatomic', type : 'boolean', value : false, description: 'use pip versions of torch, metatensor, torch, and metatomic') diff --git a/pixi.lock b/pixi.lock index 02f6c6776..233f6ad0e 100644 --- a/pixi.lock +++ b/pixi.lock @@ -114,7 +114,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-h3691f8a_5.conda - pypi: https://download.pytorch.org/whl/jinja2-3.1.6-py3-none-any.whl - pypi: https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/04/73/c188f7e6cb2d1f889b4d36fa2756b4e8d7fc1d3dfa1bb3035525b27d2e38/readcon-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/7a/d5/ac9bcac96b25bc8105d03e0478b8c6c51454e4eb20b7c63b3ffe93e01b3f/readcon-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl osx-64: - conda: https://conda.anaconda.org/conda-forge/osx-64/_openmp_mutex-4.5-7_kmp_llvm.conda @@ -228,7 +228,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h281d3d1_5.conda - pypi: https://download.pytorch.org/whl/jinja2-3.1.6-py3-none-any.whl - pypi: https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/63/c1/d56a0f4e81d0c4bcdafeb43e7c4dc63764cad5ca4465f989256819ac0ee3/readcon-0.8.0-cp312-cp312-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c5/68/4481da1271b58762548db7be86644cea12c8bacda68e02977d66a275f922/readcon-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl osx-arm64: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda @@ -347,7 +347,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://download.pytorch.org/whl/jinja2-3.1.6-py3-none-any.whl - pypi: https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl - - pypi: https://files.pythonhosted.org/packages/68/12/f0b642f03dcfbbc2e238501fa4430919c733e882939f9d4f52c7c72409b8/readcon-0.8.0-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/01/3d/40351bc98d118b47d2251ea4996155c295b13e4e1ed9e0eea13cdf5eb2db/readcon-0.10.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl win-64: - conda: https://conda.anaconda.org/conda-forge/win-64/abseil-cpp-20220623.0-h36ffca9_6.conda @@ -444,7 +444,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: https://download.pytorch.org/whl/jinja2-3.1.6-py3-none-any.whl - pypi: https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/3d/dd/80e7d17ca39acec96a74f7886be857f0d962b826ad73317e195d54882e04/readcon-0.8.0-cp312-cp312-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/90/06/d038074c417fc4311aaf652967a7bb677dd64bad1ae9d72fb1132dd0da84/readcon-0.10.0-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl ci-lammps: channels: @@ -624,7 +624,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/04/73/c188f7e6cb2d1f889b4d36fa2756b4e8d7fc1d3dfa1bb3035525b27d2e38/readcon-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/7a/d5/ac9bcac96b25bc8105d03e0478b8c6c51454e4eb20b7c63b3ffe93e01b3f/readcon-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl @@ -783,7 +783,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/63/c1/d56a0f4e81d0c4bcdafeb43e7c4dc63764cad5ca4465f989256819ac0ee3/readcon-0.8.0-cp312-cp312-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c5/68/4481da1271b58762548db7be86644cea12c8bacda68e02977d66a275f922/readcon-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl @@ -952,7 +952,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/68/12/f0b642f03dcfbbc2e238501fa4430919c733e882939f9d4f52c7c72409b8/readcon-0.8.0-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/01/3d/40351bc98d118b47d2251ea4996155c295b13e4e1ed9e0eea13cdf5eb2db/readcon-0.10.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl @@ -1114,7 +1114,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/cc/bb6395c3f2b6bb739b1d3fc0e71f94e6a1c2e256df496237cbfd13cd74a6/python_hostlist-2.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/04/73/c188f7e6cb2d1f889b4d36fa2756b4e8d7fc1d3dfa1bb3035525b27d2e38/readcon-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/7a/d5/ac9bcac96b25bc8105d03e0478b8c6c51454e4eb20b7c63b3ffe93e01b3f/readcon-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl @@ -1294,7 +1294,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/cc/bb6395c3f2b6bb739b1d3fc0e71f94e6a1c2e256df496237cbfd13cd74a6/python_hostlist-2.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/68/12/f0b642f03dcfbbc2e238501fa4430919c733e882939f9d4f52c7c72409b8/readcon-0.8.0-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/01/3d/40351bc98d118b47d2251ea4996155c295b13e4e1ed9e0eea13cdf5eb2db/readcon-0.10.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl @@ -1447,7 +1447,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/cc/bb6395c3f2b6bb739b1d3fc0e71f94e6a1c2e256df496237cbfd13cd74a6/python_hostlist-2.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/3d/dd/80e7d17ca39acec96a74f7886be857f0d962b826ad73317e195d54882e04/readcon-0.8.0-cp312-cp312-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/90/06/d038074c417fc4311aaf652967a7bb677dd64bad1ae9d72fb1132dd0da84/readcon-0.10.0-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl @@ -1602,7 +1602,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/04/73/c188f7e6cb2d1f889b4d36fa2756b4e8d7fc1d3dfa1bb3035525b27d2e38/readcon-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/7a/d5/ac9bcac96b25bc8105d03e0478b8c6c51454e4eb20b7c63b3ffe93e01b3f/readcon-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl @@ -1747,7 +1747,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/63/c1/d56a0f4e81d0c4bcdafeb43e7c4dc63764cad5ca4465f989256819ac0ee3/readcon-0.8.0-cp312-cp312-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c5/68/4481da1271b58762548db7be86644cea12c8bacda68e02977d66a275f922/readcon-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl @@ -1895,7 +1895,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/68/12/f0b642f03dcfbbc2e238501fa4430919c733e882939f9d4f52c7c72409b8/readcon-0.8.0-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/01/3d/40351bc98d118b47d2251ea4996155c295b13e4e1ed9e0eea13cdf5eb2db/readcon-0.10.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl @@ -2021,7 +2021,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3d/dd/80e7d17ca39acec96a74f7886be857f0d962b826ad73317e195d54882e04/readcon-0.8.0-cp312-cp312-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/90/06/d038074c417fc4311aaf652967a7bb677dd64bad1ae9d72fb1132dd0da84/readcon-0.10.0-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl @@ -2128,7 +2128,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-h3691f8a_5.conda - pypi: https://download.pytorch.org/whl/jinja2-3.1.6-py3-none-any.whl - pypi: https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/04/73/c188f7e6cb2d1f889b4d36fa2756b4e8d7fc1d3dfa1bb3035525b27d2e38/readcon-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/7a/d5/ac9bcac96b25bc8105d03e0478b8c6c51454e4eb20b7c63b3ffe93e01b3f/readcon-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl osx-64: - conda: https://conda.anaconda.org/conda-forge/osx-64/_openmp_mutex-4.5-7_kmp_llvm.conda @@ -2232,7 +2232,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h281d3d1_5.conda - pypi: https://download.pytorch.org/whl/jinja2-3.1.6-py3-none-any.whl - pypi: https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/63/c1/d56a0f4e81d0c4bcdafeb43e7c4dc63764cad5ca4465f989256819ac0ee3/readcon-0.8.0-cp312-cp312-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c5/68/4481da1271b58762548db7be86644cea12c8bacda68e02977d66a275f922/readcon-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl osx-arm64: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda @@ -2341,7 +2341,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://download.pytorch.org/whl/jinja2-3.1.6-py3-none-any.whl - pypi: https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl - - pypi: https://files.pythonhosted.org/packages/68/12/f0b642f03dcfbbc2e238501fa4430919c733e882939f9d4f52c7c72409b8/readcon-0.8.0-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/01/3d/40351bc98d118b47d2251ea4996155c295b13e4e1ed9e0eea13cdf5eb2db/readcon-0.10.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl win-64: - conda: https://conda.anaconda.org/conda-forge/noarch/argcomplete-3.6.3-pyhd8ed1ab_0.conda @@ -2428,7 +2428,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: https://download.pytorch.org/whl/jinja2-3.1.6-py3-none-any.whl - pypi: https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/3d/dd/80e7d17ca39acec96a74f7886be857f0d962b826ad73317e195d54882e04/readcon-0.8.0-cp312-cp312-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/90/06/d038074c417fc4311aaf652967a7bb677dd64bad1ae9d72fb1132dd0da84/readcon-0.10.0-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl dev: channels: @@ -2643,7 +2643,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/cc/bb6395c3f2b6bb739b1d3fc0e71f94e6a1c2e256df496237cbfd13cd74a6/python_hostlist-2.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/04/73/c188f7e6cb2d1f889b4d36fa2756b4e8d7fc1d3dfa1bb3035525b27d2e38/readcon-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/7a/d5/ac9bcac96b25bc8105d03e0478b8c6c51454e4eb20b7c63b3ffe93e01b3f/readcon-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl @@ -2858,7 +2858,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/cc/bb6395c3f2b6bb739b1d3fc0e71f94e6a1c2e256df496237cbfd13cd74a6/python_hostlist-2.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/68/12/f0b642f03dcfbbc2e238501fa4430919c733e882939f9d4f52c7c72409b8/readcon-0.8.0-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/01/3d/40351bc98d118b47d2251ea4996155c295b13e4e1ed9e0eea13cdf5eb2db/readcon-0.10.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl @@ -3023,7 +3023,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/4f/87/424511bdcd02c8d7acf9f65caa09f291a519b16bd83c3fb3374b3d4ae951/pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/04/73/c188f7e6cb2d1f889b4d36fa2756b4e8d7fc1d3dfa1bb3035525b27d2e38/readcon-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/7a/d5/ac9bcac96b25bc8105d03e0478b8c6c51454e4eb20b7c63b3ffe93e01b3f/readcon-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/d1/fe/66d73b76d378ba8cc2fe605920c0c75092e3a65ae746e1e767d9d020a75a/scipy-1.17.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl @@ -3174,7 +3174,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/2c/90/4fcce2c22caf044e660a198d740e7fbc14395619e3cb1abad12192c0826c/pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/63/c1/d56a0f4e81d0c4bcdafeb43e7c4dc63764cad5ca4465f989256819ac0ee3/readcon-0.8.0-cp312-cp312-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c5/68/4481da1271b58762548db7be86644cea12c8bacda68e02977d66a275f922/readcon-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/0b/11/7241a63e73ba5a516f1930ac8d5b44cbbfabd35ac73a2d08ca206df007c4/scipy-1.17.0-cp312-cp312-macosx_10_14_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl @@ -3330,7 +3330,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fd/e0/ed960067543d080691d47d6938ebccbf3976a931c9567ab2fbfab983a5dd/pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/68/12/f0b642f03dcfbbc2e238501fa4430919c733e882939f9d4f52c7c72409b8/readcon-0.8.0-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/01/3d/40351bc98d118b47d2251ea4996155c295b13e4e1ed9e0eea13cdf5eb2db/readcon-0.10.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/ed/1d/5057f812d4f6adc91a20a2d6f2ebcdb517fdbc87ae3acc5633c9b97c8ba5/scipy-1.17.0-cp312-cp312-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl @@ -3460,7 +3460,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/a2/0b/d87733741526541c909bbf159e338dcace4f982daac6e5a8d6be225ca32d/pillow-12.0.0-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3d/dd/80e7d17ca39acec96a74f7886be857f0d962b826ad73317e195d54882e04/readcon-0.8.0-cp312-cp312-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/90/06/d038074c417fc4311aaf652967a7bb677dd64bad1ae9d72fb1132dd0da84/readcon-0.10.0-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/92/ce/672ed546f96d5d41ae78c4b9b02006cedd0b3d6f2bf5bb76ea455c320c28/scipy-1.17.0-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl @@ -3641,7 +3641,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/cc/bb6395c3f2b6bb739b1d3fc0e71f94e6a1c2e256df496237cbfd13cd74a6/python_hostlist-2.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/04/73/c188f7e6cb2d1f889b4d36fa2756b4e8d7fc1d3dfa1bb3035525b27d2e38/readcon-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/7a/d5/ac9bcac96b25bc8105d03e0478b8c6c51454e4eb20b7c63b3ffe93e01b3f/readcon-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/d1/fe/66d73b76d378ba8cc2fe605920c0c75092e3a65ae746e1e767d9d020a75a/scipy-1.17.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl @@ -3833,7 +3833,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/cc/bb6395c3f2b6bb739b1d3fc0e71f94e6a1c2e256df496237cbfd13cd74a6/python_hostlist-2.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/68/12/f0b642f03dcfbbc2e238501fa4430919c733e882939f9d4f52c7c72409b8/readcon-0.8.0-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/01/3d/40351bc98d118b47d2251ea4996155c295b13e4e1ed9e0eea13cdf5eb2db/readcon-0.10.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/ed/1d/5057f812d4f6adc91a20a2d6f2ebcdb517fdbc87ae3acc5633c9b97c8ba5/scipy-1.17.0-cp312-cp312-macosx_12_0_arm64.whl @@ -4002,7 +4002,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/cc/bb6395c3f2b6bb739b1d3fc0e71f94e6a1c2e256df496237cbfd13cd74a6/python_hostlist-2.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/3d/dd/80e7d17ca39acec96a74f7886be857f0d962b826ad73317e195d54882e04/readcon-0.8.0-cp312-cp312-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/90/06/d038074c417fc4311aaf652967a7bb677dd64bad1ae9d72fb1132dd0da84/readcon-0.10.0-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ef/45/615f5babd880b4bd7d405cc0dc348234c5ffb6ed1ea33e152ede08b2072d/rich-14.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl @@ -4178,7 +4178,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/cc/bb6395c3f2b6bb739b1d3fc0e71f94e6a1c2e256df496237cbfd13cd74a6/python_hostlist-2.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/04/73/c188f7e6cb2d1f889b4d36fa2756b4e8d7fc1d3dfa1bb3035525b27d2e38/readcon-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/7a/d5/ac9bcac96b25bc8105d03e0478b8c6c51454e4eb20b7c63b3ffe93e01b3f/readcon-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl @@ -4360,7 +4360,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/cc/bb6395c3f2b6bb739b1d3fc0e71f94e6a1c2e256df496237cbfd13cd74a6/python_hostlist-2.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/68/12/f0b642f03dcfbbc2e238501fa4430919c733e882939f9d4f52c7c72409b8/readcon-0.8.0-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/01/3d/40351bc98d118b47d2251ea4996155c295b13e4e1ed9e0eea13cdf5eb2db/readcon-0.10.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl @@ -4516,7 +4516,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/cc/bb6395c3f2b6bb739b1d3fc0e71f94e6a1c2e256df496237cbfd13cd74a6/python_hostlist-2.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/3d/dd/80e7d17ca39acec96a74f7886be857f0d962b826ad73317e195d54882e04/readcon-0.8.0-cp312-cp312-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/90/06/d038074c417fc4311aaf652967a7bb677dd64bad1ae9d72fb1132dd0da84/readcon-0.10.0-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl @@ -4693,7 +4693,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/4f/87/424511bdcd02c8d7acf9f65caa09f291a519b16bd83c3fb3374b3d4ae951/pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/04/73/c188f7e6cb2d1f889b4d36fa2756b4e8d7fc1d3dfa1bb3035525b27d2e38/readcon-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/7a/d5/ac9bcac96b25bc8105d03e0478b8c6c51454e4eb20b7c63b3ffe93e01b3f/readcon-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/d1/fe/66d73b76d378ba8cc2fe605920c0c75092e3a65ae746e1e767d9d020a75a/scipy-1.17.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl @@ -4853,7 +4853,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/2c/90/4fcce2c22caf044e660a198d740e7fbc14395619e3cb1abad12192c0826c/pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/63/c1/d56a0f4e81d0c4bcdafeb43e7c4dc63764cad5ca4465f989256819ac0ee3/readcon-0.8.0-cp312-cp312-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c5/68/4481da1271b58762548db7be86644cea12c8bacda68e02977d66a275f922/readcon-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/0b/11/7241a63e73ba5a516f1930ac8d5b44cbbfabd35ac73a2d08ca206df007c4/scipy-1.17.0-cp312-cp312-macosx_10_14_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl @@ -5017,7 +5017,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fd/e0/ed960067543d080691d47d6938ebccbf3976a931c9567ab2fbfab983a5dd/pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/68/12/f0b642f03dcfbbc2e238501fa4430919c733e882939f9d4f52c7c72409b8/readcon-0.8.0-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/01/3d/40351bc98d118b47d2251ea4996155c295b13e4e1ed9e0eea13cdf5eb2db/readcon-0.10.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/ed/1d/5057f812d4f6adc91a20a2d6f2ebcdb517fdbc87ae3acc5633c9b97c8ba5/scipy-1.17.0-cp312-cp312-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl @@ -5160,7 +5160,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/a2/0b/d87733741526541c909bbf159e338dcace4f982daac6e5a8d6be225ca32d/pillow-12.0.0-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3d/dd/80e7d17ca39acec96a74f7886be857f0d962b826ad73317e195d54882e04/readcon-0.8.0-cp312-cp312-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/90/06/d038074c417fc4311aaf652967a7bb677dd64bad1ae9d72fb1132dd0da84/readcon-0.10.0-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/92/ce/672ed546f96d5d41ae78c4b9b02006cedd0b3d6f2bf5bb76ea455c320c28/scipy-1.17.0-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl @@ -5362,7 +5362,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/04/73/c188f7e6cb2d1f889b4d36fa2756b4e8d7fc1d3dfa1bb3035525b27d2e38/readcon-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/7a/d5/ac9bcac96b25bc8105d03e0478b8c6c51454e4eb20b7c63b3ffe93e01b3f/readcon-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - pypi: git+https://github.com/HaoZeke/rgpycrumbs.git?rev=52ecbd2#f2e5fd9dba285b22c4fa1414a7fd804f614e5dc0 - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl @@ -5585,7 +5585,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl - - pypi: https://files.pythonhosted.org/packages/63/c1/d56a0f4e81d0c4bcdafeb43e7c4dc63764cad5ca4465f989256819ac0ee3/readcon-0.8.0-cp312-cp312-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c5/68/4481da1271b58762548db7be86644cea12c8bacda68e02977d66a275f922/readcon-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - pypi: git+https://github.com/HaoZeke/rgpycrumbs.git?rev=52ecbd2#f2e5fd9dba285b22c4fa1414a7fd804f614e5dc0 - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl @@ -5812,7 +5812,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl - - pypi: https://files.pythonhosted.org/packages/68/12/f0b642f03dcfbbc2e238501fa4430919c733e882939f9d4f52c7c72409b8/readcon-0.8.0-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/01/3d/40351bc98d118b47d2251ea4996155c295b13e4e1ed9e0eea13cdf5eb2db/readcon-0.10.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - pypi: git+https://github.com/HaoZeke/rgpycrumbs.git?rev=52ecbd2#f2e5fd9dba285b22c4fa1414a7fd804f614e5dc0 - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl @@ -6016,7 +6016,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/3d/dd/80e7d17ca39acec96a74f7886be857f0d962b826ad73317e195d54882e04/readcon-0.8.0-cp312-cp312-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/90/06/d038074c417fc4311aaf652967a7bb677dd64bad1ae9d72fb1132dd0da84/readcon-0.10.0-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - pypi: git+https://github.com/HaoZeke/rgpycrumbs.git?rev=52ecbd2#f2e5fd9dba285b22c4fa1414a7fd804f614e5dc0 - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl @@ -6201,7 +6201,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/af/0f/3b8fdc946b4d9cc8cc1e8af42c4e409468c84441b933d037e101b3d72d86/astroid-3.3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/df/87120e2195f08d760bc5cf8a31cfa2381a6887517aa89453b23f1ae3354f/autodoc_pydantic-2.2.0-py3-none-any.whl - - pypi: git+https://github.com/HaoZeke/chemparseplot.git?rev=f71d79b#f71d79b9b9f913c717e4d97dd3ec9fd496f2c208 + - pypi: git+https://github.com/HaoZeke/chemparseplot.git?rev=f71d79b#c164f43d91ac182b9b556ffa3b11f90faba07367 - pypi: https://files.pythonhosted.org/packages/f9/a2/4c88f17ee50af5093978c4d989936883f959d7d81e1e2fa093863b080c1c/cmcrameri-1.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl @@ -6265,9 +6265,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/cc/bb6395c3f2b6bb739b1d3fc0e71f94e6a1c2e256df496237cbfd13cd74a6/python_hostlist-2.3.0.tar.gz - pypi: https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/04/73/c188f7e6cb2d1f889b4d36fa2756b4e8d7fc1d3dfa1bb3035525b27d2e38/readcon-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/7a/d5/ac9bcac96b25bc8105d03e0478b8c6c51454e4eb20b7c63b3ffe93e01b3f/readcon-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: git+https://github.com/HaoZeke/rgpycrumbs.git?rev=52ecbd2#52ecbd2951c122a53cc8f210db8949aee62f2534 + - pypi: git+https://github.com/HaoZeke/rgpycrumbs.git?rev=52ecbd2#f2e5fd9dba285b22c4fa1414a7fd804f614e5dc0 - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl @@ -6458,7 +6458,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/af/0f/3b8fdc946b4d9cc8cc1e8af42c4e409468c84441b933d037e101b3d72d86/astroid-3.3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/df/87120e2195f08d760bc5cf8a31cfa2381a6887517aa89453b23f1ae3354f/autodoc_pydantic-2.2.0-py3-none-any.whl - - pypi: git+https://github.com/HaoZeke/chemparseplot.git?rev=f71d79b#f71d79b9b9f913c717e4d97dd3ec9fd496f2c208 + - pypi: git+https://github.com/HaoZeke/chemparseplot.git?rev=f71d79b#c164f43d91ac182b9b556ffa3b11f90faba07367 - pypi: https://files.pythonhosted.org/packages/f9/a2/4c88f17ee50af5093978c4d989936883f959d7d81e1e2fa093863b080c1c/cmcrameri-1.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl @@ -6521,9 +6521,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/cc/bb6395c3f2b6bb739b1d3fc0e71f94e6a1c2e256df496237cbfd13cd74a6/python_hostlist-2.3.0.tar.gz - pypi: https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl - - pypi: https://files.pythonhosted.org/packages/68/12/f0b642f03dcfbbc2e238501fa4430919c733e882939f9d4f52c7c72409b8/readcon-0.8.0-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/01/3d/40351bc98d118b47d2251ea4996155c295b13e4e1ed9e0eea13cdf5eb2db/readcon-0.10.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: git+https://github.com/HaoZeke/rgpycrumbs.git?rev=52ecbd2#52ecbd2951c122a53cc8f210db8949aee62f2534 + - pypi: git+https://github.com/HaoZeke/rgpycrumbs.git?rev=52ecbd2#f2e5fd9dba285b22c4fa1414a7fd804f614e5dc0 - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl @@ -6687,7 +6687,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/af/0f/3b8fdc946b4d9cc8cc1e8af42c4e409468c84441b933d037e101b3d72d86/astroid-3.3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/df/87120e2195f08d760bc5cf8a31cfa2381a6887517aa89453b23f1ae3354f/autodoc_pydantic-2.2.0-py3-none-any.whl - - pypi: git+https://github.com/HaoZeke/chemparseplot.git?rev=f71d79b#f71d79b9b9f913c717e4d97dd3ec9fd496f2c208 + - pypi: git+https://github.com/HaoZeke/chemparseplot.git?rev=f71d79b#c164f43d91ac182b9b556ffa3b11f90faba07367 - pypi: https://files.pythonhosted.org/packages/f9/a2/4c88f17ee50af5093978c4d989936883f959d7d81e1e2fa093863b080c1c/cmcrameri-1.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl @@ -6751,9 +6751,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/cc/bb6395c3f2b6bb739b1d3fc0e71f94e6a1c2e256df496237cbfd13cd74a6/python_hostlist-2.3.0.tar.gz - pypi: https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/3d/dd/80e7d17ca39acec96a74f7886be857f0d962b826ad73317e195d54882e04/readcon-0.8.0-cp312-cp312-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/90/06/d038074c417fc4311aaf652967a7bb677dd64bad1ae9d72fb1132dd0da84/readcon-0.10.0-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: git+https://github.com/HaoZeke/rgpycrumbs.git?rev=52ecbd2#52ecbd2951c122a53cc8f210db8949aee62f2534 + - pypi: git+https://github.com/HaoZeke/rgpycrumbs.git?rev=52ecbd2#f2e5fd9dba285b22c4fa1414a7fd804f614e5dc0 - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl @@ -6933,7 +6933,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-h3691f8a_5.conda - pypi: https://download.pytorch.org/whl/jinja2-3.1.6-py3-none-any.whl - pypi: https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/04/73/c188f7e6cb2d1f889b4d36fa2756b4e8d7fc1d3dfa1bb3035525b27d2e38/readcon-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/7a/d5/ac9bcac96b25bc8105d03e0478b8c6c51454e4eb20b7c63b3ffe93e01b3f/readcon-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl osx-64: - conda: https://conda.anaconda.org/conda-forge/osx-64/_openmp_mutex-4.5-7_kmp_llvm.conda @@ -7061,7 +7061,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h281d3d1_5.conda - pypi: https://download.pytorch.org/whl/jinja2-3.1.6-py3-none-any.whl - pypi: https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/63/c1/d56a0f4e81d0c4bcdafeb43e7c4dc63764cad5ca4465f989256819ac0ee3/readcon-0.8.0-cp312-cp312-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c5/68/4481da1271b58762548db7be86644cea12c8bacda68e02977d66a275f922/readcon-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl osx-arm64: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda @@ -7201,7 +7201,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://download.pytorch.org/whl/jinja2-3.1.6-py3-none-any.whl - pypi: https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl - - pypi: https://files.pythonhosted.org/packages/68/12/f0b642f03dcfbbc2e238501fa4430919c733e882939f9d4f52c7c72409b8/readcon-0.8.0-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/01/3d/40351bc98d118b47d2251ea4996155c295b13e4e1ed9e0eea13cdf5eb2db/readcon-0.10.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl metatomic: channels: @@ -7342,7 +7342,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/cc/bb6395c3f2b6bb739b1d3fc0e71f94e6a1c2e256df496237cbfd13cd74a6/python_hostlist-2.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/04/73/c188f7e6cb2d1f889b4d36fa2756b4e8d7fc1d3dfa1bb3035525b27d2e38/readcon-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/7a/d5/ac9bcac96b25bc8105d03e0478b8c6c51454e4eb20b7c63b3ffe93e01b3f/readcon-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/d1/fe/66d73b76d378ba8cc2fe605920c0c75092e3a65ae746e1e767d9d020a75a/scipy-1.17.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl @@ -7500,7 +7500,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/cc/bb6395c3f2b6bb739b1d3fc0e71f94e6a1c2e256df496237cbfd13cd74a6/python_hostlist-2.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/68/12/f0b642f03dcfbbc2e238501fa4430919c733e882939f9d4f52c7c72409b8/readcon-0.8.0-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/01/3d/40351bc98d118b47d2251ea4996155c295b13e4e1ed9e0eea13cdf5eb2db/readcon-0.10.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/ed/1d/5057f812d4f6adc91a20a2d6f2ebcdb517fdbc87ae3acc5633c9b97c8ba5/scipy-1.17.0-cp312-cp312-macosx_12_0_arm64.whl @@ -7640,7 +7640,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/cc/bb6395c3f2b6bb739b1d3fc0e71f94e6a1c2e256df496237cbfd13cd74a6/python_hostlist-2.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/3d/dd/80e7d17ca39acec96a74f7886be857f0d962b826ad73317e195d54882e04/readcon-0.8.0-cp312-cp312-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/90/06/d038074c417fc4311aaf652967a7bb677dd64bad1ae9d72fb1132dd0da84/readcon-0.10.0-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ef/45/615f5babd880b4bd7d405cc0dc348234c5ffb6ed1ea33e152ede08b2072d/rich-14.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl @@ -7790,7 +7790,7 @@ environments: - pypi: https://download.pytorch.org/whl/jinja2-3.1.6-py3-none-any.whl - pypi: https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/29/f8/40e01c350ad9a2b3cb4e6adbcc8a83b17ee50dd5792102b6142385937db5/psutil-7.2.1-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/04/73/c188f7e6cb2d1f889b4d36fa2756b4e8d7fc1d3dfa1bb3035525b27d2e38/readcon-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/7a/d5/ac9bcac96b25bc8105d03e0478b8c6c51454e4eb20b7c63b3ffe93e01b3f/readcon-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl osx-64: - conda: https://conda.anaconda.org/conda-forge/osx-64/_openmp_mutex-4.5-7_kmp_llvm.conda @@ -7910,7 +7910,7 @@ environments: - pypi: https://download.pytorch.org/whl/jinja2-3.1.6-py3-none-any.whl - pypi: https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/c5/cf/5180eb8c8bdf6a503c6919f1da28328bd1e6b3b1b5b9d5b01ae64f019616/psutil-7.2.1-cp36-abi3-macosx_10_9_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/63/c1/d56a0f4e81d0c4bcdafeb43e7c4dc63764cad5ca4465f989256819ac0ee3/readcon-0.8.0-cp312-cp312-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c5/68/4481da1271b58762548db7be86644cea12c8bacda68e02977d66a275f922/readcon-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl osx-arm64: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda @@ -8035,7 +8035,7 @@ environments: - pypi: https://download.pytorch.org/whl/jinja2-3.1.6-py3-none-any.whl - pypi: https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl - pypi: https://files.pythonhosted.org/packages/c5/2c/78e4a789306a92ade5000da4f5de3255202c534acdadc3aac7b5458fadef/psutil-7.2.1-cp36-abi3-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/68/12/f0b642f03dcfbbc2e238501fa4430919c733e882939f9d4f52c7c72409b8/readcon-0.8.0-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/01/3d/40351bc98d118b47d2251ea4996155c295b13e4e1ed9e0eea13cdf5eb2db/readcon-0.10.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl rel: channels: @@ -8176,7 +8176,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/cc/bb6395c3f2b6bb739b1d3fc0e71f94e6a1c2e256df496237cbfd13cd74a6/python_hostlist-2.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/04/73/c188f7e6cb2d1f889b4d36fa2756b4e8d7fc1d3dfa1bb3035525b27d2e38/readcon-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/7a/d5/ac9bcac96b25bc8105d03e0478b8c6c51454e4eb20b7c63b3ffe93e01b3f/readcon-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/d1/fe/66d73b76d378ba8cc2fe605920c0c75092e3a65ae746e1e767d9d020a75a/scipy-1.17.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl @@ -8334,7 +8334,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/cc/bb6395c3f2b6bb739b1d3fc0e71f94e6a1c2e256df496237cbfd13cd74a6/python_hostlist-2.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/68/12/f0b642f03dcfbbc2e238501fa4430919c733e882939f9d4f52c7c72409b8/readcon-0.8.0-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/01/3d/40351bc98d118b47d2251ea4996155c295b13e4e1ed9e0eea13cdf5eb2db/readcon-0.10.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/ed/1d/5057f812d4f6adc91a20a2d6f2ebcdb517fdbc87ae3acc5633c9b97c8ba5/scipy-1.17.0-cp312-cp312-macosx_12_0_arm64.whl @@ -8474,7 +8474,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/cc/bb6395c3f2b6bb739b1d3fc0e71f94e6a1c2e256df496237cbfd13cd74a6/python_hostlist-2.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/3d/dd/80e7d17ca39acec96a74f7886be857f0d962b826ad73317e195d54882e04/readcon-0.8.0-cp312-cp312-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/90/06/d038074c417fc4311aaf652967a7bb677dd64bad1ae9d72fb1132dd0da84/readcon-0.10.0-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ef/45/615f5babd880b4bd7d405cc0dc348234c5ffb6ed1ea33e152ede08b2072d/rich-14.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl @@ -8595,7 +8595,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://download.pytorch.org/whl/jinja2-3.1.6-py3-none-any.whl - pypi: https://download.pytorch.org/whl/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/04/73/c188f7e6cb2d1f889b4d36fa2756b4e8d7fc1d3dfa1bb3035525b27d2e38/readcon-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/7a/d5/ac9bcac96b25bc8105d03e0478b8c6c51454e4eb20b7c63b3ffe93e01b3f/readcon-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl osx-64: - conda: https://conda.anaconda.org/conda-forge/osx-64/_openmp_mutex-4.5-7_kmp_llvm.conda @@ -8703,7 +8703,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: https://download.pytorch.org/whl/jinja2-3.1.6-py3-none-any.whl - pypi: https://download.pytorch.org/whl/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl - - pypi: https://files.pythonhosted.org/packages/63/c1/d56a0f4e81d0c4bcdafeb43e7c4dc63764cad5ca4465f989256819ac0ee3/readcon-0.8.0-cp312-cp312-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c5/68/4481da1271b58762548db7be86644cea12c8bacda68e02977d66a275f922/readcon-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl osx-arm64: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda @@ -8815,7 +8815,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://download.pytorch.org/whl/jinja2-3.1.6-py3-none-any.whl - pypi: https://download.pytorch.org/whl/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/68/12/f0b642f03dcfbbc2e238501fa4430919c733e882939f9d4f52c7c72409b8/readcon-0.8.0-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/01/3d/40351bc98d118b47d2251ea4996155c295b13e4e1ed9e0eea13cdf5eb2db/readcon-0.10.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl win-64: - conda: https://conda.anaconda.org/conda-forge/noarch/argcomplete-3.6.3-pyhd8ed1ab_0.conda @@ -8903,7 +8903,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: https://download.pytorch.org/whl/jinja2-3.1.6-py3-none-any.whl - pypi: https://download.pytorch.org/whl/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/3d/dd/80e7d17ca39acec96a74f7886be857f0d962b826ad73317e195d54882e04/readcon-0.8.0-cp312-cp312-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/90/06/d038074c417fc4311aaf652967a7bb677dd64bad1ae9d72fb1132dd0da84/readcon-0.10.0-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl packages: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 @@ -10250,52 +10250,6 @@ packages: - scipy>=1.11 ; extra == 'test' - xyzrender>=0.1.2 ; extra == 'xyzrender' requires_python: '>=3.10' -- pypi: git+https://github.com/HaoZeke/chemparseplot.git?rev=f71d79b#f71d79b9b9f913c717e4d97dd3ec9fd496f2c208 - name: chemparseplot - version: 1.7.1.dev59+gf71d79b9b - requires_dist: - - numpy>=1.26.2 - - pint>=0.22 - - ase>=3.22 ; extra == 'all' - - cmcrameri>=1.7 ; extra == 'all' - - h5py>=3.0 ; extra == 'all' - - matplotlib>=3.8.2 ; extra == 'all' - - polars>=0.20 ; extra == 'all' - - readcon>=0.7.0 ; extra == 'all' - - xyzrender>=0.1.2 ; extra == 'all' - - mdit-py-plugins>=0.3.4 ; extra == 'doc' - - myst-nb>=1 ; extra == 'doc' - - myst-parser>=2 ; extra == 'doc' - - sphinx-autodoc2>=0.5 ; extra == 'doc' - - sphinx-copybutton>=0.5.2 ; extra == 'doc' - - sphinx-library>=1.1.2 ; extra == 'doc' - - sphinx-sitemap>=2.5.1 ; extra == 'doc' - - sphinx-togglebutton>=0.3.2 ; extra == 'doc' - - sphinx>=7.2.6 ; extra == 'doc' - - sphinxcontrib-apidoc>=0.4 ; extra == 'doc' - - ruff>=0.1.6 ; extra == 'lint' - - ase>=3.22 ; extra == 'neb' - - h5py>=3.0 ; extra == 'neb' - - polars>=0.20 ; extra == 'neb' - - readcon>=0.7.0 ; extra == 'neb' - - cmcrameri>=1.7 ; extra == 'plot' - - matplotlib>=3.8.2 ; extra == 'plot' - - build>=0.10 ; extra == 'release' - - cocogitto>=6.2 ; extra == 'release' - - towncrier>=24.8.0 ; extra == 'release' - - twine>=4.0 ; extra == 'release' - - wheel>=0.40 ; extra == 'release' - - ase>=3.22 ; extra == 'test' - - h5py>=3.0 ; extra == 'test' - - matplotlib>=3.8.2 ; extra == 'test' - - polars>=0.20 ; extra == 'test' - - pytest-cov>=4.1.0 ; extra == 'test' - - pytest>=7.4.3 ; extra == 'test' - - readcon>=0.7.0 ; extra == 'test' - - rgpycrumbs ; extra == 'test' - - scipy>=1.11 ; extra == 'test' - - xyzrender>=0.1.2 ; extra == 'xyzrender' - requires_python: '>=3.10' - conda: https://conda.anaconda.org/conda-forge/osx-64/clang-18.1.8-default_h1323312_16.conda sha256: 50145e6fc98300740ce614d5fbf4542d6f67560c220800d5e5570939164c7690 md5: 8a21120c2c71824085a9b69e0c1a8183 @@ -24557,25 +24511,25 @@ packages: purls: [] size: 1268666 timestamp: 1769154883613 -- pypi: https://files.pythonhosted.org/packages/04/73/c188f7e6cb2d1f889b4d36fa2756b4e8d7fc1d3dfa1bb3035525b27d2e38/readcon-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/01/3d/40351bc98d118b47d2251ea4996155c295b13e4e1ed9e0eea13cdf5eb2db/readcon-0.10.0-cp312-cp312-macosx_11_0_arm64.whl name: readcon - version: 0.8.0 - sha256: c9b7f44b59f499de6d95737e4eeca7da0de20134cd01fc7a227fd236288bc836 + version: 0.10.0 + sha256: 2f12cb332e78bf1771c39963c5e91b7faffd1e7782cb9550410a1e62e89b696c requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/3d/dd/80e7d17ca39acec96a74f7886be857f0d962b826ad73317e195d54882e04/readcon-0.8.0-cp312-cp312-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/7a/d5/ac9bcac96b25bc8105d03e0478b8c6c51454e4eb20b7c63b3ffe93e01b3f/readcon-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl name: readcon - version: 0.8.0 - sha256: 25d0ab830aadf0ad049950b14640b8f7425b7fc99779e2052a189a2058acc18b + version: 0.10.0 + sha256: e5848d27c0cac0d4b249c864c5fdf6ef65b4ff6e25d2bfe8f72326dda10dd2bd requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/63/c1/d56a0f4e81d0c4bcdafeb43e7c4dc63764cad5ca4465f989256819ac0ee3/readcon-0.8.0-cp312-cp312-macosx_10_12_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/90/06/d038074c417fc4311aaf652967a7bb677dd64bad1ae9d72fb1132dd0da84/readcon-0.10.0-cp312-cp312-win_amd64.whl name: readcon - version: 0.8.0 - sha256: e6d191b42c72c0ee2a925b3f8dcbfd8458c93341e6933189dd774f1b3d8fc8c6 + version: 0.10.0 + sha256: 964c12dc6565a64064c5e5542acf2550f7ba4e6fbb09b3474490485071ae93ac requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/68/12/f0b642f03dcfbbc2e238501fa4430919c733e882939f9d4f52c7c72409b8/readcon-0.8.0-cp312-cp312-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/c5/68/4481da1271b58762548db7be86644cea12c8bacda68e02977d66a275f922/readcon-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl name: readcon - version: 0.8.0 - sha256: 41263d323a01a75f5d00854ce01344ae8c4478bbec973e6fe8b3607f33bef94b + version: 0.10.0 + sha256: 9e0da7c29725f5089faf7770fef9684601c422504f5211b1b883be515830c12b requires_python: '>=3.10' - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda sha256: 2d6d0c026902561ed77cd646b5021aef2d4db22e57a5b0178dfc669231e06d2c @@ -24702,32 +24656,6 @@ packages: - pytest-pep723>=0.1.0 ; extra == 'test' - pytest>=9.0 ; extra == 'test' requires_python: '>=3.10' -- pypi: git+https://github.com/HaoZeke/rgpycrumbs.git?rev=52ecbd2#52ecbd2951c122a53cc8f210db8949aee62f2534 - name: rgpycrumbs - version: 1.7.1.dev49+g52ecbd295 - requires_dist: - - click>=8.1 - - numpy>=1.24 - - rich>=13.0 - - ase>=3.22 ; extra == 'all' - - jax[cpu]>=0.4 ; extra == 'all' - - readcon>=0.7.0 ; extra == 'all' - - scipy>=1.11 ; extra == 'all' - - ase>=3.22 ; extra == 'analysis' - - readcon>=0.7.0 ; extra == 'analysis' - - scipy>=1.11 ; extra == 'analysis' - - scipy>=1.11 ; extra == 'interpolation' - - ruff>=0.1.6 ; extra == 'lint' - - build>=0.10 ; extra == 'release' - - cocogitto>=6.2 ; extra == 'release' - - towncrier>=24.8.0 ; extra == 'release' - - twine>=4.0 ; extra == 'release' - - wheel>=0.40 ; extra == 'release' - - jax>=0.4 ; extra == 'surfaces' - - pytest-cov>=4.1.0 ; extra == 'test' - - pytest-pep723>=0.1.0 ; extra == 'test' - - pytest>=9.0 ; extra == 'test' - requires_python: '>=3.10' - conda: https://conda.anaconda.org/conda-forge/linux-64/rhash-1.4.6-hb9d3cd8_1.conda sha256: d5c73079c1dd2c2a313c3bfd81c73dbd066b7eb08d213778c8bff520091ae894 md5: c1c9b02933fdb2cfb791d936c20e887e diff --git a/pixi.toml b/pixi.toml index 1cd798d48..f1251ce26 100644 --- a/pixi.toml +++ b/pixi.toml @@ -10,7 +10,6 @@ version = "2.14.0" [tasks] gen-ref = { cmd = "cd scripts/regression && snakemake -c4", description = "Generate SVN regression reference data (default: svn-Jul_01_2024)" } gen-ref-clean = { cmd = "cd scripts/regression && snakemake clean", description = "Clean regression build artifacts" } -ensure_cbindgen = { cmd = "command -v cbindgen > /dev/null || cargo install --root $CONDA_PREFIX cbindgen", description = "Install cbindgen into the active env's bin (needed by readcon-core; cbindgen is not packaged for conda-forge or PyPI)" } [dependencies] eigen = ">=3.4,<3.5" @@ -36,7 +35,7 @@ rust = ">=1.88" [pypi-dependencies] towncrier = ">=25.8.0, <26" -readcon = ">=0.8.0, <0.9" +readcon = ">=0.10.0, <0.11" [pypi-options] # TODO(rg): only if the CPU version is needed @@ -193,7 +192,6 @@ meson setup bbdir --reconfigure \ --libdir=lib --buildtype {{ buildtype }} \ --native-file nativeFiles/mold.ini \ --native-file nativeFiles/ccache_gnu.ini \ - -Dwith_xtb=False \ -Dwith_metatomic=True \ -Dpip_metatomic=True \ -Dtorch_version=2.9 \ @@ -201,4 +199,3 @@ meson setup bbdir --reconfigure \ -Dwith_gprd=True """ args = ["buildtype"] -depends-on = ["ensure_cbindgen"] diff --git a/pyproject.toml b/pyproject.toml index 726b82a98..4583ededb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ authors = [ ] dependencies = [ "numpy>=1.26.4", - "readcon>=0.8.0", + "readcon>=0.10.0", ] requires-python = ">=3.11" # readme = "readme.md" diff --git a/subprojects/flexiblas.wrap b/subprojects/flexiblas.wrap new file mode 100644 index 000000000..9d3f09e03 --- /dev/null +++ b/subprojects/flexiblas.wrap @@ -0,0 +1,8 @@ +[wrap-git] +directory=flexiblas +url=https://github.com/mpimd-csc/flexiblas.git +revision = v3.5.0 +depth = 1 + +[provide] +dependency_names = flexiblas diff --git a/subprojects/mpitrampoline.wrap b/subprojects/mpitrampoline.wrap new file mode 100644 index 000000000..05cea8f91 --- /dev/null +++ b/subprojects/mpitrampoline.wrap @@ -0,0 +1,8 @@ +[wrap-git] +directory=mpitrampoline +url=https://github.com/eschnett/MPItrampoline.git +revision = v5.5.1 +depth = 1 + +[provide] +dependency_names = MPItrampoline diff --git a/subprojects/rgpot.wrap b/subprojects/rgpot.wrap index c11b3cfca..97065c6b4 100644 --- a/subprojects/rgpot.wrap +++ b/subprojects/rgpot.wrap @@ -1,5 +1,5 @@ [wrap-git] directory=rgpot url=https://github.com/OmniPotentRPC/rgpot.git -revision = head +revision = v1.1.0 depth = 1 diff --git a/tools/mastereqn/Makefile b/tools/mastereqn/Makefile deleted file mode 100644 index edf31fb63..000000000 --- a/tools/mastereqn/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -CXX=g++ -CXXFLAGS=-Wall -Wfatal-errors -O3 -CPPFLAGS=-I../../client -I. -OBJS=mastereqn.o mpreal.o -LDFLAGS=-lgmp -lmpfr -mastereqn: $(OBJS) - $(CXX) -o mastereqn $(OBJS) $(LDFLAGS) - -clean: - rm mastereqn *.o diff --git a/tools/mastereqn/mastereqn.cpp b/tools/mastereqn/mastereqn.cpp deleted file mode 100644 index 759d69d62..000000000 --- a/tools/mastereqn/mastereqn.cpp +++ /dev/null @@ -1,109 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -using namespace std; -using namespace mpfr; -using namespace Eigen; - -typedef Matrix MatrixXmp; -typedef Matrix VectorXmp; - - -void getTime(double *real, double *user, double *sys) -{ - struct timeval time; - gettimeofday(&time, NULL); - *real = (double)time.tv_sec + (double)time.tv_usec/1000000.0; - struct rusage r_usage; - if (getrusage(RUSAGE_SELF, &r_usage)!=0) - { - fprintf(stderr, "problem getting usage info: %s\n", strerror(errno)); - } - if(user != NULL) - { - *user = (double)r_usage.ru_utime.tv_sec + (double)r_usage.ru_utime.tv_usec/1000000.0; - } - if(sys != NULL) - { - *sys = (double)r_usage.ru_stime.tv_sec + (double)r_usage.ru_stime.tv_usec/1000000.0; - } -} - -MatrixXmp readRateMatrix(string filename) { - FILE *fh = fopen(filename.c_str(), "r"); - - char buff[128]; - fgets(buff, 128, fh); - int N = atoi(buff); - MatrixXmp rateMatrix = MatrixXmp(N,N); - - for (int i=0;i eigensolver(rateMatrix); - getTime(&realTime1, NULL, NULL); - fprintf(stderr, "took %.3f seconds\n", realTime1-realTime0); - if (eigensolver.info() != Success) { - fprintf(stderr, "eigensolver failed\n"); - abort(); - } - VectorXmp ew = eigensolver.eigenvalues().real(); - MatrixXmp ev = eigensolver.eigenvectors().real(); - - VectorXmp p0 = VectorXmp::Zero(N); - p0(0) = 1.0; - fprintf(stderr, "converting initial probability vector into eigenvector basis\n"); - getTime(&realTime0, NULL, NULL); - VectorXmp c0 = ev.colPivHouseholderQr().solve(p0); - getTime(&realTime1, NULL, NULL); - fprintf(stderr, "took %.3f seconds\n", realTime1-realTime0); - - printf("set logscale x\n"); - printf("plot \"-\" w l\n"); - - int final_state = 9; - for (int p=15;p>=0;p--) { - mpreal t = pow(10,-p); - mpreal prob = 0.0; - for (int i=0;i -#include "mpreal.h" - -#if defined (MPREAL_HAVE_CUSTOM_MPFR_MALLOC) -#include "dlmalloc.h" -#endif - -using std::ws; -using std::cerr; -using std::endl; -using std::string; -using std::ostream; -using std::istream; - -namespace mpfr{ - -mp_rnd_t mpreal::default_rnd = MPFR_RNDN; //(mpfr_get_default_rounding_mode)(); -mp_prec_t mpreal::default_prec = 64; //(mpfr_get_default_prec)(); -int mpreal::default_base = 10; -int mpreal::double_bits = -1; - -#if defined (MPREAL_HAVE_CUSTOM_MPFR_MALLOC) -bool mpreal::is_custom_malloc = false; -#endif - -// Default constructor: creates mp number and initializes it to 0. -mpreal::mpreal() -{ - -#if defined (MPREAL_HAVE_CUSTOM_MPFR_MALLOC) - set_custom_malloc(); -#endif - - mpfr_init2(mp,default_prec); - mpfr_set_ui(mp,0,default_rnd); - - MPREAL_MSVC_DEBUGVIEW_CODE; -} - -mpreal::mpreal(const mpreal& u) -{ - -#if defined (MPREAL_HAVE_CUSTOM_MPFR_MALLOC) - set_custom_malloc(); -#endif - - mpfr_init2(mp,mpfr_get_prec(u.mp)); - mpfr_set(mp,u.mp,default_rnd); - - MPREAL_MSVC_DEBUGVIEW_CODE; -} - -mpreal::mpreal(const mpfr_t u) -{ - -#if defined (MPREAL_HAVE_CUSTOM_MPFR_MALLOC) - set_custom_malloc(); -#endif - - mpfr_init2(mp,mpfr_get_prec(u)); - mpfr_set(mp,u,default_rnd); - - MPREAL_MSVC_DEBUGVIEW_CODE; -} - -mpreal::mpreal(const mpf_t u) -{ - -#if defined (MPREAL_HAVE_CUSTOM_MPFR_MALLOC) - set_custom_malloc(); -#endif - - mpfr_init2(mp,(mp_prec_t) mpf_get_prec(u)); // (gmp: mp_bitcnt_t) unsigned long -> long (mpfr: mp_prec_t) - mpfr_set_f(mp,u,default_rnd); - - MPREAL_MSVC_DEBUGVIEW_CODE; -} - -mpreal::mpreal(const mpz_t u, mp_prec_t prec, mp_rnd_t mode) -{ - -#if defined (MPREAL_HAVE_CUSTOM_MPFR_MALLOC) - set_custom_malloc(); -#endif - - mpfr_init2(mp,prec); - mpfr_set_z(mp,u,mode); - - MPREAL_MSVC_DEBUGVIEW_CODE; -} - -mpreal::mpreal(const mpq_t u, mp_prec_t prec, mp_rnd_t mode) -{ - -#if defined (MPREAL_HAVE_CUSTOM_MPFR_MALLOC) - set_custom_malloc(); -#endif - - mpfr_init2(mp,prec); - mpfr_set_q(mp,u,mode); - - MPREAL_MSVC_DEBUGVIEW_CODE; -} - -mpreal::mpreal(const double u, mp_prec_t prec, mp_rnd_t mode) -{ - -#if defined (MPREAL_HAVE_CUSTOM_MPFR_MALLOC) - set_custom_malloc(); -#endif - - if(double_bits == -1 || fits_in_bits(u, double_bits)) - { - mpfr_init2(mp,prec); - mpfr_set_d(mp,u,mode); - - MPREAL_MSVC_DEBUGVIEW_CODE; - } - else - throw conversion_overflow(); -} - -mpreal::mpreal(const long double u, mp_prec_t prec, mp_rnd_t mode) -{ - -#if defined (MPREAL_HAVE_CUSTOM_MPFR_MALLOC) - set_custom_malloc(); -#endif - - mpfr_init2(mp,prec); - mpfr_set_ld(mp,u,mode); - - MPREAL_MSVC_DEBUGVIEW_CODE; -} - -mpreal::mpreal(const unsigned long int u, mp_prec_t prec, mp_rnd_t mode) -{ - -#if defined (MPREAL_HAVE_CUSTOM_MPFR_MALLOC) - set_custom_malloc(); -#endif - - mpfr_init2(mp,prec); - mpfr_set_ui(mp,u,mode); - - MPREAL_MSVC_DEBUGVIEW_CODE; -} - -mpreal::mpreal(const unsigned int u, mp_prec_t prec, mp_rnd_t mode) -{ - -#if defined (MPREAL_HAVE_CUSTOM_MPFR_MALLOC) - set_custom_malloc(); -#endif - - mpfr_init2(mp,prec); - mpfr_set_ui(mp,u,mode); - - MPREAL_MSVC_DEBUGVIEW_CODE; -} - -mpreal::mpreal(const long int u, mp_prec_t prec, mp_rnd_t mode) -{ - -#if defined (MPREAL_HAVE_CUSTOM_MPFR_MALLOC) - set_custom_malloc(); -#endif - - mpfr_init2(mp,prec); - mpfr_set_si(mp,u,mode); - - MPREAL_MSVC_DEBUGVIEW_CODE; -} - -mpreal::mpreal(const int u, mp_prec_t prec, mp_rnd_t mode) -{ - -#if defined (MPREAL_HAVE_CUSTOM_MPFR_MALLOC) - set_custom_malloc(); -#endif - - mpfr_init2(mp,prec); - mpfr_set_si(mp,u,mode); - - MPREAL_MSVC_DEBUGVIEW_CODE; -} - -#if defined (MPREAL_HAVE_INT64_SUPPORT) -mpreal::mpreal(const uint64_t u, mp_prec_t prec, mp_rnd_t mode) -{ - -#if defined (MPREAL_HAVE_CUSTOM_MPFR_MALLOC) - set_custom_malloc(); -#endif - - mpfr_init2(mp,prec); - mpfr_set_uj(mp, u, mode); - - MPREAL_MSVC_DEBUGVIEW_CODE; -} - -mpreal::mpreal(const int64_t u, mp_prec_t prec, mp_rnd_t mode) -{ - -#if defined (MPREAL_HAVE_CUSTOM_MPFR_MALLOC) - set_custom_malloc(); -#endif - - mpfr_init2(mp,prec); - mpfr_set_sj(mp, u, mode); - - MPREAL_MSVC_DEBUGVIEW_CODE; -} -#endif - -mpreal::mpreal(const char* s, mp_prec_t prec, int base, mp_rnd_t mode) -{ - -#if defined (MPREAL_HAVE_CUSTOM_MPFR_MALLOC) - set_custom_malloc(); -#endif - - mpfr_init2(mp,prec); - mpfr_set_str(mp, s, base, mode); - - MPREAL_MSVC_DEBUGVIEW_CODE; -} - -mpreal::mpreal(const std::string& s, mp_prec_t prec, int base, mp_rnd_t mode) -{ - -#if defined (MPREAL_HAVE_CUSTOM_MPFR_MALLOC) - set_custom_malloc(); -#endif - - mpfr_init2(mp,prec); - mpfr_set_str(mp, s.c_str(), base, mode); - - MPREAL_MSVC_DEBUGVIEW_CODE; -} - -mpreal::~mpreal() -{ - mpfr_clear(mp); -} - -// Operators - Assignment -mpreal& mpreal::operator=(const char* s) -{ - mpfr_t t; - -#if defined (MPREAL_HAVE_CUSTOM_MPFR_MALLOC) - set_custom_malloc(); -#endif - - if(0==mpfr_init_set_str(t,s,default_base,default_rnd)) - { - // We will rewrite mp anyway, so flash it and resize - mpfr_set_prec(mp,mpfr_get_prec(t)); - mpfr_set(mp,t,mpreal::default_rnd); - mpfr_clear(t); - - MPREAL_MSVC_DEBUGVIEW_CODE; - - }else{ - mpfr_clear(t); - } - - return *this; -} - -const mpreal fma (const mpreal& v1, const mpreal& v2, const mpreal& v3, mp_rnd_t rnd_mode) -{ - mpreal a; - mp_prec_t p1, p2, p3; - - p1 = v1.get_prec(); - p2 = v2.get_prec(); - p3 = v3.get_prec(); - - a.set_prec(p3>p2?(p3>p1?p3:p1):(p2>p1?p2:p1)); - - mpfr_fma(a.mp,v1.mp,v2.mp,v3.mp,rnd_mode); - return a; -} - -const mpreal fms (const mpreal& v1, const mpreal& v2, const mpreal& v3, mp_rnd_t rnd_mode) -{ - mpreal a; - mp_prec_t p1, p2, p3; - - p1 = v1.get_prec(); - p2 = v2.get_prec(); - p3 = v3.get_prec(); - - a.set_prec(p3>p2?(p3>p1?p3:p1):(p2>p1?p2:p1)); - - mpfr_fms(a.mp,v1.mp,v2.mp,v3.mp,rnd_mode); - return a; -} - -const mpreal agm (const mpreal& v1, const mpreal& v2, mp_rnd_t rnd_mode) -{ - mpreal a; - mp_prec_t p1, p2; - - p1 = v1.get_prec(); - p2 = v2.get_prec(); - - a.set_prec(p1>p2?p1:p2); - - mpfr_agm(a.mp, v1.mp, v2.mp, rnd_mode); - - return a; -} - -const mpreal sum (const mpreal tab[], unsigned long int n, mp_rnd_t rnd_mode) -{ - mpreal x; - mpfr_ptr* t; - unsigned long int i; - - t = new mpfr_ptr[n]; - for (i=0;ixp?yp:xp); - - mpfr_remquo(a.mp,q, x.mp, y.mp, rnd_mode); - - return a; -} - -template -std::string toString(T t, std::ios_base & (*f)(std::ios_base&)) -{ - std::ostringstream oss; - oss << f << t; - return oss.str(); -} - -#if (MPFR_VERSION >= MPFR_VERSION_NUM(2,4,0)) - -std::string mpreal::toString(const std::string& format) const -{ - char *s = NULL; - string out; - - if( !format.empty() ) - { - if(!(mpfr_asprintf(&s,format.c_str(),mp) < 0)) - { - out = std::string(s); - - mpfr_free_str(s); - } - } - - return out; -} - -#endif - -std::string mpreal::toString(int n, int b, mp_rnd_t mode) const -{ - (void)b; - (void)mode; -#if (MPFR_VERSION >= MPFR_VERSION_NUM(2,4,0)) - - // Use MPFR native function for output - char format[128]; - int digits; - - digits = n > 0 ? n : bits2digits(mpfr_get_prec(mp)); - - sprintf(format,"%%.%dRNg",digits); // Default format - - return toString(std::string(format)); - -#else - - char *s, *ns = NULL; - size_t slen, nslen; - mp_exp_t exp; - string out; - -#if defined (MPREAL_HAVE_CUSTOM_MPFR_MALLOC) - set_custom_malloc(); -#endif - - if(mpfr_inf_p(mp)) - { - if(mpfr_sgn(mp)>0) return "+Inf"; - else return "-Inf"; - } - - if(mpfr_zero_p(mp)) return "0"; - if(mpfr_nan_p(mp)) return "NaN"; - - s = mpfr_get_str(NULL,&exp,b,0,mp,mode); - ns = mpfr_get_str(NULL,&exp,b,n,mp,mode); - - if(s!=NULL && ns!=NULL) - { - slen = strlen(s); - nslen = strlen(ns); - if(nslen<=slen) - { - mpfr_free_str(s); - s = ns; - slen = nslen; - } - else { - mpfr_free_str(ns); - } - - // Make human eye-friendly formatting if possible - if (exp>0 && static_cast(exp)s+exp) ptr--; - - if(ptr==s+exp) out = string(s,exp+1); - else out = string(s,exp+1)+'.'+string(s+exp+1,ptr-(s+exp+1)+1); - - //out = string(s,exp+1)+'.'+string(s+exp+1); - } - else - { - // Remove zeros starting from right end - char* ptr = s+slen-1; - while (*ptr=='0' && ptr>s+exp-1) ptr--; - - if(ptr==s+exp-1) out = string(s,exp); - else out = string(s,exp)+'.'+string(s+exp,ptr-(s+exp)+1); - - //out = string(s,exp)+'.'+string(s+exp); - } - - }else{ // exp<0 || exp>slen - if(s[0]=='-') - { - // Remove zeros starting from right end - char* ptr = s+slen-1; - while (*ptr=='0' && ptr>s+1) ptr--; - - if(ptr==s+1) out = string(s,2); - else out = string(s,2)+'.'+string(s+2,ptr-(s+2)+1); - - //out = string(s,2)+'.'+string(s+2); - } - else - { - // Remove zeros starting from right end - char* ptr = s+slen-1; - while (*ptr=='0' && ptr>s) ptr--; - - if(ptr==s) out = string(s,1); - else out = string(s,1)+'.'+string(s+1,ptr-(s+1)+1); - - //out = string(s,1)+'.'+string(s+1); - } - - // Make final string - if(--exp) - { - if(exp>0) out += "e+"+mpfr::toString(exp,std::dec); - else out += "e"+mpfr::toString(exp,std::dec); - } - } - - mpfr_free_str(s); - return out; - }else{ - return "conversion error!"; - } -#endif -} - - -////////////////////////////////////////////////////////////////////////// -// I/O -ostream& operator<<(ostream& os, const mpreal& v) -{ - return os<(os.precision())); -} - -istream& operator>>(istream &is, mpreal& v) -{ - string tmp; - is >> tmp; - mpfr_set_str(v.mp, tmp.c_str(),mpreal::default_base,mpreal::default_rnd); - return is; -} - - -#if defined (MPREAL_HAVE_CUSTOM_MPFR_MALLOC) - // Optimized dynamic memory allocation/(re-)deallocation. - void * mpreal::mpreal_allocate(size_t alloc_size) - { - return(dlmalloc(alloc_size)); - } - - void * mpreal::mpreal_reallocate(void *ptr, size_t old_size, size_t new_size) - { - return(dlrealloc(ptr,new_size)); - } - - void mpreal::mpreal_free(void *ptr, size_t size) - { - dlfree(ptr); - } - - inline void mpreal::set_custom_malloc(void) - { - if(!is_custom_malloc) - { - mp_set_memory_functions(mpreal_allocate,mpreal_reallocate,mpreal_free); - is_custom_malloc = true; - } - } -#endif - -} diff --git a/tools/mastereqn/mpreal.h b/tools/mastereqn/mpreal.h deleted file mode 100644 index 69a655f91..000000000 --- a/tools/mastereqn/mpreal.h +++ /dev/null @@ -1,2735 +0,0 @@ -/* - Multi-precision real number class. C++ interface for MPFR library. - Project homepage: http://www.holoborodko.com/pavel/ - Contact e-mail: pavel@holoborodko.com - - Copyright (c) 2008-2012 Pavel Holoborodko - - Core Developers: - Pavel Holoborodko, Dmitriy Gubanov, Konstantin Holoborodko. - - Contributors: - Brian Gladman, Helmut Jarausch, Fokko Beekhof, Ulrich Mutze, - Heinz van Saanen, Pere Constans, Peter van Hoof, Gael Guennebaud, - Tsai Chia Cheng, Alexei Zubanov. - - **************************************************************************** - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - - **************************************************************************** - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. The name of the author may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - SUCH DAMAGE. -*/ - -#ifndef __MPREAL_H__ -#define __MPREAL_H__ - -#include -#include -#include -#include -#include -#include - -// Options -#define MPREAL_HAVE_INT64_SUPPORT // int64_t support: available only for MSVC 2010 & GCC -#define MPREAL_HAVE_MSVC_DEBUGVIEW // Enable Debugger Visualizer (valid only for MSVC in "Debug" builds) - -// Detect compiler using signatures from http://predef.sourceforge.net/ -#if defined(__GNUC__) && defined(__INTEL_COMPILER) - #define IsInf(x) isinf(x) // Intel ICC compiler on Linux - -#elif defined(_MSC_VER) // Microsoft Visual C++ - #define IsInf(x) (!_finite(x)) - -#elif defined(__GNUC__) - #define IsInf(x) std::isinf(x) // GNU C/C++ - -#else - #define IsInf(x) std::isinf(x) // Unknown compiler, just hope for C99 conformance -#endif - -#if defined(MPREAL_HAVE_INT64_SUPPORT) - - #define MPFR_USE_INTMAX_T // should be defined before mpfr.h - - #if defined(_MSC_VER) // is available only in msvc2010! - #if (_MSC_VER >= 1600) - #include - #else // MPFR relies on intmax_t which is available only in msvc2010 - #undef MPREAL_HAVE_INT64_SUPPORT // Besides, MPFR - MPIR have to be compiled with msvc2010 - #undef MPFR_USE_INTMAX_T // Since we cannot detect this, disable x64 by default - // Someone should change this manually if needed. - #endif - #endif - - #if defined (__MINGW32__) || defined(__MINGW64__) - #include // equivalent to msvc2010 - #elif defined (__GNUC__) - #if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) - #undef MPREAL_HAVE_INT64_SUPPORT // remove all shaman dances for x64 builds since - #undef MPFR_USE_INTMAX_T // GCC already support x64 as of "long int" is 64-bit integer, nothing left to do - #else - #include // use int64_t, uint64_t otherwise. - #endif - #endif - -#endif - -#if defined(MPREAL_HAVE_MSVC_DEBUGVIEW) && defined(_MSC_VER) && defined(_DEBUG) -#define MPREAL_MSVC_DEBUGVIEW_CODE DebugView = toString() - #define MPREAL_MSVC_DEBUGVIEW_DATA std::string DebugView -#else - #define MPREAL_MSVC_DEBUGVIEW_CODE - #define MPREAL_MSVC_DEBUGVIEW_DATA -#endif - -#include - -#if (MPFR_VERSION < MPFR_VERSION_NUM(3,0,0)) - #include // needed for random() -#endif - -namespace mpfr { - -class mpreal { -private: - mpfr_t mp; - -public: - static mp_rnd_t default_rnd; - static mp_prec_t default_prec; - static int default_base; - static int double_bits; - -public: - // Constructors && type conversion - mpreal(); - mpreal(const mpreal& u); - mpreal(const mpfr_t u); - mpreal(const mpf_t u); - mpreal(const mpz_t u, mp_prec_t prec = default_prec, mp_rnd_t mode = default_rnd); - mpreal(const mpq_t u, mp_prec_t prec = default_prec, mp_rnd_t mode = default_rnd); - mpreal(const double u, mp_prec_t prec = default_prec, mp_rnd_t mode = default_rnd); - mpreal(const long double u, mp_prec_t prec = default_prec, mp_rnd_t mode = default_rnd); - mpreal(const unsigned long int u, mp_prec_t prec = default_prec, mp_rnd_t mode = default_rnd); - mpreal(const unsigned int u, mp_prec_t prec = default_prec, mp_rnd_t mode = default_rnd); - mpreal(const long int u, mp_prec_t prec = default_prec, mp_rnd_t mode = default_rnd); - mpreal(const int u, mp_prec_t prec = default_prec, mp_rnd_t mode = default_rnd); - -#if defined (MPREAL_HAVE_INT64_SUPPORT) - mpreal(const uint64_t u, mp_prec_t prec = default_prec, mp_rnd_t mode = default_rnd); - mpreal(const int64_t u, mp_prec_t prec = default_prec, mp_rnd_t mode = default_rnd); -#endif - - mpreal(const char* s, mp_prec_t prec = default_prec, int base = default_base, mp_rnd_t mode = default_rnd); - mpreal(const std::string& s, mp_prec_t prec = default_prec, int base = default_base, mp_rnd_t mode = default_rnd); - - ~mpreal(); - - // Operations - // = - // +, -, *, /, ++, --, <<, >> - // *=, +=, -=, /=, - // <, >, ==, <=, >= - - // = - mpreal& operator=(const mpreal& v); - mpreal& operator=(const mpf_t v); - mpreal& operator=(const mpz_t v); - mpreal& operator=(const mpq_t v); - mpreal& operator=(const long double v); - mpreal& operator=(const double v); - mpreal& operator=(const unsigned long int v); - mpreal& operator=(const unsigned int v); - mpreal& operator=(const long int v); - mpreal& operator=(const int v); - mpreal& operator=(const char* s); - - // + - mpreal& operator+=(const mpreal& v); - mpreal& operator+=(const mpf_t v); - mpreal& operator+=(const mpz_t v); - mpreal& operator+=(const mpq_t v); - mpreal& operator+=(const long double u); - mpreal& operator+=(const double u); - mpreal& operator+=(const unsigned long int u); - mpreal& operator+=(const unsigned int u); - mpreal& operator+=(const long int u); - mpreal& operator+=(const int u); - -#if defined (MPREAL_HAVE_INT64_SUPPORT) - mpreal& operator+=(const int64_t u); - mpreal& operator+=(const uint64_t u); - mpreal& operator-=(const int64_t u); - mpreal& operator-=(const uint64_t u); - mpreal& operator*=(const int64_t u); - mpreal& operator*=(const uint64_t u); - mpreal& operator/=(const int64_t u); - mpreal& operator/=(const uint64_t u); -#endif - - const mpreal operator+() const; - mpreal& operator++ (); - const mpreal operator++ (int); - - // - - mpreal& operator-=(const mpreal& v); - mpreal& operator-=(const mpz_t v); - mpreal& operator-=(const mpq_t v); - mpreal& operator-=(const long double u); - mpreal& operator-=(const double u); - mpreal& operator-=(const unsigned long int u); - mpreal& operator-=(const unsigned int u); - mpreal& operator-=(const long int u); - mpreal& operator-=(const int u); - const mpreal operator-() const; - friend const mpreal operator-(const unsigned long int b, const mpreal& a); - friend const mpreal operator-(const unsigned int b, const mpreal& a); - friend const mpreal operator-(const long int b, const mpreal& a); - friend const mpreal operator-(const int b, const mpreal& a); - friend const mpreal operator-(const double b, const mpreal& a); - mpreal& operator-- (); - const mpreal operator-- (int); - - // * - mpreal& operator*=(const mpreal& v); - mpreal& operator*=(const mpz_t v); - mpreal& operator*=(const mpq_t v); - mpreal& operator*=(const long double v); - mpreal& operator*=(const double v); - mpreal& operator*=(const unsigned long int v); - mpreal& operator*=(const unsigned int v); - mpreal& operator*=(const long int v); - mpreal& operator*=(const int v); - - // / - mpreal& operator/=(const mpreal& v); - mpreal& operator/=(const mpz_t v); - mpreal& operator/=(const mpq_t v); - mpreal& operator/=(const long double v); - mpreal& operator/=(const double v); - mpreal& operator/=(const unsigned long int v); - mpreal& operator/=(const unsigned int v); - mpreal& operator/=(const long int v); - mpreal& operator/=(const int v); - friend const mpreal operator/(const unsigned long int b, const mpreal& a); - friend const mpreal operator/(const unsigned int b, const mpreal& a); - friend const mpreal operator/(const long int b, const mpreal& a); - friend const mpreal operator/(const int b, const mpreal& a); - friend const mpreal operator/(const double b, const mpreal& a); - - //<<= Fast Multiplication by 2^u - mpreal& operator<<=(const unsigned long int u); - mpreal& operator<<=(const unsigned int u); - mpreal& operator<<=(const long int u); - mpreal& operator<<=(const int u); - - //>>= Fast Division by 2^u - mpreal& operator>>=(const unsigned long int u); - mpreal& operator>>=(const unsigned int u); - mpreal& operator>>=(const long int u); - mpreal& operator>>=(const int u); - - // Boolean Operators - friend bool operator > (const mpreal& a, const mpreal& b); - friend bool operator >= (const mpreal& a, const mpreal& b); - friend bool operator < (const mpreal& a, const mpreal& b); - friend bool operator <= (const mpreal& a, const mpreal& b); - friend bool operator == (const mpreal& a, const mpreal& b); - friend bool operator != (const mpreal& a, const mpreal& b); - - // Optimized specializations for boolean operators - friend bool operator == (const mpreal& a, const unsigned long int b); - friend bool operator == (const mpreal& a, const unsigned int b); - friend bool operator == (const mpreal& a, const long int b); - friend bool operator == (const mpreal& a, const int b); - friend bool operator == (const mpreal& a, const long double b); - friend bool operator == (const mpreal& a, const double b); - - // Type Conversion operators - long toLong() const; - unsigned long toULong() const; - double toDouble() const; - long double toLDouble() const; - -#if defined (MPREAL_HAVE_INT64_SUPPORT) - int64_t toInt64() const; - uint64_t toUInt64() const; -#endif - - // Get raw pointers - ::mpfr_ptr mpfr_ptr(); - ::mpfr_srcptr mpfr_srcptr() const; - - // Convert mpreal to string with n significant digits in base b - // n = 0 -> convert with the maximum available digits - std::string toString(int n = 0, int b = default_base, mp_rnd_t mode = default_rnd) const; - -#if (MPFR_VERSION >= MPFR_VERSION_NUM(2,4,0)) - std::string toString(const std::string& format) const; -#endif - - // Math Functions - friend const mpreal sqr (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal sqrt(const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal sqrt(const unsigned long int v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal cbrt(const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal root(const mpreal& v, unsigned long int k, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal pow (const mpreal& a, const mpreal& b, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal pow (const mpreal& a, const mpz_t b, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal pow (const mpreal& a, const unsigned long int b, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal pow (const mpreal& a, const long int b, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal pow (const unsigned long int a, const mpreal& b, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal pow (const unsigned long int a, const unsigned long int b, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal fabs(const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - - friend const mpreal abs(const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal dim(const mpreal& a, const mpreal& b, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend inline const mpreal mul_2ui(const mpreal& v, unsigned long int k, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend inline const mpreal mul_2si(const mpreal& v, long int k, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend inline const mpreal div_2ui(const mpreal& v, unsigned long int k, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend inline const mpreal div_2si(const mpreal& v, long int k, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend int cmpabs(const mpreal& a,const mpreal& b); - - friend const mpreal log (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal log2 (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal log10(const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal exp (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal exp2 (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal exp10(const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal log1p(const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal expm1(const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - - friend const mpreal cos(const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal sin(const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal tan(const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal sec(const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal csc(const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal cot(const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend int sin_cos(mpreal& s, mpreal& c, const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - - friend const mpreal acos (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal asin (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal atan (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal atan2 (const mpreal& y, const mpreal& x, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal acot (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal asec (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal acsc (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - - friend const mpreal cosh (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal sinh (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal tanh (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal sech (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal csch (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal coth (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal acosh (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal asinh (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal atanh (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal acoth (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal asech (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal acsch (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - - friend const mpreal hypot (const mpreal& x, const mpreal& y, mp_rnd_t rnd_mode = mpreal::default_rnd); - - friend const mpreal fac_ui (unsigned long int v, mp_prec_t prec = mpreal::default_prec, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal eint (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - - friend const mpreal gamma (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal lngamma (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal lgamma (const mpreal& v, int *signp = 0, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal zeta (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal erf (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal erfc (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal besselj0 (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal besselj1 (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal besseljn (long n, const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal bessely0 (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal bessely1 (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal besselyn (long n, const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal fma (const mpreal& v1, const mpreal& v2, const mpreal& v3, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal fms (const mpreal& v1, const mpreal& v2, const mpreal& v3, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal agm (const mpreal& v1, const mpreal& v2, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal sum (const mpreal tab[], unsigned long int n, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend int sgn(const mpreal& v); // -1 or +1 - -// MPFR 2.4.0 Specifics -#if (MPFR_VERSION >= MPFR_VERSION_NUM(2,4,0)) - friend int sinh_cosh(mpreal& s, mpreal& c, const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal li2(const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal fmod (const mpreal& x, const mpreal& y, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal rec_sqrt(const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); -#endif - -// MPFR 3.0.0 Specifics -#if (MPFR_VERSION >= MPFR_VERSION_NUM(3,0,0)) - friend const mpreal digamma(const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal ai(const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal urandom (gmp_randstate_t& state,mp_rnd_t rnd_mode = mpreal::default_rnd); // use gmp_randinit_default() to init state, gmp_randclear() to clear - friend bool isregular(const mpreal& v); -#endif - - // Uniformly distributed random number generation in [0,1] using - // Mersenne-Twister algorithm by default. - // Use parameter to setup seed, e.g.: random((unsigned)time(NULL)) - // Check urandom() for more precise control. - friend const mpreal random(unsigned int seed = 0); - - // Exponent and mantissa manipulation - friend const mpreal frexp(const mpreal& v, mp_exp_t* exp); - friend const mpreal ldexp(const mpreal& v, mp_exp_t exp); - - // Splits mpreal value into fractional and integer parts. - // Returns fractional part and stores integer part in n. - friend const mpreal modf(const mpreal& v, mpreal& n); - - // Constants - // don't forget to call mpfr_free_cache() for every thread where you are using const-functions - friend const mpreal const_log2 (mp_prec_t prec = mpreal::default_prec, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal const_pi (mp_prec_t prec = mpreal::default_prec, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal const_euler (mp_prec_t prec = mpreal::default_prec, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal const_catalan (mp_prec_t prec = mpreal::default_prec, mp_rnd_t rnd_mode = mpreal::default_rnd); - // returns +inf iff sign>=0 otherwise -inf - friend const mpreal const_infinity(int sign = 1, mp_prec_t prec = mpreal::default_prec, mp_rnd_t rnd_mode = mpreal::default_rnd); - - // Output/ Input - friend std::ostream& operator<<(std::ostream& os, const mpreal& v); - friend std::istream& operator>>(std::istream& is, mpreal& v); - - // Integer Related Functions - friend const mpreal rint (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal ceil (const mpreal& v); - friend const mpreal floor(const mpreal& v); - friend const mpreal round(const mpreal& v); - friend const mpreal trunc(const mpreal& v); - friend const mpreal rint_ceil (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal rint_floor(const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal rint_round(const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal rint_trunc(const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal frac (const mpreal& v, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal remainder (const mpreal& x, const mpreal& y, mp_rnd_t rnd_mode = mpreal::default_rnd); - friend const mpreal remquo (long* q, const mpreal& x, const mpreal& y, mp_rnd_t rnd_mode = mpreal::default_rnd); - - // Miscellaneous Functions - friend const mpreal nexttoward (const mpreal& x, const mpreal& y); - friend const mpreal nextabove (const mpreal& x); - friend const mpreal nextbelow (const mpreal& x); - - // use gmp_randinit_default() to init state, gmp_randclear() to clear - friend const mpreal urandomb (gmp_randstate_t& state); - -// MPFR < 2.4.2 Specifics -#if (MPFR_VERSION <= MPFR_VERSION_NUM(2,4,2)) - friend const mpreal random2 (mp_size_t size, mp_exp_t exp); -#endif - - // Instance Checkers - friend bool isnan (const mpreal& v); - friend bool isinf (const mpreal& v); - friend bool isfinite(const mpreal& v); - - friend bool isnum(const mpreal& v); - friend bool iszero(const mpreal& v); - friend bool isint(const mpreal& v); - - // Set/Get instance properties - inline mp_prec_t get_prec() const; - inline void set_prec(mp_prec_t prec, mp_rnd_t rnd_mode = default_rnd); // Change precision with rounding mode - - // Aliases for get_prec(), set_prec() - needed for compatibility with std::complex interface - inline mpreal& setPrecision(int Precision, mp_rnd_t RoundingMode = (mpfr_get_default_rounding_mode)()); - inline int getPrecision() const; - - // Set mpreal to +/- inf, NaN, +/-0 - mpreal& setInf (int Sign = +1); - mpreal& setNan (); - mpreal& setZero (int Sign = +1); - mpreal& setSign (int Sign, mp_rnd_t RoundingMode = (mpfr_get_default_rounding_mode)()); - - //Exponent - mp_exp_t get_exp(); - int set_exp(mp_exp_t e); - int check_range (int t, mp_rnd_t rnd_mode = default_rnd); - int subnormalize (int t,mp_rnd_t rnd_mode = default_rnd); - - // Inexact conversion from float - inline bool fits_in_bits(double x, int n); - - // Set/Get global properties - static void set_default_prec(mp_prec_t prec); - static mp_prec_t get_default_prec(); - static void set_default_base(int base); - static int get_default_base(); - static void set_double_bits(int dbits); - static int get_double_bits(); - static void set_default_rnd(mp_rnd_t rnd_mode); - static mp_rnd_t get_default_rnd(); - static mp_exp_t get_emin (void); - static mp_exp_t get_emax (void); - static mp_exp_t get_emin_min (void); - static mp_exp_t get_emin_max (void); - static mp_exp_t get_emax_min (void); - static mp_exp_t get_emax_max (void); - static int set_emin (mp_exp_t exp); - static int set_emax (mp_exp_t exp); - - // Efficient swapping of two mpreal values - friend void swap(mpreal& x, mpreal& y); - - //Min Max - macros is evil. Needed for systems which defines max and min globally as macros (e.g. Windows) - //Hope that globally defined macros use > < operations only - friend const mpreal fmax(const mpreal& x, const mpreal& y, mp_rnd_t rnd_mode = default_rnd); - friend const mpreal fmin(const mpreal& x, const mpreal& y, mp_rnd_t rnd_mode = default_rnd); - -#if defined (MPREAL_HAVE_CUSTOM_MPFR_MALLOC) - -private: - // Optimized dynamic memory allocation/(re-)deallocation. - static bool is_custom_malloc; - static void *mpreal_allocate (size_t alloc_size); - static void *mpreal_reallocate (void *ptr, size_t old_size, size_t new_size); - static void mpreal_free (void *ptr, size_t size); - inline static void set_custom_malloc (void); - -#endif - - -private: - // Human friendly Debug Preview in Visual Studio. - // Put one of these lines: - // - // mpfr::mpreal= ; Show value only - // mpfr::mpreal=, bits ; Show value & precision - // - // at the beginning of - // [Visual Studio Installation Folder]\Common7\Packages\Debugger\autoexp.dat - MPREAL_MSVC_DEBUGVIEW_DATA -}; - -////////////////////////////////////////////////////////////////////////// -// Exceptions -class conversion_overflow : public std::exception { -public: - std::string why() { return "inexact conversion from floating point"; } -}; - -namespace internal{ - - // Use SFINAE to restrict arithmetic operations instantiation only for numeric types - // This is needed for smooth integration with libraries based on expression templates - template struct result_type {}; - - template <> struct result_type {typedef mpreal type;}; - template <> struct result_type {typedef mpreal type;}; - template <> struct result_type {typedef mpreal type;}; - template <> struct result_type {typedef mpreal type;}; - template <> struct result_type {typedef mpreal type;}; - template <> struct result_type {typedef mpreal type;}; - template <> struct result_type {typedef mpreal type;}; - template <> struct result_type {typedef mpreal type;}; - template <> struct result_type {typedef mpreal type;}; - -#if defined (MPREAL_HAVE_INT64_SUPPORT) - template <> struct result_type {typedef mpreal type;}; - template <> struct result_type {typedef mpreal type;}; -#endif -} - -// + Addition -template -inline const typename internal::result_type::type - operator+(const mpreal& lhs, const Rhs& rhs){ return mpreal(lhs) += rhs; } - -template -inline const typename internal::result_type::type - operator+(const Lhs& lhs, const mpreal& rhs){ return mpreal(rhs) += lhs; } - -// - Subtraction -template -inline const typename internal::result_type::type - operator-(const mpreal& lhs, const Rhs& rhs){ return mpreal(lhs) -= rhs; } - -template -inline const typename internal::result_type::type - operator-(const Lhs& lhs, const mpreal& rhs){ return mpreal(lhs) -= rhs; } - -// * Multiplication -template -inline const typename internal::result_type::type - operator*(const mpreal& lhs, const Rhs& rhs){ return mpreal(lhs) *= rhs; } - -template -inline const typename internal::result_type::type - operator*(const Lhs& lhs, const mpreal& rhs){ return mpreal(rhs) *= lhs; } - -// / Division -template -inline const typename internal::result_type::type - operator/(const mpreal& lhs, const Rhs& rhs){ return mpreal(lhs) /= rhs; } - -template -inline const typename internal::result_type::type - operator/(const Lhs& lhs, const mpreal& rhs){ return mpreal(lhs) /= rhs; } - -////////////////////////////////////////////////////////////////////////// -// sqrt -const mpreal sqrt(const unsigned int v, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal sqrt(const long int v, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal sqrt(const int v, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal sqrt(const long double v, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal sqrt(const double v, mp_rnd_t rnd_mode = mpreal::default_rnd); - -////////////////////////////////////////////////////////////////////////// -// pow -const mpreal pow(const mpreal& a, const unsigned int b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const mpreal& a, const int b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const mpreal& a, const long double b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const mpreal& a, const double b, mp_rnd_t rnd_mode = mpreal::default_rnd); - -const mpreal pow(const unsigned int a, const mpreal& b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const long int a, const mpreal& b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const int a, const mpreal& b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const long double a, const mpreal& b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const double a, const mpreal& b, mp_rnd_t rnd_mode = mpreal::default_rnd); - -const mpreal pow(const unsigned long int a, const unsigned int b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const unsigned long int a, const long int b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const unsigned long int a, const int b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const unsigned long int a, const long double b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const unsigned long int a, const double b, mp_rnd_t rnd_mode = mpreal::default_rnd); - -const mpreal pow(const unsigned int a, const unsigned long int b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const unsigned int a, const unsigned int b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const unsigned int a, const long int b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const unsigned int a, const int b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const unsigned int a, const long double b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const unsigned int a, const double b, mp_rnd_t rnd_mode = mpreal::default_rnd); - -const mpreal pow(const long int a, const unsigned long int b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const long int a, const unsigned int b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const long int a, const long int b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const long int a, const int b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const long int a, const long double b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const long int a, const double b, mp_rnd_t rnd_mode = mpreal::default_rnd); - -const mpreal pow(const int a, const unsigned long int b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const int a, const unsigned int b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const int a, const long int b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const int a, const int b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const int a, const long double b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const int a, const double b, mp_rnd_t rnd_mode = mpreal::default_rnd); - -const mpreal pow(const long double a, const long double b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const long double a, const unsigned long int b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const long double a, const unsigned int b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const long double a, const long int b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const long double a, const int b, mp_rnd_t rnd_mode = mpreal::default_rnd); - -const mpreal pow(const double a, const double b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const double a, const unsigned long int b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const double a, const unsigned int b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const double a, const long int b, mp_rnd_t rnd_mode = mpreal::default_rnd); -const mpreal pow(const double a, const int b, mp_rnd_t rnd_mode = mpreal::default_rnd); - -////////////////////////////////////////////////////////////////////////// -// Estimate machine epsilon for the given precision -// Returns smallest eps such that 1.0 + eps != 1.0 -inline const mpreal machine_epsilon(mp_prec_t prec = mpreal::get_default_prec()); - -// Returns the positive distance from abs(x) to the next larger in magnitude floating point number of the same precision as x -inline const mpreal machine_epsilon(const mpreal& x); - -inline const mpreal mpreal_min(mp_prec_t prec = mpreal::get_default_prec()); -inline const mpreal mpreal_max(mp_prec_t prec = mpreal::get_default_prec()); -inline bool isEqualFuzzy(const mpreal& a, const mpreal& b, const mpreal& eps); -inline bool isEqualUlps(const mpreal& a, const mpreal& b, int maxUlps); - -////////////////////////////////////////////////////////////////////////// -// Bits - decimal digits relation -// bits = ceil(digits*log[2](10)) -// digits = floor(bits*log[10](2)) - -inline mp_prec_t digits2bits(int d); -inline int bits2digits(mp_prec_t b); - -////////////////////////////////////////////////////////////////////////// -// min, max -const mpreal (max)(const mpreal& x, const mpreal& y); -const mpreal (min)(const mpreal& x, const mpreal& y); - -////////////////////////////////////////////////////////////////////////// -// Implementation -////////////////////////////////////////////////////////////////////////// - -////////////////////////////////////////////////////////////////////////// -// Operators - Assignment -inline mpreal& mpreal::operator=(const mpreal& v) -{ - if (this != &v) - { - mpfr_clear(mp); - mpfr_init2(mp,mpfr_get_prec(v.mp)); - mpfr_set(mp,v.mp,default_rnd); - - MPREAL_MSVC_DEBUGVIEW_CODE; - } - return *this; -} - -inline mpreal& mpreal::operator=(const mpf_t v) -{ - mpfr_set_f(mp,v,default_rnd); - - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator=(const mpz_t v) -{ - mpfr_set_z(mp,v,default_rnd); - - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator=(const mpq_t v) -{ - mpfr_set_q(mp,v,default_rnd); - - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator=(const long double v) -{ - mpfr_set_ld(mp,v,default_rnd); - - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator=(const double v) -{ - if(double_bits == -1 || fits_in_bits(v, double_bits)) - { - mpfr_set_d(mp,v,default_rnd); - - MPREAL_MSVC_DEBUGVIEW_CODE; - } - else - throw conversion_overflow(); - - return *this; -} - -inline mpreal& mpreal::operator=(const unsigned long int v) -{ - mpfr_set_ui(mp,v,default_rnd); - - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator=(const unsigned int v) -{ - mpfr_set_ui(mp,v,default_rnd); - - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator=(const long int v) -{ - mpfr_set_si(mp,v,default_rnd); - - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator=(const int v) -{ - mpfr_set_si(mp,v,default_rnd); - - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -////////////////////////////////////////////////////////////////////////// -// + Addition -inline mpreal& mpreal::operator+=(const mpreal& v) -{ - mpfr_add(mp,mp,v.mp,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator+=(const mpf_t u) -{ - *this += mpreal(u); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator+=(const mpz_t u) -{ - mpfr_add_z(mp,mp,u,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator+=(const mpq_t u) -{ - mpfr_add_q(mp,mp,u,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator+= (const long double u) -{ - *this += mpreal(u); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator+= (const double u) -{ -#if (MPFR_VERSION >= MPFR_VERSION_NUM(2,4,0)) - mpfr_add_d(mp,mp,u,default_rnd); -#else - *this += mpreal(u); -#endif - - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator+=(const unsigned long int u) -{ - mpfr_add_ui(mp,mp,u,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator+=(const unsigned int u) -{ - mpfr_add_ui(mp,mp,u,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator+=(const long int u) -{ - mpfr_add_si(mp,mp,u,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator+=(const int u) -{ - mpfr_add_si(mp,mp,u,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -#if defined (MPREAL_HAVE_INT64_SUPPORT) -inline mpreal& mpreal::operator+=(const int64_t u){ *this += mpreal(u); MPREAL_MSVC_DEBUGVIEW_CODE; return *this; } -inline mpreal& mpreal::operator+=(const uint64_t u){ *this += mpreal(u); MPREAL_MSVC_DEBUGVIEW_CODE; return *this; } -inline mpreal& mpreal::operator-=(const int64_t u){ *this -= mpreal(u); MPREAL_MSVC_DEBUGVIEW_CODE; return *this; } -inline mpreal& mpreal::operator-=(const uint64_t u){ *this -= mpreal(u); MPREAL_MSVC_DEBUGVIEW_CODE; return *this; } -inline mpreal& mpreal::operator*=(const int64_t u){ *this *= mpreal(u); MPREAL_MSVC_DEBUGVIEW_CODE; return *this; } -inline mpreal& mpreal::operator*=(const uint64_t u){ *this *= mpreal(u); MPREAL_MSVC_DEBUGVIEW_CODE; return *this; } -inline mpreal& mpreal::operator/=(const int64_t u){ *this /= mpreal(u); MPREAL_MSVC_DEBUGVIEW_CODE; return *this; } -inline mpreal& mpreal::operator/=(const uint64_t u){ *this /= mpreal(u); MPREAL_MSVC_DEBUGVIEW_CODE; return *this; } -#endif - -inline const mpreal mpreal::operator+()const { return mpreal(*this); } - -inline const mpreal operator+(const mpreal& a, const mpreal& b) -{ - // prec(a+b) = max(prec(a),prec(b)) - if(a.get_prec()>b.get_prec()) return mpreal(a) += b; - else return mpreal(b) += a; -} - -inline mpreal& mpreal::operator++() -{ - return *this += 1; -} - -inline const mpreal mpreal::operator++ (int) -{ - mpreal x(*this); - *this += 1; - return x; -} - -inline mpreal& mpreal::operator--() -{ - return *this -= 1; -} - -inline const mpreal mpreal::operator-- (int) -{ - mpreal x(*this); - *this -= 1; - return x; -} - -////////////////////////////////////////////////////////////////////////// -// - Subtraction -inline mpreal& mpreal::operator-= (const mpreal& v) -{ - mpfr_sub(mp,mp,v.mp,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator-=(const mpz_t v) -{ - mpfr_sub_z(mp,mp,v,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator-=(const mpq_t v) -{ - mpfr_sub_q(mp,mp,v,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator-=(const long double v) -{ - *this -= mpreal(v); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator-=(const double v) -{ -#if (MPFR_VERSION >= MPFR_VERSION_NUM(2,4,0)) - mpfr_sub_d(mp,mp,v,default_rnd); -#else - *this -= mpreal(v); -#endif - - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator-=(const unsigned long int v) -{ - mpfr_sub_ui(mp,mp,v,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator-=(const unsigned int v) -{ - mpfr_sub_ui(mp,mp,v,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator-=(const long int v) -{ - mpfr_sub_si(mp,mp,v,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator-=(const int v) -{ - mpfr_sub_si(mp,mp,v,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline const mpreal mpreal::operator-()const -{ - mpreal u(*this); - mpfr_neg(u.mp,u.mp,default_rnd); - return u; -} - -inline const mpreal operator-(const mpreal& a, const mpreal& b) -{ - // prec(a-b) = max(prec(a),prec(b)) - if(a.getPrecision() >= b.getPrecision()) - { - return mpreal(a) -= b; - }else{ - mpreal x(a); - x.setPrecision(b.getPrecision()); - return x -= b; - } -} - -inline const mpreal operator-(const double b, const mpreal& a) -{ -#if (MPFR_VERSION >= MPFR_VERSION_NUM(2,4,0)) - mpreal x(a); - mpfr_d_sub(x.mp,b,a.mp,mpreal::default_rnd); - return x; -#else - return mpreal(b) -= a; -#endif -} - -inline const mpreal operator-(const unsigned long int b, const mpreal& a) -{ - mpreal x(a); - mpfr_ui_sub(x.mp,b,a.mp,mpreal::default_rnd); - return x; -} - -inline const mpreal operator-(const unsigned int b, const mpreal& a) -{ - mpreal x(a); - mpfr_ui_sub(x.mp,b,a.mp,mpreal::default_rnd); - return x; -} - -inline const mpreal operator-(const long int b, const mpreal& a) -{ - mpreal x(a); - mpfr_si_sub(x.mp,b,a.mp,mpreal::default_rnd); - return x; -} - -inline const mpreal operator-(const int b, const mpreal& a) -{ - mpreal x(a); - mpfr_si_sub(x.mp,b,a.mp,mpreal::default_rnd); - return x; -} - -////////////////////////////////////////////////////////////////////////// -// * Multiplication -inline mpreal& mpreal::operator*= (const mpreal& v) -{ - mpfr_mul(mp,mp,v.mp,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator*=(const mpz_t v) -{ - mpfr_mul_z(mp,mp,v,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator*=(const mpq_t v) -{ - mpfr_mul_q(mp,mp,v,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator*=(const long double v) -{ - *this *= mpreal(v); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator*=(const double v) -{ -#if (MPFR_VERSION >= MPFR_VERSION_NUM(2,4,0)) - mpfr_mul_d(mp,mp,v,default_rnd); -#else - *this *= mpreal(v); -#endif - - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator*=(const unsigned long int v) -{ - mpfr_mul_ui(mp,mp,v,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator*=(const unsigned int v) -{ - mpfr_mul_ui(mp,mp,v,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator*=(const long int v) -{ - mpfr_mul_si(mp,mp,v,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator*=(const int v) -{ - mpfr_mul_si(mp,mp,v,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline const mpreal operator*(const mpreal& a, const mpreal& b) -{ - // prec(a*b) = max(prec(a),prec(b)) - if(a.getPrecision() >= b.getPrecision()) return mpreal(a) *= b; - else return mpreal(b) *= a; -} - -////////////////////////////////////////////////////////////////////////// -// / Division -inline mpreal& mpreal::operator/=(const mpreal& v) -{ - mpfr_div(mp,mp,v.mp,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator/=(const mpz_t v) -{ - mpfr_div_z(mp,mp,v,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator/=(const mpq_t v) -{ - mpfr_div_q(mp,mp,v,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator/=(const long double v) -{ - *this /= mpreal(v); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator/=(const double v) -{ -#if (MPFR_VERSION >= MPFR_VERSION_NUM(2,4,0)) - mpfr_div_d(mp,mp,v,default_rnd); -#else - *this /= mpreal(v); -#endif - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator/=(const unsigned long int v) -{ - mpfr_div_ui(mp,mp,v,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator/=(const unsigned int v) -{ - mpfr_div_ui(mp,mp,v,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator/=(const long int v) -{ - mpfr_div_si(mp,mp,v,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator/=(const int v) -{ - mpfr_div_si(mp,mp,v,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline const mpreal operator/(const mpreal& a, const mpreal& b) -{ - // prec(a/b) = max(prec(a),prec(b)) - if(a.getPrecision() >= b.getPrecision()) - { - return mpreal(a) /= b; - }else{ - - mpreal x(a); - x.setPrecision(b.getPrecision()); - return x /= b; - } -} - -inline const mpreal operator/(const unsigned long int b, const mpreal& a) -{ - mpreal x(a); - mpfr_ui_div(x.mp,b,a.mp,mpreal::default_rnd); - return x; -} - -inline const mpreal operator/(const unsigned int b, const mpreal& a) -{ - mpreal x(a); - mpfr_ui_div(x.mp,b,a.mp,mpreal::default_rnd); - return x; -} - -inline const mpreal operator/(const long int b, const mpreal& a) -{ - mpreal x(a); - mpfr_si_div(x.mp,b,a.mp,mpreal::default_rnd); - return x; -} - -inline const mpreal operator/(const int b, const mpreal& a) -{ - mpreal x(a); - mpfr_si_div(x.mp,b,a.mp,mpreal::default_rnd); - return x; -} - -inline const mpreal operator/(const double b, const mpreal& a) -{ -#if (MPFR_VERSION >= MPFR_VERSION_NUM(2,4,0)) - mpreal x(a); - mpfr_d_div(x.mp,b,a.mp,mpreal::default_rnd); - return x; -#else - return mpreal(b) /= a; -#endif -} - -////////////////////////////////////////////////////////////////////////// -// Shifts operators - Multiplication/Division by power of 2 -inline mpreal& mpreal::operator<<=(const unsigned long int u) -{ - mpfr_mul_2ui(mp,mp,u,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator<<=(const unsigned int u) -{ - mpfr_mul_2ui(mp,mp,static_cast(u),default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator<<=(const long int u) -{ - mpfr_mul_2si(mp,mp,u,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator<<=(const int u) -{ - mpfr_mul_2si(mp,mp,static_cast(u),default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator>>=(const unsigned long int u) -{ - mpfr_div_2ui(mp,mp,u,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator>>=(const unsigned int u) -{ - mpfr_div_2ui(mp,mp,static_cast(u),default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator>>=(const long int u) -{ - mpfr_div_2si(mp,mp,u,default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::operator>>=(const int u) -{ - mpfr_div_2si(mp,mp,static_cast(u),default_rnd); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline const mpreal operator<<(const mpreal& v, const unsigned long int k) -{ - return mul_2ui(v,k); -} - -inline const mpreal operator<<(const mpreal& v, const unsigned int k) -{ - return mul_2ui(v,static_cast(k)); -} - -inline const mpreal operator<<(const mpreal& v, const long int k) -{ - return mul_2si(v,k); -} - -inline const mpreal operator<<(const mpreal& v, const int k) -{ - return mul_2si(v,static_cast(k)); -} - -inline const mpreal operator>>(const mpreal& v, const unsigned long int k) -{ - return div_2ui(v,k); -} - -inline const mpreal operator>>(const mpreal& v, const long int k) -{ - return div_2si(v,k); -} - -inline const mpreal operator>>(const mpreal& v, const unsigned int k) -{ - return div_2ui(v,static_cast(k)); -} - -inline const mpreal operator>>(const mpreal& v, const int k) -{ - return div_2si(v,static_cast(k)); -} - -// mul_2ui -inline const mpreal mul_2ui(const mpreal& v, unsigned long int k, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_mul_2ui(x.mp,v.mp,k,rnd_mode); - return x; -} - -// mul_2si -inline const mpreal mul_2si(const mpreal& v, long int k, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_mul_2si(x.mp,v.mp,k,rnd_mode); - return x; -} - -inline const mpreal div_2ui(const mpreal& v, unsigned long int k, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_div_2ui(x.mp,v.mp,k,rnd_mode); - return x; -} - -inline const mpreal div_2si(const mpreal& v, long int k, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_div_2si(x.mp,v.mp,k,rnd_mode); - return x; -} - -////////////////////////////////////////////////////////////////////////// -//Boolean operators -inline bool operator > (const mpreal& a, const mpreal& b){ return (mpfr_greater_p(a.mp,b.mp) !=0); } -inline bool operator >= (const mpreal& a, const mpreal& b){ return (mpfr_greaterequal_p(a.mp,b.mp) !=0); } -inline bool operator < (const mpreal& a, const mpreal& b){ return (mpfr_less_p(a.mp,b.mp) !=0); } -inline bool operator <= (const mpreal& a, const mpreal& b){ return (mpfr_lessequal_p(a.mp,b.mp) !=0); } -inline bool operator == (const mpreal& a, const mpreal& b){ return (mpfr_equal_p(a.mp,b.mp) !=0); } -inline bool operator != (const mpreal& a, const mpreal& b){ return (mpfr_lessgreater_p(a.mp,b.mp) !=0); } - -inline bool operator == (const mpreal& a, const unsigned long int b ){ return (mpfr_cmp_ui(a.mp,b) == 0); } -inline bool operator == (const mpreal& a, const unsigned int b ){ return (mpfr_cmp_ui(a.mp,b) == 0); } -inline bool operator == (const mpreal& a, const long int b ){ return (mpfr_cmp_si(a.mp,b) == 0); } -inline bool operator == (const mpreal& a, const int b ){ return (mpfr_cmp_si(a.mp,b) == 0); } -inline bool operator == (const mpreal& a, const long double b ){ return (mpfr_cmp_ld(a.mp,b) == 0); } -inline bool operator == (const mpreal& a, const double b ){ return (mpfr_cmp_d(a.mp,b) == 0); } - - -inline bool isnan (const mpreal& v){ return (mpfr_nan_p(v.mp) != 0); } -inline bool isinf (const mpreal& v){ return (mpfr_inf_p(v.mp) != 0); } -inline bool isfinite(const mpreal& v){ return (mpfr_number_p(v.mp) != 0); } -inline bool iszero (const mpreal& v){ return (mpfr_zero_p(v.mp) != 0); } -inline bool isint (const mpreal& v){ return (mpfr_integer_p(v.mp) != 0); } - -#if (MPFR_VERSION >= MPFR_VERSION_NUM(3,0,0)) -inline bool isregular(const mpreal& v){ return (mpfr_regular_p(v.mp));} -#endif - -////////////////////////////////////////////////////////////////////////// -// Type Converters -inline long mpreal::toLong() const { return mpfr_get_si(mp,GMP_RNDZ); } -inline unsigned long mpreal::toULong() const { return mpfr_get_ui(mp,GMP_RNDZ); } -inline double mpreal::toDouble() const { return mpfr_get_d(mp,default_rnd); } -inline long double mpreal::toLDouble() const { return mpfr_get_ld(mp,default_rnd); } - -#if defined (MPREAL_HAVE_INT64_SUPPORT) -inline int64_t mpreal::toInt64() const{ return mpfr_get_sj(mp,GMP_RNDZ); } -inline uint64_t mpreal::toUInt64() const{ return mpfr_get_uj(mp,GMP_RNDZ); } -#endif - -inline ::mpfr_ptr mpreal::mpfr_ptr() { return mp; } -inline ::mpfr_srcptr mpreal::mpfr_srcptr() const { return const_cast< ::mpfr_srcptr >(mp); } - -////////////////////////////////////////////////////////////////////////// -// Bits - decimal digits relation -// bits = ceil(digits*log[2](10)) -// digits = floor(bits*log[10](2)) - -inline mp_prec_t digits2bits(int d) -{ - const double LOG2_10 = 3.3219280948873624; - - d = 10>d?10:d; - - return (mp_prec_t)std::ceil((d)*LOG2_10); -} - -inline int bits2digits(mp_prec_t b) -{ - const double LOG10_2 = 0.30102999566398119; - - b = 34>b?34:b; - - return (int)std::floor((b)*LOG10_2); -} - -////////////////////////////////////////////////////////////////////////// -// Set/Get number properties -inline int sgn(const mpreal& v) -{ - int r = mpfr_signbit(v.mp); - return (r>0?-1:1); -} - -inline mpreal& mpreal::setSign(int sign, mp_rnd_t RoundingMode) -{ - mpfr_setsign(mp,mp,(sign<0?1:0),RoundingMode); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline int mpreal::getPrecision() const -{ - return mpfr_get_prec(mp); -} - -inline mpreal& mpreal::setPrecision(int Precision, mp_rnd_t RoundingMode) -{ - mpfr_prec_round(mp,Precision, RoundingMode); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::setInf(int sign) -{ - mpfr_set_inf(mp,sign); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::setNan() -{ - mpfr_set_nan(mp); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mpreal& mpreal::setZero(int sign) -{ - mpfr_set_zero(mp,sign); - MPREAL_MSVC_DEBUGVIEW_CODE; - return *this; -} - -inline mp_prec_t mpreal::get_prec() const -{ - return mpfr_get_prec(mp); -} - -inline void mpreal::set_prec(mp_prec_t prec, mp_rnd_t rnd_mode) -{ - mpfr_prec_round(mp,prec,rnd_mode); - MPREAL_MSVC_DEBUGVIEW_CODE; -} - -inline mp_exp_t mpreal::get_exp () -{ - return mpfr_get_exp(mp); -} - -inline int mpreal::set_exp (mp_exp_t e) -{ - int x = mpfr_set_exp(mp, e); - MPREAL_MSVC_DEBUGVIEW_CODE; - return x; -} - -inline const mpreal frexp(const mpreal& v, mp_exp_t* exp) -{ - mpreal x(v); - *exp = x.get_exp(); - x.set_exp(0); - return x; -} - -inline const mpreal ldexp(const mpreal& v, mp_exp_t exp) -{ - mpreal x(v); - - // rounding is not important since we just increasing the exponent - mpfr_mul_2si(x.mp,x.mp,exp,mpreal::default_rnd); - return x; -} - -inline const mpreal machine_epsilon(mp_prec_t prec) -{ - // the smallest eps such that 1.0+eps != 1.0 - // depends (of cause) on the precision - return machine_epsilon(mpreal(1,prec)); -} - -inline const mpreal machine_epsilon(const mpreal& x) -{ - if( x < 0) - { - return nextabove(-x)+x; - }else{ - return nextabove(x)-x; - } -} - -inline const mpreal mpreal_min(mp_prec_t prec) -{ - // min = 1/2*2^emin = 2^(emin-1) - - return mpreal(1,prec) << mpreal::get_emin()-1; -} - -inline const mpreal mpreal_max(mp_prec_t prec) -{ - // max = (1-eps)*2^emax, assume eps = 0?, - // and use emax-1 to prevent value to be +inf - // max = 2^(emax-1) - - return mpreal(1,prec) << mpreal::get_emax()-1; -} - -inline bool isEqualUlps(const mpreal& a, const mpreal& b, int maxUlps) -{ - /* - maxUlps - a and b can be apart by maxUlps binary numbers. - */ - return abs(a - b) <= machine_epsilon((max)(abs(a), abs(b))) * maxUlps; -} - -inline bool isEqualFuzzy(const mpreal& a, const mpreal& b, const mpreal& eps) -{ - return abs(a - b) <= (min)(abs(a), abs(b)) * eps; -} - -inline bool isEqualFuzzy(const mpreal& a, const mpreal& b) -{ - return isEqualFuzzy(a,b,machine_epsilon((std::min)(abs(a), abs(b)))); -} - -inline const mpreal modf(const mpreal& v, mpreal& n) -{ - mpreal frac(v); - - // rounding is not important since we are using the same number - mpfr_frac(frac.mp,frac.mp,mpreal::default_rnd); - mpfr_trunc(n.mp,v.mp); - return frac; -} - -inline int mpreal::check_range (int t, mp_rnd_t rnd_mode) -{ - return mpfr_check_range(mp,t,rnd_mode); -} - -inline int mpreal::subnormalize (int t,mp_rnd_t rnd_mode) -{ - int r = mpfr_subnormalize(mp,t,rnd_mode); - MPREAL_MSVC_DEBUGVIEW_CODE; - return r; -} - -inline mp_exp_t mpreal::get_emin (void) -{ - return mpfr_get_emin(); -} - -inline int mpreal::set_emin (mp_exp_t exp) -{ - return mpfr_set_emin(exp); -} - -inline mp_exp_t mpreal::get_emax (void) -{ - return mpfr_get_emax(); -} - -inline int mpreal::set_emax (mp_exp_t exp) -{ - return mpfr_set_emax(exp); -} - -inline mp_exp_t mpreal::get_emin_min (void) -{ - return mpfr_get_emin_min(); -} - -inline mp_exp_t mpreal::get_emin_max (void) -{ - return mpfr_get_emin_max(); -} - -inline mp_exp_t mpreal::get_emax_min (void) -{ - return mpfr_get_emax_min(); -} - -inline mp_exp_t mpreal::get_emax_max (void) -{ - return mpfr_get_emax_max(); -} - -////////////////////////////////////////////////////////////////////////// -// Mathematical Functions -////////////////////////////////////////////////////////////////////////// -inline const mpreal sqr(const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_sqr(x.mp,x.mp,rnd_mode); - return x; -} - -inline const mpreal sqrt(const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_sqrt(x.mp,x.mp,rnd_mode); - return x; -} - -inline const mpreal sqrt(const unsigned long int v, mp_rnd_t rnd_mode) -{ - mpreal x; - mpfr_sqrt_ui(x.mp,v,rnd_mode); - return x; -} - -inline const mpreal sqrt(const unsigned int v, mp_rnd_t rnd_mode) -{ - return sqrt(static_cast(v),rnd_mode); -} - -inline const mpreal sqrt(const long int v, mp_rnd_t rnd_mode) -{ - if (v>=0) return sqrt(static_cast(v),rnd_mode); - else return mpreal().setNan(); // NaN -} - -inline const mpreal sqrt(const int v, mp_rnd_t rnd_mode) -{ - if (v>=0) return sqrt(static_cast(v),rnd_mode); - else return mpreal().setNan(); // NaN -} - -inline const mpreal sqrt(const long double v, mp_rnd_t rnd_mode) -{ - return sqrt(mpreal(v),rnd_mode); -} - -inline const mpreal sqrt(const double v, mp_rnd_t rnd_mode) -{ - return sqrt(mpreal(v),rnd_mode); -} - -inline const mpreal cbrt(const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_cbrt(x.mp,x.mp,rnd_mode); - return x; -} - -inline const mpreal root(const mpreal& v, unsigned long int k, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_root(x.mp,x.mp,k,rnd_mode); - return x; -} - -inline const mpreal fabs(const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_abs(x.mp,x.mp,rnd_mode); - return x; -} - -inline const mpreal abs(const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_abs(x.mp,x.mp,rnd_mode); - return x; -} - -inline const mpreal dim(const mpreal& a, const mpreal& b, mp_rnd_t rnd_mode) -{ - mpreal x(a); - mpfr_dim(x.mp,a.mp,b.mp,rnd_mode); - return x; -} - -inline int cmpabs(const mpreal& a,const mpreal& b) -{ - return mpfr_cmpabs(a.mp,b.mp); -} - -inline const mpreal log (const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_log(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal log2(const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_log2(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal log10(const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_log10(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal exp(const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_exp(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal exp2(const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_exp2(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal exp10(const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_exp10(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal cos(const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_cos(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal sin(const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_sin(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal tan(const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_tan(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal sec(const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_sec(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal csc(const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_csc(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal cot(const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_cot(x.mp,v.mp,rnd_mode); - return x; -} - -inline int sin_cos(mpreal& s, mpreal& c, const mpreal& v, mp_rnd_t rnd_mode) -{ - return mpfr_sin_cos(s.mp,c.mp,v.mp,rnd_mode); -} - -inline const mpreal acos (const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_acos(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal asin (const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_asin(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal atan (const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_atan(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal acot (const mpreal& v, mp_rnd_t rnd_mode) -{ - return atan(1/v, rnd_mode); -} - -inline const mpreal asec (const mpreal& v, mp_rnd_t rnd_mode) -{ - return acos(1/v, rnd_mode); -} - -inline const mpreal acsc (const mpreal& v, mp_rnd_t rnd_mode) -{ - return asin(1/v, rnd_mode); -} - -inline const mpreal acoth (const mpreal& v, mp_rnd_t rnd_mode) -{ - return atanh(1/v, rnd_mode); -} - -inline const mpreal asech (const mpreal& v, mp_rnd_t rnd_mode) -{ - return acosh(1/v, rnd_mode); -} - -inline const mpreal acsch (const mpreal& v, mp_rnd_t rnd_mode) -{ - return asinh(1/v, rnd_mode); -} - -inline const mpreal atan2 (const mpreal& y, const mpreal& x, mp_rnd_t rnd_mode) -{ - mpreal a; - mp_prec_t yp, xp; - - yp = y.get_prec(); - xp = x.get_prec(); - - a.set_prec(yp>xp?yp:xp); - - mpfr_atan2(a.mp, y.mp, x.mp, rnd_mode); - - return a; -} - -inline const mpreal cosh (const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_cosh(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal sinh (const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_sinh(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal tanh (const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_tanh(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal sech (const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_sech(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal csch (const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_csch(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal coth (const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_coth(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal acosh (const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_acosh(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal asinh (const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_asinh(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal atanh (const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_atanh(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal hypot (const mpreal& x, const mpreal& y, mp_rnd_t rnd_mode) -{ - mpreal a; - mp_prec_t yp, xp; - - yp = y.get_prec(); - xp = x.get_prec(); - - a.set_prec(yp>xp?yp:xp); - - mpfr_hypot(a.mp, x.mp, y.mp, rnd_mode); - - return a; -} - -inline const mpreal remainder (const mpreal& x, const mpreal& y, mp_rnd_t rnd_mode) -{ - mpreal a; - mp_prec_t yp, xp; - - yp = y.get_prec(); - xp = x.get_prec(); - - a.set_prec(yp>xp?yp:xp); - - mpfr_remainder(a.mp, x.mp, y.mp, rnd_mode); - - return a; -} - -inline const mpreal fac_ui (unsigned long int v, mp_prec_t prec, mp_rnd_t rnd_mode) -{ - mpreal x(0,prec); - mpfr_fac_ui(x.mp,v,rnd_mode); - return x; -} - -inline const mpreal log1p (const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_log1p(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal expm1 (const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_expm1(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal eint (const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_eint(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal gamma (const mpreal& x, mp_rnd_t rnd_mode) -{ - mpreal FunctionValue(x); - - // x < 0: gamma(-x) = -pi/(x * gamma(x) * sin(pi*x)) - - mpfr_gamma(FunctionValue.mp, x.mp, rnd_mode); - - return FunctionValue; -} - -inline const mpreal lngamma (const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_lngamma(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal lgamma (const mpreal& v, int *signp, mp_rnd_t rnd_mode) -{ - mpreal x(v); - int tsignp; - - if(signp) - mpfr_lgamma(x.mp,signp,v.mp,rnd_mode); - else - mpfr_lgamma(x.mp,&tsignp,v.mp,rnd_mode); - - return x; -} - -inline const mpreal zeta (const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_zeta(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal erf (const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_erf(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal erfc (const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_erfc(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal besselj0 (const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_j0(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal besselj1 (const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_j1(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal besseljn (long n, const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_jn(x.mp,n,v.mp,rnd_mode); - return x; -} - -inline const mpreal bessely0 (const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_y0(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal bessely1 (const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_y1(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal besselyn (long n, const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_yn(x.mp,n,v.mp,rnd_mode); - return x; -} - -////////////////////////////////////////////////////////////////////////// -// MPFR 2.4.0 Specifics -#if (MPFR_VERSION >= MPFR_VERSION_NUM(2,4,0)) - -inline int sinh_cosh(mpreal& s, mpreal& c, const mpreal& v, mp_rnd_t rnd_mode) -{ - return mpfr_sinh_cosh(s.mp,c.mp,v.mp,rnd_mode); -} - -inline const mpreal li2(const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_li2(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal fmod (const mpreal& x, const mpreal& y, mp_rnd_t rnd_mode) -{ - mpreal a; - mp_prec_t yp, xp; - - yp = y.get_prec(); - xp = x.get_prec(); - - a.set_prec(yp>xp?yp:xp); - - mpfr_fmod(a.mp, x.mp, y.mp, rnd_mode); - - return a; -} - -inline const mpreal rec_sqrt(const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_rec_sqrt(x.mp,v.mp,rnd_mode); - return x; -} -#endif // MPFR 2.4.0 Specifics - -////////////////////////////////////////////////////////////////////////// -// MPFR 3.0.0 Specifics -#if (MPFR_VERSION >= MPFR_VERSION_NUM(3,0,0)) - -inline const mpreal digamma(const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_digamma(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal ai(const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_ai(x.mp,v.mp,rnd_mode); - return x; -} - -#endif // MPFR 3.0.0 Specifics - -////////////////////////////////////////////////////////////////////////// -// Constants -inline const mpreal const_log2 (mp_prec_t prec, mp_rnd_t rnd_mode) -{ - mpreal x; - x.set_prec(prec); - mpfr_const_log2(x.mp,rnd_mode); - return x; -} - -inline const mpreal const_pi (mp_prec_t prec, mp_rnd_t rnd_mode) -{ - mpreal x; - x.set_prec(prec); - mpfr_const_pi(x.mp,rnd_mode); - return x; -} - -inline const mpreal const_euler (mp_prec_t prec, mp_rnd_t rnd_mode) -{ - mpreal x; - x.set_prec(prec); - mpfr_const_euler(x.mp,rnd_mode); - return x; -} - -inline const mpreal const_catalan (mp_prec_t prec, mp_rnd_t rnd_mode) -{ - mpreal x; - x.set_prec(prec); - mpfr_const_catalan(x.mp,rnd_mode); - return x; -} - -inline const mpreal const_infinity (int sign, mp_prec_t prec, mp_rnd_t rnd_mode) -{ - mpreal x; - x.set_prec(prec,rnd_mode); - mpfr_set_inf(x.mp, sign); - return x; -} - -////////////////////////////////////////////////////////////////////////// -// Integer Related Functions -inline const mpreal rint(const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_rint(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal ceil(const mpreal& v) -{ - mpreal x(v); - mpfr_ceil(x.mp,v.mp); - return x; - -} - -inline const mpreal floor(const mpreal& v) -{ - mpreal x(v); - mpfr_floor(x.mp,v.mp); - return x; -} - -inline const mpreal round(const mpreal& v) -{ - mpreal x(v); - mpfr_round(x.mp,v.mp); - return x; -} - -inline const mpreal trunc(const mpreal& v) -{ - mpreal x(v); - mpfr_trunc(x.mp,v.mp); - return x; -} - -inline const mpreal rint_ceil (const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_rint_ceil(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal rint_floor(const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_rint_floor(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal rint_round(const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_rint_round(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal rint_trunc(const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_rint_trunc(x.mp,v.mp,rnd_mode); - return x; -} - -inline const mpreal frac (const mpreal& v, mp_rnd_t rnd_mode) -{ - mpreal x(v); - mpfr_frac(x.mp,v.mp,rnd_mode); - return x; -} - -////////////////////////////////////////////////////////////////////////// -// Miscellaneous Functions -inline void swap(mpreal& a, mpreal& b) -{ - mpfr_swap(a.mp,b.mp); -} - -inline const mpreal (max)(const mpreal& x, const mpreal& y) -{ - return (x>y?x:y); -} - -inline const mpreal (min)(const mpreal& x, const mpreal& y) -{ - return (x= MPFR_VERSION_NUM(3,0,0)) -// use gmp_randinit_default() to init state, gmp_randclear() to clear -inline const mpreal urandom (gmp_randstate_t& state, mp_rnd_t rnd_mode) -{ - mpreal x; - mpfr_urandom(x.mp,state,rnd_mode); - return x; -} -#endif - -#if (MPFR_VERSION <= MPFR_VERSION_NUM(2,4,2)) -inline const mpreal random2 (mp_size_t size, mp_exp_t exp) -{ - mpreal x; - mpfr_random2(x.mp,size,exp); - return x; -} -#endif - -// Uniformly distributed random number generation -// a = random(seed); <- initialization & first random number generation -// a = random(); <- next random numbers generation -// seed != 0 -inline const mpreal random(unsigned int seed) -{ - -#if (MPFR_VERSION >= MPFR_VERSION_NUM(3,0,0)) - static gmp_randstate_t state; - static bool isFirstTime = true; - - if(isFirstTime) - { - gmp_randinit_default(state); - gmp_randseed_ui(state,0); - isFirstTime = false; - } - - if(seed != 0) gmp_randseed_ui(state,seed); - - return mpfr::urandom(state); -#else - if(seed != 0) std::srand(seed); - return mpfr::mpreal(std::rand()/(double)RAND_MAX); -#endif - -} - -////////////////////////////////////////////////////////////////////////// -// Set/Get global properties -inline void mpreal::set_default_prec(mp_prec_t prec) -{ - default_prec = prec; - mpfr_set_default_prec(prec); -} - -inline mp_prec_t mpreal::get_default_prec() -{ - return (mpfr_get_default_prec)(); -} - -inline void mpreal::set_default_base(int base) -{ - default_base = base; -} - -inline int mpreal::get_default_base() -{ - return default_base; -} - -inline void mpreal::set_default_rnd(mp_rnd_t rnd_mode) -{ - default_rnd = rnd_mode; - mpfr_set_default_rounding_mode(rnd_mode); -} - -inline mp_rnd_t mpreal::get_default_rnd() -{ - return static_cast((mpfr_get_default_rounding_mode)()); -} - -inline void mpreal::set_double_bits(int dbits) -{ - double_bits = dbits; -} - -inline int mpreal::get_double_bits() -{ - return double_bits; -} - -inline bool mpreal::fits_in_bits(double x, int n) -{ - int i; - double t; - return IsInf(x) || (std::modf ( std::ldexp ( std::frexp ( x, &i ), n ), &t ) == 0.0); -} - -inline const mpreal pow(const mpreal& a, const mpreal& b, mp_rnd_t rnd_mode) -{ - mpreal x(a); - mpfr_pow(x.mp,x.mp,b.mp,rnd_mode); - return x; -} - -inline const mpreal pow(const mpreal& a, const mpz_t b, mp_rnd_t rnd_mode) -{ - mpreal x(a); - mpfr_pow_z(x.mp,x.mp,b,rnd_mode); - return x; -} - -inline const mpreal pow(const mpreal& a, const unsigned long int b, mp_rnd_t rnd_mode) -{ - mpreal x(a); - mpfr_pow_ui(x.mp,x.mp,b,rnd_mode); - return x; -} - -inline const mpreal pow(const mpreal& a, const unsigned int b, mp_rnd_t rnd_mode) -{ - return pow(a,static_cast(b),rnd_mode); -} - -inline const mpreal pow(const mpreal& a, const long int b, mp_rnd_t rnd_mode) -{ - mpreal x(a); - mpfr_pow_si(x.mp,x.mp,b,rnd_mode); - return x; -} - -inline const mpreal pow(const mpreal& a, const int b, mp_rnd_t rnd_mode) -{ - return pow(a,static_cast(b),rnd_mode); -} - -inline const mpreal pow(const mpreal& a, const long double b, mp_rnd_t rnd_mode) -{ - return pow(a,mpreal(b),rnd_mode); -} - -inline const mpreal pow(const mpreal& a, const double b, mp_rnd_t rnd_mode) -{ - return pow(a,mpreal(b),rnd_mode); -} - -inline const mpreal pow(const unsigned long int a, const mpreal& b, mp_rnd_t rnd_mode) -{ - mpreal x(a); - mpfr_ui_pow(x.mp,a,b.mp,rnd_mode); - return x; -} - -inline const mpreal pow(const unsigned int a, const mpreal& b, mp_rnd_t rnd_mode) -{ - return pow(static_cast(a),b,rnd_mode); -} - -inline const mpreal pow(const long int a, const mpreal& b, mp_rnd_t rnd_mode) -{ - if (a>=0) return pow(static_cast(a),b,rnd_mode); - else return pow(mpreal(a),b,rnd_mode); -} - -inline const mpreal pow(const int a, const mpreal& b, mp_rnd_t rnd_mode) -{ - if (a>=0) return pow(static_cast(a),b,rnd_mode); - else return pow(mpreal(a),b,rnd_mode); -} - -inline const mpreal pow(const long double a, const mpreal& b, mp_rnd_t rnd_mode) -{ - return pow(mpreal(a),b,rnd_mode); -} - -inline const mpreal pow(const double a, const mpreal& b, mp_rnd_t rnd_mode) -{ - return pow(mpreal(a),b,rnd_mode); -} - -// pow unsigned long int -inline const mpreal pow(const unsigned long int a, const unsigned long int b, mp_rnd_t rnd_mode) -{ - mpreal x(a); - mpfr_ui_pow_ui(x.mp,a,b,rnd_mode); - return x; -} - -inline const mpreal pow(const unsigned long int a, const unsigned int b, mp_rnd_t rnd_mode) -{ - return pow(a,static_cast(b),rnd_mode); //mpfr_ui_pow_ui -} - -inline const mpreal pow(const unsigned long int a, const long int b, mp_rnd_t rnd_mode) -{ - if(b>0) return pow(a,static_cast(b),rnd_mode); //mpfr_ui_pow_ui - else return pow(a,mpreal(b),rnd_mode); //mpfr_ui_pow -} - -inline const mpreal pow(const unsigned long int a, const int b, mp_rnd_t rnd_mode) -{ - if(b>0) return pow(a,static_cast(b),rnd_mode); //mpfr_ui_pow_ui - else return pow(a,mpreal(b),rnd_mode); //mpfr_ui_pow -} - -inline const mpreal pow(const unsigned long int a, const long double b, mp_rnd_t rnd_mode) -{ - return pow(a,mpreal(b),rnd_mode); //mpfr_ui_pow -} - -inline const mpreal pow(const unsigned long int a, const double b, mp_rnd_t rnd_mode) -{ - return pow(a,mpreal(b),rnd_mode); //mpfr_ui_pow -} - -// pow unsigned int -inline const mpreal pow(const unsigned int a, const unsigned long int b, mp_rnd_t rnd_mode) -{ - return pow(static_cast(a),b,rnd_mode); //mpfr_ui_pow_ui -} - -inline const mpreal pow(const unsigned int a, const unsigned int b, mp_rnd_t rnd_mode) -{ - return pow(static_cast(a),static_cast(b),rnd_mode); //mpfr_ui_pow_ui -} - -inline const mpreal pow(const unsigned int a, const long int b, mp_rnd_t rnd_mode) -{ - if(b>0) return pow(static_cast(a),static_cast(b),rnd_mode); //mpfr_ui_pow_ui - else return pow(static_cast(a),mpreal(b),rnd_mode); //mpfr_ui_pow -} - -inline const mpreal pow(const unsigned int a, const int b, mp_rnd_t rnd_mode) -{ - if(b>0) return pow(static_cast(a),static_cast(b),rnd_mode); //mpfr_ui_pow_ui - else return pow(static_cast(a),mpreal(b),rnd_mode); //mpfr_ui_pow -} - -inline const mpreal pow(const unsigned int a, const long double b, mp_rnd_t rnd_mode) -{ - return pow(static_cast(a),mpreal(b),rnd_mode); //mpfr_ui_pow -} - -inline const mpreal pow(const unsigned int a, const double b, mp_rnd_t rnd_mode) -{ - return pow(static_cast(a),mpreal(b),rnd_mode); //mpfr_ui_pow -} - -// pow long int -inline const mpreal pow(const long int a, const unsigned long int b, mp_rnd_t rnd_mode) -{ - if (a>0) return pow(static_cast(a),b,rnd_mode); //mpfr_ui_pow_ui - else return pow(mpreal(a),b,rnd_mode); //mpfr_pow_ui -} - -inline const mpreal pow(const long int a, const unsigned int b, mp_rnd_t rnd_mode) -{ - if (a>0) return pow(static_cast(a),static_cast(b),rnd_mode); //mpfr_ui_pow_ui - else return pow(mpreal(a),static_cast(b),rnd_mode); //mpfr_pow_ui -} - -inline const mpreal pow(const long int a, const long int b, mp_rnd_t rnd_mode) -{ - if (a>0) - { - if(b>0) return pow(static_cast(a),static_cast(b),rnd_mode); //mpfr_ui_pow_ui - else return pow(static_cast(a),mpreal(b),rnd_mode); //mpfr_ui_pow - }else{ - return pow(mpreal(a),b,rnd_mode); // mpfr_pow_si - } -} - -inline const mpreal pow(const long int a, const int b, mp_rnd_t rnd_mode) -{ - if (a>0) - { - if(b>0) return pow(static_cast(a),static_cast(b),rnd_mode); //mpfr_ui_pow_ui - else return pow(static_cast(a),mpreal(b),rnd_mode); //mpfr_ui_pow - }else{ - return pow(mpreal(a),static_cast(b),rnd_mode); // mpfr_pow_si - } -} - -inline const mpreal pow(const long int a, const long double b, mp_rnd_t rnd_mode) -{ - if (a>=0) return pow(static_cast(a),mpreal(b),rnd_mode); //mpfr_ui_pow - else return pow(mpreal(a),mpreal(b),rnd_mode); //mpfr_pow -} - -inline const mpreal pow(const long int a, const double b, mp_rnd_t rnd_mode) -{ - if (a>=0) return pow(static_cast(a),mpreal(b),rnd_mode); //mpfr_ui_pow - else return pow(mpreal(a),mpreal(b),rnd_mode); //mpfr_pow -} - -// pow int -inline const mpreal pow(const int a, const unsigned long int b, mp_rnd_t rnd_mode) -{ - if (a>0) return pow(static_cast(a),b,rnd_mode); //mpfr_ui_pow_ui - else return pow(mpreal(a),b,rnd_mode); //mpfr_pow_ui -} - -inline const mpreal pow(const int a, const unsigned int b, mp_rnd_t rnd_mode) -{ - if (a>0) return pow(static_cast(a),static_cast(b),rnd_mode); //mpfr_ui_pow_ui - else return pow(mpreal(a),static_cast(b),rnd_mode); //mpfr_pow_ui -} - -inline const mpreal pow(const int a, const long int b, mp_rnd_t rnd_mode) -{ - if (a>0) - { - if(b>0) return pow(static_cast(a),static_cast(b),rnd_mode); //mpfr_ui_pow_ui - else return pow(static_cast(a),mpreal(b),rnd_mode); //mpfr_ui_pow - }else{ - return pow(mpreal(a),b,rnd_mode); // mpfr_pow_si - } -} - -inline const mpreal pow(const int a, const int b, mp_rnd_t rnd_mode) -{ - if (a>0) - { - if(b>0) return pow(static_cast(a),static_cast(b),rnd_mode); //mpfr_ui_pow_ui - else return pow(static_cast(a),mpreal(b),rnd_mode); //mpfr_ui_pow - }else{ - return pow(mpreal(a),static_cast(b),rnd_mode); // mpfr_pow_si - } -} - -inline const mpreal pow(const int a, const long double b, mp_rnd_t rnd_mode) -{ - if (a>=0) return pow(static_cast(a),mpreal(b),rnd_mode); //mpfr_ui_pow - else return pow(mpreal(a),mpreal(b),rnd_mode); //mpfr_pow -} - -inline const mpreal pow(const int a, const double b, mp_rnd_t rnd_mode) -{ - if (a>=0) return pow(static_cast(a),mpreal(b),rnd_mode); //mpfr_ui_pow - else return pow(mpreal(a),mpreal(b),rnd_mode); //mpfr_pow -} - -// pow long double -inline const mpreal pow(const long double a, const long double b, mp_rnd_t rnd_mode) -{ - return pow(mpreal(a),mpreal(b),rnd_mode); -} - -inline const mpreal pow(const long double a, const unsigned long int b, mp_rnd_t rnd_mode) -{ - return pow(mpreal(a),b,rnd_mode); //mpfr_pow_ui -} - -inline const mpreal pow(const long double a, const unsigned int b, mp_rnd_t rnd_mode) -{ - return pow(mpreal(a),static_cast(b),rnd_mode); //mpfr_pow_ui -} - -inline const mpreal pow(const long double a, const long int b, mp_rnd_t rnd_mode) -{ - return pow(mpreal(a),b,rnd_mode); // mpfr_pow_si -} - -inline const mpreal pow(const long double a, const int b, mp_rnd_t rnd_mode) -{ - return pow(mpreal(a),static_cast(b),rnd_mode); // mpfr_pow_si -} - -inline const mpreal pow(const double a, const double b, mp_rnd_t rnd_mode) -{ - return pow(mpreal(a),mpreal(b),rnd_mode); -} - -inline const mpreal pow(const double a, const unsigned long int b, mp_rnd_t rnd_mode) -{ - return pow(mpreal(a),b,rnd_mode); // mpfr_pow_ui -} - -inline const mpreal pow(const double a, const unsigned int b, mp_rnd_t rnd_mode) -{ - return pow(mpreal(a),static_cast(b),rnd_mode); // mpfr_pow_ui -} - -inline const mpreal pow(const double a, const long int b, mp_rnd_t rnd_mode) -{ - return pow(mpreal(a),b,rnd_mode); // mpfr_pow_si -} - -inline const mpreal pow(const double a, const int b, mp_rnd_t rnd_mode) -{ - return pow(mpreal(a),static_cast(b),rnd_mode); // mpfr_pow_si -} -} // End of mpfr namespace - -// Explicit specialization of std::swap for mpreal numbers -// Thus standard algorithms will use efficient version of swap (due to Koenig lookup) -// Non-throwing swap C++ idiom: http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Non-throwing_swap -namespace std -{ - template <> - inline void swap(mpfr::mpreal& x, mpfr::mpreal& y) - { - return mpfr::swap(x, y); - } -} - -#endif /* __MPREAL_H__ */ diff --git a/tools/toykmc/config.py b/tools/toykmc/config.py deleted file mode 100644 index ae6c873b7..000000000 --- a/tools/toykmc/config.py +++ /dev/null @@ -1,16 +0,0 @@ -use_sb = True - - -#tunes barrier size -dEsaddle = .1 - -#these are the directions that atoms are allowed to move -move_neighbors = [(1,0), (-1,0), (0,1), (0,-1), - (1,1), (-1,-1), (1,-1), (-1,1)] - -#these are the first nearest neighbors for energy purposes -energy_neighbors = [(1,0), (-1,0), (0,1), (0,-1)] - -#these are the second nearest neighbors for energy purposes -energy_neighbors_2 = [(1,1), (1,-1), (-1,1), (-1,-1)] -energy_2 = 0.5 #relative strength of 2NN bonds diff --git a/tools/toykmc/go.py b/tools/toykmc/go.py deleted file mode 100644 index dc66aa2c5..000000000 --- a/tools/toykmc/go.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python - -from state import State -import os -import time -from kmc import kmc -import sys - - -s = State.load(sys.argv[1]) - - -t = 0 -time_i = 0 -time_f = 0 -nsteps = 0 -for i in xrange(100000): - time_i = time.time() - s, dt = kmc(s) - time_f = time.time() - - t+=dt - nsteps+=1 - os.system('clear') - print s - print "energy:",s.energy - print "time:",t - print "dt:", dt - print "nsteps:", nsteps - print "nstates:", len(State.states) - if time_f - time_i > 0: - print "step rate:", 1/(time_f - time_i) diff --git a/tools/toykmc/kmc.py b/tools/toykmc/kmc.py deleted file mode 100644 index c933047a1..000000000 --- a/tools/toykmc/kmc.py +++ /dev/null @@ -1,36 +0,0 @@ -import random -from math import log -from state import State -import superbasinscheme - -from config import * - -if use_sb: - superbasining = superbasinscheme.TransitionCounting(50) - -def kmc(s): - ratesum = 0.0 - - dosb = use_sb and superbasining.get_containing_superbasin(s) - if dosb: - rate_table, dt, exit_state = superbasining.get_containing_superbasin(s).step(s) - else: - rate_table = s.get_rate_table() - for proc in rate_table: - ratesum += proc['rate'] - dt = -log(random.random())/ratesum - u = random.random() - p = 0.0 - for proc in rate_table: - p += proc['rate']/ratesum - if p>u: - newst = State.get_state(proc['product'].grid) - if use_sb: - if dosb: - superbasining.register_transition(exit_state, newst) - else: - superbasining.register_transition(s, newst) - return newst, dt - else: - print "Failed to choose process" - return None diff --git a/tools/toykmc/mkstruct.py b/tools/toykmc/mkstruct.py deleted file mode 100644 index c268dff27..000000000 --- a/tools/toykmc/mkstruct.py +++ /dev/null @@ -1,18 +0,0 @@ -import random -from state import State -import sys - -g = [] -w = 20 -h = 20 - -density = .05 -for i in range(h): - u = [] - for j in range(w): - if random.random(),' - 'Michael Foord ') - -__docformat__ = "restructuredtext en" - -__revision__ = '$Id: odict.py 129 2005-09-12 18:15:28Z teknico $' - -__version__ = '0.2.2' - -__all__ = ['OrderedDict', 'SequenceOrderedDict'] - -import sys -INTP_VER = sys.version_info[:2] -if INTP_VER < (2, 2): - raise RuntimeError("Python v.2.2 or later required") - -import types, warnings - -class OrderedDict(dict): - """ - A class of dictionary that keeps the insertion order of keys. - - All appropriate methods return keys, items, or values in an ordered way. - - All normal dictionary methods are available. Update and comparison is - restricted to other OrderedDict objects. - - Various sequence methods are available, including the ability to explicitly - mutate the key ordering. - - __contains__ tests: - - >>> d = OrderedDict(((1, 3),)) - >>> 1 in d - 1 - >>> 4 in d - 0 - - __getitem__ tests: - - >>> OrderedDict(((1, 3), (3, 2), (2, 1)))[2] - 1 - >>> OrderedDict(((1, 3), (3, 2), (2, 1)))[4] - Traceback (most recent call last): - KeyError: 4 - - __len__ tests: - - >>> len(OrderedDict()) - 0 - >>> len(OrderedDict(((1, 3), (3, 2), (2, 1)))) - 3 - - get tests: - - >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) - >>> d.get(1) - 3 - >>> d.get(4) is None - 1 - >>> d.get(4, 5) - 5 - >>> d - OrderedDict([(1, 3), (3, 2), (2, 1)]) - - has_key tests: - - >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) - >>> d.has_key(1) - 1 - >>> d.has_key(4) - 0 - """ - - def __init__(self, init_val=(), strict=False): - """ - Create a new ordered dictionary. Cannot init from a normal dict, - nor from kwargs, since items order is undefined in those cases. - - If the ``strict`` keyword argument is ``True`` (``False`` is the - default) then when doing slice assignment - the ``OrderedDict`` you are - assigning from *must not* contain any keys in the remaining dict. - - >>> OrderedDict() - OrderedDict([]) - >>> OrderedDict({1: 1}) - Traceback (most recent call last): - TypeError: undefined order, cannot get items from dict - >>> OrderedDict({1: 1}.items()) - OrderedDict([(1, 1)]) - >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) - >>> d - OrderedDict([(1, 3), (3, 2), (2, 1)]) - >>> OrderedDict(d) - OrderedDict([(1, 3), (3, 2), (2, 1)]) - """ - self.strict = strict - dict.__init__(self) - if isinstance(init_val, OrderedDict): - self._sequence = init_val.keys() - dict.update(self, init_val) - elif isinstance(init_val, dict): - # we lose compatibility with other ordered dict types this way - raise TypeError('undefined order, cannot get items from dict') - else: - self._sequence = [] - self.update(init_val) - -### Special methods ### - - def __delitem__(self, key): - """ - >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) - >>> del d[3] - >>> d - OrderedDict([(1, 3), (2, 1)]) - >>> del d[3] - Traceback (most recent call last): - KeyError: 3 - >>> d[3] = 2 - >>> d - OrderedDict([(1, 3), (2, 1), (3, 2)]) - >>> del d[0:1] - >>> d - OrderedDict([(2, 1), (3, 2)]) - """ - if isinstance(key, types.SliceType): - # FIXME: efficiency? - keys = self._sequence[key] - for entry in keys: - dict.__delitem__(self, entry) - del self._sequence[key] - else: - # do the dict.__delitem__ *first* as it raises - # the more appropriate error - dict.__delitem__(self, key) - self._sequence.remove(key) - - def __eq__(self, other): - """ - >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) - >>> d == OrderedDict(d) - True - >>> d == OrderedDict(((1, 3), (2, 1), (3, 2))) - False - >>> d == OrderedDict(((1, 0), (3, 2), (2, 1))) - False - >>> d == OrderedDict(((0, 3), (3, 2), (2, 1))) - False - >>> d == dict(d) - False - >>> d == False - False - """ - if isinstance(other, OrderedDict): - # FIXME: efficiency? - # Generate both item lists for each compare - return (self.items() == other.items()) - else: - return False - - def __lt__(self, other): - """ - >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) - >>> c = OrderedDict(((0, 3), (3, 2), (2, 1))) - >>> c < d - True - >>> d < c - False - >>> d < dict(c) - Traceback (most recent call last): - TypeError: Can only compare with other OrderedDicts - """ - if not isinstance(other, OrderedDict): - raise TypeError('Can only compare with other OrderedDicts') - # FIXME: efficiency? - # Generate both item lists for each compare - return (self.items() < other.items()) - - def __le__(self, other): - """ - >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) - >>> c = OrderedDict(((0, 3), (3, 2), (2, 1))) - >>> e = OrderedDict(d) - >>> c <= d - True - >>> d <= c - False - >>> d <= dict(c) - Traceback (most recent call last): - TypeError: Can only compare with other OrderedDicts - >>> d <= e - True - """ - if not isinstance(other, OrderedDict): - raise TypeError('Can only compare with other OrderedDicts') - # FIXME: efficiency? - # Generate both item lists for each compare - return (self.items() <= other.items()) - - def __ne__(self, other): - """ - >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) - >>> d != OrderedDict(d) - False - >>> d != OrderedDict(((1, 3), (2, 1), (3, 2))) - True - >>> d != OrderedDict(((1, 0), (3, 2), (2, 1))) - True - >>> d == OrderedDict(((0, 3), (3, 2), (2, 1))) - False - >>> d != dict(d) - True - >>> d != False - True - """ - if isinstance(other, OrderedDict): - # FIXME: efficiency? - # Generate both item lists for each compare - return not (self.items() == other.items()) - else: - return True - - def __gt__(self, other): - """ - >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) - >>> c = OrderedDict(((0, 3), (3, 2), (2, 1))) - >>> d > c - True - >>> c > d - False - >>> d > dict(c) - Traceback (most recent call last): - TypeError: Can only compare with other OrderedDicts - """ - if not isinstance(other, OrderedDict): - raise TypeError('Can only compare with other OrderedDicts') - # FIXME: efficiency? - # Generate both item lists for each compare - return (self.items() > other.items()) - - def __ge__(self, other): - """ - >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) - >>> c = OrderedDict(((0, 3), (3, 2), (2, 1))) - >>> e = OrderedDict(d) - >>> c >= d - False - >>> d >= c - True - >>> d >= dict(c) - Traceback (most recent call last): - TypeError: Can only compare with other OrderedDicts - >>> e >= d - True - """ - if not isinstance(other, OrderedDict): - raise TypeError('Can only compare with other OrderedDicts') - # FIXME: efficiency? - # Generate both item lists for each compare - return (self.items() >= other.items()) - - def __repr__(self): - """ - Used for __repr__ and __str__ - - >>> r1 = repr(OrderedDict((('a', 'b'), ('c', 'd'), ('e', 'f')))) - >>> r1 - "OrderedDict([('a', 'b'), ('c', 'd'), ('e', 'f')])" - >>> r2 = repr(OrderedDict((('a', 'b'), ('e', 'f'), ('c', 'd')))) - >>> r2 - "OrderedDict([('a', 'b'), ('e', 'f'), ('c', 'd')])" - >>> r1 == str(OrderedDict((('a', 'b'), ('c', 'd'), ('e', 'f')))) - True - >>> r2 == str(OrderedDict((('a', 'b'), ('e', 'f'), ('c', 'd')))) - True - """ - return '%s([%s])' % (self.__class__.__name__, ', '.join( - ['(%r, %r)' % (key, self[key]) for key in self._sequence])) - - def __setitem__(self, key, val): - """ - Allows slice assignment, so long as the slice is an OrderedDict - >>> d = OrderedDict() - >>> d['a'] = 'b' - >>> d['b'] = 'a' - >>> d[3] = 12 - >>> d - OrderedDict([('a', 'b'), ('b', 'a'), (3, 12)]) - >>> d[:] = OrderedDict(((1, 2), (2, 3), (3, 4))) - >>> d - OrderedDict([(1, 2), (2, 3), (3, 4)]) - >>> d[::2] = OrderedDict(((7, 8), (9, 10))) - >>> d - OrderedDict([(7, 8), (2, 3), (9, 10)]) - >>> d = OrderedDict(((0, 1), (1, 2), (2, 3), (3, 4))) - >>> d[1:3] = OrderedDict(((1, 2), (5, 6), (7, 8))) - >>> d - OrderedDict([(0, 1), (1, 2), (5, 6), (7, 8), (3, 4)]) - >>> d = OrderedDict(((0, 1), (1, 2), (2, 3), (3, 4)), strict=True) - >>> d[1:3] = OrderedDict(((1, 2), (5, 6), (7, 8))) - >>> d - OrderedDict([(0, 1), (1, 2), (5, 6), (7, 8), (3, 4)]) - - >>> a = OrderedDict(((0, 1), (1, 2), (2, 3)), strict=True) - >>> a[3] = 4 - >>> a - OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)]) - >>> a[::1] = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)]) - >>> a - OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)]) - >>> a[:2] = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)]) - Traceback (most recent call last): - ValueError: slice assignment must be from unique keys - >>> a = OrderedDict(((0, 1), (1, 2), (2, 3))) - >>> a[3] = 4 - >>> a - OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)]) - >>> a[::1] = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)]) - >>> a - OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)]) - >>> a[:2] = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)]) - >>> a - OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)]) - >>> a[::-1] = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)]) - >>> a - OrderedDict([(3, 4), (2, 3), (1, 2), (0, 1)]) - - >>> d = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)]) - >>> d[:1] = 3 - Traceback (most recent call last): - TypeError: slice assignment requires an OrderedDict - - >>> d = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)]) - >>> d[:1] = OrderedDict([(9, 8)]) - >>> d - OrderedDict([(9, 8), (1, 2), (2, 3), (3, 4)]) - """ - if isinstance(key, types.SliceType): - if not isinstance(val, OrderedDict): - # FIXME: allow a list of tuples? - raise TypeError('slice assignment requires an OrderedDict') - keys = self._sequence[key] - # NOTE: Could use ``range(*key.indices(len(self._sequence)))`` - indexes = range(len(self._sequence))[key] - if key.step is None: - # NOTE: new slice may not be the same size as the one being - # overwritten ! - # NOTE: What is the algorithm for an impossible slice? - # e.g. d[5:3] - pos = key.start or 0 - del self[key] - newkeys = val.keys() - for k in newkeys: - if k in self: - if self.strict: - raise ValueError('slice assignment must be from ' - 'unique keys') - else: - # NOTE: This removes duplicate keys *first* - # so start position might have changed? - del self[k] - self._sequence = (self._sequence[:pos] + newkeys + - self._sequence[pos:]) - dict.update(self, val) - else: - # extended slice - length of new slice must be the same - # as the one being replaced - if len(keys) != len(val): - raise ValueError('attempt to assign sequence of size %s ' - 'to extended slice of size %s' % (len(val), len(keys))) - # FIXME: efficiency? - del self[key] - item_list = zip(indexes, val.items()) - # smallest indexes first - higher indexes not guaranteed to - # exist - item_list.sort() - for pos, (newkey, newval) in item_list: - if self.strict and newkey in self: - raise ValueError('slice assignment must be from unique' - ' keys') - self.insert(pos, newkey, newval) - else: - if key not in self: - self._sequence.append(key) - dict.__setitem__(self, key, val) - - def __getitem__(self, key): - """ - Allows slicing. Returns an OrderedDict if you slice. - >>> b = OrderedDict([(7, 0), (6, 1), (5, 2), (4, 3), (3, 4), (2, 5), (1, 6)]) - >>> b[::-1] - OrderedDict([(1, 6), (2, 5), (3, 4), (4, 3), (5, 2), (6, 1), (7, 0)]) - >>> b[2:5] - OrderedDict([(5, 2), (4, 3), (3, 4)]) - >>> type(b[2:4]) - - """ - if isinstance(key, types.SliceType): - # FIXME: does this raise the error we want? - keys = self._sequence[key] - # FIXME: efficiency? - return OrderedDict([(entry, self[entry]) for entry in keys]) - else: - return dict.__getitem__(self, key) - - __str__ = __repr__ - - def __setattr__(self, name, value): - """ - Implemented so that accesses to ``sequence`` raise a warning and are - diverted to the new ``setkeys`` method. - """ - if name == 'sequence': - warnings.warn('Use of the sequence attribute is deprecated.' - ' Use the keys method instead.', DeprecationWarning) - # NOTE: doesn't return anything - self.setkeys(value) - else: - # FIXME: do we want to allow arbitrary setting of attributes? - # Or do we want to manage it? - object.__setattr__(self, name, value) - - def __getattr__(self, name): - """ - Implemented so that access to ``sequence`` raises a warning. - - >>> d = OrderedDict() - >>> d.sequence - [] - """ - if name == 'sequence': - warnings.warn('Use of the sequence attribute is deprecated.' - ' Use the keys method instead.', DeprecationWarning) - # NOTE: Still (currently) returns a direct reference. Need to - # because code that uses sequence will expect to be able to - # mutate it in place. - return self._sequence - else: - # raise the appropriate error - raise AttributeError("OrderedDict has no '%s' attribute" % name) - - def __deepcopy__(self, memo): - """ - To allow deepcopy to work with OrderedDict. - - >>> from copy import deepcopy - >>> a = OrderedDict([(1, 1), (2, 2), (3, 3)]) - >>> a['test'] = {} - >>> b = deepcopy(a) - >>> b == a - True - >>> b is a - False - >>> a['test'] is b['test'] - False - """ - from copy import deepcopy - return self.__class__(deepcopy(self.items(), memo), self.strict) - - -### Read-only methods ### - - def copy(self): - """ - >>> OrderedDict(((1, 3), (3, 2), (2, 1))).copy() - OrderedDict([(1, 3), (3, 2), (2, 1)]) - """ - return OrderedDict(self) - - def items(self): - """ - ``items`` returns a list of tuples representing all the - ``(key, value)`` pairs in the dictionary. - - >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) - >>> d.items() - [(1, 3), (3, 2), (2, 1)] - >>> d.clear() - >>> d.items() - [] - """ - return zip(self._sequence, self.values()) - - def keys(self): - """ - Return a list of keys in the ``OrderedDict``. - - >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) - >>> d.keys() - [1, 3, 2] - """ - return self._sequence[:] - - def values(self, values=None): - """ - Return a list of all the values in the OrderedDict. - - Optionally you can pass in a list of values, which will replace the - current list. The value list must be the same len as the OrderedDict. - - >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) - >>> d.values() - [3, 2, 1] - """ - return [self[key] for key in self._sequence] - - def iteritems(self): - """ - >>> ii = OrderedDict(((1, 3), (3, 2), (2, 1))).iteritems() - >>> ii.next() - (1, 3) - >>> ii.next() - (3, 2) - >>> ii.next() - (2, 1) - >>> ii.next() - Traceback (most recent call last): - StopIteration - """ - def make_iter(self=self): - keys = self.iterkeys() - while True: - key = keys.next() - yield (key, self[key]) - return make_iter() - - def iterkeys(self): - """ - >>> ii = OrderedDict(((1, 3), (3, 2), (2, 1))).iterkeys() - >>> ii.next() - 1 - >>> ii.next() - 3 - >>> ii.next() - 2 - >>> ii.next() - Traceback (most recent call last): - StopIteration - """ - return iter(self._sequence) - - __iter__ = iterkeys - - def itervalues(self): - """ - >>> iv = OrderedDict(((1, 3), (3, 2), (2, 1))).itervalues() - >>> iv.next() - 3 - >>> iv.next() - 2 - >>> iv.next() - 1 - >>> iv.next() - Traceback (most recent call last): - StopIteration - """ - def make_iter(self=self): - keys = self.iterkeys() - while True: - yield self[keys.next()] - return make_iter() - -### Read-write methods ### - - def clear(self): - """ - >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) - >>> d.clear() - >>> d - OrderedDict([]) - """ - dict.clear(self) - self._sequence = [] - - def pop(self, key, *args): - """ - No dict.pop in Python 2.2, gotta reimplement it - - >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) - >>> d.pop(3) - 2 - >>> d - OrderedDict([(1, 3), (2, 1)]) - >>> d.pop(4) - Traceback (most recent call last): - KeyError: 4 - >>> d.pop(4, 0) - 0 - >>> d.pop(4, 0, 1) - Traceback (most recent call last): - TypeError: pop expected at most 2 arguments, got 3 - """ - if len(args) > 1: - raise TypeError, ('pop expected at most 2 arguments, got %s' % - (len(args) + 1)) - if key in self: - val = self[key] - del self[key] - else: - try: - val = args[0] - except IndexError: - raise KeyError(key) - return val - - def popitem(self, i=-1): - """ - Delete and return an item specified by index, not a random one as in - dict. The index is -1 by default (the last item). - - >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) - >>> d.popitem() - (2, 1) - >>> d - OrderedDict([(1, 3), (3, 2)]) - >>> d.popitem(0) - (1, 3) - >>> OrderedDict().popitem() - Traceback (most recent call last): - KeyError: 'popitem(): dictionary is empty' - >>> d.popitem(2) - Traceback (most recent call last): - IndexError: popitem(): index 2 not valid - """ - if not self._sequence: - raise KeyError('popitem(): dictionary is empty') - try: - key = self._sequence[i] - except IndexError: - raise IndexError('popitem(): index %s not valid' % i) - return (key, self.pop(key)) - - def setdefault(self, key, defval = None): - """ - >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) - >>> d.setdefault(1) - 3 - >>> d.setdefault(4) is None - True - >>> d - OrderedDict([(1, 3), (3, 2), (2, 1), (4, None)]) - >>> d.setdefault(5, 0) - 0 - >>> d - OrderedDict([(1, 3), (3, 2), (2, 1), (4, None), (5, 0)]) - """ - if key in self: - return self[key] - else: - self[key] = defval - return defval - - def update(self, from_od): - """ - Update from another OrderedDict or sequence of (key, value) pairs - - >>> d = OrderedDict(((1, 0), (0, 1))) - >>> d.update(OrderedDict(((1, 3), (3, 2), (2, 1)))) - >>> d - OrderedDict([(1, 3), (0, 1), (3, 2), (2, 1)]) - >>> d.update({4: 4}) - Traceback (most recent call last): - TypeError: undefined order, cannot get items from dict - >>> d.update((4, 4)) - Traceback (most recent call last): - TypeError: cannot convert dictionary update sequence element "4" to a 2-item sequence - """ - if isinstance(from_od, OrderedDict): - for key, val in from_od.items(): - self[key] = val - elif isinstance(from_od, dict): - # we lose compatibility with other ordered dict types this way - raise TypeError('undefined order, cannot get items from dict') - else: - # FIXME: efficiency? - # sequence of 2-item sequences, or error - for item in from_od: - try: - key, val = item - except TypeError: - raise TypeError('cannot convert dictionary update' - ' sequence element "%s" to a 2-item sequence' % item) - self[key] = val - - def rename(self, old_key, new_key): - """ - Rename the key for a given value, without modifying sequence order. - - For the case where new_key already exists this raise an exception, - since if new_key exists, it is ambiguous as to what happens to the - associated values, and the position of new_key in the sequence. - - >>> od = OrderedDict() - >>> od['a'] = 1 - >>> od['b'] = 2 - >>> od.items() - [('a', 1), ('b', 2)] - >>> od.rename('b', 'c') - >>> od.items() - [('a', 1), ('c', 2)] - >>> od.rename('c', 'a') - Traceback (most recent call last): - ValueError: New key already exists: 'a' - >>> od.rename('d', 'b') - Traceback (most recent call last): - KeyError: 'd' - """ - if new_key == old_key: - # no-op - return - if new_key in self: - raise ValueError("New key already exists: %r" % new_key) - # rename sequence entry - value = self[old_key] - old_idx = self._sequence.index(old_key) - self._sequence[old_idx] = new_key - # rename internal dict entry - dict.__delitem__(self, old_key) - dict.__setitem__(self, new_key, value) - - def setitems(self, items): - """ - This method allows you to set the items in the dict. - - It takes a list of tuples - of the same sort returned by the ``items`` - method. - - >>> d = OrderedDict() - >>> d.setitems(((3, 1), (2, 3), (1, 2))) - >>> d - OrderedDict([(3, 1), (2, 3), (1, 2)]) - """ - self.clear() - # FIXME: this allows you to pass in an OrderedDict as well :-) - self.update(items) - - def setkeys(self, keys): - """ - ``setkeys`` all ows you to pass in a new list of keys which will - replace the current set. This must contain the same set of keys, but - need not be in the same order. - - If you pass in new keys that don't match, a ``KeyError`` will be - raised. - - >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) - >>> d.keys() - [1, 3, 2] - >>> d.setkeys((1, 2, 3)) - >>> d - OrderedDict([(1, 3), (2, 1), (3, 2)]) - >>> d.setkeys(['a', 'b', 'c']) - Traceback (most recent call last): - KeyError: 'Keylist is not the same as current keylist.' - """ - # FIXME: Efficiency? (use set for Python 2.4 :-) - # NOTE: list(keys) rather than keys[:] because keys[:] returns - # a tuple, if keys is a tuple. - kcopy = list(keys) - kcopy.sort() - self._sequence.sort() - if kcopy != self._sequence: - raise KeyError('Keylist is not the same as current keylist.') - # NOTE: This makes the _sequence attribute a new object, instead - # of changing it in place. - # FIXME: efficiency? - self._sequence = list(keys) - - def setvalues(self, values): - """ - You can pass in a list of values, which will replace the - current list. The value list must be the same len as the OrderedDict. - - (Or a ``ValueError`` is raised.) - - >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) - >>> d.setvalues((1, 2, 3)) - >>> d - OrderedDict([(1, 1), (3, 2), (2, 3)]) - >>> d.setvalues([6]) - Traceback (most recent call last): - ValueError: Value list is not the same length as the OrderedDict. - """ - if len(values) != len(self): - # FIXME: correct error to raise? - raise ValueError('Value list is not the same length as the ' - 'OrderedDict.') - self.update(zip(self, values)) - -### Sequence Methods ### - - def index(self, key): - """ - Return the position of the specified key in the OrderedDict. - - >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) - >>> d.index(3) - 1 - >>> d.index(4) - Traceback (most recent call last): - ValueError: list.index(x): x not in list - """ - return self._sequence.index(key) - - def insert(self, index, key, value): - """ - Takes ``index``, ``key``, and ``value`` as arguments. - - Sets ``key`` to ``value``, so that ``key`` is at position ``index`` in - the OrderedDict. - - >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) - >>> d.insert(0, 4, 0) - >>> d - OrderedDict([(4, 0), (1, 3), (3, 2), (2, 1)]) - >>> d.insert(0, 2, 1) - >>> d - OrderedDict([(2, 1), (4, 0), (1, 3), (3, 2)]) - >>> d.insert(8, 8, 1) - >>> d - OrderedDict([(2, 1), (4, 0), (1, 3), (3, 2), (8, 1)]) - """ - if key in self: - # FIXME: efficiency? - del self[key] - self._sequence.insert(index, key) - dict.__setitem__(self, key, value) - - def reverse(self): - """ - Reverse the order of the OrderedDict. - - >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) - >>> d.reverse() - >>> d - OrderedDict([(2, 1), (3, 2), (1, 3)]) - """ - self._sequence.reverse() - - def sort(self, *args, **kwargs): - """ - Sort the key order in the OrderedDict. - - This method takes the same arguments as the ``list.sort`` method on - your version of Python. - - >>> d = OrderedDict(((4, 1), (2, 2), (3, 3), (1, 4))) - >>> d.sort() - >>> d - OrderedDict([(1, 4), (2, 2), (3, 3), (4, 1)]) - """ - self._sequence.sort(*args, **kwargs) - -class Keys(object): - # FIXME: should this object be a subclass of list? - """ - Custom object for accessing the keys of an OrderedDict. - - Can be called like the normal ``OrderedDict.keys`` method, but also - supports indexing and sequence methods. - """ - - def __init__(self, main): - self._main = main - - def __call__(self): - """Pretend to be the keys method.""" - return self._main._keys() - - def __getitem__(self, index): - """Fetch the key at position i.""" - # NOTE: this automatically supports slicing :-) - return self._main._sequence[index] - - def __setitem__(self, index, name): - """ - You cannot assign to keys, but you can do slice assignment to re-order - them. - - You can only do slice assignment if the new set of keys is a reordering - of the original set. - """ - if isinstance(index, types.SliceType): - # FIXME: efficiency? - # check length is the same - indexes = range(len(self._main._sequence))[index] - if len(indexes) != len(name): - raise ValueError('attempt to assign sequence of size %s ' - 'to slice of size %s' % (len(name), len(indexes))) - # check they are the same keys - # FIXME: Use set - old_keys = self._main._sequence[index] - new_keys = list(name) - old_keys.sort() - new_keys.sort() - if old_keys != new_keys: - raise KeyError('Keylist is not the same as current keylist.') - orig_vals = [self._main[k] for k in name] - del self._main[index] - vals = zip(indexes, name, orig_vals) - vals.sort() - for i, k, v in vals: - if self._main.strict and k in self._main: - raise ValueError('slice assignment must be from ' - 'unique keys') - self._main.insert(i, k, v) - else: - raise ValueError('Cannot assign to keys') - - ### following methods pinched from UserList and adapted ### - def __repr__(self): return repr(self._main._sequence) - - # FIXME: do we need to check if we are comparing with another ``Keys`` - # object? (like the __cast method of UserList) - def __lt__(self, other): return self._main._sequence < other - def __le__(self, other): return self._main._sequence <= other - def __eq__(self, other): return self._main._sequence == other - def __ne__(self, other): return self._main._sequence != other - def __gt__(self, other): return self._main._sequence > other - def __ge__(self, other): return self._main._sequence >= other - # FIXME: do we need __cmp__ as well as rich comparisons? - def __cmp__(self, other): return cmp(self._main._sequence, other) - - def __contains__(self, item): return item in self._main._sequence - def __len__(self): return len(self._main._sequence) - def __iter__(self): return self._main.iterkeys() - def count(self, item): return self._main._sequence.count(item) - def index(self, item, *args): return self._main._sequence.index(item, *args) - def reverse(self): self._main._sequence.reverse() - def sort(self, *args, **kwds): self._main._sequence.sort(*args, **kwds) - def __mul__(self, n): return self._main._sequence*n - __rmul__ = __mul__ - def __add__(self, other): return self._main._sequence + other - def __radd__(self, other): return other + self._main._sequence - - ## following methods not implemented for keys ## - def __delitem__(self, i): raise TypeError('Can\'t delete items from keys') - def __iadd__(self, other): raise TypeError('Can\'t add in place to keys') - def __imul__(self, n): raise TypeError('Can\'t multiply keys in place') - def append(self, item): raise TypeError('Can\'t append items to keys') - def insert(self, i, item): raise TypeError('Can\'t insert items into keys') - def pop(self, i=-1): raise TypeError('Can\'t pop items from keys') - def remove(self, item): raise TypeError('Can\'t remove items from keys') - def extend(self, other): raise TypeError('Can\'t extend keys') - -class Items(object): - """ - Custom object for accessing the items of an OrderedDict. - - Can be called like the normal ``OrderedDict.items`` method, but also - supports indexing and sequence methods. - """ - - def __init__(self, main): - self._main = main - - def __call__(self): - """Pretend to be the items method.""" - return self._main._items() - - def __getitem__(self, index): - """Fetch the item at position i.""" - if isinstance(index, types.SliceType): - # fetching a slice returns an OrderedDict - return self._main[index].items() - key = self._main._sequence[index] - return (key, self._main[key]) - - def __setitem__(self, index, item): - """Set item at position i to item.""" - if isinstance(index, types.SliceType): - # NOTE: item must be an iterable (list of tuples) - self._main[index] = OrderedDict(item) - else: - # FIXME: Does this raise a sensible error? - orig = self._main.keys[index] - key, value = item - if self._main.strict and key in self and (key != orig): - raise ValueError('slice assignment must be from ' - 'unique keys') - # delete the current one - del self._main[self._main._sequence[index]] - self._main.insert(index, key, value) - - def __delitem__(self, i): - """Delete the item at position i.""" - key = self._main._sequence[i] - if isinstance(i, types.SliceType): - for k in key: - # FIXME: efficiency? - del self._main[k] - else: - del self._main[key] - - ### following methods pinched from UserList and adapted ### - def __repr__(self): return repr(self._main.items()) - - # FIXME: do we need to check if we are comparing with another ``Items`` - # object? (like the __cast method of UserList) - def __lt__(self, other): return self._main.items() < other - def __le__(self, other): return self._main.items() <= other - def __eq__(self, other): return self._main.items() == other - def __ne__(self, other): return self._main.items() != other - def __gt__(self, other): return self._main.items() > other - def __ge__(self, other): return self._main.items() >= other - def __cmp__(self, other): return cmp(self._main.items(), other) - - def __contains__(self, item): return item in self._main.items() - def __len__(self): return len(self._main._sequence) # easier :-) - def __iter__(self): return self._main.iteritems() - def count(self, item): return self._main.items().count(item) - def index(self, item, *args): return self._main.items().index(item, *args) - def reverse(self): self._main.reverse() - def sort(self, *args, **kwds): self._main.sort(*args, **kwds) - def __mul__(self, n): return self._main.items()*n - __rmul__ = __mul__ - def __add__(self, other): return self._main.items() + other - def __radd__(self, other): return other + self._main.items() - - def append(self, item): - """Add an item to the end.""" - # FIXME: this is only append if the key isn't already present - key, value = item - self._main[key] = value - - def insert(self, i, item): - key, value = item - self._main.insert(i, key, value) - - def pop(self, i=-1): - key = self._main._sequence[i] - return (key, self._main.pop(key)) - - def remove(self, item): - key, value = item - try: - assert value == self._main[key] - except (KeyError, AssertionError): - raise ValueError('ValueError: list.remove(x): x not in list') - else: - del self._main[key] - - def extend(self, other): - # FIXME: is only a true extend if none of the keys already present - for item in other: - key, value = item - self._main[key] = value - - def __iadd__(self, other): - self.extend(other) - - ## following methods not implemented for items ## - - def __imul__(self, n): raise TypeError('Can\'t multiply items in place') - -class Values(object): - """ - Custom object for accessing the values of an OrderedDict. - - Can be called like the normal ``OrderedDict.values`` method, but also - supports indexing and sequence methods. - """ - - def __init__(self, main): - self._main = main - - def __call__(self): - """Pretend to be the values method.""" - return self._main._values() - - def __getitem__(self, index): - """Fetch the value at position i.""" - if isinstance(index, types.SliceType): - return [self._main[key] for key in self._main._sequence[index]] - else: - return self._main[self._main._sequence[index]] - - def __setitem__(self, index, value): - """ - Set the value at position i to value. - - You can only do slice assignment to values if you supply a sequence of - equal length to the slice you are replacing. - """ - if isinstance(index, types.SliceType): - keys = self._main._sequence[index] - if len(keys) != len(value): - raise ValueError('attempt to assign sequence of size %s ' - 'to slice of size %s' % (len(name), len(keys))) - # FIXME: efficiency? Would be better to calculate the indexes - # directly from the slice object - # NOTE: the new keys can collide with existing keys (or even - # contain duplicates) - these will overwrite - for key, val in zip(keys, value): - self._main[key] = val - else: - self._main[self._main._sequence[index]] = value - - ### following methods pinched from UserList and adapted ### - def __repr__(self): return repr(self._main.values()) - - # FIXME: do we need to check if we are comparing with another ``Values`` - # object? (like the __cast method of UserList) - def __lt__(self, other): return self._main.values() < other - def __le__(self, other): return self._main.values() <= other - def __eq__(self, other): return self._main.values() == other - def __ne__(self, other): return self._main.values() != other - def __gt__(self, other): return self._main.values() > other - def __ge__(self, other): return self._main.values() >= other - def __cmp__(self, other): return cmp(self._main.values(), other) - - def __contains__(self, item): return item in self._main.values() - def __len__(self): return len(self._main._sequence) # easier :-) - def __iter__(self): return self._main.itervalues() - def count(self, item): return self._main.values().count(item) - def index(self, item, *args): return self._main.values().index(item, *args) - - def reverse(self): - """Reverse the values""" - vals = self._main.values() - vals.reverse() - # FIXME: efficiency - self[:] = vals - - def sort(self, *args, **kwds): - """Sort the values.""" - vals = self._main.values() - vals.sort(*args, **kwds) - self[:] = vals - - def __mul__(self, n): return self._main.values()*n - __rmul__ = __mul__ - def __add__(self, other): return self._main.values() + other - def __radd__(self, other): return other + self._main.values() - - ## following methods not implemented for values ## - def __delitem__(self, i): raise TypeError('Can\'t delete items from values') - def __iadd__(self, other): raise TypeError('Can\'t add in place to values') - def __imul__(self, n): raise TypeError('Can\'t multiply values in place') - def append(self, item): raise TypeError('Can\'t append items to values') - def insert(self, i, item): raise TypeError('Can\'t insert items into values') - def pop(self, i=-1): raise TypeError('Can\'t pop items from values') - def remove(self, item): raise TypeError('Can\'t remove items from values') - def extend(self, other): raise TypeError('Can\'t extend values') - -class SequenceOrderedDict(OrderedDict): - """ - Experimental version of OrderedDict that has a custom object for ``keys``, - ``values``, and ``items``. - - These are callable sequence objects that work as methods, or can be - manipulated directly as sequences. - - Test for ``keys``, ``items`` and ``values``. - - >>> d = SequenceOrderedDict(((1, 2), (2, 3), (3, 4))) - >>> d - SequenceOrderedDict([(1, 2), (2, 3), (3, 4)]) - >>> d.keys - [1, 2, 3] - >>> d.keys() - [1, 2, 3] - >>> d.setkeys((3, 2, 1)) - >>> d - SequenceOrderedDict([(3, 4), (2, 3), (1, 2)]) - >>> d.setkeys((1, 2, 3)) - >>> d.keys[0] - 1 - >>> d.keys[:] - [1, 2, 3] - >>> d.keys[-1] - 3 - >>> d.keys[-2] - 2 - >>> d.keys[0:2] = [2, 1] - >>> d - SequenceOrderedDict([(2, 3), (1, 2), (3, 4)]) - >>> d.keys.reverse() - >>> d.keys - [3, 1, 2] - >>> d.keys = [1, 2, 3] - >>> d - SequenceOrderedDict([(1, 2), (2, 3), (3, 4)]) - >>> d.keys = [3, 1, 2] - >>> d - SequenceOrderedDict([(3, 4), (1, 2), (2, 3)]) - >>> a = SequenceOrderedDict() - >>> b = SequenceOrderedDict() - >>> a.keys == b.keys - 1 - >>> a['a'] = 3 - >>> a.keys == b.keys - 0 - >>> b['a'] = 3 - >>> a.keys == b.keys - 1 - >>> b['b'] = 3 - >>> a.keys == b.keys - 0 - >>> a.keys > b.keys - 0 - >>> a.keys < b.keys - 1 - >>> 'a' in a.keys - 1 - >>> len(b.keys) - 2 - >>> 'c' in d.keys - 0 - >>> 1 in d.keys - 1 - >>> [v for v in d.keys] - [3, 1, 2] - >>> d.keys.sort() - >>> d.keys - [1, 2, 3] - >>> d = SequenceOrderedDict(((1, 2), (2, 3), (3, 4)), strict=True) - >>> d.keys[::-1] = [1, 2, 3] - >>> d - SequenceOrderedDict([(3, 4), (2, 3), (1, 2)]) - >>> d.keys[:2] - [3, 2] - >>> d.keys[:2] = [1, 3] - Traceback (most recent call last): - KeyError: 'Keylist is not the same as current keylist.' - - >>> d = SequenceOrderedDict(((1, 2), (2, 3), (3, 4))) - >>> d - SequenceOrderedDict([(1, 2), (2, 3), (3, 4)]) - >>> d.values - [2, 3, 4] - >>> d.values() - [2, 3, 4] - >>> d.setvalues((4, 3, 2)) - >>> d - SequenceOrderedDict([(1, 4), (2, 3), (3, 2)]) - >>> d.values[::-1] - [2, 3, 4] - >>> d.values[0] - 4 - >>> d.values[-2] - 3 - >>> del d.values[0] - Traceback (most recent call last): - TypeError: Can't delete items from values - >>> d.values[::2] = [2, 4] - >>> d - SequenceOrderedDict([(1, 2), (2, 3), (3, 4)]) - >>> 7 in d.values - 0 - >>> len(d.values) - 3 - >>> [val for val in d.values] - [2, 3, 4] - >>> d.values[-1] = 2 - >>> d.values.count(2) - 2 - >>> d.values.index(2) - 0 - >>> d.values[-1] = 7 - >>> d.values - [2, 3, 7] - >>> d.values.reverse() - >>> d.values - [7, 3, 2] - >>> d.values.sort() - >>> d.values - [2, 3, 7] - >>> d.values.append('anything') - Traceback (most recent call last): - TypeError: Can't append items to values - >>> d.values = (1, 2, 3) - >>> d - SequenceOrderedDict([(1, 1), (2, 2), (3, 3)]) - - >>> d = SequenceOrderedDict(((1, 2), (2, 3), (3, 4))) - >>> d - SequenceOrderedDict([(1, 2), (2, 3), (3, 4)]) - >>> d.items() - [(1, 2), (2, 3), (3, 4)] - >>> d.setitems([(3, 4), (2 ,3), (1, 2)]) - >>> d - SequenceOrderedDict([(3, 4), (2, 3), (1, 2)]) - >>> d.items[0] - (3, 4) - >>> d.items[:-1] - [(3, 4), (2, 3)] - >>> d.items[1] = (6, 3) - >>> d.items - [(3, 4), (6, 3), (1, 2)] - >>> d.items[1:2] = [(9, 9)] - >>> d - SequenceOrderedDict([(3, 4), (9, 9), (1, 2)]) - >>> del d.items[1:2] - >>> d - SequenceOrderedDict([(3, 4), (1, 2)]) - >>> (3, 4) in d.items - 1 - >>> (4, 3) in d.items - 0 - >>> len(d.items) - 2 - >>> [v for v in d.items] - [(3, 4), (1, 2)] - >>> d.items.count((3, 4)) - 1 - >>> d.items.index((1, 2)) - 1 - >>> d.items.index((2, 1)) - Traceback (most recent call last): - ValueError: list.index(x): x not in list - >>> d.items.reverse() - >>> d.items - [(1, 2), (3, 4)] - >>> d.items.reverse() - >>> d.items.sort() - >>> d.items - [(1, 2), (3, 4)] - >>> d.items.append((5, 6)) - >>> d.items - [(1, 2), (3, 4), (5, 6)] - >>> d.items.insert(0, (0, 0)) - >>> d.items - [(0, 0), (1, 2), (3, 4), (5, 6)] - >>> d.items.insert(-1, (7, 8)) - >>> d.items - [(0, 0), (1, 2), (3, 4), (7, 8), (5, 6)] - >>> d.items.pop() - (5, 6) - >>> d.items - [(0, 0), (1, 2), (3, 4), (7, 8)] - >>> d.items.remove((1, 2)) - >>> d.items - [(0, 0), (3, 4), (7, 8)] - >>> d.items.extend([(1, 2), (5, 6)]) - >>> d.items - [(0, 0), (3, 4), (7, 8), (1, 2), (5, 6)] - """ - - def __init__(self, init_val=(), strict=True): - OrderedDict.__init__(self, init_val, strict=strict) - self._keys = self.keys - self._values = self.values - self._items = self.items - self.keys = Keys(self) - self.values = Values(self) - self.items = Items(self) - self._att_dict = { - 'keys': self.setkeys, - 'items': self.setitems, - 'values': self.setvalues, - } - - def __setattr__(self, name, value): - """Protect keys, items, and values.""" - if not '_att_dict' in self.__dict__: - object.__setattr__(self, name, value) - else: - try: - fun = self._att_dict[name] - except KeyError: - OrderedDict.__setattr__(self, name, value) - else: - fun(value) - -if __name__ == '__main__': - if INTP_VER < (2, 3): - raise RuntimeError("Tests require Python v.2.3 or later") - # turn off warnings for tests - warnings.filterwarnings('ignore') - # run the code tests in doctest format - import doctest - m = sys.modules.get('__main__') - globs = m.__dict__.copy() - globs.update({ - 'INTP_VER': INTP_VER, - }) - doctest.testmod(m, globs=globs) diff --git a/tools/toykmc/state.py b/tools/toykmc/state.py deleted file mode 100644 index 0af580ba8..000000000 --- a/tools/toykmc/state.py +++ /dev/null @@ -1,143 +0,0 @@ -from __future__ import division -import copy -from math import exp -import odict - -from config import * - -class State: - states = odict.OrderedDict() - - @staticmethod - def get_state(grid): - gs = State.gridhash(grid) - if gs in State.states: - return State.states[gs] - else: - s = State(grid) - State.states[gs] = s - return s - - @staticmethod - def saddle_energy(s1, s2): - return max(s1.energy, s2.energy) + dEsaddle/(max(abs(s1.energy-s2.energy),1)) - - #XXX: Ugly - @staticmethod - def gridhash(grid): - #XXX: Hyper-lazy hash function. Still results in speed-up once things get all superbasiney. - gs = "" - for i in grid: - for j in i: - gs += "T" if j else "F" - return gs - - def __init__(self, grid, energy = None): - self.grid = grid - self.w = len(self.grid[0]) - self.h = len(self.grid) - - if not energy: - self.energy = self.calc_energy() - else: - self.energy = energy - self.rate_table = None - - def calc_energy(self): - e = 0 - for i in range(self.h): - for j in range(self.w): - if self.grid[i][j]: - e += self.calc_energy_at(i,j) - return e - - def calc_energy_at(self, i, j): - e = 0 - for z in energy_neighbors: - e -= self.grid[(i+z[0])%self.h][(j+z[1])%self.w] - for z in energy_neighbors_2: - e -= self.grid[(i+z[0])%self.h][(j+z[1])%self.w]*energy_2 - return e - - def __eq__(self, other): - return self.grid == other.grid - - def get_rate_table(self): - if self.rate_table: - return self.rate_table - - self.rate_table = [] - for i in range(self.h): - for j in range(self.w): - if self.grid[i][j]: - for z in move_neighbors: - m,n = (i+z[0])%self.h, (j+z[1])%self.w - if not self.grid[m][n]: - newgrid = copy.deepcopy(self.grid) - newgrid[i][j] = False - newgrid[m][n] = True - dE = self.calc_energy_at(m,n) - self.calc_energy_at(i,j) - - proc = {} - proc['product'] = State(newgrid, self.energy+2*dE) - proc['barrier'] = State.saddle_energy(self, proc['product']) - self.energy - proc['rate'] = exp(-proc['barrier']/.01) - self.rate_table.append(proc) - return self.rate_table - - def save(self, filename): - f = open(filename, 'w') - print >> f, self - f.close() - - def __hash__(self): - return hash(State.gridhash(self.grid)) - - @staticmethod - def load(filename): - f = open(filename, 'r') - grid = [] - for i in f: - if i[0]=='+': - continue - gl = [] - for j in i[1:-2]: - gl.append(False if j==' ' else True) - grid.append(gl) - f.close() - - return State(grid) - - def __str__(self): - out = "" - out+="+" - for i in range(self.w): - out+="-" - out+="+\n" - for i in range(self.h): - out+="|" - for j in range(self.w): - if self.grid[i][j]: - out+="O" - else: - out+=" " - out+="|\n" - out+="+" - for i in range(self.w): - out+="-" - out+="+\n" - - return out - -if __name__ == '__main__': - import random - g = [] - for i in range(20): - u = [] - for j in range(70): - u.append(random.choice([True, False])) - g.append(u) - - s = State(g) - print s - print s.energy diff --git a/tools/toykmc/structures/dimer.grid b/tools/toykmc/structures/dimer.grid deleted file mode 100644 index cfc07a66b..000000000 --- a/tools/toykmc/structures/dimer.grid +++ /dev/null @@ -1,7 +0,0 @@ -+-----+ -| | -| OO | -| | -| | -| | -+-----+ diff --git a/tools/toykmc/structures/island.grid b/tools/toykmc/structures/island.grid deleted file mode 100644 index cce27f363..000000000 --- a/tools/toykmc/structures/island.grid +++ /dev/null @@ -1,11 +0,0 @@ -+---------------+ -| o | -| o o| -| o | -| o | -| | -| o | -| o | -| | -| | -+---------------+ diff --git a/tools/toykmc/structures/trimer.grid b/tools/toykmc/structures/trimer.grid deleted file mode 100644 index 4b1804ad9..000000000 --- a/tools/toykmc/structures/trimer.grid +++ /dev/null @@ -1,7 +0,0 @@ -+-----+ -| | -| OO | -| O | -| | -| | -+-----+ diff --git a/tools/toykmc/superbasin.py b/tools/toykmc/superbasin.py deleted file mode 100644 index 4f5acc074..000000000 --- a/tools/toykmc/superbasin.py +++ /dev/null @@ -1,92 +0,0 @@ -import numpy - -class Superbasin: - """Class to manage super basin: calculate the mean residence time, exit probabilities, and perform Monte Carlo transitions out of the basin, """\ - """based on Novotny's Absorbing Markov Chain algorithm.""" - def __init__(self, statelist): - #TODO: reinstate statelist!!!! - self.nstates = len(statelist) - self.states = statelist - self._calculate_stuff() - - - def pick_exit_state(self, entry_state): - """Chosse an exit state (state of the basin from which we will be leaving) using absorbing Markov chain theory.""" - entry_state_index = self.states.index(entry_state) - if entry_state_index is None: - raise ValueError('Passed entry state is not in this superbasin') - - probability_vector = self.probability_matrix.transpose()[entry_state_index] - if abs(1.0-numpy.sum(probability_vector)) > 1e-3: - print "the probability vector isn't close to 1.0" - print 'probability_vector ' + str(probability_vector) + " " + str(numpy.sum(probability_vector)) - probability_vector /= numpy.sum(probability_vector) - - u = numpy.random.random_sample() - p = 0.0 - for i in range(len(self.states)): - p += probability_vector[i] - if p>u: - exit_state_index = i - break - else: - print "Warning: failed to select exit state. p = " + str(p) - time = self.mean_residence_times[entry_state_index] - exit_state = self.states[exit_state_index] - return time, exit_state - - - def step(self, entry_state): - """Perform a Monte Carlo transition: leave the basin."""\ - """The function returns a residence time as well as information to indenfity what saddle point was to leave the basin,"""\ - """from what state and to what state the system is moving to.""" - time, exit_state = self.pick_exit_state(entry_state) - assert(time >= 0.0) - - # Make a rate table for all the exit state. All processes are - # needed as the might be a discrepancy in time scale - # and it might be dangerous to weed out low rate events - rate_table = [] - process_table = exit_state.get_rate_table() - - # Determine all process OUT of the superbasin - for proc in process_table: - if proc['product'] not in self.states: - rate_table.append(proc) - return rate_table, time, exit_state - - def contains_state(self, state): - return state in self.states - - def _calculate_stuff(self): - """Build the transient and recurrent matrices."""\ - """Calculate the fundamental matrix in order to be able to calculate the mean resisdence time"""\ - """and exit probablities any initial distribution.""" - - recurrent_vector = numpy.zeros(self.nstates) - transient_matrix= numpy.zeros((self.nstates, self.nstates)) - sum=0.0 - for i, item in enumerate(self.states): - proc_table = item.get_rate_table() - for process in proc_table: - sum+=process['rate'] - if process['product'] not in self.states: - recurrent_vector[i] += process['rate'] - else: - #ouch that is complicated - j = self.states.index(process['product']) - transient_matrix[j][i] += process['rate'] - transient_matrix[i][i] -= process['rate'] - - fundamental_matrix = numpy.linalg.inv(transient_matrix) - self.mean_residence_times = numpy.zeros(len(self.states)) - self.probability_matrix = numpy.zeros((len(self.states), len(self.states))) - - for i in range(self.nstates): - for j in range(self.nstates): - self.mean_residence_times[j] -= fundamental_matrix[i][j] - self.probability_matrix[i][j] = -recurrent_vector[i]*fundamental_matrix[i][j] - - for i in self.probability_matrix.transpose(): - if abs(1-i.sum()) > 1e-3: - print "WARNING: Probability vector does not add up to 1" diff --git a/tools/toykmc/superbasinscheme.py b/tools/toykmc/superbasinscheme.py deleted file mode 100644 index 0fdc2e9e7..000000000 --- a/tools/toykmc/superbasinscheme.py +++ /dev/null @@ -1,74 +0,0 @@ -import superbasin - -class SuperbasinScheme: - ''' This poorly-named class handles keeping track of which states belong - to which superbasins, the SuperBasin object of those superbasins, and - superbasining criteria. It also expands and merges superbasins''' - - def __init__(self): - self.superbasins = [] - - def get_containing_superbasin(self, state): - for i in self.superbasins: - if i.contains_state(state): - return i - return None - - def make_basin(self, merge_states): - print "ohdeargod make a basin" - new_sb_states = [] - for i in merge_states: - sb = self.get_containing_superbasin(i) - if sb is None: - if i not in new_sb_states: - new_sb_states.append(i) - else: - for j in sb.states: - if j not in new_sb_states: - new_sb_states.append(j) - self.superbasins.remove(sb) - - #self.states.connect_states(new_sb_states) #XXX:This should ensure detailed balance - #However, it will likely be very slow. We should be able to do without it. - #Also, if confidence is changed and new processes are found, the superbasin - #will ignore these new processes. - - self.superbasins.append(superbasin.Superbasin(new_sb_states)) - - print "Created superbasin with states " #+ str([i.number for i in new_sb_states]) - - def register_transition(self, start_state, end_state): - raise NotImplementedError() - - def write_data(self): - raise NotImplementedError() - - def read_data(self): - raise NotImplementedError() - -class TransitionCounting(SuperbasinScheme): - ''' Implements the transition counting scheme for superbasining ''' - - def __init__(self, num_transitions): - self.num_transitions = num_transitions - SuperbasinScheme.__init__(self) - self.count = {} - - def register_transition(self, start_state, end_state): - if start_state == end_state: - return - - start_count = self.get_count(start_state) - if end_state not in start_count: - start_count[end_state] = 0 - start_count[end_state] += 1 - - if start_count[end_state] >= self.num_transitions: - self.make_basin([start_state, end_state]) - - def get_count(self, state): - try: - return self.count[state] - except: - self.count[state] = {} - return self.count[state]