fujaba / org.fujaba.graphengine

This project's aim is to build a graph engine, that is able to build and compare graphs - and to match patterns and apply actions on the graph, to effectively use it as a graph-transformation framework.
BSD 3-Clause "New" or "Revised" License
1 stars 0 forks source link

make attribute conditions more powerful #16

Closed hoechp closed 7 years ago

hoechp commented 7 years ago

attribute conditions must become much more powerful, probably a conjunctive normal form of attribute conditions using some sort of expression library or otherwise sufficiant expressions

see #13

hoechp commented 7 years ago

I found an old but trusty free expression library, that doesn't need to compile code at runtime: jeval.

It's fairly easy to use and performance shouldn't be that bad. Here's example code, where I let it evaluate an expression "#{type} == 'Cargo' && #{species} == 'Wolf' && (#{count} < 5 || #{count} > 1000) && #{existant} == 1.0" for all nodes of my ferryman's problem test graph (where I added some additional integer and boolean attributes). Note: Boolean inside expressions is handled as 0.0 for false and 1.0 for true, but I just mind that while building the evaluator with its variables. An variable is adressed by #{variableName}. I could allow another syntax with true or false for boolean and another kind of adressing of variables - I'd just parse that syntax, converting it to the syntax of the expression-library.

    @Test
    public void testUseExpressionLibraryOnFerrymansGraph() {
        Graph graph = getFerrymansGraph();

        String expression = "#{type} == 'Cargo' && #{species} == 'Wolf' && (#{count} < 5 || #{count} > 1000) && #{existant} == 1.0";

        boolean matched = false;
        for (Node node: graph.getNodes()) {
            Evaluator eval = buildNodeEvaluator(node);
            try {
                matched = "1.0".equals(eval.evaluate(expression));
            } catch (Throwable t) {
                matched = false;
            }
            if (matched) {
                break;
            }
        }
        Assert.assertTrue(matched);
    }

    private Evaluator buildNodeEvaluator(Node node) {
        Evaluator evaluator = new Evaluator();
        for (String key: node.getAttributes().keySet()) {
            if (node.getAttribute(key) instanceof String) {
                evaluator.putVariable(key, "'" + node.getAttribute(key) + "'");
            } else if (node.getAttribute(key) instanceof Boolean) {
                evaluator.putVariable(key, "" + ((boolean)node.getAttribute(key) ? "1.0" : "0.0"));
            } else {
                evaluator.putVariable(key, "" + node.getAttribute(key));
            }
        }
        return evaluator;
    }

    private Graph getFerrymansGraph() {
        Graph ferrymansGraph = new Graph();
        Node wolf = new Node(), goat = new Node(), cabbage = new Node(), ferry = new Node(), north = new Node(), south = new Node();
        ferrymansGraph.addNode(wolf).addNode(goat).addNode(cabbage).addNode(ferry).addNode(north).addNode(south);
        wolf.setAttribute("type", "Cargo").setAttribute("species", "Wolf").setAttribute("count", 1).setAttribute("existant", true).addEdge("eats", goat).addEdge("at", north);
        goat.setAttribute("type", "Cargo").setAttribute("species", "Goat").setAttribute("count", 1).setAttribute("existant", true).addEdge("eats", cabbage).addEdge("at", north);
        cabbage.setAttribute("type", "Cargo").setAttribute("species", "Cabbage").setAttribute("count", 5).setAttribute("existant", true).addEdge("at", north);
        ferry.setAttribute("type", "Ferry").setAttribute("count", 1).setAttribute("existant", true).addEdge("at", north);
        north.setAttribute("type", "Bank").setAttribute("side", "north").setAttribute("count", 1).setAttribute("existant", true).addEdge("opposite", south);
        south.setAttribute("type", "Bank").setAttribute("side", "south").setAttribute("count", 1).setAttribute("existant", true).addEdge("opposite", north);
        return ferrymansGraph;
    }
hoechp commented 7 years ago

Using this library would probably simplify the usage of PatternAttributes so much, that I neither need to have a conjunctive normal form of those PatternAttribute, nor would I need to use those Objects for matching at all. I could just allow an expression inside of the PatternNode to be matched. Additional PatternAttributes would only be used for creation or removal of attributes then.

@eicke123 what do you think, should I go that way - or should I stick to our idea where I'd use nested PatternAttributes in a conjunctive normal form?

Maybe I'll implement my idea in another branch first, to not break the old code too much.

hoechp commented 7 years ago

Okay, I just implemented it the way I though would be best. I didn't need any nested PatternAttributes in conjunctive normal form. Now in each PatternNode, you can specify an attributeMatchExpression, however complex or nested it may be. This expression will be evaluated additionally to the conditions specified inside of the PatternAttributes, which now use the same kind of expression themselves. This way you can use a single expression for the whole node. But if you want to match a certain attribute (e.g. for removal of the attribute), you can specify an expression for matching this attribute inside of the PatternAttribute - in this expression you can even refer to other attributes.

It works well in my first tests and I think it was the right thing to do. It's still contained in another branch named 'expressions', though.