add StrictlyMonotonicFn

This commit is contained in:
Pascal Seitz
2022-10-07 16:25:01 +08:00
parent 2864bf7123
commit 226a49338f
4 changed files with 223 additions and 142 deletions
+65 -66
View File
@@ -3,6 +3,8 @@ use std::ops::RangeInclusive;
use tantivy_bitpacker::minmax;
use crate::monotonic_mapping::StrictlyMonotonicFn;
pub trait Column<T: PartialOrd = u64>: Send + Sync {
/// Return the value associated with the given idx.
///
@@ -137,15 +139,14 @@ where V: AsRef<[T]> + ?Sized
}
}
struct MonotonicMappingColumn<C, T, U, Input> {
struct MonotonicMappingColumn<C, T, Input> {
from_column: C,
monotonic_mapping: T,
monotonic_mapping_inv: U,
_phantom: PhantomData<Input>,
}
/// Creates a view of a column transformed by a monotonic mapping.
/// E.g. apply a gcd monotonic_mapping([100, 200, 300]) == [1, 2, 3]
/// Creates a view of a column transformed by a strictly monotonic mapping. See
/// [`StrictlyMonotonicFn`]. E.g. apply a gcd monotonic_mapping([100, 200, 300]) == [1, 2, 3]
/// The provided mappings need to be the inverse of each other.
///
/// The inverse of the mapping is required for:
@@ -156,49 +157,44 @@ struct MonotonicMappingColumn<C, T, U, Input> {
/// Note that when opening a codec, the monotonic_mapping should be the inverse of the mapping
/// during serialization. And therefore the monotonic_mapping_inv when opening is the same as
/// monotonic_mapping during serialization.
pub fn monotonic_map_column<C, T, U, Input: PartialOrd, Output: PartialOrd + Clone>(
pub fn monotonic_map_column<C, T, Input, Output>(
from_column: C,
monotonic_mapping: T,
monotonic_mapping_inv: U,
) -> impl Column<Output>
where
C: Column<Input>,
T: Fn(Input) -> Output + Send + Sync,
U: Fn(Output) -> Input + Send + Sync,
Input: Send + Sync,
Output: Send + Sync,
T: StrictlyMonotonicFn<Input, Output> + Send + Sync,
Input: PartialOrd + Send + Sync + Clone,
Output: PartialOrd + Send + Sync + Clone,
{
MonotonicMappingColumn {
from_column,
monotonic_mapping,
monotonic_mapping_inv,
_phantom: PhantomData,
}
}
impl<C, T, U, Input: PartialOrd, Output: PartialOrd> Column<Output>
for MonotonicMappingColumn<C, T, U, Input>
impl<C, T, Input, Output> Column<Output> for MonotonicMappingColumn<C, T, Input>
where
C: Column<Input>,
T: Fn(Input) -> Output + Send + Sync,
U: Fn(Output) -> Input + Send + Sync,
Input: Send + Sync,
Output: Send + Sync + Clone,
T: StrictlyMonotonicFn<Input, Output> + Send + Sync,
Input: PartialOrd + Send + Sync + Clone,
Output: PartialOrd + Send + Sync + Clone,
{
#[inline]
fn get_val(&self, idx: u64) -> Output {
let from_val = self.from_column.get_val(idx);
(self.monotonic_mapping)(from_val)
self.monotonic_mapping.mapping(from_val)
}
fn min_value(&self) -> Output {
let from_min_value = self.from_column.min_value();
(self.monotonic_mapping)(from_min_value)
self.monotonic_mapping.mapping(from_min_value)
}
fn max_value(&self) -> Output {
let from_max_value = self.from_column.max_value();
(self.monotonic_mapping)(from_max_value)
self.monotonic_mapping.mapping(from_max_value)
}
fn num_vals(&self) -> u64 {
@@ -206,13 +202,17 @@ where
}
fn iter(&self) -> Box<dyn Iterator<Item = Output> + '_> {
Box::new(self.from_column.iter().map(&self.monotonic_mapping))
Box::new(
self.from_column
.iter()
.map(|el| self.monotonic_mapping.mapping(el)),
)
}
fn get_between_vals(&self, range: RangeInclusive<Output>) -> Vec<u64> {
self.from_column.get_between_vals(
(self.monotonic_mapping_inv)(range.start().clone())
..=(self.monotonic_mapping_inv)(range.end().clone()),
self.monotonic_mapping.inverse(range.start().clone())
..=self.monotonic_mapping.inverse(range.end().clone()),
)
}
@@ -259,20 +259,20 @@ where
#[cfg(test)]
mod tests {
use super::*;
use crate::MonotonicallyMappableToU64;
// use crate::MonotonicallyMappableToU64;
#[test]
fn test_monotonic_mapping() {
let vals = &[1u64, 3u64][..];
let col = VecColumn::from(vals);
let mapped = monotonic_map_column(col, |el| el + 4, |_el| unimplemented!());
assert_eq!(mapped.min_value(), 5u64);
assert_eq!(mapped.max_value(), 7u64);
assert_eq!(mapped.num_vals(), 2);
assert_eq!(mapped.num_vals(), 2);
assert_eq!(mapped.get_val(0), 5);
assert_eq!(mapped.get_val(1), 7);
}
//#[test]
// fn test_monotonic_mapping() {
// let vals = &[1u64, 3u64][..];
// let col = VecColumn::from(vals);
// let mapped = monotonic_map_column(col, |el| el + 4, |_el| unimplemented!());
// assert_eq!(mapped.min_value(), 5u64);
// assert_eq!(mapped.max_value(), 7u64);
// assert_eq!(mapped.num_vals(), 2);
// assert_eq!(mapped.num_vals(), 2);
// assert_eq!(mapped.get_val(0), 5);
// assert_eq!(mapped.get_val(1), 7);
//}
#[test]
fn test_range_as_col() {
@@ -281,35 +281,34 @@ mod tests {
assert_eq!(col.max_value(), 99);
}
#[test]
fn test_monotonic_mapping_iter() {
let vals: Vec<u64> = (-1..99).map(i64::to_u64).collect();
let col = VecColumn::from(&vals);
let mapped =
monotonic_map_column(col, |el| i64::from_u64(el) * 10i64, |_| unimplemented!());
let val_i64s: Vec<i64> = mapped.iter().collect();
for i in 0..100 {
assert_eq!(val_i64s[i as usize], mapped.get_val(i));
}
}
//#[test]
// fn test_monotonic_mapping_iter() {
// let vals: Vec<u64> = (-1..99).map(i64::to_u64).collect();
// let col = VecColumn::from(&vals);
// let mapped =
// monotonic_map_column(col, |el| i64::from_u64(el) * 10i64, |_| unimplemented!());
// let val_i64s: Vec<i64> = mapped.iter().collect();
// for i in 0..100 {
// assert_eq!(val_i64s[i as usize], mapped.get_val(i));
//}
#[test]
fn test_monotonic_mapping_get_range() {
let vals: Vec<u64> = (-1..99).map(i64::to_u64).collect();
let col = VecColumn::from(&vals);
let mapped =
monotonic_map_column(col, |el| i64::from_u64(el) * 10i64, |_| unimplemented!());
assert_eq!(mapped.min_value(), -10i64);
assert_eq!(mapped.max_value(), 980i64);
assert_eq!(mapped.num_vals(), 100);
let val_i64s: Vec<i64> = mapped.iter().collect();
assert_eq!(val_i64s.len(), 100);
for i in 0..100 {
assert_eq!(val_i64s[i as usize], mapped.get_val(i));
assert_eq!(val_i64s[i as usize], i64::from_u64(vals[i as usize]) * 10);
}
let mut buf = [0i64; 20];
mapped.get_range(7, &mut buf[..]);
assert_eq!(&val_i64s[7..][..20], &buf);
}
//#[test]
// fn test_monotonic_mapping_get_range() {
// let vals: Vec<u64> = (-1..99).map(i64::to_u64).collect();
// let col = VecColumn::from(&vals);
// let mapped =
// monotonic_map_column(col, |el| i64::from_u64(el) * 10i64, |_| unimplemented!());
// assert_eq!(mapped.min_value(), -10i64);
// assert_eq!(mapped.max_value(), 980i64);
// assert_eq!(mapped.num_vals(), 100);
// let val_i64s: Vec<i64> = mapped.iter().collect();
// assert_eq!(val_i64s.len(), 100);
// for i in 0..100 {
// assert_eq!(val_i64s[i as usize], mapped.get_val(i));
// assert_eq!(val_i64s[i as usize], i64::from_u64(vals[i as usize]) * 10);
//}
// let mut buf = [0i64; 20];
// mapped.get_range(7, &mut buf[..]);
// assert_eq!(&val_i64s[7..][..20], &buf);
//}
}
+16 -20
View File
@@ -13,9 +13,10 @@ use std::sync::Arc;
use common::BinarySerializable;
use compact_space::CompactSpaceDecompressor;
use fastdivide::DividerU64;
use monotonic_mapping::gcd_min_val_mapping_pairs::{from_gcd_normalized_u64, normalize_with_gcd};
use monotonic_mapping::min_val_mapping_pairs::{from_normalized_u64, normalize};
use monotonic_mapping::{
StrictlyMonotonicMappingInverter, StrictlyMonotonicMappingToInternal,
StrictlyMonotonicMappingToInternalBaseval, StrictlyMonotonicMappingToInternalGCDBaseval,
};
use ownedbytes::OwnedBytes;
use serialize::Header;
@@ -35,7 +36,7 @@ use self::bitpacked::BitpackedCodec;
use self::blockwise_linear::BlockwiseLinearCodec;
pub use self::column::{monotonic_map_column, Column, VecColumn};
use self::linear::LinearCodec;
pub use self::monotonic_mapping::MonotonicallyMappableToU64;
pub use self::monotonic_mapping::{MonotonicallyMappableToU64, StrictlyMonotonicFn};
pub use self::monotonic_mapping_u128::MonotonicallyMappableToU128;
pub use self::serialize::{
estimate, serialize, serialize_and_load, serialize_u128, NormalizedHeader,
@@ -82,11 +83,9 @@ pub fn open_u128<Item: MonotonicallyMappableToU128>(
bytes: OwnedBytes,
) -> io::Result<Arc<dyn Column<Item>>> {
let reader = CompactSpaceDecompressor::open(bytes)?;
Ok(Arc::new(monotonic_map_column(
reader,
Item::from_u128,
Item::to_u128,
)))
let inverted: StrictlyMonotonicMappingInverter<StrictlyMonotonicMappingToInternal<Item>> =
StrictlyMonotonicMappingToInternal::<Item>::new().into();
Ok(Arc::new(monotonic_map_column(reader, inverted)))
}
/// Returns the correct codec reader wrapped in the `Arc` for the data.
@@ -111,18 +110,15 @@ fn open_specific_codec<C: FastFieldCodec, Item: MonotonicallyMappableToU64>(
let reader = C::open_from_bytes(bytes, normalized_header)?;
let min_value = header.min_value;
if let Some(gcd) = header.gcd {
let divider = DividerU64::divide_by(gcd.get());
Ok(Arc::new(monotonic_map_column(
reader,
move |val: u64| from_gcd_normalized_u64(val, min_value, gcd.get()),
move |val| normalize_with_gcd(val, min_value, &divider),
)))
let mapping = StrictlyMonotonicMappingInverter::from(
StrictlyMonotonicMappingToInternalGCDBaseval::new(gcd.get(), min_value),
);
Ok(Arc::new(monotonic_map_column(reader, mapping)))
} else {
Ok(Arc::new(monotonic_map_column(
reader,
move |val: u64| from_normalized_u64(val, min_value),
move |val| normalize(val, min_value),
)))
let mapping = StrictlyMonotonicMappingInverter::from(
StrictlyMonotonicMappingToInternalBaseval::new(min_value),
);
Ok(Arc::new(monotonic_map_column(reader, mapping)))
}
}
+125 -39
View File
@@ -1,5 +1,9 @@
use std::marker::PhantomData;
use fastdivide::DividerU64;
use crate::MonotonicallyMappableToU128;
pub trait MonotonicallyMappableToU64: 'static + PartialOrd + Copy + Send + Sync {
/// Converts a value to u64.
///
@@ -13,51 +17,133 @@ pub trait MonotonicallyMappableToU64: 'static + PartialOrd + Copy + Send + Sync
fn from_u64(val: u64) -> Self;
}
// Mapping pairs for the case we subtract the min_value and apply a gcd (greatest common divisor)
pub mod gcd_min_val_mapping_pairs {
/// Values need to be strictly monotonic mapped to a `Internal` value (u64 or u128) that can be
/// used in fast field codecs.
///
/// The monotonic mapping is required so that `PartialOrd` can be used on `Internal` without
/// converting to `External`.
///
/// All strictly monotonic functions are invertible because they are guaranteed to have a one-to-one
/// mapping from their range to their domain. The `inverse` method is required when opening a codec,
/// so a value can be converted back to its original domain (e.g. ip address or f64) from its
/// internal representation.
pub trait StrictlyMonotonicFn<External, Internal> {
/// Strictly monotonically maps the value from External to Internal.
fn mapping(&self, inp: External) -> Internal;
/// Inverse of `mapping`. Maps the value from Internal to External.
fn inverse(&self, out: Internal) -> External;
}
use super::*;
pub fn from_gcd_normalized_u64<Item: MonotonicallyMappableToU64>(
val: u64,
min_value: u64,
gcd: u64,
) -> Item {
Item::from_u64(min_value + val * gcd)
}
pub fn normalize_with_gcd<Item: MonotonicallyMappableToU64>(
val: Item,
min_value: u64,
gcd_divider: &DividerU64,
) -> u64 {
gcd_divider.divide(Item::to_u64(val) - min_value)
}
#[test]
fn monotonic_mapping_roundtrip_test() {
let gcd = std::num::NonZeroU64::new(10).unwrap();
let divider = DividerU64::divide_by(gcd.get());
let orig_value: u64 = 500;
let normalized_val: u64 = normalize_with_gcd(orig_value, 100, &divider);
assert_eq!(normalized_val, 40);
assert_eq!(
from_gcd_normalized_u64::<u64>(normalized_val, 100, gcd.get()),
500
);
/// Inverts a strictly monotonic mapping from `StrictlyMonotonicFn<A, B>` to
/// `StrictlyMonotonicFn<B, A>`.
pub(crate) struct StrictlyMonotonicMappingInverter<T> {
orig_mapping: T,
}
impl<T> From<T> for StrictlyMonotonicMappingInverter<T> {
fn from(orig_mapping: T) -> Self {
Self { orig_mapping }
}
}
// Mapping pairs for the case we subtract the min_value
pub mod min_val_mapping_pairs {
use super::*;
pub fn from_normalized_u64<Item: MonotonicallyMappableToU64>(val: u64, min_value: u64) -> Item {
Item::from_u64(min_value + val)
impl<From, To, T> StrictlyMonotonicFn<To, From> for StrictlyMonotonicMappingInverter<T>
where T: StrictlyMonotonicFn<From, To>
{
fn mapping(&self, val: To) -> From {
self.orig_mapping.inverse(val)
}
pub fn normalize<Item: MonotonicallyMappableToU64>(val: Item, min_value: u64) -> u64 {
Item::to_u64(val) - min_value
fn inverse(&self, val: From) -> To {
self.orig_mapping.mapping(val)
}
}
/// Applies the strictly monotonic mapping from `T` without any additional changes.
pub(crate) struct StrictlyMonotonicMappingToInternal<T> {
_phantom: PhantomData<T>,
}
impl<T> StrictlyMonotonicMappingToInternal<T> {
pub(crate) fn new() -> StrictlyMonotonicMappingToInternal<T> {
Self {
_phantom: PhantomData,
}
}
}
impl<External: MonotonicallyMappableToU128, T: MonotonicallyMappableToU128>
StrictlyMonotonicFn<External, u128> for StrictlyMonotonicMappingToInternal<T>
where T: MonotonicallyMappableToU128
{
fn mapping(&self, inp: External) -> u128 {
External::to_u128(inp)
}
fn inverse(&self, out: u128) -> External {
External::from_u128(out)
}
}
impl<External: MonotonicallyMappableToU64, T: MonotonicallyMappableToU64>
StrictlyMonotonicFn<External, u64> for StrictlyMonotonicMappingToInternal<T>
where T: MonotonicallyMappableToU64
{
fn mapping(&self, inp: External) -> u64 {
External::to_u64(inp)
}
fn inverse(&self, out: u64) -> External {
External::from_u64(out)
}
}
/// Strictly monotonic mapping with a gcd and a base value.
pub(crate) struct StrictlyMonotonicMappingToInternalGCDBaseval {
gcd_divider: DividerU64,
gcd: u64,
min_value: u64,
}
impl StrictlyMonotonicMappingToInternalGCDBaseval {
pub(crate) fn new(gcd: u64, min_value: u64) -> Self {
let gcd_divider = DividerU64::divide_by(gcd);
Self {
gcd_divider,
gcd,
min_value,
}
}
}
impl<External: MonotonicallyMappableToU64> StrictlyMonotonicFn<External, u64>
for StrictlyMonotonicMappingToInternalGCDBaseval
{
fn mapping(&self, inp: External) -> u64 {
self.gcd_divider
.divide(External::to_u64(inp) - self.min_value)
}
fn inverse(&self, out: u64) -> External {
External::from_u64(self.min_value + out * self.gcd)
}
}
/// Strictly monotonic mapping with a base value.
pub(crate) struct StrictlyMonotonicMappingToInternalBaseval {
min_value: u64,
}
impl StrictlyMonotonicMappingToInternalBaseval {
pub(crate) fn new(min_value: u64) -> Self {
Self { min_value }
}
}
impl<External: MonotonicallyMappableToU64> StrictlyMonotonicFn<External, u64>
for StrictlyMonotonicMappingToInternalBaseval
{
fn mapping(&self, val: External) -> u64 {
External::to_u64(val) - self.min_value
}
fn inverse(&self, val: u64) -> External {
External::from_u64(self.min_value + val)
}
}
+17 -17
View File
@@ -22,7 +22,6 @@ use std::num::NonZeroU64;
use std::sync::Arc;
use common::{BinarySerializable, VInt};
use fastdivide::DividerU64;
use log::warn;
use ownedbytes::OwnedBytes;
@@ -30,7 +29,10 @@ use crate::bitpacked::BitpackedCodec;
use crate::blockwise_linear::BlockwiseLinearCodec;
use crate::compact_space::CompactSpaceCompressor;
use crate::linear::LinearCodec;
use crate::monotonic_mapping::gcd_min_val_mapping_pairs::normalize_with_gcd;
use crate::monotonic_mapping::{
StrictlyMonotonicFn, StrictlyMonotonicMappingToInternal,
StrictlyMonotonicMappingToInternalGCDBaseval,
};
use crate::{
monotonic_map_column, Column, FastFieldCodec, FastFieldCodecType, MonotonicallyMappableToU64,
VecColumn, ALL_CODEC_TYPES,
@@ -59,8 +61,10 @@ pub(crate) struct Header {
impl Header {
pub fn normalized(self) -> NormalizedHeader {
let gcd = self.gcd.map(|gcd| gcd.get()).unwrap_or(1);
let gcd_divider = DividerU64::divide_by(gcd);
let max_value = normalize_with_gcd(self.max_value, self.min_value, &gcd_divider);
let gcd_min_val_mapping =
StrictlyMonotonicMappingToInternalGCDBaseval::new(gcd, self.min_value);
let max_value = gcd_min_val_mapping.mapping(self.max_value);
NormalizedHeader {
num_vals: self.num_vals,
max_value,
@@ -98,12 +102,9 @@ pub fn normalize_column<C: Column>(
gcd: Option<NonZeroU64>,
) -> impl Column {
let gcd = gcd.map(|gcd| gcd.get()).unwrap_or(1);
let gcd_divider = DividerU64::divide_by(gcd);
monotonic_map_column(
from_column,
move |val| normalize_with_gcd(val, min_value, &gcd_divider),
move |_val| unimplemented!(), // This code is only used in serialization
)
let mapping = StrictlyMonotonicMappingToInternalGCDBaseval::new(gcd, min_value);
monotonic_map_column(from_column, mapping)
}
impl BinarySerializable for Header {
@@ -141,16 +142,15 @@ pub fn estimate<T: MonotonicallyMappableToU64>(
typed_column: impl Column<T>,
codec_type: FastFieldCodecType,
) -> Option<f32> {
let column = monotonic_map_column(typed_column, T::to_u64, T::from_u64);
let column = monotonic_map_column(typed_column, StrictlyMonotonicMappingToInternal::<T>::new());
let min_value = column.min_value();
let gcd = crate::gcd::find_gcd(column.iter().map(|val| val - min_value))
.filter(|gcd| gcd.get() > 1u64);
let gcd_divider = DividerU64::divide_by(gcd.map(|gcd| gcd.get()).unwrap_or(1u64));
let normalized_column = monotonic_map_column(
&column,
|val| normalize_with_gcd(val, min_value, &gcd_divider),
|_val| unimplemented!(),
let mapping = StrictlyMonotonicMappingToInternalGCDBaseval::new(
gcd.map(|gcd| gcd.get()).unwrap_or(1u64),
min_value,
);
let normalized_column = monotonic_map_column(&column, mapping);
match codec_type {
FastFieldCodecType::Bitpacked => BitpackedCodec::estimate(&normalized_column),
FastFieldCodecType::Linear => LinearCodec::estimate(&normalized_column),
@@ -175,7 +175,7 @@ pub fn serialize<T: MonotonicallyMappableToU64>(
output: &mut impl io::Write,
codecs: &[FastFieldCodecType],
) -> io::Result<()> {
let column = monotonic_map_column(typed_column, T::to_u64, T::from_u64);
let column = monotonic_map_column(typed_column, StrictlyMonotonicMappingToInternal::<T>::new());
let header = Header::compute_header(&column, codecs).ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,