From a2ce71161e38557049c4233ab25d9a6f48dc1f0f Mon Sep 17 00:00:00 2001
From: Andy Pack <andy@sarsoo.xyz>
Date: Thu, 13 Feb 2025 19:49:27 +0000
Subject: [PATCH] adding covariance, variance

---
 Cargo.lock                                | 52 +++++++++++++++++++++++
 Cargo.toml                                |  3 ++
 FinLib.NET/FinLib.Test/Basic.cs           | 22 +++++++++-
 FinLib.NET/FinLib.Test/FinLib.Test.csproj |  2 +-
 FinLib.NET/FinLib/FinLib.cs               | 30 ++++++++++++-
 FinLib.NET/FinLib/NativeMethods.g.cs      |  8 ++--
 finlib-cpp/finlib-native.h                |  4 +-
 finlib-ffi/src/lib.rs                     | 25 +++++++++--
 finlib-wasm/src/lib.rs                    |  8 ++--
 finlib/Cargo.toml                         |  1 +
 finlib/src/interest/mod.rs                | 26 +++++++++---
 finlib/src/lib.rs                         | 16 +------
 finlib/src/stats/covariance.rs            | 35 +++++++++++++++
 finlib/src/stats/mod.rs                   | 37 ++++++++++++++++
 pyfinlib/src/lib.rs                       | 10 ++---
 15 files changed, 235 insertions(+), 44 deletions(-)
 create mode 100644 finlib/src/stats/covariance.rs
 create mode 100644 finlib/src/stats/mod.rs

diff --git a/Cargo.lock b/Cargo.lock
index 3754019..897f34d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -156,6 +156,31 @@ dependencies = [
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
 [[package]]
 name = "csbindgen"
 version = "1.9.3"
@@ -166,6 +191,12 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "either"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
+
 [[package]]
 name = "equivalent"
 version = "1.0.1"
@@ -193,6 +224,7 @@ name = "finlib"
 version = "0.0.1"
 dependencies = [
  "pyo3",
+ "rayon",
 ]
 
 [[package]]
@@ -426,6 +458,26 @@ dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "rayon"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
 [[package]]
 name = "regex"
 version = "1.11.1"
diff --git a/Cargo.toml b/Cargo.toml
index 9666c80..851154d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,6 +18,9 @@ version = "0.0.1"
 authors = ["sarsoo <andy@sarsoo.xyz>"]
 edition = "2021"
 
+[workspace.dependencies]
+rayon = "1.10.0"
+
 [workspace.dependencies.pyo3]
 version = "0.23.4"
 # "abi3-py38" tells pyo3 (and maturin) to build using the stable ABI with minimum Python version 3.8
diff --git a/FinLib.NET/FinLib.Test/Basic.cs b/FinLib.NET/FinLib.Test/Basic.cs
index d23738c..b1e46a7 100644
--- a/FinLib.NET/FinLib.Test/Basic.cs
+++ b/FinLib.NET/FinLib.Test/Basic.cs
@@ -9,8 +9,26 @@ public class Tests
     }
 
     [Test]
-    public void TestAdd()
+    public void TestCompoundInterest()
     {
-        FinLib.Add(10, 10).Should().Be(20);
+        FinLib.CompoundInterest(100, 0.05, 1, 1).Should().Be(105);
+    }
+
+    [Test]
+    public void TestCompoundInterestMonthly()
+    {
+        Math.Round(FinLib.CompoundInterest(100, 0.05, 1, 12), 2).Should().Be(105.12);
+    }
+
+    [Test]
+    public void Covariance()
+    {
+        FinLib.Covariance([1d, 2d, 3d, 4], [1d, 2, 3, 4]).Should().Be(1.6666666666666667);
+    }
+
+    [Test]
+    public void CovarianceBreaking()
+    {
+        FinLib.Covariance([1d, 2d, 3d, 4], [1d]).Should().BeNull();
     }
 }
\ No newline at end of file
diff --git a/FinLib.NET/FinLib.Test/FinLib.Test.csproj b/FinLib.NET/FinLib.Test/FinLib.Test.csproj
index 710a575..f12f863 100644
--- a/FinLib.NET/FinLib.Test/FinLib.Test.csproj
+++ b/FinLib.NET/FinLib.Test/FinLib.Test.csproj
@@ -10,7 +10,7 @@
 
     <ItemGroup>
         <PackageReference Include="coverlet.collector" Version="6.0.2"/>
-        <PackageReference Include="FluentAssertions" Version="8.0.1" />
+        <PackageReference Include="FluentAssertions" Version="7.1.0" />
         <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1"/>
         <PackageReference Include="NUnit" Version="4.2.2"/>
         <PackageReference Include="NUnit.Analyzers" Version="4.3.0"/>
diff --git a/FinLib.NET/FinLib/FinLib.cs b/FinLib.NET/FinLib/FinLib.cs
index de1a631..00d5183 100644
--- a/FinLib.NET/FinLib/FinLib.cs
+++ b/FinLib.NET/FinLib/FinLib.cs
@@ -1,9 +1,35 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
 namespace FinLib;
 
 public static class FinLib
 {
-    public static ulong Add(ulong a, ulong b)
+    public static double CompoundInterest(double principal, double rate, double time, double n)
     {
-        return NativeMethods.add(a, b);
+        return NativeMethods.interest_compound(principal, rate, time, n);
+    }
+
+    public static double? Covariance(IEnumerable<double> valuesOne, IEnumerable<double> valuesTwo)
+    {
+        unsafe {
+            var valuesOneArr = valuesOne.ToArray();
+            var valuesTwoArr = valuesTwo.ToArray();
+            fixed (double* ptrOne = valuesOneArr)
+            fixed (double* ptrTwo = valuesTwoArr) {
+                var ret = NativeMethods.covariance(ptrOne, (UIntPtr)valuesOneArr.Length, ptrTwo, (UIntPtr) valuesTwoArr.Length);
+
+                if (ret == null)
+                {
+                    return null;
+                }
+                else
+                {
+                    return *ret;
+                }
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/FinLib.NET/FinLib/NativeMethods.g.cs b/FinLib.NET/FinLib/NativeMethods.g.cs
index 6a5d1a4..8544bd6 100644
--- a/FinLib.NET/FinLib/NativeMethods.g.cs
+++ b/FinLib.NET/FinLib/NativeMethods.g.cs
@@ -16,11 +16,11 @@ namespace FinLib
 
 
 
-        [DllImport(__DllName, EntryPoint = "add", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
-        internal static extern ulong add(ulong left, ulong right);
-
         [DllImport(__DllName, EntryPoint = "interest_compound", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
-        internal static extern float interest_compound(float principal, float rate, float time, float n);
+        internal static extern double interest_compound(double principal, double rate, double time, double n);
+
+        [DllImport(__DllName, EntryPoint = "covariance", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
+        internal static extern double* covariance(double* arr, nuint len, double* arr_two, nuint len_two);
 
 
     }
diff --git a/finlib-cpp/finlib-native.h b/finlib-cpp/finlib-native.h
index 3f3c94b..efcf02a 100644
--- a/finlib-cpp/finlib-native.h
+++ b/finlib-cpp/finlib-native.h
@@ -17,9 +17,9 @@ namespace finlib {
 
 extern "C" {
 
-uint64_t add(uint64_t left, uint64_t right);
+const double *covariance(const double *arr, size_t len, const double *arr_two, size_t len_two);
 
-float interest_compound(float principal, float rate, float time, float n);
+double interest_compound(double principal, double rate, double time, double n);
 
 }  // extern "C"
 
diff --git a/finlib-ffi/src/lib.rs b/finlib-ffi/src/lib.rs
index 3b11c1e..5c79ccb 100644
--- a/finlib-ffi/src/lib.rs
+++ b/finlib-ffi/src/lib.rs
@@ -1,9 +1,26 @@
+use std::ptr::null;
+use std::slice;
+
 #[no_mangle]
-pub extern "C" fn add(left: u64, right: u64) -> u64 {
-    finlib::add(left, right)
+pub extern "C" fn interest_compound(principal: f64, rate: f64, time: f64, n: f64) -> f64 {
+    finlib::interest::compound(principal, rate, time, n)
 }
 
 #[no_mangle]
-pub extern "C" fn interest_compound(principal: f32, rate: f32, time: f32, n: f32) -> f32 {
-    finlib::interest::compound(principal, rate, time, n)
+pub unsafe extern "C" fn covariance(arr: *const f64, len: usize, arr_two: *const f64, len_two: usize) -> *const f64 {
+    let input_array = unsafe {
+        assert!(!arr.is_null());
+        slice::from_raw_parts(arr, len)
+    };
+
+    let input_array_two = unsafe {
+        assert!(!arr_two.is_null());
+        slice::from_raw_parts(arr_two, len_two)
+    };
+
+    match finlib::stats::covariance(input_array, input_array_two) {
+        None => null(),
+        Some(v) => Box::into_raw(Box::new(v))
+    }
+
 }
\ No newline at end of file
diff --git a/finlib-wasm/src/lib.rs b/finlib-wasm/src/lib.rs
index 985dde6..1445120 100644
--- a/finlib-wasm/src/lib.rs
+++ b/finlib-wasm/src/lib.rs
@@ -1,11 +1,11 @@
 use wasm_bindgen::prelude::wasm_bindgen;
 
 #[wasm_bindgen]
-pub fn add(left: u64, right: u64) -> u64 {
-    finlib::add(left, right)
+pub fn compound(principal: f64, rate: f64, time: f64, n: f64) -> f64 {
+    finlib::interest::compound(principal, rate, time, n)
 }
 
 #[wasm_bindgen]
-pub fn compound(principal: f32, rate: f32, time: f32, n: f32) -> f32 {
-    finlib::interest::compound(principal, rate, time, n)
+pub fn covariance(slice: Vec<f64>, slice_two: Vec<f64>) -> Option<f64> {
+    finlib::stats::covariance(&slice, &slice_two)
 }
\ No newline at end of file
diff --git a/finlib/Cargo.toml b/finlib/Cargo.toml
index 9fa3e1b..f1d735c 100644
--- a/finlib/Cargo.toml
+++ b/finlib/Cargo.toml
@@ -6,6 +6,7 @@ edition.workspace = true
 
 [dependencies]
 pyo3 = { workspace = true, optional = true }
+rayon = { workspace = true }
 
 [features]
 py = ["dep:pyo3"]
\ No newline at end of file
diff --git a/finlib/src/interest/mod.rs b/finlib/src/interest/mod.rs
index 9aa3d87..56151fc 100644
--- a/finlib/src/interest/mod.rs
+++ b/finlib/src/interest/mod.rs
@@ -1,7 +1,11 @@
 
-pub fn compound(principal: f32, rate: f32, time: f32, n: f32) -> f32 {
+pub fn compound_32(principal: f32, rate: f32, time: f32, n: f32) -> f32 {
     principal * f32::powf( 1f32 + (rate / n), time * n)
 }
+
+pub fn compound(principal: f64, rate: f64, time: f64, n: f64) -> f64 {
+    principal * f64::powf( 1f64 + (rate / n), time * n)
+}
 /// https://www.thecalculatorsite.com/finance/calculators/compoundinterestcalculator.php
 
 #[cfg(test)]
@@ -9,14 +13,26 @@ mod tests {
     use super::*;
 
     #[test]
-    fn annual_compound() {
-        let result = compound(100f32, 0.05f32, 1f32, 1f32);
+    fn annual_compound_32() {
+        let result = compound_32(100f32, 0.05f32, 1f32, 1f32);
         assert_eq!(f32::round(result), 105f32);
     }
 
     #[test]
-    fn monthly_compound() {
-        let result = compound(100f32, 0.05f32, 1f32, 12f32);
+    fn monthly_compound_32() {
+        let result = compound_32(100f32, 0.05f32, 1f32, 12f32);
         assert_eq!(f32::round(result * 100f32) / 100f32, 105.12f32);
     }
+
+    #[test]
+    fn annual_compound() {
+        let result = compound(100f64, 0.05f64, 1f64, 1f64);
+        assert_eq!(f64::round(result), 105f64);
+    }
+
+    #[test]
+    fn monthly_compound() {
+        let result = compound(100f64, 0.05f64, 1f64, 12f64);
+        assert_eq!(f64::round(result * 100f64) / 100f64, 105.12f64);
+    }
 }
diff --git a/finlib/src/lib.rs b/finlib/src/lib.rs
index fb4057d..40ee7ce 100644
--- a/finlib/src/lib.rs
+++ b/finlib/src/lib.rs
@@ -1,16 +1,2 @@
 pub mod interest;
-
-pub fn add(left: u64, right: u64) -> u64 {
-    left + right
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn it_works() {
-        let result = add(2, 2);
-        assert_eq!(result, 4);
-    }
-}
+pub mod stats;
\ No newline at end of file
diff --git a/finlib/src/stats/covariance.rs b/finlib/src/stats/covariance.rs
new file mode 100644
index 0000000..70a3735
--- /dev/null
+++ b/finlib/src/stats/covariance.rs
@@ -0,0 +1,35 @@
+use rayon::prelude::*;
+use super::mean;
+
+pub fn covariance(slice: &[f64], slice_two: &[f64]) -> Option<f64>
+{
+    match slice.len() - slice_two.len() {
+        0 => {
+            let mean_1 = mean(slice);
+            let mean_2 = mean(slice_two);
+
+            Some(slice.par_iter().zip(slice_two.par_iter())
+                .map(|(x, y)| (x - mean_1) * (y - mean_2))
+                .sum::<f64>()
+                / ((slice.len() - 1) as f64))
+        }
+        _ => None
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn covariance_test() {
+        let result = covariance(&[1f64, 2f64, 3f64, 4f64], &[1f64, 2f64, 3f64, 4f64]);
+        assert_eq!(result.unwrap(), 1.6666666666666667f64);
+    }
+
+    #[test]
+    fn covariance_test_break() {
+        let result = covariance(&[1f64, 2f64, 3f64, 4f64], &[1f64, 2f64]);
+        assert_eq!(result.is_none(), true);
+    }
+}
\ No newline at end of file
diff --git a/finlib/src/stats/mod.rs b/finlib/src/stats/mod.rs
new file mode 100644
index 0000000..090f32e
--- /dev/null
+++ b/finlib/src/stats/mod.rs
@@ -0,0 +1,37 @@
+mod covariance;
+pub use covariance::*;
+
+use rayon::prelude::*;
+
+pub fn mean(slice: &[f64]) -> f64
+{
+    slice.par_iter().sum::<f64>() / slice.len() as f64
+}
+
+pub fn population_variance(slice: &[f64]) -> f64
+{
+    let mean = mean(slice);
+    slice.par_iter()
+        .map(|x| f64::powi(x - mean, 2))
+        .sum::<f64>()
+        / slice.len() as f64
+}
+
+pub fn sample_variance(slice: &[f64]) -> f64
+{
+    let mean = mean(slice);
+    slice.par_iter()
+        .map(|x| f64::powi(x - mean, 2))
+        .sum::<f64>()
+        / ((slice.len() - 1) as f64)
+}
+
+pub fn population_std_dev(slice: &[f64]) -> f64
+{
+    f64::sqrt(population_variance(slice))
+}
+
+pub fn sample_std_dev(slice: &[f64]) -> f64
+{
+    f64::sqrt(sample_variance(slice))
+}
\ No newline at end of file
diff --git a/pyfinlib/src/lib.rs b/pyfinlib/src/lib.rs
index 883c922..e0c923b 100644
--- a/pyfinlib/src/lib.rs
+++ b/pyfinlib/src/lib.rs
@@ -1,18 +1,18 @@
 use pyo3::prelude::*;
 
 #[pyfunction]
-fn add(left: u64, right: u64) -> PyResult<u64> {
-    Ok(finlib::add(left, right))
+pub fn compound(principal: f64, rate: f64, time: f64, n: f64) -> PyResult<f64> {
+    Ok(finlib::interest::compound(principal, rate, time, n))
 }
 
 #[pyfunction]
-pub fn compound(principal: f32, rate: f32, time: f32, n: f32) -> PyResult<f32> {
-    Ok(finlib::interest::compound(principal, rate, time, n))
+pub fn covariance(slice: Vec<f64>, slice_two: Vec<f64>) -> PyResult<Option<f64>> {
+    Ok(finlib::stats::covariance(&slice, &slice_two))
 }
 
 #[pymodule]
 fn pyfinlib(m: &Bound<'_, PyModule>) -> PyResult<()> {
-    m.add_function(wrap_pyfunction!(add, m)?)?;
     m.add_function(wrap_pyfunction!(compound, m)?)?;
+    m.add_function(wrap_pyfunction!(covariance, m)?)?;
     Ok(())
 }
\ No newline at end of file