diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 100644 index 0000000..c3a17dc --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1,37 @@ +# Agent instruction of DFTD4 Rust FFI and Wrapper + +## Notes to human developers + +You should also create a file `CLAUDE.local.md` to place local resources: +- `DFTD4_REPO_PATH`: local of original dftd4 repository. The source code can help you understand how dftd4 works. + +## DFTD4 original library + +General rules +- This repository should live at `DFTD4_REPO_PATH`, which is defined in `CLAUDE.local.md`. +- **This repository should not be modified**, unless you are going to checkout specific tags (versions) of dftd4. + +Important files for FFI and wrapper development: +- `include/dftd4.h`: the headers. Note that these files are also copied to this project under `dftd4/header` folder. +- `python/dftd4`: the python wrapper of dftd4. We should at least implement all major features of the certain wrapper: + - `interface.py`, corresponding to this project `dftd4/src/interface.rs`. + - `parameters.py`, corresponding to this project `dftd4/src/parameters.rs`. + - Make sure the functionalities are tested. We use `dftd4/examples/test_interface.rs` corresponding to `test_interface.py` in the original wrapper for testing. +- `assets/parameters.toml`: the parameters file, which should be copied to `dftd4/src/parameters.toml` in this project. + +## The additional feature in this crate + +- We support toml parsing of DFTD4 parameters. The related code is at `/dftd4/src/parsing.rs`. The related test is at `dftd4/examples/test_parsing.rs`. +- We support dynamic loading of the DFTD4 library. +- We use tags such as `api-v3_5` to reflect the API version of DFTD4 we are using. + +## Naming convention + +- For functions and structs that will be exposed to users, add prefix `dftd4_` for general functions, and `DFTD4` for structs. +- If some function is to be fallible, we can add suffix `_f` (`fn _f -> Result<_, DFTD4Error>`). + +## Header handling + +We use bindgen (python script at `scripts/generate_ffi.py`) to generate Rust bindings for the C header files. **Not modify the generated files directly**. + +Exception is `ffi_dynamic/mod.rs`. This file can be manually modified. diff --git a/.claude/commands/version-update.md b/.claude/commands/version-update.md new file mode 100644 index 0000000..fb2b4e2 --- /dev/null +++ b/.claude/commands/version-update.md @@ -0,0 +1,172 @@ +--- +description: Update dftd4-rs bindings to support a new dftd4 version +argument-hint: (e.g., v4.1.0) +--- + +You are performing a version update of the dftd4-rs bindings to support dftd4 **$ARGUMENTS**. + +Read the CLAUDE.local.md file to get `DFTD4_REPO_PATH`, then follow the procedure below. Use `DFTD4_REPO_PATH` as the path to the upstream dftd4 repository throughout. + +## Step 0: Gather upstream changes + +Determine the previous version by checking the last entry in the `api_versions` list in `dftd4/scripts/generate_ffi.py`. Then diff the upstream repo: + +```bash +cd $DFTD4_REPO_PATH +git fetch --tags +git checkout $ARGUMENTS +git diff v..$ARGUMENTS -- include/dftd4.h # header changes (CRITICAL) +git diff v..$ARGUMENTS -- python/dftd4/interface.py # Python wrapper changes +git diff v..$ARGUMENTS -- assets/parameters.toml # parameter DB changes +git diff v..$ARGUMENTS -- src/ --stat # Fortran source changes +``` + +From the header diff, identify: +- New `DFTD4_API_SUFFIX__V_X_Y` macro (defines the version tag) +- New function declarations (tagged with version suffix) +- New opaque types (e.g., `dftd4_model` subtype) +- Changed function signatures (rare) + +**Report your findings to the user before proceeding.** Summarize what's new/changed so they can confirm scope. + +## Step 1: Update generate_ffi.py + +File: `dftd4/scripts/generate_ffi.py` + +1. Add the new version to `api_versions` list (follow existing pattern): + ```python + ("V_X_Y", "api-vX_Y"), # new entry + ``` +2. Update the docstring inside `generate_static_ffi()` to list the new version. + +## Step 2: Regenerate FFI bindings + +```bash +cd dftd4/scripts && python generate_ffi.py +``` + +This auto-copies the header, runs bindgen, generates `ffi_static.rs` and all `ffi_dynamic/` files. + +**Verify** the new function appears: +- In `ffi_static.rs` with `#[cfg(feature = "api-vX_Y")]` +- In `ffi_dynamic/dyload_struct.rs`, `dyload_initializer.rs`, `dyload_compatible.rs` + +## Step 3: Update Cargo.toml features + +File: `dftd4/Cargo.toml` + +Add the new feature extending the previous one: +```toml +api-vX_Y = ["api-v"] +``` + +Rules: +- Must extend the **previous** feature (cumulative chain) +- Do **not** update the `default` feature — leave it for manual editing + +## Step 4: Update interface.rs (safe wrappers) + +This is the most manual step. For each new C function, add a safe Rust wrapper following these patterns: + +### Pattern A: New method on existing struct + +Add both infallible and fallible (`_f`) versions, gated by the new feature: +```rust +#[cfg(feature = "api-vX_Y")] +pub fn new_method(&self, /* args */) -> ReturnType { + self.new_method_f(/* args */).unwrap() +} + +#[cfg(feature = "api-vX_Y")] +pub fn new_method_f(&self, /* args */) -> Result { + let mut error = DFTD4Error::new(); + unsafe { ffi::dftd4_new_method(error.get_c_ptr(), /* args */) }; + match error.check() { + true => Err(error), + false => Ok(/* result */), + } +} +``` + +### Pattern B: New damping type + +1. Add a new `DFTD4XyzDampingParam` struct with `#[derive(Builder, Debug, Clone, Deserialize, Serialize)]` +2. Add `new_xyz_damping_f`/`new_xyz_damping` and `load_xyz_damping_f`/`load_xyz_damping` on `DFTD4Param` +3. Implement `DFTD4ParamAPI` for the new struct +4. Add `impl_damping_param_builder!` macro invocation +5. Update `dftd4_load_param`/`dftd4_load_param_f` match arms with `#[cfg]` gating + +### Pattern C: New model variant + +If a new model constructor is added (e.g., `dftd4_new_d4s_model`): +1. Add `new_variant`/`new_variant_f` and `custom_variant`/`custom_variant_f` on `DFTD4Model` +2. Gate with `#[cfg(feature = "api-vX_Y")]` + +## Step 5: Update parameters module (if parameters changed) + +If `assets/parameters.toml` changed upstream: +1. Copy: `cp $DFTD4_REPO_PATH/assets/parameters.toml dftd4/src/parameters.toml` +2. Update `parameters.rs`: + - Add variant to `D4MethodParams` and `D4DefaultParams` + - Add to `DFTD4DampingParamEnum` + - Update `convert_to_damping_param`, `get_variant_entry`, `get_variant_entry_for_defaults` + - Add `#[cfg(feature = "...")]` gates + +## Step 6: Update parsing module (if new damping variants) + +File: `dftd4/src/parsing.rs` + +1. Update `valid_fields_for_version()` with new variant's valid fields +2. Add `#[cfg(feature = "...")]` gating +3. Add `#[cfg(not(feature = "..."))]` error arm + +## Step 7: Update lib.rs (if new modules) + +If you added new interface files: +```rust +#[cfg(feature = "api-vX_Y")] +pub mod interface_xyz; +``` +And update `prelude`. + +## Step 8: Update examples + +Check upstream Python examples for new functionality. Add corresponding Rust examples in `dftd4/examples/`. + +## Step 9: Update dftd4-src (if build system changed) + +```bash +git diff v..$ARGUMENTS -- CMakeLists.txt meson.build +``` +Update `dftd4-src/external_deps/` and `build.rs` if needed. + +## Step 10: Update CI + +Update `.github/workflows/test-dftd4.yml` to use the new feature version: +- Replace `api-v` with `api-vX_Y` in all `cargo test` commands +- This applies to both the `test-static-linking` and `test-dynamic-loading` jobs + +Also check if other CI files need updates: newer conda-forge package version, updated test matrix, new test cases. + +## Step 11: Test + +```bash +cargo build --features api-vX_Y +cargo test --all-features +cargo test --features api-vX_Y +cargo doc --all-features --no-deps +``` + +## Step 12: Update documentation + +- Update `CHANGELOG.md` +- Update `readme.md` if API surface changed +- Update feature docstring in `generate_ffi.py` + +--- + +**Important invariants** (from project rules — always follow these): +- Never manually edit `ffi_static.rs` or `ffi_dynamic/` files (except `ffi_dynamic/mod.rs`) +- Naming: `dftd4_` prefix for functions, `DFTD4` prefix for structs +- Fallible variants use `_f` suffix returning `Result<_, DFTD4Error>` +- Feature flags are cumulative: each version extends the previous diff --git a/.claude/rules/agent-and-maintaince.md b/.claude/rules/agent-and-maintaince.md index 19bc8f3..0dec9aa 100644 --- a/.claude/rules/agent-and-maintaince.md +++ b/.claude/rules/agent-and-maintaince.md @@ -6,15 +6,17 @@ - Multi-co-author format (note no extra newline between co-authors): ``` Co-authored-by: Claude Code - Co-authored-by: glm-5 + Co-authored-by: glm-5.1 ``` - First co-author: Agent - Claude Code: noreply@anthropic.com - Second co-author: Model - qwen* (eg. qwen3.5-plus): qianwen_opensource@alibabacloud.com - - glm* (eg. glm-5): service@zhipuai.cn + - glm* (eg. glm-5.1): service@zhipuai.cn - minimax* (eg. MiniMax-M2.5): model@minimax.io - deepseek* (eg. DeepSeek-V3.2): service@deepseek.com - kimi* (eg. kimi-k2.5): growth@moonshot.cn - - Model name should include the version or details, such as `qwen3.5-plus`, `glm-5`, which can be inferred by Claude Code's `/model` property. + - doubao* (eg. doubao-seed-2.0-code): doubao-llm@bytedance.com + - Model name should include the version or details, such as `qwen3.5-plus`, `glm-5.1`, which can be inferred by Claude Code's `/model` property. +- You should not git commit by yourself, even you are in bypass mode. Please return the code changes to the user, and let the user decide whether to commit or not. diff --git a/.claude/rules/version-update-guide.md b/.claude/rules/version-update-guide.md new file mode 100644 index 0000000..28298ad --- /dev/null +++ b/.claude/rules/version-update-guide.md @@ -0,0 +1,41 @@ +# Version Update Invariants + +These are constant rules that always apply when working on version updates. For the step-by-step procedure, use the `/version-update ` command. + +## FFI generation rules + +- **Never manually edit** `ffi_static.rs` or any file in `ffi_dynamic/` (except `ffi_dynamic/mod.rs`). They are auto-generated by `dftd4/scripts/generate_ffi.py`. +- The only way to change FFI bindings is to update the header (`dftd4.h`) and re-run `generate_ffi.py`. +- `generate_ffi.py` reads the `DFTD4_API_SUFFIX__V_X_Y` macros from the header to determine which feature gate each function belongs to. + +## Feature flag rules + +- Feature flags are **cumulative**: `api-v3_0` → `api-v3_1` → `api-v3_2` → `api-v3_3` → `api-v3_4` → `api-v3_5` → `api-v4_0` → ... Each version extends the previous. +- A new version feature must always depend on the immediately preceding version feature. +- Static FFI respects feature flags (`#[cfg(feature = "api-vX_Y")]`). Dynamic loading ignores them — all functions are available at runtime. + +## Naming conventions + +- Functions exposed to users: `dftd4_` prefix (e.g., `dftd4_load_param`) +- Structs exposed to users: `DFTD4` prefix (e.g., `DFTD4Model`, `DFTD4RationalDampingParam`) +- Fallible variants: add `_f` suffix, returning `Result<_, DFTD4Error>` (e.g., `get_dispersion_f`) +- Infallible variants: unwrap the `_f` version (e.g., `get_dispersion`) + +## Safe wrapper patterns + +- Every new FFI function must get a safe Rust wrapper in `dftd4/src/interface.rs`. +- Each wrapper follows the error-check pattern: create `DFTD4Error`, call unsafe FFI, check `error.check()`. +- New opaque C types get their own `interface_*.rs` file, a new module in `lib.rs`, and a prelude re-export. + +## Version history reference + +| Version | Tag suffix | New functions | New types | Feature gate | +|---------|-----------|---------------|-----------|-------------| +| v3.0 | `V_3_0` | Core: version, error, structure, model (D4), rational damping, dispersion, delete | `dftd4_error`, `dftd4_structure`, `dftd4_model`, `dftd4_param` | `api-v3_0` | +| v3.1 | `V_3_1` | Custom D4 model, properties | — | `api-v3_1` | +| v3.2 | `V_3_2` | Pairwise dispersion | — | `api-v3_2` | +| v3.3 | `V_3_3` | (Reserved) | — | `api-v3_3` | +| v3.4 | `V_3_4` | (Reserved) | — | `api-v3_4` | +| v3.5 | `V_3_5` | Numerical hessian | — | `api-v3_5` | +| v4.0 | `V_4_0` | D4S model (new + custom) | — | `api-v4_0` | +| v4.2 | `V_4_2` | Realspace cutoff setters | — | `api-v4_2` | diff --git a/.github/workflows/test-dftd4.yml b/.github/workflows/test-dftd4.yml index 5ed95b9..33fbc15 100644 --- a/.github/workflows/test-dftd4.yml +++ b/.github/workflows/test-dftd4.yml @@ -20,8 +20,8 @@ jobs: ls /usr/share/miniconda/lib export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/share/miniconda/lib export DFTD4_DEV=1 - cargo test --no-default-features --features="api-v4_0" -- --nocapture - cargo test --no-default-features --features="api-v4_0" --examples -- --nocapture + cargo test --no-default-features --features="api-v4_2" -- --nocapture + cargo test --no-default-features --features="api-v4_2" --examples -- --nocapture test-dynamic-loading: runs-on: ubuntu-latest diff --git a/dftd4-src/external_deps/CMakeLists.txt b/dftd4-src/external_deps/CMakeLists.txt index c87752c..640c9f1 100644 --- a/dftd4-src/external_deps/CMakeLists.txt +++ b/dftd4-src/external_deps/CMakeLists.txt @@ -27,13 +27,13 @@ endif() if(DFTD4_VER) message(STATUS "User specified version of library (dftd4): ${DFTD4_VER}") else() - set(DFTD4_VER v4.1.0) + set(DFTD4_VER v4.2.0) message(STATUS "Download version of library (dftd4): ${DFTD4_VER}") endif() ExternalProject_Add(dftd4 GIT_REPOSITORY ${DFTD4_SRC} - GIT_TAG v4.1.0 + GIT_TAG v4.2.0 PREFIX ${PROJECT_BINARY_DIR}/deps CMAKE_ARGS -GNinja diff --git a/dftd4/Cargo.toml b/dftd4/Cargo.toml index 14b1015..16354d1 100644 --- a/dftd4/Cargo.toml +++ b/dftd4/Cargo.toml @@ -38,6 +38,7 @@ api-v3_3 = ["api-v3_2"] api-v3_4 = ["api-v3_3"] api-v3_5 = ["api-v3_4"] api-v4_0 = ["api-v3_5"] +api-v4_2 = ["api-v4_0"] [package.metadata.docs.rs] cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"] diff --git a/dftd4/examples/test_interface.rs b/dftd4/examples/test_interface.rs index f08e01a..1dbcd98 100644 --- a/dftd4/examples/test_interface.rs +++ b/dftd4/examples/test_interface.rs @@ -422,6 +422,34 @@ fn test_error_model() { assert!(param.is_err_and(|e| e.to_string().contains("not known"))); } +#[cfg(feature = "api-v4_2")] +fn test_smooth_realspace_cutoff() { + // Check smooth cutoff support changes dispersion energy + let numbers = vec![6, 6, 6, 1, 1, 1, 1]; + #[rustfmt::skip] + let positions = vec![ + 0.00000000000000, 0.00000000000000, -1.79755622305860, + 0.00000000000000, 0.00000000000000, 0.95338756106749, + 0.00000000000000, 0.00000000000000, 3.22281255790261, + -0.96412815539807, -1.66991895015711, -2.53624948351102, + -0.96412815539807, 1.66991895015711, -2.53624948351102, + 1.92825631079613, 0.00000000000000, -2.53624948351102, + 0.00000000000000, 0.00000000000000, 5.23010455462158, + ]; + + let param = DFTD4Param::load_rational_damping("pbe", false); + + // Get reference energy with default cutoffs + let mut model = DFTD4Model::new(&numbers, &positions, None, None, None); + let ref_energy = model.get_dispersion(¶m, false).energy; + + // Set tight cutoffs with smoothing, should change the energy + model.set_realspace_cutoff_smooth(4.0, 4.0, 30.0, 2.0, 2.0); + let res_energy = model.get_dispersion(¶m, false).energy; + + assert_ne!(ref_energy, res_energy); +} + #[test] fn test() { test_rational_damping_noargs(); @@ -437,6 +465,8 @@ fn test() { #[cfg(feature = "api-v3_1")] test_properties(); test_error_model(); + #[cfg(feature = "api-v4_2")] + test_smooth_realspace_cutoff(); } fn main() { @@ -453,4 +483,6 @@ fn main() { #[cfg(feature = "api-v3_1")] test_properties(); test_error_model(); + #[cfg(feature = "api-v4_2")] + test_smooth_realspace_cutoff(); } diff --git a/dftd4/header/dftd4.h b/dftd4/header/dftd4.h index 22ed0b2..6c9022d 100644 --- a/dftd4/header/dftd4.h +++ b/dftd4/header/dftd4.h @@ -32,6 +32,7 @@ #define DFTD4_API_SUFFIX__V_3_4 #define DFTD4_API_SUFFIX__V_3_5 #define DFTD4_API_SUFFIX__V_4_0 +#define DFTD4_API_SUFFIX__V_4_2 /// Error handle class typedef struct _dftd4_error* dftd4_error; @@ -145,6 +146,24 @@ dftd4_custom_d4s_model(dftd4_error /* error */, DFTD4_API_ENTRY void DFTD4_API_CALL dftd4_delete_model(dftd4_model* /* disp */) DFTD4_API_SUFFIX__V_3_0; +/// Set realspace cutoffs (quantities in Bohr) +DFTD4_API_ENTRY void DFTD4_API_CALL +dftd4_set_model_realspace_cutoff(dftd4_error /* error */, + dftd4_model /* disp */, + double /* disp2 */, + double /* disp3 */, + double /* cn */) DFTD4_API_SUFFIX__V_4_2; + +/// Set realspace cutoffs with smoothing widths (quantities in Bohr) +DFTD4_API_ENTRY void DFTD4_API_CALL +dftd4_set_model_realspace_cutoff_smooth(dftd4_error /* error */, + dftd4_model /* disp */, + double /* disp2 */, + double /* disp3 */, + double /* cn */, + double /* width2 */, + double /* width3 */) DFTD4_API_SUFFIX__V_4_2; + /* * Damping parameter class **/ diff --git a/dftd4/scripts/generate_ffi.py b/dftd4/scripts/generate_ffi.py index 1b7c89e..a4c783a 100644 --- a/dftd4/scripts/generate_ffi.py +++ b/dftd4/scripts/generate_ffi.py @@ -46,6 +46,7 @@ ("V_3_4", "api-v3_4"), ("V_3_5", "api-v3_5"), ("V_4_0", "api-v4_0"), + ("V_4_2", "api-v4_2"), ] # Default API version (used when no features are specified) @@ -145,7 +146,8 @@ def generate_static_ffi(token, version_map): //! - `api-v3_3`: Extends api-v3_2 //! - `api-v3_4`: Extends api-v3_3 //! - `api-v3_5`: Extends api-v3_4, adds numerical hessian -//! - `api-v4_0`: Full API, adds D4S model +//! - `api-v4_0`: Extends api-v3_5, adds D4S model +//! - `api-v4_2`: Extends api-v4_0, adds realspace cutoff setters //! //! Features are cumulative: enabling `api-v3_5` also enables all functions from //! earlier versions (api-v3_0, api-v3_1, api-v3_2, api-v3_3, api-v3_4). @@ -340,199 +342,6 @@ def dyload_main(token): } -DYLOAD_MOD_TEMPLATE = """//! FFI module for dftd4 (dynamic loading). -//! -//! This module provides dynamic loading support. - -#![allow(non_snake_case)] -#![allow(non_camel_case_types)] -#![allow(clippy::missing_safety_doc)] -#![allow(clippy::type_complexity)] -#![allow(clippy::too_many_arguments)] - -pub const MOD_NAME: &str = module_path!(); -pub const LIB_NAME: &str = "DFTD4"; -pub const LIB_NAME_SHOW: &str = "dftd4"; -pub const LIB_NAME_LINK: &str = "dftd4"; - -#[cfg(feature = "dynamic_loading")] -mod dynamic_loading_specific { - use super::*; - use libloading::Library; - use std::fmt::Debug; - use std::sync::OnceLock; - - use std::env::consts::{DLL_PREFIX, DLL_SUFFIX}; - - /// Detect Python interpreter path and return the corresponding lib directory. - /// Uses OnceLock pattern for lazy initialization. - static PYTHON_LIB_PATH: OnceLock> = OnceLock::new(); - - fn detect_python_lib_path() -> Option { - PYTHON_LIB_PATH.get_or_init(|| { - // 1. Check explicit environment variable first - if let Ok(python_path) = std::env::var("DFTD4_PYTHON_PATH") { - if let Some(lib_path) = extract_lib_from_python_bin(&python_path) { - return Some(lib_path); - } - } - - // 2. Try to find python in PATH - if let Ok(paths) = std::env::var("PATH") { - for path in paths.split(":") { - for python_name in ["python3", "python"] { - let python_bin = format!("{path}/{python_name}"); - if std::path::Path::new(&python_bin).exists() { - if let Some(lib_path) = extract_lib_from_python_bin(&python_bin) { - return Some(lib_path); - } - } - } - } - } - - None - }).clone() - } - - fn extract_lib_from_python_bin(python_bin: &str) -> Option { - // If python is at /path/to/bin/python, library should be at /path/to/lib/ - let bin_path = std::path::Path::new(python_bin); - if let Some(parent) = bin_path.parent() { - if let Some(base) = parent.parent() { - let lib_path = base.join("lib"); - if lib_path.exists() { - return Some(lib_path.to_string_lossy().to_string()); - } - } - } - None - } - - fn get_lib_candidates() -> Vec { - let mut candidates = vec![]; - - // User-defined candidates via environment variables - for env_var in [format!("DFTD4_DYLOAD_{LIB_NAME}").as_str(), "DFTD4_DYLOAD"] { - if let Ok(path) = std::env::var(env_var) { - candidates.extend(path.split(":").map(|s| s.to_string())); - } - } - - // LD_LIBRARY_PATH style discovery - for env_var in ["LD_LIBRARY_PATH", "DYLD_LIBRARY_PATH"] { - if let Ok(paths) = std::env::var(env_var) { - for path in paths.split(":") { - candidates.push(format!("{path}/{DLL_PREFIX}{LIB_NAME_LINK}{DLL_SUFFIX}")); - } - } - } - - // Python interpreter path discovery (cached) - if let Some(lib_path) = detect_python_lib_path() { - candidates.push(format!("{lib_path}/{DLL_PREFIX}{LIB_NAME_LINK}{DLL_SUFFIX}")); - } - - // Standard system candidates - candidates.extend(vec![ - format!("{DLL_PREFIX}{LIB_NAME_LINK}{DLL_SUFFIX}"), - format!("{DLL_PREFIX}dftd4{DLL_SUFFIX}"), - format!("/usr/lib/{DLL_PREFIX}{LIB_NAME_LINK}{DLL_SUFFIX}"), - format!("/usr/local/lib/{DLL_PREFIX}{LIB_NAME_LINK}{DLL_SUFFIX}"), - format!("/lib/{DLL_PREFIX}{LIB_NAME_LINK}{DLL_SUFFIX}"), - ]); - candidates - } - - fn check_lib_loaded(lib: &DyLoadLib) -> bool { - lib.dftd4_get_version.is_some() - } - - fn panic_no_lib_found(candidates: &[S], err_msg: &str) -> ! { - panic!( - r#" -This happens in module `{MOD_NAME}`. -Unable to dynamically load the {LIB_NAME_SHOW} (`{LIB_NAME_LINK}`) shared library. -Candidates: {candidates:#?} - -Please check: -- If dynamic-loading is not desired, disable the `dynamic_loading` feature in Cargo.toml. -- Use environment variable `DFTD4_DYLOAD_{LIB_NAME}` or `DFTD4_DYLOAD` to specify the library path. -- If `lib{LIB_NAME_LINK}.so` is installed on your system. -- If `LD_LIBRARY_PATH` is set correctly. -- Python interpreter path discovery: if Python is at `/path/bin/python`, - the library is expected at `/path/lib/libdftd4.so`. - -Error message(s): -{err_msg} -"# - ) - } - - fn panic_condition_not_met(candidates: &[S]) -> ! { - panic!( - r#" -This happens in module `{MOD_NAME}`. -Library loaded but condition not met: `dftd4_get_version` not found. -Found libraries: {candidates:#?} - -Please check that the loaded library is a valid dftd4 library. -"# - ) - } - - pub unsafe fn dyload_lib() -> &'static DyLoadLib { - static LIB: OnceLock = OnceLock::new(); - - LIB.get_or_init(|| { - let candidates = get_lib_candidates(); - let (mut libraries, mut libraries_path) = (vec![], vec![]); - let mut err_msg = String::new(); - for candidate in &candidates { - match Library::new(candidate) { - Ok(l) => { - libraries.push(l); - libraries_path.push(candidate.to_string()); - }, - Err(e) => err_msg.push_str(&format!("Failed to load `{candidate}`: {e}\n")), - } - } - let lib = DyLoadLib::new(libraries, libraries_path); - if lib.__libraries.is_empty() { - panic_no_lib_found(&candidates, &err_msg); - } - if !check_lib_loaded(&lib) { - panic_condition_not_met(&lib.__libraries_path); - } - lib - }) - } -} - -#[cfg(feature = "dynamic_loading")] -pub use dynamic_loading_specific::*; - -/* #region general configuration */ - -pub(crate) mod ffi_base; -pub use ffi_base::*; - -#[cfg(feature = "dynamic_loading")] -pub(crate) mod dyload_compatible; -#[cfg(feature = "dynamic_loading")] -pub(crate) mod dyload_initializer; -#[cfg(feature = "dynamic_loading")] -pub(crate) mod dyload_struct; - -#[cfg(feature = "dynamic_loading")] -pub use dyload_compatible::*; -#[cfg(feature = "dynamic_loading")] -pub use dyload_struct::*; - -/* #endregion */ -""" - - # ## Main execution def main(): @@ -590,9 +399,6 @@ def main(): with open(f"{path_out}/src/ffi_dynamic/dyload_compatible.rs", "w") as f: f.write(dyload_files["dyload_compatible"]) - with open(f"{path_out}/src/ffi_dynamic/mod.rs", "w") as f: - f.write(DYLOAD_MOD_TEMPLATE) - # Run cargo fmt os.chdir(path_out) subprocess.run(["cargo", "fmt"]) diff --git a/dftd4/src/ffi_dynamic/dyload_compatible.rs b/dftd4/src/ffi_dynamic/dyload_compatible.rs index f614b16..d0e5044 100644 --- a/dftd4/src/ffi_dynamic/dyload_compatible.rs +++ b/dftd4/src/ffi_dynamic/dyload_compatible.rs @@ -84,6 +84,30 @@ pub unsafe fn dftd4_delete_model(arg1: *mut dftd4_model) { dyload_lib().dftd4_delete_model.unwrap()(arg1) } +pub unsafe fn dftd4_set_model_realspace_cutoff( + arg1: dftd4_error, + arg2: dftd4_model, + arg3: f64, + arg4: f64, + arg5: f64, +) { + dyload_lib().dftd4_set_model_realspace_cutoff.unwrap()(arg1, arg2, arg3, arg4, arg5) +} + +pub unsafe fn dftd4_set_model_realspace_cutoff_smooth( + arg1: dftd4_error, + arg2: dftd4_model, + arg3: f64, + arg4: f64, + arg5: f64, + arg6: f64, + arg7: f64, +) { + dyload_lib().dftd4_set_model_realspace_cutoff_smooth.unwrap()( + arg1, arg2, arg3, arg4, arg5, arg6, arg7, + ) +} + pub unsafe fn dftd4_new_rational_damping( arg1: dftd4_error, arg2: f64, diff --git a/dftd4/src/ffi_dynamic/dyload_initializer.rs b/dftd4/src/ffi_dynamic/dyload_initializer.rs index a7502f3..ac0dfd3 100644 --- a/dftd4/src/ffi_dynamic/dyload_initializer.rs +++ b/dftd4/src/ffi_dynamic/dyload_initializer.rs @@ -28,6 +28,16 @@ impl DyLoadLib { dftd4_custom_d4_model: get_symbol(&libs, b"dftd4_custom_d4_model\0").map(|sym| *sym), dftd4_custom_d4s_model: get_symbol(&libs, b"dftd4_custom_d4s_model\0").map(|sym| *sym), dftd4_delete_model: get_symbol(&libs, b"dftd4_delete_model\0").map(|sym| *sym), + dftd4_set_model_realspace_cutoff: get_symbol( + &libs, + b"dftd4_set_model_realspace_cutoff\0", + ) + .map(|sym| *sym), + dftd4_set_model_realspace_cutoff_smooth: get_symbol( + &libs, + b"dftd4_set_model_realspace_cutoff_smooth\0", + ) + .map(|sym| *sym), dftd4_new_rational_damping: get_symbol(&libs, b"dftd4_new_rational_damping\0") .map(|sym| *sym), dftd4_load_rational_damping: get_symbol(&libs, b"dftd4_load_rational_damping\0") diff --git a/dftd4/src/ffi_dynamic/dyload_struct.rs b/dftd4/src/ffi_dynamic/dyload_struct.rs index 47573cc..b17c3af 100644 --- a/dftd4/src/ffi_dynamic/dyload_struct.rs +++ b/dftd4/src/ffi_dynamic/dyload_struct.rs @@ -61,6 +61,20 @@ pub struct DyLoadLib { ) -> dftd4_model, >, pub dftd4_delete_model: Option, + pub dftd4_set_model_realspace_cutoff: Option< + unsafe extern "C" fn(arg1: dftd4_error, arg2: dftd4_model, arg3: f64, arg4: f64, arg5: f64), + >, + pub dftd4_set_model_realspace_cutoff_smooth: Option< + unsafe extern "C" fn( + arg1: dftd4_error, + arg2: dftd4_model, + arg3: f64, + arg4: f64, + arg5: f64, + arg6: f64, + arg7: f64, + ), + >, pub dftd4_new_rational_damping: Option< unsafe extern "C" fn( arg1: dftd4_error, diff --git a/dftd4/src/ffi_static.rs b/dftd4/src/ffi_static.rs index 63213b4..1590f02 100644 --- a/dftd4/src/ffi_static.rs +++ b/dftd4/src/ffi_static.rs @@ -10,7 +10,8 @@ //! - `api-v3_3`: Extends api-v3_2 //! - `api-v3_4`: Extends api-v3_3 //! - `api-v3_5`: Extends api-v3_4, adds numerical hessian -//! - `api-v4_0`: Full API, adds D4S model +//! - `api-v4_0`: Extends api-v3_5, adds D4S model +//! - `api-v4_2`: Extends api-v4_0, adds realspace cutoff setters //! //! Features are cumulative: enabling `api-v3_5` also enables all functions from //! earlier versions (api-v3_0, api-v3_1, api-v3_2, api-v3_3, api-v3_4). @@ -113,6 +114,26 @@ unsafe extern "C" { #[cfg(feature = "api-v3_0")] #[doc = " Delete dispersion model"] pub fn dftd4_delete_model(arg1: *mut dftd4_model); + #[cfg(feature = "api-v4_2")] + #[doc = " Set realspace cutoffs (quantities in Bohr)"] + pub fn dftd4_set_model_realspace_cutoff( + arg1: dftd4_error, + arg2: dftd4_model, + arg3: f64, + arg4: f64, + arg5: f64, + ); + #[cfg(feature = "api-v4_2")] + #[doc = " Set realspace cutoffs with smoothing widths (quantities in Bohr)"] + pub fn dftd4_set_model_realspace_cutoff_smooth( + arg1: dftd4_error, + arg2: dftd4_model, + arg3: f64, + arg4: f64, + arg5: f64, + arg6: f64, + arg7: f64, + ); #[cfg(feature = "api-v3_0")] #[doc = " Create new rational damping parameters"] pub fn dftd4_new_rational_damping( diff --git a/dftd4/src/interface.rs b/dftd4/src/interface.rs index 7c336ec..e302c2b 100644 --- a/dftd4/src/interface.rs +++ b/dftd4/src/interface.rs @@ -633,6 +633,35 @@ impl DFTD4Model { Self::custom_d4_f(numbers, positions, ga, gc, wf, charge, lattice, periodic).unwrap() } + /// Set realspace cutoffs (quantities in Bohr). + /// + /// - `disp2` - Cutoff for two-body dispersion + /// - `disp3` - Cutoff for three-body dispersion + /// - `cn` - Cutoff for coordination number + #[cfg(feature = "api-v4_2")] + pub fn set_realspace_cutoff(&mut self, disp2: f64, disp3: f64, cn: f64) { + self.set_realspace_cutoff_f(disp2, disp3, cn).unwrap() + } + + /// Set realspace cutoffs with smoothing widths (quantities in Bohr). + /// + /// - `disp2` - Cutoff for two-body dispersion + /// - `disp3` - Cutoff for three-body dispersion + /// - `cn` - Cutoff for coordination number + /// - `width2` - Smoothing width for two-body dispersion + /// - `width3` - Smoothing width for three-body dispersion + #[cfg(feature = "api-v4_2")] + pub fn set_realspace_cutoff_smooth( + &mut self, + disp2: f64, + disp3: f64, + cn: f64, + width2: f64, + width3: f64, + ) { + self.set_realspace_cutoff_smooth_f(disp2, disp3, cn, width2, width3).unwrap() + } + /// Evaluate the dispersion energy and its derivatives. /// /// Output `DFTD4Output` contains @@ -790,6 +819,61 @@ impl DFTD4Model { } } + /// Set realspace cutoffs (quantities in Bohr, failable). + /// + /// # See also + /// + /// [`DFTD4Model::set_realspace_cutoff`] + #[cfg(feature = "api-v4_2")] + pub fn set_realspace_cutoff_f( + &mut self, + disp2: f64, + disp3: f64, + cn: f64, + ) -> Result<(), DFTD4Error> { + let mut error = DFTD4Error::new(); + unsafe { + ffi::dftd4_set_model_realspace_cutoff(error.get_c_ptr(), self.ptr, disp2, disp3, cn) + }; + match error.check() { + true => Err(error), + false => Ok(()), + } + } + + /// Set realspace cutoffs with smoothing widths (quantities in Bohr, + /// failable). + /// + /// # See also + /// + /// [`DFTD4Model::set_realspace_cutoff_smooth`] + #[cfg(feature = "api-v4_2")] + pub fn set_realspace_cutoff_smooth_f( + &mut self, + disp2: f64, + disp3: f64, + cn: f64, + width2: f64, + width3: f64, + ) -> Result<(), DFTD4Error> { + let mut error = DFTD4Error::new(); + unsafe { + ffi::dftd4_set_model_realspace_cutoff_smooth( + error.get_c_ptr(), + self.ptr, + disp2, + disp3, + cn, + width2, + width3, + ) + }; + match error.check() { + true => Err(error), + false => Ok(()), + } + } + /// Evaluate the dispersion energy and its derivatives (failable). /// /// # See also diff --git a/readme.md b/readme.md index 90e9f9b..1669144 100644 --- a/readme.md +++ b/readme.md @@ -2,7 +2,7 @@ This project contains dftd4 FFI bindings, wrapper and build-from-source. -Current binding of dftd4: [![v4.1.0](https://img.shields.io/github/v/release/dftd4/dftd4)](https://github.com/dftd4/dftd4/releases/v4.1.0) +Current binding of dftd4: [![v4.2.0](https://img.shields.io/github/v/release/dftd4/dftd4?filter=v4.2.0)](https://github.com/dftd4/dftd4/releases/v4.2.0) Source code of dftd4 is available on [github](https://github.com/dftd4/dftd4). @@ -16,7 +16,7 @@ This crate contains dftd4 FFI bindings and wrapper. |--|--| | Crate | [![Crate](https://img.shields.io/crates/v/dftd4.svg)](https://crates.io/crates/dftd4) | | API Document | [![API Documentation](https://docs.rs/dftd4/badge.svg)](https://docs.rs/dftd4) | -| FFI Binding | [![v4.1.0](https://img.shields.io/github/v/release/dftd4/dftd4?filter=v4.1.0)](https://github.com/dftd4/dftd4/releases/v4.1.0) | +| FFI Binding | [![v4.2.0](https://img.shields.io/github/v/release/dftd4/dftd4?filter=v4.2.0)](https://github.com/dftd4/dftd4/releases/v4.2.0) | ### Dynamic loading guide @@ -105,11 +105,13 @@ println!("Dispersion energy: {eng}"); Default cargo features of `dftd4` are: - **`api-v4_0`**: Corresponding to the original dftd4 [v4.0](https://github.com/dftd4/dftd4/releases/tag/v4.0.0). This will enable rational damping (BJ), custom D4 model, pairwise dispersion, properties, and numerical hessian. D4S model also included in `api-v4_0`. +- **`api-v4_2`**: Corresponding to the original dftd4 [v4.2](https://github.com/dftd4/dftd4/releases/tag/v4.2.0). Extends `api-v4_0` with realspace cutoff setters. - **`dynamic_loading`**: This will enable dynamic loading of `libdftd4` library, which can be more flexible for users who do not want to perform static linking. Please place `libdftd4.so` in `LD_LIBRARY_PATH` (for macos, place `libdftd4.dylib` in `DYLD_LIBRARY_PATH`), or make dftd4 available in your python environment, and function symbols will be loaded at runtime. Other cargo features of `dftd4` are: -- **`api-v3_0`** through **`api-v4_0`**: Versioned API features (cumulative). Each version enables all functions introduced in that version. Note: dynamic loading ignores API version features — all functions are available at runtime. +- **`api-v3_0`** through **`api-v4_2`**: Versioned API features (cumulative). Each version enables all functions introduced in that version. Note: dynamic loading ignores API version features — all functions are available at runtime. - **`api-v4_0`**: Enables D4S dispersion model support. +- **`api-v4_2`**: Enables realspace cutoff setters. ## Installation guide and Crate `dftd4-src` @@ -171,7 +173,7 @@ If you have not compiled `dftd4` library, you may try out cargo feature `build_f CMake configurable variables (can be defined as environment variables): - `DFTD4_SRC`: git repository source directory or URL; - - `DFTD4_VER`: version of DFT-D4 (default v4.1.0); + - `DFTD4_VER`: version of DFT-D4 (default v4.2.0); - **`static`**: This will link static libary instead of dynamic one. Please note that 1. static linking may require additional Fortran and OpenMP linking, which is not provided in this crate; 2. staticly linking LGPL-3.0 license may require your project to be GPL-3.0.