boreholeio 0.1.0

A library for interacting with borehole.io, a subsurface data management, delivery and visualisation platform
Documentation
use super::{utils::slice_view1, Base, ChannelAxis, Error, Numeric, NumericAxis, OrderedNumeric};
use ndarray::{Array1, SliceInfoElem};

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub struct LogarithmicAxis {
    pub extents: [f64; 2],
    pub size: usize,
    pub name: String,
    pub units: Option<String>,
}

impl Base for LogarithmicAxis {
    fn size(&self) -> Result<usize, Error> {
        Ok(self.size)
    }

    fn slice(&self, slice_info: &SliceInfoElem) -> Result<ChannelAxis, Error> {
        if matches!(slice_info, SliceInfoElem::NewAxis) {
            return Err("SliceInfoElem::NewAxis variant is not supported".into());
        }

        let coordinates = self.coordinates()?;
        let slice = slice_view1(coordinates.view(), slice_info)?;

        if slice.is_empty() {
            return Err("LogarithmicAxis cannot be empty".into());
        }

        Ok(Self {
            extents: [*slice.first().unwrap(), *slice.last().unwrap()],
            size: slice.len(),
            name: self.name.clone(),
            units: self.units.clone(),
        }
        .into())
    }
}

impl Numeric for LogarithmicAxis {
    fn name(&self) -> &str {
        &self.name
    }

    fn units(&self) -> &Option<String> {
        &self.units
    }

    fn coordinates(&self) -> Result<Array1<f64>, Error> {
        if self.size()? == 0 {
            return Err("LogarithmicAxis cannot be empty".into());
        }
        if self.extents[0] <= 0f64 || self.extents[1] <= 0f64 {
            return Err("LogarithmicAxis extents must be non-negative".into());
        }

        if self.size()? == 1 {
            if self.extents[0] == self.extents[1] {
                return Ok(Array1::from_vec(vec![self.extents[0]]));
            } else {
                return Err("LogarithmicAxis of size 1 must have identical extents".into());
            }
        }

        let base = (self.extents[1] / self.extents[0]).powf(1f64 / ((self.size()? as f64) - 1f64));
        let coordinates = self.extents()[0]
            * Array1::<f64>::logspace(base, 0f64, self.size()? as f64 - 1f64, self.size()?);
        Ok(coordinates)
    }

    fn make_monotonic(&self) -> Result<Vec<NumericAxis>, Error> {
        todo!()
    }
}

impl OrderedNumeric for LogarithmicAxis {
    fn extents(&self) -> [f64; 2] {
        self.extents
    }
}

#[cfg(test)]
mod tests {
    use super::{LogarithmicAxis, Numeric};

    #[test]
    fn coordinates() {
        let coordinates = LogarithmicAxis {
            name: "".into(),
            units: None,
            extents: [3.14f64, 3140f64],
            size: 4,
        }
        .coordinates()
        .unwrap();

        let eps = 1e-6;
        for (ours, gt) in coordinates.iter().zip(vec![3.14, 31.4, 314f64, 3140f64]) {
            assert!((ours - gt).abs() < eps);
        }
    }
}