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

gapt.grammars.InductionGrammar.scala Maven / Gradle / Ivy

The newest version!
package gapt.grammars

import gapt.expr._
import gapt.expr.formula.fol.{flatSubterms, folTermSize}
import gapt.expr.formula.hol.atoms
import gapt.formats.babel.{BabelExporter, MapBabelSignature, Precedence}
import gapt.grammars.InductionGrammar._
import gapt.proofs.Checkable
import gapt.provers.maxsat.{MaxSATSolver, bestAvailableMaxSatSolver}
import gapt.utils.{Doc, NameGenerator}
import cats.instances.list._
import cats.syntax.traverse._
import gapt.expr.VarOrConst
import gapt.expr.formula.And
import gapt.expr.formula.Atom
import gapt.expr.formula.Formula
import gapt.expr.formula.Or
import gapt.expr.subst.Substitution
import gapt.expr.ty.FunctionType
import gapt.expr.ty.Ty
import gapt.expr.util.constants
import gapt.expr.util.freeVariables
import gapt.expr.util.rename
import gapt.proofs.context.Context

case class InductionGrammar(
    tau: Var,
    alpha: Var,
    nus: Map[Const, NonTerminalVect],
    gamma: List[Var],
    productions: Vector[Production]
) {

  for (prod @ Production(lhs, rhs) <- productions) {
    require(lhs == List(tau) || lhs == gamma, s"$lhs is not a nonterminal")
    val fvs = freeVariables(rhs)
    require(
      nus.values.exists(n => fvs.subsetOf(Set(alpha) ++ gamma ++ n)),
      s"production violates variable condition: $prod"
    )
  }

  def nonTerminals: Vector[NonTerminalVect] =
    Vector(List(tau), List(alpha), gamma) ++ nus.values

  def terminals: Set[Const] =
    nus.keySet ++ constants.nonLogical(productions.flatMap(_.rhs))

  def indTy: Ty =
    alpha.ty

  def size: Int =
    productions.size

  /**
   * Returns the constructor constant Some(cᵢ) if `prod` contains a
   * non-terminal νᵢⱼ, or None if `prod` contains no νᵢⱼ.
   */
  def correspondingCase(prod: Production): Option[Const] = {
    val fvs = freeVariables(prod.rhs)
    if (fvs.subsetOf(Set(alpha) ++ gamma)) None
    else
      nus.find(n => fvs.subsetOf(Set(alpha) ++ gamma ++ n._2)).map(_._1)
  }

  def defaultInstGammas(term: Expr): Map[Expr, VTRATG.NonTerminalVect] = {
    val nameGen = rename.awayFrom(List(tau, alpha) ++ gamma ++ nus.values.flatten)
    flatSubterms(term).filter(_.ty == term.ty).map(s => s -> gamma.map(nameGen.fresh(_))).toMap
  }

  def inst(term: Expr): Instantiation =
    Instantiation(this, term, defaultInstGammas(term))

  def instanceGrammar(term: Expr): VTRATG =
    inst(term).instanceGrammar

  def instanceLanguage(term: Expr): Set[Expr] = {
    val fvs = freeVariables(term)
    if (fvs.isEmpty) inst(term).instanceGrammar.language
    else {
      val nameGen = rename.awayFrom(InductionGrammar.isReplaceable.names(this) ++ containedNames(term))
      val grounding = for (fv <- fvs) yield fv -> nameGen.fresh(Const(fv.name, fv.ty))
      TermReplacement(instanceLanguage(Substitution(grounding)(term)), grounding.map(_.swap).toMap)
    }
  }

  def filterProductions(pred: Production => Boolean): InductionGrammar =
    copy(productions = productions.filter(pred))

  def gammaProductions: Vector[Production] =
    productions.filter(_.lhs == gamma)

  override def toString: String =
    new IndGExporter(unicode = true, this).`export`

}

object InductionGrammar {
  type NonTerminalVect = List[Var]

  case class Production(lhs: NonTerminalVect, rhs: List[Expr]) {
    require(lhs.size == rhs.size, s"sides of production have nonequal size: $this")
    for ((l, r) <- lhs zip rhs) require(l.ty == r.ty)

    def zipped: List[(Var, Expr)] = lhs zip rhs
  }

  object Production {
    def apply(lhs: Var, rhs: Expr): Production = Production(List(lhs), List(rhs))
  }

  implicit object productionReplaceable extends ClosedUnderReplacement[Production] {
    override def replace(prod: Production, p: PartialFunction[Expr, Expr]): Production =
      Production(TermReplacement(prod.lhs, p).map(_.asInstanceOf[Var]), TermReplacement(prod.rhs, p))

    override def names(prod: Production): Set[VarOrConst] =
      containedNames(prod.lhs ++ prod.rhs)
  }

  implicit object isReplaceable extends ClosedUnderReplacement[InductionGrammar] {
    override def replace(g: InductionGrammar, p: PartialFunction[Expr, Expr]): InductionGrammar =
      InductionGrammar(
        tau = TermReplacement(g.tau, p).asInstanceOf[Var],
        alpha = TermReplacement(g.alpha, p).asInstanceOf[Var],
        nus = TermReplacement(g.nus, p).map {
          case (c, nu) =>
            c.asInstanceOf[Const] -> nu.map(_.asInstanceOf[Var])
        },
        gamma = TermReplacement(g.gamma, p).map(_.asInstanceOf[Var]),
        productions = TermReplacement(g.productions, p)
      )

    override def names(g: InductionGrammar): Set[VarOrConst] =
      containedNames(g.nonTerminals) ++ containedNames(g.productions)
  }

  implicit object checkable extends Checkable[InductionGrammar] {
    override def check(g: InductionGrammar)(implicit ctx: Context): Unit = {
      for (nt <- g.nonTerminals; x <- nt) ctx.check(x)
      val Some(ctrs) = ctx.getConstructors(g.indTy): @unchecked
      require(g.nus.keySet == ctrs.toSet)
      for (case ctr @ Const(_, FunctionType(_, argTypes), _) <- ctrs) {
        val nu = g.nus(ctr)
        require(nu.size == argTypes.size)
        for ((nui, argType) <- nu zip argTypes)
          require(nui.ty == argType)
      }
    }
  }

  case class Instantiation(grammar: InductionGrammar, term: Expr, instGammas: Map[Expr, VTRATG.NonTerminalVect]) {
    import grammar._

    val subterms: Set[Expr] = instGammas.keySet

    def instanceProductions(prod: Production): Set[VTRATG.Production] = {
      val containsOnlyAlpha = freeVariables(prod.rhs).subsetOf(Set(grammar.alpha))
      val prodCase = correspondingCase(prod)
      subterms.flatMap {
        case st @ Apps(c: Const, ss) if prodCase.forall(_ == c) =>
          val rhs = Substitution(List(alpha -> term) ++ (nus(c) zip ss) ++
            (gamma zip instGammas(st)))(prod.rhs)
          (prod.lhs: @unchecked) match {
            case List(`tau`)            => List(List(tau) -> rhs)
            case g if containsOnlyAlpha => List(instGammas(st) -> rhs)
            case g if !containsOnlyAlpha =>
              for (si <- ss if si.ty == term.ty) yield instGammas(si) -> rhs
          }
        case _ => Nil
      }
    }

    def instanceGrammar: VTRATG = {
      VTRATG(tau, Seq(List(tau)) ++ instGammas.toVector.sortBy(g => folTermSize(g._1)).map(_._2), productions.view.flatMap(instanceProductions).toSet)
    }
  }

  def defaultNonTerminalNames(
      nameGen: NameGenerator,
      indTy: Ty,
      tauTy: Ty,
      gamma: NonTerminalVect
  )(implicit ctx: Context): InductionGrammar = {
    val tau = nameGen.fresh(Var("τ", tauTy))
    val alpha = nameGen.fresh(Var("α", indTy))
    val nus = Map() ++ ctx.getConstructors(indTy).get.map {
      case ctr @ Const(_, FunctionType(_, argTypes), _) =>
        ctr -> argTypes.map(argTy => nameGen.fresh(Var("ν", argTy)))
    }
    InductionGrammar(tau, alpha, nus, gamma, Vector())
  }
}

private class IndGExporter(unicode: Boolean, g: InductionGrammar)
    extends BabelExporter(unicode, MapBabelSignature(g.terminals)) {
  import Doc._

  def csep(docs: List[Doc]): Doc = wordwrap(docs, ",")

  def showNt(nt: Var): Doc = show(nt, false, Map(), Map())._1.inPrec(0)
  def showNt(nt: List[Var]): Doc = csep(nt.map(showNt))

  def `export`: String = {
    val knownTypes = g.terminals.map { c => c.name -> c }.toMap ++ g.nonTerminals.flatten.map(nt => nt.name -> nt)

    val ntDecl =
      "Start symbol: " <> showNt(g.tau) <> line <>
        "Parameter: " <> showNt(g.alpha) <> line <>
        "Quantifiers: " <> showNt(g.gamma) <> line <>
        "Constructors: " <> csep(g.nus.toList.map {
          case (c, nu) => show(c(nu), true, Map(), knownTypes)._1.inPrec(0)
        })

    val prods = stack(g.productions.toList
      sortBy { case Production(as, ts) => (g.nonTerminals.indexOf(as), ts.toString) }
      map { p =>
        group(csep(p.zipped.map {
          case (a, t) =>
            group(group(show(a, false, Map(), knownTypes)._1.inPrec(Precedence.impl)  "→")  nest(
              show(t, true, Map(), knownTypes)._1.inPrec(Precedence.impl)
            ))
        })) <> line
      })

    group(ntDecl <> line <> line <> prods).render(lineWidth)
  }

}

object stableInductionGrammar {
  def apply(indexedTermset: Map[Expr, Set[Expr]], tau: Var, alpha: Var, nus: Map[Const, NonTerminalVect], gamma: NonTerminalVect): InductionGrammar = {
    val terms = indexedTermset.values.flatten.toSet
    val tauProds = nus.toVector.flatMap {
      case (_, nu) =>
        val stable = stableTerms(terms, Seq(alpha) ++ nu ++ gamma).toList
        stable.map(Production(tau, _))
    }.distinct
    val gammaProds = if (gamma.isEmpty) Vector()
    else {
      val subTerms = terms.flatMap { case Apps(_, as) => flatSubterms(as) }
      nus.toVector.flatMap {
        case (_, nu) =>
          val stable = stableTerms(subTerms, Seq(alpha) ++ nu ++ gamma).toList
          gamma.traverse[List, Expr](g => stable.filter(_.ty == g.ty)).map(Production(gamma, _))
      }.distinct
    }
    InductionGrammar(tau, alpha, nus, gamma, tauProds ++ gammaProds)
  }
}

case class InductionGrammarMinimizationFormula(g: InductionGrammar) {
  def productionIsIncluded(p: Production) = Atom("prodinc", p.lhs ++ p.rhs)

  def coversIndexedTermset(openIndexedTermset: Map[Expr, Set[Expr]]): Formula = {
    val grounding = Substitution {
      val nameGen = rename.awayFrom(containedNames(openIndexedTermset))
      freeVariables(openIndexedTermset.keys).map { case v @ Var(n, t) => v -> nameGen.fresh(Const(n, t)) }
    }
    val indexedTermset = openIndexedTermset.map(grounding(_))
    val cs = Seq.newBuilder[Formula]
    for ((n, ts) <- indexedTermset) {
      val inst = g.inst(n)
      val vtratgMinForm = new VectGrammarMinimizationFormula(inst.instanceGrammar) {
        override def productionIsIncluded(p: VTRATG.Production) = Atom("instprodinc", Seq(n) ++ p._1 ++ p._2)
        override def valueOfNonTerminal(t: Expr, a: Var, rest: Expr) = Atom("instntval", Seq(n, t, a, rest))
      }
      val instanceCovForm = vtratgMinForm.coversLanguage(ts)
      cs += instanceCovForm

      val atomsInInstForm = atoms(instanceCovForm)

      (for (p <- g.productions; instP <- inst.instanceProductions(p)) yield instP -> p).groupBy(_._1).values.foreach { l =>
        val vtratgProdInc = vtratgMinForm.productionIsIncluded(l.head._1)
        if (atomsInInstForm contains vtratgProdInc)
          cs += vtratgProdInc --> Or(l.map(_._2).map(productionIsIncluded))
      }
    }
    And(cs.result())
  }
}

object minimizeInductionGrammar {
  def apply(g: InductionGrammar, indexedTermset: Map[Expr, Set[Expr]], solver: MaxSATSolver = bestAvailableMaxSatSolver, weighting: Production => Int = _ => 1): Option[InductionGrammar] = {
    val formula = InductionGrammarMinimizationFormula(g)
    val hard = formula.coversIndexedTermset(indexedTermset)
    val soft = for (p <- g.productions) yield -formula.productionIsIncluded(p) -> weighting(p)
    solver.solve(hard, soft).map(model =>
      g.filterProductions(p => model(formula.productionIsIncluded(p)))
    )
  }
}

object findMinimalInductionGrammar {
  def apply(indexedTermset: Map[Expr, Set[Expr]], gamma: NonTerminalVect)(implicit ctx: Context): Option[InductionGrammar] =
    apply(indexedTermset, gamma, bestAvailableMaxSatSolver)

  def apply(
      indexedTermset: Map[Expr, Set[Expr]],
      tau: Var,
      alpha: Var,
      nus: Map[Const, NonTerminalVect],
      gamma: NonTerminalVect
  ): Option[InductionGrammar] =
    apply(indexedTermset, tau, alpha, nus, gamma, bestAvailableMaxSatSolver)

  def apply(indexedTermset: Map[Expr, Set[Expr]], gamma: NonTerminalVect, solver: MaxSATSolver)(implicit ctx: Context): Option[InductionGrammar] = {
    val nameGen = rename.awayFrom(containedNames(indexedTermset) ++ gamma)
    val defaultNames = InductionGrammar.defaultNonTerminalNames(
      nameGen,
      indexedTermset.keys.head.ty,
      indexedTermset.values.view.flatten.head.ty,
      gamma
    )
    apply(indexedTermset, defaultNames.tau, defaultNames.alpha, defaultNames.nus, gamma, solver)
  }
  def apply(
      indexedTermset: Map[Expr, Set[Expr]],
      tau: Var,
      alpha: Var,
      nus: Map[Const, NonTerminalVect],
      gamma: NonTerminalVect,
      solver: MaxSATSolver
  ): Option[InductionGrammar] =
    apply(indexedTermset, tau, alpha, nus, gamma, solver, _ => 1)

  def apply(
      indexedTermset: Map[Expr, Set[Expr]],
      tau: Var,
      alpha: Var,
      nus: Map[Const, NonTerminalVect],
      gamma: NonTerminalVect,
      solver: MaxSATSolver,
      weighting: Production => Int
  ): Option[InductionGrammar] = {
    val stable = stableInductionGrammar(indexedTermset, tau, alpha, nus, gamma)
    minimizeInductionGrammar(stable, indexedTermset, solver, weighting)
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy