Skip to content

Commit e5ae1c1

Browse files
committed
c++
1 parent 368b03e commit e5ae1c1

9 files changed

Lines changed: 183 additions & 0 deletions

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export(ks_meso_station_activity)
3333
export(ks_meso_stations)
3434
export(ks_meso_vars)
3535
export(pCO2_to_xCO2)
36+
export(rcpp_calc_exceedance_prob)
3637
export(read_ghcnh)
3738
export(read_ks_meso)
3839
export(read_shp)

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@
2020
* `has_units()` has been removed. The `units` package is no longer a dependency (#TBD).
2121
* `ks_meso_fw13()` can now retrieve Kansas Mesonet fire weather data in FW13 format for one station and date range (#TBD).
2222
* `ks_meso_most_recent()` can now retrieve the most recently ingested Kansas Mesonet timestamp for each station at a requested interval (#TBD).
23+
* `rcpp_calc_exceedance_prob()` now provides a C++ implementation of flow exceedance probability calculations (#TBD).
2324
* `read_ghcnh()` is now exported, validates file inputs, can suppress progress messages with `quiet = TRUE`, and handles files that lack `Station_name` or `Station_ID` columns (#TBD).

R/RcppExports.R

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Generated by using Rcpp::compileAttributes() -> do not edit by hand
22
# Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393
33

4+
cpp_calc_exceedance_prob <- function(flow, rm_zero = FALSE) {
5+
.Call(`_preMetabolizer_cpp_calc_exceedance_prob`, flow, rm_zero)
6+
}
7+
48
flag_z <- function(x, width = 5L, threshold = 3.0, return_z = FALSE) {
59
.Call(`_preMetabolizer_flag_z`, x, width, threshold, return_z)
610
}

R/calc_exceedance_prob.R

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,49 @@ calc_exceedance_prob <- function(flow, rm.zero = FALSE) {
7878

7979
return(full_exceedance_prob)
8080
}
81+
82+
#' Calculate Flow Exceedance Probabilities with C++
83+
#'
84+
#' Calculates exceedance probabilities using the same Weibull plotting position
85+
#' method and return shape as [calc_exceedance_prob()], but delegates ranking to
86+
#' a C++ implementation.
87+
#'
88+
#' @inheritParams calc_exceedance_prob
89+
#'
90+
#' @return A numeric vector of exceedance probabilities. If `rm.zero = TRUE`,
91+
#' the returned vector has the same length as the input with `NA` at positions
92+
#' of zero or negative values.
93+
#'
94+
#' @seealso [calc_exceedance_prob()]
95+
#'
96+
#' @examples
97+
#' rcpp_calc_exceedance_prob(c(10, 5, 0, 15, 8, NA, 0, 20))
98+
#'
99+
#' @export
100+
rcpp_calc_exceedance_prob <- function(flow, rm.zero = FALSE) {
101+
if (!is.numeric(flow)) {
102+
stop("'flow' must be a numeric vector.")
103+
}
104+
105+
if (any(is.infinite(flow))) {
106+
stop("'flow' contains infinite values. These are not allowed.")
107+
}
108+
109+
if (!is.logical(rm.zero)) {
110+
stop("'rm.zero' must be a logical value (TRUE or FALSE).")
111+
}
112+
113+
if (length(flow) == 0) {
114+
warning("'flow' is an empty vector. Returning an empty numeric vector.")
115+
return(numeric(0))
116+
}
117+
118+
if (rm.zero && length(flow[flow > 0 & !is.na(flow)]) == 0) {
119+
warning(
120+
"All non-NA values in 'flow' were zero. Returning a vector of NAs with the original length."
121+
)
122+
return(rep(NA, length(flow)))
123+
}
124+
125+
cpp_calc_exceedance_prob(flow, rm.zero)
126+
}

_pkgdown.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ reference:
5757
- calc_cv
5858
- calc_mode
5959
- calc_exceedance_prob
60+
- rcpp_calc_exceedance_prob
6061
- flag_z
6162

6263
- title: Unit conversion

man/rcpp_calc_exceedance_prob.Rd

Lines changed: 31 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/RcppExports.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,18 @@ Rcpp::Rostream<true>& Rcpp::Rcout = Rcpp::Rcpp_cout_get();
1010
Rcpp::Rostream<false>& Rcpp::Rcerr = Rcpp::Rcpp_cerr_get();
1111
#endif
1212

13+
// cpp_calc_exceedance_prob
14+
NumericVector cpp_calc_exceedance_prob(NumericVector flow, bool rm_zero);
15+
RcppExport SEXP _preMetabolizer_cpp_calc_exceedance_prob(SEXP flowSEXP, SEXP rm_zeroSEXP) {
16+
BEGIN_RCPP
17+
Rcpp::RObject rcpp_result_gen;
18+
Rcpp::RNGScope rcpp_rngScope_gen;
19+
Rcpp::traits::input_parameter< NumericVector >::type flow(flowSEXP);
20+
Rcpp::traits::input_parameter< bool >::type rm_zero(rm_zeroSEXP);
21+
rcpp_result_gen = Rcpp::wrap(cpp_calc_exceedance_prob(flow, rm_zero));
22+
return rcpp_result_gen;
23+
END_RCPP
24+
}
1325
// flag_z
1426
SEXP flag_z(NumericVector x, int width, double threshold, bool return_z);
1527
RcppExport SEXP _preMetabolizer_flag_z(SEXP xSEXP, SEXP widthSEXP, SEXP thresholdSEXP, SEXP return_zSEXP) {
@@ -26,6 +38,7 @@ END_RCPP
2638
}
2739

2840
static const R_CallMethodDef CallEntries[] = {
41+
{"_preMetabolizer_cpp_calc_exceedance_prob", (DL_FUNC) &_preMetabolizer_cpp_calc_exceedance_prob, 2},
2942
{"_preMetabolizer_flag_z", (DL_FUNC) &_preMetabolizer_flag_z, 4},
3043
{NULL, NULL, 0}
3144
};

src/calc_exceedance_prob.cpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#include <Rcpp.h>
2+
#include <algorithm>
3+
#include <vector>
4+
using namespace Rcpp;
5+
6+
struct FlowValue {
7+
double value;
8+
int output_index;
9+
};
10+
11+
// [[Rcpp::export]]
12+
NumericVector cpp_calc_exceedance_prob(NumericVector flow, bool rm_zero = false) {
13+
int n_flow = flow.size();
14+
NumericVector result(n_flow, NA_REAL);
15+
std::vector<FlowValue> values;
16+
values.reserve(n_flow);
17+
18+
for (int i = 0; i < n_flow; ++i) {
19+
double value = flow[i];
20+
if (NumericVector::is_na(value)) {
21+
continue;
22+
}
23+
if (rm_zero && value <= 0) {
24+
continue;
25+
}
26+
values.push_back({value, i});
27+
}
28+
29+
int n = values.size();
30+
if (n == 0) {
31+
return result;
32+
}
33+
34+
std::sort(
35+
values.begin(),
36+
values.end(),
37+
[](const FlowValue& a, const FlowValue& b) {
38+
return a.value > b.value;
39+
}
40+
);
41+
42+
int i = 0;
43+
while (i < n) {
44+
int j = i + 1;
45+
while (j < n && values[j].value == values[i].value) {
46+
++j;
47+
}
48+
49+
double rank = (static_cast<double>(i + 1) + static_cast<double>(j)) / 2.0;
50+
double exceedance_probability = rank / (static_cast<double>(n) + 1.0);
51+
52+
for (int k = i; k < j; ++k) {
53+
result[values[k].output_index] = exceedance_probability;
54+
}
55+
56+
i = j;
57+
}
58+
59+
return result;
60+
}

tests/testthat/test-calc_exceedance_prob.R

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,29 @@ test_that("calc_exceedance_prob errors on non-numeric input", {
2727
test_that("calc_exceedance_prob errors on Inf values", {
2828
expect_snapshot(error = TRUE, calc_exceedance_prob(c(1, Inf, 3)))
2929
})
30+
31+
test_that("rcpp_calc_exceedance_prob matches R implementation", {
32+
flow <- c(10, 5, 0, 15, 8, NA, 0, 20, 10)
33+
34+
expect_equal(
35+
rcpp_calc_exceedance_prob(flow),
36+
calc_exceedance_prob(flow)
37+
)
38+
expect_equal(
39+
rcpp_calc_exceedance_prob(flow, rm.zero = TRUE),
40+
calc_exceedance_prob(flow, rm.zero = TRUE)
41+
)
42+
})
43+
44+
test_that("rcpp_calc_exceedance_prob handles ties with average ranks", {
45+
result <- rcpp_calc_exceedance_prob(c(10, 20, 20, 30))
46+
47+
expect_equal(result, c(0.8, 0.5, 0.5, 0.2))
48+
})
49+
50+
test_that("rcpp_calc_exceedance_prob preserves NA positions", {
51+
result <- rcpp_calc_exceedance_prob(c(10, NA, 30))
52+
53+
expect_equal(result[2], NA_real_)
54+
expect_equal(result[1], 2 / 3)
55+
})

0 commit comments

Comments
 (0)