///|
pub fn Tensor::exp(self : Tensor) -> Tensor {
  let out = self.data.map(x => @math.exp(x))
  unary_tensor(self, out, self.shape, fn() {
    ExpBackward::{ input_data: self.data }
  })
}

///|
pub fn Tensor::log(self : Tensor) -> Tensor {
  let out = self.data.map(x => @math.ln(x))
  unary_tensor(self, out, self.shape, fn() {
    LogBackward::{ input_data: self.data }
  })
}

///|
pub fn Tensor::relu(self : Tensor) -> Tensor {
  let out = self.data.map(x => if x > 0.0 { x } else { 0.0 })
  unary_tensor(self, out, self.shape, fn() {
    ReluBackward::{ input_data: self.data }
  })
}

///|
const INV_SQRT_2 : Double = 0.7071067811865475

///|
const INV_SQRT_2PI : Double = 0.3989422804014327

///|
fn erf_approx(x : Double) -> Double {
  let sign = if x < 0.0 { -1.0 } else { 1.0 }
  let ax = x.abs()
  let t = 1.0 / (1.0 + 0.3275911 * ax)
  let y = 1.0 -
    (
      (((1.061405429 * t - 1.453152027) * t + 1.421413741) * t - 0.284496736) *
      t +
      0.254829592
    ) *
    t *
    @math.exp(-ax * ax)
  sign * y
}

///|
fn normal_cdf(x : Double) -> Double {
  0.5 * (1.0 + erf_approx(x * INV_SQRT_2))
}

///|
fn normal_pdf(x : Double) -> Double {
  INV_SQRT_2PI * @math.exp(-0.5 * x * x)
}

///|
fn gelu_value(x : Double) -> Double {
  x * normal_cdf(x)
}

///|
pub fn Tensor::gelu(self : Tensor) -> Tensor {
  let out = self.data.map(x => gelu_value(x))
  unary_tensor(self, out, self.shape, fn() {
    GeluBackward::{ input_data: self.data }
  })
}