1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
use smartnoise_validator::errors::*;

use crate::NodeArguments;
use smartnoise_validator::base::{Value, Array, ReleaseNode, IndexKey};
use smartnoise_validator::utilities::take_argument;
use crate::components::Evaluable;
use ndarray::ArrayD;
use smartnoise_validator::{proto, Integer};
use indexmap::map::IndexMap;


impl Evaluable for proto::Reshape {
    fn evaluate(&self, _privacy_definition: &Option<proto::PrivacyDefinition>, mut arguments: NodeArguments) -> Result<ReleaseNode> {
        let layout = match self.layout.to_lowercase().as_str() {
            "row" => Layout::Row,
            "column" => Layout::Column,
            _ => return Err("layout: unrecognized format. Must be either row or column".into())
        };

        match take_argument(&mut arguments, "data")?.array()? {
            Array::Bool(data) => {
                let mut reshaped = reshape(&data, self.symmetric, &layout, &self.shape)?;
                match reshaped.len() {
                    0 => Err("at least one record is required to reshape".into()),
                    1 => Ok(reshaped.remove(0).into()),
                    _ => Ok(Value::Partitions(reshaped.into_iter().enumerate()
                        .map(|(idx, data)|
                            (IndexKey::from(idx as Integer), data.into()))
                        .collect::<IndexMap<IndexKey, Value>>()))
                }
            }
            Array::Int(data) => {
                let mut reshaped = reshape(&data, self.symmetric, &layout, &self.shape)?;
                match reshaped.len() {
                    0 => Err("at least one record is required to reshape".into()),
                    1 => Ok(reshaped.remove(0).into()),
                    _ => Ok(Value::Partitions(reshaped.into_iter().enumerate()
                        .map(|(idx, data)|
                            (IndexKey::from(idx as Integer), data.into()))
                        .collect::<IndexMap<IndexKey, Value>>()))
                }
            }
            Array::Float(data) => {
                let mut reshaped = reshape(&data, self.symmetric, &layout, &self.shape)?;
                match reshaped.len() {
                    0 => Err("at least one record is required to reshape".into()),
                    1 => Ok(reshaped.remove(0).into()),
                    _ => Ok(Value::Partitions(reshaped.into_iter().enumerate()
                        .map(|(idx, data)|
                            (IndexKey::from(idx as Integer), data.into()))
                        .collect::<IndexMap<IndexKey, Value>>()))
                }
            }
            Array::Str(data) => {
                let mut reshaped = reshape(&data, self.symmetric, &layout, &self.shape)?;
                match reshaped.len() {
                    0 => Err("at least one record is required to reshape".into()),
                    1 => Ok(reshaped.remove(0).into()),
                    _ => Ok(Value::Partitions(reshaped.into_iter().enumerate()
                        .map(|(idx, data)|
                            (IndexKey::from(idx as Integer), data.into()))
                        .collect::<IndexMap<IndexKey, Value>>()))
                }
            }
        }.map(ReleaseNode::new)
    }
}

#[derive(PartialEq)]
pub enum Layout {
    Row,
    Column,
}

/// Reshape an upper triangular matrix or dense matrix represented in one row, to a square matrix.
/// One matrix is returned per row.
///
/// # Arguments
/// * `data` - Data for which you want a count.
/// * `symmetric` - indicates if elements of data come from an upper triangular matrix, not a dense matrix
/// * `layout` - indicates data resides in row major or column major order
/// * `shape` - shape of output matrix
///
/// # Return
/// A vector of reshaped matrices, one matrix per row.
///
/// # Example
/// ```
/// use ndarray::{ArrayD, arr1, arr2};
/// use smartnoise_runtime::components::reshape::{reshape, Layout};
/// let data = arr2(&[ [false, false, true] ]).into_dyn();
/// let n = reshape(&data, true, &Layout::Row, &vec![2, 2]).unwrap();
/// assert_eq!(n[0], arr2(&[ [false, false], [false, true] ]).into_dyn());
/// ```
pub fn reshape<T: Clone>(data: &ArrayD<T>, symmetric: bool, layout: &Layout, shape: &[u32]) -> Result<Vec<ArrayD<T>>> {
    data.genrows().into_iter()
        .map(|row| {
            if symmetric {
                let row = row.to_vec();
                let num_rows = match shape.len() {
                    1 => shape[0],
                    2 => {
                        if shape[0] != shape[1] {
                            return Err("the width and height must match to form a triangular matrix".into());
                        }
                        shape[0]
                    }
                    _ => return Err("shape must be 0 or 1-dimensional to form a triangular matrix".into())
                };

                if row.len() != (num_rows * (num_rows + 1) / 2) as usize {
                    return Err("invalid number of elements in row for reshaping into symmetric matrix".into());
                }

                let full = match layout {
                    Layout::Row =>
                        (0..num_rows).map(|i|
                            (0..num_rows).map(|j| if i <= j {
                                // upper triangular (full matrix index, less the arithmetic progression)
                                row[(i * num_rows + j - (i + 1) * i / 2) as usize].clone()
                            } else {
                                // lower triangular (symmetric with upper triangle)
                                row[(j * num_rows + i - (j + 1) * j / 2) as usize].clone()
                            }).collect::<Vec<T>>()).flatten().collect::<Vec<T>>(),
                    Layout::Column => return Err("not implemented".into())
                };

                Ok(ndarray::Array::from_shape_vec((num_rows as usize, num_rows as usize), full)?.into_dyn())
            } else {
                if &Layout::Column == layout {
                    return Err("reshaping for dense columnar memory layouts is not supported".into());
                }
                let shape = shape.iter().map(|v| *v as usize).collect::<Vec<usize>>();
                match ndarray::ArrayD::from_shape_vec(shape, row.to_vec()) {
                    Ok(arr) => Ok(arr),
                    Err(_) => Err("reshape has incorrect size".into())
                }
            }
        }).collect::<Result<Vec<ArrayD<T>>>>()
}

#[cfg(test)]
mod test_reshape {
    use ndarray::arr2;
    use crate::components::reshape::{reshape, Layout};

    #[test]
    fn test_reshape_symmetric_2x2() {
        let data = arr2(&[[false, false, true]]).into_dyn();
        let n = reshape(&data, true, &Layout::Row, &vec![2, 2]).unwrap();
        assert!(n[0] == arr2(&[[false, false], [false, true]]).into_dyn());
    }

    #[test]
    fn test_reshape_symmetric_4x4() {
        let data = arr2(&[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]]).into_dyn();
        let n = reshape(&data, true, &Layout::Row, &vec![4, 4]).unwrap();
        assert!(n[0] == arr2(&[[0, 1, 2, 3], [1, 4, 5, 6], [2, 5, 7, 8], [3, 6, 8, 9]]).into_dyn());
    }
}