///|
pub struct Dual[T] {
  value : T
  tangent : T
} derive(Eq, Debug)

///|
pub fn[T] Dual::new(value : T, tangent : T) -> Dual[T] {
  { value, tangent, }
}

///|
pub fn[T : Zero] Dual::constant(value : T) -> Dual[T] {
  { value, tangent: Zero::zero(), }
}

///|
pub fn[T : One] Dual::variable(value : T) -> Dual[T] {
  { value, tangent: One::one(), }
}

///|
pub fn[T] Dual::value(self : Dual[T]) -> T {
  self.value
}

///|
pub fn[T] Dual::tangent(self : Dual[T]) -> T {
  self.tangent
}

///|
pub impl[T : Zero] Zero for Dual[T] with fn zero() -> Dual[T] {
  Dual::constant(Zero::zero())
}

///|
pub impl[T : One + Zero] One for Dual[T] with fn one() -> Dual[T] {
  Dual::constant(One::one())
}

///|
pub impl[T : Add] Add for Dual[T] with fn add(self, other) {
  Dual::new(self.value + other.value, self.tangent + other.tangent)
}

///|
pub impl[T : Sub] Sub for Dual[T] with fn sub(self, other) {
  Dual::new(self.value - other.value, self.tangent - other.tangent)
}

///|
pub impl[T : Neg] Neg for Dual[T] with fn neg(self) {
  Dual::new(-self.value, -self.tangent)
}

///|
pub impl[T : Add + Mul] Mul for Dual[T] with fn mul(self, other) {
  Dual::new(
    self.value * other.value,
    self.value * other.tangent + other.value * self.tangent,
  )
}

///|
pub impl[T : Div + Sub + Mul] Div for Dual[T] with fn div(self, other) {
  let denominator = other.value * other.value
  Dual::new(
    self.value / other.value,
    (self.tangent * other.value - self.value * other.tangent) / denominator,
  )
}

///|
pub impl[T : AddMonoid] AddMonoid for Dual[T]

///|
pub impl[T : Semiring] MulMonoid for Dual[T]

///|
pub impl[T : AddGroup] AddGroup for Dual[T]

///|
pub impl[T : Semiring] Semiring for Dual[T]

///|
pub impl[T : Ring] Ring for Dual[T]

///|
/// The canonical map ℕ -> `Dual[T]`: a constant, so the tangent is zero.
pub impl[T : FromNat + Zero] FromNat for Dual[T] with fn from_natural(value) {
  Dual::constant(FromNat::from_natural(value))
}

///|
/// The canonical map ℤ -> `Dual[T]`: a constant, so the tangent is zero.
pub impl[T : FromInteger + Zero] FromInteger for Dual[T] with fn from_integer(
  value,
) {
  Dual::constant(FromInteger::from_integer(value))
}

///|
pub impl[T : DivChecked + Sub + Mul] DivChecked for Dual[T] with fn div_checked(
  self,
  other,
  ctx,
) -> Result[Dual[T], ArithmeticError] {
  match DivChecked::div_checked(self.value, other.value, ctx) {
    Err(err) => Err(err)
    Ok(value) =>
      match
        DivChecked::div_checked(
          self.tangent * other.value - self.value * other.tangent,
          other.value * other.value,
          ctx,
        ) {
        Err(err) => Err(err)
        Ok(tangent) => Ok(Dual::new(value, tangent))
      }
  }
}

///|
pub fn[T : SqrtChecked + DivChecked + FromInteger + Mul] Dual::sqrt_checked(
  self : Dual[T],
  ctx : ArithmeticContext,
) -> Result[Dual[T], ArithmeticError] {
  match SqrtChecked::sqrt_checked(self.value, ctx) {
    Err(err) => Err(err)
    Ok(root) => {
      let two : T = lift_to(2)
      match DivChecked::div_checked(self.tangent, two * root, ctx) {
        Err(err) => Err(err)
        Ok(tangent) => Ok(Dual::new(root, tangent))
      }
    }
  }
}

///|
pub fn[T : Sqrt + FromInteger + Mul + Div] Dual::sqrt(
  self : Dual[T],
) -> Dual[T] {
  let root = Sqrt::sqrt(self.value)
  let two : T = lift_to(2)
  Dual::new(root, self.tangent / (two * root))
}

///|
pub fn[T : Exponential + Mul] Dual::exp(self : Dual[T]) -> Dual[T] {
  let value = Exponential::exp(self.value)
  Dual::new(value, self.tangent * value)
}

///|
pub fn[T : Exponential + Logarithmic + FromInteger + Mul] Dual::exp2(
  self : Dual[T],
) -> Dual[T] {
  let value = Exponential::exp2(self.value)
  let two : T = lift_to(2)
  Dual::new(value, self.tangent * value * Logarithmic::ln(two))
}

///|
pub fn[T : Logarithmic + Div] Dual::ln(self : Dual[T]) -> Dual[T] {
  Dual::new(Logarithmic::ln(self.value), self.tangent / self.value)
}

///|
pub fn[T : Logarithmic + FromInteger + Mul + Div] Dual::log2(
  self : Dual[T],
) -> Dual[T] {
  let two : T = lift_to(2)
  Dual::new(
    Logarithmic::log2(self.value),
    self.tangent / (self.value * Logarithmic::ln(two)),
  )
}

///|
pub fn[T : Logarithmic + FromInteger + Mul + Div] Dual::log10(
  self : Dual[T],
) -> Dual[T] {
  let ten : T = lift_to(10)
  Dual::new(
    Logarithmic::log10(self.value),
    self.tangent / (self.value * Logarithmic::ln(ten)),
  )
}

///|
pub fn[T : Trigonometric + Mul] Dual::sin(self : Dual[T]) -> Dual[T] {
  Dual::new(
    Trigonometric::sin(self.value),
    self.tangent * Trigonometric::cos(self.value),
  )
}

///|
pub fn[T : Trigonometric + Mul + Neg] Dual::cos(self : Dual[T]) -> Dual[T] {
  Dual::new(
    Trigonometric::cos(self.value),
    -(self.tangent * Trigonometric::sin(self.value)),
  )
}

///|
pub fn[T : Trigonometric + Mul + Div] Dual::tan(self : Dual[T]) -> Dual[T] {
  let c = Trigonometric::cos(self.value)
  Dual::new(Trigonometric::tan(self.value), self.tangent / (c * c))
}

///|
pub impl[T : Sqrt + FromInteger + Mul + Div] Sqrt for Dual[T] with fn sqrt(self) {
  self.sqrt()
}

///|
pub impl[T : SqrtChecked + DivChecked + FromInteger + Mul] SqrtChecked for Dual[
  T,
] with fn sqrt_checked(self, ctx) {
  self.sqrt_checked(ctx)
}

///|
pub impl[T : Exponential + Logarithmic + FromInteger + Mul] Exponential for Dual[
  T,
] with fn exp(self) {
  self.exp()
}

///|
pub impl[T : Exponential + Logarithmic + FromInteger + Mul] Exponential for Dual[
  T,
] with fn exp2(self) {
  self.exp2()
}

///|
pub impl[T : Logarithmic + FromInteger + Mul + Div] Logarithmic for Dual[T] with fn ln(
  self,
) {
  self.ln()
}

///|
pub impl[T : Logarithmic + FromInteger + Mul + Div] Logarithmic for Dual[T] with fn log2(
  self,
) {
  self.log2()
}

///|
pub impl[T : Logarithmic + FromInteger + Mul + Div] Logarithmic for Dual[T] with fn log10(
  self,
) {
  self.log10()
}

///|
pub impl[T : Trigonometric + Mul + Neg + Div] Trigonometric for Dual[T] with fn sin(
  self,
) {
  self.sin()
}

///|
pub impl[T : Trigonometric + Mul + Neg + Div] Trigonometric for Dual[T] with fn cos(
  self,
) {
  self.cos()
}

///|
pub impl[T : Trigonometric + Mul + Neg + Div] Trigonometric for Dual[T] with fn tan(
  self,
) {
  self.tan()
}

///|
pub impl[T : Constants + Zero] Constants for Dual[T] with fn pi() {
  Dual::constant(Constants::pi())
}

///|
pub impl[T : Constants + Zero] Constants for Dual[T] with fn tau() {
  Dual::constant(Constants::tau())
}

///|
pub impl[T : Constants + Zero] Constants for Dual[T] with fn e() {
  Dual::constant(Constants::e())
}