diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 100644 index 0000000..657a8fb --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1,37 @@ +# Agent instruction of DFTD3 Rust FFI and Wrapper + +## Notes to human developers + +You should also create a file `CLAUDE.local.md` to place local resources: +- `DFTD3_REPO_PATH`: local of original simple-dftd3 repository. The source code can help you understand how dftd3 works. + +## DFTD3 original library + +General rules +- This repository should live at `DFTD3_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 dftd3. + +Important files for FFI and wrapper development: +- `include/s-dftd3.h`: the headers. Note that these files are also copied to this project under `dftd3/headers` folder. +- `python/dftd3`: the python wrapper of dftd3. We should at least implement all major features of the certain wrapper: + - `interface.py`, corresponding to this project `dftd3/src/interface.rs`, also `interface_gcp.rs`. + - `parameters.py`, corresponding to this project `dftd3/src/parameters.rs`. + - Make sure the functionalities are tested. We use `dftd3/example/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 `dftd3/src/assets/parameters.toml` in this project. + +## The additional feature in this crate + +- We support toml parsing of DFTD3 parameters. The related code is at `/dftd3/src/parsing.rs`. The related test is at `dftd3/example/test_parsing.rs`. +- We support dynamic loading of the DFTD3 library. +- We use tags such as `api-v0_5` to reflect the API version of DFTD3 we are using. + +## Naming convention + +- For functions and structs that will be exposed to users, add prefix `dftd3_` for general functions, and `DFTD3` for structs. +- If some function is to be fallible, we can add suffix `_f` (`fn _f -> Result<_, DFTD3Error>`). + +## Header handling + +We use bindgen (python script at `scripts/generate_bindings.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..c9ff9ac --- /dev/null +++ b/.claude/commands/version-update.md @@ -0,0 +1,174 @@ +--- +description: Update dftd3-rs bindings to support a new simple-dftd3 version +argument-hint: (e.g., v1.4.0) +--- + +You are performing a version update of the dftd3-rs bindings to support simple-dftd3 **$ARGUMENTS**. + +Read the CLAUDE.local.md file to get `DFTD3_REPO_PATH`, then follow the procedure below. Use `DFTD3_REPO_PATH` as the path to the upstream simple-dftd3 repository throughout. + +## Step 0: Gather upstream changes + +Determine the previous version by checking the last entry in the `api_versions` list in `dftd3/scripts/generate_ffi.py`. Then diff the upstream repo: + +```bash +cd $DFTD3_REPO_PATH +git fetch --tags +git checkout $ARGUMENTS +git diff v..$ARGUMENTS -- include/s-dftd3.h # header changes (CRITICAL) +git diff v..$ARGUMENTS -- python/dftd3/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 `SDFTD3_API_SUFFIX__V_X_Y` macro (defines the version tag) +- New function declarations (tagged with version suffix) +- New opaque types (e.g., `dftd3_gcp`) +- 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: `dftd3/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 dftd3/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: `dftd3/Cargo.toml` + +Add the new feature extending the previous one: +```toml +api-vX_Y = ["api-v"] +``` + +Rules: +- Must extend the **previous** feature (cumulative chain) +- If the version introduces a new capability category (like `gcp` for v1_3), create a separate feature and depend on it +- 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 = DFTD3Error::new(); + unsafe { ffi::dftd3_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 `DFTD3XyzDampingParam` 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 `DFTD3Param` +3. Implement `DFTD3ParamAPI` for the new struct +4. Add `impl_load_param_api!` and `impl_damping_param_builder!` macro invocations +5. Update `dftd3_load_param`/`dftd3_load_param_f` match arms with `#[cfg]` gating + +### Pattern C: New opaque type + +1. Create a new `interface_xyz.rs` file +2. Add module in `lib.rs` gated by appropriate feature +3. Update `prelude` +4. Follow the `interface_gcp.rs` pattern + +## Step 5: Update parameters module (if parameters changed) + +If `assets/parameters.toml` changed upstream: +1. Copy: `cp $DFTD3_REPO_PATH/assets/parameters.toml dftd3/src/parameters.toml` +2. Update `parameters.rs`: + - Add variant to `D3MethodParams` and `D3DefaultParams` + - Add to `DFTD3DampingParamEnum` + - 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: `dftd3/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. + +## Step 9: Update dftd3-src (if build system changed) + +```bash +git diff v..$ARGUMENTS -- CMakeLists.txt meson.build +``` +Update `dftd3-src/external_deps/` and `build.rs` if needed. + +## Step 10: Update CI + +Update `.github/workflows/test-dftd3.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 +- Naming: `dftd3_` prefix for functions, `DFTD3` prefix for structs +- Fallible variants use `_f` suffix returning `Result<_, DFTD3Error>` +- 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 457b24d..0dec9aa 100644 --- a/.claude/rules/agent-and-maintaince.md +++ b/.claude/rules/agent-and-maintaince.md @@ -6,17 +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 - 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`, which can be inferred by Claude Code's `/model` property. + - 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. Please return the code changes to the user, and let the user decide whether to commit or not. +- 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..6d315af --- /dev/null +++ b/.claude/rules/version-update-guide.md @@ -0,0 +1,40 @@ +# 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/`. They are auto-generated by `dftd3/scripts/generate_ffi.py`. +- The only way to change FFI bindings is to update the header (`s-dftd3.h`) and re-run `generate_ffi.py`. +- `generate_ffi.py` reads the `SDFTD3_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-v0_2` → `api-v0_3` → `api-v0_4` → `api-v0_5` → `api-v1_3` → `api-v1_4` → ... Each version extends the previous. +- A new version feature must always depend on the immediately preceding version feature. +- If a version introduces a distinct capability (like `gcp`), create a separate feature and have the version feature depend on it. +- 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: `dftd3_` prefix (e.g., `dftd3_load_param`) +- Structs exposed to users: `DFTD3` prefix (e.g., `DFTD3Model`, `DFTD3RationalDampingParam`) +- Fallible variants: add `_f` suffix, returning `Result<_, DFTD3Error>` (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 the appropriate `interface*.rs` file. +- Each wrapper follows the error-check pattern: create `DFTD3Error`, call unsafe FFI, check `error.check()`. +- New opaque C types (e.g., `dftd3_gcp`) 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 | +|---------|-----------|---------------|-----------|-------------| +| v0.2 | `V_0_2` | Core: version, error, structure, model, dispersion, delete | `dftd3_error`, `dftd3_structure`, `dftd3_model`, `dftd3_param` | `api-v0_2` | +| v0.3 | `V_0_3` | (Reserved) | — | `api-v0_3` | +| v0.4 | `V_0_4` | Damping constructors: zero, rational, mzero, mrational (new + load) | — | `api-v0_4` | +| v0.5 | `V_0_5` | Optimized power damping (new + load), pairwise dispersion, realspace cutoff | — | `api-v0_5` | +| v1.3 | `V_1_3` | CSO damping (new + load), GCP (load, cutoff, delete, counterpoise) | `dftd3_gcp` | `api-v1_3` (+`gcp`) | +| v1.4 | `V_1_4` | Smooth realspace cutoff | — | `api-v1_4` | diff --git a/.github/workflows/test-dftd3.yml b/.github/workflows/test-dftd3.yml index 7af12da..294f461 100644 --- a/.github/workflows/test-dftd3.yml +++ b/.github/workflows/test-dftd3.yml @@ -19,8 +19,8 @@ jobs: ls /usr/share/miniconda/lib export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/share/miniconda/lib export DFTD3_DEV=1 - cargo test --no-default-features --features="api-v1_3" -- --nocapture - cargo test --no-default-features --features="api-v1_3" --examples -- --nocapture + cargo test --no-default-features --features="api-v1_4" -- --nocapture + cargo test --no-default-features --features="api-v1_4" --examples -- --nocapture test-dynamic-loading: runs-on: ubuntu-latest @@ -34,5 +34,5 @@ jobs: ls /usr/share/miniconda/lib export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/share/miniconda/lib export DFTD3_DEV=1 - cargo test --features="api-v1_3" -- --nocapture - cargo test --features="api-v1_3" --examples -- --nocapture + cargo test --features="api-v1_4" -- --nocapture + cargo test --features="api-v1_4" --examples -- --nocapture diff --git a/.gitignore b/.gitignore index 2a6f7aa..1d66cba 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ tarpaulin-report.html .idea book *.npy +*.local.* # bindgen blas_bindgen.h diff --git a/dftd3/Cargo.toml b/dftd3/Cargo.toml index 794dc58..d3642cb 100644 --- a/dftd3/Cargo.toml +++ b/dftd3/Cargo.toml @@ -40,6 +40,7 @@ api-v0_3 = ["api-v0_2"] api-v0_4 = ["api-v0_3"] api-v0_5 = ["api-v0_4"] api-v1_3 = ["api-v0_5", "gcp"] +api-v1_4 = ["api-v1_3"] # JSON input support for parsing module json = ["dep:serde_json"] diff --git a/dftd3/examples/test_interface.rs b/dftd3/examples/test_interface.rs index a6d1112..3a54132 100644 --- a/dftd3/examples/test_interface.rs +++ b/dftd3/examples/test_interface.rs @@ -254,6 +254,18 @@ fn test_b97d_d3_op(model: DFTD3Model, #[case] atm: bool, #[case] expected: f64) assert_abs_diff_eq!(res.energy, expected, epsilon = 1e-8); } +#[cfg(feature = "api-v1_4")] +#[rstest] +fn test_smooth_realspace_cutoff(model: DFTD3Model) { + let param = DFTD3RationalDampingParam::load_param("pbe0", true); + let ref_energy = model.get_dispersion(¶m, false).energy; + + model.set_realspace_cutoff_smooth(8.0, 8.0, 40.0, 4.0, 4.0); + let res_energy = model.get_dispersion(¶m, false).energy; + + assert!((res_energy - ref_energy).abs() > 1e-8); +} + // GCP tests #[rstest] #[cfg(feature = "gcp")] diff --git a/dftd3/header/s-dftd3.h b/dftd3/header/s-dftd3.h index b77baba..9b670e1 100644 --- a/dftd3/header/s-dftd3.h +++ b/dftd3/header/s-dftd3.h @@ -30,6 +30,7 @@ #define SDFTD3_API_SUFFIX__V_0_4 #define SDFTD3_API_SUFFIX__V_0_5 #define SDFTD3_API_SUFFIX__V_1_3 +#define SDFTD3_API_SUFFIX__V_1_4 /// Error handle class typedef struct _dftd3_error* dftd3_error; @@ -131,6 +132,16 @@ dftd3_set_model_realspace_cutoff(dftd3_error /* error */, double /* disp3 */, double /* cn */) SDFTD3_API_SUFFIX__V_0_5; +/// Set realspace cutoffs with smoothing widths (quantities in Bohr) +SDFTD3_API_ENTRY void SDFTD3_API_CALL +dftd3_set_model_realspace_cutoff_smooth(dftd3_error /* error */, + dftd3_model /* model */, + double /* disp2 */, + double /* disp3 */, + double /* cn */, + double /* width2 */, + double /* width3 */) SDFTD3_API_SUFFIX__V_1_4; + /// Delete dispersion model SDFTD3_API_ENTRY void SDFTD3_API_CALL dftd3_delete_model(dftd3_model* /* disp */) SDFTD3_API_SUFFIX__V_0_2; @@ -298,4 +309,4 @@ dftd3_get_counterpoise(dftd3_error /* error */, dftd3_gcp /* gcp */, double* /* energy */, double* /* gradient[n][3] */, - double* /* sigma[3][3] */) SDFTD3_API_SUFFIX__V_1_3; \ No newline at end of file + double* /* sigma[3][3] */) SDFTD3_API_SUFFIX__V_1_3; diff --git a/dftd3/scripts/generate_ffi.py b/dftd3/scripts/generate_ffi.py index 4ef9f78..0a760d0 100644 --- a/dftd3/scripts/generate_ffi.py +++ b/dftd3/scripts/generate_ffi.py @@ -44,6 +44,7 @@ ("V_0_4", "api-v0_4"), ("V_0_5", "api-v0_5"), ("V_1_3", "api-v1_3"), + ("V_1_4", "api-v1_4"), ] # Default API version (used when no features are specified) @@ -142,6 +143,7 @@ def generate_static_ffi(token, version_map): //! - `api-v0_4`: Extends api-v0_3, adds damping parameter functions //! - `api-v0_5`: Extends api-v0_4, adds optimized power damping and pairwise dispersion //! - `api-v1_3`: Full API, adds CSO damping and GCP functions +//! - `api-v1_4`: Extends api-v1_3, adds smooth realspace cutoff //! //! Features are cumulative: enabling `api-v0_5` also enables all functions from //! earlier versions (api-v0_2, api-v0_3, api-v0_4). @@ -336,199 +338,6 @@ def dyload_main(token): } -DYLOAD_MOD_TEMPLATE = """//! FFI module for dftd3 (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 = "DFTD3"; -pub const LIB_NAME_SHOW: &str = "s-dftd3"; -pub const LIB_NAME_LINK: &str = "s-dftd3"; - -#[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("DFTD3_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!("DFTD3_DYLOAD_{LIB_NAME}").as_str(), "DFTD3_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}dftd3{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.dftd3_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 `DFTD3_DYLOAD_{LIB_NAME}` or `DFTD3_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/libs-dftd3.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: `dftd3_get_version` not found. -Found libraries: {candidates:#?} - -Please check that the loaded library is a valid s-dftd3 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(): @@ -586,9 +395,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/dftd3/src/ffi_dynamic/dyload_compatible.rs b/dftd3/src/ffi_dynamic/dyload_compatible.rs index f45aff4..e75d3e8 100644 --- a/dftd3/src/ffi_dynamic/dyload_compatible.rs +++ b/dftd3/src/ffi_dynamic/dyload_compatible.rs @@ -66,6 +66,20 @@ pub unsafe fn dftd3_set_model_realspace_cutoff( dyload_lib().dftd3_set_model_realspace_cutoff.unwrap()(arg1, arg2, arg3, arg4, arg5) } +pub unsafe fn dftd3_set_model_realspace_cutoff_smooth( + arg1: dftd3_error, + arg2: dftd3_model, + arg3: f64, + arg4: f64, + arg5: f64, + arg6: f64, + arg7: f64, +) { + dyload_lib().dftd3_set_model_realspace_cutoff_smooth.unwrap()( + arg1, arg2, arg3, arg4, arg5, arg6, arg7, + ) +} + pub unsafe fn dftd3_delete_model(arg1: *mut dftd3_model) { dyload_lib().dftd3_delete_model.unwrap()(arg1) } diff --git a/dftd3/src/ffi_dynamic/dyload_initializer.rs b/dftd3/src/ffi_dynamic/dyload_initializer.rs index 7142930..383ef4d 100644 --- a/dftd3/src/ffi_dynamic/dyload_initializer.rs +++ b/dftd3/src/ffi_dynamic/dyload_initializer.rs @@ -29,6 +29,11 @@ impl DyLoadLib { b"dftd3_set_model_realspace_cutoff\0", ) .map(|sym| *sym), + dftd3_set_model_realspace_cutoff_smooth: get_symbol( + &libs, + b"dftd3_set_model_realspace_cutoff_smooth\0", + ) + .map(|sym| *sym), dftd3_delete_model: get_symbol(&libs, b"dftd3_delete_model\0").map(|sym| *sym), dftd3_new_zero_damping: get_symbol(&libs, b"dftd3_new_zero_damping\0").map(|sym| *sym), dftd3_load_zero_damping: get_symbol(&libs, b"dftd3_load_zero_damping\0") diff --git a/dftd3/src/ffi_dynamic/dyload_struct.rs b/dftd3/src/ffi_dynamic/dyload_struct.rs index 179b8b5..4a12d18 100644 --- a/dftd3/src/ffi_dynamic/dyload_struct.rs +++ b/dftd3/src/ffi_dynamic/dyload_struct.rs @@ -43,6 +43,17 @@ pub struct DyLoadLib { pub dftd3_set_model_realspace_cutoff: Option< unsafe extern "C" fn(arg1: dftd3_error, arg2: dftd3_model, arg3: f64, arg4: f64, arg5: f64), >, + pub dftd3_set_model_realspace_cutoff_smooth: Option< + unsafe extern "C" fn( + arg1: dftd3_error, + arg2: dftd3_model, + arg3: f64, + arg4: f64, + arg5: f64, + arg6: f64, + arg7: f64, + ), + >, pub dftd3_delete_model: Option, pub dftd3_new_zero_damping: Option< unsafe extern "C" fn( diff --git a/dftd3/src/ffi_static.rs b/dftd3/src/ffi_static.rs index c5b66c5..acb0d2b 100644 --- a/dftd3/src/ffi_static.rs +++ b/dftd3/src/ffi_static.rs @@ -10,6 +10,7 @@ //! - `api-v0_5`: Extends api-v0_4, adds optimized power damping and pairwise //! dispersion //! - `api-v1_3`: Full API, adds CSO damping and GCP functions +//! - `api-v1_4`: Extends api-v1_3, adds smooth realspace cutoff //! //! Features are cumulative: enabling `api-v0_5` also enables all functions from //! earlier versions (api-v0_2, api-v0_3, api-v0_4). @@ -104,6 +105,17 @@ unsafe extern "C" { arg4: f64, arg5: f64, ); + #[cfg(feature = "api-v1_4")] + #[doc = " Set realspace cutoffs with smoothing widths (quantities in Bohr)"] + pub fn dftd3_set_model_realspace_cutoff_smooth( + arg1: dftd3_error, + arg2: dftd3_model, + arg3: f64, + arg4: f64, + arg5: f64, + arg6: f64, + arg7: f64, + ); #[cfg(feature = "api-v0_2")] #[doc = " Delete dispersion model"] pub fn dftd3_delete_model(arg1: *mut dftd3_model); diff --git a/dftd3/src/interface.rs b/dftd3/src/interface.rs index 66a5db2..e452229 100644 --- a/dftd3/src/interface.rs +++ b/dftd3/src/interface.rs @@ -1284,6 +1284,51 @@ impl DFTD3Model { } } + #[cfg(feature = "api-v1_4")] + /// Set realspace cutoffs with smoothing widths (in Bohr). + pub fn set_realspace_cutoff_smooth( + &self, + disp2: f64, + disp3: f64, + cn: f64, + width2: f64, + width3: f64, + ) { + self.set_realspace_cutoff_smooth_f(disp2, disp3, cn, width2, width3).unwrap() + } + + #[cfg(feature = "api-v1_4")] + /// Set realspace cutoffs with smoothing widths (in Bohr, failable). + /// + /// # See also + /// + /// [`DFTD3Model::set_realspace_cutoff_smooth`] + pub fn set_realspace_cutoff_smooth_f( + &self, + disp2: f64, + disp3: f64, + cn: f64, + width2: f64, + width3: f64, + ) -> Result<(), DFTD3Error> { + let mut error = DFTD3Error::new(); + unsafe { + ffi::dftd3_set_model_realspace_cutoff_smooth( + error.get_c_ptr(), + self.ptr, + disp2, + disp3, + cn, + width2, + width3, + ) + }; + match error.check() { + true => Err(error), + false => Ok(()), + } + } + /// Create new D3 dispersion model from structure (failable). /// /// # See also diff --git a/readme.md b/readme.md index c25e80b..d2e5f20 100644 --- a/readme.md +++ b/readme.md @@ -2,7 +2,7 @@ This project contains simple-dftd3 FFI bindings, wrapper and build-from-source. -Current binding of simple-dftd3: [![v1.3.1](https://img.shields.io/github/v/release/dftd3/simple-dftd3)](https://github.com/dftd3/simple-dftd3/releases/v1.3.1) +Current binding of simple-dftd3: [![v1.4.0](https://img.shields.io/github/v/release/dftd3/simple-dftd3?filter=v1.4.0)](https://github.com/dftd3/simple-dftd3/releases/v1.4.0) Source code of simple-dftd3 is available on [github](https://github.com/dftd3/simple-dftd3). @@ -16,7 +16,7 @@ This crate contains simple-dftd3 FFI bindings and wrapper. |--|--| | Crate | [![Crate](https://img.shields.io/crates/v/dftd3.svg)](https://crates.io/crates/dftd3) | | API Document | [![API Documentation](https://docs.rs/dftd3/badge.svg)](https://docs.rs/dftd3) | -| FFI Binding | [![v1.3.1](https://img.shields.io/github/v/release/dftd3/simple-dftd3?filter=v1.3.1)](https://github.com/dftd3/simple-dftd3/releases/v1.3.1) | +| FFI Binding | [![v1.4.0](https://img.shields.io/github/v/release/dftd3/simple-dftd3?filter=v1.4.0)](https://github.com/dftd3/simple-dftd3/releases/v1.4.0) | ### Dynamic loading guide @@ -104,8 +104,8 @@ Default cargo features of `dftd3` are: - **`dynamic_loading`**: This will enable dynamic loading of `s-dftd3` library, which can be more flexible for users who do not want to perform static linking. Please place `libs-dftd3.so` in `LD_LIBRARY_PATH` (for macos, place `libs-dftd3.dylib` in `DYLD_LIBRARY_PATH`), and function symbols will be loaded at runtime. Other cargo features of `dftd3` are: -- **`gcp`**: Support of geometric counterpoise correction. Please note that this is not available in latest stable release of simple-dftd3 (at the time writing this readme, is v1.3.1). Unless you build simple-dftd3 from git repository, you may not use this feature (especially installed simple-dftd3 from conda or similar). -- **`api-v1_3`**: Corresponding to the original simple-dftd3 [v1.3](https://github.com/dftd3/simple-dftd3/releases/tag/v1.3.1). This will additionally enable versions `cso` and cargo feature `gcp`. +- **`gcp`**: Support of geometric counterpoise correction. Please note that this is not available in latest stable release of simple-dftd3 (at the time writing this readme, is v1.4.0). Unless you build simple-dftd3 from git repository, you may not use this feature (especially installed simple-dftd3 from conda or similar). +- **`api-v1_3`**: Corresponding to the original simple-dftd3 [v1.3](https://github.com/dftd3/simple-dftd3/releases/tag/v1.4.0). This will additionally enable versions `cso` and cargo feature `gcp`. - **`json`**: This will enable JSON parsing for DFTD3 parameters. Note that toml parsing is builtin, and json is an optional feature. ## Installation guide and Crate `dftd3-src` @@ -166,7 +166,7 @@ If you have not compiled `s-dftd3` library, you may try out cargo feature `build CMake configurable variables (can be defined as environment variables): - `DFTD3_SRC`: git repository source directory or URL; - - `DFTD3_VER`: version of DFT-D3 (default v1.3.1); + - `DFTD3_VER`: version of DFT-D3 (default v1.4.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.