Metaconstraints or "stereotyped" relationships in SysMlv2

In SysML, it was possible to create profiles for domain-specific modelling. As I understand it, the equivalent in SysML v2 is the use of ‘metadata’ to create user-defined keywords, as demonstrated in the video (https://www.youtube.com/watch?v=Ay3-a3NkGF0&t=1323s). In SysML profiles, it was also possible to add meta constraints or stereotyped relationships in Enterprise Architect to control how stereotyped blocks could be connected. For example, you could define two stereotyped blocks and restrict the relationship with a stereotype applied on a aggregation. I need a similar behaviour in SysML v2. Looking at the video at 24:20 of an example usage of user-defined keywords, I want to restrict the user so that “#engine” can only be used in part definitions where #vehicle is applied. Therefore, the owner of an element with #entity must have the keyword #vehicle. Is this possible in sysmlv2?

Currently, the specification only requires that the element annotated by metadata must have a conforming metaclass, e.g. a #meta part def ... requires only meta::annotatedElements to conform to SysML::PartDefinition, see validateMetadataFeatureAnnotatedElement constraint for more details. Because of this, constraints on other metadata types are not diagnosed. However, I see no reasons why this could not be extended to check all attached metadata, not just the metaclass, so we may implement this as an extension in the future.

1 Like

Hi Marco,

While this is not currently supported “out-of-the-box” by Syside Modeler or Editor due to a gap in the specification (as described by Daumantas above), you can use Syside Automator to write your own validation rules in Python. Then, you can also use VSCode’s Tasks (Integrate with External Tools via Tasks) features to run the script and even get the errors shown in the editor itself (underlined red). However, this validation would not work in real-time as you type, but only when running the task.

1 Like

Thank you for the fast response. So my question was targeting more the SysMlv2 Specification in general. But i will take a look in the SysIDE automator, because my goal ist to read then a sysml file applied with metamodel and convert it to a target structure in json. I asked this question to know what I have to validate, and what does the SysMlv2 Specification for me.

It can roughly work like this:

import syside


SRC = """\
package Meta {
    part def Vehicle { attribute a; }
    part def Engine { attribute a; }

    metadata def vehicle :> Metaobjects::SemanticMetadata {
        :>> baseType = Vehicle meta SysML::PartDefinition;
        :> annotatedElement : SysML::PartDefinition;
    }

    metadata def engine :> Metaobjects::SemanticMetadata {
        :>> baseType = Engine meta SysML::PartDefinition;
        :> annotatedElement : SysML::PartDefinition;
        :> annotatedElement : vehicle;
    }

    #vehicle part def V { :>> a; }
    #engine part def E { :>> a; }
}
"""


def validate_annotations(meta: syside.MetadataUsage, lib: syside.Stdlib) -> None:
    targets = meta.annotated_elements
    if not targets:
        # nothing to check
        return
    base_annotated = lib.metadata_annotated_element
    assert base_annotated, "Incomplete standard library!"

    annotated_elements = filter(
        lambda feat: feat.conforms(lib.metadata_annotated_element),
        meta.features.collect(),
    )

    def check_one(
        metadata: list[syside.MetadataFeature | syside.MetadataUsage],
        feat: syside.Feature,
    ) -> syside.Type | None:
        bases: list[syside.Type] = []
        for relation, element in zip(
            feat.heritage.relationships, feat.heritage.elements
        ):
            if isinstance(relation, syside.Redefinition) or relation.is_implied:
                continue

            if element.document.document_tier == syside.DocumentTier.StandardLibrary:
                # skip standard library metaclasses, e.g. `PartDefinition``, these
                # have builtin validation
                continue

            bases.append(element)

        if not bases:
            return None

        for base in bases:
            found = False
            for meta in metadata:
                if meta.conforms(base):
                    found = True
                    break

            if not found:
                return base

        return

    for target in targets:
        metadata = target.metadata.collect()
        for annotated in annotated_elements:
            if not (invalid := check_one(metadata, annotated)):
                continue

            path = syside.decode_path(target.document.url)
            cst = target.cst_node
            line = cst.start_point.line if cst else 1
            char = cst.start_point.character if cst else 1
            print(
                f"{path}:{line}:{char}: [error] Cannot apply {meta.types.at(0)} to {target}: no metadata conforms to {invalid}",
            )


def validate(doc: syside.Document, lib: syside.Stdlib) -> None:
    for meta in doc.nodes(syside.MetadataUsage):
        validate_annotations(meta, lib)


model, _ = syside.load_model(sysml_source=SRC)
with model.user_docs[0].lock() as doc:
    validate(doc, model.lib)

Thank you very much. This is a nice starting point now.