signvec/
lib.rs

1//! # SignVec
2//!
3//! ![Crates.io](https://img.shields.io/crates/v/signvec)
4//! ![docs.rs](https://img.shields.io/docsrs/signvec)
5//! ![License](https://img.shields.io/crates/l/signvec)
6//! ![GitHub Workflow Status](https://github.com/b-vitamins/signvec/actions/workflows/rust.yml/badge.svg)
7//!
8//! `SignVec` extends the capabilities of the traditional `Vec` by providing additional functionalities
9//! to efficiently track and manipulate elements based on their sign (positive or negative) using the `Signable` trait.
10//!
11//! ## Features
12//!
13//! - **Sign-Aware Element Tracking**: Tracks the sign of elements for optimized sign-specific operations.
14//! - **Efficient Updates**: Maintains high performance even with frequent modifications.
15//! - **Versatile Operations**: Provides methods for element counting, access, and manipulation based on sign.
16//! - **Flexible Interface**: Offers both safe and unsafe methods to accommodate various requirements.
17//! - **Custom Type Support**: Seamlessly integrates with user-defined types via the `Signable` trait.
18//!
19//! ## Usage: Basic operations
20//!
21//! ```rust
22//! use nanorand::{Rng, WyRand};
23//! use signvec::{svec, Sign, SignVec};
24//!
25//!     let mut vector = svec![1, -2, 3, -4];
26//!
27//!     assert_eq!(vector.len(), 4);
28//!     assert_eq!(vector[0], 1);
29//!     assert_eq!(vector[1], -2);
30//!
31//!     // Count positive and negative elements
32//!     assert_eq!(vector.count(Sign::Plus), 2);
33//!     assert_eq!(vector.count(Sign::Minus), 2);
34//!
35//!     // Get indices of positive and negative elements
36//!     assert_eq!(
37//!         vector.indices(Sign::Plus).iter().collect::<Vec<_>>(),
38//!         vec![&0, &2]
39//!     );
40//!     assert_eq!(
41//!         vector.indices(Sign::Minus).iter().collect::<Vec<_>>(),
42//!         vec![&1, &3]
43//!     );
44//!
45//!     // Retrieve values based on their sign
46//!     assert_eq!(vector.values(Sign::Plus).collect::<Vec<_>>(), vec![&1, &3]);
47//!     assert_eq!(
48//!         vector.values(Sign::Minus).collect::<Vec<_>>(),
49//!         vec![&-2, &-4]
50//!     );
51//!
52//!     // Modify individual elements
53//!     vector.set(1, 5);
54//!     assert_eq!(vector[1], 5);
55//!
56//!     // Randomly select an element based on its sign
57//!     let mut rng = WyRand::new();
58//!     if let Some(random_positive) = vector.random(Sign::Plus, &mut rng) {
59//!         println!("Random positive value: {}", random_positive);
60//!     }
61//! ```
62//!
63//! ## Usage: Monte Carlo simulations
64//!
65//! This demonstrates a simple Monte Carlo simulation where site energies in a
66//! `SignVec` are updated based on simulated dynamics and system energy distribution.
67//!
68//! ```rust
69//! use signvec::{SignVec, svec, Sign, Signable};
70//! use nanorand::{WyRand, Rng};
71//!
72//!     let mut energies = svec![1.0, -1.0, 1.5, -1.5, 0.5, -0.5];
73//!     let mut rng = WyRand::new();
74//!
75//!     // Simulation loop for multiple Monte Carlo steps
76//!     for _step in 0..100 {
77//!         let site = rng.generate_range(0..energies.len());
78//!         let dE = rng.generate::<f64>() - 0.5; // Change in energy
79//!
80//!         let new_energy = energies[site] + dE; // Update site energy
81//!         
82//!         // Make decisions based on system's energy distribution
83//!         if energies.count(Sign::Minus) > energies.count(Sign::Plus) {
84//!             if energies[site].sign() == Sign::Minus && rng.generate::<f64>() < 0.5 {
85//!                 energies.set(site, -new_energy); // Flip energy sign
86//!             } else {
87//!                 energies.set(site, new_energy);
88//!             }
89//!         } else {
90//!             energies.set(site, new_energy); // Balanced distribution
91//!         }
92//!     }
93//!
94//!     println!("Final energy distribution: {:?}", energies);
95//! ```
96//!
97//! ## Usage: Portfolio management
98//!
99//! Demonstrates how `SignVec` can be used for managing a financial portfolio, simulating
100//! market conditions, and making decisions based on the sign-aware characteristics of
101//! assets and liabilities.
102//!
103//! ```rust
104//! use signvec::{SignVec, Sign, svec};
105//! use nanorand::WyRand;
106//!
107//!     let mut portfolio = svec![150.0, -200.0, 300.0, -50.0, 400.0];
108//!     let market_conditions = vec![1.05, 0.95, 1.10, 1.00, 1.03];
109//!
110//!     // Apply market conditions to adjust portfolio balances
111//!     for (index, &factor) in market_conditions.iter().enumerate() {
112//!         portfolio.set(index, portfolio[index] * factor);
113//!     }
114//!
115//!     // Decision making based on portfolio's sign-aware characteristics
116//!     if portfolio.count(Sign::Minus) > 2 {
117//!         println!("Consider rebalancing your portfolio to manage risk.");
118//!     } else {
119//!         println!("Your portfolio is well-balanced and diversified.");
120//!     }
121//!
122//!     // Calculate 10% of total liabilities for debt reduction
123//!     let debt_reduction = portfolio.values(Sign::Minus).sum::<f64>() * 0.1;
124//!     println!("Plan for a debt reduction of ${:.2} to strengthen your financial position.", debt_reduction.abs());
125//!
126//!     // Identify a high-performing asset for potential investment
127//!     let mut rng = WyRand::new();
128//!     if let Some(lucky_asset) = portfolio.random(Sign::Plus, &mut rng) {
129//!         println!("Consider investing more in an asset valued at ${:.2}.", lucky_asset);
130//!     } else {
131//!         println!("No standout assets for additional investment at the moment.");
132//!     }
133//! ```
134//!
135//! ## Benchmarks
136//!
137//! The table below is a summary of benchmark results for the specialized functionality of `SignVec`.
138//!
139//! | Operation         | `SignVec`      | `Vec`     | Speedup        |
140//! |-------------------|----------------|-----------|----------------|
141//! | `set`             | 1.3922 ns      | -         | -              |
142//! | `set_unchecked`   | 1.3873 ns      | -         | -              |
143//! | `random` (Plus)   | 822.90 ps      | -         | -              |
144//! | `random` (Minus)  | 829.92 ps      | -         | -              |
145//! | `random_pos`      | 652.96 ps      | -         | -              |
146//! | `random_neg`      | 687.77 ps      | -         | -              |
147//! | `count` (Plus)    | 453.21 ps      | -         | -              |
148//! | `count` (Minus)   | 458.70 ps      | -         | -              |
149//! | `count_pos`       | 229.73 ps      | -         | -              |
150//! | `count_neg`       | 228.44 ps      | -         | -              |
151//! | `indices` (Plus)  | 465.04 ps      | -         | -              |
152//! | `indices` (Minus) | 461.85 ps      | -         | -              |
153//! | `indices_pos`     | 226.99 ps      | -         | -              |
154//! | `indices_neg`     | 225.83 ps      | -         | -              |
155//! | `sync`            | 61.208 µs      | -         | -              |
156//! | `count`[^1]       | 225.74 ps      | 153.38 ns | ~679x faster   |
157//! | `indices`[^2]     | 86.42 ns       | 1.11 µs   | ~12.8x faster  |
158//! | `values`[^3]      | 579.37 ns      | 1.13 µs   | ~1.95x faster  |
159//! | `random`[^4]      | 857.86 ps      | 950.84 ns | ~1106x faster  |
160//!
161//! [^1]: The `count_pos` and `count_neg` benchmarks are used here for comparison as they represent the optimized paths for counting elements by sign.
162//! [^2]: The `indices_pos` and `indices_neg` benchmarks are presented as the optimized methods for retrieving indices by sign.
163//! [^3]: The `values` operation does not have a direct counterpart in the provided benchmarks but is included for context.
164//! [^4]: The `random_pos` and `random_neg` benchmarks provide context for the `random` operation's performance when the sign is predetermined.
165//!
166//! Benchmarks were conducted on a machine with the following specifications:
167//! - Processor: AMD Ryzen™ 5 5600G with Radeon™ Graphics x 12
168//! - Memory: 58.8 GiB
169//! - Operating System: Guix System
170//! - OS Type: 64-bit
171
172mod signvec;
173pub use signvec::SignVec;
174
175/// Enum representing the sign of a number.
176#[derive(Debug, PartialEq, Copy, Clone)]
177pub enum Sign {
178    Plus,
179    Minus,
180}
181
182/// Trait for types that can be classified by a sign.
183pub trait Signable {
184    fn sign(&self) -> Sign;
185}
186
187impl Sign {
188    pub fn flip(&self) -> Self {
189        match self {
190            Sign::Plus => Sign::Minus,
191            Sign::Minus => Sign::Plus,
192        }
193    }
194}
195
196macro_rules! signfrom {
197    ($($t:ty),*) => {$(
198        impl From<$t> for Sign {
199            fn from(num: $t) -> Self {
200                if num >= 0 as $t { Sign::Plus } else { Sign::Minus }
201            }
202        }
203    )*};
204}
205
206macro_rules! signable {
207    ($($t:ty),*) => {$(
208        impl Signable for $t {
209            fn sign(&self) -> Sign {
210                if *self >= 0 as $t { Sign::Plus } else { Sign::Minus }
211            }
212        }
213    )*};
214}
215
216#[macro_export]
217macro_rules! svec {
218    ($($x:expr),* $(,)?) => {{
219        let temp_slice = &[$($x),*];
220        SignVec::from(temp_slice)
221    }};
222}
223
224signfrom!(i8, i16, i32, i64, i128, isize, f32, f64);
225signable!(i8, i16, i32, i64, i128, isize, f32, f64);