Skip to content

Commit

Permalink
feat: add demes::ffi module (#339)
Browse files Browse the repository at this point in the history
* add valgrind CI
  • Loading branch information
molpopgen authored Jan 18, 2024
1 parent 872e1c9 commit 9717072
Show file tree
Hide file tree
Showing 12 changed files with 1,656 additions and 3 deletions.
26 changes: 23 additions & 3 deletions .github/workflows/test_ffi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,32 @@ jobs:
with:
crate: cbindgen
version: "=0.24.3"
- name: Run cmake
- name: Run cmake on demes-forward example
run: |
cmake -Sdemes-forward-capi/c_example -Bbuild
- name: Build
- name: Build demes-forward example
run: |
cmake --build build
- name: Run C example
- name: Run demes-forward C example
run: |
./build/example demes-forward-capi/example_yaml/*.yaml
- name: cleanup
run: |
rm -rf build
- name: Run cmake on demes example
run: |
cmake -Sdemes/c_example -Bbuild
- name: Build demes example
run: |
cmake --build build
- name: Run demes C example
run: |
./build/example demes/demes-spec/examples/browning_america.yaml
- run: sudo apt-get update -y
if: matrix.os == 'ubuntu-latest'
- run: sudo apt-get install -y valgrind
if: matrix.os == 'ubuntu-latest'
- name: Run demes C example through valgrind
if: matrix.os == 'ubuntu-latest'
run: |
valgrind ./build/example demes/demes-spec/examples/browning_america.yaml
40 changes: 40 additions & 0 deletions .github/workflows/valgrind.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
on:
push:
branches: [main, dev]
pull_request:

name: valgrind

jobs:
cargo-valgrind:
name: Run valgrind
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
rust:
- stable
steps:
- name: Cancel Previous Runs
uses: styfle/[email protected]
with:
access_token: ${{ secrets.GITHUB_TOKEN }}

- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
submodules: recursive
- uses: dtolnay/rust-toolchain@v1
with:
toolchain: ${{ matrix.rust }}
override: true
- uses: Swatinem/rust-cache@v2
- run: sudo apt-get update -y
if: matrix.os == 'ubuntu-latest'
- run: sudo apt-get install -y valgrind
if: matrix.os == 'ubuntu-latest'
- run: cargo install cargo-valgrind
- name: run cargo valgrind on demes::ffi
run: |
cargo valgrind test --manifest-path demes/Cargo.toml --all-features ffi
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
[submodule "demes-forward-capi/corrosion"]
path = demes-forward-capi/c_example/corrosion
url = https://github.com/corrosion-rs/corrosion.git
[submodule "demes/c_example/corrosion"]
path = demes/c_example/corrosion
url = https://github.com/corrosion-rs/corrosion.git
4 changes: 4 additions & 0 deletions demes/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@ homepage = "https://github.com/molpopgen/demes-rs"
repository = "https://github.com/molpopgen/demes-rs"
rust-version = "1.60.0"

[lib]
crate-type = ["lib", "staticlib"]

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "doc_cfg"]

[features]
json = ["serde_json"]
ffi = []

[dependencies]
thiserror = "~1"
Expand Down
26 changes: 26 additions & 0 deletions demes/c_example/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Usage from the workspace root:
# cmake -S demes/c_example -B build
# cmake --build build
# cmake --build build --target clean
cmake_minimum_required(VERSION 3.15)
project(c_example LANGUAGES C)

MESSAGE(${CMAKE_SOURCE_DIR} ${PROJECT_SOURCE_DIR})

add_compile_options(-W -Wall -Werror -Wconversion)
add_subdirectory(corrosion)

# Specify to only build the demes crate and to use the ffi cargo feature of the crate
corrosion_import_crate(MANIFEST_PATH ../Cargo.toml CRATES demes FEATURES ffi)
# The header, demes.h, will be built in the root of the build dir
get_filename_component(DEMES_HEADER_LOCATION ${CMAKE_BINARY_DIR} DIRECTORY CACHE)
add_custom_target(header DEPENDS ${DEMES_HEADER_LOCATION}/demes.h)
add_executable(example example.c)
add_dependencies(example cargo-build_demes header)
target_include_directories(example BEFORE PUBLIC ${DEMES_HEADER_LOCATION})
target_link_directories(example PUBLIC ${CMAKE_BINARY_DIR})
# We link the static C archive of demes and the C math lib to the binary
target_link_libraries(example PUBLIC libdemes.a m)

# Use cbindgen to build our header
add_custom_command(OUTPUT ${DEMES_HEADER_LOCATION}/demes.h COMMAND cbindgen -l C -o ${DEMES_HEADER_LOCATION}/demes.h ${CMAKE_SOURCE_DIR}/..)
1 change: 1 addition & 0 deletions demes/c_example/corrosion
Submodule corrosion added at 2d71b9
129 changes: 129 additions & 0 deletions demes/c_example/example.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#define DEMES_FFI = 1

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <demes.h>

void
handle_error(int rv, FFIError *error, Graph *graph)
{
char *error_msg = NULL;
if (rv != 0)
{
assert(demes_error_has_error(error));
assert((error_msg = demes_error_message(error)) != NULL);
fprintf(stderr, "%s\n", error_msg);
demes_c_char_deallocate(error_msg);
demes_error_deallocate(error);
if (graph != NULL)
{
demes_graph_deallocate(graph);
}
exit(1);
}
}

void
iterate_epochs(const Deme *deme)
{
Epoch const *epoch;
size_t num_epochs = demes_deme_num_epochs(deme);
size_t i;
double midpoint, size_at_midpoint, start_time, end_time;
for (i = 0; i < num_epochs; ++i)
{
assert((epoch = demes_deme_epoch(deme, i)) != NULL);
start_time = demes_epoch_start_time(epoch);
end_time = demes_epoch_end_time(epoch);
midpoint = end_time + (start_time - end_time)/2.0;
assert(demes_epoch_size_at(epoch, midpoint, &size_at_midpoint) == 0);
fprintf(stdout, "\t\tstart time: %lf\n", start_time);
fprintf(stdout, "\t\tend time: %lf\n", end_time);
fprintf(stdout, "\t\tstart size: %lf\n", demes_epoch_start_size(epoch));
fprintf(stdout, "\t\tmidpoint size: %lf\n", size_at_midpoint);
fprintf(stdout, "\t\tend size: %lf\n", demes_epoch_end_size(epoch));
}
}

void
iterate_ancestors_proportions(const Graph *graph, const Deme *deme)
{
size_t const *ancestor_indexes;
Deme const *ancestor;
double const *ancestor_proportions;
double proportion;
char *ancestor_name;
size_t i, num_ancestors;

num_ancestors = demes_deme_num_ancestors(deme);
ancestor_indexes = demes_deme_ancestor_indexes(deme);
ancestor_proportions = demes_deme_proportions(deme);

for (i = 0; i < num_ancestors; ++i)
{
assert((ancestor = demes_graph_deme(graph, ancestor_indexes[i])) != NULL);
ancestor_name = demes_deme_name(deme);
proportion = ancestor_proportions[i];
fprintf(stdout, "\t \t%s %lf\n", ancestor_name, proportion);
demes_c_char_deallocate(ancestor_name);
}
}

void
iterate_demes(Graph *graph, FFIError *error)
{
size_t i, num_epochs;
Deme const *deme;
size_t num_demes;
char *deme_name = NULL;

assert(!demes_error_has_error(error));

num_demes = demes_graph_num_demes(graph);
for (i = 0; i < num_demes; ++i)
{
assert((deme = demes_graph_deme(graph, i)) != NULL);
num_epochs = demes_deme_num_epochs(deme);
assert((deme_name = demes_deme_name(deme)) != NULL);
fprintf(stdout, "deme %ld:\n", i);
fprintf(stdout, "\tname: %s\n", deme_name);
fprintf(stdout, "\tno. epochs: %ld\n", num_epochs);
fprintf(stdout, "\tstart time: %lf\n", demes_deme_start_time(deme));
fprintf(stdout, "\tend time: %lf\n", demes_deme_end_time(deme));
fprintf(stdout, "\tstart size: %lf\n", demes_deme_start_size(deme));
fprintf(stdout, "\tend size: %lf\n", demes_deme_end_size(deme));
demes_c_char_deallocate(deme_name);
fprintf(stdout, "\tancestor details:\n");
iterate_ancestors_proportions(graph, deme);
fprintf(stdout, "\tepoch details:\n");
iterate_epochs(deme);
}
}

int
main(int argc, char **argv)
{
FFIError *error = NULL;
Graph *graph = NULL;
int rv;

if (argc != 2)
{
fprintf(stderr, "usage: example filename\n");
exit(1);
}

error = demes_error_allocate();

rv = demes_graph_load_from_file(argv[1], error, &graph);
handle_error(rv, error, graph);

iterate_demes(graph, error);

demes_error_deallocate(error);
if (graph != NULL)
{
demes_graph_deallocate(graph);
}
}
3 changes: 3 additions & 0 deletions demes/cbindgen.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[defines]
"feature = ffi" = "DEMES_FFI"
"feature = json" = "DEMES_JSON"
1 change: 1 addition & 0 deletions demes/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use thiserror::Error;
/// ";
/// assert!(matches!(demes::loads(yaml), Err(demes::DemesError::EpochError(_))));
/// ```
// cbindgen:no-export
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum DemesError {
Expand Down
Loading

0 comments on commit 9717072

Please sign in to comment.