10. Declarative Modeling Example

Declarative approach to modeling means that one could define or update a model using a fragment of structured text. A number of fragments could be “played” against a model in a sequence to build it up.

Enabling declarative modeling for Capella models enables a range of complex automations around modeling process that are explainable / transparent to human auditors.

This notebook will demonstrate a basic application of this approach to modeling on a coffee machine example. Please note that we will not model any specific modeling process but rather a “free-form” demo.

10.1. System Analysis of a Coffee Machine

Lets do a quick system analysis of a coffee machine. Lets assume that our meta-solution is an automated coffee machine for a household use. We may look into variant management scenario in a separate example.

10.1.1. 0. Initialize

But before we can model something lets first initialize the model. We will use an empty Capella 5.2 model as a starting point.

[1]:
import capellambse
import io
from capellambse import decl

path_to_model = "../../../tests/data/decl/empty_project_52/empty_project_52.aird"
model = capellambse.MelodyModel(path_to_model)
Cannot load PVMT extension: ValueError: Provided model does not have a PropertyValuePkg
Property values are not available in this model

to visualize the modeling results we’ll use context-diagrams extension, you may get one by uncommenting and running the command below

[ ]:
!pip install capellambse_context_diagrams

lets verify that the model is empty at SA layer:

[2]:
functions_allocated = model.sa.root_component.allocated_functions
functions_available = model.sa.root_function.functions
print(f"At SA layer the model has {len(functions_available)}, out of which {len(functions_allocated)} are allocated to Root Component")
At SA layer the model has 0, out of which 0 are allocated to Root Component

Also for this to work we’ll need “coordinates” of some key elements in the model:

[3]:
root_function = model.sa.root_function
root_component = model.sa.root_component
structure = model.sa.component_package

10.1.2. 1. Context

Lets start by renaming the root component from System to Coffee Machine, creating a human actor User and a component exchange between those two.

We can achieve this by applying the following YAML patch to an empty Capella model:

[4]:
model_update = f"""
- parent: !uuid {root_component.uuid}
  modify:
    name: Coffee Machine
- parent: !uuid {root_component.uuid}
  extend:
    ports:
      - name: usr
        direction: INOUT
        promise_id: usr-port-promise
    exchanges:
      - name: user interactions
        source: !promise usr-port-promise
        target: !promise cm-port-promise
- parent: !uuid {structure.uuid}
  extend:
    components:
      - name: User
        is_actor: true
        is_human: true
        ports:
          - name: cm
            direction: INOUT
            promise_id: cm-port-promise
"""
# the below line applies the model_update to the model
decl.apply(model, io.StringIO(model_update))
[4]:
{Promise(identifier='usr-port-promise'): <ComponentPort 'usr' (3a2ff9e0-adcc-4d2d-ae4f-7001a0c25475)>
 .applied_property_value_groups = []
 .applied_property_values = []
 .constraints = []
 .description = ''
 .direction = <FPortDir.INOUT: 3>
 .exchanges = ... # backreference to ComponentExchange - omitted: can be slow to compute
 .filtering_criteria = []
 .name = 'usr'
 .owner = <SystemComponent 'Coffee Machine' (7af5971f-1a6c-47d3-b9a8-4e709444113e)>
 .parent = <SystemComponent 'Coffee Machine' (7af5971f-1a6c-47d3-b9a8-4e709444113e)>
 .progress_status = 'NOT_SET'
 .property_value_groups = []
 .property_values = []
 .requirements = []
 .summary = None
 .traces = []
 .uuid = '3a2ff9e0-adcc-4d2d-ae4f-7001a0c25475'
 .xtype = 'org.polarsys.capella.core.data.fa:ComponentPort',
 Promise(identifier='cm-port-promise'): <ComponentPort 'cm' (99f8db47-4771-4e4e-993a-b252398d8806)>
 .applied_property_value_groups = []
 .applied_property_values = []
 .constraints = []
 .description = ''
 .direction = <FPortDir.INOUT: 3>
 .exchanges = ... # backreference to ComponentExchange - omitted: can be slow to compute
 .filtering_criteria = []
 .name = 'cm'
 .owner = <SystemComponent 'User' (d03d179b-5449-4193-94f8-61d614ea9156)>
 .parent = <SystemComponent 'User' (d03d179b-5449-4193-94f8-61d614ea9156)>
 .progress_status = 'NOT_SET'
 .property_value_groups = []
 .property_values = []
 .requirements = []
 .summary = None
 .traces = []
 .uuid = '99f8db47-4771-4e4e-993a-b252398d8806'
 .xtype = 'org.polarsys.capella.core.data.fa:ComponentPort'}

and now we can verify the changes by visualizing the context of our system under analysis:

[5]:
root_component.context_diagram
[5]:

Please note: the changes we made are not yet stored - if you like those to be saved you may use model.save() method. This will save the model back to where it was loaded from, for example by writing back into local files, or by creating a Git commit and pushing it back to the remote.