All Downloads are FREE. Search and download functionalities are using the official Maven repository.

spire.math.Interval.scala Maven / Gradle / Ivy

The newest version!
package spire.math

import spire.algebra._
import spire.implicits._

import scala.{specialized => spec}

/**
 * A Bound represents one side of an interval; it is parameterized on
 * T, the ordered type covered by the interval.
 *
 * Bound (by itself) does not imply anything about being an upper and
 * lower bound, or necessarily having a value (it may represent a
 * limit like +infinity).
 */
sealed trait Bound[@spec(Int, Long, Double) T] {
  // bounds require T to have an Order[T].
  implicit def order: Order[T]

  // convert this bound to an upper/lower bound
  def toUpper: Upper[T]
  def toLower: Lower[T]

  // compare to other bounds/points
  def compare(rhs: Bound[T]): Int
  def comparePt(t: T): Int

  // compare bounds
  def <(rhs: Bound[T]) = compare(rhs) < 0
  def <=(rhs: Bound[T]) = compare(rhs) < 1
  def >(rhs: Bound[T]) = compare(rhs) > 0
  def >=(rhs: Bound[T]) = compare(rhs) > -1

  def min(rhs: Bound[T]): Bound[T] = if (compare(rhs) < 0) this else rhs
  def max(rhs: Bound[T]): Bound[T] = if (compare(rhs) > 0) this else rhs

  // perform unary ops (correctly handling open/closed, upper/lower)
  def unop(f: T => T): Bound[T]

  // perform binary ops (correctly handling open/closed, upper/lower)
  def binop(rhs: Bound[T])(f: (T, T) => T): Bound[T]

  // TODO: it would be nice to avoid the overhead of unop/binop, but
  // for now the amount of boilerplate required to accomplish that is
  // simply too high. we could probably solve this with a macro...

  // basic Ring operations involving bounds
  def unary_-(implicit ev: Ring[T]) = unop(ev.negate(_))
  def +(rhs: T)(implicit ev: Ring[T]) = unop(ev.plus(_, rhs))
  def -(rhs: T)(implicit ev: Ring[T]) = unop(ev.minus(_, rhs))
  def *(rhs: T)(implicit ev: Ring[T]) = unop(ev.times(_, rhs))
  def +(rhs: Bound[T])(implicit ev: Ring[T]) = binop(rhs)(ev.plus(_, _))
  def -(rhs: Bound[T])(implicit ev: Ring[T]) = binop(rhs)(ev.minus(_, _))
  def *(rhs: Bound[T])(implicit ev: Ring[T]) = binop(rhs)(ev.times(_, _))
  def pow(rhs: Int)(implicit ev: Ring[T]) = unop(ev.pow(_, rhs))

  // basic EuclideanRing operations involving bounds
  def /~(rhs: T)(implicit ev: EuclideanRing[T]) = unop(ev.quot(_, rhs))
  def /~(rhs: Bound[T])(implicit ev: EuclideanRing[T]) = binop(rhs)(ev.quot(_, _))

  // basic Field operations involving bounds
  def /(rhs: T)(implicit ev: Field[T]) = unop(ev.div(_, rhs))
  def /(rhs: Bound[T])(implicit ev: Field[T]) = binop(rhs)(ev.div(_, _))
}

/**
 * Lower represents a Bound which is a lower bound.
 */
sealed trait Lower[@spec(Int, Long, Double) T] extends Bound[T] { def toLower = this }

/**
 * Upper represents a Bound which is an upper bound.
 */
sealed trait Upper[@spec(Int, Long, Double) T] extends Bound[T] { def toUpper = this }

/**
 * Unbound represents a boundary which does not limit its "side" of
 * the interval, i.e. +/- infinity.
 */
sealed trait Unbound[@spec(Int, Long, Double) T] extends Bound[T] {
  def unop(f: T => T): Bound[T] = this
  def binop(rhs: Bound[T])(f: (T, T) => T): Bound[T] = this
}

/**
 * UnboundBelow represents an unrestrainted lower bound (i.e. -inf).
 */
final case class UnboundBelow[@spec(Int, Long, Double) T]()(implicit val order: Order[T]) extends Lower[T] with Unbound[T] {
  def compare(rhs: Bound[T]) = if (rhs == this) 0 else -1
  def comparePt(t: T) = -1
  def toUpper = UnboundAbove[T]()
}

/**
 * UnboundAbove represents an unrestrainted upper bound (i.e. +inf).
 */
final case class UnboundAbove[@spec(Int, Long, Double) T]()(implicit val order: Order[T]) extends Upper[T] with Unbound[T] {
  def compare(rhs: Bound[T]) = if (rhs == this) 0 else 1
  def comparePt(t: T) = 1
  def toLower = UnboundBelow[T]()
}

/**
 * Closed is a closed bound (the interval contains this limit).
 */
sealed trait Closed[@spec(Int, Long, Double) T] {
  implicit def order: Order[T]
  def x: T
  def compare(rhs: Bound[T]) = rhs match {
    case UnboundBelow() => 1
    case UnboundAbove() => -1
    case ClosedBelow(y) => order.compare(x, y)
    case ClosedAbove(y) => order.compare(x, y)
    case OpenBelow(y) => if (order.lteqv(x, y)) -1 else 1
    case OpenAbove(y) => if (order.lt(x, y)) -1 else 1
  }
  def comparePt(t: T) = order.compare(x, t)
  def binop(rhs: Bound[T])(f: (T, T) => T): Bound[T] = rhs match {
    case UnboundBelow() => UnboundBelow()
    case UnboundAbove() => UnboundBelow()
    case OpenBelow(y) => OpenBelow(f(x, y))
    case OpenAbove(y) => OpenBelow(f(x, y))
    case ClosedBelow(y) => ClosedBelow(f(x, y))
    case ClosedAbove(y) => ClosedBelow(f(x, y))
  }
}

/**
 * ClosedBelow is a closed lower bound, e.g. a in the interval [a, b].
 */
final case class ClosedBelow[@spec(Int, Long, Double) T](x: T)(implicit val order: Order[T]) extends Lower[T] with Closed[T] {
  def toUpper = ClosedAbove(x)
  def unop(f: T => T) = ClosedBelow(f(x))
  override def binop(rhs: Bound[T])(f: (T, T) => T): Lower[T] =
    super.binop(rhs)(f).toLower
}

/**
 * ClosedAbove is a closed upper bound, e.g. b in the interval [a, b]
 */
final case class ClosedAbove[@spec(Int, Long, Double) T](x: T)(implicit val order: Order[T]) extends Upper[T] with Closed[T] {
  def toLower = ClosedBelow(x)
  def unop(f: T => T) = ClosedAbove(f(x))
  override def binop(rhs: Bound[T])(f: (T, T) => T): Upper[T] =
    super.binop(rhs)(f).toUpper
}

/**
 * Open is a open bound (the interval does not contain this limit).
 */
sealed trait Open[@spec(Int, Long, Double) T] {
  implicit def order: Order[T]
  def x: T
  def binop(rhs: Bound[T])(f: (T, T) => T): Bound[T] = rhs match {
    case UnboundBelow() => UnboundBelow()
    case UnboundAbove() => UnboundBelow()
    case OpenBelow(y) => OpenBelow(f(x, y))
    case OpenAbove(y) => OpenBelow(f(x, y))
    case ClosedBelow(y) => OpenBelow(f(x, y))
    case ClosedAbove(y) => OpenBelow(f(x, y))
  }
}

/**
 * OpenBelow is an open lower bound, e.g. a in the interval (a, b).
 */
final case class OpenBelow[@spec(Int, Long, Double) T](x: T)(implicit val order: Order[T]) extends Lower[T] with Open[T] {
  def compare(rhs: Bound[T]) = rhs match {
    case UnboundBelow() => 1
    case UnboundAbove() => -1
    case ClosedBelow(y) => comparePt(y)
    case ClosedAbove(y) => comparePt(y)
    case OpenBelow(y) => order.compare(x, y)
    case OpenAbove(y) => comparePt(y)
  }
  def comparePt(t: T) = if (order.lt(x, t)) -1 else 1
  def toUpper = OpenAbove(x)
  def unop(f: T => T) = OpenBelow(f(x))
  override def binop(rhs: Bound[T])(f: (T, T) => T): Lower[T] =
    super.binop(rhs)(f).toLower
}

/**
 * OpenAbove is an open upper bound, e.g. b in the interval (a, b).
 */
final case class OpenAbove[@spec(Int, Long, Double) T](x: T)(implicit val order: Order[T]) extends Upper[T] with Open[T] {
  def compare(rhs: Bound[T]) = rhs match {
    case UnboundBelow() => 1
    case UnboundAbove() => -1
    case ClosedBelow(y) => comparePt(y)
    case ClosedAbove(y) => comparePt(y)
    case OpenBelow(y) => comparePt(y)
    case OpenAbove(y) => order.compare(x, y)
  }
  def comparePt(t: T) = if (order.lteqv(x, t)) -1 else 1
  def toLower = OpenBelow(x)
  def unop(f: T => T) = OpenAbove(f(x))
  override def binop(rhs: Bound[T])(f: (T, T) => T): Upper[T] =
    super.binop(rhs)(f).toUpper
}

/**
 * Interval represents a set of values, usually numbers.
 * 
 * Intervals have upper and lower bounds. Each bound can be one of
 * three kinds:
 * 
 *   * Closed: The boundary value is included in the interval.
 *   * Open: The boundary value is excluded from the interval.
 *   * Unbound: There is no boundary value.
 *
 * When the underlying type of the interval supports it, intervals may
 * be used in arithmetic. There are several possible interpretations
 * of interval arithmetic: the interval can represent uncertainty
 * about a single value (for instance, a quantity +/- tolerance in
 * engineering) or it can represent all values in the interval
 * simultaneously. In this implementation we have chosen to use the
 * probabillistic interpretation.
 *
 * One common pitfall with interval arithmetic is that many familiar
 * algebraic relations do not hold. For instance, given two intervals
 * a and b:
 * 
 *   a == b does not imply a * a == a * b
 *
 * Consider a = b = [-1, 1]. Since any number times itself is
 * non-negative, a * a = [0, 1]. However, a * b = [-1, 1], since we
 * may actually have a=1 and b=-1.
 *
 * These situations will result in loss of precision (in the form of
 * wider intervals). The result is not wrong per se, but less
 * acccurate than it could be.
 */
case class Interval[@spec(Int, Long, Double) T](lower: Lower[T], upper: Upper[T])(implicit order: Order[T]) {

  // used to build new intervals with correct lower/upper bounds
  @inline private[math] final def coerce(a: Bound[T], b: Bound[T]) =
    Interval(a.toLower, b.toUpper)

  def isAbove(t: T) = lower.comparePt(t) > 0
  def isBelow(t: T) = upper.comparePt(t) < 0
  def isAt(t: T) = lower.comparePt(t) == 0 && 0 == upper.comparePt(t)
  def contains(t: T) = lower.comparePt(t) <= 0 && 0 <= upper.comparePt(t)
  def crosses(t: T) = lower.comparePt(t) < 0 && 0 < upper.comparePt(t)

  def mask(rhs: Interval[T]): Interval[T] =
    coerce(lower max rhs.lower, upper min rhs.upper)

  def split(t: T): (Interval[T], Interval[T]) = {
    val below = Interval(UnboundBelow[T], OpenAbove(t))
    val above = Interval(OpenBelow(t), UnboundAbove[T])
    (this mask below, this mask above)
  }

  def splitAtZero(implicit ev: Ring[T]) = split(ev.zero)
  
  def unary_-(implicit ev: Ring[T]) = coerce(-upper, -lower)
  
  def +(rhs: Interval[T])(implicit ev: Ring[T]): Interval[T] =
    coerce(lower + rhs.lower, upper + rhs.upper)
  def +(rhs: T)(implicit ev: Ring[T]): Interval[T] =
    coerce(lower + rhs, upper + rhs)
  
  def -(rhs: Interval[T])(implicit ev: Ring[T]): Interval[T] =
    coerce(lower - rhs.upper, upper - rhs.lower)
  def -(rhs: T)(implicit ev: Ring[T]): Interval[T] =
    coerce(lower - rhs, upper - rhs)
  
  def *(rhs: Interval[T])(implicit ev: Ring[T]): Interval[T] = {
    val lcz = crosses(ev.zero)
    val rcz = rhs.crosses(ev.zero)
  
    val ll = lower * rhs.lower
    val lu = lower * rhs.upper
    val ul = upper * rhs.lower
    val uu = upper * rhs.upper
  
    if (lcz && rcz)
      coerce(lu min ul, ll max uu)
    else if (lcz)
      coerce(ll min lu, ul max uu)
    else if (rcz)
      coerce(ll min ul, lu max uu)
    else if (isBelow(ev.zero) == rhs.isBelow(ev.zero))
      coerce(ll min uu, ll max uu)
    else
      coerce(lu min ul, lu max ul)
  }
  def *(rhs: T)(implicit ev: Ring[T]): Interval[T] = {
    val a = lower * rhs
    val b = upper * rhs
    if (a < b) coerce(a, b) else coerce(b, a)
  }

  def pow(rhs: Int)(implicit ev: Ring[T]): Interval[T] = {
    val a = lower pow rhs
    val b = upper pow rhs
  
    if (contains(ev.zero) && rhs % 2 == 0)
      coerce(ClosedBelow(ev.zero), a max b)
    else if (a < b)
      coerce(a, b)
    else
      coerce(b, a)
  }

  def mapAroundZero[A](f: Interval[T] => A)(implicit ev: Ring[T]): (A, A) =
    splitAtZero match {
      case (a, b) => (f(a), f(b))
    }

  def /~(rhs: Interval[T])(implicit ev: EuclideanRing[T]): Interval[T] = {
    if (rhs.contains(ev.zero))
      throw new java.lang.ArithmeticException("/ by zero")

    val ll = lower /~ rhs.lower
    val lu = lower /~ rhs.upper
    val ul = upper /~ rhs.lower
    val uu = upper /~ rhs.upper

    val bz = rhs.isBelow(ev.zero)

    if (crosses(ev.zero))
      if (bz) coerce(uu, lu) else coerce(ll, ul)
    else if (isAbove(ev.zero))
      if (bz) coerce(uu, ll) else coerce(lu, ul)
    else
      if (bz) coerce(ul, lu) else coerce(ll, uu)
  }
  def /~(rhs: T)(implicit ev: EuclideanRing[T]): Interval[T] = {
    val a = lower /~ rhs
    val b = upper /~ rhs
    if (a < b) coerce(a, b) else coerce(b, a)
  }

  def /(rhs: Interval[T])(implicit ev: Field[T]): Interval[T] = {
    if (rhs.contains(ev.zero))
      throw new java.lang.ArithmeticException("/ by zero")

    val ll = lower / rhs.lower
    val lu = lower / rhs.upper
    val ul = upper / rhs.lower
    val uu = upper / rhs.upper

    val bz = rhs.isBelow(ev.zero)

    if (crosses(ev.zero))
      if (bz) coerce(uu, lu) else coerce(ll, ul)
    else if (isAbove(ev.zero))
      if (bz) coerce(uu, ll) else coerce(lu, ul)
    else
      if (bz) coerce(ul, lu) else coerce(ll, uu)
  }
  def /(rhs: T)(implicit ev: Field[T]): Interval[T] = {
    val a = lower / rhs
    val b = upper / rhs
    if (a < b) coerce(a, b) else coerce(b, a)
  }
}

object Interval {

  /**
   * Interval from a to b. By default is this closed, [a, b].
   */
  def apply[@spec(Int, Long, Double) T: Order](a: T, b: T): Interval[T] =
    closed(a, b)

  /**
   * Closed interval [a, b].
   */
  def closed[@spec(Int, Long, Double) T: Order](a: T, b: T) =
    Interval[T](ClosedBelow[T](a), ClosedAbove[T](b))

  /**
   * Open interval (a, b).
   */
  def open[@spec(Int, Long, Double) T: Order](a: T, b: T) =
    Interval[T](OpenBelow[T](a), OpenAbove[T](b))

  /**
   * An interval containing all T values.
   */
  def unbound[@spec(Int, Long, Double) T: Order] =
    Interval(UnboundBelow(), UnboundAbove())

  /**
   * An interval containing exactly one value (a).
   */
  def point[@spec(Int, Long, Double) T: Order](a: T) =
    Interval(ClosedBelow(a), ClosedAbove(a))

  /**
   * An empty interval.
   * 
   * NOTE: Currently we require a Ring[T] to provide a zero
   * value. This restriction is ugly and non-obvious, and in the
   * future may be unnecessary.
   */
  def empty[@spec(Int, Long, Double) T: Order: Ring] =
    Interval(OpenBelow(Ring[T].zero), OpenAbove(Ring[T].zero))

  /**
   * Interval containing all values above (greater-than) a.
   */
  def above[@spec(Int, Long, Double) T: Order](a: T) =
    Interval(OpenBelow(a), UnboundAbove())

  /**
   * Interval containing all values below (less-than) a.
   */
  def below[@spec(Int, Long, Double) T: Order](a: T) =
    Interval(UnboundBelow(), OpenAbove(a))
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy