Systems-Modeling / SysML-v2-Pilot-Implementation

Proof-of-concept pilot implementation of the SysML v2 textual notation and visualization
GNU Lesser General Public License v3.0
128 stars 24 forks source link

ST6RI-46 Library function implementation and expression evaluation #403

Closed seidewitz closed 2 years ago

seidewitz commented 2 years ago

Note: The previous branch in this repository named ST6RI-46 has been deleted. The branch now named ST6RI-46 is a new branch, with all the same commits as the old branch except the last two.

This pull request revises the implementation of model-level expression evaluation in the org.omg.sysml project.

  1. Refactors ExpressionEvaluator to use the singleton pattern and improves the mechanism for library function implementation.
  2. Revises the evaluation of features. As before, features are evaluated relative to a given target. However, the target may now be a feature chain, in which case feature values are looked up by traversing the chain in reverse order. This provides a nested context for properly handling the redefinition of features.
  3. Adds evaluation of feature chains. A feature chain is evaluated by evaluating the first feature in the chain. The remainder of the chain is then evaluated on each value resulting from that evaluation, with the target of each subchain evaluation being the target for the original chain with a value chained to the end.
  4. Adds implementations for NumericalFunctions::sum and product as primitive library functions.
  5. Adds a Jupyter %eval magic command that evaluates an expression against previously parsed model resources. The command optionally allows a target to be specified, but this must be given as a qualified name and cannot be a feature chain. The specified target can be a package rather than a type, in which case the package content is imported for the evaluation of the expression, but the effective evaluation target is empty.
  6. Adds an EVAL style to the PlantUML-based visualization, which shows the evaluation of feature-value expressions on diagrams.

This PR does not include the implementation of any non-model-level evaluable expressions. The implementation of the evaluation of invocation of user-defined functions (which is not model-level evaluable) is to be handled in ST6RI-599.

Note: This description has been updated for changes made based on comments below.

himi commented 2 years ago

Let me confirm the commit of https://github.com/Systems-Modeling/SysML-v2-Pilot-Implementation/commit/715f0b5aaae77abadc991d760a2ef8df58d5ee90 is not included in this PR. Are you ok with this? I think that means invocations of user-defined functions are dropped.

seidewitz commented 2 years ago

As noted in the PR description:

This PR does not include the implementation of any non-model-level evaluable expressions. The implementation of the evaluation of invocation of user-defined functions (which is not model-level evaluable) is to be handled in ST6RI-599.

Getting user-defined function evaluation right without doing full structural instantiation is proving to be a bit tricky, so I want to get at least the revision to the model-level evaluation done.

himi commented 2 years ago

Sorry, I missed that. I did git reset --hard to cancel the previous ST6RI-46. I'll test it.

himi commented 2 years ago

I'm testing it with CLI interface. First I input: package EvalTest4 { part p2 { attribute a2 = 2; part p21 { attribute a21 = a2 + 2; } } }

And I tried to evaluate EvalTest4::p2.p21.a21. But none of the below worked: %eval EvalTest4::p2.p21.a21 %eval --target EvalTest4::p2 EvalTest4::p2.p21.a21 %eval --target EvalTest4::p2::p21 EvalTest4::p2.p21.a21 How can we successfully evaluate it?

I'm trying to improv the visualizer but I could not deal with such a case, that is, expressions referring to parent's attributes.

FYI, we call evaluate it by manually calling EvaluationUtil.evaluate(a21, null) but I'm puzzled why it works with setting null to the target.

seidewitz commented 2 years ago

This is not working because when you specify a non-null target, the system is trying to find the feature in just that target, and it isn't considering outer scopes. So, when you evaluate with the target EvalTest4::p2::p21, it is not able to get the value of a2 in a2 + 2, because that is defined in the outer scope EvalTest3::p2. If you use a null target, however, the system simply follows the feature reference and checks if it is bound.

Handling nested target contexts, including properly dealing with redefinition, is the tricky bit that I have been addressing for ST6RI-599. Your example works on my local ST6RI-599 branch, but I haven't pushed this update. I started dealing with this to handle chained-feature targets in invocation expressions, but then realized I also had to handle it for evaluating features in general. The latter part also applies for model-level evaluable expressions, so it should really be included here as part of ST6RI-46.

seidewitz commented 2 years ago

Try it now.

himi commented 2 years ago

@seidewitz thank you. I confirmed it worked. I updated the visualizer to properly pick up target in the visualization context. So I think it gives better visualizations. I'll show them in the following comments.

himi commented 2 years ago

With SHOWINHERITED and EVAL,

package EvalTest1 {
    part p1 {
        attribute a1 = 1;
        part p11 {
            attribute a2 = a1 + 1;
        } 
    }

    part p11 :> p1 {
        attribute a2 = a1 + 2;
        part p12 :> p1.p11;
    }

}

gives:

Screen Shot 2022-09-28 at 3 08 38 PM

himi commented 2 years ago

To test inherits and outer scopes,

package EvalTest2 {
    part p1 {
        attribute a1 = 1;
    }
    part p2 {
        attribute a2 = 2;
        part p21 :> p1 {
            attribute a21 = a1 + a2;
        }   
    }
}

gives:

Screen Shot 2022-09-28 at 3 10 00 PM

himi commented 2 years ago

To test the different inherit contexts,

package EvalTest3 {
    part p1 {
        attribute a1;
        attribute a2 = a1 + 3;

    }
    part p11 :> p1 {
        attribute :>> a1 = 11;
    }
    part p12 :> p1 {
        attribute :>> a1 = 12;
    }
}

gives:

Screen Shot 2022-09-28 at 3 11 05 PM

himi commented 2 years ago

To evaluate inherited context with a redefined (bound) outer attribute:

package EvalTest4 {

part p1 {
    attribute a1;
    attribute a2 = 3;
    constraint c11 { attribute b1 = a1 < 2; a1 < 2 }
    constraint c12 { a2 < 5 }
}

part p2 :> p1 {
    attribute :>> a1 = 1;
    attribute :>> a2 = 7;
    constraint :>> c12 { a2 < 5}
}
}

gives: Screen Shot 2022-09-28 at 3 12 12 PM

Actually, this still has an issue to evaluate ConstraintUsage c12. I guess it might be linked with user defined function evaluation.

himi commented 2 years ago

Finally, I tested it with more practical example:

package EvalTest {
    requirement req {
        require constraint { 1< 3 }
    }
    part def Vehicle {
        attribute mass;

        attribute length = 7.2;
        attribute width = 2.8;

        part engine : Engine;

        constraint weightConstraint {
            mass < 1300
        }
        constraint lengthConstraint {
            length < 8.0
        }
    }

    part def Engine {
        attribute mass = 200;
    }

    part vehicle1 : Vehicle {
        attribute :>> mass = 1000 + engine.mass;
        //attribute :>> mass = 1200;
    }

    part vehicle2 : Vehicle {
        attribute :>> mass = 1500 + engine.mass;
        attribute :>> length = 9.0;
    }

    constraint constraintTest {
        vehicle1.mass == 1200
    }

    requirement def WeightRequirement {
        subject v : Vehicle;
        attribute totalMaxMass = 1400;
        require constraint { v.mass < totalMaxMass }
    }

    requirement vehicle1Requirement : WeightRequirement {
        subject :>> v = vehicle1;
    }

    requirement vehicle2Requirement : WeightRequirement {
        subject :>> v = vehicle2;
    }
}

gives:

Screen Shot 2022-09-28 at 3 14 18 PM

This also has issues. The first one is the same with EvalTest4, it cannot evaluate weightConstraint. The other issue is not related to this PR. But typeOf relationships from vehicle1.engine to Engine are redundant. I guess it's related to inheritance. This should be fixed in ST6RI-601. I'll make a PR after this PR is merged.

seidewitz commented 2 years ago

@himi I did some clean up and refactoring on the ModelLevelExpressionEvaluator code. I made sure that all your visualizer evaluation tests still worked. I also took your EvalTest1 through EvalTest4 models and adapted them for use in a Junit ExpressionEvaluationTest that is now part of the build.

The difficulty with evaluating the constraints is that when you use a syntax like constraint c12 { a2 < 5 }, the result expression is connected to the result parameter just by a binding connector, not by a feature value, and the evaluation algorithm on this branch does not handle this (I presume you are doing something special to evaluate this expression). This will work better after ST6RI-599 (since a constraint is a predicate, which is really a kind of user-defined function).

For the Junit test, I used the following adaption of EvalTest4:

package EvalTest4 {
    part p1 {
        attribute a1;
        attribute a2 = 3;
        constraint c11 { return result = a1 < 2; }
        constraint c12 { return result = a2 < 5; }
    }

    part p2 :> p1 {
        attribute :>> a1 = 1;
        attribute :>> a2 = 7;
    }
}

and tested on, e.g., p1.c11.result. But your visualization doesn't seem to show return result parameters on constraints.

himi commented 2 years ago

I had wrongly filtered ReturnParameterMembership because it's automatically attached calculations, which are frequently used in SSRs. I changed to filter empty result only.

himi commented 2 years ago

This seems to work:

Screen Shot 2022-09-29 at 10 00 27 AM