Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
82e56f4
Stratified aggregation
guillembartrina Oct 19, 2023
ae94fe6
Add some tests and examples, fix broken tests
guillembartrina Oct 19, 2023
9c84a88
Handle anonymous variables in grouping atoms, add a few examples
guillembartrina Oct 22, 2023
9fab8d6
Simplify Grouping volcano operator, add constants generated by aggreg…
guillembartrina Oct 23, 2023
84cf40f
Remove unused AST parameter
guillembartrina Oct 26, 2023
5a746a5
Built-in constraints
guillembartrina Oct 30, 2023
21668b7
Built-in constraints
guillembartrina Oct 30, 2023
e5e71c5
Merge branch 'builtin-constraints' of github.com:guillembartrina/cara…
guillembartrina Oct 30, 2023
80f636c
Move creation of static operations, finish StagedSnippet
guillembartrina Nov 1, 2023
eb24a37
Built-in constraints
guillembartrina Oct 30, 2023
5f3088c
Move creation of static operations
guillembartrina Nov 1, 2023
2176b5a
Merge branch 'builtin-constraints' of github.com:guillembartrina/cara…
guillembartrina Nov 1, 2023
0f323c5
Optimize negation: avoid combinatorial explosion
guillembartrina Nov 5, 2023
9bb0349
Add synthetic test
guillembartrina Nov 5, 2023
e062d6e
Add bugfix comment
guillembartrina Nov 7, 2023
5a18074
Optimize negation: avoid combinatorial explosion
guillembartrina Nov 5, 2023
43451f4
Add synthetic test
guillembartrina Nov 5, 2023
0edb4a3
Merge branch 'improved-negation' of github.com:guillembartrina/carac …
guillembartrina Nov 7, 2023
dea321d
Quick fix: Prohibit negated variables from being guarded by aggregate…
guillembartrina Nov 12, 2023
61a817b
Merge branch 'main' into improved-negation
guillembartrina Nov 12, 2023
cdbd3ae
Simplify negation suboperations
guillembartrina Nov 12, 2023
9c4313f
Merge branch 'improved-negation' into builtin-constraints
guillembartrina Nov 12, 2023
5d4b52c
Remove metals files
guillembartrina Nov 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Optimize negation: avoid combinatorial explosion
  • Loading branch information
guillembartrina committed Nov 7, 2023
commit 5a1807445cc24cc60e593ca40e27ea4a46c88a39
12 changes: 9 additions & 3 deletions src/main/scala/datalog/execution/BytecodeCompiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,16 @@ class BytecodeCompiler(val storageManager: StorageManager)(using JITOptions) ext
.constantInstruction(rId)
emitSMCall(xb, meth, classOf[Int])

case ComplementOp(arity) =>
case GroundOfOp(cols) =>
xb.aload(0)
.constantInstruction(arity)
emitSMCall(xb, "getComplement", classOf[Int])
emitCols(xb, cols)
emitSMCall(xb, "getGroundOf", classOf[Seq[?]])

case ZeroOutOp(child, cols) =>
xb.aload(0)
traverse(xb, child)
emitSeq(xb, cols.map(v => xxb => emitBoolean(xxb, v)))
emitSMCall(xb, "zeroOut", classOf[EDB], classOf[Seq[?]])

case ScanEDBOp(rId) =>
xb.aload(0)
Expand Down
36 changes: 35 additions & 1 deletion src/main/scala/datalog/execution/BytecodeGenerator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,12 @@ object BytecodeGenerator {
else
xb.constantInstruction(0)

/** Emit `Boolean.valueOf($value)`. */
def emitBoolean(xb: CodeBuilder, value: Boolean): Unit =
xb.constantInstruction(if value then 1 else 0)
.invokestatic(clsDesc(classOf[java.lang.Boolean]), "valueOf",
MethodTypeDesc.of(clsDesc(classOf[java.lang.Boolean]), clsDesc(classOf[Boolean])))

def emitSeqInt(xb: CodeBuilder, value: Seq[Int]): Unit =
emitSeq(xb, value.map(v => xxb => emitInteger(xxb, v)))

Expand Down Expand Up @@ -248,6 +254,17 @@ object BytecodeGenerator {
}
}

def emitEither[A, B](xb: CodeBuilder, either: Either[A, B], emitA: (CodeBuilder, A) => Unit, emitB: (CodeBuilder, B) => Unit): Unit =
either match
case Left(value) =>
emitNew(xb, classOf[Left[A, B]], { xxb =>
emitA(xxb, value)
})
case Right(value) =>
emitNew(xb, classOf[Right[A, B]], { xxb =>
emitB(xxb, value)
})

def emitProjIndexes(xb: CodeBuilder, value: Seq[(String, Constant)]): Unit =
emitSeq(xb, value.map(v => xxb => emitStringConstantTuple2(xxb, v)))

Expand All @@ -268,6 +285,7 @@ object BytecodeGenerator {
def emitCxns(xb: CodeBuilder, value: collection.mutable.Map[String, collection.mutable.Map[Int, Seq[String]]]): Unit =
emitMap(xb, value.toSeq, emitString, emitCxnElement)

/*
def emitJoinIndexes(xb: CodeBuilder, value: JoinIndexes): Unit =
emitNew(xb, classOf[JoinIndexes], xxb =>
emitVarIndexes(xxb, value.varIndexes)
Expand All @@ -277,7 +295,11 @@ object BytecodeGenerator {
// emitArrayAtoms(xxb, value.atoms)
emitSeq(xb, value.atoms.map(a => xxb => emitAtom(xxb, a)))
emitCxns(xxb, value.cxns)
emitBool(xxb, value.edb))
// TODO: Missing negationInfo!
emitBool(xxb, value.edb),
// TODO: Missing groupingInfos!
)
*/

def emitStorageAggOp(xb: CodeBuilder, sao: StorageAggOp): Unit =
val enumCompanionCls = classOf[StorageAggOp.type]
Expand Down Expand Up @@ -315,6 +337,18 @@ object BytecodeGenerator {
emitSeqInt(xxb, value.groupingIndexes)
emitAggOpInfos(xxb, value.aggOpInfos))

def emitCols(xb: CodeBuilder, value: Seq[Either[Constant, Seq[(RelationId, Int)]]]): Unit =
emitSeq(xb, value.map(v => xxb =>
emitEither(xxb, v, emitConstant, (xxxb, s) =>
emitSeq(xxxb, s.map(vv => xxxxb =>
emitNew(xxxxb, classOf[(Int, Int)], xxxxxb =>
emitInteger(xxxxxb, vv._1)
emitInteger(xxxxxb, vv._2)
)
))
)
))

val CD_BoxedUnit = clsDesc(classOf[scala.runtime.BoxedUnit])

/** Emit `BoxedUnit.UNIT`. */
Expand Down
21 changes: 20 additions & 1 deletion src/main/scala/datalog/execution/JoinIndexes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ case class JoinIndexes(varIndexes: Seq[Seq[Int]],
deps: Seq[(PredicateType, RelationId)],
atoms: Seq[Atom],
cxns: mutable.Map[String, mutable.Map[Int, Seq[String]]],
negationInfo: Map[String, Seq[Either[Constant, Seq[(RelationId, Int)]]]],
edb: Boolean = false,
groupingIndexes: Map[String, GroupingJoinIndexes] = Map.empty
) {
Expand All @@ -54,6 +55,7 @@ case class JoinIndexes(varIndexes: Seq[Seq[Int]],
", deps:" + depsToString(ns) +
", edb:" + edb +
", cxn: " + cxnsToString(ns) +
", negation: " + negationToString(ns) +
" }"

def varToString(): String = varIndexes.map(v => v.mkString("$", "==$", "")).mkString("[", ",", "]")
Expand All @@ -66,6 +68,13 @@ case class JoinIndexes(varIndexes: Seq[Seq[Int]],
inCommon.map((count, hashs) =>
count.toString + ": " + hashs.map(h => ns.hashToAtom(h)).mkString("", "|", "")
).mkString("", ", ", "")} }").mkString("[", ",\n", "]")
def negationToString(ns: NS): String =
negationInfo.map((h, infos) =>
s"{ ${ns.hashToAtom(h)} => ${
infos.map{
case Left(value) => value
case Right(value) => s"[ ${value.map((r, c) => s"(${ns(r)}, $c)")} ]"
}} }").mkString("[", ",\n", "]")
val hash: String = atoms.map(a => a.hash).mkString("", "", "")
}

Expand Down Expand Up @@ -137,6 +146,16 @@ object JoinIndexes {
)).to(mutable.Map)
)


val variables2 = body.filterNot(_.negated).flatMap(a => a.terms.zipWithIndex.collect{ case (v: Variable, i) if !v.anon => (v, i) }.map((v, i) => (v, (a.rId, i)))).groupBy(_._1).view.mapValues(_.map(_._2))

val negationInfo = body.filter(_.negated).map(a =>
a.hash -> a.terms.map{
case c: Constant => Left(c)
case v: Variable => Right(if v.anon then Seq() else variables2(v))
}
).toMap

//groupings
val groupingIndexes = precalculatedGroupingIndexes.getOrElse(
body.collect{ case ga: GroupingAtom => ga }.map(ga =>
Expand Down Expand Up @@ -166,7 +185,7 @@ object JoinIndexes {
).toMap
)

new JoinIndexes(bodyVars, constants.to(mutable.Map), projects, deps, rule, cxns, edb = false, groupingIndexes = groupingIndexes)
new JoinIndexes(bodyVars, constants.to(mutable.Map), projects, deps, rule, cxns, negationInfo, edb = false, groupingIndexes = groupingIndexes)
}

// used to approximate poor user-defined order
Expand Down
8 changes: 6 additions & 2 deletions src/main/scala/datalog/execution/LambdaCompiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,12 @@ class LambdaCompiler(val storageManager: StorageManager)(using JITOptions) exten
}
}

case ComplementOp(arity) =>
_.getComplement(arity)
case GroundOfOp(cols) =>
_.getGroundOf(cols)

case ZeroOutOp(child, cols) =>
val clh = compile(child)
sm => sm.zeroOut(clh(sm), cols)

case ScanEDBOp(rId) =>
if (storageManager.edbContains(rId))
Expand Down
8 changes: 1 addition & 7 deletions src/main/scala/datalog/execution/NaiveExecutionEngine.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,11 @@ class NaiveExecutionEngine(val storageManager: StorageManager, stratified: Boole
idbs.getOrElseUpdate(rId, mutable.ArrayBuffer[IndexedSeq[Atom]]()).addOne(rule.toIndexedSeq)
val jIdx = getOperatorKey(rule)
prebuiltOpKeys.getOrElseUpdate(rId, mutable.ArrayBuffer[JoinIndexes]()).addOne(jIdx)
storageManager.addConstantsToDomain(jIdx.constIndexes.values.toSeq)

// We need to add the constants occurring in the grouping predicates of the grouping atoms
rule.collect{ case ga: GroupingAtom => ga}.foreach(ga =>
storageManager.addConstantsToDomain(jIdx.groupingIndexes(ga.hash).constIndexes.values.toSeq)
)
}

def insertEDB(rule: Atom): Unit = {
if (!storageManager.edbContains(rule.rId))
prebuiltOpKeys.getOrElseUpdate(rule.rId, mutable.ArrayBuffer[JoinIndexes]()).addOne(JoinIndexes(IndexedSeq(), mutable.Map(), IndexedSeq(), Seq((PredicateType.POSITIVE, rule.rId)), Seq(rule), mutable.Map.empty, true))
prebuiltOpKeys.getOrElseUpdate(rule.rId, mutable.ArrayBuffer[JoinIndexes]()).addOne(JoinIndexes(IndexedSeq(), mutable.Map(), IndexedSeq(), Seq((PredicateType.POSITIVE, rule.rId)), Seq(rule), mutable.Map.empty, Map.empty, true))
storageManager.insertEDB(rule)
}

Expand Down
9 changes: 7 additions & 2 deletions src/main/scala/datalog/execution/QuoteCompiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class QuoteCompiler(val storageManager: StorageManager)(using JITOptions) extend
${ Expr(x.deps) },
${ Expr(x.atoms) },
${ Expr(x.cxns) },
${ Expr(x.negationInfo) },
${ Expr(x.edb) }
)
}
Expand Down Expand Up @@ -135,8 +136,12 @@ class QuoteCompiler(val storageManager: StorageManager)(using JITOptions) extend
}
}

case ComplementOp(arity) =>
'{ $stagedSM.getComplement(${ Expr(arity) }) }
case GroundOfOp(cols) =>
'{ $stagedSM.getGroundOf(${ Expr(cols) }) }

case ZeroOutOp(child, cols) =>
val clh = compileIRRelOp(child)
'{ $stagedSM.zeroOut($clh, ${ Expr(cols) }) }

case ScanEDBOp(rId) =>
if (storageManager.edbContains(rId))
Expand Down
5 changes: 4 additions & 1 deletion src/main/scala/datalog/execution/StagedExecutionEngine.scala
Original file line number Diff line number Diff line change
Expand Up @@ -349,9 +349,12 @@ class StagedExecutionEngine(val storageManager: StorageManager, val defaultJITOp
case op: ScanEDBOp =>
op.run(storageManager)

case op: ComplementOp =>
case op: GroundOfOp =>
op.run(storageManager)

case op: ZeroOutOp =>
op.run_continuation(storageManager, op.children.map(o => (sm: StorageManager) => jit(o)))

case op: ProjectJoinFilterOp =>
op.run_continuation(storageManager, op.children.map(o => (sm: StorageManager) => jit(o)))

Expand Down
8 changes: 6 additions & 2 deletions src/main/scala/datalog/execution/StagedSnippetCompiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class StagedSnippetCompiler(val storageManager: StorageManager)(using val jitOpt
${ Expr(x.deps) },
${ Expr(x.atoms) },
${ Expr(x.cxns) },
${ Expr(x.negationInfo) },
${ Expr(x.edb) },
) }
}
Expand Down Expand Up @@ -127,8 +128,11 @@ class StagedSnippetCompiler(val storageManager: StorageManager)(using val jitOpt
}
}

case ComplementOp(arity) =>
'{ $stagedSM.getComplement(${ Expr(arity) }) }
case GroundOfOp(cols) =>
'{ $stagedSM.getGroundOf(${ Expr(cols) }) }

case ZeroOutOp(child, cols) =>
'{ $stagedSM.zeroOut($stagedFns.head($stagedSM), ${ Expr(cols) }) }

case ScanEDBOp(rId) =>
if (storageManager.edbContains(rId))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,12 @@ class StagedSnippetExecutionEngine(override val storageManager: StorageManager,
case op: DebugPeek =>
op.run_continuation(storageManager, op.children.map(o => (sm: StorageManager) => jit(o)))

case op: ComplementOp =>
case op: GroundOfOp =>
op.run(storageManager)

case op: ZeroOutOp =>
op.run_continuation(storageManager, op.children.map(o => (sm: StorageManager) => jit(o)))

case _ => throw new Exception(s"Error: interpretRelOp called with unit operation: code=${irTree.code}")
}
}
Expand Down
18 changes: 14 additions & 4 deletions src/main/scala/datalog/execution/ir/IROp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import scala.util.{Failure, Success}
enum OpCode:
case PROGRAM, SWAP_CLEAR, SEQ,
SCAN, SCANEDB, SCAN_DISCOVERED,
COMPLEMENT,
GROUNDOF, ZEROOUT,
SPJ, INSERT, UNION, DIFF,
GROUPING,
DEBUG, DEBUGP, DOWHILE, UPDATE_DISCOVERED,
Expand Down Expand Up @@ -196,16 +196,26 @@ case class InsertOp(rId: RelationId, db: DB, knowledge: KNOWLEDGE, override val
}
}

case class ComplementOp(arity: Int)(using JITOptions) extends IROp[EDB] {
val code: OpCode = OpCode.COMPLEMENT
case class GroundOfOp(cols: Seq[Either[Constant, Seq[(RelationId, Int)]]])(using JITOptions) extends IROp[EDB] {
val code: OpCode = OpCode.GROUNDOF

override def run(storageManager: StorageManager): EDB =
storageManager.getComplement(arity)
storageManager.getGroundOf(cols)

override def run_continuation(storageManager: StorageManager, opFns: Seq[CompiledFn[EDB]]): EDB =
run(storageManager) // bc leaf node, no difference for continuation or run
}

case class ZeroOutOp(child: IROp[EDB], var cols: Seq[Boolean])(using JITOptions) extends IROp[EDB](child) {
val code: OpCode = OpCode.ZEROOUT

override def run(storageManager: StorageManager): EDB =
storageManager.zeroOut(child.run(storageManager), cols)

override def run_continuation(storageManager: StorageManager, opFns: Seq[CompiledFn[EDB]]): EDB =
storageManager.zeroOut(opFns(0)(storageManager), cols)
}

case class ScanOp(rId: RelationId, db: DB, knowledge: KNOWLEDGE)(using JITOptions) extends IROp[EDB] {
val code: OpCode = OpCode.SCAN

Expand Down
20 changes: 14 additions & 6 deletions src/main/scala/datalog/execution/ir/IRTreeGenerator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,13 @@ class IRTreeGenerator(using val ctx: InterpreterContext)(using JITOptions) {
val q = ScanOp(r, DB.Derived, KNOWLEDGE.Known)
typ match
case PredicateType.NEGATED =>
val arity = k.atoms(i + 1).terms.length
val res = DiffOp(ComplementOp(arity), q)
debug(s"found negated relation, rule=", () => s"${ctx.storageManager.printer.ruleToString(k.atoms)}\n\tarity=$arity")
val nis = k.negationInfo(k.atoms(i + 1).hash)
val cols = nis.map(_.exists(_.isEmpty))

val compl = GroundOfOp(nis)
val nq = ZeroOutOp(q, cols)
val res = DiffOp(compl, nq)
debug(s"found negated relation, rule=", () => s"${ctx.storageManager.printer.ruleToString(k.atoms)}")
res
case PredicateType.GROUPING =>
val ga = k.atoms(i + 1).asInstanceOf[GroupingAtom]
Expand Down Expand Up @@ -119,9 +123,13 @@ class IRTreeGenerator(using val ctx: InterpreterContext)(using JITOptions) {
ScanOp(r, DB.Derived, KNOWLEDGE.Known)
typ match
case PredicateType.NEGATED =>
val arity = k.atoms(i + 1).terms.length
val res = DiffOp(ComplementOp(arity), q)
debug(s"found negated relation, rule=", () => s"${ctx.storageManager.printer.ruleToString(k.atoms)}\n\tarity=$arity")
val nis = k.negationInfo(k.atoms(i + 1).hash)
val cols = nis.map(_.exists(_.isEmpty))

val compl = GroundOfOp(nis)
val nq = ZeroOutOp(q, cols)
val res = DiffOp(compl, nq)
debug(s"found negated relation, rule=", () => s"${ctx.storageManager.printer.ruleToString(k.atoms)}")
res
case PredicateType.GROUPING =>
val ga = k.atoms(i + 1).asInstanceOf[GroupingAtom]
Expand Down
Loading