boreholeio 0.1.0

A library for interacting with borehole.io, a subsurface data management, delivery and visualisation platform
Documentation
use std::path::PathBuf;

use super::ArrayCache;
use crate::{
    schema::{
        array_uri_reference::ArrayUriReference,
        axes::{Base, CoordinateAxis, Numeric, NumericAxis},
    },
    strided_array_file::{ArrayProxy, Statistics, StridedArrayFile},
};
use ndarray::{Array1, SliceInfo, SliceInfoElem};

// TODO: Expose this as a public `CoordinateAxis::from_coordinates` method?
fn from_coordinates(coordinates: Array1<f64>) -> CoordinateAxis {
    let proxy = ArrayProxy::F64(coordinates.view().into_dyn());
    CoordinateAxis {
        name: "".into(),
        units: None,
        bounds: [
            proxy.calc_stats(Statistics::Min).unwrap(),
            proxy.calc_stats(Statistics::Max).unwrap(),
        ],
        size: coordinates.len(),
        transform: None,
        uri: ArrayCache::insert(proxy).unwrap(),
    }
}

// Just a shortcut for a fake `CoordinateAxis` creation.
fn from_uri(uri: ArrayUriReference) -> CoordinateAxis {
    CoordinateAxis {
        name: "".into(),
        units: None,
        bounds: [0.1, 0.2],
        size: 10,
        transform: None,
        uri,
    }
}

// Verifies that `CoordinateAxis::slice()` method works properly.
#[test]
fn coordinate_axis_slicing() {
    // Create `CoordinateAxis` from an in-memory coordinates array.
    let axis = {
        let coordinates = Array1::linspace(-2.7, 3.14, 42);
        from_coordinates(coordinates)
    };
    // Slice the axis.
    let slice_info = SliceInfoElem::Slice {
        start: 0,
        end: None,
        step: 2,
    };
    let sliced_axis: NumericAxis = axis.slice(&slice_info).unwrap().try_into().unwrap();
    // Manually slice the coordinates.
    let sliced_coordinates = {
        let slice_info: &[SliceInfoElem] = &[slice_info];
        let slice_info = SliceInfo::try_from(slice_info).unwrap();
        let coordinates = axis.coordinates().unwrap();
        coordinates.slice(slice_info).to_owned()
    };
    // Verify the results.
    assert_eq!(sliced_axis.coordinates().unwrap(), sliced_coordinates);
}

// An example which demonstrates that `ArrayCache::insert()` copies the data
// to a different memory location.
#[test]
fn insertion_into_cache_copies_data_to_new_location() {
    let mut array = Array1::from_vec(vec![-2.7, 3.14, 0f64, 4f64]);
    let proxy = ArrayProxy::F64(array.view().into_dyn());

    // At this point Rust won't allow us to modify the `array`. For example,
    // attempting to write:
    // ```
    //   array[2] = 100500f64;
    // ```
    // will result in a compilation error:
    // ```
    //   cannot borrow `array` as mutable because it is also borrowed as
    //   immutable
    // ```
    // However, there are other ways to shout ourselves in a leg.

    let uri = ArrayCache::insert(proxy).unwrap();
    let proxy = ArrayCache::get(&uri).unwrap();
    array[2] = 100500f64;
    assert_ne!(array.view().into_dyn(), proxy.try_as::<f64>().unwrap());
}

/// An example that tracks the cached data's life cycle when multiple copies
/// of the `Uri` object exist.
#[test]
fn reference_counting() {
    // Create a URI to a Strided Array File which does not exist yet.
    let uri = {
        let path = std::env::current_dir().unwrap().join("1.star");
        if path.is_file() {
            std::fs::remove_file(path.clone()).unwrap();
        }
        ArrayUriReference::try_from(path.as_path()).unwrap()
    };
    assert!(!PathBuf::try_from(&uri).unwrap().exists());
    assert_eq!(ArrayCache::reference_count(&uri).unwrap(), 1);

    // Create a `CoordinateAxis` pointing to this same URI.
    let axis1 = from_uri(uri.clone());
    assert!(axis1.coordinates().is_err());
    assert_eq!(ArrayCache::reference_count(&uri).unwrap(), 2);

    // Create the file.
    {
        let array = Array1::from_vec(vec![-5f64, 20f64, 0.33]);
        let file_path = PathBuf::try_from(&uri).unwrap();
        StridedArrayFile::write(&file_path, &[ArrayProxy::F64(array.view().into_dyn())]).unwrap();
    }
    assert!(axis1.coordinates().is_ok());
    assert_eq!(ArrayCache::reference_count(&uri).unwrap(), 2);

    // Create another `CoordinateAxis` pointing to that same URI.
    let axis2 = from_uri(uri.clone());
    assert!(axis1.coordinates().unwrap() == axis2.coordinates().unwrap());
    assert_eq!(ArrayCache::reference_count(&uri).unwrap(), 3);

    // Verify that reference counting works correctly.
    std::mem::drop(uri);
    std::mem::drop(axis1);
    assert_eq!(ArrayCache::reference_count(&axis2.uri).unwrap(), 1);

    // Verify that one cannot shot themselves in a leg.
    let _proxy = ArrayCache::get(&axis2.uri).unwrap();
    std::mem::drop(axis2);
    // The following code line:
    // ```
    //   _proxy;
    // ```
    // fails to compile with the following error:
    // ```
    //   cannot move out of `axis2` because it is borrowed
    // ```
}