it.unich.scalafix.graphs.GraphBody.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scalafix_3 Show documentation
Show all versions of scalafix_3 Show documentation
A Scala library for solving fixpoint equations
/**
* Copyright 2015, 2016, 2017, 2022 Gianluca Amato
*
* This file is part of ScalaFix. ScalaFix is free software: you can
* redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* ScalaFix is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of a MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* ScalaFix. If not, see .
*/
package it.unich.scalafix.graphs
import it.unich.scalafix.*
import it.unich.scalafix.utils.Domain
import it.unich.scalafix.utils.Relation
/**
* The effect of an edge `e` of a graph over an assignment `rho`.
*
* @tparam U
* the type for the unknowns of the assignment `rho`.
* @tparam V
* the type for the values assumed by the unknowns in `rho`.
* @tparam E
* the type for edges.
*/
type EdgeAction[U, V, E] = Assignment[U, V] => E => V
/**
* An body generated by an hyper-graph, i.e., a graph where each edge has
* several source nodes and a single target node. Unknowns are nodes of the
* graph. Given an assignment, each hyper-edge produces a partial value. These
* values are combined with the upper bound operation of the domain `V`.
*
* It is required that if `edgeAction(rho)(e)` depends on `rho(x)`, then `x`
* shoud be in the sources of edge `e`. There are obvious coherence conditions
* among `sources`, `target`, `ingoing` and `outgoing`.
*
* An unknown `x` which has no incoming edges corresponds to an equation `x=x`,
* with no influence from `x` to itself. This is useful for having an unknown
* which keeps its value fixed to that specificied in the initial assignment.
*
* @tparam U
* the type for the unknowns, which are also the nodes of the graph.
* @tparam V
* the type for the values assumed by the unknowns, which is also the type of
* the values generated by each edge trough the `edgeAction` function.
* @tparam E
* type of edges.
*/
trait GraphBody[U, V, E] extends Body[U, V]:
/** Map each edge to its source nodes */
def sources: Relation[E, U]
/** Map each edge to its target node. */
def target: E => U
/** Map each unknown to its outgoing edges. */
def outgoing: Relation[U, E]
/** Map each unknown to its ingoing edges. */
def ingoing: Relation[U, E]
/** Returns the edge action of the graph. */
def edgeAction: EdgeAction[U, V, E]
/**
* Returns the operation used from combining the contributions of different
* edges.
*/
def combiner: (V, V) => V
/**
* Returns a new graph-based body obtained by adding combos to this graph in a
* localized way.
*
* @param combos
* the assignment of combos to unknowns.
* @param unknownOrdering
* an ordering on unknowns used to decide the edges where combos need to be
* applied.
*/
def addLocalizedCombos(
combos: ComboAssignment[U, V],
unknownOrdering: Ordering[U]
): GraphBody[U, V, E]
/**
* Returns a new graph-based body obtained by adding warrowings to this graph
* in a localized way. Localized warrowings require a different procedure than
* standard localized widenings or narrowings. Moreover, it is not entirely
* clear whether this works as intended or not.
*
* @param widenings
* the assignment of widenings to unknowns.
* @param narrowings
* the assignment of narrowings to unknowns.
* @param unknownOrdering
* an ordering on unknowns, used to decide the edges where combos need to be
* applied.
* @param valuesParialOrdering
* a partial ordering on values, used to decide whether widening or
* narrowing should be applied.
*/
def addLocalizedWarrowing(
widenings: ComboAssignment[U, V],
narrowings: ComboAssignment[U, V],
unknownOrdering: Ordering[U]
)(using Domain[V]): Body[U, V]
/**
* Standard implementation of a `GraphBody` where all the data about the graph
* is provided explicitly.
*
* @param sources
* maps each edge to its source unknowns.
* @param target
* each edge to its target unknown.
* @param ingoing
* maps each unknown to the collection of edges departing from it.
* @param outgoing
* maps each unknown to the collection of edges arriving on it.
* @param edgeAction
* the action of and edge over an assignment.
* @param combiner
* rhe operation used from combining the contributions of different edges.
*/
case class SimpleGraphBody[U, V, E](
sources: Relation[E, U],
target: E => U,
outgoing: Relation[U, E],
ingoing: Relation[U, E],
edgeAction: EdgeAction[U, V, E],
combiner: (V, V) => V
) extends GraphBody[U, V, E]:
override def apply(rho: Assignment[U, V]) = (x: U) =>
val in = ingoing(x)
if in.isEmpty
then rho(x)
else
val contributions = for e <- in yield edgeAction(rho)(e)
contributions reduce combiner
override def addLocalizedCombos(
combos: ComboAssignment[U, V],
unknownOrdering: Ordering[U]
): GraphBody[U, V, E] =
val newEdgeAction =
(rho: Assignment[U, V]) =>
(e: E) =>
val x = target(e)
if combos.isDefinedAt(x) && sources(e).exists(unknownOrdering.lteq(x, _))
then combos(x)(rho(x), edgeAction(rho)(e))
else edgeAction(rho)(e)
if combos.combosAreIdempotent
then copy(edgeAction = newEdgeAction)
else
val newSources = (e: E) =>
val x = target(e)
if combos.isDefinedAt(x) && sources(e).exists(unknownOrdering.lteq(x, _))
then sources(e) + x
else sources(e)
val newOutgoing = (u: U) =>
if combos.isDefinedAt(u)
then
val edges = ingoing(u).filter(e => sources(e).exists(unknownOrdering.lteq(u, _)))
outgoing(u) ++ edges
else outgoing(u)
copy(
edgeAction = newEdgeAction,
sources = Relation(newSources),
outgoing = Relation(newOutgoing)
)
override def addLocalizedWarrowing(
widenings: ComboAssignment[U, V],
narrowings: ComboAssignment[U, V],
unknownOrdering: Ordering[U]
)(using Domain[V]): Body[U, V] =
(rho: Assignment[U, V]) =>
(x: U) =>
val in = ingoing(x)
if in.isEmpty then rho(x)
else
val contributions = for e <- in yield
val contrib = edgeAction(rho)(e)
val comboapply = sources(e)
.exists(unknownOrdering.lteq(x, _)) && !(contrib <= rho(x))
(contrib, comboapply)
val result = contributions reduce ((x: (V, Boolean), y: (V, Boolean)) =>
(combiner(x._1, y._1), x._2 || y._2)
)
if result._2 then widenings(x)(rho(x), result._1)
else if result._1 < rho(x) then narrowings(x)(rho(x), result._1)
else result._1
/** Collection of factory methods for graph-based bodies. */
object GraphBody:
/**
* Standard implementation of a `GraphBody` where all the data about the graph
* is provided explicitly.
*
* @see
* [[SimpleGraphBody]] for the meaning of all the parameters.
*/
def apply[U, V, E](
sources: Relation[E, U],
target: E => U,
outgoing: Relation[U, E],
ingoing: Relation[U, E],
edgeAction: EdgeAction[U, V, E],
combiner: (V, V) => V
): GraphBody[U, V, E] =
SimpleGraphBody(sources, target, outgoing, ingoing, edgeAction, combiner)
/**
* Standard implementation of a `GraphBody` where all the data about the graph
* is provided explicitly. The `combiner` parameter is given by the upper bound
* of the domain `V`.
*
* @see
* [[SimpleGraphBody]] for the meaning of all the parameters.
*/
def apply[U, V: Domain, E](
sources: Relation[E, U],
target: E => U,
outgoing: Relation[U, E],
ingoing: Relation[U, E],
edgeAction: EdgeAction[U, V, E]
): GraphBody[U, V, E] =
SimpleGraphBody(sources, target, outgoing, ingoing, edgeAction, summon[Domain[V]].upperBound)
© 2015 - 2025 Weber Informatics LLC | Privacy Policy