Skip to content

Commit 610f1a3

Browse files
committed
feature heap
1 parent 629fefc commit 610f1a3

File tree

5 files changed

+222
-3
lines changed

5 files changed

+222
-3
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,8 @@ num-complex = "0.4.6"
1919
[dev-dependencies]
2020
rand = "0.8"
2121

22+
[features]
23+
default = []
24+
heap = []
25+
2226
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Rust scientific computing for single and multi-variable calculus
4343
- [13. Line and Flux integrals](#13-line-and-flux-integrals)
4444
- [14. Curl and Divergence](#14-curl-and-divergence)
4545
- [15. Error Handling](#15-error-handling)
46+
- [16. Experimental](#16-experimental)
4647

4748
## 1. Single total derivatives
4849
```rust
@@ -308,6 +309,40 @@ Wherever possible, "safe" versions of functions are provided that fill in the de
308309
However, that is not always possible either because no default argument can be assumed, or for functions that deliberately give users the freedom to tweak the parameters.
309310
In such cases, a `Result<T, ErrorCode>` object is returned instead, where all possible `ErrorCode`s can be viewed at [error_codes](./src/utils/error_codes.rs).
310311

312+
## 16. Experimental
313+
Enable feature "heap" to access `std::Vec` based methods in certain modules. Currently this is only supported for the _Jacobian_ module via `get_on_heap()` and `get_on_heap_custom()` methods. The output is a dynamically allocated `Vec<Vec<T>>`. This is to support large datasets that might otherwise get a stack overflow with static arrays. Future plans might include adding such support for the approximation module.
314+
```rust
315+
//function is x*y*z
316+
let func1 = | args: &[f64; 3] | -> f64
317+
{
318+
return args[0]*args[1]*args[2];
319+
};
320+
321+
//function is x^2 + y^2
322+
let func2 = | args: &[f64; 3] | -> f64
323+
{
324+
return args[0].powf(2.0) + args[1].powf(2.0);
325+
};
326+
327+
let function_matrix: Vec<Box<dyn Fn(&[f64; 3]) -> f64>> = std::vec![Box::new(func1), Box::new(func2)];
328+
329+
let points = [1.0, 2.0, 3.0]; //the point around which we want the jacobian matrix
330+
331+
let result: Vec<Vec<f64>> = jacobian::get_on_heap(&function_matrix, &points).unwrap();
332+
333+
assert!(result.len() == function_matrix.len()); //number of rows
334+
assert!(result[0].len() == points.len()); //number of columns
335+
336+
let expected_result = [[6.0, 3.0, 2.0], [2.0, 4.0, 0.0]];
337+
338+
for i in 0..function_matrix.len()
339+
{
340+
for j in 0..points.len()
341+
{
342+
assert!(f64::abs(result[i][j] - expected_result[i][j]) < 0.000001);
343+
}
344+
}
345+
```
311346

312347
## Contributions Guide
313348
See [CONTRIBUTIONS.md](./CONTRIBUTIONS.md)

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33

44
#![no_std]
55

6+
#[cfg(feature = "heap")]
7+
extern crate std;
8+
69
pub mod utils;
710
pub mod numerical_integration;
811
pub mod numerical_derivative;

src/numerical_derivative/jacobian.rs

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ use crate::numerical_derivative::mode as mode;
33
use crate::utils::error_codes::ErrorCode;
44
use num_complex::ComplexFloat;
55

6+
#[cfg(feature = "heap")]
7+
use std::{boxed::Box, vec::Vec};
8+
9+
610
/// Returns the jacobian matrix for a given vector of functions
711
/// Can handle multivariable functions of any order or complexity
812
///
@@ -12,8 +16,6 @@ use num_complex::ComplexFloat;
1216
///
1317
/// where 'N' is the total number of variables, and 'M' is the total number of functions
1418
///
15-
/// consult the helper utility [`multicalc::utils::helper::transpose`] to transpose the matrix shape if required
16-
///
1719
/// NOTE: Returns a Result<T, ErrorCode>
1820
/// Possible ErrorCode are:
1921
/// VectorOfFunctionsCannotBeEmpty -> if function_matrix argument is an empty array
@@ -88,5 +90,101 @@ pub fn get_custom<T: ComplexFloat, const NUM_FUNCS: usize, const NUM_VARS: usize
8890
}
8991
}
9092

93+
return Ok(result);
94+
}
95+
96+
97+
/// This is a variant of [`get()`] that uses heap allocations.
98+
/// Useful when handling large datasets to avoid a stack overflow
99+
///
100+
/// Can handle multivariable functions of any order or complexity
101+
///
102+
/// The 2-D matrix returned has the structure [[df1/dvar1, df1/dvar2, ... , df1/dvarN],
103+
/// [ ... ],
104+
/// [dfM/dvar1, dfM/dvar2, ... , dfM/dvarN]]
105+
///
106+
/// where 'N' is the total number of variables, and 'M' is the total number of functions
107+
///
108+
/// NOTE: Returns a Result<T, ErrorCode>
109+
/// Possible ErrorCode are:
110+
/// VectorOfFunctionsCannotBeEmpty -> if function_matrix argument is an empty array
111+
///
112+
/// assume our function vector is (x*y*z , x^2 + y^2). First define both the functions
113+
/// ```
114+
/// use multicalc::numerical_derivative::jacobian;
115+
/// let my_func1 = | args: &[f64; 3] | -> f64
116+
/// {
117+
/// return args[0]*args[1]*args[2]; //x*y*z
118+
/// };
119+
///
120+
/// let my_func2 = | args: &[f64; 3] | -> f64
121+
/// {
122+
/// return args[0].powf(2.0) + args[1].powf(2.0); //x^2 + y^2
123+
/// };
124+
///
125+
/// //define the function vector
126+
/// let function_matrix: Vec<Box<dyn Fn(&[f64; 3]) -> f64>> = vec![Box::new(my_func1), Box::new(my_func2)];
127+
/// let points = [1.0, 2.0, 3.0]; //the point around which we want the jacobian matrix
128+
///
129+
/// let result = jacobian::get_on_heap(&function_matrix, &points).unwrap();
130+
/// ```
131+
///
132+
/// the above example can also be extended to complex numbers:
133+
///```
134+
/// use multicalc::numerical_derivative::jacobian;
135+
/// let my_func1 = | args: &[num_complex::Complex64; 3] | -> num_complex::Complex64
136+
/// {
137+
/// return args[0]*args[1]*args[2]; //x*y*z
138+
/// };
139+
///
140+
/// let my_func2 = | args: &[num_complex::Complex64; 3] | -> num_complex::Complex64
141+
/// {
142+
/// return args[0].powf(2.0) + args[1].powf(2.0); //x^2 + y^2
143+
/// };
144+
///
145+
/// //define the function vector
146+
/// let function_matrix: Vec<Box<dyn Fn(&[num_complex::Complex64; 3]) -> num_complex::Complex64>> = vec![Box::new(my_func1), Box::new(my_func2)];
147+
///
148+
/// //the point around which we want the jacobian matrix
149+
/// let points = [num_complex::c64(1.0, 3.0), num_complex::c64(2.0, 3.5), num_complex::c64(3.0, 0.0)];
150+
///
151+
/// let result = jacobian::get_on_heap(&function_matrix, &points).unwrap();
152+
///```
153+
///
154+
#[cfg(feature = "heap")]
155+
pub fn get_on_heap<T: ComplexFloat, const NUM_VARS: usize>(function_matrix: &Vec<Box<dyn Fn(&[T; NUM_VARS]) -> T>>, vector_of_points: &[T; NUM_VARS]) -> Result<Vec<Vec<T>>, ErrorCode>
156+
{
157+
return get_on_heap_custom(function_matrix, vector_of_points, mode::DEFAULT_STEP_SIZE, mode::DiffMode::CentralFixedStep);
158+
}
159+
160+
161+
///same as [get_on_heap()] but with the option to change the differentiation mode used, reserved for more advanced users
162+
/// NOTE: Returns a Result<T, ErrorCode>
163+
/// Possible ErrorCode are:
164+
/// VectorOfFunctionsCannotBeEmpty -> if function_matrix argument is an empty array
165+
/// NumberOfStepsCannotBeZero -> if the derivative step size is zero
166+
#[cfg(feature = "heap")]
167+
pub fn get_on_heap_custom<T: ComplexFloat, const NUM_VARS: usize>(function_matrix: &Vec<Box<dyn Fn(&[T; NUM_VARS]) -> T>>, vector_of_points: &[T; NUM_VARS], step_size: f64, mode: mode::DiffMode) -> Result<Vec<Vec<T>>, ErrorCode>
168+
{
169+
if function_matrix.is_empty()
170+
{
171+
return Err(ErrorCode::VectorOfFunctionsCannotBeEmpty);
172+
}
173+
174+
let num_funcs = function_matrix.len();
175+
176+
let mut result: Vec<Vec<T>> = Vec::new();
177+
178+
for row_index in 0..num_funcs
179+
{
180+
let mut cur_row: Vec<T> = Vec::new();
181+
for col_index in 0..NUM_VARS
182+
{
183+
cur_row.push(single_derivative::get_partial_custom(&function_matrix[row_index], col_index, vector_of_points, step_size, mode)?);
184+
}
185+
186+
result.push(cur_row);
187+
}
188+
91189
return Ok(result);
92190
}

src/numerical_derivative/test.rs

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ use crate::numerical_derivative::single_derivative as single_derivative;
44
use crate::numerical_derivative::double_derivative as double_derivative;
55
use crate::numerical_derivative::triple_derivative as triple_derivative;
66
use crate::numerical_derivative::jacobian as jacobian;
7-
87
use crate::numerical_derivative::hessian as hessian;
98

9+
#[cfg(feature = "heap")]
10+
use std::{boxed::Box, vec::Vec};
11+
12+
1013
#[test]
1114
fn test_single_derivative_forward_difference()
1215
{
@@ -723,6 +726,82 @@ fn test_jacobian_1_complex()
723726
}
724727
}
725728

729+
#[test]
730+
#[cfg(feature = "heap")]
731+
fn test_jacobian_2()
732+
{
733+
//function is x*y*z
734+
let func1 = | args: &[f64; 3] | -> f64
735+
{
736+
return args[0]*args[1]*args[2];
737+
};
738+
739+
//function is x^2 + y^2
740+
let func2 = | args: &[f64; 3] | -> f64
741+
{
742+
return args[0].powf(2.0) + args[1].powf(2.0);
743+
};
744+
745+
let function_matrix: Vec<Box<dyn Fn(&[f64; 3]) -> f64>> = std::vec![Box::new(func1), Box::new(func2)];
746+
747+
let points = [1.0, 2.0, 3.0]; //the point around which we want the jacobian matrix
748+
749+
let result: Vec<Vec<f64>> = jacobian::get_on_heap(&function_matrix, &points).unwrap();
750+
751+
assert!(result.len() == function_matrix.len()); //number of rows
752+
assert!(result[0].len() == points.len()); //number of columns
753+
754+
let expected_result = [[6.0, 3.0, 2.0], [2.0, 4.0, 0.0]];
755+
756+
for i in 0..function_matrix.len()
757+
{
758+
for j in 0..points.len()
759+
{
760+
assert!(f64::abs(result[i][j] - expected_result[i][j]) < 0.000001);
761+
}
762+
}
763+
}
764+
765+
#[test]
766+
#[cfg(feature = "heap")]
767+
fn test_jacobian_2_complex()
768+
{
769+
//function is x*y*z
770+
let func1 = | args: &[num_complex::Complex64; 3] | -> num_complex::Complex64
771+
{
772+
return args[0]*args[1]*args[2];
773+
};
774+
775+
//function is x^2 + y^2
776+
let func2 = | args: &[num_complex::Complex64; 3] | -> num_complex::Complex64
777+
{
778+
return args[0].powf(2.0) + args[1].powf(2.0);
779+
};
780+
781+
let function_matrix: Vec<Box<dyn Fn(&[num_complex::Complex64; 3]) -> num_complex::Complex64>> = std::vec![Box::new(func1), Box::new(func2)];
782+
783+
//the point around which we want the jacobian matrix
784+
let points = [num_complex::c64(1.0, 3.0), num_complex::c64(2.0, 3.5), num_complex::c64(3.0, 0.0)];
785+
786+
let result = jacobian::get_on_heap(&function_matrix, &points).unwrap();
787+
788+
assert!(result.len() == function_matrix.len()); //number of rows
789+
assert!(result[0].len() == points.len()); //number of columns
790+
791+
let expected_result = [[points[1]*points[2], points[0]*points[2], points[0]*points[1]],
792+
[2.0*points[0], 2.0*points[1], 0.0*points[2]]];
793+
794+
795+
for i in 0..function_matrix.len()
796+
{
797+
for j in 0..points.len()
798+
{
799+
assert!(num_complex::ComplexFloat::abs(result[i][j].re - expected_result[i][j].re) < 0.000001);
800+
assert!(num_complex::ComplexFloat::abs(result[i][j].im - expected_result[i][j].im) < 0.000001);
801+
}
802+
}
803+
}
804+
726805
#[test]
727806
fn test_jacobian_1_error()
728807
{

0 commit comments

Comments
 (0)