From 13eef5a5cb52fb4ff8583b61ec64a6543eb3da3d Mon Sep 17 00:00:00 2001 From: Luis Rubio Date: Mon, 30 Aug 2021 13:33:25 +0200 Subject: [PATCH 1/6] feat(tapi): include wip0017 --- config/src/config.rs | 6 +-- config/src/loaders/toml.rs | 10 ++--- data_structures/src/mainnet_validations.rs | 49 ++++++++++++++-------- node/src/actors/chain_manager/mod.rs | 19 +++++---- 4 files changed, 49 insertions(+), 35 deletions(-) diff --git a/config/src/config.rs b/config/src/config.rs index 703421305..2bf0f673f 100644 --- a/config/src/config.rs +++ b/config/src/config.rs @@ -380,10 +380,8 @@ pub struct Mempool { #[derive(Deserialize, Serialize, Default, Debug, Clone, PartialEq)] #[serde(default)] pub struct Tapi { - /// Oppose WIP0014 - pub oppose_wip0014: bool, - /// Oppose WIP0016 - pub oppose_wip0016: bool, + /// Oppose WIP0017 + pub oppose_wip0017: bool, } fn to_partial_consensus_constants(c: &ConsensusConstants) -> PartialConsensusConstants { diff --git a/config/src/loaders/toml.rs b/config/src/loaders/toml.rs index 6c583272f..dce319642 100644 --- a/config/src/loaders/toml.rs +++ b/config/src/loaders/toml.rs @@ -234,18 +234,16 @@ enabled = false // Check that the tapi table does not need to explicitly set all the new "oppose_wip" fields // and they default to "false" let empty_config = super::from_str("[tapi]").unwrap(); - let config_oppose_0016 = super::from_str( + let config_oppose_0017 = super::from_str( r" [tapi] -oppose_wip0016 = true +oppose_wip0017 = true ", ) .unwrap(); assert_eq!(empty_config.tapi, Tapi::default()); - assert!(!empty_config.tapi.oppose_wip0014); - assert!(!empty_config.tapi.oppose_wip0016); - assert!(!config_oppose_0016.tapi.oppose_wip0014); - assert!(config_oppose_0016.tapi.oppose_wip0016); + assert!(!empty_config.tapi.oppose_wip0017); + assert!(config_oppose_0017.tapi.oppose_wip0017); } } diff --git a/data_structures/src/mainnet_validations.rs b/data_structures/src/mainnet_validations.rs index 9e365243e..25aab7c16 100644 --- a/data_structures/src/mainnet_validations.rs +++ b/data_structures/src/mainnet_validations.rs @@ -45,6 +45,18 @@ pub fn wip_info() -> HashMap { active_wips.insert("WIP0008".to_string(), FIRST_HARD_FORK); active_wips.insert("WIP0009-0011-0012".to_string(), SECOND_HARD_FORK); active_wips.insert("THIRD_HARD_FORK".to_string(), THIRD_HARD_FORK); + active_wips.insert("WIP0014-0016".to_string(), 549141); + + active_wips +} + +/// Initial information about WIPs for Testnet and Development +fn test_wip_info() -> HashMap { + let mut active_wips = HashMap::::default(); + active_wips.insert("WIP0008".to_string(), 0); + active_wips.insert("WIP0009-0011-0012".to_string(), 0); + active_wips.insert("THIRD_HARD_FORK".to_string(), 0); + active_wips.insert("WIP0014-0016".to_string(), 0); active_wips } @@ -106,26 +118,24 @@ impl TapiEngine { } // Hardcoded information about WIPs in vote processing - let bit = 0; - let wip_0014 = BitVotesCounter { + let bit = 1; + let wip_0017 = BitVotesCounter { votes: 0, period: 26880, - wip: "WIP0014-0016".to_string(), + wip: "WIP0017".to_string(), // Start voting at + // TODO: Choose a right date // 13 July 2021 @ 9:00:00 UTC init: 522240, end: u32::MAX, bit, }; - voting_wips[bit] = Some(wip_0014); + voting_wips[bit] = Some(wip_0017); } Environment::Testnet | Environment::Development => { // In non-mainnet chains, all the WIPs that are active in mainnet are considered // active since epoch 0. And there is no voting. - self.wip_activation.insert("WIP0008".to_string(), 0); - self.wip_activation - .insert("WIP0009-0011-0012".to_string(), 0); - self.wip_activation.insert("THIRD_HARD_FORK".to_string(), 0); + self.wip_activation = test_wip_info(); } }; @@ -380,6 +390,10 @@ impl ActiveWips { pub fn wip0016(&self) -> bool { self.wip_active("WIP0014-0016") } + + pub fn wip0017(&self) -> bool { + self.wip_active("WIP0017") + } } #[cfg(test)] @@ -644,24 +658,25 @@ mod tests { let mut t = TapiEngine::default(); let (epoch, old_wips) = t.initialize_wip_information(Environment::Mainnet); - // The first block whose vote must be counted is the one from WIP0014 - let init_epoch_wip0014 = 522240; - assert_eq!(epoch, init_epoch_wip0014); + // The first block whose vote must be counted is the one from WIP0017 + // TODO: Update block number + let init_epoch_wip0017 = 522240; + assert_eq!(epoch, init_epoch_wip0017); // The TapiEngine was just created, there list of old_wips must be empty assert_eq!(old_wips, HashSet::new()); // The list of active WIPs only contains the first, second, and third hard forks let mut hm = HashMap::new(); - hm.insert("WIP0008".to_string(), FIRST_HARD_FORK); - hm.insert("WIP0009-0011-0012".to_string(), SECOND_HARD_FORK); - hm.insert("THIRD_HARD_FORK".to_string(), THIRD_HARD_FORK); + for (k, v) in wip_info() { + hm.insert(k, v); + } assert_eq!(t.wip_activation, hm); // Test initialize_wip_information with a non-empty TapiEngine let (epoch, old_wips) = t.initialize_wip_information(Environment::Mainnet); - // WIP0014 is already included and it won't be updated - let name_wip0014 = "WIP0014-0016".to_string(); + // WIP0017 is already included and it won't be updated + let name_wip0017 = "WIP0017".to_string(); let mut hs = HashSet::new(); - hs.insert(name_wip0014); + hs.insert(name_wip0017); assert_eq!(old_wips, hs); // There is no new WIPs to update so we obtain the max value diff --git a/node/src/actors/chain_manager/mod.rs b/node/src/actors/chain_manager/mod.rs index d0898ab22..4fa9cb730 100644 --- a/node/src/actors/chain_manager/mod.rs +++ b/node/src/actors/chain_manager/mod.rs @@ -2171,20 +2171,23 @@ impl ChainManager { /// Return the value of the version field for a block in this epoch fn tapi_signals_mask(&self, epoch: Epoch) -> u32 { - let Tapi { - oppose_wip0014, - oppose_wip0016, - } = &self.tapi; + let Tapi { oppose_wip0017 } = &self.tapi; let mut v = 0; - if !oppose_wip0014 - && !oppose_wip0016 + // Bit 1 + let bit = 1; + if !oppose_wip0017 + && !self + .chain_state + .tapi_engine + .wip_activation + .contains_key("WIP0017") && self .chain_state .tapi_engine - .in_voting_range(epoch, "WIP0014-0016") + .in_voting_range(epoch, "WIP0017") { - v |= 1 << 0; + v |= 1 << bit; } v From 0f5df36623ef3ccb52cbdbae117f91493e113e69 Mon Sep 17 00:00:00 2001 From: Luis Rubio Date: Mon, 30 Aug 2021 17:37:26 +0200 Subject: [PATCH 2/6] feat(radon): include empty median in RADON --- node/src/actors/chain_manager/mod.rs | 10 +++++----- rad/src/operators/array.rs | 14 +++++++++----- rad/src/reducers/average.rs | 22 ++++++++++++++-------- rad/src/reducers/mod.rs | 13 +++++++++++-- rad/src/script.rs | 2 +- rad/src/types/array.rs | 5 ++++- 6 files changed, 44 insertions(+), 22 deletions(-) diff --git a/node/src/actors/chain_manager/mod.rs b/node/src/actors/chain_manager/mod.rs index 4fa9cb730..978bb273b 100644 --- a/node/src/actors/chain_manager/mod.rs +++ b/node/src/actors/chain_manager/mod.rs @@ -2174,14 +2174,14 @@ impl ChainManager { let Tapi { oppose_wip0017 } = &self.tapi; let mut v = 0; + // Bit 0 + // FIXME(#2051): Assess when remove achieved bit signaling + let bit = 0; + v |= 1 << bit; + // Bit 1 let bit = 1; if !oppose_wip0017 - && !self - .chain_state - .tapi_engine - .wip_activation - .contains_key("WIP0017") && self .chain_state .tapi_engine diff --git a/rad/src/operators/array.rs b/rad/src/operators/array.rs index 92d7a413f..aee21831e 100644 --- a/rad/src/operators/array.rs +++ b/rad/src/operators/array.rs @@ -24,7 +24,11 @@ pub fn count(input: &RadonArray) -> RadonInteger { RadonInteger::from(input.value().len() as i128) } -pub fn reduce(input: &RadonArray, args: &[Value]) -> Result { +pub fn reduce( + input: &RadonArray, + args: &[Value], + context: &mut ReportContext, +) -> Result { let wrong_args = || RadError::WrongArguments { input_type: RadonArray::radon_type_name(), operator: "Reduce".to_string(), @@ -39,7 +43,7 @@ pub fn reduce(input: &RadonArray, args: &[Value]) -> Result(arg).map_err(|_| wrong_args())?; let reducer_code = RadonReducers::try_from(reducer_integer).map_err(|_| wrong_args())?; - reducers::reduce(input, reducer_code) + reducers::reduce(input, reducer_code, context) } pub fn get(input: &RadonArray, args: &[Value]) -> Result { @@ -395,7 +399,7 @@ mod tests { ]); let args = &[]; - let result = reduce(input, args); + let result = reduce(input, args, &mut ReportContext::default()); assert_eq!( &result.unwrap_err().to_string(), @@ -411,7 +415,7 @@ mod tests { ]); let args = &[Value::Text(String::from("wrong"))]; // This is RadonReducers::AverageMean - let result = reduce(input, args); + let result = reduce(input, args, &mut ReportContext::default()); assert_eq!( &result.unwrap_err().to_string(), @@ -427,7 +431,7 @@ mod tests { ]); let args = &[Value::Integer(-1)]; // This doesn't match any reducer code in RadonReducers - let result = reduce(input, args); + let result = reduce(input, args, &mut ReportContext::default()); assert_eq!( &result.unwrap_err().to_string(), diff --git a/rad/src/reducers/average.rs b/rad/src/reducers/average.rs index b9c073a4e..6f6f30f43 100644 --- a/rad/src/reducers/average.rs +++ b/rad/src/reducers/average.rs @@ -125,6 +125,11 @@ pub fn mean(input: &RadonArray, return_policy: MeanReturnPolicy) -> Result Result { + // TODO: implement + Ok(RadonTypes::Array(RadonArray::from(vec![]))) +} + #[cfg(test)] mod tests { use serde_cbor::Value; @@ -135,6 +140,7 @@ mod tests { }; use super::*; + use witnet_data_structures::radon_report::ReportContext; #[test] fn test_reduce_average_mean_float() { @@ -145,7 +151,7 @@ mod tests { let args = &[Value::Integer(RadonReducers::AverageMean as i128)]; let expected = RadonTypes::from(RadonFloat::from(1.5f64)); - let output = reduce(input, args).unwrap(); + let output = reduce(input, args, &mut ReportContext::default()).unwrap(); assert_eq!(output, expected); } @@ -168,7 +174,7 @@ mod tests { ])); let args = &[Value::Integer(0x03)]; // This is RadonReducers::AverageMean - let output = reduce(&input, args).unwrap(); + let output = reduce(&input, args, &mut ReportContext::default()).unwrap(); assert_eq!(output, expected); } @@ -193,7 +199,7 @@ mod tests { }; let args = &[Value::Integer(0x03)]; // This is RadonReducers::AverageMean - let output = reduce(&input, args).unwrap_err(); + let output = reduce(&input, args, &mut ReportContext::default()).unwrap_err(); assert_eq!(output, expected); } @@ -244,7 +250,7 @@ mod tests { let expected = RadonTypes::from(RadonArray::from(vec![array_e1, array_e2, array_e3])); let args = &[Value::Integer(0x03)]; // This is RadonReducers::AverageMean - let output = reduce(&input, args).unwrap(); + let output = reduce(&input, args, &mut ReportContext::default()).unwrap(); assert_eq!(output, expected); } @@ -265,7 +271,7 @@ mod tests { let args = &[Value::Integer(RadonReducers::AverageMean as i128)]; let expected = RadonTypes::Integer(RadonInteger::from(2)); - let output = reduce(input, args).unwrap(); + let output = reduce(input, args, &mut ReportContext::default()).unwrap(); assert_eq!(output, expected); } @@ -288,7 +294,7 @@ mod tests { ])); let args = &[Value::Integer(RadonReducers::AverageMean as i128)]; - let output = reduce(&input, args).unwrap(); + let output = reduce(&input, args, &mut ReportContext::default()).unwrap(); assert_eq!(output, expected); } @@ -300,7 +306,7 @@ mod tests { RadonString::from("world").into(), ]); let args = &[Value::Integer(0x03)]; // This is RadonReducers::AverageMean - let output = reduce(input, args).unwrap_err(); + let output = reduce(input, args, &mut ReportContext::default()).unwrap_err(); let expected = RadError::UnsupportedReducer { array: input.clone(), @@ -329,7 +335,7 @@ mod tests { }; let args = &[Value::Integer(0x03)]; // This is RadonReducers::AverageMean - let output = reduce(&input, args).unwrap_err(); + let output = reduce(&input, args, &mut ReportContext::default()).unwrap_err(); assert_eq!(output, expected); } diff --git a/rad/src/reducers/mod.rs b/rad/src/reducers/mod.rs index e2be638d5..ce3df3584 100644 --- a/rad/src/reducers/mod.rs +++ b/rad/src/reducers/mod.rs @@ -6,6 +6,7 @@ use crate::{ error::RadError, types::{array::RadonArray, RadonType, RadonTypes}, }; +use witnet_data_structures::radon_report::ReportContext; pub mod average; pub mod deviation; @@ -17,13 +18,13 @@ pub enum RadonReducers { // Implemented Mode = 0x02, AverageMean = 0x03, + AverageMedian = 0x05, DeviationStandard = 0x07, // Not implemented Min = 0x00, Max = 0x01, AverageMeanWeighted = 0x04, - AverageMedian = 0x05, AverageMedianWeighted = 0x06, DeviationAverageAbsolute = 0x08, DeviationMedianAbsolute = 0x09, @@ -36,7 +37,11 @@ impl fmt::Display for RadonReducers { } } -pub fn reduce(input: &RadonArray, reducer_code: RadonReducers) -> Result { +pub fn reduce( + input: &RadonArray, + reducer_code: RadonReducers, + context: &mut ReportContext, +) -> Result { let error = || { Err(RadError::UnsupportedReducer { array: input.clone(), @@ -51,6 +56,10 @@ pub fn reduce(input: &RadonArray, reducer_code: RadonReducers) -> Result mode::mode(input), RadonReducers::DeviationStandard => deviation::standard(input), + RadonReducers::AverageMedian => match &context.active_wips { + Some(active_wips) if active_wips.wip0017() => average::median(input), + _ => error(), + }, _ => error(), } } else { diff --git a/rad/src/script.rs b/rad/src/script.rs index 7bf189147..fb7c28d61 100644 --- a/rad/src/script.rs +++ b/rad/src/script.rs @@ -252,7 +252,7 @@ pub fn create_radon_script_from_filters_and_reducer( ) .map_err(|_| unknown_reducer(i128::from(reducer)))?; match rad_reducer { - RadonReducers::AverageMean | RadonReducers::Mode => {} + RadonReducers::AverageMean | RadonReducers::Mode | RadonReducers::AverageMedian => {} _ => { return Err(RadError::UnsupportedReducerInAT { operator: rad_reducer as u8, diff --git a/rad/src/types/array.rs b/rad/src/types/array.rs index 4b6fe4a5d..fc1c6caf9 100644 --- a/rad/src/types/array.rs +++ b/rad/src/types/array.rs @@ -149,7 +149,7 @@ impl Operable for RadonArray { array_operators::map(self, args.as_slice(), &mut ReportContext::default()) } (RadonOpCodes::ArrayReduce, Some(args)) => { - array_operators::reduce(self, args.as_slice()) + array_operators::reduce(self, args.as_slice(), &mut ReportContext::default()) } (RadonOpCodes::ArraySort, Some(args)) => { array_operators::sort(self, args.as_slice(), &mut ReportContext::default()) @@ -175,6 +175,9 @@ impl Operable for RadonArray { (RadonOpCodes::ArrayMap, Some(args)) => { array_operators::map(self, args.as_slice(), context) } + (RadonOpCodes::ArrayReduce, Some(args)) => { + array_operators::reduce(self, args.as_slice(), context) + } (RadonOpCodes::ArraySort, Some(args)) => { array_operators::sort(self, args.as_slice(), context) } From 04a39a2a1710582095233aefc415b37dad87bcbd Mon Sep 17 00:00:00 2001 From: Tomasz Polaczyk Date: Mon, 30 Aug 2021 12:17:15 +0200 Subject: [PATCH 3/6] feat(rad): implement median reducer --- Cargo.lock | 1 + rad/Cargo.toml | 1 + rad/src/reducers/average.rs | 5 - rad/src/reducers/median.rs | 286 ++++++++++++++++++++++++++++++++++++ rad/src/reducers/mod.rs | 3 +- 5 files changed, 290 insertions(+), 6 deletions(-) create mode 100644 rad/src/reducers/median.rs diff --git a/Cargo.lock b/Cargo.lock index b0e8f6e3e..6c54b2c5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5167,6 +5167,7 @@ dependencies = [ "json", "log 0.4.11", "num_enum", + "ordered-float", "rand 0.7.3", "serde", "serde_cbor", diff --git a/rad/Cargo.toml b/rad/Cargo.toml index 590573d30..0d9b64a48 100644 --- a/rad/Cargo.toml +++ b/rad/Cargo.toml @@ -15,6 +15,7 @@ if_rust_version = "1.0.0" json = "0.12.1" log = "0.4.8" num_enum = "0.4.2" +ordered-float = "1.0" rand = "0.7.3" serde = "1.0.111" serde_cbor = "0.11.1" diff --git a/rad/src/reducers/average.rs b/rad/src/reducers/average.rs index 6f6f30f43..cb5840ec9 100644 --- a/rad/src/reducers/average.rs +++ b/rad/src/reducers/average.rs @@ -125,11 +125,6 @@ pub fn mean(input: &RadonArray, return_policy: MeanReturnPolicy) -> Result Result { - // TODO: implement - Ok(RadonTypes::Array(RadonArray::from(vec![]))) -} - #[cfg(test)] mod tests { use serde_cbor::Value; diff --git a/rad/src/reducers/median.rs b/rad/src/reducers/median.rs new file mode 100644 index 000000000..2aef93605 --- /dev/null +++ b/rad/src/reducers/median.rs @@ -0,0 +1,286 @@ +use crate::{ + error::RadError, + operators::array as array_operators, + reducers::{average::mean, average::MeanReturnPolicy, RadonReducers}, + types::{array::RadonArray, float::RadonFloat, RadonType, RadonTypes}, + ReportContext, +}; +use ordered_float::NotNan; + +/// The median of a list of length N is the value at position floor(N/2) if N is odd, +/// and the average of the values at positions (N/2 - 1) and (N/2) if N is even. +/// +/// The input will be sorted using the ArraySort operator. +/// The input must be an array of RadonIntegers or RadonFloats. Any RadonFloats set to NaN will be +/// ignored for this operation. +pub fn median(input: &RadonArray) -> Result { + let value = input.value(); + let value_len = value.len(); + + match value.first() { + None => Err(RadError::ModeEmpty), + Some(RadonTypes::Float(_)) => { + // Collect non-NaN values into a vector, and sort them + let mut input_not_nan: Vec> = Vec::with_capacity(value_len); + + for item in value { + match item { + RadonTypes::Float(f64_value) => { + if let Ok(not_nan) = NotNan::new(f64_value.value()) { + input_not_nan.push(not_nan) + } + } + _ => { + return Err(RadError::MismatchingTypes { + method: RadonReducers::AverageMedian.to_string(), + expected: RadonFloat::radon_type_name(), + found: item.radon_type_name(), + }) + } + } + } + + input_not_nan.sort(); + + if input_not_nan.is_empty() { + // This can happen if all elements are NaN + Err(RadError::ModeEmpty) + } else if input_not_nan.len() % 2 == 1 { + // Odd number of elements: take element at floor(N/2): + let median_pos = input_not_nan.len() / 2; + let median_elem = input_not_nan[median_pos].into_inner(); + + Ok(RadonTypes::Float(RadonFloat::from(median_elem))) + } else { + // Even number of elements: take average of element at (N/2 - 1) and N/2 + let right_pos = input_not_nan.len() / 2; + let right_elem = input_not_nan[right_pos].into_inner(); + let left_pos = right_pos - 1; + let left_elem = input_not_nan[left_pos].into_inner(); + + // Create new array to be able to use average::mean reducer + let rl = RadonArray::from(vec![ + RadonTypes::Float(RadonFloat::from(left_elem)), + RadonTypes::Float(RadonFloat::from(right_elem)), + ]); + // MeanReturnPolicy only applies to integers, so this will actually return a float + mean(&rl, MeanReturnPolicy::RoundToInteger) + } + } + Some(RadonTypes::Integer(_)) => { + let sorted_input = + match array_operators::sort(input, &[], &mut ReportContext::default()) { + Ok(RadonTypes::Array(arr)) => arr.value(), + Ok(_different_type) => unreachable!(), + Err(e) => return Err(e), + }; + + if sorted_input.is_empty() { + // This is unreachable + Err(RadError::ModeEmpty) + } else if sorted_input.len() % 2 == 1 { + // Odd number of elements: take element at floor(N/2): + let median_pos = sorted_input.len() / 2; + + Ok(sorted_input[median_pos].clone()) + } else { + // Even number of elements: take average of element at (N/2 - 1) and N/2 + let right_pos = sorted_input.len() / 2; + let left_pos = right_pos - 1; + + // Create new array to be able to use average::mean reducer + let rl = RadonArray::from(vec![ + sorted_input[left_pos].clone(), + sorted_input[right_pos].clone(), + ]); + // RoundToInteger means that when the average is not an integer, it will be rounded to an + // integer. For example, the average of 1 and 2, which is 1.5, will be rounded to 2. + mean(&rl, MeanReturnPolicy::RoundToInteger) + } + } + Some(_rad_types) => Err(RadError::UnsupportedReducer { + array: input.clone(), + reducer: RadonReducers::AverageMedian.to_string(), + }), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::types::{float::RadonFloat, integer::RadonInteger, string::RadonString}; + use crate::RadError::ModeEmpty; + + #[test] + fn test_operate_reduce_median_empty() { + let input = RadonArray::from(vec![]); + let output = median(&input).unwrap_err(); + let expected_error = ModeEmpty; + assert_eq!(output, expected_error); + } + + #[test] + fn test_operate_reduce_median_float_odd() { + let input = RadonArray::from(vec![ + RadonFloat::from(1f64).into(), + RadonFloat::from(2f64).into(), + RadonFloat::from(2f64).into(), + ]); + let expected = RadonTypes::from(RadonFloat::from(2f64)); + let output = median(&input).unwrap(); + assert_eq!(output, expected); + } + + #[test] + fn test_operate_reduce_median_float_even() { + let input = RadonArray::from(vec![ + RadonFloat::from(1f64).into(), + RadonFloat::from(2f64).into(), + ]); + + let expected = RadonTypes::from(RadonFloat::from(1.5f64)); + let output = median(&input).unwrap(); + assert_eq!(output, expected); + } + + #[test] + fn test_operate_reduce_median_float_with_nans() { + let input = RadonArray::from(vec![ + RadonFloat::from(1f64).into(), + RadonFloat::from(2f64).into(), + RadonFloat::from(f64::NAN).into(), + ]); + + let expected = RadonTypes::from(RadonFloat::from(1.5f64)); + let output = median(&input).unwrap(); + assert_eq!(output, expected); + } + + #[test] + fn test_operate_reduce_median_float_all_nans() { + let input = RadonArray::from(vec![ + RadonFloat::from(f64::NAN).into(), + RadonFloat::from(f64::NAN).into(), + ]); + + let output = median(&input).unwrap_err(); + let expected_error = ModeEmpty; + assert_eq!(output, expected_error); + } + + #[test] + fn test_operate_reduce_median_int_odd() { + let input = RadonArray::from(vec![ + RadonInteger::from(1i128).into(), + RadonInteger::from(2i128).into(), + RadonInteger::from(2i128).into(), + ]); + let expected = RadonTypes::from(RadonInteger::from(2i128)); + let output = median(&input).unwrap(); + assert_eq!(output, expected); + } + + #[test] + fn test_operate_reduce_median_int_even() { + // The median should be 1.5, but it is rounded to 2 + let input = RadonArray::from(vec![ + RadonInteger::from(1i128).into(), + RadonInteger::from(2i128).into(), + ]); + let output = median(&input).unwrap(); + let expected = RadonTypes::from(RadonInteger::from(2i128)); + assert_eq!(output, expected); + } + + #[test] + fn test_operate_reduce_median_int_unsorted_input() { + let input = RadonArray::from(vec![ + RadonInteger::from(1i128).into(), + RadonInteger::from(1i128).into(), + RadonInteger::from(5i128).into(), + RadonInteger::from(5i128).into(), + RadonInteger::from(3i128).into(), + ]); + let expected = RadonTypes::from(RadonInteger::from(3i128)); + let output = median(&input).unwrap(); + assert_eq!(output, expected); + } + + #[test] + fn test_operate_reduce_median_str_odd() { + let input = RadonArray::from(vec![ + RadonString::from("Bye world!").into(), + RadonString::from("Hello world!").into(), + RadonString::from("Hello world!").into(), + ]); + let output = median(&input).unwrap_err(); + let expected_error = RadError::UnsupportedReducer { + array: input, + reducer: "RadonReducers::AverageMedian".to_string(), + }; + assert_eq!(output, expected_error); + } + + #[test] + fn test_operate_reduce_median_str_even() { + let input = RadonArray::from(vec![ + RadonString::from("Bye world!").into(), + RadonString::from("Hello world!").into(), + ]); + let output = median(&input).unwrap_err(); + let expected_error = RadError::UnsupportedReducer { + array: input, + reducer: "RadonReducers::AverageMedian".to_string(), + }; + assert_eq!(output, expected_error); + } + + #[test] + fn test_operate_reduce_median_array() { + let array1 = RadonArray::from(vec![ + RadonInteger::from(1i128).into(), + RadonInteger::from(2i128).into(), + RadonInteger::from(3i128).into(), + ]); + let array2 = RadonArray::from(vec![ + RadonInteger::from(2i128).into(), + RadonInteger::from(5i128).into(), + RadonInteger::from(4i128).into(), + ]); + let array3 = RadonArray::from(vec![ + RadonInteger::from(1i128).into(), + RadonInteger::from(2i128).into(), + RadonInteger::from(3i128).into(), + RadonInteger::from(4i128).into(), + ]); + + let input = RadonArray::from(vec![ + array1.clone().into(), + array1.clone().into(), + array1.into(), + array2.into(), + array3.into(), + ]); + + let output = median(&input).unwrap_err(); + let expected_error = RadError::UnsupportedReducer { + array: input, + reducer: "RadonReducers::AverageMedian".to_string(), + }; + assert_eq!(output, expected_error); + } + + #[test] + fn test_median_big_number() { + let input = RadonArray::from(vec![ + RadonInteger::from(18446744073709551616).into(), + RadonInteger::from(18446744073709551616).into(), + RadonInteger::from(2).into(), + ]); + + let expected = RadonTypes::from(RadonInteger::from(18446744073709551616)); + let output = median(&input).unwrap(); + assert_eq!(output, expected); + } +} diff --git a/rad/src/reducers/mod.rs b/rad/src/reducers/mod.rs index ce3df3584..8c932b7f1 100644 --- a/rad/src/reducers/mod.rs +++ b/rad/src/reducers/mod.rs @@ -10,6 +10,7 @@ use witnet_data_structures::radon_report::ReportContext; pub mod average; pub mod deviation; +pub mod median; pub mod mode; #[derive(Debug, PartialEq, TryFromPrimitive)] @@ -57,7 +58,7 @@ pub fn reduce( RadonReducers::Mode => mode::mode(input), RadonReducers::DeviationStandard => deviation::standard(input), RadonReducers::AverageMedian => match &context.active_wips { - Some(active_wips) if active_wips.wip0017() => average::median(input), + Some(active_wips) if active_wips.wip0017() => median::median(input), _ => error(), }, _ => error(), From 0a9bc59804dcb3b660bb8552b4c5e340ecf701bc Mon Sep 17 00:00:00 2001 From: Luis Rubio Date: Tue, 31 Aug 2021 16:08:58 +0200 Subject: [PATCH 4/6] test(rad): include test to check median activation through TAPI --- rad/src/reducers/median.rs | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/rad/src/reducers/median.rs b/rad/src/reducers/median.rs index 2aef93605..2e10e3de6 100644 --- a/rad/src/reducers/median.rs +++ b/rad/src/reducers/median.rs @@ -109,8 +109,13 @@ pub fn median(input: &RadonArray) -> Result { mod tests { use super::*; - use crate::types::{float::RadonFloat, integer::RadonInteger, string::RadonString}; - use crate::RadError::ModeEmpty; + use crate::{ + current_active_wips, + operators::array::reduce, + types::{float::RadonFloat, integer::RadonInteger, string::RadonString}, + RadError::ModeEmpty, + }; + use serde_cbor::Value; #[test] fn test_operate_reduce_median_empty() { @@ -283,4 +288,31 @@ mod tests { let output = median(&input).unwrap(); assert_eq!(output, expected); } + + #[test] + fn test_reduce_average_median_tapi_activation() { + let mut active_wips = current_active_wips(); + let mut context = ReportContext::default(); + context.active_wips = Some(active_wips.clone()); + let input = &RadonArray::from(vec![ + RadonFloat::from(1f64).into(), + RadonFloat::from(2f64).into(), + RadonFloat::from(2f64).into(), + ]); + let args = &[Value::Integer(RadonReducers::AverageMedian as i128)]; + + let expected_err = RadError::UnsupportedReducer { + array: input.clone(), + reducer: "RadonReducers::AverageMedian".to_string(), + }; + let output = reduce(input, args, &mut context).unwrap_err(); + assert_eq!(output, expected_err); + + // Activate WIP-0017 + active_wips.active_wips.insert("WIP0017".to_string(), 0); + context.active_wips = Some(active_wips); + let expected = RadonTypes::from(RadonFloat::from(2f64)); + let output = reduce(input, args, &mut context).unwrap(); + assert_eq!(output, expected); + } } From 9a332f54a9335d88aaf4ce64b97eece7ffa13145 Mon Sep 17 00:00:00 2001 From: Luis Rubio Date: Wed, 1 Sep 2021 11:24:53 +0200 Subject: [PATCH 5/6] test(rad): add and rename tests for RADON reducers --- rad/src/reducers/average.rs | 58 +++++++++-------------- rad/src/reducers/deviation.rs | 22 ++++----- rad/src/reducers/median.rs | 30 ------------ rad/src/reducers/mod.rs | 87 +++++++++++++++++++++++++++++++++++ rad/src/reducers/mode.rs | 16 +++---- rad/src/types/array.rs | 71 ++++++++++++++++++++++++++++ 6 files changed, 199 insertions(+), 85 deletions(-) diff --git a/rad/src/reducers/average.rs b/rad/src/reducers/average.rs index cb5840ec9..6709920ba 100644 --- a/rad/src/reducers/average.rs +++ b/rad/src/reducers/average.rs @@ -127,32 +127,25 @@ pub fn mean(input: &RadonArray, return_policy: MeanReturnPolicy) -> Result Date: Wed, 1 Sep 2021 12:56:41 +0200 Subject: [PATCH 6/6] refactor(rad): rename ModeEmpty by EmptyArray --- rad/src/error.rs | 2 +- rad/src/filters/mode.rs | 2 +- rad/src/reducers/median.rs | 12 ++++++------ rad/src/reducers/mode.rs | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/rad/src/error.rs b/rad/src/error.rs index 3d9c181f0..03b5243cd 100644 --- a/rad/src/error.rs +++ b/rad/src/error.rs @@ -137,7 +137,7 @@ pub enum RadError { ModeTie { values: RadonArray, max_count: u16 }, /// Tried to apply mod reducer on an empty array #[fail(display = "Tried to apply mode reducer on an empty array")] - ModeEmpty, + EmptyArray, /// The given arguments are not valid for the given operator #[fail( display = "Wrong `{}::{}()` arguments: `{:?}`", diff --git a/rad/src/filters/mode.rs b/rad/src/filters/mode.rs index 34df38162..f02a6cc44 100644 --- a/rad/src/filters/mode.rs +++ b/rad/src/filters/mode.rs @@ -66,7 +66,7 @@ mod tests { #[test] fn test_filter_mode_empty() { let input = vec![]; - let expected = RadError::ModeEmpty; + let expected = RadError::EmptyArray; let mut ctx = ReportContext { stage: Stage::Tally(TallyMetaData::default()), diff --git a/rad/src/reducers/median.rs b/rad/src/reducers/median.rs index f6164e716..2104e1029 100644 --- a/rad/src/reducers/median.rs +++ b/rad/src/reducers/median.rs @@ -18,7 +18,7 @@ pub fn median(input: &RadonArray) -> Result { let value_len = value.len(); match value.first() { - None => Err(RadError::ModeEmpty), + None => Err(RadError::EmptyArray), Some(RadonTypes::Float(_)) => { // Collect non-NaN values into a vector, and sort them let mut input_not_nan: Vec> = Vec::with_capacity(value_len); @@ -44,7 +44,7 @@ pub fn median(input: &RadonArray) -> Result { if input_not_nan.is_empty() { // This can happen if all elements are NaN - Err(RadError::ModeEmpty) + Err(RadError::EmptyArray) } else if input_not_nan.len() % 2 == 1 { // Odd number of elements: take element at floor(N/2): let median_pos = input_not_nan.len() / 2; @@ -77,7 +77,7 @@ pub fn median(input: &RadonArray) -> Result { if sorted_input.is_empty() { // This is unreachable - Err(RadError::ModeEmpty) + Err(RadError::EmptyArray) } else if sorted_input.len() % 2 == 1 { // Odd number of elements: take element at floor(N/2): let median_pos = sorted_input.len() / 2; @@ -111,14 +111,14 @@ mod tests { use crate::{ types::{float::RadonFloat, integer::RadonInteger, string::RadonString}, - RadError::ModeEmpty, + RadError::EmptyArray, }; #[test] fn test_operate_reduce_median_empty() { let input = RadonArray::from(vec![]); let output = median(&input).unwrap_err(); - let expected_error = ModeEmpty; + let expected_error = EmptyArray; assert_eq!(output, expected_error); } @@ -167,7 +167,7 @@ mod tests { ]); let output = median(&input).unwrap_err(); - let expected_error = ModeEmpty; + let expected_error = EmptyArray; assert_eq!(output, expected_error); } diff --git a/rad/src/reducers/mode.rs b/rad/src/reducers/mode.rs index a2bf330b5..7b28c4f07 100644 --- a/rad/src/reducers/mode.rs +++ b/rad/src/reducers/mode.rs @@ -17,7 +17,7 @@ pub fn mode(input: &RadonArray) -> Result { let temp_counter = counter.clone(); // Compute how many times does the most frequent item appear - let max_count = temp_counter.values().max().ok_or(RadError::ModeEmpty)?; + let max_count = temp_counter.values().max().ok_or(RadError::EmptyArray)?; // Collect items that appear as many times as the one that appears the most let mode_vector: Vec = counter @@ -42,7 +42,7 @@ mod tests { use super::*; use crate::{ - error::RadError::{ModeEmpty, ModeTie}, + error::RadError::{EmptyArray, ModeTie}, types::{float::RadonFloat, integer::RadonInteger, string::RadonString}, }; @@ -129,7 +129,7 @@ mod tests { fn test_mode_empty() { let input = RadonArray::from(vec![]); let output = mode(&input).unwrap_err(); - let expected_error = ModeEmpty; + let expected_error = EmptyArray; assert_eq!(output, expected_error); }