Skip to content

Commit 470b002

Browse files
authored
🏗️ handle types in R; expect only RealSexp in Rust (#32)
* add failing tests (R) * only handle Real number case in Rust * cast input types to double (in R) * depend on vctrs * document * reword docs * don't export internal Rust implementation
1 parent 831743e commit 470b002

File tree

11 files changed

+118
-152
lines changed

11 files changed

+118
-152
lines changed

DESCRIPTION

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,5 @@ Encoding: UTF-8
1919
Roxygen: list(markdown = TRUE)
2020
RoxygenNote: 7.3.2
2121
SystemRequirements: Cargo (Rust's package manager), rustc
22+
Imports:
23+
vctrs

R/000-wrappers.R

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,14 @@ NULL
3737
stop(class, " cannot be modified", call. = FALSE)
3838
}
3939

40-
#' Calculate the sum of a vector of integers using multiple threads.
40+
#' Calculate the sum of a vector of real numbers using multiple threads.
4141
#'
42-
#' @param x A vector of integers to sum over.
42+
#' @param x A vector of real numbers to sum over.
4343
#' @param n The number of threads used to compute this calculation (int).
4444
#'
4545
#' @return The sum of all elements of the input vector.
46-
#'
47-
#' @export
48-
`sum_with_threads` <- function(`x`, `n`) {
49-
.Call(savvy_sum_with_threads__impl, `x`, `n`)
46+
`sum_with_threads_real` <- function(`x`, `n`) {
47+
.Call(savvy_sum_with_threads_real__impl, `x`, `n`)
5048
}
5149

5250

R/add.R

Lines changed: 0 additions & 3 deletions
This file was deleted.

R/sum_with_threads.R

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#' Calculate the sum of a vector of numbers using multiple threads.
2+
#'
3+
#' @param x A vector of numbers to sum over.
4+
#' @param n The number of threads used to compute this calculation (int).
5+
#'
6+
#' @return The sum of all elements of the input vector.
7+
#' @export
8+
#'
9+
#' @examples
10+
#' sum_with_threads(c(1,2), 2)
11+
sum_with_threads <- function(x, n) {
12+
13+
tryCatch(
14+
x <- vctrs::vec_cast(x, double()),
15+
error = function(e) {
16+
stop("x must be coercible to a numeric vector")
17+
}
18+
)
19+
20+
tryCatch(
21+
n <- vctrs::vec_cast(n, integer()),
22+
error = function(e) {
23+
stop("n must be coercible to an integer")
24+
}
25+
)
26+
27+
sum_with_threads_real(x, n)
28+
}

blazr.Rproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
Version: 1.0
2+
ProjectId: b96439b3-4184-410b-ad08-b3d443f15d8e
23

34
RestoreWorkspace: No
45
SaveWorkspace: No

man/sum_with_threads.Rd

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

man/sum_with_threads_real.Rd

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

src/init.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,14 @@ SEXP handle_result(SEXP res_) {
3434
return (SEXP)res;
3535
}
3636

37-
SEXP savvy_sum_with_threads__impl(SEXP c_arg__x, SEXP c_arg__n) {
38-
SEXP res = savvy_sum_with_threads__ffi(c_arg__x, c_arg__n);
37+
SEXP savvy_sum_with_threads_real__impl(SEXP c_arg__x, SEXP c_arg__n) {
38+
SEXP res = savvy_sum_with_threads_real__ffi(c_arg__x, c_arg__n);
3939
return handle_result(res);
4040
}
4141

4242

4343
static const R_CallMethodDef CallEntries[] = {
44-
{"savvy_sum_with_threads__impl", (DL_FUNC) &savvy_sum_with_threads__impl, 2},
44+
{"savvy_sum_with_threads_real__impl", (DL_FUNC) &savvy_sum_with_threads_real__impl, 2},
4545
{NULL, NULL, 0}
4646
};
4747

src/rust/api.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
SEXP savvy_sum_with_threads__ffi(SEXP c_arg__x, SEXP c_arg__n);
1+
SEXP savvy_sum_with_threads_real__ffi(SEXP c_arg__x, SEXP c_arg__n);

src/rust/src/lib.rs

Lines changed: 35 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,22 @@
1-
use savvy::{savvy, NumericSexp, NumericTypedSexp, IntegerSexp, RealSexp, Sexp};
1+
use savvy::{savvy, RealSexp};
22
use std::thread;
33

4-
/// Calculate the sum of a vector of integers/doubles using multiple threads.
4+
/// Calculate the sum of a vector of real numbers using multiple threads.
55
///
6-
/// @param x A vector of integers/doubles to sum over.
6+
/// @param x A vector of real numbers to sum over.
77
/// @param n The number of threads used to compute this calculation (int).
88
///
99
/// @return The sum of all elements of the input vector.
10-
///
11-
/// @export
1210
#[savvy]
13-
fn sum_with_threads(x: NumericSexp, n: i32) -> savvy::Result<savvy::Sexp> {
14-
match x.into_typed() {
15-
NumericTypedSexp::Integer(i) => sum_with_threads_int(i, n),
16-
NumericTypedSexp::Real(r) => sum_with_threads_rel(r, n),
17-
}
18-
}
19-
20-
fn sum_with_threads_int(x: IntegerSexp, n: i32) -> savvy::Result<Sexp> {
21-
let x_rust = x.to_vec();
22-
let n_usize: usize = n as usize;
23-
24-
let out = sum_with_threads_int_impl(x_rust, n_usize);
25-
out.try_into()
26-
}
27-
28-
fn sum_with_threads_rel(x: RealSexp, n: i32) -> savvy::Result<Sexp> {
11+
fn sum_with_threads_real(x: RealSexp, n: i32) -> savvy::Result<savvy::Sexp> {
2912
let x_rust = x.to_vec();
30-
let n_usize: usize = n as usize;
13+
let n_size: usize = n as usize;
3114

32-
let out = sum_with_threads_rel_impl(x_rust, n_usize);
15+
let out = sum_with_threads_real_impl(x_rust, n_size);
3316
out.try_into()
3417
}
3518

36-
fn sum_with_threads_int_impl(x: Vec<i32>, n: usize) -> i32 {
37-
if x.is_empty() {
38-
eprintln!("Input vector is empty. Returning 0.");
39-
return 0;
40-
}
41-
42-
let n = n.min(x.len());
43-
let chunk_size = (x.len() + n - 1) / n;
44-
45-
let mut handles = Vec::new();
46-
for i in 0..n {
47-
let chunk = x[i * chunk_size..((i + 1) * chunk_size).min(x.len())].to_vec();
48-
handles.push(thread::spawn(move || chunk.iter().sum::<i32>()));
49-
}
50-
51-
let mut total_sum = 0;
52-
for handle in handles {
53-
total_sum += handle.join().expect("Thread panicked");
54-
}
55-
56-
total_sum
57-
}
58-
59-
fn sum_with_threads_rel_impl(x: Vec<f64>, n: usize) -> f64 {
19+
fn sum_with_threads_real_impl(x: Vec<f64>, n: usize) -> f64 {
6020
if x.is_empty() {
6121
eprintln!("Input vector is empty. Returning 0.");
6222
return 0.0;
@@ -81,125 +41,65 @@ fn sum_with_threads_rel_impl(x: Vec<f64>, n: usize) -> f64 {
8141

8242
#[cfg(test)]
8343
mod tests {
84-
use crate::{sum_with_threads_int_impl, sum_with_threads_rel_impl};
44+
use crate::sum_with_threads_real_impl;
8545

86-
/// Integers
46+
/// Real/doubles
8747
#[test]
88-
fn test_single_thread_int() {
89-
let numbers = vec![1, 2, 3, 4, 5];
48+
fn test_single_thread_rel() {
49+
let numbers = vec![1.0, 2.0, 3.0, 4.0, 5.0];
9050
let n = 1;
91-
assert_eq!(sum_with_threads_int_impl(numbers, n), 15);
51+
assert_eq!(sum_with_threads_real_impl(numbers, n), 15.0);
9252
}
9353

9454
#[test]
95-
fn test_multiple_threads_int() {
96-
let x = vec![1, 2, 3, 4];
55+
fn test_multiple_threads_rel() {
56+
let x = vec![1.0, 2.0, 3.0, 4.0];
9757
let num_threads = 2;
9858

99-
let result = sum_with_threads_int_impl(x, num_threads);
100-
assert_eq!(result, 10);
59+
let result = sum_with_threads_real_impl(x, num_threads);
60+
assert_eq!(result, 10.0);
10161
}
10262

10363
#[test]
104-
fn test_more_threads_than_elements_int() {
105-
let numbers = vec![1, 2, 3, 4, 5];
64+
fn test_more_threads_than_elements_rel() {
65+
let numbers = vec![1.0, 2.0, 3.0, 4.0, 5.0];
10666
let n = 10;
107-
assert_eq!(sum_with_threads_int_impl(numbers, n), 15);
67+
assert_eq!(sum_with_threads_real_impl(numbers, n), 15.0);
10868
}
10969

11070
#[test]
111-
fn test_empty_vector_int() {
112-
let numbers: Vec<i32> = vec![];
71+
fn test_empty_vector_rel() {
72+
let numbers: Vec<f64> = vec![];
11373
let n = 4;
114-
assert_eq!(sum_with_threads_int_impl(numbers, n), 0);
74+
assert_eq!(sum_with_threads_real_impl(numbers, n), 0.0);
11575
}
11676

11777
#[test]
118-
fn test_large_numbers_int() {
119-
let numbers = vec![1_000_000, 2_000_000, 3_000_000];
78+
fn test_large_numbers_rel() {
79+
let numbers = vec![1_000_000.0, 2_000_000.0, 3_000_000.0];
12080
let n = 3;
121-
assert_eq!(sum_with_threads_int_impl(numbers, n), 6_000_000);
81+
assert_eq!(sum_with_threads_real_impl(numbers, n), 6_000_000.0);
12282
}
12383

12484
#[test]
125-
fn test_negative_numbers_int() {
126-
let numbers = vec![-1, -2, -3, -4, -5];
85+
fn test_negative_numbers_rel() {
86+
let numbers = vec![-1.0, -2.0, -3.0, -4.0, -5.0];
12787
let n = 2;
128-
assert_eq!(sum_with_threads_int_impl(numbers, n), -15);
88+
assert_eq!(sum_with_threads_real_impl(numbers, n), -15.0);
12989
}
13090

13191
#[test]
132-
fn test_mixed_numbers_int() {
133-
let numbers = vec![-1, 2, -3, 4, -5, 6];
92+
fn test_mixed_numbers_rel() {
93+
let numbers = vec![-1.0, 2.0, -3.0, 4.0, -5.0, 6.0];
13494
let n = 3;
135-
assert_eq!(sum_with_threads_int_impl(numbers, n), 3);
95+
assert_eq!(sum_with_threads_real_impl(numbers, n), 3.0);
13696
}
13797

13898
#[test]
139-
fn test_large_vector_int() {
140-
let numbers: Vec<i32> = (1..=1_000).collect();
99+
fn test_large_vector_rel() {
100+
let numbers: Vec<f64> = (1..=1000).map(|x| x as f64).collect();
141101
let n = 4;
142-
let expected_sum: i32 = (1..=1_000).sum();
143-
assert_eq!(sum_with_threads_int_impl(numbers, n), expected_sum);
102+
let expected_sum: f64 = numbers.iter().sum();
103+
assert_eq!(sum_with_threads_real_impl(numbers, n), expected_sum);
144104
}
145105
}
146-
147-
/// Real/doubles
148-
#[test]
149-
fn test_single_thread_rel() {
150-
let numbers = vec![1.0, 2.0, 3.0, 4.0, 5.0];
151-
let n = 1;
152-
assert_eq!(sum_with_threads_rel_impl(numbers, n), 15.0);
153-
}
154-
155-
#[test]
156-
fn test_multiple_threads_rel() {
157-
let x = vec![1.0, 2.0, 3.0, 4.0];
158-
let num_threads = 2;
159-
160-
let result = sum_with_threads_rel_impl(x, num_threads);
161-
assert_eq!(result, 10.0);
162-
}
163-
164-
#[test]
165-
fn test_more_threads_than_elements_rel() {
166-
let numbers = vec![1.0, 2.0, 3.0, 4.0, 5.0];
167-
let n = 10;
168-
assert_eq!(sum_with_threads_rel_impl(numbers, n), 15.0);
169-
}
170-
171-
#[test]
172-
fn test_empty_vector_rel() {
173-
let numbers: Vec<f64> = vec![];
174-
let n = 4;
175-
assert_eq!(sum_with_threads_rel_impl(numbers, n), 0);
176-
}
177-
178-
#[test]
179-
fn test_large_numbers_rel() {
180-
let numbers = vec![1_000_000, 2_000_000, 3_000_000];
181-
let n = 3;
182-
assert_eq!(sum_with_threads_rel_impl(numbers, n), 6_000_000);
183-
}
184-
185-
#[test]
186-
fn test_negative_numbers_rel() {
187-
let numbers = vec![-1.0, -2.0, -3.0, -4.0, -5.0];
188-
let n = 2;
189-
assert_eq!(sum_with_threads_rel_impl(numbers, n), -15.0);
190-
}
191-
192-
#[test]
193-
fn test_mixed_numbers_rel() {
194-
let numbers = vec![-1.0, 2.0, -3.0, 4.0, -5.0, 6.0];
195-
let n = 3;
196-
assert_eq!(sum_with_threads_rel_impl(numbers, n), 3.0);
197-
}
198-
199-
#[test]
200-
fn test_large_vector_rel() {
201-
let numbers: Vec<f64> = (1..=1_000).collect();
202-
let n = 4;
203-
let expected_sum: i32 = (1..=1_000).sum();
204-
assert_eq!(sum_with_threads_rel_impl(numbers, n), expected_sum);
205-
}

0 commit comments

Comments
 (0)