Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/workflows/cpp-examples.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: C++ Examples

# Builds the C++ example consumers and runs their tests on Linux, Windows and
# macOS. This guards the public headers' extern "C" guards: the examples
# include dedx.h / dedx_tools.h / dedx_wrappers.h from C++ translation units
# and link the C library, so a regression in the guards breaks this workflow.
Comment thread
grzanka marked this conversation as resolved.

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

# Build/test only: the token needs no more than read access to the checkout.
permissions:
contents: read

jobs:
cpp-examples:
name: ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Tool versions
run: |
cmake --version
cmake -E echo "Generator/compiler resolved at configure time"

- name: Configure
run: >
cmake -S . -B build
-DCMAKE_BUILD_TYPE=Release
-DDEDX_BUILD_EXAMPLES=ON
-DDEDX_BUILD_CXX_EXAMPLES=ON
-DDEDX_BUILD_TESTS=OFF

- name: Build
run: cmake --build build --config Release --parallel

- name: Run C++ example tests
run: ctest --test-dir build -C Release -R dedx_cpp --output-on-failure
Comment thread
grzanka marked this conversation as resolved.
Fixed
6 changes: 6 additions & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,9 @@ add_test(NAME getdedx_lookup COMMAND getdedx 2 1 276 100)
add_test(NAME getdedx_list_programs COMMAND getdedx -1 1 276 100)

install(TARGETS getdedx DESTINATION "${CMAKE_INSTALL_BINDIR}")

# C++ example consumers (demonstrate the extern "C" public headers from C++).
option(DEDX_BUILD_CXX_EXAMPLES "Build C++ example consumers (requires a C++ compiler)" ON)
if(DEDX_BUILD_CXX_EXAMPLES)
add_subdirectory(cpp)
endif()
106 changes: 106 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# libdedx examples

Small, self-contained programs that show how to call libdedx. The C programs
live in this directory; the C++ programs (which consume the public headers
through their `extern "C"` guards) live in [`cpp/`](cpp/).

Every code block below is a **single command** — use the copy button and run
them one at a time, in order. All paths are relative to the repository root.

## Prerequisites

- CMake 3.21 or newer
- A C11 compiler (and a C++17 compiler for the `cpp/` examples)

## 1. Configure

```bash
cmake --preset debug
```

## 2. Build everything (library + examples)

```bash
cmake --build --preset debug
```

## 3. Run the C examples

```bash
./build/examples/dedx_example
```

```bash
./build/examples/dedx_list
```

```bash
./build/examples/dedx_use_wrappers
```

```bash
./build/examples/dedx_bethe
```

```bash
./build/examples/dedx_csda
```

```bash
./build/examples/dedx_custom_compound
```

## 4. Run the C++ examples

```bash
./build/examples/cpp/dedx_cpp_core
```

```bash
./build/examples/cpp/dedx_cpp_wrappers
```

## 5. Use the `getdedx` command-line tool

Look up PSTAR stopping power for a proton in water at 100 MeV/nucl:

```bash
./build/examples/getdedx 2 1 276 100
```

## Run examples as a test suite

Run every example test:

```bash
ctest --preset debug --output-on-failure
```

Run only the C++ example tests:

```bash
ctest --preset debug --output-on-failure -R dedx_cpp
```

## Building without the C++ examples

If you have no C++ compiler, turn the C++ examples off at configure time:

```bash
cmake --preset debug -DDEDX_BUILD_CXX_EXAMPLES=OFF
```

## Windows note

The Visual Studio generator is multi-config, so the binaries land in a
per-config subfolder. Build with an explicit config:

```bash
cmake --build build --config Release
```

Then run from the matching folder, e.g.:

```bash
./build/examples/cpp/Release/dedx_cpp_core.exe
```
32 changes: 32 additions & 0 deletions examples/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# C++ example consumers.
#
# These demonstrate that the public libdedx headers can be included and linked
# from C++ thanks to their extern "C" guards. The library remains C-only, so
# C++ is enabled lazily here and the whole subdirectory is skipped when no C++
# compiler is available.
include(CheckLanguage)
check_language(CXX)
if(NOT CMAKE_CXX_COMPILER)
message(STATUS "No C++ compiler found; skipping C++ example consumers")
return()
endif()
enable_language(CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

set(cpp_examples dedx_cpp_core dedx_cpp_wrappers)

foreach(example ${cpp_examples})
add_executable(${example} ${example}.cpp)
# Linking the `dedx` target transitively pulls in its PUBLIC dependencies
# (e.g. the math library on Unix), so no per-platform handling is needed.
target_link_libraries(${example} PRIVATE dedx)
if(MSVC)
target_compile_options(${example} PRIVATE /W4 /permissive-)
else()
target_compile_options(${example} PRIVATE -Wall -Wextra)
endif()
add_test(NAME ${example} COMMAND ${example})
endforeach()
51 changes: 51 additions & 0 deletions examples/cpp/dedx_cpp_core.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Core (stateful) libdedx API consumed from C++.
//
// Builds a proton-in-water stopping-power / CSDA-range table using the RAII
// helpers, std::vector and exception-based error handling. Demonstrates that
// dedx.h and dedx_tools.h link cleanly into a C++ program now that the public
// headers carry extern "C" guards.
#include <cstdlib>
#include <dedx.h>
#include <dedx_tools.h>
#include <iomanip>
#include <iostream>
#include <vector>

#include "dedx_raii.hpp"

int main() {
try {
auto ws = dedx::make_workspace(1);

auto cfg = dedx::make_config();
cfg->program = DEDX_PSTAR;
cfg->ion = DEDX_PROTON;
cfg->ion_a = 1; // nucleon number, required by dedx_get_csda
cfg->target = DEDX_WATER;

int err = DEDX_OK;
dedx_load_config(ws.get(), cfg.get(), &err);
dedx::check(err, "dedx_load_config");

const std::vector<float> energies = {1.0f, 5.0f, 10.0f, 50.0f, 100.0f, 250.0f};

std::cout << "Proton stopping power & CSDA range in liquid water (PSTAR)\n";
std::cout << " E [MeV/nucl] dE/dx [MeV cm^2/g] CSDA [g/cm^2]\n";
std::cout << std::fixed << std::setprecision(4);

for (const float e : energies) {
const float stp = dedx_get_stp(ws.get(), cfg.get(), e, &err);
dedx::check(err, "dedx_get_stp");

const double csda = dedx_get_csda(ws.get(), cfg.get(), e, &err);
dedx::check(err, "dedx_get_csda");

std::cout << std::setw(14) << e << std::setw(21) << stp << std::setw(16) << csda << '\n';
}

return EXIT_SUCCESS;
} catch (const dedx::error &ex) {
std::cerr << "libdedx error: " << ex.what() << '\n';
return EXIT_FAILURE;
}
}
74 changes: 74 additions & 0 deletions examples/cpp/dedx_cpp_wrappers.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Stateless wrapper + tools libdedx APIs consumed from C++.
//
// Reads the program's default tabulated grid, evaluates a custom energy grid
// in one batched call, and converts mass stopping power to linear stopping
// power — all backed by std::vector. Exercises dedx_wrappers.h and
// dedx_tools.h from a C++ translation unit.
#include <cstddef>
#include <cstdlib>
#include <dedx.h>
#include <dedx_tools.h>
#include <dedx_wrappers.h>
#include <iomanip>
#include <iostream>
#include <vector>

#include "dedx_raii.hpp"

int main() {
try {
constexpr int program = DEDX_PSTAR;
constexpr int ion = DEDX_PROTON;
constexpr int target = DEDX_WATER_LIQUID;

// 1) Default tabulated grid shipped with the program.
const int n = dedx_get_stp_table_size(program, ion, target);
if (n <= 0) {
std::cerr << "no tabulated data for this program/ion/target\n";
return EXIT_FAILURE;
}

std::vector<float> grid_e(static_cast<std::size_t>(n));
std::vector<float> grid_stp(static_cast<std::size_t>(n));
// Returns 0 on success / negative on error; the point count is `n`.
if (dedx_fill_default_energy_stp_table(program, ion, target, grid_e.data(), grid_stp.data()) < 0) {
std::cerr << "dedx_fill_default_energy_stp_table failed\n";
return EXIT_FAILURE;
}
std::cout << "Default PSTAR grid: " << n << " points, " << grid_e.front() << " - " << grid_e.back()
<< " MeV/nucl\n";

// 2) Evaluate an arbitrary energy grid in a single batched call.
const std::vector<float> energies = {2.0f, 20.0f, 200.0f};
std::vector<float> stps(energies.size());
dedx::check(
dedx_get_stp_table(program, ion, target, static_cast<int>(energies.size()), energies.data(), stps.data()),
"dedx_get_stp_table");

// 3) Convert mass stopping power [MeV cm^2/g] to linear units. For
// liquid water (rho = 1 g/cm^3) the MeV/cm value coincides
// numerically with the mass value, so keV/um is shown too to make
// the unit change visible.
const auto convert = [&](int to_unit) {
std::vector<float> out(stps.size());
dedx::check(
convert_units(DEDX_MEVCM2G, to_unit, target, static_cast<int>(stps.size()), stps.data(), out.data()),
"convert_units");
return out;
};
const std::vector<float> stps_mevcm = convert(DEDX_MEVCM);
const std::vector<float> stps_kevum = convert(DEDX_KEVUM);

std::cout << " E [MeV/nucl] dE/dx [MeV cm^2/g] dE/dx [MeV/cm] dE/dx [keV/um]\n";
std::cout << std::fixed << std::setprecision(4);
for (std::size_t i = 0; i < energies.size(); ++i) {
std::cout << std::setw(14) << energies[i] << std::setw(21) << stps[i] << std::setw(16) << stps_mevcm[i]
<< std::setw(16) << stps_kevum[i] << '\n';
}

return EXIT_SUCCESS;
} catch (const dedx::error &ex) {
std::cerr << "libdedx error: " << ex.what() << '\n';
return EXIT_FAILURE;
}
}
Loading
Loading