from typing import Any, List, Optional, Tuple, Union
from .dgvertexmapper import DGVertexMapper
from .derivation import Derivations
from .graphprinter import GraphPrinter
from .exceptions import *
from .labelrelation import LabelRelation
from .labeltype import LabelType
from .labelsettings import LabelSettings
from .post import summaryRaw
from .rule import Rule
from .graph import Graph, VertexFilter
from .dgstrat import DGStrat, GraphState
from .dgprintdata import DGPrintData
from .core import TSObject
_class_ref_DG = TSObject.get_class_ref("DG")
_class_ref_DGBuilder = TSObject.get_class_ref("DGBuilder")
_class_ref_DGVertex = TSObject.get_class_ref("DGVertex")
_class_ref_DGHyperEdge = TSObject.get_class_ref("DGHyperEdge")
# noinspection PyPep8Naming
[docs]
class DG(TSObject):
def __init__(self, labelSettings=LabelSettings(LabelType.String, LabelRelation.Isomorphism),
graphDatabase: List[Graph] = []) -> None:
if any(x is None for x in graphDatabase):
raise LogicError("Null pointer in graph database.")
for i, g1 in enumerate(graphDatabase):
for j, g2 in enumerate(graphDatabase):
if i != j and g1.canonKey == g2.canonKey:
raise LogicError("Isomorphic graphs '%s' and '%s' in initial graph database." % (g1.name, g2.name))
super().__init__(_class_ref_DG, labelSettings._ref, [g._ref for g in graphDatabase])
@property
def id(self) -> int:
return int(self._ref.getId())
@property
def labelSettings(self) -> LabelSettings:
return LabelSettings.wrap(self._ref.getLabelSettings())
@property
def graphDatabase(self) -> List[Graph]:
return [Graph.wrap(g) for g in self._ref.getGraphDatabase()]
@property
def createdGraphs(self) -> List[Graph]:
return [Graph.wrap(g) for g in self._ref.getCreatedGraphs()]
@property
def locked(self) -> bool:
return self._ref.isLocked()
@property
def hasActiveBuilder(self) -> bool:
return self._ref.hasActiveBuilder()
@property
def numVertices(self) -> int:
return TSObject.wrap_exception(lambda: int(self._ref.getNumVertices()))
@property
def vertices(self) -> List["DG.Vertex"]:
return TSObject.wrap_exception(lambda: [DG.Vertex.wrap(v) for v in self._ref.getVertices()])
@property
def numEdges(self) -> int:
return TSObject.wrap_exception(lambda: int(self._ref.getNumEdges()))
@property
def edges(self) -> List["DG.HyperEdge"]:
return TSObject.wrap_exception(lambda: [DG.HyperEdge.wrap(e) for e in self._ref.getEdges()])
[docs]
def findVertex(self, g: Graph) -> "DG.Vertex":
return DG.Vertex.wrap(self._ref.findVertex(g._ref))
[docs]
def findEdge(self, sources: List[Union[Graph, "DG.Vertex"]],
targets: List[Union[Graph, "DG.Vertex"]]) -> "DG.HyperEdge":
return DG.HyperEdge.wrap(self._ref.findEdge([x._ref for x in sources], [x._ref for x in targets]))
[docs]
def build(self) -> "DG.Builder":
return TSObject.wrap_exception(lambda: DG.Builder.wrap(self._ref.build()))
[docs]
def print(
self,
printer: Optional[GraphPrinter] = None,
data: Optional[DGPrintData] = None,
):
"""
Emit a static SVG visualization of the derivation graph.
Every DG vertex renders as an oval-framed molecule depiction (produced
by the shared `GraphSVGDrawer`, in the mode / collapse-H setting picked
up from `printer`) with the graph's name captioned inside the oval.
Every hyperedge with a single source and a single target collapses to a
direct arrow labeled with `e{id}`; every multi-educt or multi-product
hyperedge gets a small labeled square "junction" node with arrows from
the educts into the junction and from the junction to the products.
Node positions come from a force-directed layout so junctions settle
between their attached molecules; each oval's dimensions match the
aspect ratio of its molecule SVG's viewBox so nothing gets stretched.
Everything is one inline SVG, so the dynamic light/dark theme follows
the outer page's `[data-bs-theme]` via CSS without needing any
JavaScript.
"""
printer_ref = printer._ref if printer is not None else None
svg = TSObject.wrap_exception(
lambda: self._ref.getSVG(printer_ref)
)
title = f"DG {self.id} ({self.numVertices} vertices, {self.numEdges} hyperedges)"
summaryRaw(f"""<div class="card mb-4" style="max-width: 1200px; margin: auto">
<div class="d-flex m-2">
<h5 class="card-title flex-grow-1 mb-0">{title}</h5>
</div>
<div class="m-2">
{svg}
</div>
</div>""")
def __str__(self):
return self._ref.toString()
def __repr__(self):
return self._ref.toString()
# noinspection PyPep8Naming
[docs]
class Vertex(TSObject):
def __init__(self) -> None:
super().__init__(_class_ref_DGVertex, None, -1, None)
[docs]
def isNull(self) -> bool:
return self._ref.isNull()
def __bool__(self):
return not self._ref.isNull()
@property
def id(self) -> int:
return TSObject.wrap_exception(lambda: int(self._ref.getId()))
@property
def dg(self) -> "DG":
return TSObject.wrap_exception(lambda: DG.wrap(self._ref.getDG()))
@property
def inDegree(self) -> int:
return TSObject.wrap_exception(lambda: int(self._ref.getInDegree()))
@property
def inEdges(self) -> List["DG.HyperEdge"]:
return TSObject.wrap_exception(lambda: [DG.HyperEdge.wrap(e) for e in self._ref.getInEdges()])
@property
def outDegree(self) -> int:
return TSObject.wrap_exception(lambda: int(self._ref.getOutDegree()))
@property
def outEdges(self) -> List["DG.HyperEdge"]:
return TSObject.wrap_exception(lambda: [DG.HyperEdge.wrap(e) for e in self._ref.getOutEdges()])
@property
def graph(self) -> Graph:
return TSObject.wrap_exception(lambda: Graph.wrap(self._ref.getGraph()))
# noinspection PyPep8Naming
[docs]
class HyperEdge(TSObject):
def __init__(self) -> None:
super().__init__(_class_ref_DGHyperEdge, None, -1, [], [], [])
def __str__(self):
return self._ref.toString()
def __repr__(self):
return self._ref.toString()
[docs]
def isNull(self) -> bool:
return self._ref.isNull()
def __bool__(self):
return not self._ref.isNull()
@property
def id(self) -> int:
return TSObject.wrap_exception(lambda: int(self._ref.getId()))
@property
def dg(self) -> "DG":
return TSObject.wrap_exception(lambda: DG.wrap(self._ref.getDG()))
@property
def numSources(self) -> int:
return TSObject.wrap_exception(lambda: int(self._ref.getNumSources()))
@property
def sources(self) -> List["DG.Vertex"]:
return TSObject.wrap_exception(lambda: [DG.Vertex.wrap(v) for v in self._ref.getSources()])
@property
def numTargets(self) -> int:
return TSObject.wrap_exception(lambda: int(self._ref.getNumTargets()))
@property
def targets(self) -> List["DG.Vertex"]:
return TSObject.wrap_exception(lambda: [DG.Vertex.wrap(v) for v in self._ref.getTargets()])
@property
def rules(self) -> List[Rule]:
return TSObject.wrap_exception(lambda: [Rule.wrap(r) for r in self._ref.getRules()])
@property
def inverse(self) -> "DG.HyperEdge":
return TSObject.wrap_exception(lambda: DG.HyperEdge.wrap(self._ref.getInverse()))
[docs]
def vertexMaps(self, limit: int = 1, vertex_filter: Optional[VertexFilter] = None):
return DGVertexMapper(self, limit, vertex_filter)
def __eq__(self, other):
if not isinstance(other, DG.HyperEdge):
return NotImplemented
return self._ref.equals(other._ref)[0]
[docs]
def equals(self, other: "DG.HyperEdge") -> Tuple[bool, bool]:
return self._ref.equals(other._ref)
[docs]
def print(self, printer: Optional[GraphPrinter] = None):
svgs: List[str] = TSObject.wrap_exception(lambda: self._ref.getSVG())
title = f"{{{', '.join(s.graph.name for s in self.sources)}}} -> {{{', '.join(t.graph.name for t in self.targets)}}}"
for svg in svgs:
summaryRaw(f"""<div class="card mb-4" style="max-width: 800px; margin: auto">
<div class="d-flex m-2">
<h5 class="card-title flex-grow-1 mb-0">Derivation {title}</h5>
</div>
{svg}
</div>""")
# noinspection PyPep8Naming
[docs]
class Builder(TSObject):
def __init__(self, dg: "DG") -> None:
super().__init__(_class_ref_DGBuilder, dg._ref)
@property
def dg(self) -> "DG":
return self._ref.getDG()
@property
def isActive(self) -> bool:
return self._ref.isActive()
def __del__(self):
self._ref.lock()
def __enter__(self):
return self
def __exit__(self, *_: Any) -> None:
self._ref.lock()
[docs]
def execute(self, strategy: DGStrat, verbosity: int = 2, ignoreRuleLabelTypes=False) -> "ExecuteResult":
"""
:param strategy: The strategy to execute
:param verbosity: The verbosity defaults to level 2. The levels have the following meaning:
* 0 (or less): no information is printed.
* 2: Repetition strategies print information for each round.
* 4: All strategies print minimal information.
* 6: Derivation predicate strategies and filtering strategies also print their predicates.
* 8: Rule strategies print minimal information about graph binding.
* 10: Rule strategies print more information about graph binding, including failure due to derivation predicates.
* 50: Print information about morphism generation for rule composition.
* 60: Print rule composition information.
:param ignoreRuleLabelTypes: Whether rules in the strategy should be checked beforehand for whether they
have an associated LabelType which matches the one in the underlying derivation graph.
"""
return GraphState.wrap(self._ref.execute(strategy._ref, verbosity, ignoreRuleLabelTypes))
[docs]
def apply(self, graphs: List[Graph], r: Rule, onlyProper=True, verbosity=0) -> List["DG.HyperEdge"]:
return [DG.HyperEdge.wrap(e) for e in
self._ref.apply([g._ref for g in graphs], r._ref, onlyProper, verbosity)]
[docs]
def addDerivation(self, d: Derivations) -> "DG.HyperEdge":
return DG.HyperEdge.wrap(self._ref.addDerivation(d._ref))
[docs]
class ExecuteResult:
def __init__(self, state: GraphState):
self._state = state
[docs]
def subset(self) -> List[Graph]:
return self._state.subset
[docs]
def universe(self) -> List[Graph]:
return self._state.universe