///|
fn shape_size(shape : Array[Int]) -> Int {
  let mut size = 1
  for dim in shape {
    if dim <= 0 {
      abort("tensor dimensions must be positive")
    }
    size *= dim
  }
  size
}

///|
fn copy_ints(xs : Array[Int]) -> Array[Int] {
  xs.copy()
}

///|
fn copy_doubles(xs : Array[Double]) -> Array[Double] {
  xs.copy()
}

///|
fn broadcast_shape(a : Array[Int], b : Array[Int]) -> Array[Int] {
  let rank_a = a.length()
  let rank_b = b.length()
  let rank = if rank_a > rank_b { rank_a } else { rank_b }
  let out = Array::make(rank, 1)
  for i in 0.. db { da } else { db }
    } else {
      abort("tensor shapes are not broadcast-compatible")
    }
  }
  out
}

///|
fn unravel_index(linear : Int, shape : Array[Int]) -> Array[Int] {
  let mut remaining = linear
  let idx = Array::make(shape.length(), 0)
  let mut axis = shape.length() - 1
  while axis >= 0 {
    let dim = shape[axis]
    idx[axis] = remaining % dim
    remaining = remaining / dim
    if axis == 0 {
      break
    }
    axis -= 1
  }
  idx
}

///|
fn ravel_index(idx : Array[Int], shape : Array[Int]) -> Int {
  let mut linear = 0
  for axis in 0.. Int {
  if in_shape.length() == 0 {
    0
  } else {
    let offset = out_idx.length() - in_shape.length()
    let in_idx = Array::make(in_shape.length(), 0)
    for axis in 0.. Array[Double] {
  let out = Array::make(shape_size(to_shape), 0.0)
  for i in 0..