Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
armanbilge authored and pikinier20 committed Oct 25, 2021
1 parent 2d79eeb commit 8cee6f1
Show file tree
Hide file tree
Showing 7 changed files with 242 additions and 9 deletions.
30 changes: 29 additions & 1 deletion algebra-core/src/main/scala/algebra/instances/bigInt.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ trait BigIntInstances extends cats.kernel.instances.BigIntInstances {
new BigIntAlgebra
}

class BigIntAlgebra extends CommutativeRing[BigInt] with Serializable {
class BigIntAlgebra extends EuclideanRing[BigInt] with Serializable {

val zero: BigInt = BigInt(0)
val one: BigInt = BigInt(1)
Expand All @@ -25,4 +25,32 @@ class BigIntAlgebra extends CommutativeRing[BigInt] with Serializable {

override def fromInt(n: Int): BigInt = BigInt(n)
override def fromBigInt(n: BigInt): BigInt = n

override def lcm(a: BigInt, b: BigInt)(implicit ev: Eq[BigInt]): BigInt =
if (a.signum == 0 || b.signum == 0) zero else (a / a.gcd(b)) * b
override def gcd(a: BigInt, b: BigInt)(implicit ev: Eq[BigInt]): BigInt = a.gcd(b)

def euclideanFunction(a: BigInt): BigInt = a.abs

override def equotmod(a: BigInt, b: BigInt): (BigInt, BigInt) = {
val (qt, rt) = a /% b // truncated quotient and remainder
if (rt.signum >= 0) (qt, rt)
else if (b.signum > 0) (qt - 1, rt + b)
else (qt + 1, rt - b)
}

def equot(a: BigInt, b: BigInt): BigInt = {
val (qt, rt) = a /% b // truncated quotient and remainder
if (rt.signum >= 0) qt
else if (b.signum > 0) qt - 1
else qt + 1
}

def emod(a: BigInt, b: BigInt): BigInt = {
val rt = a % b // truncated remainder
if (rt.signum >= 0) rt
else if (b > 0) rt + b
else rt - b
}

}
22 changes: 22 additions & 0 deletions algebra-core/src/main/scala/algebra/ring/DivisionRing.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package algebra
package ring

import scala.{specialized => sp}

trait DivisionRing[@sp(Byte, Short, Int, Long, Float, Double) A] extends Any with Ring[A] with MultiplicativeGroup[A] {
self =>

def fromDouble(a: Double): A = Field.defaultFromDouble[A](a)(self, self)

}

trait DivisionRingFunctions[F[T] <: DivisionRing[T]] extends RingFunctions[F] with MultiplicativeGroupFunctions[F] {
def fromDouble[@sp(Int, Long, Float, Double) A](n: Double)(implicit ev: F[A]): A =
ev.fromDouble(n)
}

object DivisionRing extends DivisionRingFunctions[DivisionRing] {

@inline final def apply[A](implicit f: DivisionRing[A]): DivisionRing[A] = f

}
55 changes: 55 additions & 0 deletions algebra-core/src/main/scala/algebra/ring/EuclideanRing.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package algebra
package ring

import scala.annotation.tailrec
import scala.{specialized => sp}

/**
* EuclideanRing implements a Euclidean domain.
*
* The formal definition says that every euclidean domain A has (at
* least one) euclidean function f: A -> N (the natural numbers) where:
*
* (for every x and non-zero y) x = yq + r, and r = 0 or f(r) < f(y).
*
* This generalizes the Euclidean division of integers, where f represents
* a measure of length (or absolute value), and the previous equation
* represents finding the quotient and remainder of x and y. So:
*
* quot(x, y) = q
* mod(x, y) = r
*/
trait EuclideanRing[@sp(Int, Long, Float, Double) A] extends Any with GCDRing[A] { self =>
def euclideanFunction(a: A): BigInt
def equot(a: A, b: A): A
def emod(a: A, b: A): A
def equotmod(a: A, b: A): (A, A) = (equot(a, b), emod(a, b))
def gcd(a: A, b: A)(implicit ev: Eq[A]): A =
EuclideanRing.euclid(a, b)(ev, self)
def lcm(a: A, b: A)(implicit ev: Eq[A]): A =
if (isZero(a) || isZero(b)) zero else times(equot(a, gcd(a, b)), b)
}

trait EuclideanRingFunctions[R[T] <: EuclideanRing[T]] extends GCDRingFunctions[R] {
def euclideanFunction[@sp(Int, Long, Float, Double) A](a: A)(implicit ev: R[A]): BigInt =
ev.euclideanFunction(a)
def equot[@sp(Int, Long, Float, Double) A](a: A, b: A)(implicit ev: R[A]): A =
ev.equot(a, b)
def emod[@sp(Int, Long, Float, Double) A](a: A, b: A)(implicit ev: R[A]): A =
ev.emod(a, b)
def equotmod[@sp(Int, Long, Float, Double) A](a: A, b: A)(implicit ev: R[A]): (A, A) =
ev.equotmod(a, b)
}

object EuclideanRing extends EuclideanRingFunctions[EuclideanRing] {

@inline final def apply[A](implicit e: EuclideanRing[A]): EuclideanRing[A] = e

/**
* Simple implementation of Euclid's algorithm for gcd
*/
@tailrec final def euclid[@sp(Int, Long, Float, Double) A: Eq: EuclideanRing](a: A, b: A): A = {
if (EuclideanRing[A].isZero(b)) a else euclid(b, EuclideanRing[A].emod(a, b))
}

}
21 changes: 16 additions & 5 deletions algebra-core/src/main/scala/algebra/ring/Field.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,21 @@ package ring

import scala.{specialized => sp}

trait Field[@sp(Int, Long, Float, Double) A]
extends Any
with CommutativeRing[A]
with MultiplicativeCommutativeGroup[A] { self =>
trait Field[@sp(Int, Long, Float, Double) A] extends Any with EuclideanRing[A] with MultiplicativeCommutativeGroup[A] {
self =>

// default implementations for GCD

override def gcd(a: A, b: A)(implicit eqA: Eq[A]): A =
if (isZero(a) && isZero(b)) zero else one
override def lcm(a: A, b: A)(implicit eqA: Eq[A]): A = times(a, b)

// default implementations for Euclidean division in a field (as every nonzero element is a unit!)

def euclideanFunction(a: A): BigInt = BigInt(0)
def equot(a: A, b: A): A = div(a, b)
def emod(a: A, b: A): A = zero
override def equotmod(a: A, b: A): (A, A) = (div(a, b), zero)

/**
* This is implemented in terms of basic Field ops. However, this is
Expand All @@ -20,7 +31,7 @@ trait Field[@sp(Int, Long, Float, Double) A]

}

trait FieldFunctions[F[T] <: Field[T]] extends RingFunctions[F] with MultiplicativeGroupFunctions[F] {
trait FieldFunctions[F[T] <: Field[T]] extends EuclideanRingFunctions[F] with MultiplicativeGroupFunctions[F] {
def fromDouble[@sp(Int, Long, Float, Double) A](n: Double)(implicit ev: F[A]): A =
ev.fromDouble(n)
}
Expand Down
41 changes: 41 additions & 0 deletions algebra-core/src/main/scala/algebra/ring/GCDRing.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package algebra
package ring

import scala.{specialized => sp}

/**
* GCDRing implements a GCD ring.
*
* For two elements x and y in a GCD ring, we can choose two elements d and m
* such that:
*
* d = gcd(x, y)
* m = lcm(x, y)
*
* d * m = x * y
*
* Additionally, we require:
*
* gcd(0, 0) = 0
* lcm(x, 0) = lcm(0, x) = 0
*
* and commutativity:
*
* gcd(x, y) = gcd(y, x)
* lcm(x, y) = lcm(y, x)
*/
trait GCDRing[@sp(Int, Long, Float, Double) A] extends Any with CommutativeRing[A] {
def gcd(a: A, b: A)(implicit ev: Eq[A]): A
def lcm(a: A, b: A)(implicit ev: Eq[A]): A
}

trait GCDRingFunctions[R[T] <: GCDRing[T]] extends RingFunctions[R] {
def gcd[@sp(Int, Long, Float, Double) A](a: A, b: A)(implicit ev: R[A], eqA: Eq[A]): A =
ev.gcd(a, b)(eqA)
def lcm[@sp(Int, Long, Float, Double) A](a: A, b: A)(implicit ev: R[A], eqA: Eq[A]): A =
ev.lcm(a, b)(eqA)
}

object GCDRing extends GCDRingFunctions[GCDRing] {
@inline final def apply[A](implicit ev: GCDRing[A]): GCDRing[A] = ev
}
76 changes: 76 additions & 0 deletions algebra-laws/shared/src/main/scala/algebra/laws/RingLaws.scala
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,57 @@ trait RingLaws[A] extends GroupLaws[A] { self =>
parents = Seq(ring, commutativeRig, commutativeRng)
)

def gcdRing(implicit A: GCDRing[A]) = RingProperties.fromParent(
name = "gcd domain",
parent = commutativeRing,
"gcd/lcm" -> forAll { (x: A, y: A) =>
val d = A.gcd(x, y)
val m = A.lcm(x, y)
A.times(x, y) ?== A.times(d, m)
},
"gcd is commutative" -> forAll { (x: A, y: A) =>
A.gcd(x, y) ?== A.gcd(y, x)
},
"lcm is commutative" -> forAll { (x: A, y: A) =>
A.lcm(x, y) ?== A.lcm(y, x)
},
"gcd(0, 0)" -> (A.gcd(A.zero, A.zero) ?== A.zero),
"lcm(0, 0) === 0" -> (A.lcm(A.zero, A.zero) ?== A.zero),
"lcm(x, 0) === 0" -> forAll { (x: A) => A.lcm(x, A.zero) ?== A.zero }
)

def euclideanRing(implicit A: EuclideanRing[A]) = RingProperties.fromParent(
name = "euclidean ring",
parent = gcdRing,
"euclidean division rule" -> forAll { (x: A, y: A) =>
pred(y) ==> {
val (q, r) = A.equotmod(x, y)
x ?== A.plus(A.times(y, q), r)
}
},
"equot" -> forAll { (x: A, y: A) =>
pred(y) ==> {
A.equotmod(x, y)._1 ?== A.equot(x, y)
}
},
"emod" -> forAll { (x: A, y: A) =>
pred(y) ==> {
A.equotmod(x, y)._2 ?== A.emod(x, y)
}
},
"euclidean function" -> forAll { (x: A, y: A) =>
pred(y) ==> {
val (_, r) = A.equotmod(x, y)
A.isZero(r) || (A.euclideanFunction(r) < A.euclideanFunction(y))
}
},
"submultiplicative function" -> forAll { (x: A, y: A) =>
(pred(x) && pred(y)) ==> {
A.euclideanFunction(x) <= A.euclideanFunction(A.times(x, y))
}
}
)

// boolean rings

def boolRng(implicit A: BoolRng[A]) = RingProperties.fromParent(
Expand All @@ -231,6 +282,31 @@ trait RingLaws[A] extends GroupLaws[A] { self =>
// zero * x == x * zero hold.
// Luckily, these follow from the other field and group axioms.
def field(implicit A: Field[A]) = new RingProperties(
name = "field",
al = additiveCommutativeGroup,
ml = multiplicativeCommutativeGroup,
parents = Seq(euclideanRing),
"fromDouble" -> forAll { (n: Double) =>
if (Platform.isJvm) {
// TODO: BigDecimal(n) is busted in scalajs, so we skip this test.
val bd = new java.math.BigDecimal(n)
val unscaledValue = new BigInt(bd.unscaledValue)
val expected =
if (bd.scale > 0) {
A.div(A.fromBigInt(unscaledValue), A.fromBigInt(BigInt(10).pow(bd.scale)))
} else {
A.fromBigInt(unscaledValue * BigInt(10).pow(-bd.scale))
}
Field.fromDouble[A](n) ?== expected
} else {
Prop(true)
}
}
)

// Approximate fields such a Float or Double, even through filtered using FPFilter, do not work well with
// Euclidean ring tests
def approxField(implicit A: Field[A]) = new RingProperties(
name = "field",
al = additiveCommutativeGroup,
ml = multiplicativeCommutativeGroup,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,10 @@ class LawTests extends munit.DisciplineSuite {
checkAll("Long", RingLaws[Long].commutativeRing)
checkAll("Long", LatticeLaws[Long].boundedDistributiveLattice)

checkAll("BigInt", RingLaws[BigInt].commutativeRing)
checkAll("BigInt", RingLaws[BigInt].euclideanRing)

checkAll("FPApprox[Float]", RingLaws[FPApprox[Float]].field)
checkAll("FPApprox[Double]", RingLaws[FPApprox[Double]].field)
checkAll("FPApprox[Float]", RingLaws[FPApprox[Float]].approxField)
checkAll("FPApprox[Double]", RingLaws[FPApprox[Double]].approxField)

// let's limit our BigDecimal-related tests to the JVM for now.
if (Platform.isJvm) {
Expand Down

0 comments on commit 8cee6f1

Please sign in to comment.