Skip to content

Commit

Permalink
Merge pull request #121 from Chymyst/feature/prepare-0.1.8
Browse files Browse the repository at this point in the history
miscellaneous cleanups in code and documentation
  • Loading branch information
winitzki authored Mar 27, 2017
2 parents d3d3883 + 9ef8792 commit 2b331d0
Show file tree
Hide file tree
Showing 23 changed files with 1,467 additions and 816 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ See also these [talk slides revised for the current syntax](https://github.com/w

#### [Comparison of the chemical machine vs. the coroutines / channels approach (CSP)](docs/chymyst_vs_jc.md#comparison-chemical-machine-vs-csp)

#### [Technical documentation for `Chymyst Core`](docs/chymyst-core.md).
#### [Technical documentation for `Chymyst Core`](docs/chymyst-core.md)

## Example: "dining philosophers"

Expand Down
77 changes: 67 additions & 10 deletions core/src/main/scala/io/chymyst/jc/ConjunctiveNormalForm.scala
Original file line number Diff line number Diff line change
@@ -1,24 +1,81 @@
package io.chymyst.jc

/** Helper functions that perform computations with Boolean formulas while keeping them in the conjunctive normal form.
*
* A Boolean formula in CNF is represented by a list of lists of an arbitrary type `T`.
* For instance, the Boolean formula (a || b) && (c || d || e) is represented as
* {{{ List( List(a, b), List(c, d, e) ) }}}
*
* These helper methods will compute disjunction, conjunction, and negation of Boolean formulas in CNF, outputting the results also in CNF.
* Simplifications are performed only in so far as to remove exact duplicate terms or clauses such as `a || a` or `(a || b) && (a || b)`.
*
* The type `T` represents primitive Boolean terms that cannot be further simplified or factored to CNF.
* These terms could be represented by expression trees or in another way; the CNF computations do not depend on the representation of terms.
*
* Note that negation such as `! a` is considered to be a primitive term.
* Negation of a conjunction or disjunction, such as `! (a || b)`, can be simplified to CNF.
*/
object ConjunctiveNormalForm {
def disjunctionOneTerm[T](a: T, b: List[List[T]]): List[List[T]] = b.map(y => (a :: y).distinct).distinct

def disjunctionOneClause[T](a: List[T], b: List[List[T]]): List[List[T]] = b.map(y => (a ++ y).distinct).distinct
type CNF[T] = List[List[T]]

def disjunction[T](a: List[List[T]], b: List[List[T]]): List[List[T]] = a.flatMap(x => disjunctionOneClause(x, b)).distinct
/** Compute `a || b` where `a` is a single Boolean term and `b` is a Boolean formula in CNF.
*
* @param a Primitive Boolean term that cannot be simplified; does not contain disjunctions or conjunctions.
* @param b A Boolean formula in CNF.
* @tparam T Type of primitive Boolean terms.
* @return The resulting Boolean formula in CNF.
*/
def disjunctionOneTerm[T](a: T, b: CNF[T]): CNF[T] = b.map(y => (a :: y).distinct).distinct

def conjunction[T](a: List[List[T]], b: List[List[T]]): List[List[T]] = (a ++ b).distinct
/** Compute `a || b` where `a` is a single "clause", i.e. a disjunction of primitive Boolean terms, and `b` is a Boolean formula in CNF.
*
* @param a A list of primitive Boolean terms. This list represents a single disjunction clause, e.g. `List(a, b, c)` represents `a || b || c`.
* @param b A Boolean formula in CNF.
* @tparam T Type of primitive Boolean terms.
* @return The resulting Boolean formula in CNF.
*/
def disjunctionOneClause[T](a: List[T], b: CNF[T]): CNF[T] = b.map(y => (a ++ y).distinct).distinct

def negation[T](negationOneTerm: T => T)(a: List[List[T]]): List[List[T]] = a match {
/** Compute `a || b` where `a` and `b` are Boolean formulas in CNF.
*
* @param a A Boolean formula in CNF.
* @param b A Boolean formula in CNF.
* @tparam T Type of primitive Boolean terms.
* @return The resulting Boolean formula in CNF.
*/
def disjunction[T](a: CNF[T], b: CNF[T]): CNF[T] = a.flatMap(x => disjunctionOneClause(x, b)).distinct

/** Compute `a && b` where `a` and `b` are Boolean formulas in CNF.
*
* @param a A Boolean formula in CNF.
* @param b A Boolean formula in CNF.
* @tparam T Type of primitive Boolean terms.
* @return The resulting Boolean formula in CNF.
*/
def conjunction[T](a: CNF[T], b: CNF[T]): CNF[T] = (a ++ b).distinct

/** Compute `! a` where `a` is a Boolean formula in CNF.
*
* @param a A Boolean formula in CNF.
* @param negateOneTerm A function that describes the transformation of a primitive term under negation.
* For instance, `negateOneTerm(a)` should return `! a` in the term's appropriate representation.
* @tparam T Type of primitive Boolean terms.
* @return The resulting Boolean formula in CNF.
*/
def negation[T](negateOneTerm: T => T)(a: CNF[T]): CNF[T] = a match {
case x :: xs =>
val nxs = negation(negationOneTerm)(xs)
x.flatMap(t => disjunctionOneTerm(negationOneTerm(t), nxs))
val nxs = negation(negateOneTerm)(xs)
x.flatMap(t => disjunctionOneTerm(negateOneTerm(t), nxs))
case Nil => List(List()) // negation of true is false
}

def trueConstant[T]: List[List[T]] = List()
/** Represents the constant `true` value in CNF. */
def trueConstant[T]: CNF[T] = List()

def falseConstant[T]: List[List[T]] = List(List())
/** Represents the constant `false` value in CNF. */
def falseConstant[T]: CNF[T] = List(List())

def oneTerm[T](a: T): List[List[T]] = List(List(a))
/** Injects a single primitive term into a CNF. */
def oneTerm[T](a: T): CNF[T] = List(List(a))
}
52 changes: 26 additions & 26 deletions core/src/main/scala/io/chymyst/jc/Core.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,23 @@ package io.chymyst.jc
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.atomic.AtomicLong

//import java.util.function.{Function, BiFunction}

import scala.annotation.tailrec
import scala.collection.mutable
import scala.util.{Left, Right}


/** Syntax helper for zero-argument molecule emitters.
* This trait has a single method, `getUnit`, which returns a value of type `T`, but the only instance will exist if `T` is `Unit` and will return `()`.
*
* @tparam A Type of the molecule value. If this is `Unit`, we will have an implicit value of type `TypeIsUnit[A]`, which will provide extra functionality.
* @tparam A Type of the molecule value. If this is `Unit`, we will have an implicit value of type `TypeIsUnit[A]`, which will define `getUnit` to return `()`.
*/
sealed trait TypeMustBeUnit[A] {
type UnapplyType

def getUnit: A
}

/** Syntax helper for molecules with unit values.
* A value of [[TypeMustBeUnit]]`[A]` is available only for `A == Unit`.
* An implicit value of [[TypeMustBeUnit]]`[A]` is available only if `A == Unit`.
*/
object TypeMustBeUnitValue extends TypeMustBeUnit[Unit] {
override type UnapplyType = Boolean

override def getUnit: Unit = ()
}

Expand Down Expand Up @@ -70,6 +64,12 @@ object Core {
def =!=(other: A): Boolean = self != other
}

/** Compute the difference between sequences, enforcing type equality.
* (The standard `diff` method will allow type mismatch, which has lead to an error.)
*
* @param s Sequence whose elements need to be "subtracted".
* @tparam T Type of sequence elements.
*/
implicit final class SafeSeqDiff[T](val s: Seq[T]) extends AnyVal {
def difff(t: Seq[T]): Seq[T] = s diff t
}
Expand All @@ -78,24 +78,18 @@ object Core {
def difff(t: List[T]): List[T] = s diff t
}

/** Provide `.toScalaSymbol` method for `String` values. */
implicit final class StringToSymbol(val s: String) extends AnyVal {
def toScalaSymbol: scala.Symbol = scala.Symbol(s)
}

/** Type alias for reaction body.
*
*/
/** Type alias for reaction body, which is used often. */
private[jc] type ReactionBody = PartialFunction[ReactionBodyInput, Any]

// for M[T] molecules, the value inside AbsMolValue[T] is of type T; for B[T,R] molecules, the value is of type
// ReplyValue[T,R]. For now, we don't use shapeless to enforce this typing relation.
// private[jc] type MoleculeBag = MutableBag[Molecule, AbsMolValue[_]]
private[jc] type MutableLinearMoleculeBag = mutable.Map[Molecule, AbsMolValue[_]]

/** For each site-wide molecule, this array holds the values of the molecules actually present at the reaction site.
* The `MutableBag` instance may be of different subclass for each molecule. */
private[jc] type MoleculeBagArray = Array[MutableBag[AbsMolValue[_]]]

// private[jc] def moleculeBagToString(mb: MoleculeBag): String = moleculeBagToString(mb.getMap)

private[jc] def moleculeBagToString(mb: Map[Molecule, Map[AbsMolValue[_], Int]]): String =
mb.toSeq
.map { case (mol, vs) => (s"$mol${pipelineSuffix(mol)}", vs) }
Expand All @@ -107,29 +101,35 @@ object Core {
}
}.sorted.mkString(" + ")

private[jc] def moleculeBagToString(inputs: InputMoleculeList): String =
inputs.map {
case (mol, jmv) => s"$mol${pipelineSuffix(mol)}($jmv)"
private[jc] def moleculeBagToString(reaction: Reaction, inputs: InputMoleculeList): String =
inputs.indices.map { i
val jmv = inputs(i)
val mol = reaction.info.inputs(i).molecule
s"$mol${pipelineSuffix(mol)}($jmv)"
}.toSeq.sorted.mkString(" + ")

/** The pipeline suffix is printed only in certain debug messages; the molecule's [[Molecule.name]] does not include that suffix.
* The reason for this choice is that typically many molecules are automatically pipelined,
* so output messages would be unnecessarily encumbered with the `/P` suffix.
*/
private def pipelineSuffix(mol: Molecule): String =
if (mol.isPipelined)
"/P"
else
""

/** Global log of all error and warning messages ever emitted by any reaction site. */
private[jc] val errorLog = new ConcurrentLinkedQueue[String]

private[jc] def reportError(message: String): Unit = {
errorLog.add(message)
()
}

// TODO: simplify InputMoleculeList to an array of AbsMolValue, eliminating the tuple - we already have the input list and it's fixed for each reaction at compile time
/** List of molecules used as inputs by a reaction. */
type InputMoleculeList = Array[(Molecule, AbsMolValue[_])]
/** List of molecules used as inputs by a reaction. The molecules are ordered the same as in the reaction input list. */
type InputMoleculeList = Array[AbsMolValue[_]]

// Type used as argument for ReactionBody.
/** Type used as argument for [[ReactionBody]]. The `Int` value is the index into the [[InputMoleculeList]] array. */
type ReactionBodyInput = (Int, InputMoleculeList)

implicit final class EitherMonad[L, R](val e: Either[L, R]) extends AnyVal {
Expand Down
12 changes: 12 additions & 0 deletions core/src/main/scala/io/chymyst/jc/CrossMoleculeSorting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,16 @@ private[jc] object CrossMoleculeSorting {

}

/** Commands used while searching for molecule values among groups of input molecules that are constrained by cross-molecule guards or conditionals.
* A sequence of these commands (the "SearchDSL program") is computed for each reaction by the reaction site.
* SearchDSL programs are interpreted at run time by [[Reaction.findInputMolecules]].
*/
private[jc] sealed trait SearchDSL

/** Choose a molecule value among the molecules available at the reaction site.
*
* @param i Index of the molecule within the reaction input list (the "input index").
*/
private[jc] final case class ChooseMol(i: Int) extends SearchDSL

/** Impose a guard condition on the molecule values found so far.
Expand All @@ -120,4 +128,8 @@ private[jc] final case class ChooseMol(i: Int) extends SearchDSL
*/
private[jc] final case class ConstrainGuard(i: Int) extends SearchDSL

/** A group of cross-dependent molecules has been closed.
* At this point, we can select one set of molecule values and stop searching for other molecule values within this group.
* If no molecule values are found that satisfy all constraints for this group, the search for the molecule values can be abandoned (the current reaction cannot run).
*/
private[jc] case object CloseGroup extends SearchDSL
Loading

0 comments on commit 2b331d0

Please sign in to comment.