Commit 5b881251 authored by William DeMeo's avatar William DeMeo 💬
Browse files

add kConsistency experimental worksheet

parent 57eae79e
package finder
import Array._
import java.io._
package basic_algebra
import scala.Array._
class CIB(val n: Int, val t: List[List[Int]], val name: String = "A") {
......
// File: Magma.scala
// Author: williamdemeo@gmail.com
// Date: 15 Sep 2020
package basic_algebra
import cats.Eq
import cats.kernel.Eq._
//=============== Magmas ================================
trait Magma[A] {
val name : String = "default name"
val domain: IndexedSeq[A]
def binaryOp: A => A => A
}
case class LiftMagma[A](mA: Magma[A])
(restrictedDomain: IndexedSeq[A]) extends Magma[Option[A]] {
override val name: String = mA.name
override val domain : IndexedSeq[Option[A]] =
mA.domain.map { a =>
if (restrictedDomain.contains(a)) Some(a) else None
}
override def binaryOp: Option[A] => Option[A] => Option[A] =
a => b => (a, b) match {
case (Some(a), Some(b))
if restrictedDomain.contains(a) && restrictedDomain.contains(b) &&
restrictedDomain.contains(mA.binaryOp(a)(b)) => Some(mA.binaryOp(a)(b))
case (_, _) => None
}
}
object Magma {
def apply[A](implicit magma: Magma[A]): Magma[A] = magma
def equality[A](implicit e: Eq[A]): Eq[A] = e
def magmaEquality[A](mA: Magma[A], mB: Magma[A]): Boolean =
mA.domain == mB.domain && mA.binaryOp == mB.binaryOp
implicit def eqOpt[B](implicit eqB: Eq[B]) : Eq[Option[B]] = {
case (Some(b1), Some(b2)) => eqB.eqv(b1, b2)
case (None, _) => true
case (_, None) => true
case _ => false
}
def commutativeLaw[A](x: A, y: A) (m: Magma[A]) (implicit eA: Eq[A]): Boolean =
equality(eA).eqv(m.binaryOp(x)(y), m.binaryOp(y)(x))
def isCommutative[A](mA: Magma[A])(implicit e: Eq[A]): Boolean =
mA.domain.flatMap( i => mA.domain.map( j => (i, j) ) ).
forall( pr => commutativeLaw(pr._1, pr._2)(mA) )
/**
* Return true if f and the binary op are compatible at (x, y).
* @param x arbitrary value from carrier of magma mA
* @param y arbitrary value from carrier of magma mA
* @param f an arbitrary map from carrier type of mA to carrier type of mB
* @param mA an arbitrary domain over type A
* @param mB an arbitrary magma over type B
* @param eB implicit equality of values in codomain
* @tparam A domain magma carrier type
* @tparam B codomain magma carrier type
* @return true if f and the binary op are compatible at (x, y).
*/
def isHomomorphicAt[A, B](x: A, y: A)
(f: A => B)
(mA: Magma[A])
(mB: Magma[B])
(implicit eB: Eq[B]): Boolean =
eqv( f(mA.binaryOp(x)(y)), mB.binaryOp(f(x))(f(y)) )
def isHomomorphic[A, B](f : A => B)
(mA: Magma[A])
(mB: Magma[B])
(implicit eB: Eq[B]): Boolean =
mA.domain.flatMap( i => mA.domain.map( j => (i, j) ) ).
forall( pr => isHomomorphicAt(pr._1, pr._2)(f)(mA)(mB) )
def isPartialHom[A, B](f: A => B)
(mA: Magma[A])
(mB: Magma[B])
(subDom: IndexedSeq[A])
(implicit eB: Eq[B]) : Boolean =
isHomomorphic[Option[A], Option[B]] (liftFunc(f)) (LiftMagma(mA)(subDom)) (LiftMagma(mB)(mB.domain))
def hasPartialHom[A, B](mA: Magma[A])
(mB: Magma[B])
(subDom: IndexedSeq[A])
(implicit eB: Eq[B]): Boolean =
allMaps(mA)(mB).exists(f => isPartialHom(f)(mA)(mB)(subDom))
def partialHoms[A, B](mA: Magma[A])
(mB: Magma[B])
(subDom: IndexedSeq[A])
(implicit eB: Eq[B]): Iterator[A => B] =
allMaps(mA)(mB).filter( f => isPartialHom(f)(mA)(mB)(subDom) )
// combinations with repetitions allowed
def comb[A](as: IndexedSeq[A], k: Int): Iterator[IndexedSeq[A]] =
IndexedSeq.fill(k)(as).flatten.combinations(k)
/**
* The collection of all maps from the domain of A to the domain of B
* @param mA an arbitrary magma over type A
* @param mB an arbitrary magma over type B
* @tparam A type of carrier of magma mA
* @tparam B type of carrier of magma mB
* @return all maps from the domain of A to the domain of B
*/
def allMapRanges[A, B](mA: Magma[A])(mB: Magma[B]): Iterator[Iterator[IndexedSeq[B]]] =
comb(mB.domain, mA.domain.length).map (_.permutations)
def allMaps[A, B](mA: Magma[A])(mB: Magma[B]) : Iterator[A => B] =
allMapRanges(mA)(mB).flatten.map(m => (a: A) => m(mA.domain.indexOf(a)))
def hasHom[A, B](mA: Magma[A])(mB: Magma[B])(implicit eB: Eq[B]): Boolean =
allMaps(mA)(mB).exists(f => isHomomorphic(f)(mA)(mB))
def getHom[A, B](mA: Magma[A])(mB: Magma[B])(implicit eB: Eq[B]): Option[A => B] =
allMaps(mA)(mB).find(f => isHomomorphic(f)(mA)(mB))
def hasHom2[A, B](mA: Magma[A])(mB: Magma[B])(implicit eB: Eq[B]): Boolean =
allMapRanges(mA)(mB).exists(_ exists (m =>
isHomomorphic((a: A) => m(mA.domain.indexOf(a)))(mA)(mB)))
def kConsistentMaps[A, B](k: Int)
(mA: Magma[A])
(mB: Magma[B])
(implicit eB: Eq[B]): Iterator[A => B] =
allMaps(mA)(mB).filter( f => mA.domain.combinations(k).
forall(sd => isPartialHom(f)(mA)(mB)(sd)) )
def updateName[A](mA: Magma[A], newName: String): Magma[A] = new Magma[A] {
override def binaryOp: A => A => A = mA.binaryOp
override val domain: IndexedSeq[A] = mA.domain
override val name: String = newName
}
def liftFuncCod[A, B](f: A => B)(dom: IndexedSeq[A], cod: IndexedSeq[B]): A => Option[B] =
a => if (dom.contains(a) && cod.contains(f(a))) Some(f(a)) else None
def liftFuncRestricted[A, B](f: A => B)
(dom: IndexedSeq[A], cod: IndexedSeq[B]): Option[A] => Option[B] = {
case Some(a) if dom.contains(a) && cod.contains(f(a)) => Some(f(a))
case _ => None
}
def liftFunc[A, B](f: A => B): Option[A] => Option[B] = {
case Some(a) => Some(f(a))
case _ => None
}
}
package algebra
package basic_algebra
abstract class Nat {
def isZero: Boolean
def predecessor: Nat
def successor = new Succ(this)
def successor = Succ(this)
def + (that: Nat): Nat
def - (that: Nat): Nat
def maxnat(that : Nat) : Nat
......@@ -14,23 +14,24 @@ object Zero extends Nat {
def isZero = true
def predecessor = throw new Error("0.predecessor")
// def successor = new Succ(this) (moved to superclass)
def +(that: Nat) = that
def +(that: Nat): Nat = that
def -(that: Nat) = if (that.isZero) this else throw new Error("0-negative")
def maxnat(that : Nat) = that
def maxnat(that : Nat): Nat = that
def minnat(that : Nat) = this
}
case class Succ(n: Nat) extends Nat {
def isZero = false
def predecessor = n
def predecessor: Nat = n
// def successor = new Succ(this) (moved to superclass)
def +(that: Nat) = new Succ(n + that)
def +(that: Nat): Nat = Succ(n + that)
def -(that: Nat) = if (that.isZero) this else n - that.predecessor
def maxnat(that: Nat) = (this, that) match {
case (_, Zero) => this
case (Succ(m), Succ(n)) => m maxnat n
case (_, _) => this
}
def minnat(that: Nat) = if (that.isZero) that else (this.predecessor minnat that.predecessor)
def minnat(that: Nat) = if (that.isZero) that else this.predecessor minnat that.predecessor
}
......@@ -46,7 +47,7 @@ abstract class Fin {
object fzero extends Fin {
def isEmpty = true
def bound = Zero
def +(that: Fin) = that
def +(that: Fin): Fin = that
def *(that: Fin) = this
// def max(that: Fin) = that.bound
}
......@@ -54,7 +55,7 @@ object fzero extends Fin {
case class fsucc (n: Nat) extends Fin {
def isEmpty = false
def bound = n
def +(that: Fin) = new fsucc(this.bound maxnat that.bound)
def *(that: Fin) = new fsucc(this.bound minnat that.bound)
def +(that: Fin) = fsucc(this.bound maxnat that.bound)
def *(that: Fin) = fsucc(this.bound minnat that.bound)
}
// File: Printable.scala
// Author: williamdemeo@gmail.com
// Date: 15 Sep 2020
// Refs: https://www.scalawithcats.com/dist/scala-with-cats.html#exercise-printable-library
// https://www.scalawithcats.com/dist/scala-with-cats.html#contravariant
package basic_algebra
import basic_algebra.algebra_util.{binaryOp, dropDomain, unaryOp}
//============== Formatting Output ==========================
trait Printable[A] { self =>
def format(value: A): String
def contraMap[B](f: B => A): Printable[B] =
(value: B) => self.format(f(value))
}
object PrintableInstances {
def format[A](a: A)(implicit p: Printable[A]): String = p.format(a)
// For testing purposes, here are some instances of Printable for String, Boolean, and Int:
implicit val stringPrintable: Printable[String] = (str: String) => s"'$str'"
implicit val booleanPrintable: Printable[Boolean] = (b: Boolean) => if (b) "true" else "false"
implicit val intPrintable: Printable[Int] = (i: Int) => i.toString
implicit val intListPrintable: Printable[List[Int]] = (l: List[Int]) =>
l.foldLeft("")((b, i) => b + ", " + i.toString)
implicit def pairPrintable[A, B](implicit pa: Printable[A], pb: Printable[B]): Printable[(A, B)] = pr =>
"(" + pa.format(pr._1) + ", " + pb.format(pr._2) + ")"
implicit def listPrintable[A](implicit p: Printable[A]): Printable[List[A]] = (l: List[A]) =>
l.foldRight("")((a, b) => format(a) + ", " + b)
implicit def indexedSeqPrintable[A](implicit p: Printable[A]): Printable[IndexedSeq[A]] =
(l: IndexedSeq[A]) => l.foldRight("")((a, b) => format(a) + ", " + b)
implicit def iteratorPrintable[A](implicit p: Printable[A]): Printable[Iterator[A]] =
(l: Iterator[A]) => l.foldRight("")((a, b) => format(a) + ", " + b)
implicit def iteratorIndexedSeqPrintable[A](implicit p: Printable[A],
pi: Printable[IndexedSeq[A]]): Printable[Iterator[IndexedSeq[A]]] =
(l: Iterator[IndexedSeq[A]]) => l.foldRight("")((ia, b) => format(ia) + ", " + b)
implicit def unaryOpPrintable[A](implicit p: Printable[A]): Printable[unaryOp[A]] = (opA : unaryOp[A]) =>
opA.dom.foldRight("")((a, b) => "(" + format(a) + ", " + format(opA.f(a)) + "), " + b)
implicit def funPrintable[A, B](implicit pb: Printable[B]): Printable[(A => B, IndexedSeq[A])] =
(pr: (A => B, IndexedSeq[A])) => pr._2.foldRight("")((a, b) => pb.format(pr._1(a)) + ", " + b)
implicit def iteratorFunPrintable[A, B]
(implicit fab: Printable[(A => B, IndexedSeq[A])]): Printable[(Iterator[A => B], IndexedSeq[A])] =
(pr: (Iterator[A => B], IndexedSeq[A])) => pr._1.foldRight("")((f, b) => fab.format(f, pr._2) + ", " + b)
implicit def binaryOpPrintable[A](implicit p: Printable[A]): Printable[binaryOp[A]] = (opA : binaryOp[A]) => {
val str1 = opA.dom.foldLeft(" ")((b, i) => b + "." + format(i) + ". ")
val str2 = opA.dom.foldLeft("")((b, i) => b + "\n" + format(i) + ": " +
opA.dom.foldLeft("")((b, j) => b + format(opA.f(i)(j)) + " | "))
str1 + str2
}
implicit def magmaPrintable[A](implicit p: Printable[A]): Printable[Magma[A]] =
(mA: Magma[A]) => "Magma: " + mA.name + "\ndomain: " + format(mA.domain) + "\nCayley table:\n" +
format(binaryOp((a: A) => (b: A) => mA.binaryOp(a)(b), mA.domain))
// Here's another example where we use contramap to define the implicit Printable.
// First, we define a new container class called Box.
final case class Box[A](value: A)
// Then, we create an instance from an existing instance using contramap.
implicit def boxPrintable[A](implicit p: Printable[A]): Printable[Box[A]] =
p.contraMap[Box[A]](_.value) // (pretty cool)
implicit def optionPrintable[A](implicit p: Printable[A]): Printable[Option[A]] =
p.contraMap[Option[A]] {
case Some(a) => a
case None => throw new IllegalArgumentException
}
implicit def optionFunPrintable[A, B](implicit
pf: Printable[(A => B, IndexedSeq[A])],
pb: Printable[B]): Printable[(Option[A => B], IndexedSeq[A])] =
(pr: (Option[A => B], IndexedSeq[A])) =>
pr._2.foldRight("")((a, b) => format(dropDomain(pr._1)(a)) + ", " + b)
// This works as follows:
// format(Box("hello world")) // res: String = 'hello world'
// format(Box(true)) // res: String = yes
// format(Box(123)) // res: String = 123
}
package basic_algebra
import algebra_util._
class Tuma (n: Int, m: Int) {
private val N = Math.pow(2, m).toInt
def cardinality: Int = N
//-- processState(s: String, iters: Int): String ------------------
// INPUT t: String representing an Int in base 2
// iters: Int number of iterations
/** Process state
* @param inputstate
* @param t a String representing an Int in base 2
* @param iters an Int number of iterations
*/
def processState(inputstate: String, t: String, iters: Int): String = {
@scala.annotation.tailrec
def proc_state_aux(state: String, indx: Int): String = {
......@@ -23,15 +22,11 @@ class Tuma (n: Int, m: Int) {
proc_state_aux(inputstate, iters)
}
def state_list(x: Int, y: Int, z: Int): String
= {
def state_list(x: Int, y: Int, z: Int): String =
int2bin((N * (N * x + y)) + z, 3 * m)
}
val t : String = int2bin(n, 8).reverse
def op: (Int, Int, Int) => Int = (x, y, z) =>
Integer.parseInt(processState(state_list(x, y, z), t, m), 2)
}
......@@ -11,81 +11,113 @@ object UACalcAlgebraFactory {
// type alias to point out that util.List is the Java representation of a list
type JavaList[T] = java.util.List[T]
/** makeUACalcOperation
/** UACalcOpFromUnaryFun
* Return a subclass of AbstractOperation with intValueAt() defined using the function fn.
*
* @param fn : the function used to define the intValueAt() method of the class.
* @param op_name : a string name for the class.
* @param arity : the arity of the operation (must be 1 if fn is a list or tuple)
* @param algSize : the size of the universe of the algebra on which this operates.
*
* @example :
* @param fn the function used to define the intValueAt() method of the class.
* @param op_name a string name for the class.
* @param arity the arity of the operation (must be 1 if fn is a list or tuple)
* @param algSize the size of the universe of the algebra on which this operates.
*
* (1) Define a scala function, e.g., addition mod 5.
* ```scala
* def plus_mod5(args: List[Int]): Int = {
* def plus_mod5_aux(xs: List[Int], acc: Int): Int = xs match {
* case Nil => acc % 5
* case y :: ys => plus_mod5_aux(ys, acc + y)
* }
* plus_mod5_aux(args, 0)
* @example
* 1. Define a scala function, e.g., addition mod 5.
* {{{
* def plusMod5(args: List[Int]): Int = {
* def plusMod5_aux(xs: List[Int], acc: Int): Int = xs match {
* case Nil => acc % 5
* case y :: ys => plus_mod5_aux(ys, acc + y)
* }
* ```
* plusMod5_aux(args, 0)
* }
* }}}
* 2. Use the operation defined in 1 to make some UACalcOperations.
* {{{
* val op1: UACalcOperation =
* UACalcOpFromUnaryFun(plusMod5, "binaryPlusMod5", 2, 5)
*
* (2) Use the operation defined in (1) to make some UACalcOperations.
* val op2: UACalcOperation =
* UACalcOpFromUnaryFun(plusMod5, "ternaryPlusMod5", 3, 5)
* }}}
*
* ```scala
* val op1: UACalcOperation = makeUACalcOperation(plus_mod5, "binaryPlusMod5", 2, 5)
* val op2: UACalcOperation = makeUACalcOperation(plus_mod5, "ternaryPlusMod5", 3, 5)
* ```
*
* (3) Check that the op1([4,10]) and op2([4,10, 1]) work as expected.
*
* ```scala
* 3. Check that the op1([4,10]) and op2([4,10, 1]) work as expected.
* {{{
* val ans1: Int = op1.intValueAt(Array(4,10))
* println("4 + 10 mod 5 = " + ans1.toString)
* val ans2: Int = op2.intValueAt(Array(4,10,1))
* println("4 + 10 + 1 mod 5 = " + ans2.toString)
* ```
*
* (4) Make a JavaList of the operations from (2) that you want for the basic operations of your algebra.
*
* ```scala
* val my_ops = seqAsJavaList(List(op1, op2))
* ```
* }}}
*
* (5) Build a UACalcAlgebra.
* 4. Make a JavaList of the operations from 2 that you want for the basic operations of your algebra.
* {{{
* val my_ops = asJava(List(op1, op2))
* }}}
*
* ```scala
* 5. Build a UACalcAlgebra.
* {{{
* val myAlg: BasicAlgebra = new BasicAlgebra("My Alg", 5, my_ops)
* ```
* }}}
*/
def makeUACalcOperationFromScalaFunction( fn : List[Int] => Int,
op_name : String,
arity : Int,
algSize : Int ): UACalcOperation =
def UACalcOpFromFun(fn : List[Int] => Int,
op_name : String,
arity : Int,
algSize : Int ): UACalcOperation =
new AbstractOperation(op_name, arity, algSize) {
override def intValueAt(args: Array[Int]): Int = fn(args.toList)
override def valueAt(list: JavaList[_]): AnyRef = ???
}
def makeUACalcOperationFromScalaList( fn : List[Int],
op_name : String,
arity : Int,
algSize : Int ): UACalcOperation =
/** UACalcOpFromList
* Construct a UACalc operation from a Scala list of ints.
* @param fn list of ints
* @param op_name name of the operation (op symbol)
* @param arity arity of the operation
* @param algSize size of the algebra
* @return new UACalc AbstractOperation
*/
def UACalcOpFromList(fn : List[Int],
op_name : String,
arity : Int,
algSize : Int ): UACalcOperation =
new AbstractOperation(op_name, arity, algSize) {
override def intValueAt(arg: Int): Int = fn(arg)
override def valueAt(list: JavaList[_]): AnyRef = ???
}
def makeUACalcOperationFromScalaTable( fn : Array[Array[Int]],
op_name : String,
arity : Int,
algSize : Int ): UACalcOperation =
/** UACalcOpFromArrayOfArrays
* Construct a UACalc operation from a Scala Array of Arrays of Ints.
* @param fn array of arrays of ints
* @param op_name name of operation
* @param arity arity of operation
* @param algSize size of algebra
* @return UACalcOperation
*/
def UACalcOpFromArrayOfArrays(fn : Array[Array[Int]],
op_name : String,
arity : Int,
algSize : Int ): UACalcOperation =
makeBinaryIntOperation(new OperationSymbol(op_name, arity),algSize,fn)
/**
* Construct a UACalc operation from an integer-valued function of arity |A|
* @param fn arbitrary function of type (A => Int) => Int,
* @param embedding (of A into Int)
* @param op_name name of operation
* @param arity (of operation)
* @param algSize size of the algebra
* @tparam A
* @return AbstractOperation
*/
def UACalcOpFromGeneralFun[A](fn : (A => Int) => Int,
embedding: A => Int,
op_name : String,
arity : Int,
algSize : Int ): UACalcOperation =
new AbstractOperation(op_name, arity, algSize) {
override def intValueAt(args: Array[Int]): Int = fn(ArrayToTuple(args, embedding))
override def valueAt(list: JavaList[_]): AnyRef = ???
}
}
\ No newline at end of file
def ArrayToTuple[A](ar : Array[Int], embedding: A => Int): A => Int =
a => ar(embedding(a))
}
// File: algebra_util.scala
// Author: williamdemeo@gmail.com
// Date: 15 Sep 2020
package basic_algebra
import cats._
import cats.implicits._
object algebra_util {
// Convert Int to String giving the binary representation, w/ optional padding (numPos)
def int2bin(i: Int, numPos: Int): String = {
@scala.annotation.tailrec
......@@ -12,6 +19,9 @@ object algebra_util {
nextpow.toBinaryString.substring(1)
} // This is like IntegerDigits[i,2,numPos] in Mathematica.
// Convert Array of Arrays of Ints into binary operation: Int => Int => Int
def array2op(ar: Array[Array[Int]]): Int => Int => Int = {i => j => ar(i)(j)}
//Convert vals of unary fun `f` to String (for inputs `inseq`)
def fun2str(f: Int => Int, inseq: IndexedSeq[Int]): String =
inseq.foldLeft[String]("")((acc: String, x) => acc + f(x).toString)
......@@ -63,9 +73,44 @@ object algebra_util {
//character at index k, as a String
def strAt(s: String, k: Int): String = s.charAt(k).toString
// ========== Equality =======================
val eqInt: Eq[Int] = Eq[Int]
val eqString: Eq[String] = Eq[String]
//We can use eqInt directly to test for equality:
eqInt.eqv(123, 123) // res1: Boolean = true
eqInt.eqv(123, 231) // res2: Boolean = false
// Unlike Scala's == method, if we try to compare objects of different types using eqv we get a compile error:
//eqInt.eqv(123, "234") // error: type mismatch;
//We can also import the interface syntax in cats.syntax.eq to use the === and =!= methods:
// (moved above)
123 === 123 // res2: Boolean = true
123 =!= 231 // res3: Boolean = true
def getCombineInt(f: (Int, Int) => Int, dom: List[Int]): String =
dom.foldLeft("")((b, i) => b + dom.foldLeft("\n")((b2, j) => b2 + ", " + f(i, j).toString))
def getCombineBool(f: (Boolean, Boolean) => Boolean, dom: List[Boolean]): String =
dom.foldLeft("")((b, i) => b + dom.foldLeft("\n")((b2, j) => b2 + ", " + f(i, j).toString))